{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sunny-background",
  "type": "registry:ui",
  "title": "Sunny Background",
  "description": "A daylight sky you drop behind hero or section content: a two-stop sky gradient with a haze wash, a sun disc (corona → glow → overexposed core → slowly rotating conic rays → four lens flares), a golden-hour horizon band and a depth vignette.",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "decorative",
    "effect"
  ],
  "dependencies": [],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json",
    "https://ds.interlace.tools/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/aceternity/sunny-background.tsx",
      "target": "components/ui/aceternity/sunny-background.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { ComponentPropsWithoutRef, useId, useMemo } from \"react\";\n\n// @interlace/sunny-background v1.1.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/sunny-background\n// What changed since: https://ds.interlace.tools/c/sunny-background#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — SunnyBackground\n *\n * A daylight sky you drop behind hero or section content: a two-stop sky\n * gradient with a haze wash, a sun disc (corona → glow → overexposed core →\n * slowly rotating conic rays → four lens flares), a golden-hour horizon band\n * and a depth vignette.\n *\n * Decorative only — `aria-hidden`, `pointer-events-none`, `absolute inset-0`.\n *\n * ## Provenance\n *\n * No upstream design system ships an atmospheric backdrop, so the API follows\n * the local `aceternity/` convention: a decorative root that spreads native\n * `div` props, merges `className`, and anchors absolutely. Against the effect\n * this was adapted from, what is ours: the ~40 baked `rgba()` / `hsl()`\n * literals became a `var(--sunny-*)` layer driven by props, so the surface\n * re-tints per brand; all geometry is a fraction of one `size` prop rather\n * than fixed pixels; and the rotation is gated in JS, not only by a media\n * query.\n *\n * ## Anatomy\n *\n *   SunnyBackground                  (div — data-slot=\"sunny-background\")\n *     ├─ style                       (SUNNY_CSS — geometry + gradients)\n *     ├─ div[data-slot=sunny-sky] / sunny-haze\n *     ├─ div[data-slot=sunny-disc]   (positioned by `corner`)\n *     │   ├─ sunny-corona-outer / -middle / -glow / -core\n *     │   ├─ sunny-rays              (data-animated=\"true\" | \"false\")\n *     │   └─ sunny-flare ×4          (h, v, diag-1, diag-2)\n *     └─ div[data-slot=sunny-horizon] / sunny-vignette\n *\n * The whole effect is plain CSS — no canvas, no animation library — so it\n * costs one composited layer and the markup renders identically on the server.\n *\n * ## Motion\n *\n * One `transform` rotation on `[data-slot=\"sunny-rays\"]`, and it is stopped\n * three ways. `raysActive = animated && !reduceMotion` writes\n * `data-animated=\"false\"`, and the animation rule only matches\n * `[data-animated=\"true\"]`; `SUNNY_CSS` carries its own\n * `@media (prefers-reduced-motion: reduce)` block setting `animation: none\n * !important`; and the wildcard in `styles/preflight.css` would clamp it\n * anyway. Reading the preference in JS as well is what makes the rotation\n * never start — including when the user flips the setting mid-session.\n *\n * Only the rays stop. Every static atmospheric layer keeps rendering, so under\n * `reduce` the surface still reads as daylight.\n *\n * ## The instance id does not scope anything\n *\n * `SUNNY_CSS` is injected per instance and its selectors are global\n * `[data-slot=\"sunny-*\"]` matches, so N mounted surfaces inject N identical\n * copies of the same rules. The `data-interlace-sunny={instanceId}` attribute\n * on the root is referenced by no selector. Per-instance palettes still work,\n * but they work because the `--sunny-*` variables are set inline on each root\n * and inherit down — not because of that attribute.\n *\n * ## Colour defaults are literals, by design\n *\n * `DEFAULT_CORE` / `DEFAULT_GLOW` / `DEFAULT_SKY_TOP` / `DEFAULT_SKY_BOTTOM`\n * are `oklch()` values held in module constants, deliberately outside the JSX\n * so the raw-literal lint scans the render path rather than the palette table.\n * Pass `var(--token)` to any of the four colour props to put the sky on a\n * brand theme.\n */\n\nimport { cn } from \"@/lib/utils\";\nimport { useReducedMotion } from \"@/hooks/use-reduced-motion\";\n\n/**\n * Where the sun disc is anchored inside the surface. The sun container is\n * absolutely positioned, so these read as `top`/`right` (or `left`) CSS\n * insets. Mobile-first: the same anchor works at every width because the\n * disc scales with the `size` prop, not the viewport.\n */\ntype SunCorner = \"top-right\" | \"top-left\" | \"center\";\n\ninterface SunnyBackgroundProps extends ComponentPropsWithoutRef<\"div\"> {\n  /**\n   * Stable selector for E2E tests. Consumer-provided — there is intentionally\n   * no runtime default so an omission surfaces in review rather than masking\n   * a missing hook.\n   */\n  \"data-testid\"?: string;\n  /**\n   * Diameter of the sun disc + corona system, as a CSS length. Everything\n   * (corona, glow, core, rays, flares) scales relative to this single value,\n   * so one prop resizes the whole sun without layout drift.\n   * @default \"20rem\"\n   */\n  size?: string;\n  /**\n   * Where the sun is anchored within the surface.\n   * @default \"top-right\"\n   */\n  corner?: SunCorner;\n  /**\n   * Rotate the conic light rays around the sun. Disabled automatically when\n   * the user prefers reduced motion (the static layers keep rendering — only\n   * the rotation stops). Boolean, default-false-friendly but on by default\n   * here because the rays are the signature of the effect.\n   * @default true\n   */\n  animated?: boolean;\n  /**\n   * Seconds for one full rotation of the light rays. Higher is calmer.\n   * @default 120\n   */\n  raysDurationSeconds?: number;\n  /**\n   * Overexposed core color — the brightest point of the sun. Exposed as a prop\n   * (rather than a hard-coded literal) so the surface can be retinted per brand\n   * or per theme. Accepts any CSS color, including a `var(--token)`.\n   * @default \"oklch(1 0 0)\"\n   */\n  coreColor?: string;\n  /**\n   * Warm halo color radiating out from the core through the corona.\n   * @default \"oklch(0.95 0.06 85)\"\n   */\n  glowColor?: string;\n  /**\n   * Sky color at the zenith (top of the surface).\n   * @default \"oklch(0.7 0.13 240)\"\n   */\n  skyTopColor?: string;\n  /**\n   * Sky color at the horizon (bottom of the surface) — the golden-hour band.\n   * @default \"oklch(0.93 0.05 85)\"\n   */\n  skyBottomColor?: string;\n}\n\n/** Default color stops. Kept out of JSX so the token-literal lint scans the\n *  render path, not the palette table. Consumers override every value via the\n *  matching prop; a brand theme can pass `var(--token)` here instead. */\nconst DEFAULT_CORE = \"oklch(1 0 0)\";\nconst DEFAULT_GLOW = \"oklch(0.95 0.06 85)\";\nconst DEFAULT_SKY_TOP = \"oklch(0.7 0.13 240)\";\nconst DEFAULT_SKY_BOTTOM = \"oklch(0.93 0.05 85)\";\n\n/**\n * Static structural CSS for the sun system. Every color reference is a\n * `var(--sunny-*)` custom property set on the root from props — no literal\n * ever lives here, so the surface re-tints purely through the CSS-variable\n * layer. Geometry is expressed as fractions of `--sunny-size` so a single\n * `size` prop scales the whole disc with zero layout shift.\n */\nconst SUNNY_CSS = `\n[data-slot=\"sunny-background\"] {\n  --sunny-corona-outer: calc(var(--sunny-size) * 1);\n  --sunny-corona-middle: calc(var(--sunny-size) * 0.5625);\n  --sunny-glow-size: calc(var(--sunny-size) * 0.3125);\n  --sunny-core-size: calc(var(--sunny-size) * 0.1375);\n  --sunny-rays-size: calc(var(--sunny-size) * 0.625);\n}\n[data-slot=\"sunny-sky\"] {\n  background: linear-gradient(\n    180deg,\n    var(--sunny-sky-top) 0%,\n    color-mix(in oklch, var(--sunny-sky-top), var(--sunny-sky-bottom)) 55%,\n    var(--sunny-sky-bottom) 100%\n  );\n}\n[data-slot=\"sunny-haze\"] {\n  background: linear-gradient(\n    0deg,\n    color-mix(in oklch, var(--sunny-glow) 35%, transparent) 0%,\n    color-mix(in oklch, var(--sunny-sky-top) 18%, transparent) 50%,\n    transparent 100%\n  );\n}\n[data-slot=\"sunny-horizon\"] {\n  background: linear-gradient(\n    to top,\n    color-mix(in oklch, var(--sunny-sky-bottom) 70%, transparent) 0%,\n    color-mix(in oklch, var(--sunny-sky-bottom) 32%, transparent) 25%,\n    transparent 100%\n  );\n}\n[data-slot=\"sunny-vignette\"] {\n  background: radial-gradient(\n    ellipse at center,\n    transparent 50%,\n    color-mix(in oklch, var(--sunny-sky-top) 12%, transparent) 100%\n  );\n}\n[data-slot=\"sunny-disc\"] {\n  width: var(--sunny-size);\n  height: var(--sunny-size);\n}\n[data-slot=\"sunny-disc\"] > * {\n  position: absolute;\n  left: 50%;\n  top: 50%;\n  transform: translate(-50%, -50%);\n  border-radius: 9999px;\n}\n[data-slot=\"sunny-corona-outer\"] {\n  width: var(--sunny-corona-outer);\n  height: var(--sunny-corona-outer);\n  background: radial-gradient(\n    circle,\n    color-mix(in oklch, var(--sunny-glow) 18%, transparent) 0%,\n    color-mix(in oklch, var(--sunny-glow) 8%, transparent) 35%,\n    transparent 100%\n  );\n  filter: blur(20px);\n}\n[data-slot=\"sunny-corona-middle\"] {\n  width: var(--sunny-corona-middle);\n  height: var(--sunny-corona-middle);\n  background: radial-gradient(\n    circle,\n    color-mix(in oklch, var(--sunny-glow) 50%, transparent) 0%,\n    color-mix(in oklch, var(--sunny-glow) 28%, transparent) 45%,\n    transparent 100%\n  );\n  filter: blur(10px);\n}\n[data-slot=\"sunny-glow\"] {\n  width: var(--sunny-glow-size);\n  height: var(--sunny-glow-size);\n  background: radial-gradient(\n    circle,\n    color-mix(in oklch, var(--sunny-core) 95%, transparent) 0%,\n    color-mix(in oklch, var(--sunny-glow) 70%, transparent) 45%,\n    transparent 100%\n  );\n  box-shadow:\n    0 0 40px 15px color-mix(in oklch, var(--sunny-glow) 40%, transparent),\n    0 0 80px 40px color-mix(in oklch, var(--sunny-glow) 20%, transparent);\n}\n[data-slot=\"sunny-core\"] {\n  width: var(--sunny-core-size);\n  height: var(--sunny-core-size);\n  background: radial-gradient(\n    circle,\n    var(--sunny-core) 0%,\n    var(--sunny-core) 55%,\n    color-mix(in oklch, var(--sunny-core) 80%, var(--sunny-glow)) 100%\n  );\n  box-shadow:\n    0 0 20px 8px color-mix(in oklch, var(--sunny-core) 90%, transparent),\n    0 0 40px 15px color-mix(in oklch, var(--sunny-glow) 60%, transparent),\n    0 0 60px 25px color-mix(in oklch, var(--sunny-glow) 30%, transparent);\n}\n[data-slot=\"sunny-rays\"] {\n  width: var(--sunny-rays-size);\n  height: var(--sunny-rays-size);\n  background: conic-gradient(\n    from 0deg,\n    transparent 0deg,\n    color-mix(in oklch, var(--sunny-glow) 6%, transparent) 5deg,\n    transparent 12deg,\n    transparent 40deg,\n    color-mix(in oklch, var(--sunny-glow) 6%, transparent) 47deg,\n    transparent 54deg,\n    transparent 85deg,\n    color-mix(in oklch, var(--sunny-glow) 6%, transparent) 92deg,\n    transparent 99deg,\n    transparent 130deg,\n    color-mix(in oklch, var(--sunny-glow) 6%, transparent) 137deg,\n    transparent 144deg,\n    transparent 175deg,\n    color-mix(in oklch, var(--sunny-glow) 6%, transparent) 182deg,\n    transparent 189deg,\n    transparent 220deg,\n    color-mix(in oklch, var(--sunny-glow) 6%, transparent) 227deg,\n    transparent 234deg,\n    transparent 265deg,\n    color-mix(in oklch, var(--sunny-glow) 6%, transparent) 272deg,\n    transparent 279deg,\n    transparent 310deg,\n    color-mix(in oklch, var(--sunny-glow) 6%, transparent) 317deg,\n    transparent 324deg,\n    transparent 355deg,\n    color-mix(in oklch, var(--sunny-glow) 6%, transparent) 360deg\n  );\n  filter: blur(2px);\n}\n[data-slot=\"sunny-rays\"][data-animated=\"true\"] {\n  animation: sunny-rays-rotate var(--sunny-rays-duration) linear infinite;\n}\n[data-slot=\"sunny-flare\"] {\n  position: absolute;\n  left: 50%;\n  top: 50%;\n  transform: translate(-50%, -50%);\n  background: linear-gradient(\n    90deg,\n    transparent 0%,\n    color-mix(in oklch, var(--sunny-core) 60%, transparent) 45%,\n    color-mix(in oklch, var(--sunny-core) 80%, transparent) 50%,\n    color-mix(in oklch, var(--sunny-core) 60%, transparent) 55%,\n    transparent 100%\n  );\n  filter: blur(0.5px);\n}\n[data-slot-variant=\"sunny-flare-h\"] {\n  width: calc(var(--sunny-size) * 0.375);\n  height: 2px;\n  opacity: 0.4;\n}\n[data-slot-variant=\"sunny-flare-v\"] {\n  width: 2px;\n  height: calc(var(--sunny-size) * 0.25);\n  background: linear-gradient(\n    180deg,\n    transparent 0%,\n    color-mix(in oklch, var(--sunny-core) 50%, transparent) 45%,\n    color-mix(in oklch, var(--sunny-core) 70%, transparent) 50%,\n    color-mix(in oklch, var(--sunny-core) 50%, transparent) 55%,\n    transparent 100%\n  );\n  opacity: 0.3;\n}\n[data-slot-variant=\"sunny-flare-diag-1\"] {\n  width: calc(var(--sunny-size) * 0.1875);\n  height: 1px;\n  transform: translate(-50%, -50%) rotate(45deg);\n  opacity: 0.2;\n}\n[data-slot-variant=\"sunny-flare-diag-2\"] {\n  width: calc(var(--sunny-size) * 0.1875);\n  height: 1px;\n  transform: translate(-50%, -50%) rotate(-45deg);\n  opacity: 0.2;\n}\n@keyframes sunny-rays-rotate {\n  from {\n    transform: translate(-50%, -50%) rotate(0deg);\n  }\n  to {\n    transform: translate(-50%, -50%) rotate(360deg);\n  }\n}\n@media (prefers-reduced-motion: reduce) {\n  [data-slot=\"sunny-rays\"] {\n    animation: none !important;\n  }\n}\n`;\n\n/**\n * SunnyBackground — see the file header for the layer stack, the CSS-variable\n * palette layer, and the reduced-motion contract.\n */\nexport function SunnyBackground({\n  className,\n  \"data-testid\": testId,\n  size = \"20rem\",\n  corner = \"top-right\",\n  animated = true,\n  raysDurationSeconds = 120,\n  coreColor = DEFAULT_CORE,\n  glowColor = DEFAULT_GLOW,\n  skyTopColor = DEFAULT_SKY_TOP,\n  skyBottomColor = DEFAULT_SKY_BOTTOM,\n  style,\n  ...props\n}: SunnyBackgroundProps) {\n  const reduceMotion = useReducedMotion();\n  // Scope the injected <style> to this instance via a unique attribute so two\n  // SunnyBackgrounds with different palettes never clobber each other's vars.\n  const instanceId = useId();\n  const raysActive = animated && !reduceMotion;\n\n  // The one place R18 allows inline style: dynamic CSS-variable overrides.\n  // All literal colors arrive as props (or their signature defaults) and are\n  // funneled into custom properties the static stylesheet consumes — keeping\n  // every raw color out of the JSX render path.\n  const cssVars = useMemo(\n    () =>\n      ({\n        \"--sunny-size\": size,\n        \"--sunny-rays-duration\": `${raysDurationSeconds}s`,\n        \"--sunny-core\": coreColor,\n        \"--sunny-glow\": glowColor,\n        \"--sunny-sky-top\": skyTopColor,\n        \"--sunny-sky-bottom\": skyBottomColor,\n      }) as React.CSSProperties,\n    [size, raysDurationSeconds, coreColor, glowColor, skyTopColor, skyBottomColor],\n  );\n\n  const sunPosition = cn({\n    \"top-8 right-8 sm:top-12 sm:right-16\": corner === \"top-right\",\n    \"top-8 left-8 sm:top-12 sm:left-16\": corner === \"top-left\",\n    \"top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2\": corner === \"center\",\n  });\n\n  return (\n    <div\n      data-slot=\"sunny-background\"\n      data-testid={testId}\n      data-interlace-sunny={instanceId}\n      aria-hidden=\"true\"\n      className={cn(\n        \"pointer-events-none absolute inset-0 overflow-hidden\",\n        className,\n      )}\n      style={{ ...cssVars, ...style }}\n      {...props}\n    >\n      <style\n        // Static structural CSS keyed only to data-slot selectors; all colors\n        // resolve from the CSS variables set above. No literal ships here.\n        dangerouslySetInnerHTML={{ __html: SUNNY_CSS }}\n      />\n\n      {/* Layered atmospheric sky. */}\n      <div data-slot=\"sunny-sky\" className=\"absolute inset-0\" />\n      <div data-slot=\"sunny-haze\" className=\"absolute inset-0\" />\n\n      {/* Sun disc — every child scales with --sunny-size. */}\n      <div data-slot=\"sunny-disc\" className={cn(\"absolute\", sunPosition)}>\n        <div data-slot=\"sunny-corona-outer\" />\n        <div data-slot=\"sunny-corona-middle\" />\n        <div data-slot=\"sunny-glow\" />\n        <div data-slot=\"sunny-core\" />\n        <div data-slot=\"sunny-rays\" data-animated={raysActive} />\n        <div data-slot=\"sunny-flare\" data-slot-variant=\"sunny-flare-h\" />\n        <div data-slot=\"sunny-flare\" data-slot-variant=\"sunny-flare-v\" />\n        <div data-slot=\"sunny-flare\" data-slot-variant=\"sunny-flare-diag-1\" />\n        <div data-slot=\"sunny-flare\" data-slot-variant=\"sunny-flare-diag-2\" />\n      </div>\n\n      {/* Horizon golden-hour band + depth vignette. */}\n      <div\n        data-slot=\"sunny-horizon\"\n        className=\"absolute inset-x-0 bottom-0 h-1/2\"\n      />\n      <div data-slot=\"sunny-vignette\" className=\"absolute inset-0\" />\n    </div>\n  );\n}\n"
    }
  ],
  "meta": {
    "tier": "effect",
    "client": true,
    "minViewport": null,
    "loading": false,
    "version": "1.1.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/sunny-background\n\nInstalled to `components/ui/aceternity/sunny-background.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/aceternity/sunny-background';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/sunny-background\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
