{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "eclipse",
  "type": "registry:ui",
  "title": "Eclipse",
  "description": "A total solar eclipse: a black moon disk occludes a dead-center sun ringed by a white-hot corona of writhing radial streamers, a razor limb ring, and one warm diamond-ring bead drifting around the rim over a seeded starfield. Original OGL fragment shader, dithered, offscreen-paused, reduced-motion-safe.",
  "dependencies": [
    "ogl"
  ],
  "registryDependencies": [
    "https://crucibleui.com/r/ogl-canvas.json",
    "https://crucibleui.com/r/use-visibility-pause.json",
    "https://crucibleui.com/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/default/backgrounds/eclipse/eclipse.tsx",
      "type": "registry:ui",
      "target": "components/crucible/eclipse.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { OglCanvas } from \"@/lib/ogl-canvas\";\n\nexport interface EclipseProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  /**\n   * Three tones: `[core, streamer, glint]`. `core` is the white-hot inner\n   * corona hugging the limb, `streamer` is the cooler outer corona the rays\n   * fade into, `glint` is the warm diamond-ring bead. Any hex string.\n   * @default [\"#f4f6ff\", \"#a8c2ec\", \"#ffcf8a\"]\n   */\n  colors?: [string, string, string];\n  /**\n   * Animation speed multiplier: scales the corona's writhe and the diamond\n   * glint's travel around the ring. 1 = default, 2 = twice as fast.\n   * @default 1\n   */\n  speed?: number;\n  /**\n   * Corona brightness 0–2: scales the glow, streamers, limb ring, and diamond\n   * glint together. The starfield stays fixed so raising it never blows out\n   * the sky. @default 1\n   */\n  intensity?: number;\n  /** Freeze the animation at its current frame. @default false */\n  paused?: boolean;\n  /**\n   * Idle autoplay: after ~1.5s with no pointer present, a seeded ghost drifts\n   * slowly across the field and shifts the parallax, so thumbnails and heroes\n   * never look still; a real pointer takes over instantly. Never runs under\n   * reduced motion. Default true.\n   */\n  idle?: boolean;\n}\n\nconst DEFAULT_COLORS: [string, string, string] = [\n  \"#f4f6ff\", // core — white-hot inner corona\n  \"#a8c2ec\", // streamer — cool ice-blue outer corona\n  \"#ffcf8a\", // glint — warm diamond-ring bead\n];\n\n/** Hex → [r,g,b] in 0–1. Accepts #rgb / #rrggbb. */\nfunction hexToRgb(hex: string): [number, number, number] {\n  let h = hex.replace(\"#\", \"\").trim();\n  if (h.length === 3) {\n    h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];\n  }\n  const int = parseInt(h, 16);\n  if (Number.isNaN(int) || h.length !== 6) return [0.96, 0.96, 1.0];\n  return [((int >> 16) & 255) / 255, ((int >> 8) & 255) / 255, (int & 255) / 255];\n}\n\n/**\n * Original total-solar-eclipse fragment shader. A black moon disk occludes a\n * sun dead-center, ringed by a white-hot corona: broad angular lobes and fine\n * rays are built from seamless direction-space fbm (sampled on the unit circle\n * so the rays are perfectly radial with no atan seam), each streamer reaching\n * farther the brighter it is, so the corona fades outward with a dithered\n * exponential falloff instead of banding. A razor limb ring sits at the moon's\n * edge, and one intense diamond-ring bead — a hot core, soft bloom, and a\n * subtle cross-flare, warm against the neutral corona — travels very slowly\n * around the rim. A seeded starfield sits behind, washed out under the corona\n * and occluded by the disk. Filmic highlight rolloff keeps the bloom clean;\n * grain dithering defeats 8-bit banding. GLSL ES 1.00 (WebGL1/2 compatible).\n */\nconst FRAGMENT = /* glsl */ `\nprecision highp float;\n\nvarying vec2 vUv;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform vec2 uPointer;\nuniform float uPointerActive; // eased 0-1 pointer/ghost presence\n\nuniform vec3 uCore;      // inner corona\nuniform vec3 uStreamer;  // outer corona\nuniform vec3 uGlint;     // diamond bead\nuniform float uSpeed;\nuniform float uIntensity;\n\n// Sun/moon radius in vertical-half units (uv.y spans 1.0 across the frame).\nconst float RIM = 0.15;\n\nfloat hash21(vec2 p) {\n  p = fract(p * vec2(123.34, 456.21));\n  p += dot(p, p + 45.32);\n  return fract(p.x * p.y);\n}\n\nfloat vnoise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  float a = hash21(i);\n  float b = hash21(i + vec2(1.0, 0.0));\n  float c = hash21(i + vec2(0.0, 1.0));\n  float d = hash21(i + vec2(1.0, 1.0));\n  vec2 u = f * f * (3.0 - 2.0 * f);\n  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\n// 4-octave fbm. Rotated lacunarity kills the axis-aligned look.\nfloat fbm(vec2 p) {\n  float v = 0.0;\n  float amp = 0.5;\n  mat2 m = mat2(1.6, 1.2, -1.2, 1.6);\n  for (int i = 0; i < 4; i++) {\n    v += amp * vnoise(p);\n    p = m * p;\n    amp *= 0.5;\n  }\n  return v;\n}\n\nvoid main() {\n  vec2 uv = vUv;\n  float aspect = uResolution.x / max(uResolution.y, 1.0);\n  float resY = max(uResolution.y, 1.0);\n  float t = uTime * uSpeed;\n\n  // Subtle parallax: the disk shifts a hair more than the stars with the\n  // pointer, giving the void depth. At rest (pointer centered) it's dead center.\n  // Presence-scaled so the disk recenters when nobody is there.\n  vec2 ptr = mix(vec2(0.5), uPointer, uPointerActive);\n  vec2 center = vec2(0.5) + (ptr - 0.5) * 0.025;\n\n  vec2 p = (uv - center) * vec2(aspect, 1.0);\n  float rr = length(p);\n  vec2 dir = p / max(rr, 1e-4);\n\n  // Distance outside the limb, in sun-radii.\n  float x = max(rr - RIM, 0.0) / RIM;\n\n  // ------------------------------------------------------------- corona --\n  // Seamless angular fields: sampling fbm on the unit circle makes the value\n  // continuous around the ring (period 2pi, no seam) and constant along a\n  // radius — i.e. perfectly radial streamers. Slow drift = the writhe.\n  float lobes = fbm(dir * 2.4 + t * vec2(0.05, -0.035));\n  float rays  = fbm(dir * 6.5 + vec2(9.3, 2.1) - t * vec2(0.045, 0.06));\n  float streak = clamp(lobes * 0.62 + rays * 0.48, 0.0, 1.0);\n  streak = pow(streak, 1.7);\n  // Faint radial breakup so rays are filaments, not perfect spokes.\n  streak *= 0.72 + 0.28 * fbm(dir * 9.5 + vec2(0.0, rr * 6.0) - t * 0.05);\n\n  // Brighter streamers reach farther out.\n  float reach = mix(0.55, 1.9, streak);\n  float streamer = exp(-x / reach) * streak;\n  float baseGlow = exp(-x * 1.7) * 0.42;      // smooth halo filling the gaps\n  float breathe = 0.94 + 0.06 * sin(t * 0.3);\n  float corAmt = (baseGlow + streamer * 1.15) * breathe;\n\n  // Razor limb ring + a soft collar hugging the moon's edge.\n  float ringD = (rr - RIM) / RIM;\n  float ring = exp(-ringD * ringD * 320.0);\n  float collar = exp(-x * 7.0);\n\n  // ------------------------------------------------------- diamond ring --\n  float phi = t * 0.06 + 0.4;                 // travels slowly around the rim\n  vec2 gdir = vec2(cos(phi), sin(phi));\n  float nearG = smoothstep(0.4, 1.0, dot(dir, gdir)); // limb glows near the bead\n  vec2 gpos = gdir * (RIM * 1.02);            // just outside the limb\n  vec2 gp = p - gpos;\n  float gdist = length(gp);\n  float gcore = exp(-gdist * gdist * 2200.0);\n  float gbloom = exp(-gdist * 20.0);\n  float fH = exp(-gp.y * gp.y * 2600.0) * exp(-abs(gp.x) * 9.0);\n  float fV = exp(-gp.x * gp.x * 2600.0) * exp(-abs(gp.y) * 9.0);\n  float glint = gcore * 3.5 + gbloom * 0.55 + (fH + fV) * 0.45;\n\n  // --------------------------------------------------------------- sky --\n  vec3 col = vec3(0.0015, 0.002, 0.005); // faint deep-space cast, never dead #000\n\n  // Seeded starfield, washed out under the corona, parallaxed a touch.\n  vec2 sPar = (ptr - 0.5) * 0.008;\n  vec2 sc = (uv - sPar) * vec2(aspect, 1.0) * 92.0;\n  vec2 sid = floor(sc);\n  float pick = hash21(sid);\n  float starW = 1.0 - clamp(corAmt * 2.2, 0.0, 1.0);\n  if (pick > 0.985) {\n    vec2 sp = vec2(hash21(sid + 3.1), hash21(sid + 7.7));\n    float dst = length(fract(sc) - sp);\n    float b = pow(max(0.0, 1.0 - dst * 3.2), 5.0);\n    float tw = 0.55 + 0.45 * sin(t * 1.1 + pick * 54.0);\n    col += vec3(0.7, 0.78, 1.0) * b * tw * (0.35 + 0.65 * hash21(sid + 1.7)) * starW;\n  }\n\n  // ------------------------------------------------------------ light --\n  // Corona hue drifts from white-hot core at the limb to cool streamer far out.\n  vec3 cCol = mix(uCore, uStreamer, clamp(x * 0.6, 0.0, 1.0));\n\n  vec3 light = vec3(0.0);\n  light += cCol * corAmt * (1.0 + 0.6 * nearG);\n  light += mix(uCore, vec3(1.0), 0.5) * ring * (0.9 + 1.4 * nearG);\n  light += uCore * collar * (0.35 + 0.5 * nearG);\n  light += uGlint * glint;\n  col += light * uIntensity;\n\n  // The moon occludes everything: stars behind it, the inner half of the ring.\n  float aa = 1.5 / resY;\n  float disk = smoothstep(RIM - aa, RIM + aa, rr);\n  col *= disk;\n\n  // ---------------------------------------------------------- surface --\n  vec2 vg = (uv - 0.5) * vec2(aspect, 1.0);\n  col *= 1.0 - 0.4 * dot(vg, vg); // sink the frame edges into black\n\n  // Filmic highlight rolloff: only the hottest points bloom to white.\n  col = 1.0 - exp(-col * 1.5);\n\n  // Grain dithering to defeat 8-bit banding across the falloff.\n  float grain = hash21(gl_FragCoord.xy + fract(t) * 137.0) - 0.5;\n  col += grain * (2.0 / 255.0);\n\n  gl_FragColor = vec4(max(col, 0.0), 1.0);\n}\n`;\n\n/**\n * Eclipse — a total solar eclipse in GL. A black moon disk occludes a\n * dead-center sun ringed by a white-hot corona: radial streamers writhe and\n * fade outward with a dithered falloff, a razor limb ring burns at the edge,\n * and one warm diamond-ring bead travels slowly around the rim over a seeded\n * starfield. Built on the `ogl-canvas` harness: GPU-light, offscreen-paused,\n * reduced-motion-safe. Position it inside any `relative` container; it fills\n * and sits behind content.\n */\nexport function Eclipse({\n  colors = DEFAULT_COLORS,\n  speed = 1,\n  intensity = 1,\n  paused = false,\n  idle = true,\n  className,\n  ...props\n}: EclipseProps) {\n  const uniforms = React.useMemo(\n    () => ({\n      uCore: { value: hexToRgb(colors[0]) },\n      uStreamer: { value: hexToRgb(colors[1]) },\n      uGlint: { value: hexToRgb(colors[2]) },\n      uSpeed: { value: Math.max(speed, 0) },\n      uIntensity: { value: Math.min(Math.max(intensity, 0), 2) },\n    }),\n    [colors, speed, intensity]\n  );\n\n  return (\n    <OglCanvas\n      aria-hidden\n      data-crucible=\"eclipse\"\n      {...props}\n      className={cn(\"absolute inset-0 -z-10 bg-neutral-950\", className)}\n      fragment={FRAGMENT}\n      uniforms={uniforms}\n      paused={paused}\n      ghost={idle}\n      dpr={1.75}\n      reducedMotionTime={13.5}\n    />\n  );\n}\n"
    }
  ]
}