{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "retro-grid",
  "type": "registry:ui",
  "title": "Retro Grid",
  "description": "An animated scrolling retro grid effect",
  "files": [
    {
      "path": "registry/magicui/retro-grid.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport type { CSSProperties, HTMLAttributes } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ANIMATION_DURATION_SECONDS = 15\nconst GRID_HEIGHT_RATIO = 3\nconst GRID_LINE_ALIGNMENT_OFFSET_PX = 0.5\nconst GRID_LINE_ANTIALIAS_MULTIPLIER = 0.9\nconst GRID_LINE_WIDTH_PX = 0.92\nconst GRID_START_OFFSET_RATIO = -0.5\nconst GRID_WIDTH_RATIO = 6\nconst GRID_X_OFFSET_RATIO = -2\nconst MAX_ANGLE = 89\nconst MAX_DEVICE_PIXEL_RATIO = 2\nconst MIN_ANGLE = 1\nconst PERSPECTIVE_PX = 200\nconst FALLBACK_ANIMATION_NAME = \"retro-grid-fallback-scroll\"\nconst FALLBACK_STYLES = `\n@keyframes ${FALLBACK_ANIMATION_NAME} {\n  from {\n    transform: translateY(-50%);\n  }\n\n  to {\n    transform: translateY(0);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  [data-retro-grid-scroll=\"true\"] {\n    animation: none !important;\n    transform: translateY(-50%) !important;\n  }\n}\n`\n\nconst VERTEX_SHADER_SOURCE = `\nattribute vec2 a_position;\n\nvoid main() {\n  gl_Position = vec4(a_position, 0.0, 1.0);\n}\n`\n\nconst FRAGMENT_SHADER_SOURCE = `\n#extension GL_OES_standard_derivatives : enable\nprecision highp float;\n\nuniform vec2 u_container_size;\nuniform vec2 u_viewport_size;\nuniform vec4 u_line_color;\nuniform float u_angle;\nuniform float u_cell_size;\nuniform float u_device_pixel_ratio;\nuniform float u_time;\n\nconst float animationDurationSeconds = ${ANIMATION_DURATION_SECONDS.toFixed(1)};\nconst float gridHeightRatio = ${GRID_HEIGHT_RATIO.toFixed(1)};\nconst float gridStartOffsetRatio = ${GRID_START_OFFSET_RATIO.toFixed(1)};\nconst float gridWidthRatio = ${GRID_WIDTH_RATIO.toFixed(1)};\nconst float gridXOffsetRatio = ${GRID_X_OFFSET_RATIO.toFixed(1)};\nconst float gridLineAlignmentOffsetPx = ${GRID_LINE_ALIGNMENT_OFFSET_PX.toFixed(1)};\nconst float gridLineAntialiasMultiplier = ${GRID_LINE_ANTIALIAS_MULTIPLIER.toFixed(1)};\nconst float horizontalLodLevelOneEndPx = 5.6;\nconst float horizontalLodLevelOneStartPx = 2.8;\nconst float horizontalLodLevelTwoEndPx = 3.0;\nconst float horizontalLodLevelTwoStartPx = 1.4;\nconst float horizontalCompressionEndPx = 2.8;\nconst float horizontalCompressionStartPx = 1.2;\nconst float lineWidthPx = ${GRID_LINE_WIDTH_PX.toFixed(2)};\nconst float perspectivePx = ${PERSPECTIVE_PX.toFixed(1)};\nconst float gridTravelRatio = 0.5;\nconst float verticalCompressionEndPx = 2.6;\nconst float verticalCompressionStartPx = 1.0;\nconst float verticalEdgeCompressionEnd = 0.95;\nconst float verticalEdgeCompressionStart = 0.45;\nconst float verticalLodLevelEnd = 0.64;\nconst float verticalLodLevelStart = 0.22;\nconst float verticalTopCompressionEndCells = 6.0;\nconst float verticalTopCompressionStartCells = 2.0;\n\nfloat renderGridLine(\n  float wrappedCoord,\n  float antiAliasWidth,\n  float softnessBoost\n) {\n  return 1.0 - smoothstep(\n    lineWidthPx,\n    lineWidthPx + (antiAliasWidth * (1.5 + softnessBoost)),\n    wrappedCoord\n  );\n}\n\nvoid main() {\n  float angle = radians(clamp(u_angle, 1.0, 89.0));\n  float sinAngle = sin(angle);\n  float cosAngle = cos(angle);\n  vec2 screen = vec2(\n    (gl_FragCoord.x / u_device_pixel_ratio) - (u_container_size.x * 0.5),\n    (u_container_size.y * 0.5) - (gl_FragCoord.y / u_device_pixel_ratio)\n  );\n\n  vec3 rayOrigin = vec3(0.0, 0.0, perspectivePx);\n  vec3 rayDirection = normalize(vec3(screen, -perspectivePx));\n  vec3 planeXAxis = vec3(1.0, 0.0, 0.0);\n  vec3 planeYAxis = vec3(0.0, cosAngle, sinAngle);\n  vec3 planeNormal = normalize(cross(planeXAxis, planeYAxis));\n  float denominator = dot(rayDirection, planeNormal);\n\n  if (abs(denominator) < 0.0001) {\n    discard;\n  }\n\n  float distanceToPlane = dot(-rayOrigin, planeNormal) / denominator;\n\n  if (distanceToPlane <= 0.0) {\n    discard;\n  }\n\n  vec3 hitPoint = rayOrigin + (rayDirection * distanceToPlane);\n  float localX = hitPoint.x;\n  float localY = dot(hitPoint, planeYAxis);\n  float gridWidth = u_viewport_size.x * gridWidthRatio;\n  float gridHeight = u_viewport_size.y * gridHeightRatio;\n  float gridScrollSpeed = (gridHeight * gridTravelRatio) / animationDurationSeconds;\n  float patternOffsetY = u_time * gridScrollSpeed;\n  float gridLeft = (-0.5 * u_container_size.x) + (gridXOffsetRatio * u_container_size.x);\n  float gridTop = (-0.5 * u_container_size.y) + (gridStartOffsetRatio * gridHeight);\n  vec2 planePosition = vec2(localX - gridLeft, localY - gridTop);\n\n  if (\n    planePosition.x < 0.0 ||\n    planePosition.y < 0.0 ||\n    planePosition.x > gridWidth ||\n    planePosition.y > gridHeight\n  ) {\n    discard;\n  }\n\n  vec2 patternPosition = vec2(planePosition.x, planePosition.y - patternOffsetY);\n  vec2 wrapped = mod(\n    patternPosition + vec2(gridLineAlignmentOffsetPx),\n    u_cell_size\n  );\n  vec2 patternDerivative = max(fwidth(patternPosition), vec2(0.0001));\n  vec2 antiAliasWidth = patternDerivative * gridLineAntialiasMultiplier;\n  float horizontalCellSpanPx = u_cell_size / patternDerivative.y;\n  float horizontalCompression = 1.0 - smoothstep(\n    horizontalCompressionStartPx,\n    horizontalCompressionEndPx,\n    horizontalCellSpanPx\n  );\n  float verticalCellSpanPx = u_cell_size / patternDerivative.x;\n  float sideDistance = abs((planePosition.x / gridWidth) * 2.0 - 1.0);\n  float verticalEdgeCompression = smoothstep(\n    verticalEdgeCompressionStart,\n    verticalEdgeCompressionEnd,\n    sideDistance\n  );\n  float verticalTopCompression = 1.0 - smoothstep(\n    u_cell_size * verticalTopCompressionStartCells,\n    u_cell_size * verticalTopCompressionEndCells,\n    planePosition.y\n  );\n  float verticalCompression =\n    (1.0 - smoothstep(\n      verticalCompressionStartPx,\n      verticalCompressionEndPx,\n      verticalCellSpanPx\n    )) * verticalEdgeCompression * verticalTopCompression;\n  float horizontalSoftnessBoost = 1.0 + (horizontalCompression * 3.0);\n  float verticalSoftnessBoost = 1.0 + (verticalCompression * 3.5);\n  float verticalLod = smoothstep(\n    verticalLodLevelStart,\n    verticalLodLevelEnd,\n    verticalCompression\n  );\n  float verticalLineFine = renderGridLine(\n    wrapped.x,\n    antiAliasWidth.x,\n    verticalSoftnessBoost\n  );\n  float verticalWrappedLod = mod(\n    patternPosition.x + gridLineAlignmentOffsetPx,\n    u_cell_size * 2.0\n  );\n  float verticalLineCoarse = renderGridLine(\n    verticalWrappedLod,\n    antiAliasWidth.x,\n    verticalSoftnessBoost + verticalLod\n  );\n  float verticalLine = max(\n    verticalLineFine * (1.0 - verticalLod),\n    verticalLineCoarse * verticalLod\n  );\n  float horizontalLodLevelOne = 1.0 - smoothstep(\n    horizontalLodLevelOneStartPx,\n    horizontalLodLevelOneEndPx,\n    horizontalCellSpanPx\n  );\n  float horizontalLodLevelTwo = 1.0 - smoothstep(\n    horizontalLodLevelTwoStartPx,\n    horizontalLodLevelTwoEndPx,\n    horizontalCellSpanPx\n  );\n  float horizontalLineFine = renderGridLine(\n    wrapped.y,\n    antiAliasWidth.y,\n    horizontalSoftnessBoost\n  );\n  float horizontalWrappedLodOne = mod(\n    patternPosition.y + gridLineAlignmentOffsetPx,\n    u_cell_size * 2.0\n  );\n  float horizontalWrappedLodTwo = mod(\n    patternPosition.y + gridLineAlignmentOffsetPx,\n    u_cell_size * 4.0\n  );\n  float horizontalLineCoarse = renderGridLine(\n    horizontalWrappedLodOne,\n    antiAliasWidth.y,\n    horizontalSoftnessBoost + horizontalLodLevelOne\n  );\n  float horizontalLineExtraCoarse = renderGridLine(\n    horizontalWrappedLodTwo,\n    antiAliasWidth.y,\n    horizontalSoftnessBoost + horizontalLodLevelOne + horizontalLodLevelTwo\n  );\n  float horizontalLineReduced = max(\n    horizontalLineFine * (1.0 - horizontalLodLevelOne),\n    horizontalLineCoarse * horizontalLodLevelOne\n  );\n  float horizontalLine = max(\n    horizontalLineReduced * (1.0 - horizontalLodLevelTwo),\n    horizontalLineExtraCoarse * horizontalLodLevelTwo\n  );\n  float line = max(verticalLine, horizontalLine);\n\n  if (line <= 0.001) {\n    discard;\n  }\n\n  float alpha = u_line_color.a * line;\n  gl_FragColor = vec4(u_line_color.rgb * alpha, alpha);\n}\n`\n\ninterface RetroGridProps extends HTMLAttributes<HTMLDivElement> {\n  /**\n   * Additional CSS classes to apply to the grid container\n   */\n  className?: string\n  /**\n   * Rotation angle of the grid in degrees\n   * @default 65\n   */\n  angle?: number\n  /**\n   * Grid cell size in pixels\n   * @default 60\n   */\n  cellSize?: number\n  /**\n   * Grid opacity value between 0 and 1\n   * @default 0.5\n   */\n  opacity?: number\n  /**\n   * Grid line color in light mode\n   * @default \"gray\"\n   */\n  lightLineColor?: string\n  /**\n   * Grid line color in dark mode\n   * @default \"gray\"\n   */\n  darkLineColor?: string\n}\n\ninterface ProgramInfo {\n  attributeLocation: number\n  program: WebGLProgram\n  uniforms: {\n    angle: WebGLUniformLocation\n    cellSize: WebGLUniformLocation\n    containerSize: WebGLUniformLocation\n    devicePixelRatio: WebGLUniformLocation\n    lineColor: WebGLUniformLocation\n    time: WebGLUniformLocation\n    viewportSize: WebGLUniformLocation\n  }\n}\n\nlet colorResolveContext: CanvasRenderingContext2D | null | undefined\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(Math.max(value, min), max)\n}\n\nfunction createShader(gl: WebGLRenderingContext, type: number, source: string) {\n  const shader = gl.createShader(type)\n\n  if (!shader) {\n    return null\n  }\n\n  gl.shaderSource(shader, source)\n  gl.compileShader(shader)\n\n  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n    return shader\n  }\n\n  gl.deleteShader(shader)\n  return null\n}\n\nfunction createProgram(gl: WebGLRenderingContext) {\n  const vertexShader = createShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER_SOURCE)\n  const fragmentShader = createShader(\n    gl,\n    gl.FRAGMENT_SHADER,\n    FRAGMENT_SHADER_SOURCE\n  )\n\n  if (!vertexShader || !fragmentShader) {\n    return null\n  }\n\n  const program = gl.createProgram()\n\n  if (!program) {\n    gl.deleteShader(vertexShader)\n    gl.deleteShader(fragmentShader)\n    return null\n  }\n\n  gl.attachShader(program, vertexShader)\n  gl.attachShader(program, fragmentShader)\n  gl.linkProgram(program)\n  gl.deleteShader(vertexShader)\n  gl.deleteShader(fragmentShader)\n\n  if (gl.getProgramParameter(program, gl.LINK_STATUS)) {\n    return program\n  }\n\n  gl.deleteProgram(program)\n  return null\n}\n\nfunction getProgramInfo(\n  gl: WebGLRenderingContext,\n  program: WebGLProgram\n): ProgramInfo | null {\n  const attributeLocation = gl.getAttribLocation(program, \"a_position\")\n  const angle = gl.getUniformLocation(program, \"u_angle\")\n  const cellSize = gl.getUniformLocation(program, \"u_cell_size\")\n  const containerSize = gl.getUniformLocation(program, \"u_container_size\")\n  const devicePixelRatio = gl.getUniformLocation(\n    program,\n    \"u_device_pixel_ratio\"\n  )\n  const lineColor = gl.getUniformLocation(program, \"u_line_color\")\n  const time = gl.getUniformLocation(program, \"u_time\")\n  const viewportSize = gl.getUniformLocation(program, \"u_viewport_size\")\n\n  if (\n    attributeLocation < 0 ||\n    !angle ||\n    !cellSize ||\n    !containerSize ||\n    !devicePixelRatio ||\n    !lineColor ||\n    !time ||\n    !viewportSize\n  ) {\n    return null\n  }\n\n  return {\n    attributeLocation,\n    program,\n    uniforms: {\n      angle,\n      cellSize,\n      containerSize,\n      devicePixelRatio,\n      lineColor,\n      time,\n      viewportSize,\n    },\n  }\n}\n\nfunction isDarkMode(colorScheme: MediaQueryList) {\n  const root = document.documentElement\n\n  if (root.classList.contains(\"dark\")) {\n    return true\n  }\n\n  if (root.classList.contains(\"light\")) {\n    return false\n  }\n\n  return colorScheme.matches\n}\n\nfunction getColorResolveContext() {\n  if (colorResolveContext !== undefined) {\n    return colorResolveContext\n  }\n\n  const canvas = document.createElement(\"canvas\")\n  canvas.width = 1\n  canvas.height = 1\n  colorResolveContext = canvas.getContext(\"2d\", {\n    willReadFrequently: true,\n  })\n\n  return colorResolveContext\n}\n\nfunction resolveLineColor(color: string, element: HTMLElement) {\n  const resolver = document.createElement(\"span\")\n  resolver.style.color = color\n  resolver.style.opacity = \"0\"\n  resolver.style.pointerEvents = \"none\"\n  resolver.style.position = \"absolute\"\n  element.appendChild(resolver)\n\n  const resolvedColor = getComputedStyle(resolver).color\n  resolver.remove()\n  const context = getColorResolveContext()\n\n  if (!context) {\n    return new Float32Array([0.5, 0.5, 0.5, 1])\n  }\n\n  context.clearRect(0, 0, 1, 1)\n  context.fillStyle = resolvedColor\n  context.fillRect(0, 0, 1, 1)\n  const pixel = context.getImageData(0, 0, 1, 1).data\n\n  return new Float32Array([\n    pixel[0] / 255,\n    pixel[1] / 255,\n    pixel[2] / 255,\n    pixel[3] / 255,\n  ])\n}\n\nfunction createFallbackGridStyle(\n  cellSize: number,\n  lineColor: string\n): CSSProperties {\n  return {\n    animation: `${FALLBACK_ANIMATION_NAME} ${ANIMATION_DURATION_SECONDS}s linear infinite`,\n    backgroundImage: `linear-gradient(to right, ${lineColor} 1px, transparent 0), linear-gradient(to bottom, ${lineColor} 1px, transparent 0)`,\n    backgroundRepeat: \"repeat\",\n    backgroundSize: `${cellSize}px ${cellSize}px`,\n    transform: \"translateY(-50%)\",\n  }\n}\n\nexport function RetroGrid({\n  className,\n  angle = 65,\n  cellSize = 60,\n  opacity = 0.5,\n  lightLineColor = \"gray\",\n  darkLineColor = \"gray\",\n  style,\n  ...props\n}: RetroGridProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null)\n  const containerRef = useRef<HTMLDivElement>(null)\n  const [isWebGlReady, setIsWebGlReady] = useState(false)\n  const angleRef = useRef(angle)\n  const cellSizeRef = useRef(cellSize)\n  const darkLineColorRef = useRef(darkLineColor)\n  const lightLineColorRef = useRef(lightLineColor)\n  const syncSceneRef = useRef<(() => void) | null>(null)\n\n  useEffect(() => {\n    angleRef.current = angle\n    cellSizeRef.current = cellSize\n    darkLineColorRef.current = darkLineColor\n    lightLineColorRef.current = lightLineColor\n    syncSceneRef.current?.()\n  }, [angle, cellSize, darkLineColor, lightLineColor])\n\n  useEffect(() => {\n    const canvas = canvasRef.current\n    const container = containerRef.current\n\n    if (!canvas || !container) {\n      return\n    }\n\n    const reducedMotion = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n    const colorScheme = window.matchMedia(\"(prefers-color-scheme: dark)\")\n\n    let animationFrameId: number | null = null\n    let currentWidth = 0\n    let currentHeight = 0\n    let currentDevicePixelRatio = 1\n    let gl: WebGLRenderingContext | null = null\n    let isVisible = true\n    let isContextLost = false\n    let lineColor = resolveLineColor(lightLineColorRef.current, container)\n    let positionBuffer: WebGLBuffer | null = null\n    let programInfo: ProgramInfo | null = null\n\n    const getContext = () => {\n      const nextGl = canvas.getContext(\"webgl\", {\n        alpha: true,\n        antialias: true,\n        premultipliedAlpha: true,\n      })\n\n      if (!nextGl || !nextGl.getExtension(\"OES_standard_derivatives\")) {\n        return null\n      }\n\n      return nextGl\n    }\n\n    const releasePipeline = (shouldDeleteResources: boolean) => {\n      if (shouldDeleteResources && gl) {\n        if (positionBuffer) {\n          gl.deleteBuffer(positionBuffer)\n        }\n\n        if (programInfo) {\n          gl.deleteProgram(programInfo.program)\n        }\n      }\n\n      positionBuffer = null\n      programInfo = null\n\n      if (shouldDeleteResources) {\n        gl = null\n      }\n    }\n\n    const initializePipeline = () => {\n      const nextGl = getContext()\n\n      if (!nextGl) {\n        releasePipeline(false)\n        return false\n      }\n\n      gl = nextGl\n      releasePipeline(true)\n      gl = nextGl\n\n      const program = createProgram(nextGl)\n\n      if (!program) {\n        return false\n      }\n\n      const nextProgramInfo = getProgramInfo(nextGl, program)\n\n      if (!nextProgramInfo) {\n        nextGl.deleteProgram(program)\n        return false\n      }\n\n      const nextPositionBuffer = nextGl.createBuffer()\n\n      if (!nextPositionBuffer) {\n        nextGl.deleteProgram(program)\n        return false\n      }\n\n      nextGl.bindBuffer(nextGl.ARRAY_BUFFER, nextPositionBuffer)\n      nextGl.bufferData(\n        nextGl.ARRAY_BUFFER,\n        new Float32Array([-1, -1, 3, -1, -1, 3]),\n        nextGl.STATIC_DRAW\n      )\n\n      positionBuffer = nextPositionBuffer\n      programInfo = nextProgramInfo\n\n      return true\n    }\n\n    const updateLineColor = () => {\n      const activeColor = isDarkMode(colorScheme)\n        ? darkLineColorRef.current\n        : lightLineColorRef.current\n      lineColor = resolveLineColor(activeColor, container)\n    }\n\n    const resizeCanvas = () => {\n      currentWidth = Math.floor(container.clientWidth)\n      currentHeight = Math.floor(container.clientHeight)\n\n      if (currentWidth === 0 || currentHeight === 0 || !gl) {\n        return\n      }\n\n      currentDevicePixelRatio = Math.min(\n        window.devicePixelRatio || 1,\n        MAX_DEVICE_PIXEL_RATIO\n      )\n\n      canvas.width = Math.floor(currentWidth * currentDevicePixelRatio)\n      canvas.height = Math.floor(currentHeight * currentDevicePixelRatio)\n      canvas.style.width = `${currentWidth}px`\n      canvas.style.height = `${currentHeight}px`\n      gl.viewport(0, 0, canvas.width, canvas.height)\n    }\n\n    const draw = (timestamp: number) => {\n      if (\n        currentWidth === 0 ||\n        currentHeight === 0 ||\n        !gl ||\n        !positionBuffer ||\n        !programInfo ||\n        isContextLost\n      ) {\n        return\n      }\n\n      gl.useProgram(programInfo.program)\n      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)\n      gl.enableVertexAttribArray(programInfo.attributeLocation)\n      gl.vertexAttribPointer(\n        programInfo.attributeLocation,\n        2,\n        gl.FLOAT,\n        false,\n        0,\n        0\n      )\n      gl.clearColor(0, 0, 0, 0)\n      gl.clear(gl.COLOR_BUFFER_BIT)\n      gl.uniform1f(\n        programInfo.uniforms.angle,\n        clamp(angleRef.current, MIN_ANGLE, MAX_ANGLE)\n      )\n      gl.uniform1f(\n        programInfo.uniforms.cellSize,\n        Math.max(cellSizeRef.current, 1)\n      )\n      gl.uniform2f(\n        programInfo.uniforms.containerSize,\n        currentWidth,\n        currentHeight\n      )\n      gl.uniform1f(\n        programInfo.uniforms.devicePixelRatio,\n        currentDevicePixelRatio\n      )\n      gl.uniform4fv(programInfo.uniforms.lineColor, lineColor)\n      gl.uniform1f(\n        programInfo.uniforms.time,\n        reducedMotion.matches ? 0 : timestamp / 1000\n      )\n      gl.uniform2f(\n        programInfo.uniforms.viewportSize,\n        window.innerWidth,\n        window.innerHeight\n      )\n      gl.drawArrays(gl.TRIANGLES, 0, 3)\n    }\n\n    const stopAnimation = () => {\n      if (animationFrameId !== null) {\n        cancelAnimationFrame(animationFrameId)\n        animationFrameId = null\n      }\n    }\n\n    const frame = (timestamp: number) => {\n      draw(timestamp)\n\n      if (!reducedMotion.matches && isVisible) {\n        animationFrameId = requestAnimationFrame(frame)\n        return\n      }\n\n      animationFrameId = null\n    }\n\n    const syncScene = () => {\n      if (isContextLost) {\n        stopAnimation()\n        setIsWebGlReady(false)\n        return\n      }\n\n      if (!gl || !positionBuffer || !programInfo) {\n        if (!initializePipeline()) {\n          stopAnimation()\n          setIsWebGlReady(false)\n          return\n        }\n      }\n\n      resizeCanvas()\n\n      if (currentWidth === 0 || currentHeight === 0) {\n        stopAnimation()\n        return\n      }\n\n      updateLineColor()\n      draw(performance.now())\n      setIsWebGlReady(true)\n\n      if (reducedMotion.matches || !isVisible) {\n        stopAnimation()\n        return\n      }\n\n      if (animationFrameId === null) {\n        animationFrameId = requestAnimationFrame(frame)\n      }\n    }\n\n    syncSceneRef.current = syncScene\n\n    const resizeObserver = new ResizeObserver(() => {\n      syncScene()\n    })\n    resizeObserver.observe(container)\n\n    const handleWindowResize = () => {\n      syncScene()\n    }\n\n    const intersectionObserver = new IntersectionObserver(([entry]) => {\n      isVisible = entry?.isIntersecting ?? false\n\n      if (isVisible) {\n        syncScene()\n        return\n      }\n\n      stopAnimation()\n    })\n    intersectionObserver.observe(container)\n\n    const themeObserver = new MutationObserver(() => {\n      syncScene()\n    })\n    themeObserver.observe(document.documentElement, {\n      attributeFilter: [\"class\"],\n      attributes: true,\n    })\n\n    const handleMotionChange = () => {\n      syncScene()\n    }\n\n    const handleColorSchemeChange = () => {\n      syncScene()\n    }\n\n    const handleContextLost = (event: Event) => {\n      event.preventDefault()\n      isContextLost = true\n      stopAnimation()\n      releasePipeline(false)\n      setIsWebGlReady(false)\n    }\n\n    const handleContextRestored = () => {\n      isContextLost = false\n      syncScene()\n    }\n\n    reducedMotion.addEventListener(\"change\", handleMotionChange)\n    colorScheme.addEventListener(\"change\", handleColorSchemeChange)\n    window.addEventListener(\"resize\", handleWindowResize)\n    canvas.addEventListener(\"webglcontextlost\", handleContextLost)\n    canvas.addEventListener(\"webglcontextrestored\", handleContextRestored)\n\n    syncScene()\n\n    return () => {\n      stopAnimation()\n      resizeObserver.disconnect()\n      intersectionObserver.disconnect()\n      themeObserver.disconnect()\n      reducedMotion.removeEventListener(\"change\", handleMotionChange)\n      colorScheme.removeEventListener(\"change\", handleColorSchemeChange)\n      window.removeEventListener(\"resize\", handleWindowResize)\n      canvas.removeEventListener(\"webglcontextlost\", handleContextLost)\n      canvas.removeEventListener(\"webglcontextrestored\", handleContextRestored)\n      syncSceneRef.current = null\n      releasePipeline(!isContextLost)\n    }\n  }, [])\n\n  const gridStyles = {\n    ...style,\n    opacity,\n  } as CSSProperties\n  const normalizedAngle = clamp(angle, MIN_ANGLE, MAX_ANGLE)\n  const normalizedCellSize = Math.max(cellSize, 1)\n  const fallbackProjectionStyles = {\n    perspective: `${PERSPECTIVE_PX}px`,\n  } as CSSProperties\n  const fallbackRotationStyles = {\n    transform: `rotateX(${normalizedAngle}deg)`,\n  } as CSSProperties\n  const lightFallbackGridStyles = createFallbackGridStyle(\n    normalizedCellSize,\n    lightLineColor\n  )\n  const darkFallbackGridStyles = createFallbackGridStyle(\n    normalizedCellSize,\n    darkLineColor\n  )\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(\n        \"pointer-events-none absolute size-full overflow-hidden\",\n        className\n      )}\n      style={gridStyles}\n      {...props}\n    >\n      <style>{FALLBACK_STYLES}</style>\n      {!isWebGlReady ? (\n        <div className=\"absolute inset-0\" style={fallbackProjectionStyles}>\n          <div className=\"absolute inset-0\" style={fallbackRotationStyles}>\n            <div\n              data-retro-grid-scroll=\"true\"\n              className=\"absolute inset-[0%_0px] ml-[-200%] h-[300vh] w-[600vw] origin-[100%_0_0] dark:hidden\"\n              style={lightFallbackGridStyles}\n            />\n            <div\n              data-retro-grid-scroll=\"true\"\n              className=\"absolute inset-[0%_0px] ml-[-200%] hidden h-[300vh] w-[600vw] origin-[100%_0_0] dark:block\"\n              style={darkFallbackGridStyles}\n            />\n          </div>\n        </div>\n      ) : null}\n      <canvas\n        ref={canvasRef}\n        className={cn(\n          \"absolute inset-0 size-full\",\n          isWebGlReady ? \"opacity-100\" : \"opacity-0\"\n        )}\n      />\n      <div className=\"absolute inset-0 bg-linear-to-t from-white to-transparent to-90% dark:from-black\" />\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}