{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progressive-flux-loader",
  "type": "registry:ui",
  "title": "Progressive Flux Loader",
  "description": "A glowing progress bar with phase labels that fly in along the Z-axis and reveal letter by letter.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/ruixenui/progressive-flux-loader.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  AnimatePresence,\n  motion,\n  useReducedMotion,\n  type Transition,\n} from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/* ── types ───────────────────────────────────────────────────── */\n\nexport interface ProgressiveFluxPhase {\n  /** Progress threshold (`0`–`100`) at or past which `label` is shown. */\n  at: number;\n  /** Text revealed once this threshold is reached. */\n  label: string;\n}\n\nexport interface ProgressiveFluxLoaderProps {\n  /**\n   * Controlled progress, `0`–`100`. When set, the loader follows this value and\n   * the phase label switches at the configured thresholds. Omit it to let the\n   * loader run its own looping sweep.\n   */\n  value?: number;\n  /** Phase thresholds and their labels. Each `at` is a `0`–`100` mark. */\n  phases?: ProgressiveFluxPhase[];\n  /** Seconds for one full sweep when uncontrolled. Default `12`. */\n  duration?: number;\n  /** Restart from `0` after reaching `100` (uncontrolled only). Default `true`. */\n  loop?: boolean;\n  /** Show the animated phase label above the bar. Default `true`. */\n  showLabel?: boolean;\n  /**\n   * CSS background for the bar fill. Defaults to the signature vivid blue → cyan\n   * flux gradient. Pass any CSS background to replace it, or recolor the default\n   * via the `--flux-from` / `--flux-to` CSS variables (e.g. set them to\n   * `hsl(var(--primary))` to follow the theme).\n   */\n  gradient?: string;\n  /** Fires once when progress reaches `100` — in both controlled and uncontrolled modes. */\n  onComplete?: () => void;\n  /** Classes for the root wrapper. */\n  className?: string;\n  /** Classes for the bar track. */\n  barClassName?: string;\n  /** Classes for the phase label. */\n  textClassName?: string;\n}\n\n/* ── constants ───────────────────────────────────────────────── */\n\nconst DEFAULT_PHASES: ProgressiveFluxPhase[] = [\n  { at: 0, label: \"starting up\" },\n  { at: 25, label: \"loading assets\" },\n  { at: 55, label: \"preparing magic\" },\n  { at: 80, label: \"almost there\" },\n  { at: 100, label: \"all done\" },\n];\n\n// Signature \"flux\" palette — a vivid blue → cyan → blue fill. The two end\n// colors are read from CSS variables with built-in defaults, so the bar can be\n// recolored per instance without touching the component: set `--flux-from` /\n// `--flux-to` (e.g. to `hsl(var(--primary))` to follow the theme), or override\n// the whole fill with the `gradient` prop. The surrounding track and label stay\n// on shadcn theme tokens, so the loader still adapts to light and dark. These\n// are component-level custom properties, so the v3 build leaves them untouched\n// and the fill renders identically on Tailwind v3 and v4.\nconst FLUX_FROM = \"var(--flux-from, #1d6ffb)\";\nconst FLUX_TO = \"var(--flux-to, #74e1ff)\";\nconst FLUX_MID = `color-mix(in oklab, ${FLUX_FROM}, ${FLUX_TO})`;\n\nconst DEFAULT_GRADIENT = `linear-gradient(90deg, ${FLUX_FROM} 0%, ${FLUX_MID} 35%, ${FLUX_TO} 55%, ${FLUX_MID} 78%, ${FLUX_FROM} 100%)`;\n\n// Colored glow drawn from the same flux palette, a white top-edge highlight,\n// and a deep-blue inset for depth.\nconst BAR_SHADOW = `0 0 18px color-mix(in oklab, ${FLUX_FROM} 55%, transparent), 0 0 32px color-mix(in oklab, ${FLUX_TO} 40%, transparent), inset 0 1.5px 0 rgba(255, 255, 255, 0.5), inset 0 -2px 3px rgba(0, 40, 120, 0.35)`;\n\n// White sweep over the colored fill (blended with `screen`), so the highlight\n// reads as a bright glide regardless of theme.\nconst SHEEN_GRADIENT =\n  \"linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.55) 50%, transparent 100%)\";\n\nconst Z_TRANSITION: Transition = { duration: 0.9, ease: [0.22, 1, 0.36, 1] };\nconst LETTER_TRANSITION: Transition = {\n  duration: 0.45,\n  ease: [0.22, 1, 0.36, 1],\n};\n\n/* ── helpers ─────────────────────────────────────────────────── */\n\n/** Latest label whose threshold has been crossed. Expects pre-sorted phases. */\nfunction pickLabel(value: number, sortedPhases: ProgressiveFluxPhase[]) {\n  let active = sortedPhases[0]?.label ?? \"\";\n  for (const phase of sortedPhases) {\n    if (value >= phase.at) active = phase.label;\n  }\n  return active;\n}\n\n/* ── label ───────────────────────────────────────────────────── */\n\ninterface FluxLabelProps {\n  label: string;\n  /** Render plain, static text instead of the 3D fly-in (reduced motion). */\n  reduced: boolean;\n  className?: string;\n}\n\n// The label is decorative and `aria-hidden`; the progressbar carries the spoken\n// progress via `aria-valuetext`. Under reduced motion it is plain static text.\nfunction FluxLabel({ label, reduced, className }: FluxLabelProps) {\n  const base = cn(\n    \"absolute inset-0 flex items-center justify-center text-center text-3xl font-semibold tracking-tight text-muted-foreground sm:text-4xl\",\n    className,\n  );\n\n  if (reduced) {\n    return (\n      <div aria-hidden className={base}>\n        {label}\n      </div>\n    );\n  }\n\n  return (\n    <AnimatePresence mode=\"wait\">\n      <motion.div\n        key={label}\n        aria-hidden\n        className={base}\n        style={{ transformStyle: \"preserve-3d\" }}\n        initial={{ opacity: 0, z: -380, scale: 0.65, filter: \"blur(14px)\" }}\n        animate={{\n          opacity: [0, 1, 1, 1],\n          z: [-380, 60, -8, 0],\n          scale: [0.65, 1.08, 0.985, 1],\n          filter: [\"blur(14px)\", \"blur(0px)\", \"blur(0px)\", \"blur(0px)\"],\n        }}\n        exit={{\n          opacity: 0,\n          z: 220,\n          scale: 1.35,\n          filter: \"blur(10px)\",\n          transition: { duration: 0.45, ease: [0.7, 0, 0.84, 0] },\n        }}\n        transition={Z_TRANSITION}\n      >\n        <span className=\"inline-flex\">\n          {label.split(\"\").map((char, index) => (\n            <motion.span\n              key={`${label}-${index}`}\n              className=\"inline-block\"\n              initial={{ opacity: 0, y: 12, filter: \"blur(8px)\" }}\n              animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n              transition={{ ...LETTER_TRANSITION, delay: 0.18 + index * 0.035 }}\n            >\n              {char === \" \" ? \" \" : char}\n            </motion.span>\n          ))}\n        </span>\n      </motion.div>\n    </AnimatePresence>\n  );\n}\n\n/* ── component ───────────────────────────────────────────────── */\n\nexport function ProgressiveFluxLoader({\n  value,\n  phases = DEFAULT_PHASES,\n  duration = 12,\n  loop = true,\n  showLabel = true,\n  gradient = DEFAULT_GRADIENT,\n  onComplete,\n  className,\n  barClassName,\n  textClassName,\n}: ProgressiveFluxLoaderProps) {\n  const reduced = !!useReducedMotion();\n  const isControlled = typeof value === \"number\";\n  const [internal, setInternal] = React.useState(0);\n\n  // Keep the latest `onComplete` in a ref so a fresh inline callback on every\n  // parent render never tears down and restarts the sweep below.\n  const onCompleteRef = React.useRef(onComplete);\n  React.useEffect(() => {\n    onCompleteRef.current = onComplete;\n  });\n\n  const completedRef = React.useRef(false);\n\n  // Uncontrolled self-run sweep.\n  React.useEffect(() => {\n    if (isControlled) return;\n    let raf = 0;\n    let timer = 0;\n    let start: number | null = null;\n    const totalMs = Math.max(500, duration * 1000);\n\n    const tick = (ts: number) => {\n      if (start === null) start = ts;\n      const pct = Math.min(100, ((ts - start) / totalMs) * 100);\n      setInternal(pct);\n      if (pct >= 100) {\n        if (!completedRef.current) {\n          completedRef.current = true;\n          onCompleteRef.current?.();\n        }\n        if (loop) {\n          start = null;\n          completedRef.current = false;\n          timer = window.setTimeout(() => {\n            setInternal(0);\n            raf = requestAnimationFrame(tick);\n          }, 700);\n        }\n        return;\n      }\n      raf = requestAnimationFrame(tick);\n    };\n\n    raf = requestAnimationFrame(tick);\n    return () => {\n      cancelAnimationFrame(raf);\n      clearTimeout(timer);\n    };\n  }, [isControlled, duration, loop]);\n\n  const raw = isControlled ? value! : internal;\n  const current = Number.isFinite(raw) ? Math.min(100, Math.max(0, raw)) : 0;\n\n  // Controlled completion: fire once when `value` crosses 100, re-arm below it.\n  React.useEffect(() => {\n    if (!isControlled) return;\n    if (current >= 100 && !completedRef.current) {\n      completedRef.current = true;\n      onCompleteRef.current?.();\n    } else if (current < 100) {\n      completedRef.current = false;\n    }\n  }, [isControlled, current]);\n\n  const sortedPhases = React.useMemo(\n    () => [...phases].sort((a, b) => a.at - b.at),\n    [phases],\n  );\n  const label = React.useMemo(\n    () => pickLabel(current, sortedPhases),\n    [current, sortedPhases],\n  );\n  const rounded = Math.round(current);\n\n  return (\n    <div\n      className={cn(\n        \"mx-auto flex w-full max-w-md flex-col items-center gap-8\",\n        className,\n      )}\n    >\n      {showLabel && (\n        <div\n          className=\"relative h-16 w-full select-none\"\n          style={reduced ? undefined : { perspective: \"1000px\" }}\n        >\n          <FluxLabel\n            label={label}\n            reduced={reduced}\n            className={textClassName}\n          />\n        </div>\n      )}\n\n      <div\n        className={cn(\n          \"relative h-5 w-full overflow-hidden rounded-full bg-muted shadow-[inset_0_2px_3px_rgba(0,0,0,0.09),inset_0_-1px_2px_rgba(255,255,255,0.7)] dark:shadow-[inset_0_2px_3px_rgba(0,0,0,0.45),inset_0_-1px_2px_rgba(255,255,255,0.05)]\",\n          barClassName,\n        )}\n        role=\"progressbar\"\n        aria-valuemin={0}\n        aria-valuemax={100}\n        aria-valuenow={rounded}\n        aria-valuetext={label ? `${rounded}% – ${label}` : `${rounded}%`}\n        aria-label=\"Loading\"\n      >\n        <motion.div\n          className=\"relative h-full rounded-full\"\n          style={{ background: gradient, boxShadow: BAR_SHADOW }}\n          initial={false}\n          animate={{ width: `${current}%` }}\n          transition={\n            reduced\n              ? { duration: 0 }\n              : { duration: 0.55, ease: [0.22, 1, 0.36, 1] }\n          }\n        >\n          {!reduced && (\n            <motion.span\n              aria-hidden\n              className=\"pointer-events-none absolute inset-y-0 left-0 w-1/2 rounded-full\"\n              style={{ background: SHEEN_GRADIENT, mixBlendMode: \"screen\" }}\n              animate={{ x: [\"-110%\", \"210%\"] }}\n              transition={{ duration: 1.6, ease: \"linear\", repeat: Infinity }}\n            />\n          )}\n        </motion.div>\n      </div>\n    </div>\n  );\n}\n\nexport default ProgressiveFluxLoader;\n",
      "type": "registry:ui",
      "target": "components/ruixen/progressive-flux-loader.tsx"
    }
  ]
}