{"version":1,"id":"gradual-card","framework":"next","kind":"block","files":[{"path":"src/blocks/gfa/gradual-card.tsx","content":"import { GradualCardProtectedImage } from \"@/blocks/gfa/gradual-card-protected-image\";\nimport { GradualCardTvNoise } from \"@/blocks/gfa/gradual-card-tv-noise\";\nimport { TwitterGoldVerifiedBadge } from \"@/blocks/gfa/twitter-gold-verified-badge\";\nimport { cn } from \"@/lib/utils\";\n\nconst BOTTOM_FADE_MASK =\n  \"linear-gradient(to top, black 0%, black 14%, rgba(0,0,0,0.35) 40%, transparent 100%)\";\n\nconst DEFAULT_IMAGE = \"/media/gradual-card/gfa-gradual-card-portrait.png\";\n\nexport type GradualCardProps = {\n  className?: string;\n  title: string;\n  /** X tarzı altın verified rozeti */\n  goldVerified?: boolean;\n  /** `public` altındaki yol veya tam URL */\n  imageSrc?: string;\n  imageAlt?: string;\n};\n\n/** Görsel yüklenene kadar arka plan */\nconst CARD_FILL = \"#0D0B61\";\n\n/**\n * Kare kart; başlık fotoğrafın üzerinde, alttaki gradual blur bölgesinde (ayrı header şeridi yok).\n */\nexport function GradualCard({\n  className,\n  title,\n  goldVerified = false,\n  imageSrc = DEFAULT_IMAGE,\n  imageAlt = \"Siyah beyaz portre, araç içinden\",\n}: GradualCardProps) {\n  const titleId = \"gradual-card-title\";\n\n  return (\n    <article\n      data-gfa-gradual-card=\"\"\n      aria-labelledby={titleId}\n      className={cn(\n        \"mx-auto w-full max-w-[min(88vw,220px)] overflow-hidden rounded-2xl sm:max-w-[240px]\",\n        className,\n      )}\n    >\n      <div className=\"relative isolate aspect-square w-full\" style={{ backgroundColor: CARD_FILL }}>\n        <GradualCardProtectedImage\n          src={imageSrc}\n          alt={imageAlt}\n          fill\n          className=\"z-0\"\n          sizes=\"(max-width: 640px) 88vw, 240px\"\n          priority\n        />\n\n        {/* Kademeli “liquid glass” — maske ile alttan buzlanan cam (iOS vibrancy hissi) */}\n        <div\n          className=\"pointer-events-none absolute inset-x-0 bottom-0 top-[28%] z-[1]\"\n          style={{\n            maskImage: BOTTOM_FADE_MASK,\n            WebkitMaskImage: BOTTOM_FADE_MASK,\n          }}\n          aria-hidden\n        >\n          {/* Translüsen buz tint + ince üst parlak çizgi */}\n          <div className=\"absolute inset-0 bg-gradient-to-t from-white/[0.26] via-white/[0.09] to-transparent shadow-[inset_0_1px_0_rgba(255,255,255,0.28)]\" />\n          <div\n            className=\"absolute inset-0 backdrop-blur-xl backdrop-saturate-[1.75] sm:backdrop-blur-2xl\"\n            style={{\n              maskImage: BOTTOM_FADE_MASK,\n              WebkitMaskImage: BOTTOM_FADE_MASK,\n            }}\n          />\n        </div>\n\n        <GradualCardTvNoise />\n\n        {/* Başlık: gradual alanın içinde, gürültünün üstünde */}\n        <div className=\"pointer-events-none absolute inset-x-0 bottom-0 z-[3] flex flex-col justify-end px-4 pb-4 pt-14 sm:px-5 sm:pb-5\">\n          <div className=\"flex flex-wrap items-center gap-x-1.5 gap-y-0.5\">\n            <h2\n              id={titleId}\n              className=\"font-heading text-lg font-semibold leading-none tracking-tight text-balance text-white drop-shadow-[0_2px_12px_rgba(0,0,0,0.65)] sm:text-xl\"\n            >\n              {title}\n            </h2>\n            {goldVerified ? (\n              <span className=\"inline-flex translate-y-[0.05em]\" title=\"Verified\">\n                <TwitterGoldVerifiedBadge className=\"h-[1.05em] w-[1.05em] sm:h-[1.1em] sm:w-[1.1em]\" />\n              </span>\n            ) : null}\n          </div>\n        </div>\n      </div>\n    </article>\n  );\n}\n"},{"path":"src/blocks/gfa/gradual-card-tv-noise.tsx","content":"\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { useReducedMotion } from \"motion/react\";\n\n/** SVG feTurbulence — tekrarlayan gren; konum jitter ile TV statiği */\nconst NOISE_TILE =\n  \"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\";\n\nexport function GradualCardTvNoise() {\n  const reduceMotion = useReducedMotion();\n  const ref = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    if (reduceMotion) return;\n    const el = ref.current;\n    if (!el) return;\n\n    let raf = 0;\n    let frame = 0;\n\n    const tick = () => {\n      if (document.visibilityState === \"hidden\") {\n        raf = 0;\n        return;\n      }\n      frame += 1;\n      if (frame % 2 === 0) {\n        const x = (Math.random() - 0.5) * 120;\n        const y = (Math.random() - 0.5) * 120;\n        el.style.backgroundPosition = `${x}px ${y}px`;\n      }\n      raf = requestAnimationFrame(tick);\n    };\n\n    const stop = () => {\n      cancelAnimationFrame(raf);\n      raf = 0;\n    };\n\n    const start = () => {\n      if (raf !== 0) return;\n      raf = requestAnimationFrame(tick);\n    };\n\n    const onVisibility = () => {\n      if (document.visibilityState === \"hidden\") {\n        stop();\n      } else {\n        start();\n      }\n    };\n\n    start();\n    document.addEventListener(\"visibilitychange\", onVisibility);\n\n    return () => {\n      stop();\n      document.removeEventListener(\"visibilitychange\", onVisibility);\n    };\n  }, [reduceMotion]);\n\n  return (\n    <div\n      ref={ref}\n      className=\"pointer-events-none absolute inset-0 z-[2] opacity-[0.42] mix-blend-luminosity will-change-[background-position]\"\n      style={{\n        backgroundImage: `url(\"${NOISE_TILE}\")`,\n        backgroundRepeat: \"repeat\",\n        backgroundSize: \"56px 56px\",\n        backgroundPosition: \"0 0\",\n      }}\n      aria-hidden\n    />\n  );\n}\n"},{"path":"src/blocks/gfa/gradual-card-protected-image.tsx","content":"\"use client\";\n\nimport Image, { type ImageProps } from \"next/image\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype Props = Omit<ImageProps, \"draggable\">;\n\n/**\n * Sürükleme, sağ tık menüsü ve (mümkün olduğunca) uzun basarak kaydetmeyi engeller.\n */\nexport function GradualCardProtectedImage({ className, ...props }: Props) {\n  return (\n    <div\n      className=\"absolute inset-0 z-0 select-none\"\n      onContextMenu={(e) => e.preventDefault()}\n      onDragStart={(e) => e.preventDefault()}\n    >\n      <Image\n        {...props}\n        draggable={false}\n        className={cn(\n          \"object-cover object-center [-webkit-touch-callout:none] [-webkit-user-drag:none]\",\n          className,\n        )}\n      />\n    </div>\n  );\n}\n"},{"path":"src/blocks/gfa/twitter-gold-verified-badge.tsx","content":"\"use client\";\n\nimport { useId } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** X / Twitter verificationBadge SVG (kullanıcı tarafından iletilen kaynak). */\nexport function TwitterGoldVerifiedBadge({ className }: { className?: string }) {\n  const uid = useId().replace(/:/g, \"\");\n  const paint0 = `${uid}-paint0`;\n  const paint1 = `${uid}-paint1`;\n\n  return (\n    <svg\n      viewBox=\"0 0 22 22\"\n      aria-hidden\n      className={cn(\"inline-block shrink-0 align-[-0.12em]\", className)}\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <defs>\n        <linearGradient id={paint0} x1=\"4\" x2=\"19.5\" y1=\"1.5\" y2=\"22\" gradientUnits=\"userSpaceOnUse\">\n          <stop stopColor=\"#F4E72A\" />\n          <stop offset=\"0.539\" stopColor=\"#CD8105\" />\n          <stop offset=\"0.68\" stopColor=\"#CB7B00\" />\n          <stop offset=\"1\" stopColor=\"#F4EC26\" />\n          <stop offset=\"1\" stopColor=\"#F4E72A\" />\n        </linearGradient>\n        <linearGradient id={paint1} x1=\"5\" x2=\"17.5\" y1=\"2.5\" y2=\"19.5\" gradientUnits=\"userSpaceOnUse\">\n          <stop stopColor=\"#F9E87F\" />\n          <stop offset=\"0.406\" stopColor=\"#E2B719\" />\n          <stop offset=\"0.989\" stopColor=\"#E2B719\" />\n        </linearGradient>\n      </defs>\n      <g>\n        <path\n          clipRule=\"evenodd\"\n          fillRule=\"evenodd\"\n          fill={`url(#${paint0})`}\n          d=\"M13.596 3.011L11 .5 8.404 3.011l-3.576-.506-.624 3.558-3.19 1.692L2.6 11l-1.586 3.245 3.19 1.692.624 3.558 3.576-.506L11 21.5l2.596-2.511 3.576.506.624-3.558 3.19-1.692L19.4 11l1.586-3.245-3.19-1.692-.624-3.558-3.576.506zM6 11.39l3.74 3.74 6.2-6.77L14.47 7l-4.8 5.23-2.26-2.26L6 11.39z\"\n        />\n        <path\n          clipRule=\"evenodd\"\n          fillRule=\"evenodd\"\n          fill={`url(#${paint1})`}\n          d=\"M13.348 3.772L11 1.5 8.651 3.772l-3.235-.458-.565 3.219-2.886 1.531L3.4 11l-1.435 2.936 2.886 1.531.565 3.219 3.235-.458L11 20.5l2.348-2.272 3.236.458.564-3.219 2.887-1.531L18.6 11l1.435-2.936-2.887-1.531-.564-3.219-3.236.458zM6 11.39l3.74 3.74 6.2-6.77L14.47 7l-4.8 5.23-2.26-2.26L6 11.39z\"\n        />\n        <path\n          clipRule=\"evenodd\"\n          fillRule=\"evenodd\"\n          fill=\"#D18800\"\n          d=\"M6 11.39l3.74 3.74 6.197-6.767h.003V9.76l-6.2 6.77L6 12.79v-1.4zm0 0z\"\n        />\n      </g>\n    </svg>\n  );\n}\n"}],"meta":{"studioName":"GFA Development & Design Studio","siteUrl":"https://ui.gkhn.dev"}}