{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ember",
  "type": "registry:ui",
  "title": "Ember",
  "description": "A field of glowing embers rising from below in molten forge tones, each with its own phase, flicker, wind drift and color ramp; the cursor stirs a gentle updraft. Original OGL fragment shader, offscreen-paused and 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/ember/ember.tsx",
      "type": "registry:ui",
      "target": "components/crucible/ember.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 EmberProps {\n  /**\n   * Two forge tones for the ember color ramp: `[base, tip]`. `base` is the\n   * color at the bottom where embers are born (ember red); `tip` is the color\n   * they carry as they rise (molten amber). Any hex string.\n   */\n  colors?: [string, string];\n  /**\n   * Wind vector `[x, y]`. `x` drifts rising embers sideways (positive =\n   * rightward), roughly −1…1; positive `y` quickens their rise (0 or\n   * negative leaves the base rise speed — embers never sink).\n   * Default `[0.25, 0]` — a soft rightward draft.\n   */\n  wind?: [number, number];\n  /** Rise-speed multiplier. 1 = default. Default 1. */\n  speed?: number;\n  /** Ember count / field density, clamped 8–64. Default 44. */\n  density?: number;\n  /** Overall glow / bloom of the embers. 1 = default. Default 1. */\n  intensity?: number;\n  /** Freeze the animation, holding the current frame. */\n  paused?: boolean;\n  /**\n   * Idle autoplay: after ~1.5s with no pointer present, a seeded ghost drifts\n   * slowly across the field and raises the updraft, 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  /** Extra classes for the wrapper. Positioned `absolute inset-0 -z-10`. */\n  className?: string;\n}\n\nconst DEFAULT_COLORS: [string, string] = [\n  \"#c1121f\", // ember red — base\n  \"#ff6b35\", // molten amber — tip\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 [1, 0.42, 0.21];\n  return [((int >> 16) & 255) / 255, ((int >> 8) & 255) / 255, (int & 255) / 255];\n}\n\n/**\n * Original ember fragment shader. A field of point-sprite embers, each seeded\n * by an inline hash, rising from the bottom edge with a per-particle phase,\n * convection wobble, and a caller-supplied horizontal wind. Color ramps from\n * base (ember red) at birth to tip (molten amber) as it climbs; brightness\n * flickers per particle; glow accumulates additively over a dark coal bed.\n * The pointer raises a gentle updraft in the embers nearest it. GLSL ES 1.00.\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 uColorBase; // ember red (birth)\nuniform vec3 uColorTip;  // molten amber (rising)\nuniform vec2 uWind;      // horizontal drift + rise bias\nuniform float uSpeed;\nuniform float uDensity;\nuniform float uIntensity;\n\nconst int MAX_EMBERS = 64;\n\n// --- inline hashes (original) ---\nfloat hash11(float n) {\n  return fract(sin(n * 127.1) * 43758.5453);\n}\nvec2 hash21(float n) {\n  return fract(sin(vec2(n * 127.1, n * 311.7)) * 43758.5453);\n}\n\nvoid main() {\n  vec2 uv = vUv;\n  float aspect = uResolution.x / max(uResolution.y, 1.0);\n  float t = uTime * uSpeed;\n\n  int count = int(clamp(uDensity, 8.0, float(MAX_EMBERS)));\n\n  // Dark field with a faint warm coal glow banked along the bottom edge.\n  vec3 col = vec3(0.015, 0.010, 0.020);\n  col += uColorBase * 0.10 * pow(1.0 - uv.y, 5.0);\n\n  for (int i = 0; i < MAX_EMBERS; i++) {\n    if (i >= count) break;\n    float fi = float(i);\n\n    vec2 rnd = hash21(fi + 1.37);\n    float speed = 0.10 + rnd.x * 0.22 + max(uWind.y, 0.0) * 0.15;\n    float phase = rnd.y;\n\n    // life: 0 at the bottom edge, 1 at the top, looping.\n    float life = fract(t * speed + phase);\n\n    // Horizontal home + convection wobble, then wind carries it with height.\n    float bx = hash11(fi * 3.17 + 5.0);\n    float wob = sin(t * (0.8 + rnd.x * 2.0) + fi * 2.3) * 0.03;\n    float x = bx + wob + uWind.x * life * 0.45;\n    x = fract(x);\n\n    float y = life;\n\n    // Pointer updraft: embers horizontally near the pointer and below it get\n    // lifted and jostled — a gentle convection column, never repulsion.\n    float dxp = (x - uPointer.x) * aspect;\n    float prox = exp(-dxp * dxp * 6.0);\n    float below = smoothstep(uPointer.y + 0.20, uPointer.y - 0.30, y);\n    float updraft = prox * below * uPointerActive;\n    y += updraft * 0.05;\n    x += updraft * sin(t * 5.0 + fi) * 0.008;\n\n    // Point-sprite: gaussian falloff, shrinking as the ember rises.\n    float size = (0.006 + rnd.x * 0.011) * (1.0 - 0.45 * life);\n    size *= 1.0 + updraft * 0.30;\n    vec2 d = vec2((uv.x - x) * aspect, uv.y - y);\n    float g = exp(-dot(d, d) / (size * size));\n\n    // Per-particle flicker + fade-in at birth, fade-out near the top.\n    float flick = 0.65 + 0.35 * sin(t * (6.0 + rnd.y * 8.0) + fi * 4.0);\n    float fade = smoothstep(0.0, 0.07, life) * (1.0 - smoothstep(0.72, 1.0, life));\n\n    // Color ramp base → tip along the rise.\n    vec3 ec = mix(uColorBase, uColorTip, smoothstep(0.0, 0.85, life));\n\n    col += ec * g * flick * fade * uIntensity;\n  }\n\n  // Soft tone curve so stacked glows bloom instead of clipping.\n  col = col / (col + vec3(0.85));\n\n  // Film-grain dithering to defeat 8-bit banding.\n  float grain = hash11(dot(gl_FragCoord.xy, vec2(12.9898, 78.233)) + fract(t) * 137.0) - 0.5;\n  col += grain * (1.6 / 255.0);\n\n  gl_FragColor = vec4(max(col, 0.0), 1.0);\n}\n`;\n\n/**\n * Ember — a field of glowing embers rising from below in molten forge tones.\n * Each ember carries its own phase, convection wobble, flicker and color ramp\n * (ember red → molten amber), with a caller-supplied wind drift and a gentle\n * cursor updraft. Built on the `ogl-canvas` harness: GPU-light, offscreen-\n * paused, reduced-motion-safe. Drop it inside any `relative` container; it\n * fills and sits behind content.\n */\nexport function Ember({\n  colors = DEFAULT_COLORS,\n  wind = [0.25, 0],\n  speed = 1,\n  density = 44,\n  intensity = 1,\n  paused = false,\n  idle = true,\n  className,\n}: EmberProps) {\n  const uniforms = React.useMemo(\n    () => ({\n      uColorBase: { value: hexToRgb(colors[0]) },\n      uColorTip: { value: hexToRgb(colors[1]) },\n      uWind: { value: [wind[0], wind[1]] as [number, number] },\n      uSpeed: { value: speed },\n      uDensity: { value: density },\n      uIntensity: { value: intensity },\n    }),\n    [colors, wind, speed, density, intensity]\n  );\n\n  return (\n    <OglCanvas\n      aria-hidden\n      data-crucible=\"ember\"\n      className={cn(\"absolute inset-0 -z-10\", className)}\n      fragment={FRAGMENT}\n      uniforms={uniforms}\n      paused={paused}\n      ghost={idle}\n      dpr={1.75}\n      reducedMotionTime={2.5}\n    />\n  );\n}\n"
    }
  ]
}