{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stat-inline",
  "title": "Stat Inline",
  "description": "A key number inside a sentence is highlighted: 'Builders shipped 12,480 launch videos this year.'.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/stat-inline/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadGeist } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadGeistMono } from \"@remotion/google-fonts/GeistMono\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport {\n  AbsoluteFill,\n  continueRender,\n  delayRender,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst geist = loadGeist(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst mono = loadGeistMono(\"normal\", { weights: [\"400\"], subsets: [\"latin\"] });\n\nexport interface StatInlineProps {\n  value?: string;\n  text?: string;\n  label?: string;\n  align?: \"left\" | \"center\";\n  accent?: string;\n  background?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst MESH = [\"#ecdcf2\", \"#8796ee\", \"#2e45b7\", \"#0e1645\"] as const;\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    fg: \"#F4F3F0\",\n    label: \"#8C8B91\",\n    stops: (a: string) => [MESH[0], MESH[1], a, MESH[1]],\n    ground: 0.55,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#111113\",\n    label: \"#76746E\",\n    stops: (a: string) => [MESH[3], MESH[2], a, \"#7d8cf0\"],\n    ground: 0.32,\n  },\n} as const;\n\nconst TRACK_HERO = -0.045;\nconst TRACK_TEXT = -0.022;\nconst LINE = 1.16;\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_IO = Easing.bezier(0.72, 0, 0.18, 1);\n\ninterface Seg {\n  text: string;\n  value: boolean;\n}\n\nfunction parse(text: string, value: string): Seg[][] {\n  const units: Seg[][] = [];\n  for (const raw of text.split(/\\s+/).filter(Boolean)) {\n    const at = raw.indexOf(\"{value}\");\n    if (at < 0) {\n      units.push([{ text: raw, value: false }]);\n      continue;\n    }\n    const segs: Seg[] = [];\n    if (at > 0) segs.push({ text: raw.slice(0, at), value: false });\n    segs.push({ text: value, value: true });\n    const rest = raw.slice(at + 7);\n    if (rest) segs.push({ text: rest, value: false });\n    units.push(segs);\n  }\n  if (!units.some((u) => u.some((s) => s.value))) {\n    units.unshift([{ text: value, value: true }]);\n  }\n  return units;\n}\n\ninterface Metrics {\n  key: string;\n  seg: number[][];\n  space: number;\n  glyphX: number[];\n  valueW: number;\n  valueTextW: number;\n}\n\nfunction useMetrics(units: Seg[][], value: string): Metrics {\n  const key = `${value}\\u0002${units\n    .map((u) => u.map((s) => `${s.value ? 1 : 0}${s.text}`).join(\"\\u0003\"))\n    .join(\"\\u0001\")}`;\n  const estimate = (): Metrics => {\n    const glyphX = [...value].map((_, i) => i * 0.56);\n    return {\n      key,\n      seg: units.map((u) => u.map((s) => s.text.length * 0.5)),\n      space: 0.24,\n      glyphX,\n      valueW: value.length * 0.56,\n      valueTextW: value.length * 0.55,\n    };\n  };\n  const [m, setM] = useState<Metrics | null>(null);\n  const [handle] = useState(() => delayRender(\"stat-inline: measuring\"));\n  const released = useRef(false);\n\n  useEffect(() => {\n    let live = true;\n    const f400 = `400 100px \"${geist.fontFamily}\"`;\n    const f500 = `500 100px \"${geist.fontFamily}\"`;\n    Promise.all([\n      geist.waitUntilDone(),\n      document.fonts.load(f400),\n      document.fonts.load(f500),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n        if (!ctx) return;\n        const c = ctx as unknown as { letterSpacing: string };\n        const [v, ...rest] = key.split(\"\\u0002\");\n        const list = rest\n          .join(\"\\u0002\")\n          .split(\"\\u0001\")\n          .map((u) =>\n            u.split(\"\\u0003\").map((s) => ({\n              value: s[0] === \"1\",\n              text: s.slice(1),\n            })),\n          );\n        const width = (s: string, font: string, track: number) => {\n          ctx.font = font;\n          c.letterSpacing = `${track * 100}px`;\n          return ctx.measureText(s).width / 100;\n        };\n        const chars = [...v];\n        const glyphX = chars.map((_, i) =>\n          i === 0 ? 0 : width(chars.slice(0, i).join(\"\"), f500, TRACK_HERO),\n        );\n        const valueW = width(v, f500, TRACK_HERO) - TRACK_HERO;\n        const valueTextW = width(v, f500, TRACK_TEXT) - TRACK_TEXT;\n        const seg = list.map((u) =>\n          u.map((s) =>\n            s.value ? valueTextW : width(s.text, f400, TRACK_TEXT) - TRACK_TEXT,\n          ),\n        );\n        const space = width(\" \", f400, 0);\n        setM({ key, seg, space, glyphX, valueW, valueTextW });\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [key, handle]);\n\n  return m && m.key === key ? m : estimate();\n}\n\nfunction balance(w: number[], space: number, lines: number) {\n  const n = w.length;\n  const rowW = (a: number, b: number) => {\n    let s = 0;\n    for (let i = a; i < b; i++) s += w[i] + (i > a ? space : 0);\n    return s;\n  };\n  const best: number[][] = [];\n  const cut: number[][] = [];\n  for (let k = 0; k <= lines; k++) {\n    best.push(new Array(n + 1).fill(Number.POSITIVE_INFINITY));\n    cut.push(new Array(n + 1).fill(0));\n  }\n  best[0][0] = 0;\n  for (let k = 1; k <= lines; k++) {\n    for (let i = 1; i <= n; i++) {\n      for (let j = k - 1; j < i; j++) {\n        const v = Math.max(best[k - 1][j], rowW(j, i));\n        if (v < best[k][i]) {\n          best[k][i] = v;\n          cut[k][i] = j;\n        }\n      }\n    }\n  }\n  const rows: [number, number][] = [];\n  let i = n;\n  for (let k = lines; k >= 1; k--) {\n    const j = cut[k][i];\n    rows.unshift([j, i]);\n    i = j;\n  }\n  return { rows, widest: best[lines][n], rowW };\n}\n\nexport function StatInline({\n  value = \"12,480\",\n  text = \"Builders shipped {value} launch videos with remocn this year.\",\n  label = \"Launch videos shipped\",\n  align = \"left\",\n  accent = \"#3B5BFD\",\n  background,\n  speed = 1,\n  theme = \"dark\",\n}: StatInlineProps) {\n  const frame = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const t = THEMES[theme];\n  const id = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n\n  const units = parse(text, value);\n  const m = useMetrics(units, value);\n  const unitW = m.seg.map((s) => s.reduce((a, b) => a + b, 0));\n  const valueUnit = units.findIndex((u) => u.some((s) => s.value));\n\n  const portrait = H > W * 1.1;\n  const boxW = W * (portrait ? 0.84 : 0.8);\n  const maxFs = Math.min(\n    H * (units.length <= 3 ? 0.2 : 0.13),\n    W * (portrait ? 0.11 : 0.09),\n  );\n  let pick = balance(unitW, m.space, 1);\n  let fs = 0;\n  for (let n = 1; n <= Math.min(7, units.length); n++) {\n    const b = balance(unitW, m.space, n);\n    const f = Math.min(maxFs, boxW / b.widest, (H * 0.64) / (n * LINE));\n    if (f > fs * 1.04) {\n      fs = f;\n      pick = b;\n    }\n  }\n  const rows = pick.rows;\n  const blockW = pick.widest * fs;\n  const blockH = (rows.length - 1) * LINE * fs + 0.72 * fs;\n  const blockTop = H / 2 - blockH / 2;\n  const blockLeft = (W - blockW) / 2;\n\n  const placed = units.map(() => ({ x: 0, y: 0, row: 0 }));\n  rows.forEach(([a, b], r) => {\n    const rw = pick.rowW(a, b) * fs;\n    let x = align === \"center\" ? (W - rw) / 2 : blockLeft;\n    const y = blockTop + 0.72 * fs + r * LINE * fs;\n    for (let i = a; i < b; i++) {\n      placed[i] = { x, y, row: r };\n      x += (unitW[i] + m.space) * fs;\n    }\n  });\n  const vRow = placed[valueUnit]?.row ?? 0;\n  const vSegIndex = units[valueUnit].findIndex((s) => s.value);\n  const slotX =\n    placed[valueUnit].x +\n    m.seg[valueUnit].slice(0, vSegIndex).reduce((a, b) => a + b, 0) * fs;\n  const slotY = placed[valueUnit].y;\n\n  const heroFs = Math.min(\n    H * (portrait ? 0.24 : 0.42),\n    (W * (portrait ? 0.86 : 0.8)) / m.valueW,\n  );\n  const labelFs = label ? Math.max(14, Math.min(W, H) * 0.03) : 0;\n  const labelGap = heroFs * 0.24 + labelFs * 1.1;\n  const heroBase = H / 2 + (0.72 * heroFs) / 2 - (label ? labelGap : 0) / 2;\n  const heroX = (W - m.valueW * heroFs) / 2;\n\n  const MOVE = 52;\n  const MOVE_LEN = 28;\n  const mv = interpolate(frame, [MOVE, MOVE + MOVE_LEN], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const ratio = m.valueTextW / m.valueW;\n  const chars = [...value];\n  const B = heroFs;\n  const k = (fs / heroFs) ** mv;\n  const ox = heroX + (slotX - heroX) * mv;\n  const oy = heroBase + (slotY - heroBase) * mv;\n  const spread = 1 + (ratio - 1) * mv;\n\n  const glyphRise = (i: number) =>\n    interpolate(frame, [i * 2, i * 2 + 20], [1, 0], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n\n  const labelIn = interpolate(frame, [12, 28], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const labelOut = interpolate(frame, [MOVE - 2, MOVE + 8], [1, 0], {\n    ...CLAMP,\n    easing: Easing.bezier(0.55, 0, 1, 0.45),\n  });\n\n  const orderOf = (i: number) => {\n    const p = placed[i];\n    const dRow = Math.abs(p.row - vRow);\n    const dx = Math.abs(p.x - slotX) / Math.max(1, fs * 3);\n    return dRow * 1.5 + dx;\n  };\n  const wordRise = (i: number) => {\n    const s = MOVE + 17 + orderOf(i) * 2.5;\n    return interpolate(frame, [s, s + 18], [1, 0], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n  };\n\n  const groundIn = interpolate(frame, [0, 30], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const groundSettle = 1 - 0.45 * mv;\n  const drift = interpolate(frame, [0, 130], [0, 1], CLAMP);\n  const ground = [\n    `radial-gradient(60% 55% at ${30 + drift * 6}% ${108 - drift * 4}%, color-mix(in oklab, ${MESH[2]} 70%, transparent) 0%, transparent 70%)`,\n    `radial-gradient(45% 40% at ${78 - drift * 5}% ${100 - drift * 3}%, color-mix(in oklab, ${MESH[1]} 45%, transparent) 0%, transparent 72%)`,\n    `radial-gradient(40% 35% at ${52 + drift * 3}% 112%, color-mix(in oklab, ${accent} 55%, transparent) 0%, transparent 75%)`,\n  ].join(\", \");\n\n  const stops = t.stops(accent);\n  const vw = m.valueW;\n\n  return (\n    <AbsoluteFill style={{ background: background ?? t.bg }}>\n      {background === \"transparent\" ? null : (\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            backgroundImage: ground,\n            opacity: t.ground * groundIn * groundSettle,\n          }}\n        />\n      )}\n      <svg\n        width={W}\n        height={H}\n        viewBox={`0 0 ${W} ${H}`}\n        style={{ position: \"absolute\", inset: 0 }}\n        aria-hidden\n      >\n        <defs>\n          <linearGradient\n            id={`${id}-fill`}\n            gradientUnits=\"userSpaceOnUse\"\n            x1={0}\n            y1={-0.78 * B}\n            x2={vw * B}\n            y2={0.1 * B}\n          >\n            <stop offset=\"0\" stopColor={stops[0]} />\n            <stop offset={0.32 + 0.06 * drift} stopColor={stops[1]} />\n            <stop offset={0.7 + 0.04 * drift} stopColor={stops[2]} />\n            <stop offset=\"1\" stopColor={stops[3]} />\n          </linearGradient>\n          {chars.map((c, i) => (\n            <clipPath\n              key={`${c}-${i}`}\n              id={`${id}-g${i}`}\n              clipPathUnits=\"userSpaceOnUse\"\n            >\n              <rect\n                x={(m.glyphX[i] * spread - 0.1) * B}\n                y={-0.86 * B}\n                width={((m.glyphX[i + 1] ?? vw) - m.glyphX[i] + 0.2) * B}\n                height={1.12 * B}\n              />\n            </clipPath>\n          ))}\n        </defs>\n\n        <g transform={`translate(${ox} ${oy}) scale(${k})`}>\n          {chars.map((c, i) => (\n            <g key={`${c}-${i}`} clipPath={`url(#${id}-g${i})`}>\n              <text\n                x={m.glyphX[i] * spread * B}\n                y={glyphRise(i) * 1.05 * B}\n                fontSize={B}\n                fill={`url(#${id}-fill)`}\n                style={{ fontFamily: geist.fontFamily, fontWeight: 500 }}\n              >\n                {c}\n              </text>\n            </g>\n          ))}\n        </g>\n\n        {label ? (\n          <text\n            x={W / 2}\n            y={heroBase + labelGap + (1 - labelIn) * labelFs * 0.6}\n            fontSize={labelFs}\n            fill={t.label}\n            opacity={labelIn * labelOut}\n            textAnchor=\"middle\"\n            style={{\n              fontFamily: mono.fontFamily,\n              fontWeight: 400,\n              letterSpacing: \"0.08em\",\n              textTransform: \"uppercase\",\n            }}\n          >\n            {label}\n          </text>\n        ) : null}\n\n        {units.map((u, i) => {\n          const p = placed[i];\n          const r = wordRise(i);\n          let x = p.x;\n          return (\n            <g key={i}>\n              <clipPath id={`${id}-w${i}`} clipPathUnits=\"userSpaceOnUse\">\n                <rect\n                  x={p.x - fs * 0.1}\n                  y={p.y - fs * 0.98}\n                  width={(unitW[i] + 0.2) * fs}\n                  height={fs * 1.3}\n                />\n              </clipPath>\n              <g clipPath={`url(#${id}-w${i})`}>\n                {u.map((s, j) => {\n                  const sx = x;\n                  x += m.seg[i][j] * fs;\n                  if (s.value) return null;\n                  return (\n                    <text\n                      key={j}\n                      x={sx}\n                      y={p.y + r * fs * 1.05}\n                      fontSize={fs}\n                      fill={t.fg}\n                      style={{\n                        fontFamily: geist.fontFamily,\n                        fontWeight: 400,\n                        letterSpacing: `${TRACK_TEXT}em`,\n                      }}\n                    >\n                      {s.text}\n                    </text>\n                  );\n                })}\n              </g>\n            </g>\n          );\n        })}\n      </svg>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/stat-inline.tsx"
    }
  ],
  "type": "registry:component"
}
