{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroll-background",
  "title": "Scroll Background",
  "description": "A background that blends from one colour to the next as you scroll, with CSS scroll-driven animations and no JavaScript.",
  "registryDependencies": [
    "@corsair-ui/utils"
  ],
  "files": [
    {
      "path": "registry/default/ui/scroll-background.tsx",
      "content": "import type { ComponentProps, CSSProperties } from \"react\";\n\nimport { cn } from \"@/registry/default/lib/utils\";\n\ninterface ScrollBackgroundProps extends ComponentProps<\"div\"> {\n  /**\n   * The colours it moves through, in order, as any CSS colours. The first\n   * is the background before any scrolling, and where scroll timelines are\n   * not supported. Every one of them has to contrast with the text on it.\n   */\n  colors?: string[];\n  /**\n   * What drives it: \"view\" is the element crossing the viewport, \"root\" the\n   * page's scroll, \"nearest\" the closest scrolling ancestor.\n   */\n  timeline?: \"view\" | \"root\" | \"nearest\";\n  /**\n   * With `timeline=\"view\"`, which part of the crossing the colours spread\n   * over: \"cover\" from the first pixel entering to the last leaving,\n   * \"contain\" while the element fills the viewport (or fits inside it).\n   */\n  range?: \"cover\" | \"contain\";\n  /**\n   * Where each colour is reached, in percent of the timeline, one per\n   * colour and in increasing order: `[0, 5, 14, 65, 100]`. The blend into a\n   * colour runs from the stop before it to its own. Without it the colours\n   * are spread evenly.\n   */\n  stops?: number[];\n}\n\n/** Evenly spread stops from 0 to 100, or the given ones clamped and kept in order. */\nfunction resolveStops(count: number, stops?: number[]) {\n  if (!stops || stops.length !== count) {\n    return Array.from({ length: count }, (_, index) =>\n      count > 1 ? (index / (count - 1)) * 100 : 0\n    );\n  }\n  let floor = 0;\n  return stops.map((stop) => {\n    floor = Math.min(Math.max(stop, floor), 100);\n    return floor;\n  });\n}\n\nconst percent = (value: number) => `${Math.round(value * 100) / 100}%`;\n\nconst DEFAULT_COLORS = [\n  \"var(--background)\",\n  \"var(--muted)\",\n  \"color-mix(in srgb, var(--primary) 12%, var(--background))\",\n];\n\n/**\n * A background that shifts from one colour to the next as you scroll. It is\n * CSS scroll-driven animation: each colour is a layer that fades in over its\n * own stretch of the scroll, so neighbouring colours blend evenly, with no\n * JavaScript and no scroll listener. Works in server components. Browsers\n * without scroll timelines keep the first colour. The layers are decorative\n * and hidden from screen readers. Like `scroll-progress`, it keeps following\n * the scroll with `prefers-reduced-motion`, since nothing moves.\n *\n * It follows the nearest scroll container, and `overflow: hidden` makes one\n * that never scrolls. Clip with `overflow: clip` (`overflow-clip`) instead.\n *\n * For a background behind the whole page, give it no children and fix it in\n * place: `<ScrollBackground timeline=\"root\" className=\"fixed inset-0 -z-10\" />`.\n * A colour can come back later in the list: each layer covers the ones before.\n *\n * @example\n * <ScrollBackground colors={[\"#0b1d2a\", \"#12344d\", \"#1f5f7a\"]} className=\"text-white\">\n *   <section className=\"min-h-svh\">…</section>\n * </ScrollBackground>\n */\nfunction ScrollBackground({\n  colors = DEFAULT_COLORS,\n  timeline = \"view\",\n  range = \"cover\",\n  stops,\n  className,\n  style,\n  children,\n  ...props\n}: ScrollBackgroundProps) {\n  const [base, ...rest] = colors;\n  const reached = resolveStops(colors.length, stops);\n  const animationTimeline =\n    timeline === \"view\" ? \"view()\" : timeline === \"nearest\" ? \"scroll(nearest)\" : \"scroll(root)\";\n  // Layer i (colour i + 1) fades in between the stop before its colour and its own.\n  const stretch = (index: number) => {\n    const start = percent(reached[index]!);\n    const end = percent(reached[index + 1]!);\n    return timeline === \"view\" ? `${range} ${start} ${range} ${end}` : `${start} ${end}`;\n  };\n\n  return (\n    <div\n      data-slot=\"scroll-background\"\n      data-timeline={timeline}\n      className={cn(\"relative isolate\", className)}\n      style={{ backgroundColor: base, ...style }}\n      {...props}\n    >\n      <div\n        aria-hidden=\"true\"\n        data-slot=\"scroll-background-layers\"\n        className=\"pointer-events-none absolute inset-0 -z-10\"\n      >\n        {rest.map((color, index) => (\n          <div\n            key={index}\n            data-slot=\"scroll-background-layer\"\n            className=\"supports-[animation-timeline:scroll()]:animate-scroll-background absolute inset-0 opacity-0\"\n            style={\n              {\n                backgroundColor: color,\n                // After the utility's shorthand, which would reset them.\n                animationTimeline,\n                animationRange: stretch(index),\n              } as CSSProperties\n            }\n          />\n        ))}\n      </div>\n      {children}\n    </div>\n  );\n}\n\nexport { ScrollBackground, type ScrollBackgroundProps };\n",
      "type": "registry:ui"
    }
  ],
  "tailwind": {
    "config": {
      "theme": {
        "extend": {
          "keyframes": {
            "scroll-background": {
              "from": {
                "opacity": "0"
              },
              "to": {
                "opacity": "1"
              }
            }
          },
          "animation": {
            "scroll-background": "scroll-background linear both"
          }
        }
      }
    }
  },
  "cssVars": {
    "theme": {
      "animate-scroll-background": "scroll-background linear both"
    }
  },
  "css": {
    "@keyframes scroll-background": {
      "from": {
        "opacity": "0"
      },
      "to": {
        "opacity": "1"
      }
    }
  },
  "categories": [
    "motion"
  ],
  "type": "registry:ui"
}