{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rolling-number",
  "title": "Rolling Number",
  "description": "Odometer where every place rolls at its own speed, new places rising into view, settling exactly on the target value.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/rolling-number/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadMono } from \"@remotion/google-fonts/JetBrainsMono\";\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: MONO_FAMILY } = loadMono();\n\nexport interface RollingNumberProps {\n  from?: number;\n  to?: number;\n  fontSize?: number;\n  color?: string;\n  speed?: number;\n}\n\nconst COUNT_PORTION = 0.8;\nconst COUNT_EASING = Easing.out(Easing.poly(4));\nconst REVEAL_BAND = 0.4;\nconst REVEAL_EASING = Easing.inOut(Easing.sin);\nconst REVEAL_RISE_EM = 0.32;\n\nfunction easedProgress(\n  frame: number,\n  speed: number,\n  durationInFrames: number,\n): number {\n  return interpolate(\n    frame * speed,\n    [0, durationInFrames * COUNT_PORTION],\n    [0, 1],\n    {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n      easing: COUNT_EASING,\n    },\n  );\n}\n\nfunction placeTravel(start: number, end: number, place: number): number {\n  const pow = 10 ** place;\n  const up = end >= start;\n  const diff = Math.abs(end - start);\n  const startDigit = Math.floor(start / pow) % 10;\n  const finalDigit = Math.floor(end / pow) % 10;\n  const fullTurns = Math.floor(diff / (pow * 10));\n  const stepDelta = up\n    ? (finalDigit - startDigit + 10) % 10\n    : (startDigit - finalDigit + 10) % 10;\n  const magnitude = fullTurns * 10 + stepDelta;\n  return up ? magnitude : -magnitude;\n}\n\nfunction placeReveal(current: number, place: number): number {\n  if (place === 0) return 1;\n  const threshold = 10 ** place;\n  return interpolate(current, [threshold * REVEAL_BAND, threshold], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: REVEAL_EASING,\n  });\n}\n\nfunction wrap10(value: number): number {\n  return ((value % 10) + 10) % 10;\n}\n\nfunction DigitColumn({\n  value,\n  fontSize,\n  reveal,\n}: {\n  value: number;\n  fontSize: number;\n  reveal: number;\n}) {\n  const cellHeight = fontSize * 1.1;\n  return (\n    <span\n      style={{\n        display: \"inline-block\",\n        overflow: \"hidden\",\n        height: cellHeight,\n        width: \"0.62em\",\n        verticalAlign: \"top\",\n        textAlign: \"center\",\n        opacity: reveal,\n        translate: `0 ${(1 - reveal) * fontSize * REVEAL_RISE_EM}px`,\n      }}\n    >\n      <span\n        style={{\n          display: \"flex\",\n          flexDirection: \"column\",\n          translate: `0 ${-value * cellHeight}px`,\n        }}\n      >\n        {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0].map((d, i) => (\n          <span\n            key={i}\n            style={{ height: cellHeight, lineHeight: `${cellHeight}px` }}\n          >\n            {d}\n          </span>\n        ))}\n      </span>\n    </span>\n  );\n}\n\nexport function RollingNumber({\n  from = 0,\n  to = 24813,\n  fontSize = 120,\n  color = \"#171717\",\n  speed = 1,\n}: RollingNumberProps) {\n  const frame = useCurrentFrame();\n  const { durationInFrames } = useVideoConfig();\n\n  const start = Math.max(0, Math.round(from));\n  const end = Math.max(0, Math.round(to));\n\n  const e = easedProgress(frame, speed, durationInFrames);\n  const current = start + e * (end - start);\n\n  const maxVal = Math.max(start, end);\n  const digits = String(maxVal);\n  const cellHeight = fontSize * 1.1;\n\n  const cells: React.ReactNode[] = [];\n  for (let place = 0; place < digits.length; place++) {\n    if (place > 0 && place % 3 === 0) {\n      cells.unshift(\n        <span\n          key={`s${place}`}\n          style={{\n            display: \"inline-block\",\n            width: \"0.34em\",\n            height: cellHeight,\n          }}\n        />,\n      );\n    }\n    const pow = 10 ** place;\n    const startDigit = Math.floor(start / pow) % 10;\n    const travel = placeTravel(start, end, place);\n    const pos = startDigit + e * travel;\n    cells.unshift(\n      <DigitColumn\n        key={`d${place}`}\n        value={wrap10(pos)}\n        fontSize={fontSize}\n        reveal={placeReveal(current, place)}\n      />,\n    );\n  }\n\n  return (\n    <AbsoluteFill\n      style={{\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          alignItems: \"flex-start\",\n          fontSize,\n          fontWeight: 800,\n          color,\n          fontFamily: MONO_FAMILY,\n          fontVariantNumeric: \"tabular-nums\",\n          letterSpacing: \"-0.03em\",\n          lineHeight: 1,\n        }}\n      >\n        {cells}\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/rolling-number.tsx"
    }
  ],
  "type": "registry:component"
}
