{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "grid-cell",
  "title": "Grid Cell",
  "description": "A grid cell lights up and 'Introducing remocn 2.0' is typed into it with a mono label.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/grid-cell/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadGeist } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadGeistMono } from \"@remotion/google-fonts/GeistMono\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport {\n  AbsoluteFill,\n  continueRender,\n  delayRender,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst geist = loadGeist(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\nconst geistMono = loadGeistMono(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\n\nexport interface GridCellProps {\n  /** Typed into the lit cell. */\n  title?: string;\n  /** Tiny mono label in the cell's top-left corner. */\n  label?: string;\n  /** Caret and the label's marker. */\n  accent?: string;\n  /** Playback multiplier. */\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0A\",\n    line: \"rgba(255,255,255,0.11)\",\n    edge: \"rgba(255,255,255,0.34)\",\n    tick: \"rgba(255,255,255,0.7)\",\n    fill: \"rgba(255,255,255,0.025)\",\n    wash: \"rgba(255,255,255,0.035)\",\n    fg: \"#EDEDED\",\n    muted: \"#8A8A8F\",\n  },\n  light: {\n    bg: \"#FAFAF9\",\n    line: \"rgba(10,10,10,0.09)\",\n    edge: \"rgba(10,10,10,0.3)\",\n    tick: \"rgba(10,10,10,0.62)\",\n    fill: \"rgba(10,10,10,0.018)\",\n    wash: \"rgba(255,255,255,0.55)\",\n    fg: \"#0F0F10\",\n    muted: \"#77777C\",\n  },\n} as const;\n\n/** Base cell at 1280 wide: 16 × 9 cells tile the frame exactly. */\nconst CELL = 80;\nconst FONT_SIZE = 54;\nconst TRACK = -0.035;\nconst PAD_X = 0.9; // em of air either side of the headline inside the cell\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\nconst T = {\n  gridStart: 2,\n  gridStep: 2.4, // frames per cell of distance from the centre\n  gridDur: 26,\n  edgeStart: 38,\n  edgeDur: 22,\n  mergeStart: 44,\n  mergeDur: 22,\n  labelStart: 58,\n  typeStart: 70,\n  perChar: 1.5,\n  dimStart: 96,\n  dimDur: 34,\n};\n\n/** Headline width in em, measured once the face is ready. */\nfunction useTitleWidth(title: string) {\n  const [width, setWidth] = useState<number | null>(null);\n  const [handle] = useState(() => delayRender(\"grid-cell: measuring\"));\n  const released = useRef(false);\n\n  useEffect(() => {\n    let live = true;\n    const font = `400 100px \"${geist.fontFamily}\"`;\n    Promise.all([geist.waitUntilDone(), document.fonts.load(font)])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n        let w = [...title].length * 0.52;\n        if (ctx) {\n          ctx.font = font;\n          w = ctx.measureText(title).width / 100 + TRACK * [...title].length;\n        }\n        setWidth(w);\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [title, handle]);\n\n  return width ?? [...title].length * 0.52;\n}\n\nexport function GridCell({\n  title = \"Introducing remocn 2.0\",\n  label = \"v2.0\",\n  accent = \"#3B5BFD\",\n  speed = 1,\n  theme = \"dark\",\n}: GridCellProps) {\n  const frame = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const c = THEMES[theme];\n  const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n\n  const s = Math.min(W / 1280, H / 720);\n  const cell = CELL * s;\n  const fontSize = FONT_SIZE * s;\n  const cx = W / 2;\n  const cy = H / 2;\n\n  // The lit cell: two rows tall, an even number of columns so it stays centred\n  // on the grid's middle line.\n  const titleEm = useTitleWidth(title);\n  let cols = Math.ceil(((titleEm + PAD_X * 2) * fontSize) / cell);\n  if (cols % 2 === 1) cols += 1;\n  cols = Math.max(4, cols);\n  const cw = cols * cell;\n  const ch = 2 * cell;\n  const x0 = cx - cw / 2;\n  const y0 = cy - ch / 2;\n\n  // Grid lines, symmetric around the centre.\n  const kx = Math.ceil(cx / cell);\n  const ky = Math.ceil(cy / cell);\n  const verticals: number[] = [];\n  const horizontals: number[] = [];\n  for (let k = -kx; k <= kx; k++) verticals.push(k);\n  for (let k = -ky; k <= ky; k++) horizontals.push(k);\n\n  const grow = (k: number) =>\n    interpolate(\n      frame,\n      [\n        T.gridStart + Math.abs(k) * T.gridStep,\n        T.gridStart + Math.abs(k) * T.gridStep + T.gridDur,\n      ],\n      [0, 1],\n      { ...CLAMP, easing: EASE_OUT },\n    );\n\n  const merge = interpolate(\n    frame,\n    [T.mergeStart, T.mergeStart + T.mergeDur],\n    [0, 1],\n    { ...CLAMP, easing: EASE_IO },\n  );\n  const edge = interpolate(\n    frame,\n    [T.edgeStart, T.edgeStart + T.edgeDur],\n    [0, 1],\n    { ...CLAMP, easing: EASE_OUT },\n  );\n  const ticks = interpolate(\n    frame,\n    [T.labelStart - 4, T.labelStart + 12],\n    [0, 1],\n    {\n      ...CLAMP,\n      easing: EASE_OUT,\n    },\n  );\n  const labelIn = interpolate(\n    frame,\n    [T.labelStart, T.labelStart + 16],\n    [0, 1],\n    { ...CLAMP, easing: EASE_OUT },\n  );\n  const dim = interpolate(\n    frame,\n    [T.dimStart, T.dimStart + T.dimDur],\n    [1, 0.5],\n    {\n      ...CLAMP,\n      easing: EASE_IO,\n    },\n  );\n\n  const chars = [...title];\n  const typed = Math.max(\n    0,\n    Math.min(chars.length, Math.floor((frame - T.typeStart) / T.perChar) + 1),\n  );\n  const typingDone = T.typeStart + chars.length * T.perChar;\n  const caretOn =\n    frame >= T.typeStart - 6 &&\n    (frame < typingDone + 6 || Math.floor((frame - typingDone) / 15) % 2 === 1);\n  const caretIn = interpolate(\n    frame,\n    [T.typeStart - 8, T.typeStart - 2],\n    [0, 1],\n    CLAMP,\n  );\n\n  // Edges of the cell brighten from each edge's midpoint outward.\n  const edgeSegs = [\n    { x1: cx, y1: y0, dx: cw / 2, dy: 0 },\n    { x1: cx, y1: y0 + ch, dx: cw / 2, dy: 0 },\n    { x1: x0, y1: cy, dx: 0, dy: ch / 2 },\n    { x1: x0 + cw, y1: cy, dx: 0, dy: ch / 2 },\n  ];\n  const arm = 7 * s;\n  const corners = [\n    [x0, y0],\n    [x0 + cw, y0],\n    [x0, y0 + ch],\n    [x0 + cw, y0 + ch],\n  ];\n\n  return (\n    <AbsoluteFill style={{ background: c.bg, overflow: \"hidden\" }}>\n      <svg\n        width={W}\n        height={H}\n        viewBox={`0 0 ${W} ${H}`}\n        style={{ position: \"absolute\", inset: 0 }}\n        aria-hidden\n      >\n        <defs>\n          <radialGradient\n            id={`${uid}-vig`}\n            cx={cx}\n            cy={cy}\n            r={Math.hypot(cx, cy)}\n            gradientUnits=\"userSpaceOnUse\"\n          >\n            <stop offset=\"0\" stopColor=\"#fff\" stopOpacity={1} />\n            <stop offset=\"0.38\" stopColor=\"#fff\" stopOpacity={0.8} />\n            <stop offset=\"0.85\" stopColor=\"#fff\" stopOpacity={0.14} />\n            <stop offset=\"1\" stopColor=\"#fff\" stopOpacity={0.04} />\n          </radialGradient>\n          <mask\n            id={`${uid}-mask`}\n            maskUnits=\"userSpaceOnUse\"\n            x={0}\n            y={0}\n            width={W}\n            height={H}\n          >\n            <rect width={W} height={H} fill={`url(#${uid}-vig)`} />\n            <rect\n              x={x0}\n              y={y0}\n              width={cw}\n              height={ch}\n              fill=\"#000\"\n              fillOpacity={merge}\n            />\n          </mask>\n          <radialGradient\n            id={`${uid}-wash`}\n            cx={x0}\n            cy={y0}\n            r={cw * 0.7}\n            gradientUnits=\"userSpaceOnUse\"\n          >\n            <stop offset=\"0\" stopColor={c.wash} />\n            <stop offset=\"1\" stopColor={c.wash} stopOpacity={0} />\n          </radialGradient>\n        </defs>\n\n        <g mask={`url(#${uid}-mask)`} opacity={dim}>\n          {verticals.map((k) => {\n            const x = cx + k * cell;\n            const p = grow(k);\n            if (p <= 0) return null;\n            const half = (H / 2 + cell) * p;\n            return (\n              <line\n                key={`v${k}`}\n                x1={x}\n                x2={x}\n                y1={cy - half}\n                y2={cy + half}\n                stroke={c.line}\n                strokeWidth={1}\n                shapeRendering=\"crispEdges\"\n              />\n            );\n          })}\n          {horizontals.map((k) => {\n            const y = cy + k * cell;\n            const p = grow(k * 1.6);\n            if (p <= 0) return null;\n            const half = (W / 2 + cell) * p;\n            return (\n              <line\n                key={`h${k}`}\n                x1={cx - half}\n                x2={cx + half}\n                y1={y}\n                y2={y}\n                stroke={c.line}\n                strokeWidth={1}\n                shapeRendering=\"crispEdges\"\n              />\n            );\n          })}\n        </g>\n\n        {/* The lit cell */}\n        <rect\n          x={x0}\n          y={y0}\n          width={cw}\n          height={ch}\n          fill={c.fill}\n          opacity={merge}\n        />\n        <rect\n          x={x0}\n          y={y0}\n          width={cw}\n          height={ch}\n          fill={`url(#${uid}-wash)`}\n          opacity={merge * (theme === \"dark\" ? 1 : 0.8)}\n        />\n        {edgeSegs.map((e) => (\n          <line\n            key={`${e.x1}-${e.y1}-${e.dx}`}\n            x1={e.x1 - e.dx * edge}\n            x2={e.x1 + e.dx * edge}\n            y1={e.y1 - e.dy * edge}\n            y2={e.y1 + e.dy * edge}\n            stroke={c.edge}\n            strokeWidth={1}\n            shapeRendering=\"crispEdges\"\n            opacity={edge > 0 ? 1 : 0}\n          />\n        ))}\n        <g opacity={ticks}>\n          {corners.map(([x, y]) => (\n            <g key={`${x}-${y}`} stroke={c.tick} strokeWidth={1}>\n              <line\n                x1={x - arm}\n                x2={x + arm}\n                y1={y}\n                y2={y}\n                shapeRendering=\"crispEdges\"\n              />\n              <line\n                x1={x}\n                x2={x}\n                y1={y - arm}\n                y2={y + arm}\n                shapeRendering=\"crispEdges\"\n              />\n            </g>\n          ))}\n        </g>\n      </svg>\n\n      {/* Corner label */}\n      <div\n        style={{\n          position: \"absolute\",\n          left: x0 + 14 * s,\n          top: y0 + 12 * s,\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: 7 * s,\n          fontFamily: geistMono.fontFamily,\n          fontWeight: 400,\n          fontSize: 12 * s,\n          letterSpacing: \"0.02em\",\n          lineHeight: 1,\n          color: c.muted,\n          opacity: labelIn,\n          filter: `blur(${(1 - labelIn) * 6 * s}px)`,\n          transform: `translateY(${(1 - labelIn) * 6 * s}px)`,\n        }}\n      >\n        <span\n          style={{\n            width: 5 * s,\n            height: 5 * s,\n            background: accent,\n            display: \"block\",\n          }}\n        />\n        {label}\n      </div>\n\n      {/* Headline, typed in place: untyped glyphs keep their slot */}\n      <div\n        style={{\n          position: \"absolute\",\n          left: x0,\n          top: y0,\n          width: cw,\n          height: ch,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n        }}\n      >\n        <div\n          style={{\n            fontFamily: geist.fontFamily,\n            fontWeight: 400,\n            fontSize,\n            letterSpacing: `${TRACK}em`,\n            lineHeight: 1,\n            color: c.fg,\n            whiteSpace: \"pre\",\n            fontKerning: \"normal\",\n            transform: `translateY(${4 * s}px)`,\n          }}\n        >\n          <span>{chars.slice(0, typed).join(\"\")}</span>\n          <span\n            style={{\n              position: \"relative\",\n              display: \"inline-block\",\n              width: 0,\n              height: \"1em\",\n              verticalAlign: \"-0.12em\",\n            }}\n          >\n            <span\n              style={{\n                position: \"absolute\",\n                left: 0.04 * fontSize,\n                top: 0.02 * fontSize,\n                width: Math.max(2, 2.5 * s),\n                height: 0.96 * fontSize,\n                background: accent,\n                opacity: caretOn ? caretIn : 0,\n              }}\n            />\n          </span>\n          <span style={{ visibility: \"hidden\" }}>\n            {chars.slice(typed).join(\"\")}\n          </span>\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/grid-cell.tsx"
    }
  ],
  "type": "registry:component"
}
