{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shape-pack",
  "title": "Shape Pack",
  "description": "Tiles pack together into a poster grid, one block locking a 3.2x stat.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/shape-pack/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/BricolageGrotesque\";\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  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"300\", \"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"600\"],\n  subsets: [\"latin\"],\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/** Full logo as on remocn.dev: 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 \"grain\" palette: the stat block that locks the poster.\nconst GRAIN = [\"#ffd8b4\", \"#e0703b\", \"#8a2c1d\", \"#2b1015\"] as const;\nconst GRAIN_BG = \"#070505\";\n\nexport interface ShapePackProps {\n  value?: number;\n  decimals?: number;\n  suffix?: string;\n  label?: string;\n  logo?: React.ReactNode;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\ninterface Block {\n  key: string;\n  c: number;\n  r: number;\n  w: number;\n  h: number;\n  tone: number;\n  at: number;\n}\n\nconst COLS = 12;\nconst ROWS = 8;\nconst FALL = 14;\nconst ACCENT_FALL = 11;\n\nconst BLOCKS: Block[] = [\n  { key: \"b1\", c: 0, r: 5, w: 4, h: 3, tone: 1, at: 8 },\n  { key: \"b2\", c: 4, r: 5, w: 3, h: 3, tone: 2, at: 13 },\n  { key: \"g\", c: 9, r: 4, w: 3, h: 4, tone: 3, at: 18 },\n  { key: \"h\", c: 7, r: 5, w: 2, h: 3, tone: 4, at: 23 },\n  { key: \"a\", c: 0, r: 0, w: 4, h: 5, tone: 0, at: 29 },\n  { key: \"e\", c: 4, r: 3, w: 3, h: 2, tone: 5, at: 35 },\n  { key: \"d\", c: 9, r: 0, w: 3, h: 4, tone: 6, at: 41 },\n  { key: \"f\", c: 7, r: 3, w: 2, h: 2, tone: 1, at: 47 },\n];\nconst SLOT = { c: 4, r: 0, w: 5, h: 3, at: 62 };\nconst LOCK = SLOT.at + ACCENT_FALL;\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.045)\",\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    guide: \"rgba(255,255,255,0.09)\",\n    tones: [\n      \"#E9E8E4\",\n      \"#26262A\",\n      \"#36363B\",\n      \"#2E2E33\",\n      \"#4A4A50\",\n      \"#5C5C62\",\n      \"#7C7B81\",\n    ],\n    onTone: \"#121214\",\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    key: \"rgba(255,255,255,0.85)\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    guide: \"rgba(18,18,20,0.1)\",\n    tones: [\n      \"#141416\",\n      \"#E4E2DC\",\n      \"#D3D1CA\",\n      \"#DCDAD3\",\n      \"#BEBCB5\",\n      \"#ABA9A2\",\n      \"#8F8D87\",\n    ],\n    onTone: \"#F3F2EF\",\n    grain: 0.045,\n  },\n} as const;\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={17}\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 drop(t: number, at: number, fall: number, from: number, to: number) {\n  const local = t - at;\n  if (local <= 0) return { y: from, landed: false, since: local };\n  if (local < fall) {\n    const k = local / fall;\n    return { y: from + (to - from) * k * k, landed: false, since: local };\n  }\n  return { y: to, landed: true, since: local - fall };\n}\n\nexport function ShapePack({\n  value = 3.2,\n  decimals = 1,\n  suffix = \"×\",\n  label = \"faster renders\",\n  logo,\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: ShapePackProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const u = H / 720;\n  const c = THEMES[theme];\n\n  const g = 10 * u;\n  const posterH = H * 0.74;\n  const cell = (posterH - g * (ROWS - 1)) / ROWS;\n  const posterW = cell * COLS + g * (COLS - 1);\n  const px = (W - posterW) / 2;\n  const py = (H - posterH) / 2;\n  const X = (col: number) => px + col * (cell + g);\n  const Y = (row: number) => py + row * (cell + g);\n  const span = (n: number) => n * cell + (n - 1) * g;\n\n  const guideIn = interpolate(t, [0, 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const guideOut = interpolate(t, [LOCK, LOCK + 18], [1, 0], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const guideFlash = interpolate(\n    t,\n    [LOCK, LOCK + 2, LOCK + 10],\n    [0, 1, 0],\n    CLAMP,\n  );\n\n  const impact = (s: number, amp: number) =>\n    s < 0 ? 0 : amp * Math.sin((Math.PI * s) / 5) * Math.exp(-s / 3.2);\n\n  const accentDrop = drop(\n    t,\n    SLOT.at,\n    ACCENT_FALL,\n    -span(SLOT.h) - 40 * u,\n    Y(SLOT.r),\n  );\n  const shake = impact(accentDrop.landed ? accentDrop.since : -1, 3.5 * u);\n  const flash = accentDrop.landed ? Math.exp(-accentDrop.since / 4) : 0;\n\n  const count = interpolate(t, [LOCK + 4, LOCK + 30], [0, 1], {\n    ...CLAMP,\n    easing: Easing.bezier(0.22, 1, 0.36, 1),\n  });\n  const statIn = interpolate(t, [LOCK + 2, LOCK + 16], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const labelIn = interpolate(t, [LOCK + 10, LOCK + 26], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const push = interpolate(t, [LOCK, LOCK + 70], [1, 1.018], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n\n  const gid = `sp${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  // Grain palette: a peach key light top-left, falling through ember into a\n  // deep wine bottom-right. The key drifts a few % across the shot.\n  const kx = 18 + Math.sin(t / 38) * 4;\n  const ky = 20 + Math.cos(t / 46) * 3;\n  const ang = 158 + Math.sin(t / 55) * 4;\n  const panel = [\n    `radial-gradient(60% 45% at ${kx}% ${ky}%, ${GRAIN[0]} 0%, transparent 60%)`,\n    `linear-gradient(${ang}deg, ${GRAIN[0]} 0%, ${GRAIN[1]} 24%, ${GRAIN[2]} 48%, ${GRAIN[3]} 70%, transparent 85%)`,\n    GRAIN_BG,\n  ].join(\", \");\n  const shown = (value * (0.3 + 0.7 * count)).toFixed(decimals);\n  const statH = span(SLOT.h);\n  const statW = span(SLOT.w);\n  const pad = 20 * u;\n  const numF = Math.min(\n    statH * 0.6,\n    (statW - pad * 2) / Math.max(1.4, (shown.length + suffix.length) * 0.58),\n  );\n\n  return (\n    <AbsoluteFill style={{ background: c.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 70% 80% at 40% 30%, ${c.key}, transparent 70%)`,\n        }}\n      />\n      <AbsoluteFill\n        style={{\n          transform: `translateY(${shake}px) scale(${push})`,\n          transformOrigin: \"50% 50%\",\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            opacity: guideIn * guideOut,\n          }}\n        >\n          {Array.from({ length: COLS * ROWS }, (_, i) => {\n            const col = i % COLS;\n            const row = Math.floor(i / COLS);\n            return (\n              <div\n                key={`m${col}-${row}`}\n                style={{\n                  position: \"absolute\",\n                  left: X(col),\n                  top: Y(row),\n                  width: cell,\n                  height: cell,\n                  boxSizing: \"border-box\",\n                  border: `${Math.max(1, u)}px solid ${guideFlash > 0.01 ? accent : c.guide}`,\n                  opacity: guideFlash > 0.01 ? 0.35 + guideFlash * 0.4 : 1,\n                }}\n              />\n            );\n          })}\n        </div>\n\n        {BLOCKS.map((b) => {\n          const h = span(b.h);\n          const d = drop(t, b.at, FALL, -h - 40 * u, Y(b.r));\n          const hop = d.landed ? -impact(d.since, 2.5 * u) : 0;\n          const fill = c.tones[b.tone];\n          return (\n            <div\n              key={b.key}\n              style={{\n                position: \"absolute\",\n                left: X(b.c),\n                top: d.y + hop,\n                width: span(b.w),\n                height: h,\n                background: fill,\n                borderRadius: 2 * u,\n              }}\n            >\n              {b.key === \"a\" ? (\n                <div\n                  style={{\n                    position: \"absolute\",\n                    left: pad,\n                    bottom: pad,\n                  }}\n                >\n                  {logo ?? <RemocnLogo fontSize={30 * u} color={c.onTone} />}\n                </div>\n              ) : null}\n            </div>\n          );\n        })}\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: X(SLOT.c),\n            top: accentDrop.y,\n            width: statW,\n            height: statH,\n            background: panel,\n            borderRadius: 2 * u,\n            overflow: \"hidden\",\n          }}\n        >\n          <svg\n            width=\"100%\"\n            height=\"100%\"\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              opacity: 0.22,\n              mixBlendMode: \"overlay\",\n            }}\n            aria-hidden\n          >\n            <filter id={`${gid}n`}>\n              <feTurbulence\n                type=\"fractalNoise\"\n                baseFrequency=\"0.75\"\n                numOctaves={2}\n                seed={9}\n                stitchTiles=\"stitch\"\n              />\n              <feColorMatrix values=\"0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 1 0\" />\n            </filter>\n            <rect width=\"100%\" height=\"100%\" filter={`url(#${gid}n)`} />\n          </svg>\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              background: \"#FFFFFF\",\n              opacity: flash * 0.3,\n            }}\n          />\n          <div\n            style={{\n              position: \"absolute\",\n              left: pad,\n              top: pad - 2 * u,\n              fontFamily: sans,\n              fontWeight: 400,\n              fontSize: 20 * u,\n              lineHeight: 1.2,\n              letterSpacing: \"-0.01em\",\n              color: \"rgba(43,16,21,0.82)\",\n              opacity: labelIn,\n              transform: `translateY(${(1 - labelIn) * 6 * u}px)`,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {label}\n          </div>\n          <div\n            style={{\n              position: \"absolute\",\n              left: pad - 0.04 * numF,\n              bottom: pad - 0.18 * numF,\n              fontFamily: sans,\n              fontWeight: 400,\n              fontSize: numF,\n              lineHeight: 1,\n              letterSpacing: \"-0.028em\",\n              fontVariantNumeric: \"tabular-nums\",\n              color: \"#FFF4EC\",\n              whiteSpace: \"nowrap\",\n              opacity: statIn,\n              transform: `translateY(${(1 - statIn) * 14 * u}px)`,\n            }}\n          >\n            {shown}\n            <span style={{ fontWeight: 300, marginLeft: \"0.02em\" }}>\n              {suffix}\n            </span>\n          </div>\n        </div>\n      </AbsoluteFill>\n      <Grain opacity={c.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/shape-pack.tsx"
    }
  ],
  "type": "registry:component"
}
