{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "speed-trap",
  "title": "Speed Trap",
  "description": "A speed readout locks on 348 km/h over a segmented bar with last year's marker and delta.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/speed-trap/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadMono } from \"@remotion/google-fonts/GeistMono\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { loadFont } from \"@remotion/google-fonts/Sora\";\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sora } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: mono } = loadMono(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"600\"],\n  subsets: [\"latin\"],\n});\n\nconst REMOCN_R_PATH =\n  \"M 0.01 0.81 C 0.01 1.73, 0.36 2.79, 1.09 4.13 C 4.91 11.04, 13.45 17.16, 21.7 18.9 C 22.94 19.16, 23.18 19.16, 51.39 19.27 C 76.07 19.36, 80.02 19.4, 81.32 19.57 C 89.89 20.69, 96.2 24.68, 99.38 31.01 C 103.19 38.56, 102.53 50.1, 97.91 57.07 C 94.66 61.96, 89.68 64.99, 83.26 66 C 82.81 66.07, 70.18 66.83, 55.2 67.69 C 24.82 69.43, 27.03 69.23, 24.18 70.4 C 19.9 72.15, 14.84 75.7, 10.65 79.89 C 4.86 85.68, 1.3 91.91, 0.25 98.13 C 0.12 98.85, 0.08 103.13, 0.04 116.66 L 0 134.26 9.5 134.26 L 19 134.26 19.05 119.41 C 19.1 103.27, 19.08 103.82, 19.82 101.04 C 21.79 93.65, 27.86 88.75, 36.45 87.63 C 37.23 87.53, 39.41 87.5, 43.57 87.53 C 50.12 87.59, 50.75 87.65, 53.22 88.45 C 56.61 89.56, 59.67 91.86, 62.02 95.07 C 62.52 95.76, 69.35 103.85, 77.2 113.07 C 85.04 122.28, 91.63 130.04, 91.85 130.32 C 92.07 130.59, 92.5 131.34, 92.82 131.97 C 93.52 133.35, 94.11 133.93, 95.06 134.13 C 95.5 134.23, 98.97 134.26, 106.36 134.23 L 117.01 134.19 100.82 113.07 C 91.91 101.45, 84.52 91.78, 84.39 91.57 C 83.36 89.89, 83.66 87.53, 85.09 86 C 85.79 85.25, 86.36 84.94, 88.07 84.38 C 96.18 81.72, 104.15 76.62, 109.97 70.36 C 120.59 58.93, 124.06 44.32, 119.44 30.43 C 114.59 15.81, 101.93 4.02, 87.64 0.83 C 83.93 0.01, 88.09 0.08, 41.85 0.04 L 0.01 0 0.01 0.81 Z\";\n\n/** Full logo as on remocn.dev: R mark (0.76em tall, baseline-aligned) + \"emocn\" in Manrope 600. */\nfunction RemocnLogo({ fontSize, color }: { fontSize: number; color: string }) {\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"baseline\",\n        color,\n        fontFamily: manrope.fontFamily,\n        fontWeight: 600,\n        fontSize,\n        letterSpacing: \"-0.025em\",\n        lineHeight: 1,\n      }}\n    >\n      <svg\n        viewBox=\"0 0 124.06 134.26\"\n        style={{ height: \"0.76em\", width: \"auto\" }}\n        aria-hidden\n      >\n        <path fill=\"currentColor\" d={REMOCN_R_PATH} />\n      </svg>\n      <span style={{ marginLeft: \"0.04em\" }}>emocn</span>\n    </div>\n  );\n}\n\nexport interface SpeedTrapProps {\n  /** Speed the readout locks on. */\n  value?: number;\n  /** Last year's mark, drawn as a line on the bar. The delta is computed from it. */\n  previous?: number;\n  /** Full scale of the bar. */\n  max?: number;\n  /** Unit after the number. */\n  unit?: string;\n  /** Banner that flashes when the readout locks. */\n  banner?: string;\n  /** Label on the previous mark. */\n  previousLabel?: string;\n  /** Text after the delta, e.g. \"vs last year\". */\n  comparison?: string;\n  /** Small label at the top left. */\n  label?: string;\n  /** The live dot next to the label. */\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\n// Beat sheet (internal clock = frame + 3, 30fps, speed 1):\n//   0–8    the three-sector bar track snaps out from the left, labels up\n//   3–42   the readout floods up to 348 and the gradient bar races with it\n//          (fast off the line, tapering into the top end); each sector label\n//          lights as the fill crosses into it; the frame pushes in a touch\n//  42      lock: the number kicks, the bar flashes white for two frames\n//  44–56   the \"Top speed\" banner wipes in white beside the number, turns\n//          orange and blinks once, broadcast style\n//  50–70   last year's mark drops onto the bar, the delta slice between the\n//          two marks lights up, the comparison line slides out under it\n//  70–     poster hold\nconst COUNT_AT = 3;\nconst LOCK_AT = 42;\n\nconst SLAM = Easing.bezier(0.22, 1, 0.36, 1);\nconst RUN = Easing.bezier(0.3, 0.55, 0.25, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\n// gradients.sh \"fluted\": bg #050507, #1d2a8f #2f7bff #ff7a3d #ffe2c4.\nconst FLUTED = [\"#1D2A8F\", \"#2F7BFF\", \"#FF7A3D\", \"#FFE2C4\"] as const;\nconst FILL = `linear-gradient(90deg, ${FLUTED[0]} 0%, ${FLUTED[1]} 42%, ${FLUTED[2]} 84%, ${FLUTED[3]} 100%)`;\n\nconst THEMES = {\n  dark: {\n    stage: \"#0A0A0B\",\n    ink: \"#F3F2EF\",\n    dim: \"#86858C\",\n    track: \"#1A1A1E\",\n    mark: \"#F3F2EF\",\n  },\n  light: {\n    stage: \"#F5F4F0\",\n    ink: \"#121214\",\n    dim: \"#7E7D77\",\n    track: \"#E2E0D9\",\n    mark: \"#121214\",\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 SpeedTrap({\n  value = 348,\n  previous = 300,\n  max = 360,\n  unit = \"km/h\",\n  banner = \"Top speed\",\n  previousLabel = \"2025\",\n  comparison = \"vs last year\",\n  label = \"Speed trap · Sector 3\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: SpeedTrapProps) {\n  // Starts three frames in, so frame 0 already shows the bar snapping out.\n  const t = useCurrentFrame() * speed + 3;\n  const { width: W, height: H } = useVideoConfig();\n  const c = THEMES[theme];\n  const u = Math.min(W / 1280, H / 720);\n\n  // ── Readout ───────────────────────────────────────────────────────────────\n  const run = RUN(ramp(t, COUNT_AT, LOCK_AT - COUNT_AT));\n  const locked = t >= LOCK_AT;\n  const now = locked ? value : Math.floor(value * run);\n  const digits = String(Math.max(0, Math.round(value))).length;\n  const shown = String(now).padStart(digits, \" \");\n  const lockKick = kick(t - LOCK_AT, 9);\n  const barFlash = locked && t < LOCK_AT + 2 ? 1 : 0;\n\n  // ── Layout ────────────────────────────────────────────────────────────────\n  const marginX = 76 * u;\n  const barX = marginX;\n  const barW = W - 2 * marginX;\n  const barH = 64 * u;\n  const barY = H * 0.69;\n  const sectorGap = 6 * u;\n  const sectors = 3;\n  const sectorW = (barW - (sectors - 1) * sectorGap) / sectors;\n  const xAt = (v: number) => barX + (Math.min(v, max) / max) * barW;\n\n  const numFs = 290 * u;\n  const cellW = 0.64 * numFs;\n  const numTop = barY - 64 * u - numFs * 0.98;\n\n  const trackIn = SLAM(ramp(t, 0, 8));\n  const labelsIn = ramp(t, 3, 8);\n  const fillFrac = (locked ? value : value * run) / max;\n\n  const bannerIn = SLAM(ramp(t, LOCK_AT + 2, 6));\n  const bannerBlink = t >= LOCK_AT + 11 && t < LOCK_AT + 14 ? 0 : 1;\n  const markIn = SLAM(ramp(t, LOCK_AT + 8, 8));\n  const deltaIn = SLAM(ramp(t, LOCK_AT + 13, 8));\n  const compIn = SLAM(ramp(t, LOCK_AT + 16, 10));\n\n  const push = 1 + 0.035 * Easing.bezier(0.65, 0, 0.35, 1)(ramp(t, 0, LOCK_AT));\n  const scale = push * (1 + 0.012 * lockKick);\n\n  const delta = Math.round(value - previous);\n  const deltaText = `${delta >= 0 ? \"+\" : \"−\"}${Math.abs(delta)} ${unit}`;\n\n  const small: React.CSSProperties = {\n    fontFamily: mono,\n    fontWeight: 400,\n    fontSize: 16 * u,\n    letterSpacing: \"0.14em\",\n    textTransform: \"uppercase\",\n    color: c.dim,\n  };\n\n  const prevX = xAt(previous);\n  const valX = xAt(value);\n\n  return (\n    <AbsoluteFill style={{ background: c.stage, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          transform: `scale(${scale})`,\n          transformOrigin: `${W / 2}px ${barY}px`,\n        }}\n      >\n        {/* Top row: label and logo. */}\n        <div\n          style={{\n            ...small,\n            position: \"absolute\",\n            left: marginX,\n            top: 64 * u,\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: 12 * u,\n            opacity: labelsIn,\n          }}\n        >\n          <div\n            style={{\n              width: 8 * u,\n              height: 8 * u,\n              background: accent,\n              opacity: locked ? 1 : Math.floor(t / 6) % 2 === 0 ? 1 : 0.3,\n            }}\n          />\n          {label}\n        </div>\n        <div\n          style={{\n            position: \"absolute\",\n            right: marginX,\n            top: 58 * u,\n            opacity: labelsIn,\n          }}\n        >\n          <RemocnLogo fontSize={28 * u} color={c.ink} />\n        </div>\n\n        {/* Readout. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: marginX - 0.04 * numFs,\n            top: numTop,\n            display: \"flex\",\n            alignItems: \"baseline\",\n            fontFamily: sora,\n            fontWeight: 500,\n            color: c.ink,\n            transform: `scale(${1 + 0.05 * lockKick})`,\n            transformOrigin: \"0% 100%\",\n          }}\n        >\n          {shown.split(\"\").map((ch, i) => (\n            <div\n              key={i}\n              style={{\n                width: cellW,\n                textAlign: \"center\",\n                fontSize: numFs,\n                lineHeight: 1,\n                letterSpacing: \"-0.04em\",\n              }}\n            >\n              {ch === \" \" ? \"\" : ch}\n            </div>\n          ))}\n          <div\n            style={{\n              marginLeft: 14 * u,\n              fontSize: 64 * u,\n              fontWeight: 400,\n              letterSpacing: \"-0.02em\",\n              color: c.dim,\n            }}\n          >\n            {unit}\n          </div>\n        </div>\n\n        {/* Top speed banner. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: marginX + digits * cellW + 190 * u,\n            top: numTop + numFs * 0.22,\n            height: 72 * u,\n            clipPath: `inset(0 ${(1 - bannerIn) * 100}% 0 0)`,\n            opacity: bannerBlink,\n            background:\n              t < LOCK_AT + 4\n                ? \"#FFFFFF\"\n                : `linear-gradient(100deg, ${FLUTED[2]} 0%, #FF9A5E 100%)`,\n            display: \"flex\",\n            alignItems: \"center\",\n            paddingLeft: 22 * u,\n            paddingRight: 26 * u,\n            fontFamily: sora,\n            fontWeight: 500,\n            fontSize: 38 * u,\n            letterSpacing: \"0.02em\",\n            textTransform: \"uppercase\",\n            color: \"#0A0A0B\",\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          {banner}\n        </div>\n\n        {/* Sector labels. */}\n        {Array.from({ length: sectors }, (_, i) => {\n          const lit = fillFrac * barW >= i * (sectorW + sectorGap);\n          return (\n            <div\n              key={`s${i + 1}`}\n              style={{\n                ...small,\n                position: \"absolute\",\n                left: barX + i * (sectorW + sectorGap),\n                top: barY - 30 * u,\n                color: lit ? c.ink : c.dim,\n                opacity: labelsIn,\n              }}\n            >\n              S{i + 1}\n            </div>\n          );\n        })}\n\n        {/* Bar: three sector tracks, gradient fill clipped to the speed. */}\n        {Array.from({ length: sectors }, (_, i) => {\n          const x0 = i * (sectorW + sectorGap);\n          const fillPx = Math.max(0, Math.min(sectorW, fillFrac * barW - x0));\n          const vis = Math.max(0, Math.min(sectorW, trackIn * barW - x0));\n          return (\n            <div\n              key={`b${i + 1}`}\n              style={{\n                position: \"absolute\",\n                left: barX + x0,\n                top: barY,\n                width: vis,\n                height: barH,\n                background: c.track,\n                overflow: \"hidden\",\n              }}\n            >\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: 0,\n                  top: 0,\n                  width: fillPx,\n                  height: barH,\n                  overflow: \"hidden\",\n                }}\n              >\n                <div\n                  style={{\n                    position: \"absolute\",\n                    left: -x0,\n                    top: 0,\n                    width: barW,\n                    height: barH,\n                    background: barFlash ? \"#FFFFFF\" : FILL,\n                  }}\n                />\n              </div>\n            </div>\n          );\n        })}\n\n        {/* Leading edge while the bar runs. */}\n        {!locked && run > 0 ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: barX + fillFrac * barW - 3 * u,\n              top: barY - 8 * u,\n              width: 3 * u,\n              height: barH + 16 * u,\n              background: c.mark,\n            }}\n          />\n        ) : null}\n\n        {/* Delta slice between last year's mark and the new top speed. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: prevX,\n            top: barY,\n            width: (valX - prevX) * deltaIn,\n            height: barH,\n            boxShadow: `inset 0 0 0 ${3 * u}px ${c.mark}`,\n          }}\n        />\n\n        {/* Last year's mark. */}\n        <div\n          style={{\n            position: \"absolute\",\n            left: prevX - 1.5 * u,\n            top: barY - 46 * u,\n            width: 3 * u,\n            height: barH + 46 * u,\n            background: c.mark,\n            transformOrigin: \"50% 100%\",\n            transform: `scaleY(${markIn})`,\n          }}\n        />\n        <div\n          style={{\n            ...small,\n            position: \"absolute\",\n            right: W - prevX + 12 * u,\n            top: barY - 48 * u,\n            color: c.ink,\n            opacity: markIn,\n            transform: `translateY(${(1 - markIn) * 10 * u}px)`,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          {previousLabel} · {previous}\n        </div>\n\n        {/* Comparison line, sliding out from under the bar. */}\n        <div\n          style={{\n            position: \"absolute\",\n            right: W - valX,\n            top: barY + barH,\n            height: 90 * u,\n            overflow: \"hidden\",\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"baseline\",\n              gap: 14 * u,\n              paddingTop: 18 * u,\n              transform: `translateY(${-(1 - compIn) * 90 * u}px)`,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            <span\n              style={{\n                fontFamily: sora,\n                fontWeight: 500,\n                fontSize: 52 * u,\n                letterSpacing: \"-0.02em\",\n                color: c.ink,\n              }}\n            >\n              {deltaText}\n            </span>\n            <span\n              style={{\n                fontFamily: sora,\n                fontWeight: 400,\n                fontSize: 30 * u,\n                color: c.dim,\n              }}\n            >\n              {comparison}\n            </span>\n          </div>\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/speed-trap.tsx"
    }
  ],
  "type": "registry:component"
}
