{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "text-fill",
  "title": "Text fill",
  "description": "A controlled character sweep from muted text through vermilion into ink. Drive progress with a slider, scroll, or a video timeline.",
  "registryDependencies": [
    "@jbm/tokens"
  ],
  "files": [
    {
      "path": "registry/jbm/ui/text-fill.tsx",
      "content": "import type { CSSProperties } from \"react\"\nimport { color, font } from \"../lib/tokens\"\n\nexport type TextFillProps = {\n  text: string\n  /** Controlled progress, 0–1. Drive with scroll, a slider, or a video timeline. */\n  progress: number\n  dimColor?: string\n  accentColor?: string\n  textColor?: string\n  /** Render the complete text without a sweep. */\n  reducedMotion?: boolean\n  style?: CSSProperties\n  className?: string\n}\n\n/** A deterministic character sweep. No timers, browser globals, or Remotion. */\nexport function TextFill({\n  text,\n  progress,\n  dimColor = color.line,\n  accentColor = color.accent,\n  textColor = color.ink,\n  reducedMotion = false,\n  style,\n  className,\n}: TextFillProps) {\n  const amount = reducedMotion\n    ? 1\n    : Math.min(1, Math.max(0, Number.isFinite(progress) ? progress : 0))\n  const segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" })\n  const words = text.split(/(\\s+)/u)\n  const count = [...segmenter.segment(text.replace(/\\s/gu, \"\"))].length\n  let index = 0\n  return (\n    <span\n      className={className}\n      style={{\n        fontFamily: font.sans,\n        fontWeight: 800,\n        lineHeight: 1.15,\n        whiteSpace: \"pre-wrap\",\n        overflowWrap: \"anywhere\",\n        ...style,\n      }}\n    >\n      <span\n        style={{\n          position: \"absolute\",\n          width: 1,\n          height: 1,\n          padding: 0,\n          margin: -1,\n          overflow: \"hidden\",\n          clipPath: \"inset(50%)\",\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        {text}\n      </span>\n      <span aria-hidden=\"true\">\n        {words.map((word, wordIndex) =>\n          /^\\s*$/u.test(word) ? (\n            word\n          ) : (\n            <span\n              key={wordIndex}\n              style={{ display: \"inline-block\", maxWidth: \"100%\" }}\n            >\n              {[...segmenter.segment(word)].map(({ segment }, charIndex) => {\n                const local = Math.min(\n                  1,\n                  Math.max(0, amount * (count + 3) - index++)\n                )\n                // A short accent wave travels ahead of the settled ink.\n                const settled = Math.min(\n                  1,\n                  Math.max(0, (amount * (count + 3) - (index - 1) - 1) / 3)\n                )\n                const tint =\n                  local < 1\n                    ? `color-mix(in srgb, ${dimColor}, ${accentColor} ${local * 100}%)`\n                    : `color-mix(in srgb, ${accentColor}, ${textColor} ${settled * 100}%)`\n                return (\n                  <span\n                    key={charIndex}\n                    style={{ color: amount === 1 ? textColor : tint }}\n                  >\n                    {segment}\n                  </span>\n                )\n              })}\n            </span>\n          )\n        )}\n      </span>\n    </span>\n  )\n}\n",
      "type": "registry:ui",
      "target": "src/jbm/ui/text-fill.tsx"
    }
  ],
  "type": "registry:ui"
}