{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "timer-duel",
  "title": "Timer Duel",
  "description": "Two stopwatch timers run side by side, the faster one ending on 38x faster.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/timer-duel/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/SpaceGrotesk\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst display = sans;\n\n/** Space Grotesk 500 with tnum: digits 0.62em, \":\" and \".\" 0.276em. */\nconst READ_TRACK = -0.02;\nfunction readoutEm(text: string) {\n  let w = 0;\n  for (const ch of text) w += (/[0-9]/.test(ch) ? 0.62 : 0.276) + READ_TRACK;\n  return w - READ_TRACK;\n}\n\nexport interface TimerDuelProps {\n  before?: number;\n  after?: number;\n  beforeLabel?: string;\n  afterLabel?: string;\n  delta?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst START = 18;\nconst RUN = 32;\nconst CHASE = 50;\nconst STOP_AFTER = START + RUN;\nconst STOP_BEFORE = STOP_AFTER + CHASE;\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    stage: \"rgba(255,255,255,0.05)\",\n    fg: \"#F3F2EF\",\n    muted: \"#85848A\",\n    dim: \"#4A4A50\",\n    track: \"rgba(243,242,239,0.12)\",\n    grain: 0.065,\n    fluted:\n      \"linear-gradient(100deg, #2f7bff 0%, #6d7cf0 30%, #ff7a3d 72%, #ffe2c4 100%)\",\n    band: \"linear-gradient(90deg, #1d2a8f 0%, #2f7bff 45%, #ff7a3d 100%)\",\n    glow: \"color-mix(in oklab, #2f7bff 14%, transparent)\",\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    stage: \"rgba(255,255,255,0.9)\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    dim: \"#B4B1A9\",\n    track: \"rgba(18,18,20,0.12)\",\n    grain: 0.045,\n    fluted: \"linear-gradient(100deg, #1d2a8f 0%, #2f7bff 38%, #ff7a3d 100%)\",\n    band: \"linear-gradient(90deg, #1d2a8f 0%, #2f7bff 45%, #ff7a3d 100%)\",\n    glow: \"color-mix(in oklab, #ff7a3d 9%, transparent)\",\n  },\n} as const;\n\ntype Palette = (typeof THEMES)[keyof typeof THEMES];\n\nfunction format(sec: number, long: boolean) {\n  const s = Math.max(0, sec);\n  if (long) {\n    const h = Math.floor(s / 3600);\n    const m = Math.floor((s % 3600) / 60);\n    const ss = Math.floor(s % 60);\n    return `${h}:${String(m).padStart(2, \"0\")}:${String(ss).padStart(2, \"0\")}`;\n  }\n  const cs = Math.floor(s * 100 + 1e-6);\n  const m = Math.floor(cs / 6000);\n  const ss = Math.floor((cs % 6000) / 100);\n  const c = cs % 100;\n  return `${String(m).padStart(2, \"0\")}:${String(ss).padStart(2, \"0\")}.${String(c).padStart(2, \"0\")}`;\n}\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `g${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{ position: \"absolute\", inset: 0, opacity, pointerEvents: \"none\" }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={41}\n          stitchTiles=\"stitch\"\n        />\n        <feColorMatrix values=\"0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0.9 0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\nfunction Readout({\n  text,\n  size,\n  color,\n  lead,\n  sep,\n}: {\n  text: string;\n  size: number;\n  color: string;\n  lead: string;\n  sep: string;\n}) {\n  const cut = text.length - 4;\n  let first = text.search(/[1-9]/);\n  if (first < 0 || first > cut) first = cut;\n  return (\n    <div\n      style={{\n        fontFamily: display,\n        fontWeight: 400,\n        fontSize: size,\n        lineHeight: 1,\n        letterSpacing: `${READ_TRACK}em`,\n        fontVariantNumeric: \"tabular-nums\",\n        whiteSpace: \"nowrap\",\n      }}\n    >\n      {text.split(\"\").map((ch, i) => {\n        const isSep = ch === \":\" || ch === \".\";\n        const c = i < first ? lead : isSep ? sep : color;\n        return (\n          <span key={`${i}-${ch}`} style={{ color: c }}>\n            {ch}\n          </span>\n        );\n      })}\n    </div>\n  );\n}\n\nexport function TimerDuel({\n  before = 2718.4,\n  after = 70.9,\n  beforeLabel = \"Before\",\n  afterLabel = \"With remocn\",\n  delta,\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: TimerDuelProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height } = useVideoConfig();\n  const u = height / 720;\n  const p: Palette = THEMES[theme];\n  const slow = Math.max(before, after * 1.5);\n  const fast = Math.max(0.01, Math.min(after, before));\n  const long = slow >= 3600;\n  const ratio = slow / fast;\n  const deltaText =\n    delta ?? `${ratio >= 10 ? Math.round(ratio) : ratio.toFixed(1)}× faster`;\n\n  const r0 = fast / RUN;\n  const clock = (f: number) => {\n    if (f <= START) return 0;\n    if (f <= STOP_AFTER) return (f - START) * r0;\n    const dt = Math.min(CHASE, f - STOP_AFTER);\n    const k = EASE_IO(dt / CHASE);\n    return fast + r0 * dt + (slow - fast - r0 * CHASE) * k;\n  };\n  const c = clock(t);\n  const left = Math.min(slow, c);\n  const right = Math.min(fast, c);\n\n  const appear = (d: number) =>\n    interpolate(t, [d, d + 18], [0, 1], { ...CLAMP, easing: EASE_OUT });\n  const rightStop = interpolate(\n    t,\n    [STOP_AFTER, STOP_AFTER + 2, STOP_AFTER + 16],\n    [0, 1, 0],\n    CLAMP,\n  );\n  const rightLit = t >= STOP_AFTER;\n  const freeze = interpolate(t, [STOP_BEFORE + 2, STOP_BEFORE + 16], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const leftStop = interpolate(\n    t,\n    [STOP_BEFORE, STOP_BEFORE + 2, STOP_BEFORE + 12],\n    [0, 1, 0],\n    CLAMP,\n  );\n\n  const rise = interpolate(t, [STOP_BEFORE + 6, STOP_BEFORE + 36], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const deltaIn = interpolate(t, [STOP_BEFORE + 14, STOP_BEFORE + 38], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const hold = interpolate(\n    t,\n    [STOP_BEFORE + 38, STOP_BEFORE + 120],\n    [0, 1],\n    CLAMP,\n  );\n\n  const size = Math.min(height * 0.16, width * 0.085);\n  const sample = format(slow, long);\n  const colW = size * (readoutEm(sample) + 0.08);\n  const gapX = Math.max(width * 0.06, size * 0.6);\n  const rowW = colW * 2 + gapX;\n  const rowY = interpolate(rise, [0, 1], [height * 0.47, height * 0.31]);\n  const rowScale = interpolate(rise, [0, 1], [1, 0.8]);\n  const labelSize = 22 * u;\n  const deltaSize = Math.min(height * 0.2, width * 0.11);\n\n  const columns = [\n    {\n      key: \"before\",\n      label: beforeLabel,\n      text: format(left, long),\n      fill: left / slow,\n      color: freeze > 0.5 ? p.muted : p.fg,\n      bar: p.fg,\n      opacity: 1 - freeze * 0.35,\n      pulse: leftStop,\n      x: 0,\n      d: 0,\n    },\n    {\n      key: \"after\",\n      label: afterLabel,\n      text: format(right, long),\n      fill: right / slow,\n      color: rightLit ? accent : p.fg,\n      bar: p.band,\n      opacity: 1,\n      pulse: rightStop,\n      x: colW + gapX,\n      d: 3,\n    },\n  ];\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: [\n            `radial-gradient(ellipse 46% 40% at 50% ${70 - 3 * hold}%, ${p.glow}, transparent 72%)`,\n            `radial-gradient(ellipse 60% 60% at 50% 42%, ${p.stage}, transparent 75%)`,\n          ].join(\", \"),\n        }}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          left: (width - rowW) / 2,\n          top: rowY,\n          width: rowW,\n          height: 0,\n          transform: `scale(${rowScale})`,\n        }}\n      >\n        {columns.map((col) => {\n          const a = appear(col.d);\n          return (\n            <div\n              key={col.key}\n              style={{\n                position: \"absolute\",\n                left: col.x,\n                top: 0,\n                width: colW,\n                transform: `translateY(calc(-50% + ${(1 - a) * 16 * u}px))`,\n                opacity: a,\n              }}\n            >\n              <div\n                style={{\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  gap: 10 * u,\n                  marginBottom: size * 0.2,\n                  fontFamily: sans,\n                  fontWeight: 500,\n                  fontSize: labelSize,\n                  letterSpacing: \"-0.01em\",\n                  color: p.muted,\n                }}\n              >\n                <div\n                  style={{\n                    width: 8 * u,\n                    height: 8 * u,\n                    borderRadius: 8 * u,\n                    background: col.key === \"after\" ? accent : p.muted,\n                    boxShadow: `0 0 0 ${col.pulse * 6 * u}px ${col.key === \"after\" ? `${accent}33` : p.track}`,\n                  }}\n                />\n                {col.label}\n              </div>\n              <div\n                style={{\n                  opacity: col.opacity,\n                  transformOrigin: \"0% 60%\",\n                  transform: `scale(${1 + col.pulse * 0.02})`,\n                }}\n              >\n                <Readout\n                  text={col.text}\n                  size={size}\n                  color={col.color}\n                  lead={p.dim}\n                  sep={col.key === \"after\" && rightLit ? accent : p.muted}\n                />\n              </div>\n              <div\n                style={{\n                  position: \"relative\",\n                  marginTop: size * 0.26,\n                  width: colW,\n                  height: Math.max(1, u),\n                  background: p.track,\n                }}\n              >\n                <div\n                  style={{\n                    position: \"absolute\",\n                    left: 0,\n                    top: col.key === \"after\" ? -1.5 * u : -u,\n                    height: (col.key === \"after\" ? 4 : 3) * u,\n                    borderRadius: 2 * u,\n                    width: Math.max(c > 0 ? 3 * u : 0, colW * col.fill),\n                    background: col.bar,\n                    backgroundSize: `${colW}px 100%`,\n                    opacity: col.opacity,\n                  }}\n                />\n              </div>\n            </div>\n          );\n        })}\n      </div>\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          width,\n          top: height * 0.69,\n          display: \"flex\",\n          justifyContent: \"center\",\n          transform: `translateY(calc(-50% + ${(1 - deltaIn) * 26 * u}px)) scale(${1 + hold * 0.015})`,\n          opacity: deltaIn,\n          fontFamily: display,\n          fontWeight: 500,\n          fontSize: deltaSize,\n          lineHeight: 1,\n          letterSpacing: \"-0.04em\",\n          fontVariantNumeric: \"tabular-nums\",\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        <span\n          style={{\n            color: \"transparent\",\n            backgroundImage: p.fluted,\n            backgroundSize: `${100 + 30 * hold}% 100%`,\n            WebkitBackgroundClip: \"text\",\n            backgroundClip: \"text\",\n            padding: \"0.06em 0.04em\",\n          }}\n        >\n          {deltaText.split(\" \")[0]}\n        </span>\n        {deltaText.includes(\" \") ? (\n          <span style={{ color: p.muted, marginLeft: deltaSize * 0.24 }}>\n            {deltaText.slice(deltaText.indexOf(\" \") + 1)}\n          </span>\n        ) : null}\n      </div>\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/timer-duel.tsx"
    }
  ],
  "type": "registry:component"
}
