{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shimmer-text",
  "title": "Shimmer Text",
  "description": "A soft band of light sweeps across a label while something is working. Every shimmer on the page shares one phase, status changes swap in without restarting the band, and turning it off fades the band out in place.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://eased.dev/r/eased-tokens.json",
    "https://eased.dev/r/eased-motion.json",
    "https://eased.dev/r/eased-text-swap.json"
  ],
  "files": [
    {
      "path": "src/registry/eased/shimmer-text/shimmer-text.tsx",
      "content": "\"use client\";\n\nimport { useInView } from \"motion/react\";\nimport { useEffect, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport { TextSwap } from \"../_primitives/text-swap/text-swap\";\n\nexport type ShimmerTextProps = {\n  /** The text. Loading labels end with \"…\" (R47). */\n  children: ReactNode;\n  /**\n   * Sweep while true. Turning it off fades the band out where it is (300ms) instead of cutting\n   * it mid-sweep, leaving plain text in the base colour. Default true.\n   */\n  active?: boolean;\n  /** Seconds per cycle: the sweep takes the first 70%, then the band rests off the right edge. Default 2.4. */\n  duration?: number;\n  /**\n   * Route text changes through Text Swap, so \"Searching…\" → \"Reading 4 pages…\" lifts and blurs\n   * instead of popping, and the box springs to the new width. Default: on when `children` is a\n   * string or number. Swapped text does not wrap; set `swap={false}` for multi-line copy.\n   */\n  swap?: boolean;\n  /**\n   * Colour classes. The text colour is the base the band sweeps over (default\n   * `text-text-muted`); set `[--shimmer-highlight:…]` to change the band (default the\n   * `text` token, so it lifts toward full contrast in both themes).\n   */\n  className?: string;\n  style?: CSSProperties;\n};\n\n/*\n * The base colour is `currentColor` (text-fill is transparent, `color` is untouched), so the\n * text takes any text-* class. The band is a fixed 5em wide whatever the label's length, so a\n * short label and a long one shine with the same light. Its colour is a registered property,\n * so `active` turning off fades it out in place. Forced colours draw plain text.\n */\nconst CSS = `\n@property --eased-shimmer-hl { syntax: \"<color>\"; inherits: true; initial-value: transparent; }\n@keyframes eased-shimmer-sweep {\n  from { background-position: calc(0% - var(--eased-shimmer-band)) 0, 0 0; }\n  70%, to { background-position: calc(100% + var(--eased-shimmer-band)) 0, 0 0; }\n}\n.eased-shimmer {\n  --eased-shimmer-band: 5em;\n  -webkit-text-fill-color: transparent;\n  background-image: linear-gradient(90deg, transparent, var(--eased-shimmer-hl) 50%, transparent), linear-gradient(currentColor, currentColor);\n  background-size: var(--eased-shimmer-band) 100%, 100% 100%;\n  background-position: calc(0% - var(--eased-shimmer-band)) 0, 0 0;\n  background-repeat: no-repeat;\n  -webkit-background-clip: text;\n  background-clip: text;\n  -webkit-box-decoration-break: clone;\n  box-decoration-break: clone;\n  transition: --eased-shimmer-hl 300ms var(--eased-ease-standard), color 150ms var(--eased-ease-standard);\n}\n.eased-shimmer[data-on] { --eased-shimmer-hl: var(--shimmer-highlight, var(--eased-text)); }\n.eased-shimmer[data-sweep] { animation: eased-shimmer-sweep var(--eased-shimmer-dur, 2.4s) cubic-bezier(0.45, 0, 0.3, 1) infinite; }\n@media (prefers-reduced-motion: reduce) { .eased-shimmer[data-sweep] { animation: none; } }\n@media (forced-colors: active) { .eased-shimmer { -webkit-text-fill-color: currentColor; background: none; } }\n`;\n\n/**\n * Text with one soft band of light sweeping across it, the \"working on it\" state for labels\n * and statuses. Every shimmer on the page shares one phase (pinned to the document timeline),\n * so a status that swaps mid-sweep carries the band on rather than restarting it, and a list\n * of them shines together. Pauses off-screen; under reduced motion it is plain text.\n */\nexport function ShimmerText({ children, active = true, duration = 2.4, swap, className = \"\", style }: ShimmerTextProps) {\n  const color = /(^|\\s)text-(text|brand|success|warning|danger|info|on-|current|inherit)/.test(className) ? \"\" : \"text-text-muted\";\n  const swapping = swap ?? (typeof children === \"string\" || typeof children === \"number\");\n  const band = (\n    <Band active={active} duration={duration} className={`${color} ${className}`} style={style}>\n      {children}\n    </Band>\n  );\n  if (!swapping) return band;\n  // Clip horizontally while the width springs, so a longer status is revealed left to right\n  // instead of spilling over whatever follows it.\n  return (\n    <TextSwap swapKey={typeof children === \"string\" || typeof children === \"number\" ? children : undefined} className=\"overflow-x-clip\">\n      {band}\n    </TextSwap>\n  );\n}\n\nfunction Band({\n  active,\n  duration,\n  className,\n  style,\n  children,\n}: {\n  active: boolean;\n  duration: number;\n  className: string;\n  style?: CSSProperties;\n  children: ReactNode;\n}) {\n  const ref = useRef<HTMLSpanElement>(null);\n  const inView = useInView(ref);\n  // Keep sweeping through the 300ms fade-out after `active` turns off, then stop painting.\n  const [sweep, setSweep] = useState(active);\n  if (active && !sweep) setSweep(true);\n  useEffect(() => {\n    if (active) return;\n    const t = window.setTimeout(() => setSweep(false), 320);\n    return () => clearTimeout(t);\n  }, [active]);\n\n  const running = sweep && inView;\n\n  // One phase for every shimmer: start each cycle on the document timeline's zero.\n  useLayoutEffect(() => {\n    if (!running) return;\n    for (const a of ref.current?.getAnimations() ?? []) a.startTime = 0;\n  }, [running, duration]);\n\n  return (\n    <span\n      ref={ref}\n      data-on={active ? \"\" : undefined}\n      data-sweep={running ? \"\" : undefined}\n      className={`eased-shimmer ${className}`}\n      style={{ ...style, \"--eased-shimmer-dur\": `${duration}s` } as CSSProperties}\n    >\n      {children}\n      <style href=\"eased-shimmer-text\" precedence=\"default\">\n        {CSS}\n      </style>\n    </span>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@components/eased/shimmer-text/shimmer-text.tsx"
    }
  ],
  "docs": "Import ShimmerText from components/eased/shimmer-text/shimmer-text. The Eased tokens were merged into your global CSS; nothing to import.",
  "type": "registry:ui"
}