{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rebuild-screens",
  "title": "RebuildScreens",
  "description": "A phone screen built piece by piece, then rebuilt on new screens on each cue; Stamp for a loud sticker.",
  "dependencies": [
    "remotion"
  ],
  "registryDependencies": [
    "@jbm/motion-hooks",
    "@jbm/pop",
    "@jbm/ui-bits",
    "@jbm/paper"
  ],
  "files": [
    {
      "path": "registry/jbm/motion/rebuild-screens.tsx",
      "content": "import * as React from \"react\";\nimport { interpolate, Easing } from \"remotion\";\nimport { useIn, useSec } from \"./hooks\";\nimport { Pop } from \"./pop\";\nimport { PhoneFrame, Piece, type PieceKind } from \"../ui/ui-bits\";\nimport { Sticker } from \"../ui/paper\";\n\n/**\n * A phone screen that gets built piece by piece, then built AGAIN on a new screen, and again.\n * `pieces` enter on their own cue in the first screen; every cue in `again` adds a screen (offset, tilted,\n * in front) whose pieces are rebuilt in a quick stagger. The group re-centres as screens arrive.\n * `sticker` stamps a loud word over the pile. Sized by `w`/`h` (the block's box).\n */\nexport type ScreenPiece = { kind: PieceKind; at: number };\n\nconst ORDER: PieceKind[] = [\"card\", \"input\", \"button\"];\n\nexport function RebuildScreens({\n  w,\n  h,\n  pieces,\n  again = [],\n  sticker,\n  rebuildStep = 0.22,\n  phoneScale = 1,\n}: {\n  w: number;\n  h: number;\n  pieces: ScreenPiece[];\n  again?: number[];\n  sticker?: { text: string; at: number };\n  rebuildStep?: number;\n  phoneScale?: number;\n}) {\n  const sec = useSec();\n  const n = 1 + again.length;\n  if (!Number.isFinite(phoneScale) || phoneScale <= 0) throw new Error(\"phoneScale must be positive and finite\");\n  const pw = phoneScale === 1 ? Math.min(470, Math.round(w * 0.5), Math.round(h * 0.47)) : Math.min(Math.min(470, Math.round(w * 0.5), Math.round(h * 0.47)) * phoneScale, w * 0.9 / (1 + (n - 1) * 0.42), h * 0.9 / (1.82 * (1 + (n - 1) * 0.09)));\n  const ph = Math.round(pw * 1.82);\n  const dx = Math.round(pw * 0.42);\n  const dy = Math.round(ph * 0.09);\n  // How many screens have arrived (eased) drives the group's centring shift.\n  const arrived = again.reduce((s, a) => s + interpolate(sec, [a, a + 0.6], [0, 1], { extrapolateLeft: \"clamp\", extrapolateRight: \"clamp\", easing: Easing.out(Easing.cubic) }), 0);\n  const groupW = pw + arrived * dx;\n  const baseX = (w - groupW) / 2;\n  const baseY = h - ph - (h - ph - arrived * dy) / 2 - 0; // pile grows upward; keep it vertically centred\n  const cursorOn = Math.floor(sec * 2.2) % 2 === 0;\n  const rot = [0, 4, -3, 5, -4];\n  const pieceW = Math.round(pw * 0.7);\n  const sorted = [...pieces].sort((a, b) => ORDER.indexOf(a.kind) - ORDER.indexOf(b.kind));\n\n  return (\n    <div style={{ position: \"relative\", width: w, height: h }}>\n      {Array.from({ length: n }).map((_, i) => {\n        const startAt = i === 0 ? 0 : again[i - 1];\n        return (\n          <Screen key={i} first={i === 0} startAt={startAt} x={baseX + i * dx} y={baseY - i * dy} dx={dx} dy={dy}>\n            <PhoneFrame w={pw} h={ph} rotate={rot[i % rot.length]} gap={Math.round(pw * 0.07)}>\n              {sorted.map((pc) => {\n                const at = i === 0 ? pc.at : startAt + 0.12 + pieces.findIndex((q) => q.kind === pc.kind) * rebuildStep;\n                return (\n                  <Pop key={pc.kind} at={at} from=\"scale\" dist={0}>\n                    <Piece kind={pc.kind} w={pieceW} cursorOn={cursorOn} />\n                  </Pop>\n                );\n              })}\n            </PhoneFrame>\n          </Screen>\n        );\n      })}\n      {sticker ? <Stamp at={sticker.at} text={sticker.text} left={Math.max(0, baseX - pw * 0.25)} top={Math.round(h * 0.06)} size={Math.round(pw * 0.22)} /> : null}\n    </div>\n  );\n}\n\n/** One screen of the pile: the first is always there, the others spring in from the upper right. */\nfunction Screen({ first, startAt, x, y, dx, dy, children }: { first: boolean; startAt: number; x: number; y: number; dx: number; dy: number; children: React.ReactNode }) {\n  const spring = useIn(startAt, { damping: 12, stiffness: 110 });\n  const p = first ? 1 : spring;\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        left: x,\n        top: y,\n        opacity: p,\n        transform: `translate(${(1 - p) * dx * 1.2}px, ${(1 - p) * -dy * 2}px) scale(${0.8 + 0.2 * p})`,\n        transformOrigin: \"50% 80%\",\n      }}\n    >\n      {children}\n    </div>\n  );\n}\n\n/** Slams in: scale 1.6→1 with a slight settle, used for the loud word. */\nexport function Stamp({ at, text, left, top, size, rotate = -7 }: { at: number; text: string; left: number; top: number; size: number; rotate?: number }) {\n  const p = useIn(at, { damping: 9, stiffness: 160 });\n  const sc = interpolate(p, [0, 1], [1.7, 1]);\n  return (\n    <div style={{ position: \"absolute\", left, top, opacity: Math.min(1, p * 2), transform: `scale(${sc})`, transformOrigin: \"50% 50%\" }}>\n      <Sticker size={size} rotate={rotate}>\n        {text}\n      </Sticker>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "src/jbm/motion/rebuild-screens.tsx"
    }
  ],
  "type": "registry:component"
}