{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "catalog",
  "title": "Catalog",
  "description": "Catalogue sheet of interface pieces that unfolds a second row of design-token glyphs.",
  "dependencies": [
    "remotion"
  ],
  "registryDependencies": [
    "@jbm/tokens",
    "@jbm/motion-hooks",
    "@jbm/pop",
    "@jbm/paper",
    "@jbm/ui-bits",
    "@jbm/rebuild-screens"
  ],
  "files": [
    {
      "path": "registry/jbm/motion/catalog.tsx",
      "content": "import * as React from \"react\";\nimport { color } from \"../lib/tokens\";\nimport { useIn, useProgress, useSec } from \"./hooks\";\nimport { Pop } from \"./pop\";\nimport { Paper, Sticker, Caption } from \"../ui/paper\";\nimport { Badge, Piece, TokenGlyph, type PieceKind, type TokenKind } from \"../ui/ui-bits\";\nimport { Stamp } from \"./rebuild-screens\";\n\n/**\n * A catalogue sheet: a large paper card with labelled slots. The top row holds interface pieces\n * (button, card, input), each arriving on its cue and getting a \"tested\" check a beat later. At\n * `tokensAt` the sheet unfolds downward to reveal a second row of design-token glyphs (color, type,\n * spacing) that fill on their own cues. A `stamp` sticker lands on the sheet.\n */\nexport type CatalogItem = { kind: PieceKind; label: string; at: number };\nexport type CatalogToken = { kind: TokenKind; label: string; at: number };\n\nexport function Catalog({\n  w,\n  at,\n  title,\n  items,\n  tokens = [],\n  tokensAt,\n  stamp,\n  checkDelay = 0.5,\n}: {\n  w: number;\n  at: number;\n  title?: string;\n  items: CatalogItem[];\n  tokens?: CatalogToken[];\n  tokensAt?: number;\n  stamp?: { text: string; at: number };\n  checkDelay?: number;\n}) {\n  const sec = useSec();\n  const pad = Math.round(w * 0.038);\n  const gap = Math.round(w * 0.026);\n  const cols = Math.max(items.length, tokens.length, 1);\n  const slotW = Math.floor((w - pad * 2 - gap * (cols - 1)) / cols);\n  const pieceW = Math.round(slotW * 0.8);\n  const pieceH = Math.round(pieceW * 0.78); // the card, the tallest piece\n  const capSize = Math.round(slotW * 0.13);\n  const badge = Math.round(slotW * 0.19);\n  const slotH = pieceH + capSize + Math.round(slotW * 0.3);\n  const tokH = Math.round(slotW * 0.9);\n  const cursorOn = Math.floor(sec * 2.2) % 2 === 0;\n  const sheet = useIn(at, { damping: 13 });\n  const unfoldAt = tokensAt ?? (tokens.length ? tokens[0].at - 0.6 : 1e6);\n  const unfold = useProgress(unfoldAt, 1, 0.55); // no token row: unfoldAt is never reached\n  const rowGap = Math.round(gap * 1.2);\n  const tokenBlockH = rowGap + 3 + rowGap + tokH;\n\n  return (\n    <div style={{ position: \"relative\", width: w, opacity: sheet, transform: `translateY(${(1 - sheet) * 40}px)` }}>\n      <Paper tone=\"paper\" w={w} radius={30} style={{ padding: pad, paddingTop: title ? pad + 30 : pad }}>\n        <div style={{ display: \"flex\", gap }}>\n          {items.map((it, k) => (\n            <Slot key={k} w={slotW} h={slotH} at={it.at}>\n              <div style={{ position: \"relative\", height: pieceH, display: \"flex\", alignItems: \"center\" }}>\n                <Piece kind={it.kind} w={pieceW} cursorOn={cursorOn} />\n                <Pop at={it.at + checkDelay} from=\"scale\" dist={0} style={{ position: \"absolute\", right: -badge * 0.4, top: (pieceH - Math.round(pieceW * (it.kind === \"card\" ? 0.78 : 0.32))) / 2 - badge * 0.4 }}>\n                  <Badge kind=\"check\" size={badge} />\n                </Pop>\n              </div>\n              <Caption size={capSize}>{it.label}</Caption>\n            </Slot>\n          ))}\n        </div>\n        {tokens.length ? (\n          <div style={{ height: tokenBlockH * unfold, overflow: \"hidden\", opacity: Math.min(1, unfold * 1.5) }}>\n            <div style={{ height: rowGap }} />\n            <div style={{ height: 0, borderTop: `3px dashed ${color.line}` }} />\n            <div style={{ height: rowGap }} />\n            <div style={{ display: \"flex\", gap }}>\n              {tokens.map((tk, k) => (\n                <Slot key={k} w={slotW} h={tokH} at={tk.at}>\n                  <TokenGlyph kind={tk.kind} size={Math.round(slotW * 0.56)} />\n                  <Caption size={capSize}>{tk.label}</Caption>\n                </Slot>\n              ))}\n            </div>\n          </div>\n        ) : null}\n      </Paper>\n      {title ? (\n        <div style={{ position: \"absolute\", left: pad * 0.6, top: -30 }}>\n          <Sticker tone=\"ink\" size={Math.round(w * 0.04)} rotate={-2}>\n            {title}\n          </Sticker>\n        </div>\n      ) : null}\n      {stamp ? <Stamp at={stamp.at} text={stamp.text} left={w * 0.44} top={(title ? pad + 30 : pad) + slotH + tokenBlockH * unfold + pad - Math.round(w * 0.062) * 1.1} size={Math.round(w * 0.062)} rotate={4} /> : null}\n    </div>\n  );\n}\n\n/** Dashed slot on the sheet; its content pops in on `at`. */\nfunction Slot({ w, h, at, children }: { w: number; h: number; at: number; children: React.ReactNode }) {\n  return (\n    <div style={{ width: w, height: h, boxSizing: \"border-box\", border: `3px dashed ${color.line}`, borderRadius: 22, display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\" }}>\n      <Pop at={at} from=\"up\" dist={24} style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: Math.round(h * 0.06) }}>\n        {children}\n      </Pop>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/jbm/motion/catalog.tsx"
    }
  ],
  "type": "registry:component"
}