{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tabs-illustration-vercel",
  "type": "registry:ui",
  "description": "Tabs with Vercel-style illustrations",
  "files": [
    {
      "path": "registry/default/ui/tabs-illustration-vercel.tsx",
      "content": "\"use client\"\n\nimport {\n  useCallback,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  type ReactNode,\n} from \"react\"\n\ntype Feature = {\n  id: string\n  title: string\n  description: string\n  badge?: string\n}\n\nconst FEATURES: Feature[] = [\n  {\n    id: \"text\",\n    title: \"Text\",\n    description:\n      \"Access the latest from every major model lab and provider. Power your AI features all through a single endpoint.\",\n  },\n  {\n    id: \"image\",\n    title: \"Image\",\n    description:\n      \"Generate and edit with best-in-class image models, no extra setup.\",\n  },\n  {\n    id: \"video\",\n    title: \"Video\",\n    description:\n      \"Ship production-ready video across a wide range of models with just a prompt.\",\n    badge: \"New\",\n  },\n]\n\nconst TRAIL_CONFIGS = [\n  { color: \"#EBE51A\", dur: 6.5 },\n  { color: \"#A4E600\", dur: 4.1 },\n  { color: \"#2DDD69\", dur: 5.7 },\n  { color: \"#FF904D\", dur: 5.7 },\n  { color: \"#62DE00\", dur: 4.9 },\n  { color: \"#FFBB3D\", dur: 4.9 },\n  { color: \"#F8E52C\", dur: 5.7 },\n]\n\nconst TRAIL_PATHS = [\n  {\n    d: \"M617.415,100 h-61.743M555.673,100 h-74.252M506.605,200 A 123.097 400 0 0 0 481.421 100M506.605,200 h-106.605M400,200 h-106.605\",\n    cx: \"617.415;617.415;555.673;481.421;506.605;400;293.395;293.395;0\",\n    cy: \"100;100;100;100;200;200;200;200;0\",\n    r: \"0;50;50;50;50;50;50;0;0\",\n    kt: \"0;0.088;0.176;0.264;0.352;0.44;0.527;0.615;1\",\n    op: \"0;1;1;1;1;1;1;0;0\",\n  },\n  {\n    d: \"M603.824,200 h-97.219M519.188,300 A 123.097 400 0 0 0 506.605 200\",\n    cx: \"603.824;603.824;506.605;519.188;519.188;0\",\n    cy: \"200;200;200;300;300;0\",\n    r: \"0;50;50;50;0;0\",\n    kt: \"0;0.098;0.195;0.293;0.39;1\",\n    op: \"0;1;1;1;0;0\",\n  },\n  {\n    d: \"M718.264,300 h-90.382M627.882,300 A 235.355 400 0 0 0 603.824 200M603.824,200 h-97.219M506.605,200 h-106.605\",\n    cx: \"718.264;718.264;627.882;603.824;506.605;400;400;0\",\n    cy: \"300;300;300;200;200;200;200;0\",\n    r: \"0;50;50;50;50;50;0;0\",\n    kt: \"0;0.094;0.187;0.281;0.374;0.468;0.561;1\",\n    op: \"0;1;1;1;1;1;0;0\",\n  },\n  {\n    d: \"M627.882,300 h-108.694M519.188,300 h-119.188M400,300 h-119.188M280.812,300 A -123.097 400 0 0 0 276.903 400\",\n    cx: \"627.882;627.882;519.188;400;280.812;276.903;276.903;0\",\n    cy: \"300;300;300;300;300;400;400;0\",\n    r: \"0;50;50;50;50;50;0;0\",\n    kt: \"0;0.094;0.187;0.281;0.374;0.468;0.561;1\",\n    op: \"0;1;1;1;1;1;0;0\",\n  },\n  {\n    d: \"M318.579,100 h-74.252M244.327,100 h-61.743M182.585,100 A -328.701 400 0 0 0 115.336 200\",\n    cx: \"318.579;318.579;244.327;182.585;115.336;115.336;0\",\n    cy: \"100;100;100;100;200;200;0\",\n    r: \"0;50;50;50;50;0;0\",\n    kt: \"0;0.098;0.196;0.294;0.392;0.49;1\",\n    op: \"0;1;1;1;1;0;0\",\n  },\n  {\n    d: \"M196.176,200 h-80.84M115.336,200 h-61.747M53.59,200 A -400 400 0 0 0 12.702 300\",\n    cx: \"196.176;196.176;115.336;53.59;12.702;12.702;0\",\n    cy: \"200;200;200;200;300;300;0\",\n    r: \"0;50;50;50;50;0;0\",\n    kt: \"0;0.098;0.196;0.294;0.392;0.49;1\",\n    op: \"0;1;1;1;1;0;0\",\n  },\n  {\n    d: \"M280.812,300 h-108.694M172.118,300 h-90.382M81.736,300 h-69.035M12.702,300 A -400 400 0 0 0 0 400\",\n    cx: \"280.812;280.812;172.118;81.736;12.702;0;0;0\",\n    cy: \"300;300;300;300;300;400;400;0\",\n    r: \"0;50;50;50;50;50;0;0\",\n    kt: \"0;0.094;0.187;0.281;0.374;0.468;0.561;1\",\n    op: \"0;1;1;1;1;1;0;0\",\n  },\n]\n\n/* ─── Icons ─── */\nfunction XIcon({ size = 16 }) {\n  return (\n    <svg fill=\"currentColor\" height={size} viewBox=\"0 0 24 24\" width={size}>\n      <title>X Icon</title>\n      <path d=\"M2.3 8.776L12.047 23h4.333L6.632 8.776H2.3zM6.629 16.676L2.295 23h4.336l2.165-3.161-2.167-3.163zM17.371 1L9.88 11.931l2.167 3.163L21.707 1h-4.336zM18.156 7.764V23h3.551V2.582l-3.551 5.182z\" />\n    </svg>\n  )\n}\n\nfunction OpenAIIcon({ size = 16 }) {\n  return (\n    <svg fill=\"currentColor\" height={size} viewBox=\"0 0 16 16\" width={size}>\n      <title>OpenAI Icon</title>\n      <path d=\"M14.945 6.549c.368-1.09.241-2.283-.347-3.274-.885-1.52-2.664-2.302-4.401-1.934A4.436 4.436 0 007.15 0C5.375-.004 3.8 1.124 3.253 2.791a4.435 4.435 0 00-2.701 1.934C-.187 6.24.016 8.152 1.055 9.452a4.476 4.476 0 00.347 3.274c.885 1.52 2.664 2.302 4.401 1.934a4.436 4.436 0 003.047 1.34c1.776.004 3.353-1.125 3.899-2.793a4.435 4.435 0 002.701-1.934c.89-1.516.687-3.426-.504-4.724zM8.85 14.954a3.502 3.502 0 01-1.945-.7l.095-.055 3.229-1.84a.527.527 0 00.266-.457V7.422l1.365.778a.048.048 0 01.026.024v3.72c-.002 1.654-1.36 2.995-3.036 2.999zM2.321 12.203a3.44 3.44 0 01-.362-2.01l.096.057 3.229 1.84a.538.538 0 00.53 0l3.942-2.246v1.555a.046.046 0 01-.019.04l-3.264 1.86a3.06 3.06 0 01-4.152-1.097zM1.472 5.248a3.44 3.44 0 011.581-1.315v3.791a.524.524 0 00.265.456l3.941 2.245-1.364.778a.046.046 0 01-.046.006l-3.265-1.86A3.003 3.003 0 011.472 5.248zm10.211 2.574L7.742 5.577l1.364-.777a.046.046 0 01.046-.005l3.265 1.86a3.004 3.004 0 011.112 4.097 3.44 3.44 0 01-1.581 1.314V8.276a.526.526 0 00-.264-.454zm1.358-2.017a4.19 4.19 0 00-.096-.056L9.717 3.909a.538.538 0 00-.53 0L5.244 6.155V4.6a.046.046 0 01.02-.041l3.264-1.858A3.063 3.063 0 0113.678 3.8c.354.606.482 1.316.362 2.006zM5.503 8.577L4.137 7.8a.048.048 0 01-.026-.024V4.043a3.003 3.003 0 013.04-2.997 3.5 3.5 0 011.945.694l-.096.054-3.229 1.84a.527.527 0 00-.265.457l-.002 4.49zm.741-1.577L8 5.999l1.756 1v2.001L8 10l-1.756-1V7z\" />\n    </svg>\n  )\n}\n\nfunction PlayIcon({ size = 16 }) {\n  return (\n    <svg fill=\"currentColor\" height={size} viewBox=\"0 0 24 24\" width={size}>\n      <title>Play Icon</title>\n      <polygon points=\"6 3 20 12 6 21\" />\n    </svg>\n  )\n}\n\n/* ─── Chat bubble tail ─── */\nfunction BubbleTail() {\n  const tailMaskId = `tail-mask-${useId().replace(/:/g, \"\")}`\n\n  return (\n    <svg\n      fill=\"none\"\n      height=\"24\"\n      style={{ position: \"absolute\", bottom: -2, left: -7, display: \"block\" }}\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n    >\n      <title>Bubble Tail</title>\n      {/* No full-rect fill: avoids a second “page color” that can mismatch the real parent behind the tail. */}\n      <mask\n        height=\"24\"\n        id={tailMaskId}\n        maskUnits=\"userSpaceOnUse\"\n        style={{ maskType: \"alpha\" }}\n        width=\"24\"\n        x=\"0\"\n        y=\"0\"\n      >\n        <path d=\"M0 0h24v24H0V0z\" fill=\"#D9D9D9\" />\n      </mask>\n      <g mask={`url(#${tailMaskId})`}>\n        <path\n          clipRule=\"evenodd\"\n          d=\"M27-19C15.954-19 7-10.046 7 1c0 .335.008.669.025 1H7v10a15 15 0 01-3 9c4.116 0 7.845-1.658 10.555-4.342A19.915 19.915 0 0027 21c11.046 0 20-8.954 20-20s-8.954-20-20-20z\"\n          fill=\"var(--ga-surface)\"\n          fillRule=\"evenodd\"\n        />\n        <path\n          d=\"M7.025 2v1h1.05l-.052-1.05-.998.05zM7 2V1H6v1h1zm-3 19l-.8-.6L2 22h2v-1zm10.555-4.342l.623-.783-.695-.553-.631.625.703.71zM8 1c0-10.493 8.507-19 19-19v-2C15.402-20 6-10.598 6 1h2zm.023.95A19.327 19.327 0 018 1H6c0 .352.009.702.026 1.05l1.997-.1zM7 3h.025V1H7v2zm1 9V2H6v10h2zm-3.2 9.6A16 16 0 008 12H6a14 14 0 01-2.8 8.4l1.6 1.2zm9.052-5.653A13.952 13.952 0 014 20v2c4.39 0 8.37-1.77 11.259-4.632l-1.407-1.42zM27 20c-4.47 0-8.577-1.542-11.822-4.125l-1.245 1.565A20.915 20.915 0 0027 22v-2zM46 1c0 10.493-8.507 19-19 19v2c11.598 0 21-9.402 21-21h-2zM27-18c10.493 0 19 8.507 19 19h2c0-11.598-9.402-21-21-21v2z\"\n          fill=\"var(--ga-border-svg)\"\n        />\n      </g>\n    </svg>\n  )\n}\n\n/* ─── Globe wireframe with SMIL animations ─── */\nfunction GlobeWireframe() {\n  const idPrefix = useId().replace(/:/g, \"\")\n  const ggId = `${idPrefix}-gg`\n  const gmgId = `${idPrefix}-gmg`\n  const gmId = `${idPrefix}-gm`\n\n  return (\n    <svg\n      aria-hidden=\"true\"\n      height=\"100%\"\n      style={{ overflow: \"visible\", display: \"block\" }}\n      viewBox=\"-1 -1 802 402\"\n      width=\"100%\"\n    >\n      <defs>\n        <linearGradient\n          gradientUnits=\"userSpaceOnUse\"\n          id={ggId}\n          x1=\"0\"\n          x2=\"0\"\n          y1=\"0\"\n          y2=\"400\"\n        >\n          <stop offset=\"0%\" stopColor=\"var(--ga-grid)\" />\n          <stop offset=\"100%\" stopColor=\"var(--ga-grid)\" />\n        </linearGradient>\n        <linearGradient gradientTransform=\"rotate(90)\" id={gmgId}>\n          <stop offset=\".7\" stopColor=\"white\" stopOpacity=\"1\" />\n          <stop offset=\"1\" stopColor=\"white\" stopOpacity=\"0\" />\n        </linearGradient>\n        <mask id={gmId}>\n          <rect fill={`url(#${gmgId})`} height=\"100%\" width=\"100%\" />\n        </mask>\n      </defs>\n      <g mask={`url(#${gmId})`}>\n        <circle cx=\"400\" cy=\"400\" fill=\"var(--ga-bg)\" r=\"400\" />\n        {[\n          -400, -328.701, -235.355, -123.097, 0, 123.097, 235.355, 328.701, 400,\n        ].map((a) => (\n          <path\n            d={\n              a <= 0\n                ? `M 400 800 A ${a} 400 0 0 0 400 0`\n                : `M 400 0 A ${a} 400 0 0 0 400 800`\n            }\n            fill=\"none\"\n            key={`v-${a}`}\n            stroke={`url(#${ggId})`}\n            strokeWidth=\"1\"\n            vectorEffect=\"non-scaling-stroke\"\n          />\n        ))}\n        {[\n          [135.425, 100, 529.15],\n          [53.59, 200, 692.82],\n          [12.702, 300, 774.597],\n          [0, 400, 800],\n          [12.702, 500, 774.597],\n          [53.59, 600, 692.82],\n          [135.425, 700, 529.15],\n        ].map(([x, y, w]) => (\n          <path\n            d={`M${x},${y} h${w}`}\n            fill=\"none\"\n            key={`h-${y}`}\n            stroke={`url(#${ggId})`}\n            strokeWidth=\"1\"\n            vectorEffect=\"non-scaling-stroke\"\n          />\n        ))}\n      </g>\n      {TRAIL_CONFIGS.map((t, i) => {\n        const p = TRAIL_PATHS[i]\n        if (!p) return null\n        const id = `t${i}`,\n          dur = `${t.dur}s`\n        return (\n          <g key={id} mask={`url(#${gmId})`}>\n            <path\n              d={p.d}\n              fill=\"none\"\n              stroke={`url(#${id}g)`}\n              strokeLinecap=\"round\"\n              strokeWidth=\"2\"\n              vectorEffect=\"non-scaling-stroke\"\n            >\n              <animate\n                attributeName=\"opacity\"\n                dur={dur}\n                keyTimes={p.kt}\n                repeatCount=\"indefinite\"\n                values={p.op}\n              />\n            </path>\n            <defs>\n              <radialGradient\n                cx=\"100\"\n                cy=\"100\"\n                gradientUnits=\"userSpaceOnUse\"\n                id={`${id}g`}\n                r=\"0\"\n              >\n                <stop offset=\"0\" stopColor={t.color} />\n                <stop offset=\"0.4\" stopColor={t.color} />\n                <stop offset=\"1\" stopColor={t.color} stopOpacity=\"0\" />\n                <animate\n                  attributeName=\"cx\"\n                  dur={dur}\n                  keyTimes={p.kt}\n                  repeatCount=\"indefinite\"\n                  values={p.cx}\n                />\n                <animate\n                  attributeName=\"cy\"\n                  dur={dur}\n                  keyTimes={p.kt}\n                  repeatCount=\"indefinite\"\n                  values={p.cy}\n                />\n                <animate\n                  attributeName=\"r\"\n                  dur={dur}\n                  keyTimes={p.kt}\n                  repeatCount=\"indefinite\"\n                  values={p.r}\n                />\n              </radialGradient>\n            </defs>\n          </g>\n        )\n      })}\n    </svg>\n  )\n}\n\n/* ─── Shared chat bubble shell ─── */\nfunction ChatBubbleShell({\n  icon,\n  children,\n}: {\n  icon: ReactNode\n  children: ReactNode\n}) {\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"flex-end\",\n        gap: 12,\n        width: \"100%\",\n      }}\n    >\n      <div\n        style={{\n          width: 32,\n          height: 32,\n          borderRadius: 8,\n          background: \"var(--ga-surface)\",\n          flexShrink: 0,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          boxShadow: \"var(--ga-shadow)\",\n        }}\n      >\n        {icon}\n      </div>\n      <div style={{ position: \"relative\", flex: 1, minWidth: 0 }}>\n        <div\n          style={{\n            background: \"var(--ga-surface)\",\n            borderRadius: 24,\n            overflow: \"hidden\",\n            boxShadow: \"var(--ga-shadow)\",\n          }}\n        >\n          {children}\n        </div>\n        <BubbleTail />\n      </div>\n    </div>\n  )\n}\n\n/* ─── Tab: Text ─── streaming text with cursor ─── */\nfunction TextPanel() {\n  const text =\n    \"Vercel is a cloud platform for deploying and hosting websites, apps, and serverless functions with speed, scalability, and simplicity. It gives teams one workflow from development to global delivery, so products ship faster while keeping reliability and performance high across every request. With robust developer tooling and seamless integrations, Vercel enables engineering teams to collaborate efficiently and manage code from preview to production.\"\n  const [charCount, setCharCount] = useState(0)\n  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)\n\n  useEffect(() => {\n    setCharCount(0)\n    intervalRef.current = setInterval(() => {\n      setCharCount((c) => {\n        if (c >= text.length) {\n          if (intervalRef.current) {\n            clearInterval(intervalRef.current)\n          }\n          return c\n        }\n        return c + 2\n      })\n    }, 16)\n    return () => {\n      if (intervalRef.current) {\n        clearInterval(intervalRef.current)\n      }\n    }\n  }, [])\n\n  return (\n    <ChatBubbleShell icon={<OpenAIIcon size={16} />}>\n      <div\n        style={{\n          padding: 20,\n          aspectRatio: \"16/9\",\n          display: \"flex\",\n          alignItems: \"flex-start\",\n        }}\n      >\n        <div\n          style={{\n            position: \"relative\",\n            width: \"100%\",\n            height: \"100%\",\n            overflow: \"hidden\",\n            flex: 1,\n            maxHeight: 240,\n          }}\n        >\n          <p\n            style={{\n              fontSize: 15,\n              lineHeight: 1.65,\n              color: \"var(--ga-fg-body)\",\n              margin: 0,\n              WebkitMaskImage:\n                \"linear-gradient(175deg, black 40%, transparent 90%)\",\n              maskImage: \"linear-gradient(175deg, black 40%, transparent 90%)\",\n            }}\n          >\n            {text.slice(0, charCount)}\n            {charCount < text.length && (\n              <span\n                style={{\n                  display: \"inline-block\",\n                  width: 2,\n                  height: 15,\n                  background: \"var(--ga-fg-body)\",\n                  marginLeft: 1,\n                  verticalAlign: \"text-bottom\",\n                  animation: \"blink 1s step-end infinite\",\n                }}\n              />\n            )}\n          </p>\n        </div>\n      </div>\n    </ChatBubbleShell>\n  )\n}\n\n/* ─── Tab: Image ─── globe wireframe with progress ─── */\nfunction ImagePanel() {\n  const [progress, setProgress] = useState(0)\n  useEffect(() => {\n    setProgress(0)\n    const start = Date.now()\n    const dur = 8000\n    let raf: number | null = null\n    function tick() {\n      const e = Date.now() - start\n      setProgress(Math.min((e / dur) * 100, 100))\n      if (e < dur) raf = requestAnimationFrame(tick)\n    }\n    raf = requestAnimationFrame(tick)\n    return () => {\n      if (raf !== null) {\n        cancelAnimationFrame(raf)\n      }\n    }\n  }, [])\n\n  return (\n    <ChatBubbleShell icon={<XIcon size={16} />}>\n      <div\n        style={{\n          aspectRatio: \"16/9\",\n          position: \"relative\",\n          overflow: \"hidden\",\n          background: \"var(--ga-surface)\",\n        }}\n      >\n        <GlobeWireframe />\n      </div>\n      <div style={{ padding: \"0 12px 10px\" }}>\n        <div\n          style={{\n            height: 4,\n            background: \"var(--ga-track)\",\n            borderRadius: 9999,\n            overflow: \"hidden\",\n          }}\n        >\n          <div\n            style={{\n              height: \"100%\",\n              background: \"var(--ga-fill)\",\n              borderRadius: 9999,\n              width: `${progress}%`,\n              transition: \"width 80ms linear\",\n            }}\n          />\n        </div>\n      </div>\n    </ChatBubbleShell>\n  )\n}\n\n/* ─── Tab: Video ─── waveform visualizer with timeline ─── */\nfunction VideoPanel() {\n  const [time, setTime] = useState(0)\n  const rafRef = useRef<number | null>(null)\n\n  useEffect(() => {\n    setTime(0)\n    const start = Date.now()\n    function tick() {\n      setTime(((Date.now() - start) / 1000) % 12)\n      rafRef.current = requestAnimationFrame(tick)\n    }\n    rafRef.current = requestAnimationFrame(tick)\n    return () => {\n      if (rafRef.current !== null) {\n        cancelAnimationFrame(rafRef.current)\n      }\n    }\n  }, [])\n\n  const fmt = (s: number) =>\n    `${Math.floor(s / 60)}:${Math.floor(s % 60)\n      .toString()\n      .padStart(2, \"0\")}`\n\n  return (\n    <ChatBubbleShell icon={<PlayIcon size={14} />}>\n      <div\n        style={{\n          aspectRatio: \"16/9\",\n          position: \"relative\",\n          overflow: \"hidden\",\n          background: \"var(--ga-surface)\",\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n        }}\n      >\n        <div\n          style={{ display: \"flex\", alignItems: \"center\", gap: 3, height: 80 }}\n        >\n          {Array.from({ length: 32 }, (_, index) => index + 1).map((bar) => {\n            const phase = (time * 2.5 + (bar - 1) * 0.3) % (Math.PI * 2)\n            const h = 10 + Math.abs(Math.sin(phase)) * 58\n            const hue = ((bar - 1) * 11 + time * 30) % 360\n            return (\n              <div\n                key={`wave-${bar}`}\n                style={{\n                  width: 6,\n                  borderRadius: 3,\n                  height: h,\n                  background: `hsl(${hue}, 65%, 58%)`,\n                  opacity: 0.85,\n                  transition: \"height 80ms linear\",\n                }}\n              />\n            )\n          })}\n        </div>\n        <div\n          style={{\n            position: \"absolute\",\n            bottom: 10,\n            left: 12,\n            right: 12,\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: 8,\n          }}\n        >\n          <span\n            style={{\n              fontSize: 11,\n              color: \"rgba(255,255,255,0.6)\",\n              fontVariantNumeric: \"tabular-nums\",\n              fontFamily: \"monospace\",\n            }}\n          >\n            {fmt(time)}\n          </span>\n          <div\n            style={{\n              flex: 1,\n              height: 3,\n              background: \"rgba(255,255,255,0.12)\",\n              borderRadius: 9999,\n              overflow: \"hidden\",\n            }}\n          >\n            <div\n              style={{\n                height: \"100%\",\n                background: \"rgba(255,255,255,0.5)\",\n                borderRadius: 9999,\n                width: `${(time / 12) * 100}%`,\n                transition: \"width 60ms linear\",\n              }}\n            />\n          </div>\n          <span\n            style={{\n              fontSize: 11,\n              color: \"rgba(255,255,255,0.4)\",\n              fontVariantNumeric: \"tabular-nums\",\n              fontFamily: \"monospace\",\n            }}\n          >\n            0:12\n          </span>\n        </div>\n      </div>\n    </ChatBubbleShell>\n  )\n}\n\n/* ─── Tab button ─── */\nfunction TabButton({\n  active,\n  title,\n  badge,\n  progress,\n  onClick,\n}: {\n  active: boolean\n  title: string\n  badge?: string\n  progress: number\n  onClick: () => void\n}) {\n  return (\n    <button\n      onClick={onClick}\n      style={{\n        all: \"unset\",\n        cursor: \"pointer\",\n        display: \"block\",\n        padding: \"10px 0\",\n        userSelect: \"none\",\n        WebkitTapHighlightColor: \"transparent\",\n      }}\n      type=\"button\"\n    >\n      <div style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n        <span\n          style={{\n            fontSize: 16,\n            fontWeight: 600,\n            letterSpacing: \"-0.01em\",\n            color: active ? \"var(--ga-fg)\" : \"var(--ga-fg-muted)\",\n            transition: \"color 200ms cubic-bezier(0.23, 1, 0.32, 1)\",\n          }}\n        >\n          {title}\n        </span>\n        {badge && (\n          <span\n            style={{\n              fontSize: 12,\n              fontWeight: 500,\n              color: \"var(--ga-badge)\",\n              opacity: active ? 1 : 0.6,\n              transition: \"opacity 200ms ease\",\n            }}\n          >\n            {badge}\n          </span>\n        )}\n      </div>\n      <div\n        style={{\n          height: 2,\n          marginTop: 4,\n          borderRadius: 9999,\n          background: active ? \"var(--ga-track-active)\" : \"transparent\",\n          overflow: \"hidden\",\n          transition: \"background 200ms ease\",\n        }}\n      >\n        <div\n          style={{\n            height: \"100%\",\n            borderRadius: 9999,\n            background: \"var(--ga-fg)\",\n            width: active ? `${progress * 100}%` : \"0%\",\n            transition: active ? \"none\" : \"width 0ms\",\n          }}\n        />\n      </div>\n    </button>\n  )\n}\n\nconst CYCLE_MS = 6000\n\n/* ─── Main export ─── */\nexport default function GenerateAnything({\n  title = \"Generate anything\",\n  features = FEATURES,\n}: {\n  title?: string\n  features?: Feature[]\n}) {\n  const [activeTab, setActiveTab] = useState(\"text\")\n  const [panelKey, setPanelKey] = useState(0)\n  const [progress, setProgress] = useState(1)\n  const startRef = useRef(Date.now())\n  const rafRef = useRef<number | null>(null)\n\n  const advanceTab = useCallback(() => {\n    setActiveTab((prev) => {\n      const idx = features.findIndex((f) => f.id === prev)\n      return features[(idx + 1) % features.length].id\n    })\n    setPanelKey((k) => k + 1)\n    startRef.current = Date.now()\n  }, [features])\n\n  useEffect(() => {\n    function tick() {\n      const elapsed = Date.now() - startRef.current\n      const remaining = Math.max(1 - elapsed / CYCLE_MS, 0)\n      setProgress(remaining)\n      if (remaining <= 0) {\n        advanceTab()\n      }\n      rafRef.current = requestAnimationFrame(tick)\n    }\n    rafRef.current = requestAnimationFrame(tick)\n    return () => {\n      if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)\n    }\n  }, [advanceTab])\n\n  const handleTabChange = useCallback(\n    (id: string) => {\n      if (id === activeTab) return\n      setActiveTab(id)\n      setPanelKey((k) => k + 1)\n      startRef.current = Date.now()\n      setProgress(1)\n    },\n    [activeTab]\n  )\n\n  return (\n    <>\n      <style>{`\n        [data-generate-anything] {\n          --ga-bg: #fafafa;\n          --ga-surface: #fff;\n          --ga-fg: #000;\n          --ga-fg-muted: #888;\n          --ga-fg-body: #666;\n          --ga-border: rgba(0,0,0,0.08);\n          --ga-border-subtle: rgba(0,0,0,0.04);\n          --ga-border-svg: rgba(0,0,0,0.06);\n          --ga-track: #eee;\n          --ga-fill: #1a1a1a;\n          --ga-grid: #ccc;\n          --ga-track-active: rgba(0,0,0,0.1);\n          --ga-badge: #0070f3;\n          --ga-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 0 0 1px rgba(0,0,0,0.04);\n        }\n        :is(.dark) [data-generate-anything] {\n          --ga-bg: #0a0a0a;\n          --ga-surface: #1a1a1a;\n          --ga-fg: #ededed;\n          --ga-fg-muted: #a0a0a0;\n          --ga-fg-body: #a1a1a1;\n          --ga-border: rgba(255,255,255,0.08);\n          --ga-border-subtle: rgba(255,255,255,0.04);\n          --ga-border-svg: rgba(255,255,255,0.08);\n          --ga-track: #333;\n          --ga-fill: #ededed;\n          --ga-grid: #333;\n          --ga-track-active: rgba(255,255,255,0.1);\n          --ga-badge: #3291ff;\n          --ga-shadow: 0 1px 3px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.06);\n        }\n        @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }\n        @keyframes panelIn {\n          from { opacity:0; transform:scale(0.97) translateY(6px); }\n          to { opacity:1; transform:scale(1) translateY(0); }\n        }\n        @keyframes descIn {\n          from { opacity:0; transform:translateY(4px); }\n          to { opacity:1; transform:translateY(0); }\n        }\n      `}</style>\n      <div\n        data-generate-anything\n        style={{\n          display: \"flex\",\n          flexDirection: \"row\",\n          width: \"100%\",\n          minHeight: 380,\n          background: \"var(--ga-bg)\",\n          overflow: \"hidden\",\n          fontFamily:\n            '\"Geist\",\"Inter\",-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif',\n        }}\n      >\n        {/* Left: tabs + description */}\n        <div\n          style={{\n            flex: 1,\n            padding: \"48px 40px\",\n            display: \"flex\",\n            flexDirection: \"column\",\n            justifyContent: \"center\",\n          }}\n        >\n          <h2\n            style={{\n              fontSize: 24,\n              fontWeight: 700,\n              color: \"var(--ga-fg)\",\n              margin: \"0 0 36px\",\n              letterSpacing: \"-0.03em\",\n              lineHeight: 1.2,\n            }}\n          >\n            {title}\n          </h2>\n          <div style={{ display: \"flex\", flexDirection: \"column\" }}>\n            {features.map((f) => {\n              const isActive = f.id === activeTab\n              return (\n                <div key={f.id}>\n                  <TabButton\n                    active={isActive}\n                    badge={f.badge}\n                    onClick={() => handleTabChange(f.id)}\n                    progress={isActive ? progress : 0}\n                    title={f.title}\n                  />\n                  <div\n                    style={{\n                      overflow: \"hidden\",\n                      maxHeight: isActive ? 120 : 0,\n                      opacity: isActive ? 1 : 0,\n                      transition:\n                        \"max-height 350ms cubic-bezier(0.23,1,0.32,1), opacity 250ms cubic-bezier(0.23,1,0.32,1)\",\n                    }}\n                  >\n                    <p\n                      key={`d-${f.id}-${isActive}`}\n                      style={{\n                        fontSize: 15,\n                        lineHeight: 1.6,\n                        color: \"var(--ga-fg-muted)\",\n                        margin: \"0 0 16px\",\n                        maxWidth: 400,\n                        animation: isActive\n                          ? \"descIn 400ms cubic-bezier(0.23,1,0.32,1) both\"\n                          : \"none\",\n                      }}\n                    >\n                      {f.description}\n                    </p>\n                  </div>\n                </div>\n              )\n            })}\n          </div>\n        </div>\n\n        {/* Divider */}\n        <div\n          style={{\n            width: 1,\n            background: \"var(--ga-border)\",\n            alignSelf: \"stretch\",\n          }}\n        />\n\n        {/* Right: animated panel */}\n        <div\n          style={{\n            flex: 1,\n            padding: 40,\n            display: \"flex\",\n            flexDirection: \"column\",\n            alignItems: \"flex-start\",\n            justifyContent: \"flex-end\",\n          }}\n        >\n          <div\n            key={panelKey}\n            style={{\n              width: \"100%\",\n              animation: \"panelIn 400ms cubic-bezier(0.23,1,0.32,1) both\",\n            }}\n          >\n            {activeTab === \"text\" && <TextPanel />}\n            {activeTab === \"image\" && <ImagePanel />}\n            {activeTab === \"video\" && <VideoPanel />}\n          </div>\n        </div>\n      </div>\n    </>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}