{
  "name": "drape-background",
  "type": "registry:component",
  "dependencies": [],
  "files": [
    {
      "type": "registry:component",
      "content": "\"use client\";\n\n/**\n * @author: @dorianbaffier\n * @description: Drape Background\n * @version: 1.0.0\n * @date: 2026-10-14\n * @license: MIT\n * @website: https://kokonutui.com\n * @github: https://github.com/kokonut-labs/kokonutui\n */\n\nimport { type ReactNode, useEffect, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface DrapeBackgroundProps {\n  className?: string;\n  /** Content shown over the curtain. Pass `null` for a bare background. */\n  children?: ReactNode;\n  /** Strength of the breeze. 0 lets the curtain hang still. */\n  wind?: number;\n  /** Depth of the pleats along the top, in px. */\n  pleat?: number;\n  /** Push the fabric with the cursor and grab it to drag it around. */\n  interactive?: boolean;\n}\n\n// Simulation, in px and seconds, stepped at a fixed rate so it behaves the\n// same at any refresh rate.\nconst STEP = 1 / 60;\nconst GRAVITY = 980;\nconst DAMPING = 0.986;\nconst ITERATIONS = 4;\n// Roughly this many columns across the surface, never finer than MIN_CELL.\nconst COLUMNS = 46;\nconst MIN_CELL = 16;\n// The top edge is gathered onto a rod: pins sit closer than the cloth\n// between them, and the slack falls into folds.\nconst GATHER = 0.8;\nconst PLEAT_PERIOD = 4;\n// How far the hand reaches into the cloth, and how hard it pushes.\nconst HAND_RADIUS = 120;\nconst HAND_PUSH = 7;\nconst HAND_DRAG = 2.4;\n// Stiffness of the skip-one links that keep folds round instead of creased.\nconst BEND = 0.35;\n// Perspective: distance from the eye to the screen plane, in px.\nconst FOCAL = 1400;\n// Below this much movement per step, with no wind, the cloth is asleep.\nconst REST = 0.02;\n\ninterface Cloth {\n  cols: number;\n  rows: number;\n  rest: number;\n  pos: Float32Array;\n  prev: Float32Array;\n  pinned: Uint8Array;\n  pins: Float32Array;\n  links: Uint32Array;\n  lengths: Float32Array;\n  stiffness: Float32Array;\n}\n\n/**\n * Links between neighbours hold the weave; skip-one links give it a little\n * body, so it folds in curves instead of creasing.\n */\nfunction createLinks(cols: number, rows: number, rest: number) {\n  const links: number[] = [];\n  const lengths: number[] = [];\n  const stiffness: number[] = [];\n  const link = (a: number, b: number, length: number, stiff: number) => {\n    links.push(a, b);\n    lengths.push(length);\n    stiffness.push(stiff);\n  };\n  for (let j = 0; j < rows; j++) {\n    for (let i = 0; i < cols; i++) {\n      const a = j * cols + i;\n      if (i + 1 < cols && j > 0) {\n        link(a, a + 1, rest, 1);\n      }\n      if (j + 1 < rows) {\n        link(a, a + cols, rest, 1);\n      }\n      if (i + 2 < cols && j > 0) {\n        link(a, a + 2, rest * 2, BEND);\n      }\n      if (j + 2 < rows) {\n        link(a, a + cols * 2, rest * 2, BEND);\n      }\n    }\n  }\n  return { links, lengths, stiffness };\n}\n\nfunction createCloth(width: number, height: number, pleat: number): Cloth {\n  const rest = Math.max(MIN_CELL, (width * 1.16) / COLUMNS / GATHER);\n  const spacing = rest * GATHER;\n  const cols = Math.ceil((width * 1.16) / spacing) + 1;\n  const rows = Math.ceil((height * 1.12) / rest) + 1;\n  const left = (width - (cols - 1) * spacing) / 2;\n  const top = -rest * 0.5;\n  const count = cols * rows;\n  const pos = new Float32Array(count * 3);\n  const pinned = new Uint8Array(count);\n  const pins = new Float32Array(cols * 3);\n\n  // Folds a little uneven, as a hand-gathered curtain hangs.\n  const folds = Array.from({ length: cols }, (_, i) => {\n    let phase = 0;\n    for (let c = 0; c < i; c++) {\n      phase += ((Math.PI * 2) / PLEAT_PERIOD) * (1 + 0.3 * Math.sin(c * 0.9));\n    }\n    return pleat * Math.sin(phase) * (0.8 + 0.2 * Math.cos(i * 0.37));\n  });\n  for (let j = 0; j < rows; j++) {\n    for (let i = 0; i < cols; i++) {\n      const k = (j * cols + i) * 3;\n      const z = folds[i];\n      pos[k] = left + i * spacing;\n      pos[k + 1] = top + j * rest;\n      pos[k + 2] = z;\n      if (j === 0) {\n        pinned[i] = 1;\n        pins.set([pos[k], pos[k + 1], z], i * 3);\n      }\n    }\n  }\n\n  const { links, lengths, stiffness } = createLinks(cols, rows, rest);\n\n  return {\n    cols,\n    rows,\n    rest,\n    pos,\n    prev: pos.slice(),\n    pinned,\n    pins,\n    links: Uint32Array.from(links),\n    lengths: Float32Array.from(lengths),\n    stiffness: Float32Array.from(stiffness),\n  };\n}\n\ninterface Forces {\n  time: number;\n  wind: number;\n  hand: { x: number; y: number; vx: number; vy: number; on: boolean };\n  grab: { index: number; x: number; y: number } | null;\n}\n\nconst accel: [number, number, number] = [0, 0, 0];\n\n/** Gravity, the breeze and the hand, acting on a point at (x, y). */\nfunction accelerate(x: number, y: number, forces: Forces) {\n  const { time, wind, hand } = forces;\n  accel[0] = 0;\n  accel[1] = GRAVITY;\n  accel[2] = 0;\n  if (wind > 0) {\n    // Gusts: slow sine waves that roll across the curtain and swell.\n    const gust =\n      Math.sin(x * 0.006 + time * 0.9) * 0.6 +\n      Math.sin(x * 0.013 - time * 1.7 + y * 0.004) * 0.4;\n    const swell = 0.6 + 0.4 * Math.sin(time * 0.35);\n    accel[2] += gust * swell * wind * 260 * (y / 600);\n    accel[0] += Math.sin(time * 0.5 + y * 0.003) * wind * 30;\n  }\n  if (hand.on) {\n    const dx = x - hand.x;\n    const dy = y - hand.y;\n    const d2 = dx * dx + dy * dy;\n    if (d2 < HAND_RADIUS * HAND_RADIUS) {\n      const fall = 1 - Math.sqrt(d2) / HAND_RADIUS;\n      const f = fall * fall;\n      // Press the fabric back, and drag it a little along with the hand.\n      accel[2] += f * HAND_PUSH * 1000;\n      accel[0] += f * hand.vx * HAND_DRAG * 60;\n      accel[1] += f * hand.vy * HAND_DRAG * 60;\n    }\n  }\n  return accel;\n}\n\n/** Verlet step: move every free point under its forces. */\nfunction integrate(cloth: Cloth, forces: Forces) {\n  const { pos, prev, pinned } = cloth;\n  const dt2 = STEP * STEP;\n  let motion = 0;\n  for (let n = 0; n < pinned.length; n++) {\n    if (pinned[n]) {\n      continue;\n    }\n    const k = n * 3;\n    const x = pos[k];\n    const y = pos[k + 1];\n    const z = pos[k + 2];\n    const [ax, ay, az] = accelerate(x, y, forces);\n    const vx = (x - prev[k]) * DAMPING;\n    const vy = (y - prev[k + 1]) * DAMPING;\n    const vz = (z - prev[k + 2]) * DAMPING;\n    prev[k] = x;\n    prev[k + 1] = y;\n    prev[k + 2] = z;\n    pos[k] = x + vx + ax * dt2;\n    pos[k + 1] = y + vy + ay * dt2;\n    pos[k + 2] = z + vz + az * dt2;\n    motion += Math.abs(vx) + Math.abs(vy) + Math.abs(vz);\n  }\n  return motion / pinned.length;\n}\n\nfunction relax(cloth: Cloth, grab: Forces[\"grab\"]) {\n  const { pos, pinned, links, lengths, stiffness, pins } = cloth;\n  for (let it = 0; it < ITERATIONS; it++) {\n    for (let l = 0; l < lengths.length; l++) {\n      const a = links[l * 2];\n      const b = links[l * 2 + 1];\n      const ka = a * 3;\n      const kb = b * 3;\n      const dx = pos[kb] - pos[ka];\n      const dy = pos[kb + 1] - pos[ka + 1];\n      const dz = pos[kb + 2] - pos[ka + 2];\n      const d = Math.sqrt(dx * dx + dy * dy + dz * dz) || 1;\n      const diff = (d - lengths[l]) / d;\n      // Bend links only resist being squeezed, so folds stay soft.\n      if (stiffness[l] < 1 && diff > 0) {\n        continue;\n      }\n      // Pinned points don't move; links between two pins aren't made.\n      const wa = 1 - pinned[a];\n      const wb = 1 - pinned[b];\n      const s = (diff * stiffness[l]) / (wa + wb);\n      pos[ka] += dx * s * wa;\n      pos[ka + 1] += dy * s * wa;\n      pos[ka + 2] += dz * s * wa;\n      pos[kb] -= dx * s * wb;\n      pos[kb + 1] -= dy * s * wb;\n      pos[kb + 2] -= dz * s * wb;\n    }\n    if (grab) {\n      const k = grab.index * 3;\n      pos[k] = grab.x;\n      pos[k + 1] = grab.y;\n    }\n    for (let i = 0; i < cloth.cols; i++) {\n      pos.set(pins.subarray(i * 3, i * 3 + 3), i * 3);\n    }\n  }\n}\n\n/** Interleaved position and normal for every point, for the GPU. */\nfunction writeVertices(cloth: Cloth, out: Float32Array) {\n  const { cols, rows, pos } = cloth;\n  for (let j = 0; j < rows; j++) {\n    for (let i = 0; i < cols; i++) {\n      const n = j * cols + i;\n      const l = (j * cols + Math.max(i - 1, 0)) * 3;\n      const r = (j * cols + Math.min(i + 1, cols - 1)) * 3;\n      const u = (Math.max(j - 1, 0) * cols + i) * 3;\n      const d = (Math.min(j + 1, rows - 1) * cols + i) * 3;\n      const ax = pos[r] - pos[l];\n      const ay = pos[r + 1] - pos[l + 1];\n      const az = pos[r + 2] - pos[l + 2];\n      const bx = pos[d] - pos[u];\n      const by = pos[d + 1] - pos[u + 1];\n      const bz = pos[d + 2] - pos[u + 2];\n      // Down × across points out of the screen, towards the viewer.\n      let nx = by * az - bz * ay;\n      let ny = bz * ax - bx * az;\n      let nz = bx * ay - by * ax;\n      const len = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;\n      nx /= len;\n      ny /= len;\n      nz /= len;\n      const o = n * 6;\n      out[o] = pos[n * 3];\n      out[o + 1] = pos[n * 3 + 1];\n      out[o + 2] = pos[n * 3 + 2];\n      out[o + 3] = nx;\n      out[o + 4] = ny;\n      out[o + 5] = nz;\n    }\n  }\n}\n\nconst VERTEX = `\nattribute vec3 aPos;\nattribute vec3 aNormal;\nuniform vec2 uSize;\nuniform float uFocal;\nvarying vec3 vNormal;\nvoid main() {\n  float s = uFocal / (uFocal + aPos.z);\n  vec2 p = uSize * 0.5 + (aPos.xy - uSize * 0.5) * s;\n  gl_Position = vec4(p.x / uSize.x * 2.0 - 1.0, 1.0 - p.y / uSize.y * 2.0, clamp(aPos.z / 4000.0, -1.0, 1.0), 1.0);\n  vNormal = aNormal;\n}`;\n\n// Soft wrapped light from the top left and a satin sheen, blended between\n// two theme colors so the curtain sits in the page in light and dark mode.\nconst FRAGMENT = `\nprecision mediump float;\nuniform vec3 uLit;\nuniform vec3 uShade;\nvarying vec3 vNormal;\nvoid main() {\n  vec3 n = normalize(vNormal);\n  if (n.z > 0.0) n = -n;\n  vec3 light = normalize(vec3(-0.45, -0.55, -0.7));\n  float diffuse = max(dot(n, light), 0.0);\n  float wrap = diffuse * 0.8 + 0.2;\n  float sheen = pow(max(dot(reflect(-light, n), vec3(0.0, 0.0, -1.0)), 0.0), 18.0);\n  float t = clamp(wrap * wrap + sheen * 0.15, 0.0, 1.0);\n  gl_FragColor = vec4(mix(uShade, uLit, t), 1.0);\n}`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, source: string) {\n  const shader = gl.createShader(type);\n  if (!shader) {\n    return null;\n  }\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n  return shader;\n}\n\nfunction createRenderer(gl: WebGLRenderingContext) {\n  const program = gl.createProgram();\n  const vs = compile(gl, gl.VERTEX_SHADER, VERTEX);\n  const fs = compile(gl, gl.FRAGMENT_SHADER, FRAGMENT);\n  if (!(program && vs && fs)) {\n    return null;\n  }\n  gl.attachShader(program, vs);\n  gl.attachShader(program, fs);\n  gl.linkProgram(program);\n  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n    return null;\n  }\n  // biome-ignore lint/correctness/useHookAtTopLevel: a WebGL call, not a React hook.\n  gl.useProgram(program);\n  const vertexBuffer = gl.createBuffer();\n  const indexBuffer = gl.createBuffer();\n  const aPos = gl.getAttribLocation(program, \"aPos\");\n  const aNormal = gl.getAttribLocation(program, \"aNormal\");\n  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);\n  gl.enableVertexAttribArray(aPos);\n  gl.enableVertexAttribArray(aNormal);\n  gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 24, 0);\n  gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 24, 12);\n  gl.enable(gl.DEPTH_TEST);\n  gl.depthFunc(gl.LEQUAL);\n  return {\n    uSize: gl.getUniformLocation(program, \"uSize\"),\n    uFocal: gl.getUniformLocation(program, \"uFocal\"),\n    uLit: gl.getUniformLocation(program, \"uLit\"),\n    uShade: gl.getUniformLocation(program, \"uShade\"),\n    setMesh(cols: number, rows: number) {\n      const indices = new Uint16Array((cols - 1) * (rows - 1) * 6);\n      let o = 0;\n      for (let j = 0; j < rows - 1; j++) {\n        for (let i = 0; i < cols - 1; i++) {\n          const a = j * cols + i;\n          indices.set([a, a + 1, a + cols, a + 1, a + cols + 1, a + cols], o);\n          o += 6;\n        }\n      }\n      gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);\n      return indices.length;\n    },\n    destroy() {\n      gl.deleteBuffer(vertexBuffer);\n      gl.deleteBuffer(indexBuffer);\n      gl.deleteProgram(program);\n      gl.deleteShader(vs);\n      gl.deleteShader(fs);\n    },\n  };\n}\n\n/** Any CSS color (oklch included) as 0-1 RGB, via a 1px canvas. */\nfunction toRgb(color: string, probe: CanvasRenderingContext2D) {\n  probe.clearRect(0, 0, 1, 1);\n  probe.fillStyle = color;\n  probe.fillRect(0, 0, 1, 1);\n  const [r, g, b] = probe.getImageData(0, 0, 1, 1).data;\n  return [r / 255, g / 255, b / 255] as const;\n}\n\nexport default function DrapeBackground({\n  className,\n  children,\n  wind = 1,\n  pleat = 34,\n  interactive = true,\n}: DrapeBackgroundProps) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const litRef = useRef<HTMLSpanElement>(null);\n  const shadeRef = useRef<HTMLSpanElement>(null);\n\n  useEffect(() => {\n    const root = rootRef.current;\n    const canvas = canvasRef.current;\n    const gl = canvas?.getContext(\"webgl\", { antialias: true });\n    const probe = document\n      .createElement(\"canvas\")\n      .getContext(\"2d\", { willReadFrequently: true });\n    if (!(root && canvas && gl && probe)) {\n      return;\n    }\n    const renderer = createRenderer(gl);\n    if (!renderer) {\n      return;\n    }\n\n    const reduceMotion = window.matchMedia(\n      \"(prefers-reduced-motion: reduce)\"\n    ).matches;\n    const forces: Forces = {\n      time: 0,\n      wind: reduceMotion ? 0 : wind,\n      hand: { x: 0, y: 0, vx: 0, vy: 0, on: false },\n      grab: null,\n    };\n\n    let width = 0;\n    let height = 0;\n    let cloth: Cloth | null = null;\n    let vertices = new Float32Array(0);\n    let indexCount = 0;\n    let raf = 0;\n    let last = 0;\n    let carry = 0;\n    let quiet = 0;\n    let visible = true;\n    let colorKey = \"\";\n\n    const readColors = () => {\n      const lit = getComputedStyle(litRef.current as Element).color;\n      const shade = getComputedStyle(shadeRef.current as Element).color;\n      if (`${lit}|${shade}` === colorKey) {\n        return false;\n      }\n      colorKey = `${lit}|${shade}`;\n      gl.uniform3fv(renderer.uLit, toRgb(lit, probe));\n      gl.uniform3fv(renderer.uShade, toRgb(shade, probe));\n      return true;\n    };\n\n    const draw = () => {\n      if (!cloth) {\n        return;\n      }\n      writeVertices(cloth, vertices);\n      gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);\n      // biome-ignore lint/suspicious/noBitwiseOperators: WebGL clear flags.\n      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n      gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_SHORT, 0);\n    };\n\n    const build = () => {\n      const rect = canvas.getBoundingClientRect();\n      const dpr = Math.min(window.devicePixelRatio || 1, 2);\n      width = rect.width;\n      height = rect.height;\n      if (width < 2 || height < 2) {\n        return;\n      }\n      canvas.width = Math.round(width * dpr);\n      canvas.height = Math.round(height * dpr);\n      gl.viewport(0, 0, canvas.width, canvas.height);\n      gl.uniform2f(renderer.uSize, width, height);\n      gl.uniform1f(renderer.uFocal, FOCAL);\n      cloth = createCloth(width, height, pleat);\n      vertices = new Float32Array(cloth.cols * cloth.rows * 6);\n      indexCount = renderer.setMesh(cloth.cols, cloth.rows);\n      // Let it fall and settle before the first frame, so it opens hanging.\n      const settleForces = { ...forces, hand: { ...forces.hand, on: false } };\n      for (let i = 0; i < 180; i++) {\n        settleForces.time = forces.time + i * STEP;\n        integrate(cloth, settleForces);\n        relax(cloth, null);\n      }\n      draw();\n    };\n\n    const frame = (now: number) => {\n      raf = 0;\n      if (!(visible && cloth)) {\n        return;\n      }\n      carry += last ? Math.min((now - last) / 1000, 0.1) : STEP;\n      last = now;\n      let moved = 0;\n      let steps = 0;\n      while (carry >= STEP && steps < 3) {\n        forces.time += STEP;\n        moved = integrate(cloth, forces);\n        relax(cloth, forces.grab);\n        carry -= STEP;\n        steps++;\n      }\n      carry = Math.min(carry, STEP);\n      forces.hand.vx *= 0.8;\n      forces.hand.vy *= 0.8;\n      draw();\n      quiet = moved < REST ? quiet + 1 : 0;\n      const awake =\n        forces.wind > 0 || forces.hand.on || forces.grab || quiet < 30;\n      if (awake) {\n        raf = requestAnimationFrame(frame);\n      }\n    };\n\n    const start = () => {\n      if (!raf && visible && !reduceMotion) {\n        last = 0;\n        quiet = 0;\n        raf = requestAnimationFrame(frame);\n      }\n    };\n\n    readColors();\n    gl.clearColor(0, 0, 0, 0);\n    build();\n    canvas.style.opacity = \"1\";\n    start();\n\n    const resizeObserver = new ResizeObserver(() => {\n      build();\n      start();\n    });\n    resizeObserver.observe(canvas);\n\n    const intersection = new IntersectionObserver(([entry]) => {\n      visible = entry.isIntersecting && document.visibilityState === \"visible\";\n      start();\n    });\n    intersection.observe(root);\n    const onVisibility = () => {\n      visible = document.visibilityState === \"visible\";\n      start();\n    };\n    document.addEventListener(\"visibilitychange\", onVisibility);\n\n    // Follow light and dark mode without asking the app for a theme.\n    const colorTimer = window.setInterval(() => {\n      if (readColors() && !raf) {\n        draw();\n      }\n    }, 500);\n\n    const local = (event: PointerEvent) => {\n      const rect = canvas.getBoundingClientRect();\n      return { x: event.clientX - rect.left, y: event.clientY - rect.top };\n    };\n\n    /** The point drawn nearest the pointer, within reach. */\n    const nearest = (x: number, y: number) => {\n      if (!cloth) {\n        return -1;\n      }\n      let best = -1;\n      let bestD = 70 * 70;\n      for (let n = cloth.cols; n < cloth.pinned.length; n++) {\n        const k = n * 3;\n        const s = FOCAL / (FOCAL + cloth.pos[k + 2]);\n        const px = width / 2 + (cloth.pos[k] - width / 2) * s;\n        const py = height / 2 + (cloth.pos[k + 1] - height / 2) * s;\n        const d = (px - x) ** 2 + (py - y) ** 2;\n        if (d < bestD) {\n          bestD = d;\n          best = n;\n        }\n      }\n      return best;\n    };\n\n    const onMove = (event: PointerEvent) => {\n      const p = local(event);\n      const hand = forces.hand;\n      if (forces.grab) {\n        forces.grab.x = p.x;\n        forces.grab.y = p.y;\n      } else if (event.pointerType === \"mouse\") {\n        if (hand.on) {\n          hand.vx = hand.vx * 0.5 + (p.x - hand.x) * 0.5;\n          hand.vy = hand.vy * 0.5 + (p.y - hand.y) * 0.5;\n        }\n        hand.on = true;\n      }\n      hand.x = p.x;\n      hand.y = p.y;\n      start();\n    };\n    const onLeave = () => {\n      forces.hand.on = false;\n    };\n    const onDown = (event: PointerEvent) => {\n      // Clicks on the content (links, buttons) are not meant for the cloth.\n      if ((event.target as HTMLElement).closest(\"a, button, input, textarea\")) {\n        return;\n      }\n      const p = local(event);\n      const index = nearest(p.x, p.y);\n      if (index < 0) {\n        return;\n      }\n      event.preventDefault();\n      root.setPointerCapture(event.pointerId);\n      forces.grab = { index, ...p };\n      forces.hand.on = false;\n      root.dataset.grabbing = \"\";\n      start();\n    };\n    const onUp = () => {\n      forces.grab = null;\n      delete root.dataset.grabbing;\n    };\n    if (interactive && !reduceMotion) {\n      root.addEventListener(\"pointermove\", onMove);\n      root.addEventListener(\"pointerleave\", onLeave);\n      root.addEventListener(\"pointerdown\", onDown);\n      root.addEventListener(\"pointerup\", onUp);\n      root.addEventListener(\"pointercancel\", onUp);\n    }\n\n    return () => {\n      cancelAnimationFrame(raf);\n      resizeObserver.disconnect();\n      intersection.disconnect();\n      document.removeEventListener(\"visibilitychange\", onVisibility);\n      window.clearInterval(colorTimer);\n      root.removeEventListener(\"pointermove\", onMove);\n      root.removeEventListener(\"pointerleave\", onLeave);\n      root.removeEventListener(\"pointerdown\", onDown);\n      root.removeEventListener(\"pointerup\", onUp);\n      root.removeEventListener(\"pointercancel\", onUp);\n      renderer.destroy();\n    };\n  }, [wind, pleat, interactive]);\n\n  return (\n    <div\n      className={cn(\n        \"relative flex min-h-[560px] w-full touch-pan-y items-center justify-center overflow-hidden bg-zinc-100 text-zinc-900 data-[grabbing]:cursor-grabbing dark:bg-zinc-950 dark:text-zinc-100\",\n        className\n      )}\n      ref={rootRef}\n    >\n      {/* Theme colors for the shader: the lit face and the deepest fold. */}\n      <span className=\"hidden text-white dark:text-zinc-800\" ref={litRef} />\n      <span className=\"hidden text-zinc-300 dark:text-black\" ref={shadeRef} />\n      <div aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0\">\n        <canvas\n          className=\"h-full w-full opacity-0 transition-opacity duration-700 ease-out\"\n          ref={canvasRef}\n        />\n      </div>\n      {children === undefined ? <DefaultContent /> : children}\n    </div>\n  );\n}\n\nfunction DefaultContent() {\n  return (\n    <div className=\"pointer-events-none relative flex max-w-xl flex-col items-center gap-4 px-6 text-center\">\n      <h1 className=\"font-semibold text-4xl tracking-tight sm:text-5xl\">\n        Behind the curtain\n      </h1>\n      <p className=\"max-w-sm text-base text-zinc-500 dark:text-zinc-400\">\n        A pleated curtain in a light breeze. Sweep the cursor through it, or\n        grab a fold and pull.\n      </p>\n    </div>\n  );\n}\n",
      "path": "/components/kokonutui/drape-background.tsx",
      "target": "components/kokonutui/drape-background.tsx"
    }
  ]
}