{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cajon",
  "title": "Cajón",
  "description": "A fixed-size filing drawer with complete folders packed front to back.",
  "registryDependencies": [
    "@jbm/tokens",
    "@jbm/scene-geometry",
    "@jbm/folder"
  ],
  "files": [
    {
      "path": "registry/jbm/motion/cajon.tsx",
      "content": "import { color, font } from \"../lib/tokens\"\nimport { unit, type Box } from \"../lib/geometry\"\nimport { FolderOutline } from \"../ui/folder\"\n\nexport type DrawerFolder = { name: string; accent?: boolean; pulled?: number }\nexport type CajonProps = Partial<Box> & {\n  folders: readonly DrawerFolder[]\n  open?: number\n}\n\n/** Fixed physical bounds. Index zero is nearest the drawer front. */\nexport function cajonLayout({\n  x = 0,\n  y = 0,\n  w = 420,\n  h = 420,\n  folders,\n  open = 1,\n}: CajonProps) {\n  const p = unit(open)\n  const front = y - 22 + 104 * p\n  const folderHeight = ((w - 52) * 150) / 205\n  const spacing = 48 / Math.max(5, folders.length - 1)\n  const frontHeight = folderHeight + 8\n  return {\n    x,\n    y,\n    w,\n    h,\n    front,\n    frontHeight,\n    folders: folders.map((f, i) => {\n      const top =\n        front +\n        32 -\n        62 * p -\n        i * spacing * p -\n        unit(f.pulled ?? 0) * p * (folderHeight + 24)\n      const tabWidth = Math.min((w - 52) * 0.55, 28 + f.name.length * 9)\n      const tabX = x + 26 + ((i % 3) * (w - 52 - tabWidth)) / 2\n      return {\n        x: x + 26,\n        y: top,\n        w: w - 52,\n        h: folderHeight,\n        tabX,\n        tabWidth,\n        anchor: { x: tabX + tabWidth / 2, y: top + 10 },\n      }\n    }),\n  }\n}\n\n/** A drawer alone. Complete folders are occluded by its front, never shortened. */\nexport function Cajon(props: CajonProps) {\n  const l = cajonLayout(props)\n  return (\n    <g\n      role=\"img\"\n      aria-label={`Filing drawer, ${props.folders.length} folders`}\n      stroke={color.ink}\n      strokeWidth={2}\n      strokeLinejoin=\"round\"\n    >\n      <path\n        d={`M${l.x + 20} ${l.y + 10}H${l.x + l.w - 20}L${l.x + l.w} ${l.front + 32}H${l.x}Z`}\n        fill={color.ink}\n      />\n      {[...props.folders.keys()].reverse().map((i) => {\n        const f = props.folders[i],\n          q = l.folders[i]\n        return (\n          <g key={i} data-folder-index={i}>\n            <FolderOutline {...q} fill={f.accent ? color.accent : color.card} />\n            <path d={`M${q.x + 5} ${q.y + 38}H${q.x + q.w - 5}`} fill=\"none\" />\n            <text\n              x={q.tabX + 8}\n              y={q.y + 16}\n              fontFamily={font.mono}\n              fontSize={13}\n              stroke=\"none\"\n              fill={f.accent ? color.card : color.ink}\n            >\n              {f.name.length > 16 ? f.name.slice(0, 15) + \"…\" : f.name}\n            </text>\n          </g>\n        )\n      })}\n      <path\n        d={`M${l.x + 20} ${l.y + 10}L${l.x} ${l.front + 32}V${l.front + 32 + l.frontHeight}L${l.x + 20} ${l.y + 10 + l.frontHeight}ZM${l.x + l.w - 20} ${l.y + 10}L${l.x + l.w} ${l.front + 32}V${l.front + 32 + l.frontHeight}L${l.x + l.w - 20} ${l.y + 10 + l.frontHeight}Z`}\n        fill={color.bg}\n      />\n      <rect\n        x={l.x}\n        y={l.front + 32}\n        width={l.w}\n        height={l.frontHeight}\n        rx={2}\n        fill={color.card}\n      />\n      <rect\n        x={l.x + l.w / 2 - 36}\n        y={l.front + 63}\n        width={72}\n        height={25}\n        rx={3}\n        fill={color.bg}\n      />\n      <path\n        d={`M${l.x + l.w / 2 - 27} ${l.front + 72}h54v10h-54Z`}\n        fill={color.ink}\n      />\n    </g>\n  )\n}\n",
      "type": "registry:component",
      "target": "src/jbm/motion/cajon.tsx"
    }
  ],
  "type": "registry:component"
}