{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "light-field",
  "type": "registry:component",
  "title": "Light field",
  "description": "A WebGL shader background of drifting color silk with a soft cursor light and grain. It renders at half resolution, pauses offscreen and adapts to dark mode.",
  "dependencies": [],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/premium/light-field.tsx",
      "type": "registry:ui",
      "target": "components/ui/light-field.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n/*\n * LightField: a living background drawn by a small WebGL shader. Colour\n * silk made of warped noise drifts slowly, the cursor carries a soft light\n * through it, and fine grain keeps it from looking digital. It renders at\n * half resolution, sleeps when off screen or in a background tab, and draws\n * a single still frame for reduced motion. Glass surfaces placed over it\n * frost it beautifully.\n */\n\nconst VERT = `attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}`\n\nconst FRAG = `precision highp float;\nuniform vec2 r;uniform float t;uniform vec2 m;uniform float dk;uniform vec3 c1;uniform vec3 c2;uniform vec3 c3;\nfloat h(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}\nfloat n(vec2 p){vec2 i=floor(p),f=fract(p);vec2 u=f*f*(3.-2.*f);\nreturn mix(mix(h(i),h(i+vec2(1,0)),u.x),mix(h(i+vec2(0,1)),h(i+vec2(1,1)),u.x),u.y);}\nfloat fbm(vec2 p){float v=0.,a=.5;mat2 R=mat2(.8,.6,-.6,.8);for(int i=0;i<5;i++){v+=a*n(p);p=R*p*2.02;a*=.5;}return v;}\nvoid main(){\n  vec2 uv=gl_FragCoord.xy/r;vec2 p=uv*vec2(r.x/r.y,1.)*1.6;\n  float tt=t*.045;\n  vec2 q=vec2(fbm(p+vec2(0.,tt)),fbm(p+vec2(5.2,1.3)-tt));\n  vec2 w=vec2(fbm(p+3.*q+vec2(1.7,9.2)+tt*1.3),fbm(p+3.*q+vec2(8.3,2.8)-tt));\n  float f=fbm(p+2.5*w);\n  vec3 col=mix(c1,c2,smoothstep(.2,.8,f));\n  col=mix(col,c3,smoothstep(.45,.95,length(w)*.9));\n  // silk highlights along the folds\n  float silk=pow(smoothstep(.55,.9,f),3.);\n  col+=silk*.35;\n  // the light\n  vec2 mm=m*vec2(r.x/r.y,1.)*1.6;float d=distance(p,mm);\n  float glow=exp(-d*d*2.2);\n  vec3 bg=mix(vec3(.985,.984,.99),vec3(.07,.07,.09),dk);\n  float amt=mix(.72,.8,dk)+glow*.3;\n  vec3 o=mix(bg,col,amt*smoothstep(1.05,.15,uv.y*.9+.1));\n  o+=glow*mix(.18,.22,dk)*mix(vec3(1.),c1,.35);\n  o+=(h(gl_FragCoord.xy+fract(t))-.5)*mix(.035,.05,dk);\n  gl_FragColor=vec4(o,1.);\n}`\n\ntype Palette = { c1: [number, number, number]; c2: [number, number, number]; c3: [number, number, number] }\nconst LIGHT: Palette = { c1: [0.66, 0.54, 1.0], c2: [0.55, 0.78, 1.0], c3: [1.0, 0.7, 0.86] }\nconst DARK: Palette = { c1: [0.42, 0.22, 0.95], c2: [0.1, 0.4, 0.9], c3: [0.8, 0.2, 0.6] }\n\ntype LightFieldProps = { className?: string; palette?: { light?: Palette; dark?: Palette } }\n\nfunction LightField({ className, palette }: LightFieldProps) {\n  const ref = React.useRef<HTMLCanvasElement>(null)\n  React.useEffect(() => {\n    const canvas = ref.current\n    if (!canvas) return\n    const gl = canvas.getContext(\"webgl\", { antialias: false, premultipliedAlpha: false, powerPreference: \"low-power\" })\n    if (!gl) return\n    const sh = (type: number, src: string) => {\n      const s = gl.createShader(type)!\n      gl.shaderSource(s, src)\n      gl.compileShader(s)\n      return s\n    }\n    const prog = gl.createProgram()!\n    gl.attachShader(prog, sh(gl.VERTEX_SHADER, VERT))\n    gl.attachShader(prog, sh(gl.FRAGMENT_SHADER, FRAG))\n    gl.linkProgram(prog)\n    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) return\n    gl.useProgram(prog)\n    const buf = gl.createBuffer()\n    gl.bindBuffer(gl.ARRAY_BUFFER, buf)\n    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW)\n    const loc = gl.getAttribLocation(prog, \"p\")\n    gl.enableVertexAttribArray(loc)\n    gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0)\n    const U = (k: string) => gl.getUniformLocation(prog, k)\n    const uR = U(\"r\"), uT = U(\"t\"), uM = U(\"m\"), uD = U(\"dk\"), u1 = U(\"c1\"), u2 = U(\"c2\"), u3 = U(\"c3\")\n\n    const reduce = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n    let raf = 0\n    let visible = true\n    const start = performance.now()\n    const mouse = { x: 0.3, y: 0.7, tx: 0.3, ty: 0.7 }\n    let dark = document.documentElement.classList.contains(\"dark\") ? 1 : 0\n\n    const resize = () => {\n      const scale = Math.min(window.devicePixelRatio || 1, 2) * 0.5\n      canvas.width = Math.max(1, Math.floor(canvas.clientWidth * scale))\n      canvas.height = Math.max(1, Math.floor(canvas.clientHeight * scale))\n      gl.viewport(0, 0, canvas.width, canvas.height)\n    }\n    const setPalette = () => {\n      const pal = dark ? (palette?.dark ?? DARK) : (palette?.light ?? LIGHT)\n      gl.uniform3fv(u1, pal.c1)\n      gl.uniform3fv(u2, pal.c2)\n      gl.uniform3fv(u3, pal.c3)\n      gl.uniform1f(uD, dark)\n    }\n    const draw = (now: number) => {\n      mouse.x += (mouse.tx - mouse.x) * 0.06\n      mouse.y += (mouse.ty - mouse.y) * 0.06\n      gl.uniform2f(uR, canvas.width, canvas.height)\n      gl.uniform1f(uT, reduce ? 12 : (now - start) / 1000)\n      gl.uniform2f(uM, mouse.x, mouse.y)\n      gl.drawArrays(gl.TRIANGLES, 0, 6)\n    }\n    const loop = (now: number) => {\n      draw(now)\n      if (visible && !document.hidden && !reduce) raf = requestAnimationFrame(loop)\n    }\n    const kick = () => {\n      cancelAnimationFrame(raf)\n      raf = requestAnimationFrame(loop)\n    }\n    const move = (e: PointerEvent) => {\n      const b = canvas.getBoundingClientRect()\n      mouse.tx = (e.clientX - b.left) / b.width\n      mouse.ty = 1 - (e.clientY - b.top) / b.height\n    }\n    const io = new IntersectionObserver(([e]) => {\n      visible = e.isIntersecting\n      if (visible) kick()\n    })\n    const mo = new MutationObserver(() => {\n      dark = document.documentElement.classList.contains(\"dark\") ? 1 : 0\n      setPalette()\n      kick()\n    })\n    const ro = new ResizeObserver(() => {\n      resize()\n      kick()\n    })\n    resize()\n    setPalette()\n    io.observe(canvas)\n    ro.observe(canvas)\n    mo.observe(document.documentElement, { attributes: true, attributeFilter: [\"class\"] })\n    window.addEventListener(\"pointermove\", move, { passive: true })\n    document.addEventListener(\"visibilitychange\", kick)\n    kick()\n    return () => {\n      cancelAnimationFrame(raf)\n      io.disconnect()\n      ro.disconnect()\n      mo.disconnect()\n      window.removeEventListener(\"pointermove\", move)\n      document.removeEventListener(\"visibilitychange\", kick)\n    }\n  }, [palette])\n  return <canvas ref={ref} data-slot=\"light-field\" aria-hidden className={cn(\"block size-full\", className)} />\n}\n\nexport { LightField }\nexport type { LightFieldProps }\n"
    }
  ]
}
