{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "paper-tape",
  "title": "Paper tape",
  "description": "Feeding paper with shared coordinates for writing, markers, and attached notes.",
  "registryDependencies": [
    "@jbm/tokens",
    "@jbm/paper",
    "@jbm/tape-marker"
  ],
  "files": [
    {
      "path": "registry/jbm/ui/paper-tape.tsx",
      "content": "import type { CSSProperties, ReactNode } from \"react\"\nimport { color } from \"../lib/tokens\"\nimport { paperShadow } from \"./paper\"\nimport { TapeMarker } from \"./tape-marker\"\n\nconst positive = (n: number) =>\n  Number.isFinite(n) ? Math.min(Number.MAX_SAFE_INTEGER / 2, Math.max(0, n)) : 0\n/** Paper coordinate to screen coordinate. Attachments use the same origin as the printed marks. */\nexport const paperAt = (length: number, coordinate: number) =>\n  positive(length) - coordinate\nexport type TapeAttachment = { id: string; at: number; content: ReactNode }\nexport type PaperTapeProps = {\n  length: number\n  window?: number\n  thickness?: number\n  direction?: \"horizontal\" | \"vertical\"\n  markers?: { id: string; at: number; label?: string }[]\n  attachments?: TapeAttachment[]\n  style?: CSSProperties\n}\n/** A feeding strip. Only marks inside the visible window are rendered, even for very long runs. */\nexport function PaperTape({\n  length,\n  window = 400,\n  thickness = 48,\n  direction = \"horizontal\",\n  markers = [],\n  attachments = [],\n  style,\n}: PaperTapeProps) {\n  const len = positive(length),\n    visible = Math.min(len, positive(window)),\n    thick = Math.max(16, positive(thickness))\n  const horizontal = direction === \"horizontal\"\n  const marks: ReactNode[] = []\n  const first = Math.max(0, Math.floor((len - visible - 52) / 72))\n  const last = Math.floor((len - 24) / 72)\n  for (let i = first; i <= last; i++) {\n    const x = len - (i * 72 + 24)\n    if (x <= 16) continue\n    for (const row of [0.36, 0.64])\n      marks.push(\n        <line\n          key={`${i}-${row}`}\n          x1={horizontal ? Math.max(16, x - 40) : thick * row}\n          y1={horizontal ? thick * row : Math.max(16, x - 40)}\n          x2={horizontal ? x : thick * row}\n          y2={horizontal ? thick * row : x}\n          stroke={color.ink}\n          strokeWidth={3}\n          strokeLinecap=\"round\"\n        />\n      )\n  }\n  const placed = [\n    ...markers.map((m) => ({\n      ...m,\n      marker: true,\n      content: <TapeMarker label={m.label} />,\n    })),\n    ...attachments.map((a) => ({ ...a, marker: false })),\n  ]\n  return (\n    <div\n      style={{\n        position: \"relative\",\n        width: horizontal ? positive(window) : thick + 180,\n        height: horizontal ? thick + 150 : positive(window),\n        maxWidth: \"100%\",\n        overflow: \"hidden\",\n        ...style,\n      }}\n    >\n      <div\n        style={{\n          position: \"absolute\",\n          left: horizontal ? 0 : 70,\n          top: horizontal ? 48 : 0,\n          width: horizontal ? visible : thick,\n          height: horizontal ? thick : visible,\n          boxSizing: \"border-box\",\n          background: color.card,\n          border: visible > 0 ? `2px solid ${color.ink}` : undefined,\n          boxShadow: paperShadow,\n        }}\n      >\n        <svg\n          width=\"100%\"\n          height=\"100%\"\n          aria-hidden\n          style={{ position: \"absolute\", inset: 0, overflow: \"hidden\" }}\n        >\n          {marks}\n        </svg>\n        {placed\n          .filter(\n            (a) =>\n              Number.isFinite(a.at) &&\n              a.at >= 0 &&\n              a.at <= len &&\n              paperAt(len, a.at) <= visible\n          )\n          .map((a) => (\n            <div\n              key={a.id}\n              style={{\n                position: \"absolute\",\n                left: horizontal\n                  ? paperAt(len, a.at)\n                  : a.marker\n                    ? thick / 2\n                    : thick - 8,\n                top: horizontal\n                  ? a.marker\n                    ? -12\n                    : thick - 8\n                  : paperAt(len, a.at),\n                transform: horizontal\n                  ? \"translateX(-50%)\"\n                  : a.marker\n                    ? \"translate(-50%, -50%) rotate(90deg)\"\n                    : \"translateY(-50%)\",\n              }}\n            >\n              {a.content}\n            </div>\n          ))}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "src/jbm/ui/paper-tape.tsx"
    }
  ],
  "type": "registry:ui"
}