{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "abacus-count",
  "title": "Abacus Count",
  "description": "Abacus beads slide along their rods to count up to 48,216.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/abacus-count/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Geist\";\nimport { type ReactNode, useId, useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: geist } = loadFont(\"normal\", {\n  weights: [\"300\", \"400\"],\n  subsets: [\"latin\"],\n});\n\n// gradients.sh \"rise\" palette\nconst RISE = {\n  bg: \"#030306\",\n  c: [\"#fff1e2\", \"#ff8a3d\", \"#a3301a\", \"#2a0d0a\"],\n} as const;\n\nexport interface AbacusCountProps {\n  value?: number;\n  label?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst BEADS = 9;\nconst START = 16;\nconst TICK = 5;\nconst CARRY_DELAY = 3;\nconst MID = [0.37, 0.58, 0.46, 0.63, 0.52];\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;\nconst BEAD_SPRING = { damping: 14, stiffness: 260, mass: 0.55 };\n\nconst THEMES = {\n  dark: {\n    bg: RISE.bg,\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    faint: \"#2A2A30\",\n    bead: \"#2E2E34\",\n    ground: 0.26,\n    stops: [RISE.c[0], \"#ffb47a\", RISE.c[1], \"#d4522a\"],\n    rod: \"rgba(255,255,255,0.24)\",\n    post: \"rgba(255,255,255,0.32)\",\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    faint: \"#DAD8D2\",\n    bead: \"#D6D4CE\",\n    ground: 0,\n    stops: [\"#ffb47a\", RISE.c[1], \"#d4522a\", RISE.c[2]],\n    rod: \"rgba(18,18,20,0.26)\",\n    post: \"rgba(18,18,20,0.34)\",\n    grain: 0.045,\n  },\n} as const;\n\nfunction digitAt(v: number, place: number) {\n  return Math.floor(v / 10 ** place) % 10;\n}\n\nfunction leadOf(v: number) {\n  return v <= 0 ? -1 : Math.floor(Math.log10(v) + 1e-9);\n}\n\nfunction buildSteps(value: number, places: number) {\n  const seq: number[] = [0];\n  const add = (v: number) => {\n    if (v > seq[seq.length - 1] && v <= value) seq.push(v);\n  };\n  for (const v of [3, 6, 9, 10]) add(v);\n  for (let pl = 2; pl < places; pl++) {\n    const top = 10 ** pl;\n    add(Math.round(top * MID[pl % MID.length]));\n    add(top - 1);\n    add(top);\n  }\n  add(value);\n  const times: number[] = [START];\n  let clock = START;\n  for (let k = 1; k < seq.length; k++) {\n    const prog = (k - 1) / Math.max(1, seq.length - 2);\n    let gap = interpolate(prog, [0, 1], [9, 5]);\n    let carry = false;\n    for (let pl = 0; pl < places; pl++) {\n      if (digitAt(seq[k], pl) < digitAt(seq[k - 1], pl)) carry = true;\n    }\n    if (carry) gap += 4;\n    if (k === seq.length - 1 && k > 1) gap += 6;\n    clock += gap;\n    times.push(clock);\n  }\n  return { seq, times };\n}\n\ninterface Move {\n  at: number;\n  from: number;\n  to: number;\n}\n\nfunction buildMoves(seq: number[], times: number[], places: number) {\n  const moves: Move[][][] = [];\n  for (let pl = 0; pl < places; pl++) {\n    const rod: Move[][] = Array.from({ length: BEADS }, () => []);\n    for (let k = 1; k < seq.length; k++) {\n      const d0 = digitAt(seq[k - 1], pl);\n      const d1 = digitAt(seq[k], pl);\n      if (d0 === d1) continue;\n      let lowerReset = false;\n      for (let q = 0; q < pl; q++) {\n        if (digitAt(seq[k], q) < digitAt(seq[k - 1], q)) lowerReset = true;\n      }\n      const delay = d1 > d0 && lowerReset ? CARRY_DELAY : 0;\n      for (let i = 0; i < BEADS; i++) {\n        const c0 = i >= BEADS - d0 ? 1 : 0;\n        const c1 = i >= BEADS - d1 ? 1 : 0;\n        if (c0 === c1) continue;\n        const order = c1 > c0 ? BEADS - d0 - 1 - i : i - (BEADS - d0);\n        rod[i].push({ at: times[k] + delay + order * 0.7, from: c0, to: c1 });\n      }\n    }\n    moves.push(rod);\n  }\n  return moves;\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 }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={23}\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\nexport function AbacusCount({\n  value = 48216,\n  label = \"renders this week\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: AbacusCountProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height, fps } = useVideoConfig();\n  const u = Math.min(width / 1280, height / 720);\n  const p = THEMES[theme];\n  const target = Math.max(0, Math.min(999999, Math.round(value)));\n  const places = Math.max(3, String(target).length);\n  const { seq, times } = useMemo(\n    () => buildSteps(target, places),\n    [target, places],\n  );\n  const moves = useMemo(\n    () => buildMoves(seq, times, places),\n    [seq, times, places],\n  );\n\n  let kNow = 0;\n  for (let k = 0; k < times.length; k++) if (t >= times[k]) kNow = k;\n  const lead = leadOf(seq[kNow]);\n  const lastAt = times[times.length - 1];\n\n  const cx = width / 2;\n  const cy = height / 2;\n  const D = 32 * u;\n  const rodGap = 58 * u;\n  const pad = 6 * u;\n  const travel = 5.5 * D;\n  const rodLen = BEADS * D + travel + 2 * pad;\n  const postW = 5 * u;\n  const numSize = 132 * u;\n  const cell = numSize * 0.58;\n  const commaW = numSize * 0.28;\n  const commas = Math.floor((places - 1) / 3);\n  const readW = places * cell + commas * commaW;\n  const gapX = 88 * u;\n  const totalW = rodLen + 2 * postW + gapX + readW;\n  const x0 = cx - totalW / 2 + postW;\n  const x1 = x0 + rodLen;\n  const readX = x1 + postW + gapX;\n  const abH = (places - 1) * rodGap;\n  const rodY = (row: number) => cy - abH / 2 + row * rodGap;\n  const postTop = rodY(0) - D / 2 - 18 * u;\n  const postBot = rodY(places - 1) + D / 2 + 18 * u;\n\n  const restX = (i: number) => x0 + pad + i * D + D / 2;\n  const countX = (i: number) => x1 - pad - (BEADS - 1 - i) * D - D / 2;\n\n  const frameIn = interpolate(t, [0, 16], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const push = interpolate(t, [0, lastAt + 40], [0.985, 1.01], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n\n  const gradId = `ab${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  const drift = interpolate(t, [0, lastAt + 40], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const leadGrad = `linear-gradient(${165 - 10 * drift}deg, ${p.stops[0]} 0%, ${p.stops[1]} 30%, ${p.stops[2]} 66%, ${p.stops[3]} 100%)`;\n\n  const rodsEls: ReactNode[] = [];\n  const beadEls: ReactNode[] = [];\n  for (let row = 0; row < places; row++) {\n    const pl = places - 1 - row;\n    const y = rodY(row);\n    const rodK = interpolate(t, [2 + row * 2, 18 + row * 2], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n    rodsEls.push(\n      <line\n        key={`r${pl}`}\n        x1={x0}\n        y1={y}\n        x2={x0 + rodLen * rodK}\n        y2={y}\n        stroke={p.rod}\n        strokeWidth={Math.max(1, 1.5 * u)}\n      />,\n    );\n    const xs: number[] = [];\n    const states: number[] = [];\n    for (let i = 0; i < BEADS; i++) {\n      let st = i >= BEADS - digitAt(seq[0], pl) ? 1 : 0;\n      for (const m of moves[pl][i]) {\n        if (t < m.at) break;\n        st +=\n          (m.to - m.from) *\n          spring({ frame: t - m.at, fps, config: BEAD_SPRING });\n      }\n      states.push(st);\n      xs.push(restX(i) + (countX(i) - restX(i)) * st);\n    }\n    for (let i = 1; i < BEADS; i++) xs[i] = Math.max(xs[i], xs[i - 1] + D);\n    for (let i = BEADS - 2; i >= 0; i--) xs[i] = Math.min(xs[i], xs[i + 1] - D);\n    for (let i = 0; i < BEADS; i++) {\n      const bIn = interpolate(\n        t,\n        [6 + row * 2 + i * 0.6, 14 + row * 2 + i * 0.6],\n        [0, 1],\n        { ...CLAMP, easing: EASE_OUT },\n      );\n      const by = y + (1 - bIn) * -6 * u;\n      const br = D / 2 - 0.75 * u;\n      const lit = Math.max(0, Math.min(1, states[i]));\n      beadEls.push(\n        <g key={`b${pl}-${i.toString()}`} opacity={bIn}>\n          <circle cx={xs[i]} cy={by} r={br} fill={p.bead} />\n          {lit > 0 ? (\n            <circle\n              cx={xs[i]}\n              cy={by}\n              r={br}\n              fill={`url(#${gradId})`}\n              opacity={lit}\n            />\n          ) : null}\n        </g>,\n      );\n    }\n  }\n\n  const digitEls: ReactNode[] = [];\n  for (let pl = places - 1; pl >= 0; pl--) {\n    let kc = 0;\n    for (let k = 1; k <= kNow; k++) {\n      if (digitAt(seq[k], pl) !== digitAt(seq[k - 1], pl)) kc = k;\n    }\n    const tick =\n      kc === 0\n        ? 1\n        : interpolate(t, [times[kc], times[kc] + TICK], [0, 1], {\n            ...CLAMP,\n            easing: EASE_OUT,\n          });\n    const dNew = digitAt(seq[kc], pl);\n    const isLead = pl === lead;\n    const color = pl > lead ? p.faint : isLead ? \"transparent\" : p.fg;\n    digitEls.push(\n      <span\n        key={`d${pl}`}\n        style={{\n          display: \"inline-block\",\n          width: cell,\n          textAlign: \"center\",\n          color,\n          ...(isLead\n            ? {\n                backgroundImage: leadGrad,\n                WebkitBackgroundClip: \"text\",\n                backgroundClip: \"text\",\n              }\n            : null),\n          opacity: 0.8 + 0.2 * tick,\n          transform: `translateY(${(1 - tick) * 6}%)`,\n        }}\n      >\n        {dNew}\n      </span>,\n    );\n    if (pl > 0 && pl % 3 === 0) {\n      digitEls.push(\n        <span\n          key={`c${pl}`}\n          style={{\n            display: \"inline-block\",\n            width: commaW,\n            textAlign: \"center\",\n            color: pl > lead ? p.faint : p.fg,\n          }}\n        >\n          ,\n        </span>,\n      );\n    }\n  }\n\n  const labelIn = interpolate(t, [lastAt + 6, lastAt + 24], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const blockH = numSize * 1.12 + 10 * u + 24 * u * 1.3;\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          background: `radial-gradient(75% 55% at ${46 + 6 * drift}% 112%, color-mix(in oklab, ${RISE.c[2]} ${p.ground * 100}%, transparent) 0%, color-mix(in oklab, ${RISE.c[3]} ${p.ground * 100}%, transparent) 45%, transparent 100%)`,\n          opacity: frameIn,\n        }}\n      />\n      <AbsoluteFill\n        style={{ transform: `scale(${push})`, transformOrigin: \"50% 50%\" }}\n      >\n        <svg\n          width={width}\n          height={height}\n          style={{ position: \"absolute\", inset: 0 }}\n          aria-hidden\n        >\n          <g opacity={frameIn}>\n            <rect\n              x={x0 - postW}\n              y={postTop}\n              width={postW}\n              height={postBot - postTop}\n              rx={postW / 2}\n              fill={p.post}\n            />\n            <rect\n              x={x1}\n              y={postTop}\n              width={postW}\n              height={postBot - postTop}\n              rx={postW / 2}\n              fill={p.post}\n            />\n          </g>\n          <defs>\n            <linearGradient\n              id={gradId}\n              gradientUnits=\"userSpaceOnUse\"\n              x1={x1 - travel * (1.1 + 0.15 * drift)}\n              y1={postTop}\n              x2={x1}\n              y2={postBot}\n            >\n              <stop offset=\"0\" stopColor={p.stops[0]} />\n              <stop offset=\"0.34\" stopColor={p.stops[1]} />\n              <stop offset=\"0.7\" stopColor={p.stops[2]} />\n              <stop offset=\"1\" stopColor={p.stops[3]} />\n            </linearGradient>\n          </defs>\n          {rodsEls}\n          {beadEls}\n        </svg>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: readX,\n            top: cy - blockH / 2,\n            opacity: frameIn,\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              fontFamily: geist,\n              fontWeight: 300,\n              fontSize: numSize,\n              lineHeight: 1.12,\n              letterSpacing: \"-0.03em\",\n              fontVariantNumeric: \"tabular-nums\",\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {digitEls}\n          </div>\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"center\",\n              gap: 12 * u,\n              marginTop: 10 * u,\n              paddingLeft: numSize * 0.06,\n              fontFamily: geist,\n              fontWeight: 400,\n              fontSize: 24 * u,\n              lineHeight: 1.3,\n              letterSpacing: \"-0.01em\",\n              color: p.muted,\n              whiteSpace: \"nowrap\",\n              opacity: labelIn,\n              transform: `translateY(${(1 - labelIn) * 8 * u}px)`,\n            }}\n          >\n            <span\n              style={{\n                width: 10 * u,\n                height: 10 * u,\n                borderRadius: \"50%\",\n                background: accent,\n                flexShrink: 0,\n              }}\n            />\n            {label}\n          </div>\n        </div>\n      </AbsoluteFill>\n\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 80% 80% at 50% 50%, transparent 62%, ${p.bg} 100%)`,\n          opacity: 0.55,\n        }}\n      />\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/abacus-count.tsx"
    }
  ],
  "type": "registry:component"
}
