{
  "name": "cloud-shader",
  "type": "registry:ui",
  "files": [
    {
      "path": "components/ui/cloud-shader.tsx",
      "content": "\"use client\";\n\nimport React, { useEffect, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CloudShaderProps = {\n  className?: string;\n  children?: React.ReactNode;\n  /** Animation speed multiplier. 1 = default drift. */\n  speed?: number;\n  /** Number of clouds (1-6). */\n  count?: number;\n  /** Cloud tint color (hex or rgb string). */\n  cloudColor?: string;\n  /** Sky color at the top (hex or rgb string). */\n  skyTopColor?: string;\n  /** Sky color at the bottom (hex or rgb string). */\n  skyBottomColor?: string;\n};\n\nconst VERT = `\nattribute vec2 a_pos;\nvarying vec2 v_uv;\nvoid main() {\n  v_uv = a_pos * 0.5 + 0.5;\n  gl_Position = vec4(a_pos, 0.0, 1.0);\n}\n`;\n\n// Original cloud shader for Aceternity UI.\n// Each cloud is an asymmetric envelope (dome top, flat base) filled with\n// domain-warped billow noise. A second density sample above the pixel\n// approximates self-shadowing. Clouds drift horizontally and wrap around.\nconst FRAG = `\nprecision highp float;\n\nvarying vec2 v_uv;\n\nuniform vec2 u_res;\nuniform float u_time;\nuniform float u_count;\nuniform vec3 u_cloud;\nuniform vec3 u_skyTop;\nuniform vec3 u_skyBottom;\n\nconst mat2 R = mat2(0.80, 0.60, -0.60, 0.80);\n\nfloat hash(vec2 p) {\n  return fract(sin(dot(p, vec2(41.31, 289.17))) * 26737.367);\n}\n\nfloat vnoise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  f = f * f * (3.0 - 2.0 * f);\n  float a = hash(i);\n  float b = hash(i + vec2(1.0, 0.0));\n  float c = hash(i + vec2(0.0, 1.0));\n  float d = hash(i + vec2(1.0, 1.0));\n  return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);\n}\n\nfloat fbm(vec2 p) {\n  float sum = 0.0;\n  float amp = 0.5;\n  for (int i = 0; i < 4; i++) {\n    sum += amp * vnoise(p);\n    p = R * p * 2.03 + 19.19;\n    amp *= 0.5;\n  }\n  return sum;\n}\n\n// billow noise: sharp puffy ridges, like cauliflower cloud tops\nfloat billow(vec2 p) {\n  float sum = 0.0;\n  float amp = 0.5;\n  for (int i = 0; i < 5; i++) {\n    sum += amp * (1.0 - abs(2.0 * vnoise(p) - 1.0));\n    p = R * p * 2.11 + 13.37;\n    amp *= 0.5;\n  }\n  return sum;\n}\n\n// raw density for one cloud at point p\nfloat cloudDensity(vec2 p, vec2 c, vec2 r, float seed, float t) {\n  vec2 q = p - c;\n\n  // envelope: dome above the center, flat base below\n  float ry = q.y > 0.0 ? r.y : r.y * 0.42;\n  float env = 1.0 - length(vec2(q.x / r.x, q.y / ry));\n  if (env < -0.35) return 0.0;\n\n  // domain-warped billow detail, moves with the cloud, evolves slowly\n  vec2 dp = q * (2.4 / r.x) + seed;\n  dp += 0.6 * vec2(\n    fbm(dp * 1.4 + t * 0.04),\n    fbm(dp * 1.4 + 7.7 - t * 0.03)\n  );\n  float detail = billow(dp * 1.6);\n\n  return env + (detail - 0.62) * 0.62;\n}\n\n// shades one cloud and blends it over the current color\nvec3 shadeCloud(vec3 color, vec3 sky, vec2 p, vec2 c, vec2 r, float seed, float t, float dist) {\n  float d = cloudDensity(p, c, r, seed, t);\n  if (d < 0.02) return color;\n\n  // sample density toward the sun (straight up) for self-shadowing\n  float dUp = cloudDensity(p + vec2(0.0, r.y * 0.55), c, r, seed, t);\n  float occl = clamp((dUp - d) * 1.1 + d * 0.55, 0.0, 1.0);\n\n  vec3 lit = u_cloud * 1.04;\n  vec3 shadow = mix(u_cloud * 0.60, sky, 0.38);\n  vec3 cloudCol = mix(lit, shadow, occl * 0.85);\n\n  float alpha = smoothstep(0.02, 0.38, d);\n\n  // silver lining on thin edges\n  float rim = smoothstep(0.02, 0.14, d) * (1.0 - smoothstep(0.14, 0.40, d));\n  cloudCol += rim * 0.10;\n\n  // atmospheric perspective: far clouds fade into the sky\n  cloudCol = mix(cloudCol, sky, dist * 0.35);\n  alpha *= mix(1.0, 0.8, dist);\n\n  return mix(color, cloudCol, alpha);\n}\n\n// one drifting cloud: horizontal wrap + gentle vertical bob\nvec3 cloudPass(vec3 color, vec3 sky, vec2 p, float aspect, float t,\n               float spd, float phase, float y, vec2 r, float seed, float dist) {\n  float cx = mix(-r.x - 0.25, aspect + r.x + 0.25, fract(t * spd + phase));\n  float cy = y + sin(t * 0.05 + phase * 6.2831) * 0.012;\n  return shadeCloud(color, sky, p, vec2(cx, cy), r, seed, t, dist);\n}\n\nvoid main() {\n  float aspect = u_res.x / u_res.y;\n  vec2 p = vec2(v_uv.x * aspect, v_uv.y);\n  float t = u_time;\n\n  vec3 sky = mix(u_skyBottom, u_skyTop, v_uv.y);\n  vec3 color = sky;\n\n  // faint haze band near the horizon\n  color = mix(color, u_skyBottom * 1.06, smoothstep(0.35, 0.0, v_uv.y) * 0.5);\n\n  // soft sun glow, upper area\n  vec2 sunPos = vec2(aspect * 0.78, 0.92);\n  float sunDist = length(p - sunPos);\n  color += vec3(1.0, 0.95, 0.82) * exp(-sunDist * sunDist * 5.0) * 0.28;\n\n  // thin cirrus streaks, stretched horizontally, high in the sky\n  float cirrusBand = smoothstep(0.55, 0.8, v_uv.y) * (1.0 - smoothstep(0.9, 1.0, v_uv.y));\n  if (cirrusBand > 0.01) {\n    float streak = fbm(vec2(p.x * 1.6 - t * 0.006, p.y * 12.0));\n    float wisp = smoothstep(0.52, 0.78, streak) * cirrusBand;\n    color = mix(color, u_cloud * 0.98, wisp * 0.35);\n  }\n\n  // far layer: small, high, slow\n  if (u_count > 5.5) {\n    color = cloudPass(color, sky, p, aspect, t, 0.006, 0.10, 0.84, vec2(0.20, 0.10), 43.7, 1.0);\n  }\n  if (u_count > 4.5) {\n    color = cloudPass(color, sky, p, aspect, t, 0.008, 0.62, 0.73, vec2(0.24, 0.12), 71.3, 0.85);\n  }\n\n  // middle layer\n  if (u_count > 3.5) {\n    color = cloudPass(color, sky, p, aspect, t, 0.011, 0.33, 0.60, vec2(0.34, 0.16), 17.3, 0.55);\n  }\n  if (u_count > 2.5) {\n    color = cloudPass(color, sky, p, aspect, t, 0.013, 0.80, 0.47, vec2(0.30, 0.15), 29.9, 0.45);\n  }\n\n  // near layer: big, low, fast\n  if (u_count > 1.5) {\n    color = cloudPass(color, sky, p, aspect, t, 0.016, 0.05, 0.35, vec2(0.46, 0.20), 91.1, 0.15);\n  }\n  color = cloudPass(color, sky, p, aspect, t, 0.020, 0.48, 0.20, vec2(0.56, 0.24), 57.2, 0.0);\n\n  gl_FragColor = vec4(color, 1.0);\n}\n`;\n\nfunction parseHex(color: string): [number, number, number] {\n  const value = color.trim();\n  if (value.startsWith(\"#\")) {\n    const hex = value.slice(1);\n    if (hex.length === 3) {\n      return [\n        parseInt(hex[0] + hex[0], 16) / 255,\n        parseInt(hex[1] + hex[1], 16) / 255,\n        parseInt(hex[2] + hex[2], 16) / 255,\n      ];\n    }\n    return [\n      parseInt(hex.slice(0, 2), 16) / 255,\n      parseInt(hex.slice(2, 4), 16) / 255,\n      parseInt(hex.slice(4, 6), 16) / 255,\n    ];\n  }\n  const rgb = value.match(/[\\d.]+/g);\n  if (rgb && rgb.length >= 3) {\n    return [Number(rgb[0]) / 255, Number(rgb[1]) / 255, Number(rgb[2]) / 255];\n  }\n  return [0.95, 0.95, 0.95];\n}\n\nfunction compile(gl: WebGLRenderingContext, type: number, source: string) {\n  const shader = gl.createShader(type);\n  if (!shader) return null;\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n    gl.deleteShader(shader);\n    return null;\n  }\n  return shader;\n}\n\nexport const CloudShader = ({\n  className,\n  children,\n  speed = 1,\n  count = 6,\n  cloudColor = \"#fbf8f2\",\n  skyTopColor = \"#3876ba\",\n  skyBottomColor = \"#8cbfe8\",\n}: CloudShaderProps) => {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const paramsRef = useRef({\n    speed,\n    count,\n    cloudColor,\n    skyTopColor,\n    skyBottomColor,\n  });\n\n  paramsRef.current = {\n    speed,\n    count,\n    cloudColor,\n    skyTopColor,\n    skyBottomColor,\n  };\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n\n    const gl = canvas.getContext(\"webgl\", {\n      alpha: false,\n      antialias: false,\n      premultipliedAlpha: false,\n    });\n    if (!gl) return;\n\n    const vert = compile(gl, gl.VERTEX_SHADER, VERT);\n    const frag = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n    if (!vert || !frag) return;\n\n    const program = gl.createProgram();\n    if (!program) return;\n    gl.attachShader(program, vert);\n    gl.attachShader(program, frag);\n    gl.bindAttribLocation(program, 0, \"a_pos\");\n    gl.linkProgram(program);\n    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return;\n    gl.useProgram(program);\n\n    const buffer = gl.createBuffer();\n    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n    gl.bufferData(\n      gl.ARRAY_BUFFER,\n      new Float32Array([-1, -1, 3, -1, -1, 3]),\n      gl.STATIC_DRAW,\n    );\n    gl.enableVertexAttribArray(0);\n    gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n\n    const loc = {\n      res: gl.getUniformLocation(program, \"u_res\"),\n      time: gl.getUniformLocation(program, \"u_time\"),\n      count: gl.getUniformLocation(program, \"u_count\"),\n      cloud: gl.getUniformLocation(program, \"u_cloud\"),\n      skyTop: gl.getUniformLocation(program, \"u_skyTop\"),\n      skyBottom: gl.getUniformLocation(program, \"u_skyBottom\"),\n    };\n\n    let frame = 0;\n    let running = true;\n    const reduceMotion = window.matchMedia(\n      \"(prefers-reduced-motion: reduce)\",\n    ).matches;\n\n    const resize = () => {\n      const dpr = Math.min(window.devicePixelRatio || 1, 2);\n      const width = canvas.clientWidth;\n      const height = canvas.clientHeight;\n      const w = Math.max(1, Math.floor(width * dpr));\n      const h = Math.max(1, Math.floor(height * dpr));\n      if (canvas.width !== w || canvas.height !== h) {\n        canvas.width = w;\n        canvas.height = h;\n      }\n      gl.viewport(0, 0, w, h);\n      gl.uniform2f(loc.res, w, h);\n    };\n\n    const observer = new ResizeObserver(resize);\n    observer.observe(canvas);\n    resize();\n\n    const start = performance.now();\n    const draw = (now: number) => {\n      if (!running) return;\n      const p = paramsRef.current;\n      const elapsed = reduceMotion ? 0 : ((now - start) / 1000) * p.speed;\n      const cloud = parseHex(p.cloudColor);\n      const skyTop = parseHex(p.skyTopColor);\n      const skyBottom = parseHex(p.skyBottomColor);\n\n      gl.uniform1f(loc.time, elapsed);\n      gl.uniform1f(loc.count, Math.min(6, Math.max(1, p.count)));\n      gl.uniform3f(loc.cloud, cloud[0], cloud[1], cloud[2]);\n      gl.uniform3f(loc.skyTop, skyTop[0], skyTop[1], skyTop[2]);\n      gl.uniform3f(loc.skyBottom, skyBottom[0], skyBottom[1], skyBottom[2]);\n      gl.drawArrays(gl.TRIANGLES, 0, 3);\n      frame = requestAnimationFrame(draw);\n    };\n\n    frame = requestAnimationFrame(draw);\n\n    return () => {\n      running = false;\n      cancelAnimationFrame(frame);\n      observer.disconnect();\n      gl.deleteBuffer(buffer);\n      gl.deleteProgram(program);\n      gl.deleteShader(vert);\n      gl.deleteShader(frag);\n    };\n  }, []);\n\n  return (\n    <div\n      className={cn(\n        \"relative h-full min-h-80 w-full overflow-hidden\",\n        className,\n      )}\n    >\n      <canvas\n        ref={canvasRef}\n        className=\"pointer-events-none absolute inset-0 h-full w-full\"\n      />\n      {children ? (\n        <div className=\"relative z-10 flex h-full w-full items-center justify-center\">\n          {children}\n        </div>\n      ) : null}\n    </div>\n  );\n};\n",
      "type": "registry:ui",
      "target": "components/ui/cloud-shader.tsx"
    }
  ],
  "author": "Manu Arora <hi@manuarora.in>",
  "title": "Cloud Shader"
}