{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spec-sheet",
  "title": "Spec Sheet",
  "description": "A tech spec table with hairline rows reveals values like 4K, 60 fps and 1 command.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/spec-sheet/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadSans } from \"@remotion/google-fonts/InstrumentSans\";\nimport { loadFont as loadSerif } from \"@remotion/google-fonts/InstrumentSerif\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport type React from \"react\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  interpolateColors,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadSans(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: serif } = loadSerif(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"500\", \"600\"],\n  subsets: [\"latin\"],\n});\n\nexport interface SpecRow {\n  label: string;\n  value: string;\n}\n\nexport interface SpecSheetProps {\n  rows?: SpecRow[];\n  highlight?: number;\n  title?: string;\n  name?: string;\n  logo?: React.ReactNode;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst DEFAULT_ROWS: SpecRow[] = [\n  { label: \"Resolution\", value: \"4K\" },\n  { label: \"Frame rate\", value: \"60 fps\" },\n  { label: \"Components\", value: \"236\" },\n  { label: \"Install\", value: \"1 command\" },\n  { label: \"License\", value: \"MIT\" },\n];\n\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    key: \"rgba(255,255,255,0.05)\",\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    hair: \"rgba(255,255,255,0.14)\",\n    rule: \"rgba(243,242,239,0.9)\",\n    grain: 0.07,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    key: \"rgba(255,255,255,0.9)\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    hair: \"rgba(18,18,20,0.14)\",\n    rule: \"rgba(18,18,20,0.9)\",\n    grain: 0.045,\n  },\n} as const;\n\nconst STAGGER = 5;\nconst EXPAND = 62;\nconst FIGURE_SPACE = \" \";\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `g${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{ position: \"absolute\", inset: 0, opacity, pointerEvents: \"none\" }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={13}\n          stitchTiles=\"stitch\"\n        />\n        <feColorMatrix values=\"0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0.9 0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\nconst REMOCN_R_PATH =\n  \"M 0.01 0.81 C 0.01 1.73, 0.36 2.79, 1.09 4.13 C 4.91 11.04, 13.45 17.16, 21.7 18.9 C 22.94 19.16, 23.18 19.16, 51.39 19.27 C 76.07 19.36, 80.02 19.4, 81.32 19.57 C 89.89 20.69, 96.2 24.68, 99.38 31.01 C 103.19 38.56, 102.53 50.1, 97.91 57.07 C 94.66 61.96, 89.68 64.99, 83.26 66 C 82.81 66.07, 70.18 66.83, 55.2 67.69 C 24.82 69.43, 27.03 69.23, 24.18 70.4 C 19.9 72.15, 14.84 75.7, 10.65 79.89 C 4.86 85.68, 1.3 91.91, 0.25 98.13 C 0.12 98.85, 0.08 103.13, 0.04 116.66 L 0 134.26 9.5 134.26 L 19 134.26 19.05 119.41 C 19.1 103.27, 19.08 103.82, 19.82 101.04 C 21.79 93.65, 27.86 88.75, 36.45 87.63 C 37.23 87.53, 39.41 87.5, 43.57 87.53 C 50.12 87.59, 50.75 87.65, 53.22 88.45 C 56.61 89.56, 59.67 91.86, 62.02 95.07 C 62.52 95.76, 69.35 103.85, 77.2 113.07 C 85.04 122.28, 91.63 130.04, 91.85 130.32 C 92.07 130.59, 92.5 131.34, 92.82 131.97 C 93.52 133.35, 94.11 133.93, 95.06 134.13 C 95.5 134.23, 98.97 134.26, 106.36 134.23 L 117.01 134.19 100.82 113.07 C 91.91 101.45, 84.52 91.78, 84.39 91.57 C 83.36 89.89, 83.66 87.53, 85.09 86 C 85.79 85.25, 86.36 84.94, 88.07 84.38 C 96.18 81.72, 104.15 76.62, 109.97 70.36 C 120.59 58.93, 124.06 44.32, 119.44 30.43 C 114.59 15.81, 101.93 4.02, 87.64 0.83 C 83.93 0.01, 88.09 0.08, 41.85 0.04 L 0.01 0 0.01 0.81 Z\";\n\n/** remocn.dev logo: R mark (0.76em, on the baseline) + \"emocn\" in Manrope 600. */\nfunction RemocnLogo({ fontSize, color }: { fontSize: number; color: string }) {\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"baseline\",\n        color,\n        fontFamily: manrope.fontFamily,\n        fontWeight: 600,\n        fontSize,\n        letterSpacing: \"-0.025em\",\n        lineHeight: 1,\n      }}\n    >\n      <svg\n        viewBox=\"0 0 124.06 134.26\"\n        style={{ height: \"0.76em\", width: \"auto\" }}\n        aria-hidden\n      >\n        <path fill=\"currentColor\" d={REMOCN_R_PATH} />\n      </svg>\n      <span style={{ marginLeft: \"0.04em\" }}>emocn</span>\n    </div>\n  );\n}\n\n// gradients.sh \"halo\" palette (its blue stop is the accent): the highlighted\n// value fills with it as it grows.\nconst HALO = [\"#fff4ec\", \"#ffb36b\", \"#e2457a\", \"#3d4dff\"] as const;\n\nfunction haloFill(\n  theme: \"dark\" | \"light\",\n  t: number,\n  veil: string,\n  blue: string,\n) {\n  const a = 100 + 6 * Math.sin(t / 60);\n  const hi = 18 + 10 * Math.sin(t / 50);\n  const stops =\n    theme === \"dark\"\n      ? `${HALO[1]} 0%, ${HALO[2]} 48%, ${blue} 100%`\n      : `${HALO[2]} 0%, #b8357f 45%, ${blue} 100%`;\n  const glint =\n    theme === \"dark\"\n      ? `radial-gradient(40% 90% at ${hi}% 20%, color-mix(in oklab, ${HALO[0]} 85%, transparent) 0%, transparent 70%), `\n      : \"\";\n  return `linear-gradient(${veil}, ${veil}), ${glint}linear-gradient(${a}deg, ${stops})`;\n}\n\nfunction rgba(hex: string, a: number) {\n  const v = Number.parseInt(hex.replace(\"#\", \"\").slice(0, 6), 16);\n  return `rgba(${(v >> 16) & 255},${(v >> 8) & 255},${v & 255},${a})`;\n}\n\nfunction Value({ value, p }: { value: string; p: number }) {\n  const m = /^(\\d[\\d,.]*)(.*)$/.exec(value);\n  const num = m ? m[1] : \"\";\n  const rest = m ? m[2] : value;\n  const countP = m ? Math.min(1, p / 0.7) : 0;\n  let shown = \"\";\n  if (num) {\n    const target = Number(num.replace(/,/g, \"\"));\n    const decimals = num.includes(\".\") ? num.split(\".\")[1].length : 0;\n    const cur = EASE_OUT(countP) * target;\n    const formatted = cur.toLocaleString(\"en-US\", {\n      minimumFractionDigits: decimals,\n      maximumFractionDigits: decimals,\n      useGrouping: num.includes(\",\"),\n    });\n    shown = formatted.padStart(num.length, FIGURE_SPACE);\n    if (countP === 0) shown = FIGURE_SPACE.repeat(num.length);\n  }\n  const typeStart = num ? 0.45 : 0;\n  const chars = Array.from(rest);\n  const per = (1 - typeStart) / (chars.length + 0.6);\n  return (\n    <>\n      {shown}\n      {chars.map((ch, i) => {\n        const a = interpolate(\n          p,\n          [typeStart + i * per, typeStart + (i + 1.6) * per],\n          [0, 1],\n          CLAMP,\n        );\n        return (\n          <span key={`${i}-${ch}`} style={{ opacity: a }}>\n            {ch}\n          </span>\n        );\n      })}\n    </>\n  );\n}\n\nexport function SpecSheet({\n  rows = DEFAULT_ROWS,\n  highlight = 3,\n  title = \"Tech specs\",\n  name = \"3.0\",\n  logo,\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: SpecSheetProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const u = H / 720;\n  const c = THEMES[theme];\n  const n = Math.max(1, rows.length);\n  const hl = Math.min(Math.max(0, Math.round(highlight)), n - 1);\n\n  const mX = W * 0.075;\n  const cw = W - mX * 2;\n  const col = cw / 12;\n  const tableX = mX + col * 4;\n  const tableW = W - mX - tableX;\n  const hair = Math.max(1, u);\n\n  const baseH = Math.min(82 * u, (H * 0.6) / n);\n  const extra = 66 * u;\n  const valueF = 40 * u;\n  const longestLabel = Math.max(...rows.map((r) => r.label.length), 1);\n  const labelW = longestLabel * 22 * u * 0.56 + 48 * u;\n  const hlF = Math.min(\n    84 * u,\n    (tableW - labelW) / Math.max(rows[hl]?.value.length ?? 1, 1) / 0.56,\n  );\n  const hScale = valueF / hlF;\n\n  const grow = interpolate(t, [EXPAND, EXPAND + 22], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const heights = rows.map((_, i) => baseH + (i === hl ? extra * grow : 0));\n  const total = heights.reduce((a, b) => a + b, 0);\n  const top = (H - total) / 2;\n  const rowTop = (i: number) => {\n    let y = top;\n    for (let k = 0; k < i; k++) y += heights[k];\n    return y;\n  };\n\n  const rule = (i: number) =>\n    interpolate(t, [4 + i * STAGGER, 26 + i * STAGGER], [0, 1], {\n      ...CLAMP,\n      easing: EASE_IO,\n    });\n  const rise = (d: number) =>\n    interpolate(t, [d, d + 18], [0, 1], { ...CLAMP, easing: EASE_OUT });\n  const valueP = (i: number) =>\n    interpolate(t, [12 + i * STAGGER, 34 + i * STAGGER], [0, 1], CLAMP);\n\n  const titleF = 84 * u;\n  const titleP = rise(0);\n  const lockP = rise(8);\n  const bottom = top + total;\n\n  return (\n    <AbsoluteFill style={{ background: c.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 70% 80% at 30% 30%, ${c.key}, transparent 70%)`,\n        }}\n      />\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: mX,\n          top: top - 2 * u,\n          height: titleF * 1.2,\n          overflow: \"hidden\",\n        }}\n      >\n        <div\n          style={{\n            fontFamily: serif,\n            fontWeight: 400,\n            fontSize: titleF,\n            lineHeight: 1.2,\n            letterSpacing: \"-0.02em\",\n            whiteSpace: \"nowrap\",\n            color: c.fg,\n            transform: `translateY(${(1 - titleP) * titleF * 1.2}px)`,\n          }}\n        >\n          {title}\n        </div>\n      </div>\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: mX,\n          top: bottom - 24 * u - 28 * u,\n          height: 28 * u,\n          display: \"flex\",\n          alignItems: \"baseline\",\n          gap: 10 * u,\n          fontFamily: sans,\n          fontWeight: 400,\n          fontSize: 22 * u,\n          lineHeight: 1,\n          letterSpacing: \"-0.01em\",\n          color: c.muted,\n          opacity: lockP,\n          transform: `translateY(${(1 - lockP) * 10 * u}px)`,\n        }}\n      >\n        {logo ?? <RemocnLogo fontSize={26 * u} color={c.fg} />}\n        {name}\n      </div>\n\n      {Array.from({ length: n + 1 }, (_, i) => {\n        const strong = i === 0;\n        const th = strong ? 2 * u : hair;\n        return (\n          <div\n            key={`r${i}`}\n            style={{\n              position: \"absolute\",\n              left: tableX,\n              top: rowTop(i) - th / 2,\n              width: tableW * rule(i),\n              height: th,\n              background: strong ? c.rule : c.hair,\n            }}\n          />\n        );\n      })}\n\n      {rows.map((row, i) => {\n        const y = rowTop(i);\n        const h = heights[i];\n        const isHl = i === hl;\n        const lp = rise(10 + i * STAGGER);\n        const scale = isHl ? hScale + (1 - hScale) * grow : 1;\n        const fill =\n          isHl && grow > 0\n            ? {\n                backgroundImage: haloFill(\n                  theme,\n                  t,\n                  rgba(c.fg, 1 - grow),\n                  accent,\n                ),\n                WebkitBackgroundClip: \"text\",\n                backgroundClip: \"text\",\n                color: \"transparent\",\n              }\n            : { color: c.fg };\n        const labelColor = isHl\n          ? interpolateColors(grow, [0, 1], [c.muted, c.fg])\n          : c.muted;\n        return (\n          <div key={`${i}-${row.label}`}>\n            <div\n              style={{\n                position: \"absolute\",\n                left: tableX,\n                top: y,\n                height: h,\n                display: \"flex\",\n                alignItems: \"center\",\n                fontFamily: sans,\n                fontWeight: 400,\n                fontSize: 22 * u,\n                lineHeight: 1,\n                letterSpacing: \"-0.005em\",\n                whiteSpace: \"nowrap\",\n                color: labelColor,\n                opacity: lp,\n                transform: `translateY(${(1 - lp) * 12 * u}px)`,\n              }}\n            >\n              {row.label}\n            </div>\n            <div\n              style={{\n                position: \"absolute\",\n                right: mX,\n                top: y,\n                height: h,\n                display: \"flex\",\n                alignItems: \"center\",\n                fontFamily: sans,\n                fontWeight: 500,\n                fontSize: isHl ? hlF : valueF,\n                lineHeight: 1.1,\n                letterSpacing: \"-0.03em\",\n                fontVariantNumeric: \"tabular-nums\",\n                whiteSpace: \"pre\",\n                ...fill,\n                transform: `scale(${scale})`,\n                transformOrigin: \"100% 50%\",\n              }}\n            >\n              <Value value={row.value} p={valueP(i)} />\n            </div>\n          </div>\n        );\n      })}\n\n      <Grain opacity={c.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/spec-sheet.tsx"
    }
  ],
  "type": "registry:component"
}
