{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "the-tab",
  "title": "The Tab",
  "description": "A receipt of dotted lines prices every item at $0, totalling \"one Sunday morning\".",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/the-tab/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/AlbertSans\";\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n  AbsoluteFill,\n  continueRender,\n  delayRender,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst albert = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface TheTabItem {\n  item: string;\n  price?: string;\n}\n\nexport interface TheTabProps {\n  items?: TheTabItem[];\n  price?: string;\n  totalLabel?: string;\n  total?: string;\n  accent?: string;\n  background?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst THEMES = {\n  dark: { bg: \"#141414\", ink: \"#FFFFFF\", accent: \"#3DD9FF\" },\n  light: { bg: \"#FFFFFF\", ink: \"#000000\", accent: \"#0077B6\" },\n} as const;\n\nconst DEFAULT_ITEMS: TheTabItem[] = [\n  { item: \"Every component\" },\n  { item: \"Every template\" },\n  { item: \"Every future update\" },\n  { item: \"Seats for the whole team\" },\n  { item: \"Renders on your own machine\" },\n];\n\nconst TRACK = -0.012;\nconst TRACK_TOTAL = -0.025;\n\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nfunction useWidths(keys: string[]) {\n  const key = keys.join(\"\\u0001\");\n  const [state, setState] = useState<{\n    key: string;\n    w: Record<string, number>;\n  } | null>(null);\n  const [handle] = useState(() => delayRender(\"the-tab: measuring\"));\n  const released = useRef(false);\n  const ref = useRef(keys);\n  ref.current = keys;\n  useEffect(() => {\n    let live = true;\n    Promise.all([\n      albert.waitUntilDone(),\n      document.fonts.load(`400 100px \"${albert.fontFamily}\"`),\n      document.fonts.load(`500 100px \"${albert.fontFamily}\"`),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n        const w: Record<string, number> = {};\n        for (const k of ref.current) {\n          const cut = k.indexOf(\"|\");\n          const wt = k.slice(0, cut);\n          const s = k.slice(cut + 1);\n          if (ctx) ctx.font = `${wt} 100px \"${albert.fontFamily}\"`;\n          w[k] = ctx ? ctx.measureText(s).width / 100 : s.length * 0.5;\n        }\n        setState({ key, w });\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [key, handle]);\n  return state && state.key === key ? state.w : null;\n}\n\nexport function TheTab({\n  items = DEFAULT_ITEMS,\n  price = \"$0\",\n  totalLabel = \"Total\",\n  total = \"one Sunday morning.\",\n  accent,\n  background,\n  speed = 1,\n  theme = \"dark\",\n}: TheTabProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H, durationInFrames } = useVideoConfig();\n  const T = durationInFrames * speed;\n  const c = THEMES[theme];\n  const hi = accent ?? c.accent;\n  const stage =\n    background === \"transparent\" ? \"transparent\" : (background ?? c.bg);\n\n  const rows = items\n    .filter((r) => r.item.trim().length > 0)\n    .map((r) => ({ item: r.item, price: r.price ?? price }));\n  const totalWords = total.split(/\\s+/).filter(Boolean);\n  const keys = [\n    ...rows.map((r) => `400|${r.item}`),\n    ...rows.map((r) => `400|${r.price}`),\n    `500|${totalLabel}`,\n    `500|${total}`,\n    ...totalWords.map((s) => `500|${s}`),\n    \"500| \",\n  ];\n  const w = useWidths(keys);\n\n  if (!w) {\n    return (\n      <div style={{ position: \"absolute\", inset: 0, background: stage }} />\n    );\n  }\n\n  const adv = (wt: number, s: string, track: number) =>\n    (w[`${wt}|${s}`] ?? 0) + track * Array.from(s).length;\n\n  const portrait = H > W * 1.1;\n  const unit = Math.min(W, H);\n  const n = rows.length;\n  const mx = W * (portrait ? 0.08 : 0.085);\n  const inner = W - 2 * mx;\n\n  const rowEm = Math.max(\n    0.01,\n    ...rows.map(\n      (r) => adv(400, r.item, TRACK) + adv(400, r.price, TRACK) + 2.2,\n    ),\n  );\n  const totalEm = adv(500, totalLabel, TRACK_TOTAL) + 1.2;\n  const valueEm = adv(500, total, TRACK_TOTAL);\n  let F = Math.min(\n    unit * (portrait ? 0.062 : 0.07),\n    inner / rowEm,\n    (H * (portrait ? 0.5 : 0.7)) / Math.max(1, n * 1.75 + 4),\n  );\n  F = Math.max(14, F);\n  let Ft = F * 1.75;\n  const totalOnOwnLine = portrait || (totalEm + valueEm) * Ft > inner;\n  if (totalOnOwnLine) Ft = Math.min(Ft, inner / Math.max(0.01, valueEm));\n  const rowH = F * 1.75;\n  const ruleGap = F * 0.9;\n  const totalH = totalOnOwnLine ? Ft * 2.6 : Ft * 1.5;\n  const blockH = n * rowH + ruleGap + totalH;\n  const top = H / 2 - blockH / 2;\n\n  const first = 6;\n  const tail = 56;\n  const step =\n    n > 0\n      ? Math.max(6, Math.min(14, (T - first - tail - 34) / Math.max(1, n)))\n      : 0;\n  const rowAt = (i: number) => first + i * step;\n  const ruleAt = rowAt(Math.max(0, n - 1)) + 18;\n  const totalAt = ruleAt + 10;\n\n  const dotR = Math.max(1.2, F * 0.045);\n  const dotGap = F * 0.3;\n\n  const rowNodes = rows.map((r, i) => {\n    const s = rowAt(i);\n    const base = top + i * rowH + F * 1.1;\n    const lIn = interpolate(t, [s, s + 14], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n    const lead = interpolate(t, [s + 4, s + 16], [0, 1], {\n      ...CLAMP,\n      easing: EASE_IO,\n    });\n    const pIn = interpolate(t, [s + 13, s + 25], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n    const x0 = mx + adv(400, r.item, TRACK) * F + F * 0.45;\n    const x1 = W - mx - adv(400, r.price, TRACK) * F - F * 0.45;\n    const xe = x0 + (x1 - x0) * lead;\n    const cy = base - F * 0.08;\n    return (\n      <g key={`r${i}`}>\n        <text\n          x={mx}\n          y={base}\n          fontSize={F}\n          fontWeight={400}\n          letterSpacing={`${TRACK}em`}\n          fill={c.ink}\n          opacity={lIn}\n          transform={`translate(0 ${((1 - lIn) * F * 0.5).toFixed(2)})`}\n        >\n          {r.item}\n        </text>\n        {lead > 0 && x1 > x0 ? (\n          <line\n            x1={x0}\n            y1={cy}\n            x2={xe}\n            y2={cy}\n            stroke={c.ink}\n            strokeOpacity={0.4}\n            strokeWidth={dotR * 2}\n            strokeLinecap=\"round\"\n            strokeDasharray={`0 ${dotGap.toFixed(2)}`}\n          />\n        ) : null}\n        <text\n          x={W - mx}\n          y={base}\n          textAnchor=\"end\"\n          fontSize={F}\n          fontWeight={400}\n          letterSpacing={`${TRACK}em`}\n          fill={c.ink}\n          opacity={pIn}\n          transform={`translate(0 ${((1 - pIn) * -F * 0.4).toFixed(2)})`}\n        >\n          {r.price}\n        </text>\n      </g>\n    );\n  });\n\n  const ruleY = top + n * rowH + ruleGap * 0.35;\n  const rule = interpolate(t, [ruleAt, ruleAt + 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const tlIn = interpolate(t, [totalAt, totalAt + 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const totalBase = ruleY + ruleGap * 0.65 + Ft * 1.05;\n  const valueBase = totalOnOwnLine ? totalBase + Ft * 1.3 : totalBase;\n  const space = w[\"500| \"] ?? 0.25;\n  const wordWs = totalWords.map((s) => adv(500, s, TRACK_TOTAL));\n  const valueW =\n    wordWs.reduce((a, b) => a + b, 0) + space * (totalWords.length - 1);\n  const valueX0 = totalOnOwnLine ? mx : W - mx - valueW * Ft;\n\n  let cursor = 0;\n  const valueNodes = totalWords.map((s, k) => {\n    const x = valueX0 + cursor * Ft;\n    cursor += wordWs[k] + space;\n    const at = totalAt + 8 + k * 5;\n    const v = interpolate(t, [at, at + 14], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n    return (\n      <text\n        key={`v${k}`}\n        x={x}\n        y={valueBase}\n        fontSize={Ft}\n        fontWeight={500}\n        letterSpacing={`${TRACK_TOTAL}em`}\n        fill={hi}\n        opacity={v}\n        transform={`translate(0 ${((1 - v) * Ft * 0.45).toFixed(2)})`}\n      >\n        {s}\n      </text>\n    );\n  });\n\n  return (\n    <AbsoluteFill style={{ background: stage, overflow: \"hidden\" }}>\n      <svg\n        width={W}\n        height={H}\n        viewBox={`0 0 ${W} ${H}`}\n        style={{ position: \"absolute\", inset: 0 }}\n        fontFamily={albert.fontFamily}\n        aria-hidden\n      >\n        {rowNodes}\n        {rule > 0 ? (\n          <rect\n            x={mx}\n            y={ruleY - Math.max(1, F * 0.03)}\n            width={inner * rule}\n            height={Math.max(2, F * 0.06)}\n            fill={c.ink}\n          />\n        ) : null}\n        <text\n          x={mx}\n          y={totalBase}\n          fontSize={Ft}\n          fontWeight={500}\n          letterSpacing={`${TRACK_TOTAL}em`}\n          fill={c.ink}\n          opacity={tlIn}\n          transform={`translate(0 ${((1 - tlIn) * Ft * 0.45).toFixed(2)})`}\n        >\n          {totalLabel}\n        </text>\n        {valueNodes}\n      </svg>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/the-tab.tsx"
    }
  ],
  "type": "registry:component"
}
