{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "GlassIcons-JS-TW",
	"title": "GlassIcons",
	"description": "Liquid glass icons that bend the color behind them, for app grids, docks and social links.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "GlassIcons/GlassIcons.jsx",
			"content": "'use client';\n\nimport { useEffect, useId, useLayoutEffect, useRef, useState } from 'react';\n\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\nconst INTRO_HIDE = 'group-data-[intro]/root:opacity-0';\nconst ROOT =\n  'group/root relative isolate mx-auto box-border flex flex-wrap justify-center [gap:var(--glass-icons-row-gap)_var(--glass-icons-gap)] max-w-[calc(var(--glass-icons-columns)*var(--glass-icons-size)+(var(--glass-icons-columns)-1)*var(--glass-icons-gap))]';\nconst DEFS = 'pointer-events-none absolute h-0 w-0 overflow-hidden';\nconst CANVAS = 'pointer-events-none absolute z-[1] block';\nconst ICON =\n  'group/icon relative m-0 h-[var(--glass-icons-size)] w-[var(--glass-icons-size)] flex-none cursor-pointer border-0 bg-transparent p-0 text-inherit no-underline outline-none [font:inherit] [-webkit-tap-highlight-color:transparent]';\nconst PLATE_BASE = `absolute z-0 will-change-[transform,opacity] ${INTRO_HIDE}`;\nconst PLATE_SHAPES = {\n  tilt: `${PLATE_BASE} inset-0 origin-bottom-right rounded-[var(--glass-icons-radius)] [transform:rotate(var(--glass-icons-tilt))]`,\n  offset: `${PLATE_BASE} inset-0 rounded-[var(--glass-icons-radius)] [transform:translate(calc(var(--glass-icons-spread)*26%),calc(var(--glass-icons-spread)*-26%))]`,\n  circle: `${PLATE_BASE} inset-[9%] rounded-full [transform:translate(calc(var(--glass-icons-spread)*29%),calc(var(--glass-icons-spread)*-29%))]`\n};\nconst GLASS = `absolute inset-0 z-0 overflow-hidden rounded-[var(--glass-icons-radius)] [background:linear-gradient(var(--glass-icons-tint),var(--glass-icons-tint))] [box-shadow:0_12px_24px_-14px_var(--glass-icons-drop)] will-change-[transform,opacity] ${INTRO_HIDE}`;\nconst SHADE =\n  'pointer-events-none absolute inset-0 rounded-[inherit] bg-[length:100%_100%] opacity-[var(--glass-icons-shade)]';\nconst SHINE =\n  'pointer-events-none absolute inset-0 rounded-[inherit] bg-[length:100%_100%] opacity-[var(--glass-icons-shine)]';\nconst FACE = `pointer-events-none absolute inset-0 z-[2] grid place-items-center rounded-[var(--glass-icons-radius)] will-change-[transform,opacity] group-focus-visible/icon:outline-2 group-focus-visible/icon:outline-offset-4 group-focus-visible/icon:outline-[var(--glass-icons-label)] ${INTRO_HIDE}`;\nconst GLYPH =\n  'grid h-[40%] w-[40%] place-items-center text-[var(--glass-icons-ink)] [filter:drop-shadow(0_1px_3px_var(--glass-icons-glyph-shadow))] [&>svg]:h-full [&>svg]:w-full';\nconst LABEL_BASE =\n  'pointer-events-none absolute left-1/2 top-[calc(100%+10px)] z-[2] whitespace-nowrap text-[13px] font-medium leading-[1.2] text-[var(--glass-icons-label)] [transition:opacity_0.3s_cubic-bezier(0.22,1,0.36,1),transform_0.3s_cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none';\nconst LABEL_ALWAYS = `${LABEL_BASE} [transform:translateX(-50%)] ${INTRO_HIDE}`;\nconst LABEL_HOVER = `${LABEL_BASE} opacity-0 [transform:translate(-50%,-4px)] group-hover/icon:opacity-100 group-hover/icon:[transform:translate(-50%,0)] group-focus-visible/icon:opacity-100 group-focus-visible/icon:[transform:translate(-50%,0)]`;\n\nconst GRADIENTS = {\n  blue: 'linear-gradient(135deg, hsl(214, 95%, 66%), hsl(226, 80%, 50%))',\n  purple: 'linear-gradient(135deg, hsl(276, 84%, 70%), hsl(262, 66%, 52%))',\n  red: 'linear-gradient(135deg, hsl(354, 95%, 68%), hsl(342, 80%, 52%))',\n  indigo: 'linear-gradient(135deg, hsl(236, 90%, 72%), hsl(246, 64%, 54%))',\n  orange: 'linear-gradient(135deg, hsl(38, 100%, 62%), hsl(20, 94%, 54%))',\n  green: 'linear-gradient(135deg, hsl(146, 66%, 54%), hsl(162, 82%, 34%))',\n  teal: 'linear-gradient(135deg, hsl(184, 80%, 56%), hsl(196, 84%, 40%))',\n  pink: 'linear-gradient(135deg, hsl(326, 92%, 74%), hsl(314, 70%, 54%))',\n  graphite: 'linear-gradient(135deg, hsl(250, 6%, 46%), hsl(250, 8%, 22%))',\n  silver: 'linear-gradient(135deg, hsl(240, 6%, 92%), hsl(240, 5%, 70%))'\n};\n\nconst THEMES = {\n  dark: {\n    '--glass-icons-label': 'rgba(244, 244, 245, 0.86)',\n    '--glass-icons-edge': 'rgba(255, 255, 255, 0.16)',\n    '--glass-icons-fallback': 'rgba(255, 255, 255, 0.1)',\n    '--glass-icons-drop': 'rgba(0, 0, 0, 0.45)',\n    '--glass-icons-shade': '0.35'\n  },\n  light: {\n    '--glass-icons-label': 'rgba(24, 24, 27, 0.78)',\n    '--glass-icons-edge': 'rgba(24, 24, 27, 0.08)',\n    '--glass-icons-fallback': 'rgba(255, 255, 255, 0.32)',\n    '--glass-icons-drop': 'rgba(24, 24, 27, 0.28)',\n    '--glass-icons-shade': '0.22'\n  }\n};\n\nconst PLATES = ['tilt', 'offset', 'circle', 'none'];\nconst HOVERS = ['lift', 'float', 'press', 'none'];\nconst LABELS = ['hover', 'always', 'none'];\nconst PLATE_CODES = { none: 0, tilt: 1, offset: 2, circle: 3 };\nconst SIDES = { top: 0, right: 90, bottom: 180, left: 270 };\nconst STAGGER = 0.07;\nconst MAPS = new Map();\n\nconst VERTEX = `#version 300 es\nin vec2 aCorner;\nuniform vec2 uCanvas;\nuniform vec2 uCenter;\nuniform float uExtent;\nout vec2 vLocal;\nvoid main() {\n  vLocal = aCorner * uExtent;\n  vec2 position = (uCenter + vLocal) / uCanvas * 2.0 - 1.0;\n  gl_Position = vec4(position.x, -position.y, 0.0, 1.0);\n}`;\n\nconst FRAGMENT = `#version 300 es\nprecision highp float;\nin vec2 vLocal;\nout vec4 outColor;\nuniform float uSize;\nuniform float uRadius;\nuniform float uBevel;\nuniform float uStrength;\nuniform float uSplit;\nuniform float uSoft;\nuniform float uShine;\nuniform float uShade;\nuniform vec4 uTint;\nuniform vec2 uLight;\nuniform int uPlate;\nuniform vec2 uOrigin;\nuniform mat2 uTurn;\nuniform vec2 uShift;\nuniform vec2 uOffset;\nuniform float uScale;\nuniform float uDisc;\nuniform float uRow;\nuniform vec2 uDirection;\nuniform float uSpan;\nuniform sampler2D uGradients;\nuniform vec2 uLift;\nuniform float uGlassScale;\nuniform float uGlassAlpha;\nuniform float uPlateAlpha;\nuniform float uPixel;\n\nfloat roundBox(vec2 p, float reach, float radius) {\n  vec2 q = abs(p) - vec2(reach - radius);\n  return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;\n}\n\nvec2 roundNormal(vec2 p, float reach, float radius) {\n  vec2 q = abs(p) - vec2(reach - radius);\n  vec2 s = vec2(p.x < 0.0 ? -1.0 : 1.0, p.y < 0.0 ? -1.0 : 1.0);\n  if (q.x > 0.0 && q.y > 0.0) return normalize(q) * s;\n  return q.x > q.y ? vec2(s.x, 0.0) : vec2(0.0, s.y);\n}\n\nvec4 plate(vec2 p, float soft) {\n  if (uPlate == 0) return vec4(0.0);\n  vec2 q = uTurn * ((p - uOrigin - uShift) / uScale) + uOrigin - uOffset;\n  float d = uPlate == 3 ? length(q) - uDisc : roundBox(q, uSize * 0.5, uRadius);\n  float a = clamp(0.5 - d / soft, 0.0, 1.0);\n  if (a <= 0.0) return vec4(0.0);\n  float t = clamp(dot(q, uDirection) / uSpan + 0.5, 0.0, 1.0);\n  return vec4(texture(uGradients, vec2(t, uRow)).rgb * a, a);\n}\n\nvoid main() {\n  vec2 p = vLocal;\n  float reach = uSize * 0.5;\n  vec2 g = (p - uLift) / uGlassScale;\n  float dg = roundBox(g, reach, uRadius);\n  float glass = clamp(0.5 - dg * uGlassScale / uPixel, 0.0, 1.0) * uGlassAlpha;\n\n  vec4 color = plate(p, uPixel) * uPlateAlpha;\n  if (color.a > 0.0) {\n    float drop = roundBox(g - vec2(0.0, uSize * 0.1), reach, uRadius);\n    color.rgb *= 1.0 - (1.0 - smoothstep(-uSize * 0.12, uSize * 0.14, drop)) * 0.28 * uGlassAlpha;\n  }\n\n  if (glass > 0.0) {\n    float inside = max(0.0, -dg);\n    float t = 1.0 - clamp(inside / uBevel, 0.0, 1.0);\n    vec2 n = roundNormal(g, reach, uRadius);\n    vec2 bend = n * pow(t, 2.2);\n    vec4 seen = vec4(0.0);\n    if (uPlate != 0) {\n      vec4 red = plate(p - bend * (uStrength + uSplit), uSoft);\n      vec4 green = plate(p - bend * uStrength, uSoft);\n      vec4 blue = plate(p - bend * (uStrength - uSplit), uSoft);\n      seen = vec4(red.r, green.g, blue.b, green.a) * uPlateAlpha;\n      float luma = dot(seen.rgb, vec3(0.2126, 0.7152, 0.0722));\n      seen.rgb = clamp(mix(vec3(luma), seen.rgb, 1.25) * 1.04, 0.0, 1.0);\n      seen.rgb = min(mix(seen.rgb, uTint.rgb * seen.a, uTint.a), vec3(seen.a));\n    }\n    float facing = dot(n, uLight);\n    float rim = clamp(inside / 1.2, 0.0, 1.0);\n    float shade = (max(-facing, 0.0) * 0.75 + 0.25) * t * t * rim * uShade;\n    float light = clamp((max(facing, 0.0) * 0.95 + max(-facing, 0.0) * 0.35) * t * t * t * rim * uShine, 0.0, 1.0);\n    seen = seen * (1.0 - shade) + vec4(0.0, 0.0, 0.0, shade);\n    seen = seen * (1.0 - light) + vec4(light);\n    color = mix(color, seen, glass);\n  }\n  outColor = color;\n}`;\n\nconst UNIFORMS = [\n  'uCanvas',\n  'uCenter',\n  'uExtent',\n  'uSize',\n  'uRadius',\n  'uBevel',\n  'uStrength',\n  'uSplit',\n  'uSoft',\n  'uShine',\n  'uShade',\n  'uTint',\n  'uLight',\n  'uPlate',\n  'uOrigin',\n  'uTurn',\n  'uShift',\n  'uOffset',\n  'uScale',\n  'uDisc',\n  'uRow',\n  'uDirection',\n  'uSpan',\n  'uGradients',\n  'uLift',\n  'uGlassScale',\n  'uGlassAlpha',\n  'uPlateAlpha',\n  'uPixel'\n];\n\nconst clamp = (value, min, max) => Math.min(max, Math.max(min, value));\nconst ease = t => 1 - Math.pow(1 - clamp(t, 0, 1), 3);\nconst fade = (from, to, t) => {\n  const x = clamp((t - from) / (to - from), 0, 1);\n  return x * x * (3 - 2 * x);\n};\n\nconst drive = (value, velocity, target, stiffness, damping, dt) => {\n  const next = velocity + (stiffness * (target - value) - damping * velocity) * dt;\n  return [value + next * dt, next];\n};\n\nconst isDark = color => {\n  const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(color).trim())?.[1];\n  if (!hex) return false;\n  const full = hex.length === 3 ? [...hex].map(c => c + c).join('') : hex;\n  const [r, g, b] = [0, 2, 4].map(i => parseInt(full.slice(i, i + 2), 16) / 255);\n  return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.5;\n};\n\nconst splitTopLevel = text => {\n  const parts = [];\n  let depth = 0;\n  let start = 0;\n  for (let i = 0; i < text.length; i++) {\n    if (text[i] === '(') depth++;\n    else if (text[i] === ')') depth--;\n    else if (text[i] === ',' && depth === 0) {\n      parts.push(text.slice(start, i).trim());\n      start = i + 1;\n    }\n  }\n  parts.push(text.slice(start).trim());\n  return parts;\n};\n\nconst parseGradient = value => {\n  const text = String(value).trim();\n  const match = /^linear-gradient\\((.*)\\)$/is.exec(text);\n  if (!match) {\n    return {\n      angle: 180,\n      stops: [\n        [0, text],\n        [1, text]\n      ]\n    };\n  }\n  const parts = splitTopLevel(match[1]);\n  const head = parts[0].toLowerCase();\n  let angle = 180;\n  if (/^-?[\\d.]+(deg|turn|rad|grad)$/.test(head)) {\n    const number = parseFloat(head);\n    if (head.endsWith('turn')) angle = number * 360;\n    else if (head.endsWith('grad')) angle = number * 0.9;\n    else if (head.endsWith('rad')) angle = (number * 180) / Math.PI;\n    else angle = number;\n    parts.shift();\n  } else if (head.startsWith('to ')) {\n    const sides = head.slice(3).split(/\\s+/);\n    const vertical = sides.includes('top') ? -1 : sides.includes('bottom') ? 1 : 0;\n    const horizontal = sides.includes('left') ? -1 : sides.includes('right') ? 1 : 0;\n    angle = sides.length === 1 ? (SIDES[sides[0]] ?? 180) : (Math.atan2(horizontal, -vertical) * 180) / Math.PI;\n    parts.shift();\n  }\n  const stops = parts.map(part => {\n    const stop = /^(.*?)\\s+(-?[\\d.]+)%$/.exec(part);\n    return stop ? [Number(stop[2]) / 100, stop[1].trim()] : [null, part];\n  });\n  if (!stops.length) stops.push([0, '#ffffff']);\n  if (stops[0][0] === null) stops[0][0] = 0;\n  if (stops[stops.length - 1][0] === null) stops[stops.length - 1][0] = 1;\n  for (let i = 1; i < stops.length - 1; i++) {\n    if (stops[i][0] !== null) continue;\n    let next = i + 1;\n    while (stops[next][0] === null) next++;\n    const from = stops[i - 1][0];\n    stops[i][0] = from + (stops[next][0] - from) / (next - i + 1);\n  }\n  return { angle, stops };\n};\n\nconst buildMaps = (size, radius, bevel) => {\n  const key = `${size}:${radius}:${bevel}`;\n  if (MAPS.has(key)) return MAPS.get(key);\n  const res = 2;\n  const w = Math.max(8, Math.round(size * res));\n  const canvas = document.createElement('canvas');\n  canvas.width = w;\n  canvas.height = w;\n  const ctx = canvas.getContext('2d');\n  if (!ctx) return null;\n  const refraction = ctx.createImageData(w, w);\n  const shine = ctx.createImageData(w, w);\n  const shade = ctx.createImageData(w, w);\n  const r = Math.min(radius * res, w / 2);\n  const band = Math.max(1, bevel * res);\n  const half = w / 2;\n  const lx = -0.55;\n  const ly = -0.83;\n  for (let y = 0; y < w; y++) {\n    for (let x = 0; x < w; x++) {\n      const px = x + 0.5 - half;\n      const py = y + 0.5 - half;\n      const qx = Math.abs(px) - (half - r);\n      const qy = Math.abs(py) - (half - r);\n      const ox = Math.max(qx, 0);\n      const oy = Math.max(qy, 0);\n      const distance = Math.hypot(ox, oy) + Math.min(Math.max(qx, qy), 0) - r;\n      let nx = 0;\n      let ny = 0;\n      if (qx > 0 && qy > 0) {\n        const length = Math.hypot(ox, oy) || 1;\n        nx = (ox / length) * Math.sign(px);\n        ny = (oy / length) * Math.sign(py);\n      } else if (qx > qy) {\n        nx = Math.sign(px);\n      } else {\n        ny = Math.sign(py);\n      }\n      const inside = -distance;\n      const t = inside >= band ? 0 : inside <= 0 ? 1 : 1 - inside / band;\n      const slope = Math.pow(t, 2.2);\n      const i = (y * w + x) * 4;\n      refraction.data[i] = Math.round(128 + 127 * nx * slope);\n      refraction.data[i + 1] = Math.round(128 + 127 * ny * slope);\n      refraction.data[i + 2] = 128;\n      refraction.data[i + 3] = 255;\n      const facing = nx * lx + ny * ly;\n      const edge = inside <= 0 ? 0 : Math.min(1, inside / (1.2 * res));\n      const glow = (Math.max(0, facing) * 0.95 + Math.max(0, -facing) * 0.35) * t * t * t * edge;\n      shine.data[i] = 255;\n      shine.data[i + 1] = 255;\n      shine.data[i + 2] = 255;\n      shine.data[i + 3] = Math.round(255 * Math.min(1, glow));\n      const dim = (Math.max(0, -facing) * 0.75 + 0.25) * t * t * edge;\n      shade.data[i + 3] = Math.round(255 * Math.min(1, dim));\n    }\n  }\n  ctx.putImageData(refraction, 0, 0);\n  const refractionUrl = canvas.toDataURL();\n  ctx.putImageData(shine, 0, 0);\n  const shineUrl = canvas.toDataURL();\n  ctx.putImageData(shade, 0, 0);\n  const shadeUrl = canvas.toDataURL();\n  const maps = { refractionUrl, shineUrl, shadeUrl };\n  MAPS.set(key, maps);\n  return maps;\n};\n\nconst supportsLiquid = () => {\n  if (typeof window === 'undefined' || typeof navigator === 'undefined') return false;\n  const agent = navigator.userAgent;\n  const safari = /Safari/.test(agent) && !/Chrome|Chromium|Edg/.test(agent);\n  if (safari || /Firefox/.test(agent)) return false;\n  return window.CSS?.supports?.('backdrop-filter', 'url(#a)') ?? false;\n};\n\nconst compile = (gl, type, source) => {\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)) return shader;\n  gl.deleteShader(shader);\n  return null;\n};\n\nconst poseOf = (state, s) => {\n  const size = s.tile;\n  const enter = clamp(state.e, 0, 1);\n  const swing = ease((enter - 0.18) / 0.82);\n  const hover = state.h;\n  const lift = s.hover === 'lift' ? hover : 0;\n  const clear = s.plate === 'none';\n  let liftY = 0;\n  let glassScale = 1;\n  if (!clear && s.hover === 'lift') liftY = -0.08 * size * hover;\n  if (!clear && s.hover === 'float') liftY = -0.14 * size * hover;\n  if (s.hover === 'press') {\n    liftY = clear ? 0 : -0.03 * size * hover;\n    glassScale = 1 - 0.08 * state.p;\n  }\n  const settle = clear ? 1 : 0.92 + 0.08 * ease(enter);\n  const glyphRise = (1 - ease((enter - 0.25) / 0.75)) * 0.1 * size;\n  const shift = [-state.x * s.parallax * 0.07 * size, -state.y * s.parallax * 0.07 * size];\n  let angle = 0;\n  let offset = [0, 0];\n  let grow = 1;\n  if (s.plate === 'tilt') {\n    angle = (s.tilt + 10 * lift) * swing;\n    offset = [-0.08 * size * lift, -0.08 * size * lift];\n  } else if (s.plate === 'offset') {\n    const reach = s.spread * (0.26 + 0.08 * lift) * size * swing;\n    offset = [reach, -reach];\n  } else if (s.plate === 'circle') {\n    const reach = (s.spread * (0.24 + 0.06 * lift) + 0.06 * lift) * size * swing;\n    offset = [reach, -reach];\n    grow = 1 + 0.08 * lift;\n  }\n  const light = [-0.55 + state.x * 0.35, -0.83 + state.y * 0.35];\n  const length = Math.hypot(light[0], light[1]) || 1;\n  return {\n    liftY,\n    glassScale: clear ? 1 : glassScale * settle,\n    domScale: clear ? 1 : glassScale * settle,\n    glassAlpha: fade(0, 0.55, enter),\n    plateAlpha: fade(0, 0.35, enter),\n    faceY: (clear ? -0.05 * size * hover : liftY) + glyphRise,\n    faceScale: glassScale,\n    faceAlpha: fade(0.3, 0.85, enter),\n    plateScale: (s.hover === 'press' ? 1 - 0.08 * state.p : 1) * grow,\n    shift,\n    angle,\n    offset,\n    bend: 1 + 1.2 * (1 - ease(enter)),\n    glow: 1 + (clear ? 0.6 : 0.25) * hover,\n    light: [light[0] / length, light[1] / length]\n  };\n};\n\nconst GlassIcons = ({\n  items = [],\n  size = 82,\n  gap = 66,\n  columns = 3,\n  roundness = 0.6,\n  refraction = 0.8,\n  bevel = 0.6,\n  dispersion = 0,\n  frost = 0.2,\n  shine = 0.85,\n  tint = '#dfdfdf',\n  tintOpacity = 0.2,\n  iconColor,\n  plate = 'tilt',\n  tilt = 30,\n  spread = 0.5,\n  hover = 'press',\n  parallax = 1,\n  labels = 'hover',\n  intro = true,\n  theme = 'dark',\n  className = '',\n  style\n}) => {\n  const filterId = `glass-icons-${useId().replace(/[^a-zA-Z0-9-]/g, '')}`;\n  const rootRef = useRef(null);\n  const canvasRef = useRef(null);\n  const plateRefs = useRef([]);\n  const glassRefs = useRef([]);\n  const faceRefs = useRef([]);\n  const labelRefs = useRef([]);\n  const engineRef = useRef(null);\n  const [gpu, setGpu] = useState(false);\n  const [liquid, setLiquid] = useState(false);\n  const [maps, setMaps] = useState(null);\n  const tile = Math.max(24, Math.round(size));\n  const radius = Math.round((tile / 2) * clamp(roundness, 0, 1));\n  const edge = Math.max(2, Math.round(tile * 0.26 * clamp(bevel, 0, 1)));\n  const plateMode = PLATES.includes(plate) ? plate : 'tilt';\n  const hoverMode = HOVERS.includes(hover) ? hover : 'lift';\n  const labelMode = LABELS.includes(labels) ? labels : 'hover';\n  const ink = iconColor ?? (theme === 'light' && plateMode === 'none' ? '#27272a' : '#ffffff');\n  const colors = items.map(item => GRADIENTS[item.color] ?? item.color ?? GRADIENTS.blue);\n  const settings = {\n    count: items.length,\n    tile,\n    radius,\n    edge,\n    strength: tile * 0.3 * clamp(refraction, 0, 1.5),\n    split: tile * 0.07 * clamp(dispersion, 0, 1) * clamp(refraction, 0, 1.5),\n    soft: 1 + clamp(frost, 0, 1) * tile * 0.15,\n    shine: clamp(shine, 0, 1),\n    shade: theme === 'light' ? 0.22 : 0.35,\n    tint,\n    tintOpacity: clamp(tintOpacity, 0, 1),\n    plate: plateMode,\n    hover: hoverMode,\n    labels: labelMode,\n    tilt,\n    spread: clamp(spread, 0, 1),\n    parallax: clamp(parallax, 0, 1),\n    intro,\n    colors,\n    colorKey: colors.join('|')\n  };\n  const settingsRef = useRef(settings);\n  settingsRef.current = settings;\n\n  useEffect(() => {\n    setLiquid(supportsLiquid());\n  }, []);\n\n  useEffect(() => {\n    setMaps(buildMaps(tile, radius, edge));\n  }, [tile, radius, edge]);\n\n  useEffect(() => {\n    const root = rootRef.current;\n    const canvas = canvasRef.current;\n    if (!root || !canvas) return undefined;\n    const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;\n    const states = [];\n    const layout = { width: 1, height: 1, margin: 0, dpr: 1, centers: [] };\n    const paint = document.createElement('canvas');\n    const paintContext = paint.getContext('2d');\n    let gl = canvas.getContext('webgl2', { alpha: true, premultipliedAlpha: true, antialias: false });\n    let program = null;\n    let buffer = null;\n    let texture = null;\n    let vertex = null;\n    let fragment = null;\n    const uniforms = {};\n    let gradientKey = '';\n    let gradients = [];\n    let tintKey = '';\n    let tintColor = [1, 1, 1];\n    let raf = 0;\n    let last = 0;\n    let alive = true;\n    let entered = false;\n\n    if (gl) {\n      vertex = compile(gl, gl.VERTEX_SHADER, VERTEX);\n      fragment = compile(gl, gl.FRAGMENT_SHADER, FRAGMENT);\n      program = gl.createProgram();\n      if (vertex && fragment && program) {\n        gl.attachShader(program, vertex);\n        gl.attachShader(program, fragment);\n        gl.linkProgram(program);\n      }\n      if (!program || !vertex || !fragment || !gl.getProgramParameter(program, gl.LINK_STATUS)) {\n        gl = null;\n      } else {\n        gl.useProgram(program);\n        buffer = gl.createBuffer();\n        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n        const corner = gl.getAttribLocation(program, 'aCorner');\n        gl.enableVertexAttribArray(corner);\n        gl.vertexAttribPointer(corner, 2, gl.FLOAT, false, 0, 0);\n        for (const name of UNIFORMS) uniforms[name] = gl.getUniformLocation(program, name);\n        texture = gl.createTexture();\n        gl.activeTexture(gl.TEXTURE0);\n        gl.bindTexture(gl.TEXTURE_2D, texture);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n        gl.uniform1i(uniforms.uGradients, 0);\n        gl.enable(gl.BLEND);\n        gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n      }\n    }\n    setGpu(Boolean(gl));\n\n    const stateOf = index => {\n      while (states.length <= index) {\n        const start = settingsRef.current.intro && !reduce ? 0 : 1;\n        states.push({\n          h: 0,\n          hv: 0,\n          ht: 0,\n          p: 0,\n          pv: 0,\n          pt: 0,\n          x: 0,\n          xv: 0,\n          xt: 0,\n          y: 0,\n          yv: 0,\n          yt: 0,\n          e: start,\n          ev: 0,\n          et: start,\n          at: 0\n        });\n      }\n      return states[index];\n    };\n\n    const refreshTextures = () => {\n      const s = settingsRef.current;\n      if (paintContext && s.tint !== tintKey) {\n        tintKey = s.tint;\n        paint.width = 1;\n        paint.height = 1;\n        paintContext.fillStyle = '#ffffff';\n        paintContext.fillStyle = String(s.tint);\n        paintContext.fillRect(0, 0, 1, 1);\n        const [r, g, b] = paintContext.getImageData(0, 0, 1, 1).data;\n        tintColor = [r / 255, g / 255, b / 255];\n      }\n      if (!gl || !paintContext || s.colorKey === gradientKey) return;\n      gradientKey = s.colorKey;\n      gradients = s.colors.map(parseGradient);\n      paint.width = 256;\n      paint.height = Math.max(1, gradients.length);\n      gradients.forEach((gradient, row) => {\n        const fill = paintContext.createLinearGradient(0, 0, 256, 0);\n        gradient.stops.forEach(([at, color]) => {\n          try {\n            fill.addColorStop(clamp(at, 0, 1), color);\n          } catch {\n            fill.addColorStop(clamp(at, 0, 1), '#ffffff');\n          }\n        });\n        paintContext.fillStyle = fill;\n        paintContext.fillRect(0, row, 256, 1);\n      });\n      gl.activeTexture(gl.TEXTURE0);\n      gl.bindTexture(gl.TEXTURE_2D, texture);\n      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, paint);\n    };\n\n    const measure = () => {\n      const s = settingsRef.current;\n      const margin = Math.ceil(s.tile * 0.7);\n      layout.margin = margin;\n      layout.width = Math.max(1, root.clientWidth + margin * 2);\n      layout.height = Math.max(1, root.clientHeight + margin * 2);\n      layout.dpr = Math.min(window.devicePixelRatio || 1, 2);\n      canvas.style.left = `${-margin}px`;\n      canvas.style.top = `${-margin}px`;\n      canvas.style.width = `${layout.width}px`;\n      canvas.style.height = `${layout.height}px`;\n      const width = Math.round(layout.width * layout.dpr);\n      const height = Math.round(layout.height * layout.dpr);\n      if (canvas.width !== width) canvas.width = width;\n      if (canvas.height !== height) canvas.height = height;\n      layout.centers = Array.from(root.querySelectorAll('[data-glass-icon]')).map(icon => [\n        icon.offsetLeft + icon.offsetWidth / 2 + margin,\n        icon.offsetTop + icon.offsetHeight / 2 + margin\n      ]);\n    };\n\n    const applyDom = () => {\n      const s = settingsRef.current;\n      for (let i = 0; i < s.count; i++) {\n        const pose = poseOf(stateOf(i), s);\n        const glass = glassRefs.current[i];\n        if (glass) {\n          glass.style.transform = `translate3d(0, ${pose.liftY.toFixed(2)}px, 0) scale(${pose.domScale.toFixed(4)})`;\n          glass.style.opacity = pose.glassAlpha.toFixed(3);\n        }\n        const face = faceRefs.current[i];\n        if (face) {\n          face.style.transform = `translate3d(0, ${pose.faceY.toFixed(2)}px, 0) scale(${pose.faceScale.toFixed(4)})`;\n          face.style.opacity = pose.faceAlpha.toFixed(3);\n        }\n        const label = labelRefs.current[i];\n        if (label && s.labels === 'always') label.style.opacity = pose.faceAlpha.toFixed(3);\n        const card = plateRefs.current[i];\n        if (card) {\n          const [sx, sy] = pose.shift;\n          const [ox, oy] = pose.offset;\n          card.style.transform = `translate(${sx.toFixed(2)}px, ${sy.toFixed(2)}px) scale(${pose.plateScale.toFixed(4)}) rotate(${pose.angle.toFixed(3)}deg) translate(${ox.toFixed(2)}px, ${oy.toFixed(2)}px)`;\n          card.style.opacity = pose.plateAlpha.toFixed(3);\n        }\n      }\n    };\n\n    const draw = () => {\n      if (!gl) return;\n      const s = settingsRef.current;\n      refreshTextures();\n      gl.viewport(0, 0, canvas.width, canvas.height);\n      gl.clearColor(0, 0, 0, 0);\n      gl.clear(gl.COLOR_BUFFER_BIT);\n      if (!s.count) return;\n      const size = s.tile;\n      gl.uniform2f(uniforms.uCanvas, layout.width, layout.height);\n      gl.uniform1f(uniforms.uExtent, size * 1.08);\n      gl.uniform1f(uniforms.uSize, size);\n      gl.uniform1f(uniforms.uRadius, Math.min(s.radius, size / 2));\n      gl.uniform1f(uniforms.uBevel, s.edge);\n      gl.uniform1f(uniforms.uSoft, s.soft);\n      gl.uniform1f(uniforms.uShade, s.shade);\n      gl.uniform4f(uniforms.uTint, tintColor[0], tintColor[1], tintColor[2], s.tintOpacity);\n      gl.uniform1i(uniforms.uPlate, PLATE_CODES[s.plate]);\n      gl.uniform1f(uniforms.uDisc, size * 0.41);\n      gl.uniform1f(uniforms.uPixel, 1 / layout.dpr);\n      const origin = s.plate === 'tilt' ? size / 2 : 0;\n      gl.uniform2f(uniforms.uOrigin, origin, origin);\n      for (let i = 0; i < s.count; i++) {\n        const center = layout.centers[i];\n        if (!center) continue;\n        const pose = poseOf(stateOf(i), s);\n        const gradient = gradients[i] ?? { angle: 180 };\n        const turn = (-pose.angle * Math.PI) / 180;\n        const box = s.plate === 'circle' ? size * 0.82 : size;\n        const direction = (gradient.angle * Math.PI) / 180;\n        const dx = Math.sin(direction);\n        const dy = -Math.cos(direction);\n        gl.uniform2f(uniforms.uCenter, center[0], center[1]);\n        gl.uniform1f(uniforms.uStrength, s.strength * pose.bend);\n        gl.uniform1f(uniforms.uSplit, s.split * pose.bend);\n        gl.uniform1f(uniforms.uShine, s.shine * pose.glow);\n        gl.uniform2f(uniforms.uLight, pose.light[0], pose.light[1]);\n        gl.uniformMatrix2fv(uniforms.uTurn, false, [Math.cos(turn), Math.sin(turn), -Math.sin(turn), Math.cos(turn)]);\n        gl.uniform2f(uniforms.uShift, pose.shift[0], pose.shift[1]);\n        gl.uniform2f(uniforms.uOffset, pose.offset[0], pose.offset[1]);\n        gl.uniform1f(uniforms.uScale, pose.plateScale);\n        gl.uniform1f(uniforms.uRow, (i + 0.5) / Math.max(1, gradients.length));\n        gl.uniform2f(uniforms.uDirection, dx, dy);\n        gl.uniform1f(uniforms.uSpan, Math.max(1, Math.abs(box * dx) + Math.abs(box * dy)));\n        gl.uniform2f(uniforms.uLift, 0, pose.liftY);\n        gl.uniform1f(uniforms.uGlassScale, pose.glassScale);\n        gl.uniform1f(uniforms.uGlassAlpha, pose.glassAlpha);\n        gl.uniform1f(uniforms.uPlateAlpha, pose.plateAlpha);\n        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n      }\n    };\n\n    const tick = now => {\n      raf = 0;\n      if (!alive) return;\n      const s = settingsRef.current;\n      const time = now / 1000;\n      const dt = last ? Math.min(1 / 30, time - last) : 1 / 60;\n      last = time;\n      const steps = Math.max(1, Math.ceil(dt * 240));\n      const h = dt / steps;\n      let moving = false;\n      for (let i = 0; i < s.count; i++) {\n        const state = stateOf(i);\n        if (state.et === 0) {\n          if (entered && time >= state.at) state.et = 1;\n          else moving = moving || entered;\n        }\n        if (reduce) {\n          state.h = state.ht;\n          state.p = state.pt;\n          state.x = state.xt;\n          state.y = state.yt;\n          state.e = state.et;\n          continue;\n        }\n        for (let k = 0; k < steps; k++) {\n          [state.h, state.hv] = drive(state.h, state.hv, state.ht, 170, 24, h);\n          [state.p, state.pv] = drive(state.p, state.pv, state.pt, 420, 40, h);\n          [state.x, state.xv] = drive(state.x, state.xv, state.xt, 90, 19, h);\n          [state.y, state.yv] = drive(state.y, state.yv, state.yt, 90, 19, h);\n          [state.e, state.ev] = drive(state.e, state.ev, state.et, 60, 15.5, h);\n        }\n        const pairs = [\n          ['h', 'hv', 'ht'],\n          ['p', 'pv', 'pt'],\n          ['x', 'xv', 'xt'],\n          ['y', 'yv', 'yt'],\n          ['e', 'ev', 'et']\n        ];\n        for (const [value, velocity, target] of pairs) {\n          if (Math.abs(state[target] - state[value]) < 0.0005 && Math.abs(state[velocity]) < 0.001) {\n            state[value] = state[target];\n            state[velocity] = 0;\n          } else {\n            moving = true;\n          }\n        }\n      }\n      applyDom();\n      draw();\n      if (moving) raf = requestAnimationFrame(tick);\n      else last = 0;\n    };\n\n    const wake = () => {\n      if (!raf && alive) raf = requestAnimationFrame(tick);\n    };\n\n    const visibility = new IntersectionObserver(\n      entries => {\n        if (entered || !entries.some(entry => entry.isIntersecting)) return;\n        entered = true;\n        const time = performance.now() / 1000;\n        for (let i = 0; i < settingsRef.current.count; i++) stateOf(i).at = time + i * STAGGER;\n        wake();\n      },\n      { threshold: 0.15 }\n    );\n    visibility.observe(root);\n\n    const resizeObserver = new ResizeObserver(() => {\n      measure();\n      applyDom();\n      draw();\n    });\n    resizeObserver.observe(root);\n\n    const onContextLost = event => {\n      event.preventDefault();\n      gl = null;\n      setGpu(false);\n    };\n    canvas.addEventListener('webglcontextlost', onContextLost);\n\n    engineRef.current = {\n      hover: (index, on) => {\n        const state = stateOf(index);\n        state.ht = on ? 1 : 0;\n        if (!on) {\n          state.xt = 0;\n          state.yt = 0;\n          state.pt = 0;\n        }\n        wake();\n      },\n      aim: (index, x, y) => {\n        const state = stateOf(index);\n        state.xt = x;\n        state.yt = y;\n        wake();\n      },\n      press: (index, on) => {\n        stateOf(index).pt = on ? 1 : 0;\n        wake();\n      },\n      sync: () => {\n        measure();\n        applyDom();\n        draw();\n        wake();\n      }\n    };\n    engineRef.current.sync();\n\n    return () => {\n      alive = false;\n      cancelAnimationFrame(raf);\n      engineRef.current = null;\n      visibility.disconnect();\n      resizeObserver.disconnect();\n      canvas.removeEventListener('webglcontextlost', onContextLost);\n      if (gl) {\n        gl.deleteTexture(texture);\n        gl.deleteBuffer(buffer);\n        gl.deleteProgram(program);\n        gl.deleteShader(vertex);\n        gl.deleteShader(fragment);\n      }\n    };\n  }, []);\n\n  useIsomorphicLayoutEffect(() => {\n    engineRef.current?.sync();\n  });\n\n  const point = (index, event) => {\n    if (event.pointerType === 'touch') return;\n    const rect = event.currentTarget.getBoundingClientRect();\n    const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;\n    const y = ((event.clientY - rect.top) / rect.height) * 2 - 1;\n    engineRef.current?.aim(index, clamp(x, -1, 1), clamp(y, -1, 1));\n  };\n\n  const focusVisible = element => {\n    try {\n      return element.matches(':focus-visible');\n    } catch {\n      return false;\n    }\n  };\n\n  const clearGlass = plateMode === 'none';\n  const glassFilter =\n    clearGlass && liquid && maps\n      ? `url(#${filterId}) saturate(1.35) brightness(1.04)`\n      : `blur(${(2 + clamp(frost, 0, 1) * 10).toFixed(1)}px) saturate(1.4)`;\n  const blur = clamp(frost, 0, 1) * tile * 0.06;\n\n  return (\n    <div\n      ref={rootRef}\n      className={`${ROOT}${className ? ` ${className}` : ''}`}\n      data-intro={intro ? '' : undefined}\n      style={{\n        ...(THEMES[theme] ?? THEMES.dark),\n        '--glass-icons-size': `${tile}px`,\n        '--glass-icons-radius': `${radius}px`,\n        '--glass-icons-gap': `${Math.max(0, gap)}px`,\n        '--glass-icons-row-gap': `${Math.max(0, gap) + (labelMode === 'always' ? 26 : 0)}px`,\n        '--glass-icons-columns': columns > 0 ? Math.round(columns) : Math.max(1, items.length),\n        '--glass-icons-tint': `color-mix(in srgb, ${tint} ${Math.round(clamp(tintOpacity, 0, 1) * 100)}%, transparent)`,\n        '--glass-icons-ink': ink,\n        '--glass-icons-glyph-shadow': isDark(ink) ? 'rgba(255, 255, 255, 0.35)' : 'rgba(0, 0, 0, 0.28)',\n        '--glass-icons-tilt': `${tilt}deg`,\n        '--glass-icons-spread': clamp(spread, 0, 1),\n        '--glass-icons-shine': clamp(shine, 0, 1),\n        ...style\n      }}\n    >\n      {clearGlass && liquid && maps && (\n        <svg className={DEFS} aria-hidden=\"true\" focusable=\"false\">\n          <filter id={filterId} colorInterpolationFilters=\"sRGB\" x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">\n            <feImage\n              href={maps.refractionUrl}\n              x=\"0\"\n              y=\"0\"\n              width={tile}\n              height={tile}\n              preserveAspectRatio=\"none\"\n              result=\"map\"\n            />\n            <feDisplacementMap\n              in=\"SourceGraphic\"\n              in2=\"map\"\n              scale={-settings.strength * 2 - settings.split * 2}\n              xChannelSelector=\"R\"\n              yChannelSelector=\"G\"\n              result=\"shiftRed\"\n            />\n            <feColorMatrix\n              in=\"shiftRed\"\n              type=\"matrix\"\n              values=\"1 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1 0\"\n              result=\"red\"\n            />\n            <feDisplacementMap\n              in=\"SourceGraphic\"\n              in2=\"map\"\n              scale={-settings.strength * 2}\n              xChannelSelector=\"R\"\n              yChannelSelector=\"G\"\n              result=\"shiftGreen\"\n            />\n            <feColorMatrix\n              in=\"shiftGreen\"\n              type=\"matrix\"\n              values=\"0 0 0 0 0  0 1 0 0 0  0 0 0 0 0  0 0 0 1 0\"\n              result=\"green\"\n            />\n            <feDisplacementMap\n              in=\"SourceGraphic\"\n              in2=\"map\"\n              scale={-settings.strength * 2 + settings.split * 2}\n              xChannelSelector=\"R\"\n              yChannelSelector=\"G\"\n              result=\"shiftBlue\"\n            />\n            <feColorMatrix\n              in=\"shiftBlue\"\n              type=\"matrix\"\n              values=\"0 0 0 0 0  0 0 0 0 0  0 0 1 0 0  0 0 0 1 0\"\n              result=\"blue\"\n            />\n            <feBlend in=\"red\" in2=\"green\" mode=\"screen\" result=\"redGreen\" />\n            <feBlend in=\"redGreen\" in2=\"blue\" mode=\"screen\" result=\"glass\" />\n            {blur > 0 && <feGaussianBlur in=\"glass\" stdDeviation={blur.toFixed(2)} />}\n          </filter>\n        </svg>\n      )}\n\n      <canvas ref={canvasRef} className={CANVAS} aria-hidden=\"true\" />\n\n      {items.map((item, index) => {\n        const Tag = item.href ? 'a' : 'button';\n        return (\n          <Tag\n            key={item.id ?? index}\n            className={`${ICON}${item.className ? ` ${item.className}` : ''}`}\n            data-glass-icon=\"\"\n            aria-label={item.label}\n            {...(item.href\n              ? { href: item.href, target: item.target, rel: item.target === '_blank' ? 'noreferrer' : undefined }\n              : { type: 'button' })}\n            onClick={item.onClick}\n            onPointerEnter={event => {\n              if (event.pointerType !== 'touch') engineRef.current?.hover(index, true);\n            }}\n            onPointerMove={event => point(index, event)}\n            onPointerLeave={() => engineRef.current?.hover(index, false)}\n            onPointerDown={() => engineRef.current?.press(index, true)}\n            onPointerUp={() => engineRef.current?.press(index, false)}\n            onPointerCancel={() => engineRef.current?.press(index, false)}\n            onFocus={event => {\n              if (focusVisible(event.currentTarget)) engineRef.current?.hover(index, true);\n            }}\n            onBlur={() => engineRef.current?.hover(index, false)}\n          >\n            {!gpu && plateMode !== 'none' && (\n              <span\n                ref={element => {\n                  plateRefs.current[index] = element;\n                }}\n                className={PLATE_SHAPES[plateMode]}\n                style={{ background: settings.colors[index] }}\n              />\n            )}\n            <span\n              ref={element => {\n                glassRefs.current[index] = element;\n              }}\n              className={GLASS}\n              style={{ backdropFilter: glassFilter, WebkitBackdropFilter: glassFilter }}\n            >\n              {!gpu && maps && <span className={SHADE} style={{ backgroundImage: `url(${maps.shadeUrl})` }} />}\n              {!gpu && maps && <span className={SHINE} style={{ backgroundImage: `url(${maps.shineUrl})` }} />}\n            </span>\n            <span\n              ref={element => {\n                faceRefs.current[index] = element;\n              }}\n              className={FACE}\n            >\n              <span className={GLYPH} aria-hidden=\"true\">\n                {item.icon}\n              </span>\n            </span>\n            {labelMode !== 'none' && item.label && (\n              <span\n                ref={element => {\n                  labelRefs.current[index] = element;\n                }}\n                className={labelMode === 'always' ? LABEL_ALWAYS : LABEL_HOVER}\n              >\n                {item.label}\n              </span>\n            )}\n          </Tag>\n        );\n      })}\n    </div>\n  );\n};\n\nexport default GlassIcons;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}