{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "lean-count",
  "title": "Lean Count",
  "description": "A heavy italic figure leans into place as it counts up to 3.2 followed by a red cross.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/lean-count/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadBricolage } from \"@remotion/google-fonts/BricolageGrotesque\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport {\n  AbsoluteFill,\n  continueRender,\n  delayRender,\n  Easing,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst bricolage = loadBricolage(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface LeanCountProps {\n  value?: number;\n  from?: number;\n  decimals?: number;\n  prefix?: string;\n  suffix?: string;\n  label?: string;\n  lean?: number;\n  steps?: number;\n  accent?: string;\n  background?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst THEMES = {\n  dark: {\n    bg: \"#16130F\",\n    ink: \"#F2EFE8\",\n    accent: \"#E5342A\",\n    rule: \"rgba(242,239,232,0.28)\",\n    grain: 0.05,\n  },\n  light: {\n    bg: \"#F2EFE8\",\n    ink: \"#111111\",\n    accent: \"#E5342A\",\n    rule: \"rgba(17,17,17,0.24)\",\n    grain: 0.035,\n  },\n} as const;\n\nconst TRACK = -0.04;\nconst RISE_AT = 0;\nconst RISE_LEN = 18;\nconst RISE_GAP = 3;\nconst FIRST_SURGE = 30;\nconst SURGE_GAP = 16;\nconst COUNT_LEN = 13;\nconst LABEL_AT = 6;\n\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_IN_OUT = Easing.bezier(0.65, 0, 0.35, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\nconst SNAP = { damping: 11, stiffness: 230, mass: 0.6 };\n\ninterface Metrics {\n  adv: Record<string, number>;\n  cap: number;\n  baseline: number;\n}\n\nfunction measure(chars: string[]): Metrics {\n  const probe = document.createElement(\"span\");\n  Object.assign(probe.style, {\n    position: \"absolute\",\n    left: \"-99999px\",\n    top: \"0\",\n    visibility: \"hidden\",\n    whiteSpace: \"pre\",\n    fontFamily: `\"${bricolage.fontFamily}\"`,\n    fontWeight: \"500\",\n    fontSize: \"100px\",\n    lineHeight: \"1\",\n    fontFeatureSettings: '\"tnum\" 1',\n  });\n  document.body.appendChild(probe);\n  const adv: Record<string, number> = {};\n  for (const ch of chars) {\n    probe.textContent = ch;\n    adv[ch] = probe.getBoundingClientRect().width / 100 + TRACK;\n  }\n  probe.remove();\n  const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n  let cap = 0.72;\n  let baseline = 0.8;\n  if (ctx) {\n    ctx.font = `500 100px \"${bricolage.fontFamily}\"`;\n    const fm = ctx.measureText(\"0123456789\");\n    cap = fm.actualBoundingBoxAscent / 100;\n    const asc = fm.fontBoundingBoxAscent / 100;\n    const desc = fm.fontBoundingBoxDescent / 100;\n    baseline = (1 - (asc + desc)) / 2 + asc;\n  }\n  return { adv, cap, baseline };\n}\n\nfunction useMetrics(chars: string[]) {\n  const key = chars.join(\"\");\n  const [state, setState] = useState<{ key: string; m: Metrics } | null>(null);\n  const [handle] = useState(() => delayRender(\"lean-count: measuring\"));\n  const released = useRef(false);\n  useEffect(() => {\n    let live = true;\n    Promise.all([\n      bricolage.waitUntilDone(),\n      document.fonts.load(`500 100px \"${bricolage.fontFamily}\"`),\n      document.fonts.load(`400 100px \"${bricolage.fontFamily}\"`),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        setState({ key, m: measure(Array.from(key)) });\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [key, handle]);\n  return state && state.key === key ? state.m : null;\n}\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `lc${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={9}\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 format(v: number, decimals: number) {\n  return v.toLocaleString(\"en-US\", {\n    minimumFractionDigits: decimals,\n    maximumFractionDigits: decimals,\n  });\n}\n\nexport function LeanCount({\n  value = 3.2,\n  from = 1,\n  decimals = 1,\n  prefix = \"\",\n  suffix = \"×\",\n  label = \"Faster renders on the laptop you already own.\",\n  lean = 14,\n  steps = 3,\n  accent,\n  background,\n  speed = 1,\n  theme = \"dark\",\n}: LeanCountProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H, fps } = useVideoConfig();\n  const c = THEMES[theme];\n  const spot = accent ?? c.accent;\n  const n = Math.max(1, Math.round(steps));\n  const chars = Array.from(\n    new Set(\n      Array.from(\n        `${prefix}${suffix}0123456789.,${format(value, decimals)}${format(from, decimals)}`,\n      ),\n    ),\n  ).sort();\n  const m = useMetrics(chars);\n\n  const transparent = background === \"transparent\";\n  const stage = transparent ? \"transparent\" : (background ?? c.bg);\n\n  if (!m) {\n    return (\n      <div style={{ position: \"absolute\", inset: 0, background: stage }} />\n    );\n  }\n\n  const surgeAt = (k: number) => FIRST_SURGE + k * SURGE_GAP;\n  let countK = 0;\n  let slantK = 0;\n  for (let k = 0; k < n; k++) {\n    countK += interpolate(t, [surgeAt(k), surgeAt(k) + COUNT_LEN], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n    slantK += spring({ frame: t - surgeAt(k), fps, config: SNAP });\n  }\n  countK /= n;\n  slantK /= n;\n  const v = from + (value - from) * countK;\n  const glyphs = Array.from(`${prefix}${format(v, decimals)}${suffix}`);\n  const runOf = (s: string) =>\n    Array.from(s).reduce((a, ch) => a + (m.adv[ch] ?? 0.6), -TRACK);\n  const widest = Math.max(\n    runOf(`${prefix}${format(value, decimals)}${suffix}`),\n    runOf(`${prefix}${format(from, decimals)}${suffix}`),\n  );\n  const slant = -lean + 2 * lean * slantK;\n  const tan = Math.tan((Math.abs(lean) * Math.PI) / 180);\n\n  const portrait = H > W * 1.1;\n  const M = Math.min(W, H) * (portrait ? 0.08 : 0.075);\n  const labelSize = portrait ? W * 0.068 : H * 0.062;\n  const labelW = portrait ? W - M * 2 : W * 0.4;\n  const F = Math.min(\n    (H * (portrait ? 0.26 : 0.5)) / m.cap,\n    (W - M * 2) / (widest + tan * m.cap),\n  );\n  const capPx = m.cap * F;\n  const baseY = H - M * (portrait ? 2.2 : 1.25);\n  const right = W - M - tan * capPx;\n\n  let x = 0;\n  const slots = glyphs.map((ch, i) => {\n    const gx = x;\n    x += m.adv[ch] ?? 0.6;\n    return { ch, i, gx };\n  });\n  const runW = (x - TRACK) * F;\n  const left = right - runW;\n  const suffixFrom = glyphs.length - Array.from(suffix).length;\n\n  const ruleP = interpolate(t, [2, 30], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IN_OUT,\n  });\n  const labelWords = label ? label.split(\" \") : [];\n\n  return (\n    <AbsoluteFill style={{ background: stage, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: baseY + Math.max(1.5, F * 0.012),\n          width: W,\n          height: Math.max(1.5, F * 0.008),\n          background: c.rule,\n          transformOrigin: \"100% 50%\",\n          transform: `scaleX(${ruleP.toFixed(4)})`,\n        }}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          left,\n          top: baseY - m.baseline * F,\n          width: runW + F,\n          height: F * 1.3,\n          transformOrigin: `0px ${(m.baseline * F).toFixed(2)}px`,\n          transform: `skewX(${(-slant).toFixed(3)}deg)`,\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            clipPath: `inset(${(-F * 0.3).toFixed(1)}px ${-F}px ${((1.3 - m.baseline - 0.02) * F).toFixed(1)}px ${-F}px)`,\n          }}\n        >\n          {slots.map(({ ch, i, gx }) => {\n            const p = interpolate(\n              t,\n              [RISE_AT + i * RISE_GAP, RISE_AT + i * RISE_GAP + RISE_LEN],\n              [0, 1],\n              { ...CLAMP, easing: EASE_OUT },\n            );\n            return (\n              <span\n                key={`g${i}`}\n                style={{\n                  position: \"absolute\",\n                  left: gx * F,\n                  top: 0,\n                  height: F * 1.3,\n                  lineHeight: `${F}px`,\n                  whiteSpace: \"pre\",\n                  fontFamily: bricolage.fontFamily,\n                  fontWeight: 500,\n                  fontSize: F,\n                  fontFeatureSettings: '\"tnum\" 1',\n                  color: i >= suffixFrom ? spot : c.ink,\n                  transform: `translateY(${((1 - p) * F * 0.95).toFixed(2)}px)`,\n                }}\n              >\n                {ch}\n              </span>\n            );\n          })}\n        </div>\n      </div>\n      {label ? (\n        <div\n          style={{\n            position: \"absolute\",\n            left: M,\n            top: M,\n            width: labelW,\n            fontFamily: bricolage.fontFamily,\n            fontWeight: 400,\n            fontSize: labelSize,\n            letterSpacing: \"-0.02em\",\n            lineHeight: 1.1,\n            color: c.ink,\n            textWrap: \"balance\",\n          }}\n        >\n          {labelWords.map((w, i) => {\n            const p = interpolate(\n              t,\n              [LABEL_AT + i * 3, LABEL_AT + i * 3 + 16],\n              [0, 1],\n              { ...CLAMP, easing: EASE_OUT },\n            );\n            return (\n              <span key={`${w}-${i}`}>\n                <span\n                  style={{\n                    display: \"inline-block\",\n                    opacity: p,\n                    transform: `translateY(${((1 - p) * labelSize * 0.6).toFixed(2)}px)`,\n                  }}\n                >\n                  {w}\n                </span>\n                {i < labelWords.length - 1 ? \" \" : \"\"}\n              </span>\n            );\n          })}\n        </div>\n      ) : null}\n      {transparent ? null : <Grain opacity={c.grain} />}\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/lean-count.tsx"
    }
  ],
  "type": "registry:component"
}
