{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gradient-text",
  "title": "Gradient Text",
  "description": "A headline painted with a gradient from theme tokens, legible in every palette, with solid fallbacks for high contrast and forced colours.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/gradient-text.tsx",
      "content": "import type * as React from \"react\"\nimport { useRef, useState, useSyncExternalStore } from \"react\"\nimport { useInView, useReducedMotion } from \"motion/react\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype GradientTextVariant = \"fade\" | \"accent\" | \"aurora\" | \"metal\"\ntype GradientTextEffect = \"flow\"\ntype GradientTextTag = \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"p\" | \"span\" | \"div\"\n\n/** Lives with the component so an install needs no global CSS. React 19 hoists a style with `href` into the head once. */\nconst KEYFRAMES = \"@keyframes mise-gradient-flow{to{background-position-x:-200%}}\"\n\n/**\n * Each gradient is only applied where relative oklch works, so an older browser keeps the solid colour instead of\n * transparent text over nothing. The stops are clamped into a lightness band per theme: an amber accent gets dark enough\n * to read on white, a near-black accent gets light enough to read on black and to differ from the foreground beside it.\n * Aurora starts on the accent and walks away from hue 100: yellow darkened for a light page turns to mud, and the sine picks the direction and slows the walk near blue-violet.\n */\nconst STOP_CLASS: Record<GradientTextVariant, string> = {\n  fade: \"\",\n  accent:\n    \"[--mise-gradient-accent:oklch(from_var(--mise)_clamp(0.46,l,0.58)_c_h)] dark:[--mise-gradient-accent:oklch(from_var(--mise)_clamp(0.66,l,0.74)_c_h)]\",\n  aurora:\n    \"[--mise-gradient-1:oklch(from_var(--mise)_clamp(0.38,l,0.54)_c_h)] [--mise-gradient-2:oklch(from_var(--mise)_clamp(0.52,l,0.6)_c_calc(h_+_40_*_sin((h_-_100)_*_1deg)))] [--mise-gradient-3:oklch(from_var(--mise)_clamp(0.38,l,0.54)_c_calc(h_+_80_*_sin((h_-_100)_*_1deg)))] dark:[--mise-gradient-1:oklch(from_var(--mise)_clamp(0.72,l,0.82)_c_h)] dark:[--mise-gradient-2:oklch(from_var(--mise)_clamp(0.62,l,0.7)_c_calc(h_+_40_*_sin((h_-_100)_*_1deg)))] dark:[--mise-gradient-3:oklch(from_var(--mise)_clamp(0.72,l,0.82)_c_calc(h_+_80_*_sin((h_-_100)_*_1deg)))]\",\n  metal:\n    \"[--mise-gradient-base:var(--foreground)] [--mise-gradient-band:var(--muted-foreground)] dark:[--mise-gradient-base:var(--muted-foreground)] dark:[--mise-gradient-band:var(--foreground)]\",\n}\n\n/** The colour if the gradient cannot be drawn. */\nconst SOLID_CLASS: Record<GradientTextVariant, string> = {\n  fade: \"text-foreground\",\n  accent: \"text-foreground\",\n  aurora: \"text-foreground\",\n  metal: \"text-foreground\",\n}\n\nconst STILL_CLASS: Record<GradientTextVariant, string> = {\n  fade: \"supports-[color:oklch(from_red_l_c_h)]:text-transparent supports-[color:oklch(from_red_l_c_h)]:[background-image:linear-gradient(180deg,var(--foreground)_20%,var(--muted-foreground)_100%)]\",\n  accent:\n    \"supports-[color:oklch(from_red_l_c_h)]:text-transparent supports-[color:oklch(from_red_l_c_h)]:[background-image:linear-gradient(105deg_in_oklab,var(--foreground)_15%,var(--mise-gradient-accent)_85%)]\",\n  aurora:\n    \"supports-[color:oklch(from_red_l_c_h)]:text-transparent supports-[color:oklch(from_red_l_c_h)]:[background-image:linear-gradient(100deg_in_oklab,var(--mise-gradient-1),var(--mise-gradient-2)_50%,var(--mise-gradient-3))]\",\n  metal:\n    \"supports-[color:oklch(from_red_l_c_h)]:text-transparent supports-[color:oklch(from_red_l_c_h)]:[background-image:linear-gradient(100deg,var(--mise-gradient-base)_25%,var(--mise-gradient-band)_50%,var(--mise-gradient-base)_75%)]\",\n}\n\n/** Both ends match and the image is twice as wide as the text, so moving it by exactly its own width never shows a seam. */\nconst FLOW_CLASS: Record<GradientTextVariant, string> = {\n  fade: \"supports-[color:oklch(from_red_l_c_h)]:text-transparent supports-[color:oklch(from_red_l_c_h)]:[background-image:linear-gradient(90deg,var(--foreground),var(--muted-foreground),var(--foreground))]\",\n  accent:\n    \"supports-[color:oklch(from_red_l_c_h)]:text-transparent supports-[color:oklch(from_red_l_c_h)]:[background-image:linear-gradient(90deg_in_oklab,var(--foreground),var(--mise-gradient-accent),var(--foreground))]\",\n  aurora:\n    \"supports-[color:oklch(from_red_l_c_h)]:text-transparent supports-[color:oklch(from_red_l_c_h)]:[background-image:linear-gradient(90deg_in_oklab,var(--mise-gradient-1),var(--mise-gradient-2),var(--mise-gradient-3),var(--mise-gradient-1))]\",\n  metal:\n    \"supports-[color:oklch(from_red_l_c_h)]:text-transparent supports-[color:oklch(from_red_l_c_h)]:[background-image:linear-gradient(90deg,var(--mise-gradient-base)_30%,var(--mise-gradient-band)_50%,var(--mise-gradient-base)_70%)]\",\n}\n\nconst FLOW_DRIFT = \"[background-size:200%_100%] animate-[mise-gradient-flow_14s_linear_infinite] motion-reduce:animate-none\"\n\n/**\n * - Padding and an equal negative margin give descenders room that background-clip would otherwise cut.\n * - box-decoration-clone repaints the gradient per line; without it a wrapped second line starts halfway through.\n * - Selected text gets a real colour, because the selection paints over the gradient the letters are cut from.\n * - Important, because high contrast and forced colours have to win against the supports rule whatever the order.\n */\nconst PAINT_CLASS = [\n  \"bg-clip-text box-decoration-clone pb-[0.14em] -mb-[0.14em]\",\n  \"selection:bg-mise-soft selection:text-foreground\",\n  \"contrast-more:bg-none! contrast-more:text-foreground!\",\n  \"forced-colors:bg-none! forced-colors:text-[CanvasText]!\",\n].join(\" \")\n\nconst subscribeToVisibility = (onChange: () => void) => {\n  document.addEventListener(\"visibilitychange\", onChange)\n  return () => document.removeEventListener(\"visibilitychange\", onChange)\n}\n\nfunction useTabVisible() {\n  return useSyncExternalStore(\n    subscribeToVisibility,\n    () => !document.hidden,\n    () => true,\n  )\n}\n\ntype GradientTextProps = React.HTMLAttributes<HTMLElement> & {\n  as?: GradientTextTag\n  /** `fade` foreground into muted, `accent` foreground into the accent, `aurora` three neighbouring hues, `metal` a light band across a neutral. */\n  variant?: GradientTextVariant\n  /** `flow` drifts the colours slowly. It stops off screen, in a hidden tab, and on hover and focus. */\n  effect?: GradientTextEffect\n  /** Holds the flow. Tie it to a pause button on touch screens, where hover does not exist: motion past five seconds needs a way to stop it. */\n  paused?: boolean\n}\n\nfunction GradientText({ as: Tag = \"span\", variant = \"fade\", effect, paused = false, className, children, onPointerEnter, onPointerLeave, onFocus, onBlur, ...props }: GradientTextProps) {\n  const reduceMotion = useReducedMotion() ?? false\n  const ref = useRef<HTMLSpanElement>(null)\n  const inView = useInView(ref)\n  const tabVisible = useTabVisible()\n  const [held, setHeld] = useState(false)\n  const flowing = effect === \"flow\" && !reduceMotion\n  const playing = flowing && !paused && inView && tabVisible && !held\n  const handlers = {\n    onPointerEnter: (event: React.PointerEvent<HTMLElement>) => {\n      setHeld(true)\n      onPointerEnter?.(event)\n    },\n    onPointerLeave: (event: React.PointerEvent<HTMLElement>) => {\n      setHeld(false)\n      onPointerLeave?.(event)\n    },\n    onFocus: (event: React.FocusEvent<HTMLElement>) => {\n      setHeld(true)\n      onFocus?.(event)\n    },\n    onBlur: (event: React.FocusEvent<HTMLElement>) => {\n      setHeld(false)\n      onBlur?.(event)\n    },\n  }\n  return (\n    <Tag {...props} {...handlers} data-slot=\"gradient-text\" data-variant={variant} data-effect={effect} data-playing={flowing ? (playing ? \"true\" : \"false\") : undefined} className={className}>\n      {flowing ? <style href=\"mise-gradient-text\" precedence=\"default\">{KEYFRAMES}</style> : null}\n      <span\n        ref={ref}\n        data-slot=\"gradient-text-paint\"\n        className={cn(PAINT_CLASS, STOP_CLASS[variant], SOLID_CLASS[variant], flowing ? FLOW_CLASS[variant] : STILL_CLASS[variant], flowing && FLOW_DRIFT)}\n        style={flowing ? { animationPlayState: playing ? \"running\" : \"paused\" } : undefined}\n      >\n        {children}\n      </span>\n    </Tag>\n  )\n}\n\nexport { GradientText, type GradientTextProps, type GradientTextVariant, type GradientTextEffect }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "text"
  ],
  "type": "registry:ui"
}