{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shot-clock",
  "title": "Shot Clock",
  "description": "A shot clock counts down to 0.0 in red, then 'SHIPPED' lands 'at the buzzer'.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/shot-clock/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/AzeretMono\";\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: mono } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface ShotClockProps {\n  /** Seconds on the clock when it starts. Whole seconds, then tenths below `tenthsBelow`. */\n  from?: number;\n  /** Below this many seconds the clock shows tenths and turns red. */\n  tenthsBelow?: number;\n  /** The big word that slams in at the buzzer. Set in caps. */\n  title?: string;\n  /** The line under it. */\n  subtitle?: string;\n  /** Small label over the clock, left. */\n  label?: string;\n  /** Small label over the clock, right. */\n  period?: string;\n  /** Colour of the last seconds and the buzzer light. */\n  alarm?: string;\n  /** The live marker next to the period label. */\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\n// Beat sheet (internal clock = frame + 2, 30fps, speed 1, from 24):\n//   0–9    \"24\" drops in; the light bar over it and the possession bar under\n//          it draw out from the centre, labels fade up\n//  14–64   the whole seconds tick down, one every 2.6 frames, while the clock\n//          pushes in slowly (7% by the buzzer)\n//  64      \"5.0\": the clock turns red with a kick and starts counting tenths,\n//          one per frame; the possession bar runs out with it\n// 114      0.0, the buzzer: the frame flashes with the \"rise\" gradient, the\n//          light bar goes solid red and stays lit; the 0.0 holds 6 frames\n// 120–131  the clock shrinks up under the light; the title slams in at the\n//          centre as it clears, the subtitle four frames after it\n// 135–     poster hold\nconst DROP_AT = 0;\nconst TICK_AT = 14;\nconst TICK = 2.6;\nconst TENTH = 1;\nconst SHRINK_DELAY = 6;\nconst SHRINK_LEN = 11;\n\nconst SLAM = Easing.bezier(0.22, 1, 0.36, 1);\nconst MOVE = Easing.bezier(0.7, 0, 0.2, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\n// gradients.sh \"rise\": bg #030306, #fff1e2 #ff8a3d #a3301a #2a0d0a.\nconst RISE = [\"#FFF1E2\", \"#FF8A3D\", \"#A3301A\", \"#2A0D0A\"] as const;\n\nconst THEMES = {\n  dark: {\n    stage: \"#0A0A0B\",\n    ink: \"#F3F2EF\",\n    dim: \"#86858C\",\n    track: \"#1C1C20\",\n    flashOp: 1,\n    titleFill: `linear-gradient(180deg, ${RISE[0]} 8%, ${RISE[1]} 55%, #E0502A 100%)`,\n  },\n  light: {\n    stage: \"#F5F4F0\",\n    ink: \"#121214\",\n    dim: \"#7E7D77\",\n    track: \"#E2E0D9\",\n    flashOp: 0.9,\n    titleFill: `linear-gradient(180deg, ${RISE[1]} 0%, #E0502A 50%, ${RISE[2]} 100%)`,\n  },\n} as const;\n\nfunction ramp(t: number, from: number, len: number) {\n  return interpolate(t, [from, from + len], [0, 1], CLAMP);\n}\n\n/** A short hit: up in 1 frame, eases back over `len`. */\nfunction kick(d: number, len: number) {\n  if (d < 0 || d > len + 1) return 0;\n  if (d < 1) return d;\n  const k = 1 - (d - 1) / len;\n  return k * k;\n}\n\nexport function ShotClock({\n  from = 24,\n  tenthsBelow = 5,\n  title = \"Shipped\",\n  subtitle = \"at the buzzer\",\n  label = \"Shot clock\",\n  period = \"Q4 · 00:24\",\n  alarm = \"#FF4433\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: ShotClockProps) {\n  // Starts two frames in, so frame 0 already shows the drop.\n  const t = useCurrentFrame() * speed + 2;\n  const { width: W, height: H } = useVideoConfig();\n  const c = THEMES[theme];\n  const u = Math.min(W / 1280, H / 720);\n\n  const start = Math.max(tenthsBelow + 1, Math.floor(from));\n  const wholeTicks = start - tenthsBelow;\n  const redAt = TICK_AT + wholeTicks * TICK;\n  const buzzAt = redAt + tenthsBelow * 10 * TENTH;\n\n  // Clock value in tenths of a second.\n  let tenths: number;\n  if (t < TICK_AT) tenths = start * 10;\n  else if (t < redAt) tenths = (start - Math.floor((t - TICK_AT) / TICK)) * 10;\n  else tenths = Math.max(0, tenthsBelow * 10 - Math.floor((t - redAt) / TENTH));\n  const red = t >= redAt;\n  const buzzed = t >= buzzAt;\n  const text = red\n    ? `${Math.floor(tenths / 10)}.${tenths % 10}`\n    : `${Math.round(tenths / 10)}`;\n\n  // Possession bar: continuous, so it runs smoothly between ticks.\n  const remaining = interpolate(\n    t,\n    [TICK_AT, redAt, buzzAt],\n    [start, tenthsBelow, 0],\n    CLAMP,\n  );\n  const barFrac = remaining / start;\n\n  // ── Layout ────────────────────────────────────────────────────────────────\n  const clockFs = 380 * u;\n  const blockW = 820 * u;\n  const cx = W / 2;\n  const clockCY = H / 2 + 14 * u;\n  const lightY = clockCY - 0.5 * clockFs - 34 * u;\n  const barY = clockCY + 0.5 * clockFs + 18 * u;\n\n  const drop = SLAM(ramp(t, DROP_AT, 9));\n  const draw = SLAM(ramp(t, DROP_AT + 2, 12));\n  const labelsIn = ramp(t, 5, 8);\n  const redKick = kick(t - redAt, 8);\n  const buzzKick = kick(t - buzzAt, 10);\n  const flash = buzzed ? kick(t - buzzAt, 9) : 0;\n\n  // Shrink to the poster position under the light.\n  const shrink = MOVE(ramp(t, buzzAt + SHRINK_DELAY, SHRINK_LEN));\n  const posterScale = 0.38;\n  const posterCY = 150 * u;\n  // A slow push-in while the clock runs down builds the pressure.\n  const push = Easing.bezier(\n    0.65,\n    0,\n    0.35,\n    1,\n  )(ramp(t, TICK_AT, buzzAt - TICK_AT));\n  const groupScale =\n    interpolate(shrink, [0, 1], [1 + 0.07 * push, posterScale]) *\n    (1 + 0.03 * redKick + 0.04 * buzzKick);\n  const groupDY = interpolate(shrink, [0, 1], [0, posterCY - clockCY]);\n  const sideFade = 1 - ramp(shrink, 0, 0.4);\n\n  const titleAt = buzzAt + SHRINK_DELAY + SHRINK_LEN - 4;\n  const titleIn = SLAM(ramp(t, titleAt, 7));\n  const subIn = SLAM(ramp(t, titleAt + 4, 9));\n  const titleHit = kick(t - (titleAt + 3), 8);\n\n  const clockColor = red ? alarm : c.ink;\n  const lightOn = buzzed;\n\n  const chars = text.split(\"\");\n\n  const smallLabel: React.CSSProperties = {\n    fontFamily: mono,\n    fontWeight: 400,\n    fontSize: 17 * u,\n    letterSpacing: \"0.16em\",\n    textTransform: \"uppercase\",\n    color: c.dim,\n  };\n\n  // Rise ground, lingering after the buzzer.\n  const ground = [\n    `radial-gradient(70% 55% at 50% 108%, color-mix(in oklab, ${RISE[1]} 55%, transparent) 0%, transparent 70%)`,\n    `radial-gradient(45% 35% at 50% 100%, color-mix(in oklab, ${RISE[0]} 35%, transparent) 0%, transparent 70%)`,\n    `radial-gradient(90% 70% at 50% 115%, color-mix(in oklab, ${RISE[2]} 70%, transparent) 0%, transparent 80%)`,\n  ].join(\", \");\n  const groundOp = (theme === \"dark\" ? 0.75 : 0.45) * ramp(t, buzzAt, 6);\n\n  return (\n    <AbsoluteFill style={{ background: c.stage, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          background: ground,\n          opacity: groundOp,\n        }}\n      />\n\n      {/* Clock group: light, labels, digits, possession bar. */}\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          transform: `translateY(${groupDY}px) scale(${groupScale})`,\n          transformOrigin: `${cx}px ${clockCY}px`,\n        }}\n      >\n        {/* Buzzer light. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: cx - (blockW / 2) * draw,\n            top: lightY,\n            width: blockW * draw,\n            height: 14 * u,\n            background: lightOn ? alarm : c.track,\n          }}\n        />\n        <div\n          style={{\n            ...smallLabel,\n            position: \"absolute\",\n            left: cx - blockW / 2,\n            top: lightY - 40 * u,\n            opacity: labelsIn * sideFade,\n          }}\n        >\n          {label}\n        </div>\n        <div\n          style={{\n            ...smallLabel,\n            position: \"absolute\",\n            right: W - (cx + blockW / 2),\n            top: lightY - 40 * u,\n            opacity: labelsIn * sideFade,\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: 10 * u,\n          }}\n        >\n          <div\n            style={{\n              width: 8 * u,\n              height: 8 * u,\n              background: accent,\n              opacity: buzzed ? 0 : Math.floor(t / 15) % 2 === 0 ? 1 : 0.35,\n            }}\n          />\n          {period}\n        </div>\n\n        {/* Digits. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            right: 0,\n            top: clockCY - clockFs / 2,\n            height: clockFs,\n            display: \"flex\",\n            justifyContent: \"center\",\n            alignItems: \"center\",\n            fontFamily: mono,\n            fontWeight: 500,\n            fontSize: clockFs,\n            lineHeight: 1,\n            letterSpacing: \"-0.04em\",\n            color: clockColor,\n            transform: `translateY(${-(1 - drop) * 90 * u}px)`,\n            opacity: drop,\n          }}\n        >\n          {chars.map((ch, i) =>\n            ch === \".\" ? (\n              <span\n                key={i}\n                style={{\n                  width: \"0.42em\",\n                  display: \"inline-flex\",\n                  justifyContent: \"center\",\n                }}\n              >\n                .\n              </span>\n            ) : (\n              <span key={i} style={{ display: \"inline-block\" }}>\n                {ch}\n              </span>\n            ),\n          )}\n        </div>\n\n        {/* Possession bar, running out towards the centre. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: cx - (blockW / 2) * draw,\n            top: barY,\n            width: blockW * draw,\n            height: 6 * u,\n            background: c.track,\n            opacity: sideFade,\n          }}\n        />\n        <div\n          style={{\n            position: \"absolute\",\n            left: cx - (blockW / 2) * draw * barFrac,\n            top: barY,\n            width: blockW * draw * barFrac,\n            height: 6 * u,\n            background: red ? alarm : c.ink,\n            opacity: sideFade,\n          }}\n        />\n      </div>\n\n      {/* Title. */}\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          right: 0,\n          top: H / 2 - 120 * u,\n          display: \"flex\",\n          justifyContent: \"center\",\n          opacity: ramp(titleIn, 0, 0.25),\n          transform: `scale(${(1.22 - 0.22 * titleIn) * (1 + 0.025 * titleHit)})`,\n          transformOrigin: `${cx}px ${100 * u}px`,\n        }}\n      >\n        <div\n          style={{\n            fontFamily: mono,\n            fontWeight: 500,\n            fontSize: 176 * u,\n            lineHeight: 1.15,\n            letterSpacing: \"-0.05em\",\n            textTransform: \"uppercase\",\n            whiteSpace: \"nowrap\",\n            backgroundImage: c.titleFill,\n            WebkitBackgroundClip: \"text\",\n            backgroundClip: \"text\",\n            color: \"transparent\",\n          }}\n        >\n          {title}\n        </div>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          right: 0,\n          top: H / 2 + 112 * u,\n          display: \"flex\",\n          justifyContent: \"center\",\n          fontFamily: mono,\n          fontWeight: 400,\n          fontSize: 46 * u,\n          letterSpacing: \"0.04em\",\n          textTransform: \"uppercase\",\n          color: c.ink,\n          whiteSpace: \"nowrap\",\n          opacity: subIn,\n          transform: `translateY(${(1 - subIn) * 36 * u}px)`,\n        }}\n      >\n        {subtitle}\n      </div>\n\n      {/* Buzzer flash. */}\n      {flash > 0 ? (\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            background: `radial-gradient(120% 90% at 50% 100%, ${RISE[0]} 0%, ${RISE[1]} 38%, ${RISE[2]} 72%, ${RISE[3]} 100%)`,\n            opacity: flash * c.flashOp,\n          }}\n        />\n      ) : null}\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/shot-clock.tsx"
    }
  ],
  "type": "registry:component"
}
