{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fraction-split",
  "title": "Fraction Split",
  "description": "A row of eight boxes shows one filled, matching the headline \"1/8 the time\".",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/fraction-split/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/SchibstedGrotesk\";\nimport { type ReactNode, useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\n// gradients.sh \"fluted\" palette\nconst FLUTED = {\n  bg: \"#050507\",\n  c: [\"#1d2a8f\", \"#2f7bff\", \"#ff7a3d\", \"#ffe2c4\"],\n} as const;\n\nexport interface FractionSplitProps {\n  line?: string;\n  highlight?: string;\n  accentPiece?: number;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst SEGS = 8;\nconst LEVELS = [\n  { at: 24, label: \"1/2\", size: 4 },\n  { at: 50, label: \"1/4\", size: 2 },\n  { at: 76, label: \"1/8\", size: 1 },\n];\nconst REG = 104;\nconst REG_LEN = 26;\nconst TEXT_IN = REG + 12;\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 SPLIT_SPRING = { damping: 17, stiffness: 170, mass: 0.7 };\n\nconst THEMES = {\n  dark: {\n    bg: FLUTED.bg,\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    outline: \"rgba(243,242,239,0.34)\",\n    hl: [\"#6a9dff\", FLUTED.c[2], \"#ffb27e\"],\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    outline: \"rgba(18,18,20,0.34)\",\n    hl: [FLUTED.c[0], FLUTED.c[2], \"#e85a26\"],\n    grain: 0.045,\n  },\n} as const;\n\nfunction boundaryLevel(b: number) {\n  if (b === 4) return 0;\n  if (b === 2 || b === 6) return 1;\n  return 2;\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={37}\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 FractionSplit({\n  line = \"Renders in 1/8 the time.\",\n  highlight = \"1/8\",\n  accentPiece = 0,\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: FractionSplitProps) {\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 ap = Math.max(0, Math.min(SEGS - 1, Math.round(accentPiece)));\n\n  const cx = width / 2;\n  const cy = height / 2;\n  const barW = Math.min(width * 0.8, 1040 * u);\n  const barH = 116 * u;\n  const G = 14 * u;\n  const R = 8 * u;\n  const E = 3 * G;\n  const textSize = 96 * u;\n  const textLines = line.length * 0.43 * textSize > barW * 0.98 ? 2 : 1;\n  const textH = textSize * textLines * 1.02;\n  const textGap = 58 * u;\n  const finalTop = cy - (barH + textGap + textH) / 2;\n\n  const open: number[] = [0];\n  for (let b = 1; b < SEGS; b++) {\n    const lv = LEVELS[boundaryLevel(b)];\n    const stagger = boundaryLevel(b) === 2 ? Math.abs(b - 4) * 0.6 : 0;\n    open.push(\n      spring({ frame: t - lv.at - stagger, fps, config: SPLIT_SPRING }),\n    );\n  }\n  const totalGap = open.reduce((s, x) => s + x, 0) * G;\n  const segW = (barW - totalGap) / SEGS;\n\n  const reg = interpolate(t, [REG, REG + REG_LEN], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const rise = interpolate(t, [REG, REG + 16], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const drainOf = (j: number) => {\n    const order = j > ap ? j - 1 : j;\n    return interpolate(\n      t,\n      [REG + 2 + order * 1.5, REG + 16 + order * 1.5],\n      [0, 1],\n      { ...CLAMP, easing: EASE_IO },\n    );\n  };\n  const left = cx - barW / 2 - (E / 2) * reg;\n  const xs: number[] = [];\n  let acc = left;\n  for (let j = 0; j < SEGS; j++) {\n    if (j > 0) acc += open[j] * G;\n    xs.push(acc + (j > ap ? E * reg : 0));\n    acc += segW;\n  }\n  const top = cy - barH / 2 + (finalTop - (cy - barH / 2)) * reg;\n\n  const wipe = interpolate(t, [2, 22], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n\n  const runs: { j0: number; j1: number }[] = [];\n  for (let j = 0; j < SEGS; j++) {\n    const solo = reg > 0 || j === 0 || open[j] > 0.002;\n    if (solo) runs.push({ j0: j, j1: j });\n    else runs[runs.length - 1].j1 = j;\n  }\n\n  const cornerAt = (b: number) =>\n    b <= 0 || b >= SEGS ? R : R * Math.min(1, open[b] * 1.6 + reg);\n\n  const labelStyle = {\n    position: \"absolute\",\n    left: 14 * u,\n    fontFamily: sans,\n    fontWeight: 500,\n    fontSize: 20 * u,\n    lineHeight: 1.2,\n    letterSpacing: \"-0.01em\",\n    fontVariantNumeric: \"tabular-nums\",\n    whiteSpace: \"nowrap\",\n  } as const;\n  const lastLabel = LEVELS[LEVELS.length - 1].label;\n  const drift = interpolate(t, [REG, REG + REG_LEN + 50], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  // the palette's blue stop follows the accent prop\n  const blue = accent;\n  const flute = (deg: number) =>\n    `linear-gradient(${deg + 12 * drift}deg, ${FLUTED.c[3]} 0%, ${FLUTED.c[2]} 30%, ${blue} 68%, ${FLUTED.c[0]} 100%)`;\n\n  const pieces = runs.map(({ j0, j1 }) => {\n    const x = xs[j0];\n    const w = xs[j1] + segW - x;\n    const rl = cornerAt(j0);\n    const rr = cornerAt(j1 + 1);\n    const box = {\n      position: \"absolute\",\n      left: x,\n      top,\n      width: w,\n      height: barH,\n      borderTopLeftRadius: rl,\n      borderBottomLeftRadius: rl,\n      borderTopRightRadius: rr,\n      borderBottomRightRadius: rr,\n    } as const;\n    if (reg <= 0) {\n      return <div key={`p${j0}-${j1}`} style={{ ...box, background: p.fg }} />;\n    }\n    const isAccent = j0 === ap;\n    const fillH = barH * (isAccent ? rise : 1 - drainOf(j0));\n    return (\n      <div key={`p${j0}-${j1}`}>\n        {isAccent ? null : (\n          <div\n            style={{\n              ...box,\n              boxSizing: \"border-box\",\n              border: `${Math.max(1, 1.5 * u)}px solid ${p.outline}`,\n            }}\n          />\n        )}\n        <div\n          style={{\n            ...box,\n            overflow: \"hidden\",\n            background: isAccent ? p.fg : \"transparent\",\n          }}\n        >\n          {isAccent ? (\n            <div style={{ ...labelStyle, top: 12 * u, color: p.bg }}>\n              {lastLabel}\n            </div>\n          ) : null}\n          <div\n            style={{\n              position: \"absolute\",\n              left: 0,\n              right: 0,\n              bottom: 0,\n              height: fillH,\n              overflow: \"hidden\",\n              backgroundColor: p.fg,\n              backgroundImage: isAccent ? flute(20) : undefined,\n              backgroundSize: isAccent ? `${w}px ${barH}px` : undefined,\n              backgroundPosition: \"left bottom\",\n            }}\n          >\n            <div\n              style={{\n                ...labelStyle,\n                top: 12 * u - (barH - fillH),\n                color: isAccent ? \"#FFFFFF\" : p.bg,\n              }}\n            >\n              {lastLabel}\n            </div>\n          </div>\n        </div>\n      </div>\n    );\n  });\n\n  const labels: ReactNode[] = [];\n  LEVELS.forEach((lv, li) => {\n    const inK = interpolate(t, [lv.at + 6, lv.at + 16], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n    const next = LEVELS[li + 1];\n    const outK = next\n      ? interpolate(t, [next.at - 5, next.at], [0, 1], CLAMP)\n      : 0;\n    if (!next && reg > 0) return;\n    for (let g = 0; g < SEGS / lv.size; g++) {\n      const j0 = g * lv.size;\n      const op = inK * (1 - outK);\n      if (op <= 0) continue;\n      labels.push(\n        <div\n          key={`l${li}-${g.toString()}`}\n          style={{\n            ...labelStyle,\n            left: xs[j0] + 14 * u,\n            top: top + 12 * u,\n            color: p.bg,\n            opacity: op,\n            transform: `translateY(${(1 - inK) * 6 * u}px)`,\n          }}\n        >\n          {lv.label}\n        </div>,\n      );\n    }\n  });\n\n  const idx = highlight ? line.indexOf(highlight) : -1;\n  const words: { text: string; hl: boolean }[] = [];\n  const pushWords = (s: string, hl: boolean) => {\n    for (const w of s.split(/\\s+/).filter(Boolean)) words.push({ text: w, hl });\n  };\n  if (idx >= 0) {\n    pushWords(line.slice(0, idx), false);\n    words.push({ text: highlight, hl: true });\n    const rest = line.slice(idx + highlight.length);\n    if (/^\\S/.test(rest) && words.length) {\n      const m = /^(\\S+)(.*)$/.exec(rest);\n      if (m) {\n        words[words.length - 1] = {\n          text: `${highlight}${m[1]}`,\n          hl: true,\n        };\n        pushWords(m[2], false);\n      }\n    } else pushWords(rest, false);\n  } else pushWords(line, false);\n\n  const push = interpolate(t, [0, REG + REG_LEN + 40], [0.985, 1.01], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{ transform: `scale(${push})`, transformOrigin: \"50% 50%\" }}\n      >\n        <AbsoluteFill\n          style={{\n            clipPath: `inset(0 ${(1 - wipe) * 100}% 0 0)`,\n          }}\n        >\n          {pieces}\n          {labels}\n        </AbsoluteFill>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: cx - barW * 0.5,\n            width: barW,\n            top: finalTop + barH + textGap,\n            textAlign: \"center\",\n            fontFamily: sans,\n            fontWeight: 400,\n            fontSize: textSize,\n            lineHeight: 1.02,\n            letterSpacing: \"-0.035em\",\n            color: p.fg,\n            textWrap: \"balance\",\n          }}\n        >\n          {words.map((w, i) => {\n            const k = interpolate(\n              t,\n              [TEXT_IN + i * 3, TEXT_IN + i * 3 + 18],\n              [0, 1],\n              { ...CLAMP, easing: EASE_OUT },\n            );\n            return (\n              <span key={`w${i.toString()}`}>\n                <span\n                  style={{\n                    display: \"inline-block\",\n                    color: w.hl ? \"transparent\" : p.fg,\n                    ...(w.hl\n                      ? {\n                          fontWeight: 500,\n                          paddingRight: \"0.04em\",\n                          backgroundImage: `linear-gradient(${172 + 10 * drift}deg, ${p.hl[0]} 8%, ${blue} 32%, ${p.hl[1]} 70%, ${p.hl[2]} 94%)`,\n                          WebkitBackgroundClip: \"text\",\n                          backgroundClip: \"text\",\n                        }\n                      : null),\n                    opacity: k,\n                    filter: k < 0.99 ? `blur(${(1 - k) * 8 * u}px)` : undefined,\n                    transform: `translateY(${(1 - k) * 18 * u}px)`,\n                  }}\n                >\n                  {w.text}\n                </span>\n                {i < words.length - 1 ? \" \" : null}\n              </span>\n            );\n          })}\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/fraction-split.tsx"
    }
  ],
  "type": "registry:component"
}
