{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "compact-stat",
  "title": "Compact Stat",
  "description": "A big 1.2M with a blue-gradient suffix sits above a small caption on a dark ground.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/compact-stat/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadHanken } from \"@remotion/google-fonts/HankenGrotesk\";\nimport type React from \"react\";\nimport { useEffect, useId, useMemo, 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 hanken = loadHanken(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface CompactStatProps {\n  value?: number;\n  decimals?: number;\n  suffix?: string;\n  prefix?: string;\n  label?: string;\n  accent?: string;\n  background?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    ink: \"#F4F3F0\",\n    muted: \"#8E8D94\",\n    fill: (a: string) =>\n      `linear-gradient(170deg, #f4f7ff 6%, #8fb4ff 38%, ${a} 72%, #1a1660 118%)`,\n    ground: (a: string) =>\n      `radial-gradient(60% 46% at 50% 112%, color-mix(in oklab, ${a} 34%, transparent) 0%, color-mix(in oklab, #1a1660 40%, transparent) 48%, transparent 80%)`,\n    grain: 0.05,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    ink: \"#111113\",\n    muted: \"#6E6C67\",\n    fill: (a: string) =>\n      `linear-gradient(170deg, #1a1660 0%, ${a} 48%, #8fb4ff 112%)`,\n    ground: (a: string) =>\n      `radial-gradient(60% 46% at 50% 114%, color-mix(in oklab, ${a} 16%, transparent) 0%, transparent 78%)`,\n    grain: 0.035,\n  },\n} as const;\n\nconst TRACK = -0.025;\nconst FONT_PX = 400;\n\nconst RISE_AT = 0;\nconst RISE_LEN = 18;\nconst RISE_GAP = 2;\nconst LABEL_AT = 12;\nconst RETRACT_AT = 40;\nconst RETRACT_LEN = 18;\nconst SUFFIX_AT = 52;\nconst GROW_AT = 58;\nconst GROW_LEN = 28;\n\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_REEL = Easing.bezier(0.55, 0, 0.2, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst SCALES = [\"\", \"K\", \"M\", \"B\", \"T\"];\n\ninterface Plan {\n  full: string[];\n  short: string[];\n  keep: number[];\n  sep: number;\n  mag: number;\n}\n\nfunction plan(\n  value: number,\n  decimals: number,\n  suffix: string | undefined,\n  prefix: string,\n): Plan {\n  const v = Math.max(0, Math.floor(Math.abs(value)));\n  const digits = String(v);\n  const tier = Math.min(\n    SCALES.length - 1,\n    Math.max(0, Math.floor((digits.length - 1) / 3)),\n  );\n  const intLen = digits.length - tier * 3;\n  let dec = digits.slice(intLen, intLen + Math.max(0, Math.min(2, decimals)));\n  dec = dec.replace(/0+$/, \"\");\n  const grouped: string[] = [];\n  for (let i = 0; i < digits.length; i++) {\n    const fromEnd = digits.length - i;\n    if (i > 0 && fromEnd % 3 === 0) grouped.push(\",\");\n    grouped.push(digits[i]);\n  }\n  const pre = Array.from(prefix);\n  const full = [...pre, ...grouped];\n  const mag = suffix ?? SCALES[tier];\n  const short = [\n    ...pre,\n    ...Array.from(digits.slice(0, intLen)),\n    ...(dec ? [\".\", ...Array.from(dec)] : []),\n    ...Array.from(mag),\n  ];\n  const keep = full.map(() => -1);\n  let j = 0;\n  for (let i = 0; i < pre.length; i++) keep[i] = j++;\n  let seen = 0;\n  let sep = -1;\n  const keptDigits = intLen + dec.length;\n  for (let i = pre.length; i < full.length; i++) {\n    const ch = full[i];\n    if (ch === \",\") {\n      if (seen === intLen && dec.length > 0 && tier > 0) {\n        keep[i] = j++;\n        sep = i;\n      }\n      continue;\n    }\n    if (seen < keptDigits) keep[i] = j++;\n    seen++;\n  }\n  return {\n    full,\n    short,\n    keep,\n    sep,\n    mag: tier > 0 || suffix ? j : -1,\n  };\n}\n\ninterface Metrics {\n  xf: number[];\n  wf: number[];\n  xs: number[];\n  ws: number[];\n  fullW: number;\n  shortW: number;\n  cap: number;\n  base: number;\n  label: number;\n}\n\nfunction layout(ctx: CanvasRenderingContext2D, chars: string[]) {\n  const x: number[] = [];\n  const w: number[] = [];\n  let prefix = \"\";\n  for (let i = 0; i < chars.length; i++) {\n    const a = ctx.measureText(prefix).width / FONT_PX + TRACK * i;\n    prefix += chars[i];\n    const b = ctx.measureText(prefix).width / FONT_PX + TRACK * (i + 1);\n    x.push(a);\n    w.push(b - a);\n  }\n  const total =\n    ctx.measureText(chars.join(\"\")).width / FONT_PX +\n    TRACK * Math.max(0, chars.length - 1);\n  return { x, w, total };\n}\n\nfunction measure(p: Plan, label: string): Metrics {\n  const canvas = document.createElement(\"canvas\");\n  const ctx = canvas.getContext(\"2d\");\n  if (!ctx) throw new Error(\"compact-stat: no 2d context\");\n  ctx.font = `500 ${FONT_PX}px \"${hanken.fontFamily}\"`;\n  const f = layout(ctx, p.full);\n  const s = layout(ctx, p.short);\n  const zm = ctx.measureText(\"0\");\n  const cap = zm.actualBoundingBoxAscent / FONT_PX;\n  const asc = zm.fontBoundingBoxAscent / FONT_PX;\n  const desc = zm.fontBoundingBoxDescent / FONT_PX;\n  ctx.font = `400 ${FONT_PX}px \"${hanken.fontFamily}\"`;\n  return {\n    xf: f.x,\n    wf: f.w,\n    xs: s.x,\n    ws: s.w,\n    fullW: f.total,\n    shortW: s.total,\n    cap,\n    base: (1 - (asc + desc)) / 2 + asc,\n    label: label ? ctx.measureText(label).width / FONT_PX : 0,\n  };\n}\n\nfunction useMetrics(p: Plan, label: string) {\n  const [metrics, setMetrics] = useState<Metrics | null>(null);\n  const [handle] = useState(() => delayRender(\"compact-stat: measuring\"));\n  const released = useRef(false);\n  useEffect(() => {\n    let live = true;\n    Promise.all([\n      hanken.waitUntilDone(),\n      document.fonts.load(`500 100px \"${hanken.fontFamily}\"`),\n      document.fonts.load(`400 100px \"${hanken.fontFamily}\"`),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        setMetrics(measure(p, label));\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [p, label, handle]);\n  return metrics;\n}\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `cs${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        opacity,\n        mixBlendMode: \"overlay\",\n        pointerEvents: \"none\",\n      }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={3}\n        />\n        <feColorMatrix type=\"saturate\" values=\"0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\nexport function CompactStat({\n  value = 1200000,\n  decimals = 1,\n  suffix,\n  prefix = \"\",\n  label = \"frames rendered with remocn\",\n  accent = \"#3B5BFD\",\n  background,\n  speed = 1,\n  theme = \"dark\",\n}: CompactStatProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H, fps } = useVideoConfig();\n  const c = THEMES[theme];\n  const bg = background ?? c.bg;\n\n  const p = useMemo(\n    () => plan(value, decimals, suffix, prefix),\n    [value, decimals, suffix, prefix],\n  );\n  const m = useMetrics(p, label);\n\n  if (!m) {\n    return <div style={{ position: \"absolute\", inset: 0, background: bg }} />;\n  }\n\n  const F1 = Math.min(H * 0.3, (W * 0.86) / m.fullW);\n  const F2 = Math.min(H * 0.48, (W * 0.8) / m.shortW, F1 * 2.6);\n  const grow = interpolate(t, [GROW_AT, GROW_AT + GROW_LEN], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const F = F1 + (F2 - F1) * grow;\n\n  const reel = interpolate(t, [RETRACT_AT, RETRACT_AT + RETRACT_LEN], [0, 1], {\n    ...CLAMP,\n    easing: EASE_REEL,\n  });\n  const magP = spring({\n    frame: t - SUFFIX_AT,\n    fps,\n    config: { damping: 18, stiffness: 120, mass: 0.9 },\n  });\n\n  const keptX = (i: number) => {\n    const j = p.keep[i];\n    return m.xf[i] + (m.xs[j] - m.xf[i]) * reel;\n  };\n  const lastKept = p.keep.reduce((acc, j, i) => (j >= 0 ? i : acc), -1);\n  const slotEm =\n    lastKept >= 0\n      ? keptX(lastKept) +\n        m.wf[lastKept] +\n        (m.ws[p.keep[lastKept]] - m.wf[lastKept]) * reel\n      : 0;\n  const tailStart = lastKept + 1;\n  const tailFrom = tailStart < p.full.length ? m.xf[tailStart] : slotEm;\n  const tailEnd = m.fullW;\n  const tailShift = (tailEnd - tailFrom + 0.04) * reel + (tailFrom - slotEm);\n\n  const hasMag = p.mag >= 0;\n  const magW = hasMag\n    ? m.xs[p.short.length - 1] + m.ws[p.short.length - 1] - m.xs[p.mag]\n    : 0;\n  const magX = hasMag\n    ? slotEm - magW + (m.xs[p.mag] - slotEm + magW) * magP\n    : slotEm;\n\n  const width = Math.max(slotEm, tailEnd - tailShift, magX + magW);\n  const left = (W - width * F) / 2;\n\n  const labelSize = label\n    ? Math.min(Math.max(F2 * 0.12, H * 0.044), H * 0.056, (W * 0.84) / m.label)\n    : 0;\n  const gap = Math.max(F * 0.28, H * 0.05);\n  const blockH = m.cap * F + (label ? gap + labelSize : 0);\n  const baseY = (H - blockH) / 2 + m.cap * F;\n\n  const maskTop = baseY - m.cap * F - F * 0.08;\n  const maskH = m.cap * F + F * 0.3;\n\n  const glyph = (\n    ch: string,\n    x: number,\n    key: string,\n    rise: number,\n    extra?: React.CSSProperties,\n  ) => (\n    <div\n      key={key}\n      style={{\n        position: \"absolute\",\n        left: x,\n        top: baseY - maskTop - m.base * F + (1 - rise) * maskH * 1.05,\n        height: F,\n        fontFamily: hanken.fontFamily,\n        fontWeight: 500,\n        fontSize: F,\n        lineHeight: `${F}px`,\n        color: c.ink,\n        whiteSpace: \"pre\",\n        fontFeatureSettings: '\"lnum\" 1',\n        ...extra,\n      }}\n    >\n      {ch}\n    </div>\n  );\n\n  const riseOf = (i: number) =>\n    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\n  const slotPx = left + slotEm * F;\n  const kept: React.ReactNode[] = [];\n  const tail: React.ReactNode[] = [];\n  p.full.forEach((ch, i) => {\n    const r = riseOf(i);\n    if (p.keep[i] >= 0) {\n      if (i === p.sep) {\n        const swap = interpolate(reel, [0.25, 0.6], [0, 1], CLAMP);\n        const x = left + keptX(i) * F;\n        kept.push(\n          glyph(ch, x, `k${i}`, r, {\n            opacity: 1 - swap,\n            clipPath: `inset(0 0 ${(swap * 22).toFixed(1)}% 0)`,\n          }),\n          glyph(\".\", x, `k${i}p`, r, { opacity: swap }),\n        );\n        return;\n      }\n      kept.push(glyph(ch, left + keptX(i) * F, `k${i}`, r));\n      return;\n    }\n    const x = left + (m.xf[i] - tailShift) * F - slotPx;\n    tail.push(glyph(ch, x, `t${i}`, r));\n  });\n\n  const magGlyphs = hasMag\n    ? p.short.slice(p.mag).map((ch, k) =>\n        glyph(\n          ch,\n          left + (magX + m.xs[p.mag + k] - m.xs[p.mag]) * F - slotPx,\n          `m${k}`,\n          1,\n          {\n            backgroundImage: c.fill(accent),\n            WebkitBackgroundClip: \"text\",\n            backgroundClip: \"text\",\n            color: \"transparent\",\n          },\n        ),\n      )\n    : [];\n\n  const labelP = interpolate(t, [LABEL_AT, LABEL_AT + 18], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const groundP = interpolate(t, [GROW_AT, GROW_AT + GROW_LEN + 10], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n\n  return (\n    <AbsoluteFill style={{ background: bg, overflow: \"hidden\" }}>\n      {bg === \"transparent\" ? null : (\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            background: c.ground(accent),\n            opacity: groundP,\n          }}\n        />\n      )}\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          width: W,\n          top: maskTop,\n          height: maskH,\n          overflow: \"hidden\",\n        }}\n      >\n        {kept}\n        <div\n          style={{\n            position: \"absolute\",\n            left: slotPx,\n            top: 0,\n            width: Math.max(0, W - slotPx),\n            height: maskH,\n            overflow: \"hidden\",\n          }}\n        >\n          {tail}\n          {magGlyphs}\n        </div>\n      </div>\n      {label ? (\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            width: W,\n            top: baseY + gap - labelSize * 0.2,\n            height: labelSize * 1.4,\n            overflow: \"hidden\",\n            textAlign: \"center\",\n            fontFamily: hanken.fontFamily,\n            fontWeight: 400,\n            fontSize: labelSize,\n            lineHeight: `${labelSize * 1.2}px`,\n            letterSpacing: \"-0.01em\",\n            color: c.muted,\n            whiteSpace: \"pre\",\n          }}\n        >\n          <div\n            style={{\n              transform: `translateY(${((1 - labelP) * labelSize * 1.3).toFixed(2)}px)`,\n            }}\n          >\n            {label}\n          </div>\n        </div>\n      ) : null}\n      {bg === \"transparent\" ? null : <Grain opacity={c.grain} />}\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/compact-stat.tsx"
    }
  ],
  "type": "registry:component"
}
