{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shader-weave",
  "title": "Shader Weave",
  "description": "A woven substrate of warp and weft threads crossing on a domain-warped grid, with a travelling sheen.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/shader-weave/index.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n  continueRender,\n  delayRender,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\n// A woven substrate — warp and weft threads crossing on a domain-warped grid,\n// interlacing over/under like real cloth, with a slow sheen of light travelling\n// across the fabric rather than the grid scrolling. The metaphor: the base a\n// component is woven from. Written for the remocn registry as a custom WebGL\n// shader (not a paper-design wrapper): frame-driven, deterministic, no\n// wall-clock time anywhere.\n//\n// `accent` / `accentAmount` run a coloured thread through the weave — the tint\n// lives mostly IN the threads, so raising `accentAmount` mid-video reads as a\n// violet thread lighting up across the whole cloth (a natural carrier for a\n// \"new base\" story). The grid itself never moves; only the light does.\n\nexport interface ShaderWeaveProps {\n  /** Palette: fabric-shadow, fabric-mid, fabric-warm, thread-highlight. 4 stops. */\n  colors?: [string, string, string, string];\n  /** Tint carried by the threads. */\n  accent?: string;\n  /** 0..1 — how much accent the threads take. */\n  accentAmount?: number;\n  /** Thread density across the frame height. */\n  scale?: number;\n  /** Domain-warp amount — how hand-woven the threads bend (fractions of frame). */\n  warp?: number;\n  /** Global time multiplier (drives only the sheen, never the grid position). */\n  speed?: number;\n  className?: string;\n}\n\nconst VERT = `\nattribute vec2 a_pos;\nvoid main() {\n  gl_Position = vec4(a_pos, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `\nprecision highp float;\n\nuniform vec2 u_res;\nuniform float u_time;\nuniform float u_scale;\nuniform float u_warp;\nuniform vec3 u_c0;\nuniform vec3 u_c1;\nuniform vec3 u_c2;\nuniform vec3 u_c3;\nuniform vec3 u_accent;\nuniform float u_accentAmt;\n\nfloat hash(vec2 p) {\n  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);\n}\n\nfloat noise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  vec2 u = f * f * (3.0 - 2.0 * f);\n  return mix(\n    mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x),\n    mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),\n    u.y\n  );\n}\n\nfloat fbm(vec2 p) {\n  float v = 0.0;\n  float a = 0.5;\n  for (int i = 0; i < 4; i++) {\n    v += a * noise(p);\n    p *= 2.03;\n    a *= 0.5;\n  }\n  return v;\n}\n\n// A thin ridge line at every integer of x — the profile of one thread.\nfloat thread(float x, float sharp) {\n  float f = fract(x);\n  float d = min(f, 1.0 - f);\n  return smoothstep(sharp, 0.0, d);\n}\n\nvoid main() {\n  vec2 uv = gl_FragCoord.xy / u_res;\n  float aspect = u_res.x / u_res.y;\n\n  // Domain warp — bend the weave so it reads hand-made, not a CSS grid. The\n  // warp field drifts very slowly; the grid lattice itself stays put.\n  vec2 q = uv + u_warp * vec2(\n    fbm(uv * 3.0 + vec2(u_time * 0.03, 0.0)) - 0.5,\n    fbm(uv * 3.0 + vec2(7.3, -u_time * 0.02)) - 0.5\n  );\n\n  float qx = q.x * u_scale * aspect;\n  float qy = q.y * u_scale;\n\n  float tw = thread(qx, 0.09); // warp (vertical threads)\n  float tf = thread(qy, 0.09); // weft (horizontal threads)\n\n  // Interlace: per cell, one direction sits over the other.\n  float over = mod(floor(qx) + floor(qy), 2.0);\n  float warpB = tw * (over < 0.5 ? 1.0 : 0.5);\n  float weftB = tf * (over < 0.5 ? 0.5 : 1.0);\n  float weave = max(warpB, weftB);\n\n  // Sheen — light travelling across a still cloth (this is the only motion).\n  float sheen = fbm(q * 1.7 + vec2(u_time * 0.05, u_time * 0.028));\n  weave *= 0.3 + 0.95 * sheen;\n\n  // Fabric ground: near-black, faintly breathing between the shadow stops.\n  vec3 base = mix(u_c0, u_c1, smoothstep(0.15, 0.8, sheen));\n  base = mix(base, u_c2, smoothstep(0.62, 1.0, sheen) * 0.5);\n\n  // Threads sit a touch above the ground.\n  vec3 col = mix(base, u_c3, clamp(weave, 0.0, 1.0) * 0.55);\n\n  // The accent runs through the threads — a violet thread across the cloth.\n  float amt = clamp(u_accentAmt, 0.0, 1.0);\n  col = mix(col, u_accent, amt * (0.08 + 0.34 * weave));\n  col += u_accent * amt * 0.015;\n\n  // A whisper of living grain, time-derived so it stays deterministic.\n  col += (hash(gl_FragCoord.xy + floor(u_time * 9.0)) - 0.5) * 0.012;\n\n  gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n  const h = hex.replace(\"#\", \"\");\n  const v =\n    h.length === 3\n      ? h\n          .split(\"\")\n          .map((c) => c + c)\n          .join(\"\")\n      : h;\n  const n = parseInt(v, 16);\n  return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n};\n\ntype GlState = {\n  gl: WebGLRenderingContext;\n  uniforms: Record<string, WebGLUniformLocation | null>;\n};\n\nexport function ShaderWeave({\n  colors = [\"#050506\", \"#0a0a0c\", \"#111015\", \"#1b1a22\"],\n  accent = \"#6733FF\",\n  accentAmount = 0,\n  scale = 22,\n  warp = 0.03,\n  speed = 1,\n  className,\n}: ShaderWeaveProps) {\n  const frame = useCurrentFrame();\n  const { fps, width, height } = useVideoConfig();\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const stateRef = useRef<GlState | null>(null);\n  const [handle] = useState(() => delayRender(\"shader-weave\"));\n  const continuedRef = useRef(false);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas || stateRef.current) return;\n    const gl = canvas.getContext(\"webgl\", {\n      antialias: false,\n      preserveDrawingBuffer: true,\n    });\n    if (!gl) return;\n\n    const compile = (type: number, src: string) => {\n      const shader = gl.createShader(type);\n      if (!shader) throw new Error(\"Failed to create WebGL shader\");\n      gl.shaderSource(shader, src);\n      gl.compileShader(shader);\n      return shader;\n    };\n    const program = gl.createProgram();\n    if (!program) return;\n    gl.attachShader(program, compile(gl.VERTEX_SHADER, VERT));\n    gl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAG));\n    gl.linkProgram(program);\n    // biome-ignore lint/correctness/useHookAtTopLevel: gl.useProgram is a WebGL method, not a React hook\n    gl.useProgram(program);\n\n    const buf = gl.createBuffer();\n    gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n    gl.bufferData(\n      gl.ARRAY_BUFFER,\n      new Float32Array([-1, -1, 3, -1, -1, 3]),\n      gl.STATIC_DRAW,\n    );\n    const loc = gl.getAttribLocation(program, \"a_pos\");\n    gl.enableVertexAttribArray(loc);\n    gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);\n\n    const uniforms: GlState[\"uniforms\"] = {};\n    for (const name of [\n      \"u_res\",\n      \"u_time\",\n      \"u_scale\",\n      \"u_warp\",\n      \"u_c0\",\n      \"u_c1\",\n      \"u_c2\",\n      \"u_c3\",\n      \"u_accent\",\n      \"u_accentAmt\",\n    ]) {\n      uniforms[name] = gl.getUniformLocation(program, name);\n    }\n    stateRef.current = { gl, uniforms };\n  }, []);\n\n  useEffect(() => {\n    const state = stateRef.current;\n    const canvas = canvasRef.current;\n    if (!state || !canvas) return;\n    const { gl, uniforms } = state;\n\n    gl.viewport(0, 0, canvas.width, canvas.height);\n    gl.uniform2f(uniforms.u_res, canvas.width, canvas.height);\n    gl.uniform1f(uniforms.u_time, (frame / fps) * speed);\n    gl.uniform1f(uniforms.u_scale, scale);\n    gl.uniform1f(uniforms.u_warp, warp);\n    gl.uniform3fv(uniforms.u_c0, hexToRgb(colors[0]));\n    gl.uniform3fv(uniforms.u_c1, hexToRgb(colors[1]));\n    gl.uniform3fv(uniforms.u_c2, hexToRgb(colors[2]));\n    gl.uniform3fv(uniforms.u_c3, hexToRgb(colors[3]));\n    gl.uniform3fv(uniforms.u_accent, hexToRgb(accent));\n    gl.uniform1f(uniforms.u_accentAmt, accentAmount);\n    gl.drawArrays(gl.TRIANGLES, 0, 3);\n\n    if (!continuedRef.current) {\n      continuedRef.current = true;\n      requestAnimationFrame(() =>\n        requestAnimationFrame(() => continueRender(handle)),\n      );\n    }\n  }, [frame, fps, speed, scale, warp, colors, accent, accentAmount, handle]);\n\n  return (\n    <div className={className} style={{ position: \"absolute\", inset: 0 }}>\n      <canvas\n        ref={canvasRef}\n        width={width}\n        height={height}\n        style={{ width: \"100%\", height: \"100%\", display: \"block\" }}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/shader-weave.tsx"
    }
  ],
  "type": "registry:component"
}
