{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "unit-grid",
  "title": "Unit Grid",
  "description": "A 10x10 dot grid lights up 33 dots next to a 33 / 100 stat line.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/unit-grid/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadMono } from \"@remotion/google-fonts/GeistMono\";\nimport type React from \"react\";\nimport { useId, useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: mono } = loadMono(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\n\nexport interface UnitGridProps {\n  total?: number;\n  highlighted?: number;\n  caption?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\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    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    unit: \"#3A3A40\",\n    unitLit: \"#4A4A51\",\n    back: \"#1E1E22\",\n    light: \"rgba(255,255,255,0.05)\",\n    // gradients.sh \"limb\", lit left to right across the highlighted units.\n    lit: [\"#e9ecff\", \"#8a8bff\", \"#4b2bd6\"],\n    glow: \"color-mix(in oklab, #4b2bd6 26%, transparent)\",\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    unit: \"#CBC9C2\",\n    unitLit: \"#D8D6CF\",\n    back: \"#B9B7B0\",\n    light: \"rgba(255,255,255,0.8)\",\n    lit: [\"#8a8bff\", \"#5a3ff0\", \"#2f1d9e\"],\n    glow: \"color-mix(in oklab, #8a8bff 22%, transparent)\",\n    grain: 0.045,\n  },\n} as const;\n\nconst APPEAR_STEP = 0.9;\nconst APPEAR_DUR = 12;\nconst FLIP_START = 34;\nconst FLIP_DUR = 10;\n\nexport const unitGridLength = (highlighted = 33) =>\n  FLIP_START + flipSpan(highlighted) + FLIP_DUR + 18;\n\nfunction flipSpan(n: number) {\n  return Math.min(60, Math.max(10, 8 + n * 0.9));\n}\n\nfunction hash(n: number) {\n  const x = Math.sin(n * 127.1 + 311.7) * 43758.5453;\n  return x - Math.floor(x);\n}\n\nfunction mix(a: string, b: string, t: number) {\n  const pa = Number.parseInt(a.slice(1), 16);\n  const pb = Number.parseInt(b.slice(1), 16);\n  const ch = (p: number, s: number) => (p >> s) & 255;\n  const c = (s: number) =>\n    Math.round(ch(pa, s) + (ch(pb, s) - ch(pa, s)) * t)\n      .toString(16)\n      .padStart(2, \"0\");\n  return `#${c(16)}${c(8)}${c(0)}`;\n}\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={11}\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\nfunction gridShape(total: number) {\n  if (total === 100) return { cols: 10, rows: 10 };\n  const cols = Math.max(1, Math.ceil(Math.sqrt(total)));\n  return { cols, rows: Math.ceil(total / cols) };\n}\n\nexport function UnitGrid({\n  total = 100,\n  highlighted = 33,\n  caption = \"1 in 3 teams ship weekly\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: UnitGridProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const u = H / 720;\n  const pal = THEMES[theme];\n  const n = Math.max(1, Math.round(total));\n  const hi = Math.max(0, Math.min(n, Math.round(highlighted)));\n\n  const { cols, rows } = gridShape(n);\n  const gridH = H * 0.56;\n  const pitch = Math.min(gridH / rows, (W * 0.4) / cols);\n  const gw = pitch * cols;\n  const gh = pitch * rows;\n  const colW = W * 0.36;\n  const gap = W * 0.07;\n  const left = (W - (gw + gap + colW)) / 2;\n  const top = (H - gh) / 2;\n  const r = pitch * 0.34;\n\n  const span = flipSpan(hi);\n  const flipEnd = FLIP_START + span + FLIP_DUR;\n\n  const units = useMemo(\n    () =>\n      Array.from({ length: n }, (_, i) => {\n        const c = i % cols;\n        const row = Math.floor(i / cols);\n        const order = hi > 1 ? i / (hi - 1) : 0;\n        return {\n          i,\n          x: left + (c + 0.5) * pitch,\n          y: top + (row + 0.5) * pitch,\n          appear: (c + row) * APPEAR_STEP + hash(i + 1.3) * 2,\n          flip: i < hi ? FLIP_START + EASE_IO(order) * span : -1,\n          hue: c / Math.max(1, cols - 1),\n          light:\n            1 -\n            ((c / Math.max(1, cols - 1)) * 0.6 +\n              (row / Math.max(1, rows - 1)) * 0.4),\n        };\n      }),\n    [n, cols, rows, hi, left, top, pitch, span],\n  );\n\n  const litAt = (k: number) =>\n    k < 0.5\n      ? mix(pal.lit[0], pal.lit[1], k * 2)\n      : mix(pal.lit[1], pal.lit[2], (k - 0.5) * 2);\n  const glowIn = interpolate(t, [FLIP_START, flipEnd + 10], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  let count = 0;\n  const settle = interpolate(t, [flipEnd, flipEnd + 22], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n\n  const dots: React.ReactNode[] = [];\n  for (const d of units) {\n    const a = interpolate(t + 4 - d.appear, [0, APPEAR_DUR], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n    if (a <= 0) continue;\n    let sx = 1;\n    let s = 0.55 + 0.45 * a;\n    let fill: string = mix(pal.unit, pal.unitLit, d.light);\n    let opacity = a * (1 - settle * 0.22);\n    if (d.flip >= 0) {\n      const p = interpolate(t - d.flip, [0, FLIP_DUR], [0, 1], CLAMP);\n      if (p > 0) {\n        const e = EASE_IO(p);\n        sx = Math.max(0.06, Math.abs(Math.cos(Math.PI * e)));\n        if (e < 0.5) {\n          fill = mix(fill, pal.back, e * 2);\n        } else {\n          count++;\n          fill = mix(pal.back, litAt(d.hue), Math.min(1, (e - 0.5) * 2.6));\n          opacity = a;\n          const pop = Math.sin(Math.PI * Math.min(1, (e - 0.5) * 2));\n          s *= 1 + 0.12 * pop;\n        }\n      }\n    }\n    dots.push(\n      <ellipse\n        key={d.i}\n        cx={d.x}\n        cy={d.y}\n        rx={r * s * sx}\n        ry={r * s}\n        fill={fill}\n        opacity={opacity}\n      />,\n    );\n  }\n\n  const numIn = interpolate(t, [16, 34], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const capIn = interpolate(t, [flipEnd - 8, flipEnd + 12], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const cam = interpolate(t, [0, flipEnd + 40], [1, 1.03], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const numSize = Math.min(gh * 0.5, colW * 0.46);\n  const digits = String(hi).length;\n  const colLeft = left + gw + gap;\n\n  return (\n    <AbsoluteFill style={{ background: pal.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 55% 60% at 40% 42%, ${pal.light}, transparent 75%)`,\n        }}\n      />\n      <AbsoluteFill\n        style={{\n          opacity: glowIn,\n          background: `radial-gradient(ellipse ${gw * 0.9}px ${gh * 0.55}px at ${left + gw / 2}px ${top + gh * 0.2}px, ${pal.glow}, transparent 80%)`,\n        }}\n      />\n      <AbsoluteFill\n        style={{ transform: `scale(${cam})`, transformOrigin: \"50% 50%\" }}\n      >\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          {dots}\n        </svg>\n        <div\n          style={{\n            position: \"absolute\",\n            left: colLeft,\n            top: top - numSize * 0.16,\n            width: colW,\n            display: \"flex\",\n            alignItems: \"baseline\",\n            gap: 10 * u,\n            color: pal.fg,\n            opacity: numIn,\n            transform: `translateY(${(1 - numIn) * 14 * u}px)`,\n            filter: `blur(${(1 - numIn) * 6 * u}px)`,\n          }}\n        >\n          <span\n            style={{\n              fontFamily: sans,\n              fontWeight: 400,\n              fontSize: numSize,\n              lineHeight: 1,\n              letterSpacing: \"-0.04em\",\n              fontVariantNumeric: \"tabular-nums\",\n              width: `calc(${digits}ch - ${digits * 0.04}em)`,\n              textAlign: \"right\",\n              flexShrink: 0,\n            }}\n          >\n            {count}\n          </span>\n          <span\n            style={{\n              fontFamily: mono,\n              fontWeight: 400,\n              fontSize: numSize * 0.22,\n              lineHeight: 1,\n              letterSpacing: \"-0.01em\",\n              fontVariantNumeric: \"tabular-nums\",\n              color: pal.muted,\n            }}\n          >\n            / {n}\n          </span>\n        </div>\n        <div\n          style={{\n            position: \"absolute\",\n            left: colLeft,\n            top,\n            width: colW,\n            height: gh,\n            display: \"flex\",\n            flexDirection: \"column\",\n            justifyContent: \"flex-end\",\n          }}\n        >\n          <div\n            style={{\n              width: colW * EASE_IO(capIn),\n              height: Math.max(1, u),\n              marginBottom: 20 * u,\n              background: accent,\n              opacity: 0.8,\n            }}\n          />\n          <div\n            style={{\n              fontFamily: sans,\n              fontWeight: 400,\n              fontSize: 34 * u,\n              lineHeight: 1.15,\n              letterSpacing: \"-0.02em\",\n              textWrap: \"balance\",\n              color: pal.fg,\n              opacity: capIn,\n              transform: `translateY(${(1 - capIn) * 12 * u}px)`,\n              filter: `blur(${(1 - capIn) * 5 * u}px)`,\n            }}\n          >\n            {caption}\n          </div>\n        </div>\n      </AbsoluteFill>\n      <Grain opacity={pal.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/unit-grid.tsx"
    }
  ],
  "type": "registry:component"
}
