{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "HoloCard-TS-CSS",
	"title": "HoloCard",
	"description": "Collectible trading card with a real holographic foil and glitter that sparkles as it tilts.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "HoloCard.css",
			"target": "@components/HoloCard.css",
			"content": ".holo-card {\n  --hc-w: 320px;\n  --hc-ratio: 0.716;\n  --hc-radius: 14px;\n\n  position: relative;\n  display: inline-block;\n  width: var(--hc-w);\n  max-width: 100%;\n  aspect-ratio: var(--hc-ratio);\n  border-radius: var(--hc-radius);\n  outline: none;\n  user-select: none;\n  -webkit-user-select: none;\n  -webkit-touch-callout: none;\n  -webkit-tap-highlight-color: transparent;\n  touch-action: pan-y;\n}\n\n.holo-card[data-flippable] {\n  cursor: pointer;\n}\n\n.holo-card[data-flippable]:focus-visible {\n  outline: 2px solid currentColor;\n  outline-offset: 6px;\n}\n\n.holo-card__shadow {\n  position: absolute;\n  inset: 10% 7% -4%;\n  border-radius: var(--hc-radius);\n  background: rgba(0, 0, 0, 0.42);\n  filter: blur(26px);\n  pointer-events: none;\n  will-change: transform;\n}\n\n.holo-card__rotor {\n  position: absolute;\n  inset: 0;\n  transform-style: preserve-3d;\n  will-change: transform;\n}\n\n.holo-card__face {\n  position: absolute;\n  inset: 0;\n  overflow: hidden;\n  border-radius: var(--hc-radius);\n  backface-visibility: hidden;\n  -webkit-backface-visibility: hidden;\n}\n\n.holo-card__face--back {\n  transform: rotateY(180deg);\n}\n\n.holo-card__face img,\n.holo-card__face canvas {\n  position: absolute;\n  inset: 0;\n  display: block;\n  width: 100%;\n  height: 100%;\n}\n\n.holo-card__face img {\n  object-fit: cover;\n  pointer-events: none;\n  -webkit-user-drag: none;\n}\n\n.holo-card__face canvas {\n  opacity: 0;\n  transition: opacity 0.3s ease;\n}\n\n.holo-card[data-ready] .holo-card__face canvas {\n  opacity: 1;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "HoloCard.tsx",
			"content": "'use client';\n\nimport { useEffect, useRef, useState } from 'react';\nimport type { CSSProperties, PointerEvent as ReactPointerEvent } from 'react';\n\nimport './HoloCard.css';\n\ntype HoloPreset = 'bursts' | 'stars' | 'shards' | 'cosmos' | 'rainbow' | 'swirl' | 'glitter' | 'gold';\n\nexport interface HoloCardProps {\n  image?: string;\n  backImage?: string;\n  alt?: string;\n  preset?: HoloPreset;\n  intensity?: number;\n  scale?: number;\n  edgeSparkle?: number;\n  frame?: number;\n  glare?: number;\n  foilColor?: string;\n  width?: number;\n  radius?: number;\n  tiltMax?: number;\n  hoverScale?: number;\n  idle?: boolean;\n  shadow?: boolean;\n  className?: string;\n  style?: CSSProperties;\n}\n\ninterface Settings {\n  preset: number;\n  intensity: number;\n  scale: number;\n  edgeSparkle: number;\n  frame: number;\n  glare: number;\n  foil: number[];\n  radius: number;\n  tiltMax: number;\n  hoverScale: number;\n  idle: boolean;\n}\n\nconst PRESETS: HoloPreset[] = ['bursts', 'stars', 'shards', 'cosmos', 'rainbow', 'swirl', 'glitter', 'gold'];\nconst PERSPECTIVE = 1100;\nconst CARD_RATIO = 63 / 88;\n\nconst VERTEX = `#version 300 es\nin vec2 position;\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAGMENT = `#version 300 es\nprecision highp float;\n\nuniform sampler2D uArt;\nuniform vec2 uSize;\nuniform float uAspect;\nuniform vec2 uTilt;\nuniform vec2 uLight;\nuniform float uDistance;\nuniform int uPreset;\nuniform float uIntensity;\nuniform float uScale;\nuniform float uEdge;\nuniform float uFrame;\nuniform float uRadius;\nuniform float uGlare;\nuniform vec3 uFoil;\nuniform float uReady;\n\nout vec4 outColor;\n\nconst float TAU = 6.28318530718;\n\nstruct Foil {\n  float mask;\n  float angle;\n  float jitter;\n  vec2 facet;\n};\n\nfloat hash(vec2 p) {\n  p = fract(p * vec2(234.34, 435.345));\n  p += dot(p, p + 34.23);\n  return fract(p.x * p.y);\n}\n\nvec2 hash2(vec2 p) {\n  return vec2(hash(p), hash(p + 17.31));\n}\n\nvec3 hue(float t) {\n  t = abs(fract(t * 0.5) * 2.0 - 1.0);\n  vec3 c = mix(vec3(0.1, 0.25, 1.0), vec3(0.0, 0.85, 1.0), smoothstep(0.0, 0.25, t));\n  c = mix(c, vec3(0.25, 1.0, 0.2), smoothstep(0.25, 0.45, t));\n  c = mix(c, vec3(1.0, 0.92, 0.05), smoothstep(0.45, 0.65, t));\n  c = mix(c, vec3(1.0, 0.45, 0.05), smoothstep(0.65, 0.85, t));\n  return mix(c, vec3(0.95, 0.12, 0.3), smoothstep(0.85, 1.0, t));\n}\n\nFoil bursts(vec2 p) {\n  vec2 q = mat2(0.7071068, 0.7071068, -0.7071068, 0.7071068) * (p - vec2(0.5, 0.5 * uAspect)) / (0.283 / uScale);\n  vec2 d = fract(q) - 0.5;\n  float u = length(d) / 0.49;\n  float angle = atan(d.y, d.x);\n  if (u < 0.12) {\n    float ring = min(floor(u / 0.045 + 0.5), 2.0);\n    float count = max(1.0, ring * 6.0);\n    float spot = (floor(angle / TAU * count) + 0.5) / count * TAU;\n    vec2 dot = vec2(cos(spot), sin(spot)) * ring * 0.045;\n    float m = smoothstep(0.024, 0.012, length(d / 0.49 - dot));\n    return Foil(m, angle, 0.5, vec2(0.0));\n  }\n  bool inner = u < 0.66;\n  float slot = angle / TAU * (inner ? 56.0 : 72.0);\n  bool shifted = mod(floor(slot), 2.0) > 0.5;\n  vec2 band = inner ? (shifted ? vec2(0.45, 0.61) : vec2(0.33, 0.49)) : (shifted ? vec2(0.84, 0.99) : vec2(0.7, 0.85));\n  float across = abs(u - (band.x + band.y) * 0.5) / ((band.y - band.x) * 0.5);\n  float along = abs(fract(slot) - 0.5) * 2.0;\n  float width = inner ? 0.72 : 0.6;\n  float m = smoothstep(1.0, 0.78, across) * smoothstep(width, width - 0.2, along);\n  return Foil(m, angle, 0.5, vec2(0.0));\n}\n\nFoil stars(vec2 p) {\n  Foil best = Foil(0.0, 0.0, 0.0, vec2(0.0));\n  vec2 g = p / (0.08 / uScale);\n  vec2 base = floor(g);\n  for (int j = -1; j <= 1; j++) {\n    for (int i = -1; i <= 1; i++) {\n      vec2 id = base + vec2(float(i), float(j));\n      float pick = hash(id + 1.3);\n      vec2 d = g - (id + 0.5 + (hash2(id) - 0.5) * 0.55);\n      float m = smoothstep(0.07, 0.035, length(d));\n      if (pick < 0.62) {\n        float size = mix(0.16, 0.5, pow(hash(id + 9.0), 1.8));\n        float sector = TAU / (pick < 0.3 ? 4.0 : 5.0);\n        float k = abs(fract((atan(d.y, d.x) + hash(id + 4.0) * TAU) / sector) - 0.5) * 2.0;\n        float edge = size * mix(1.0, 0.36, pow(k, 0.6));\n        m = smoothstep(edge, edge * 0.8, length(d));\n      }\n      if (m > best.mask) best = Foil(m, hash(id + 6.0) * TAU, hash(id + 8.0), (hash2(id + 2.0) - 0.5) * 0.6);\n    }\n  }\n  return best;\n}\n\nFoil shards(vec2 p) {\n  vec2 g = p * 19.0 * uScale;\n  vec2 base = floor(g);\n  float best = 9.0;\n  float second = 9.0;\n  vec2 id = base;\n  for (int j = -1; j <= 1; j++) {\n    for (int i = -1; i <= 1; i++) {\n      vec2 cell = base + vec2(float(i), float(j));\n      float d = length(cell + 0.08 + hash2(cell) * 0.84 - g);\n      if (d < best) {\n        second = best;\n        best = d;\n        id = cell;\n      } else if (d < second) {\n        second = d;\n      }\n    }\n  }\n  float crack = smoothstep(0.015, 0.07, second - best);\n  float shine = 0.25 + 0.75 * pow(hash(id + 7.7), 2.0);\n  return Foil((0.2 + 0.8 * crack) * shine, hash(id + 3.17) * TAU, hash(id + 11.73), (hash2(id + 5.0) - 0.5) * 0.5);\n}\n\nFoil cosmos(vec2 p) {\n  Foil best = Foil(0.0, 0.0, 0.0, vec2(0.0));\n  vec2 g = p / (0.16 / uScale);\n  vec2 base = floor(g);\n  for (int j = -1; j <= 1; j++) {\n    for (int i = -1; i <= 1; i++) {\n      vec2 id = base + vec2(float(i), float(j));\n      vec2 d = g - (id + 0.5 + (hash2(id) - 0.5) * 0.7);\n      float size = mix(0.16, 0.52, hash(id + 2.0));\n      float r = length(d);\n      float m = max(smoothstep(0.05, 0.0, abs(r - size)), smoothstep(size, size - 0.04, r) * 0.22);\n      if (m > best.mask) best = Foil(m, hash(id + 6.0) * TAU + atan(d.y, d.x) * 0.5, hash(id + 1.0), (hash2(id + 4.0) - 0.5) * 0.5);\n    }\n  }\n  vec2 fine = p / (0.035 / uScale);\n  vec2 cell = floor(fine);\n  float dots = smoothstep(0.2, 0.1, length(fine - cell - 0.5 - (hash2(cell + 9.0) - 0.5) * 0.6)) * step(0.55, hash(cell + 2.0));\n  if (dots > best.mask) best = Foil(dots, hash(cell) * TAU, hash(cell + 3.0), (hash2(cell + 7.0) - 0.5) * 0.6);\n  return best;\n}\n\nFoil rainbow(vec2 p) {\n  return Foil(0.86 + 0.14 * hash(floor(p * 700.0 * uScale)), 0.2, 0.5, vec2(0.0));\n}\n\nFoil swirl(vec2 p) {\n  vec2 d = p - vec2(0.5, 0.42 * uAspect);\n  float angle = atan(d.y, d.x);\n  float rays = 0.55 + 0.45 * smoothstep(0.3, 0.7, abs(fract(angle / TAU * 60.0 * uScale) - 0.5) * 2.0);\n  return Foil(rays, angle, length(d), vec2(0.0));\n}\n\nFoil glitter(vec2 p) {\n  vec2 g = p * 72.0 * uScale;\n  vec2 cell = floor(g);\n  float size = mix(0.18, 0.4, hash(cell + 3.0));\n  float m = smoothstep(size, size * 0.55, length(fract(g) - 0.5 - (hash2(cell) - 0.5) * 0.5)) * step(0.25, hash(cell + 5.0));\n  return Foil(m, hash(cell + 7.0) * TAU, hash(cell + 9.0), (hash2(cell + 11.0) - 0.5) * 0.9);\n}\n\nFoil gold(vec2 p) {\n  Foil flake = glitter(p * 0.8);\n  if (flake.mask > 0.5) return flake;\n  float etch = 0.55 + 0.45 * smoothstep(0.2, 0.8, abs(fract((p.x * 0.6 + p.y) * 140.0 * uScale) - 0.5) * 2.0);\n  return Foil(etch, 0.9, 0.4, vec2(0.0));\n}\n\nvec3 sparkles(vec2 p, vec3 halfway, vec2 sweep, float awake) {\n  vec2 g = p * 62.0;\n  vec2 cell = floor(g);\n  vec2 d = fract(g) - 0.5 - (hash2(cell) - 0.5) * 0.36;\n  float spin = hash(cell + 3.0) * TAU;\n  d = mat2(cos(spin), -sin(spin), sin(spin), cos(spin)) * d;\n  vec2 a = abs(d);\n  float hexagon = max(a.x * 0.866 + a.y * 0.5, a.y);\n  float size = mix(0.22, 0.38, hash(cell + 2.0));\n  float piece = smoothstep(size, size - 0.07, hexagon) * step(0.42, hash(cell + 4.0));\n  vec3 facet = normalize(vec3((hash2(cell + 6.0) - 0.5) * 0.9, 1.0));\n  float flash = pow(max(dot(facet, halfway), 0.0), 6.0);\n  vec3 tint = hue(dot(sweep, vec2(cos(spin), sin(spin))) * 1.2 + dot(sweep, vec2(0.8, 0.6)) * 0.8 + hash(cell + 8.0) * 0.5);\n  vec3 color = piece * tint * (0.3 + 1.6 * flash) * awake;\n  vec2 fine = p * 210.0;\n  vec2 speckCell = floor(fine);\n  float speck = smoothstep(0.24, 0.0, length(fract(fine) - 0.5 - (hash2(speckCell) - 0.5) * 0.5)) * step(0.72, hash(speckCell + 1.0));\n  float speckFlash = pow(max(dot(normalize(vec3((hash2(speckCell + 2.0) - 0.5) * 0.9, 1.0)), halfway), 0.0), 10.0);\n  return color + vec3(1.2) * speck * speckFlash * (1.0 - piece);\n}\n\nvoid main() {\n  vec2 uv = vec2(gl_FragCoord.x / uSize.x, 1.0 - gl_FragCoord.y / uSize.y);\n  vec2 p = vec2(uv.x, uv.y * uAspect);\n  vec2 center = vec2(0.5, 0.5 * uAspect);\n  vec2 q = abs(p - center) - center + uRadius;\n  float outside = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - uRadius;\n  float pixel = 1.0 / uSize.x;\n  float alpha = clamp(0.5 - outside / pixel, 0.0, 1.0);\n  vec4 sampled = texture(uArt, uv);\n  if (uReady > 0.5) alpha *= sampled.a;\n  if (alpha <= 0.0) {\n    outColor = vec4(0.0);\n    return;\n  }\n  vec3 art = uReady > 0.5 ? pow(sampled.rgb, vec3(2.2)) : vec3(0.12);\n\n  mat3 rx = mat3(1.0, 0.0, 0.0, 0.0, cos(uTilt.x), sin(uTilt.x), 0.0, -sin(uTilt.x), cos(uTilt.x));\n  mat3 ry = mat3(cos(uTilt.y), 0.0, -sin(uTilt.y), 0.0, 1.0, 0.0, sin(uTilt.y), 0.0, cos(uTilt.y));\n  mat3 turn = rx * ry;\n  vec3 world = turn * vec3(p - center, 0.0);\n  vec3 view = normalize(transpose(turn) * (vec3(0.0, 0.0, uDistance) - world));\n  vec3 lamp = vec3((uLight - 0.5) * vec2(1.1, 1.1 * uAspect), 0.9);\n  vec3 light = normalize(transpose(turn) * (lamp - world));\n  vec3 halfway = normalize(light + view);\n  vec2 sweep = light.xy + view.xy;\n  float sheen = pow(max(halfway.z, 0.0), 10.0);\n  float shine = pow(max(halfway.z, 0.0), 80.0);\n  float frame = 1.0 - smoothstep(uFrame - pixel, uFrame + pixel, -outside);\n\n  Foil f;\n  if (uPreset == 0) f = bursts(p);\n  else if (uPreset == 1) f = stars(p);\n  else if (uPreset == 2) f = shards(p);\n  else if (uPreset == 3) f = cosmos(p);\n  else if (uPreset == 4) f = rainbow(p);\n  else if (uPreset == 5) f = swirl(p);\n  else if (uPreset == 6) f = glitter(p);\n  else f = gold(p);\n\n  vec3 metal = uPreset == 7 ? vec3(1.0, 0.74, 0.38) * dot(uFoil, vec3(0.3333)) * 1.15 : uFoil;\n  vec4 look = uPreset == 0 ? vec4(0.25, 1.9, 0.12, 1.0)\n    : uPreset == 1 ? vec4(0.6, 1.4, 0.3, 1.0)\n    : uPreset == 2 ? vec4(0.9, 0.9, 0.6, 0.5)\n    : uPreset == 3 ? vec4(0.7, 1.2, 0.35, 0.85)\n    : uPreset == 4 ? vec4(0.0, 2.2, 0.0, 0.45)\n    : uPreset == 5 ? vec4(1.6, 0.3, 0.0, 0.4)\n    : uPreset == 6 ? vec4(0.9, 1.0, 0.6, 1.0)\n    : vec4(0.3, 1.0, 0.2, 0.95);\n  vec2 dir = vec2(cos(f.angle), sin(f.angle));\n  float phase = dot(sweep, dir) * look.x + dot(sweep, vec2(0.8, 0.6)) * look.y + f.jitter * look.z;\n  float spoke = 1.0;\n  if (uPreset == 5) {\n    float facing = dot(normalize(sweep + 0.0001), dir);\n    phase = f.jitter * 2.4 + facing * 0.6 + length(sweep) * 0.8;\n    spoke = 0.3 + 0.7 * smoothstep(0.25, 0.95, abs(facing));\n  }\n  vec3 tint = hue(phase);\n  if (uPreset == 7) tint = mix(tint, metal, 0.65);\n  float glint = pow(max(dot(normalize(vec3(f.facet, 1.0)), halfway), 0.0), 24.0) * step(0.001, length(f.facet));\n  bool layered = uPreset == 2 || uPreset == 4 || uPreset == 5 || uPreset == 7;\n  vec3 foil = (layered ? tint : mix(tint, vec3(1.0), 0.12)) * (1.15 + 0.3 * sheen) + metal * glint * 1.2;\n  float tilted = smoothstep(0.02, 0.21, length(sin(uTilt)));\n  float awake = mix(0.2, 1.0, tilted);\n  float strength = uIntensity * f.mask * (1.0 - frame) * awake;\n  float band = uPreset == 5 ? spoke : layered ? 0.25 + 0.75 * smoothstep(0.3, 0.95, 0.5 + 0.5 * cos(phase * TAU * 0.5 + 1.3)) : 1.0;\n  vec3 color = layered\n    ? 1.0 - (1.0 - art) * (1.0 - clamp(foil * look.w * band * strength, 0.0, 1.0))\n    : mix(art, foil, clamp(strength * look.w, 0.0, 1.0));\n  color += sparkles(p, halfway, sweep, mix(0.55, 1.0, tilted)) * uEdge * frame;\n  color += uFoil * sheen * 0.12 * frame;\n  color += vec3(sheen * 0.07 + shine * 0.3) * uGlare;\n  color = pow(clamp(color, 0.0, 1.0), vec3(1.0 / 2.2));\n  outColor = vec4(color * alpha, alpha);\n}\n`;\n\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst parseColor = (value: string): number[] => {\n  const fallback = [0.78, 0.8, 0.84];\n  if (typeof document === 'undefined') return fallback;\n  const ctx = document.createElement('canvas').getContext('2d');\n  if (!ctx) return fallback;\n  ctx.fillStyle = '#000000';\n  ctx.fillStyle = value;\n  const resolved = ctx.fillStyle;\n  if (!resolved.startsWith('#') || resolved.length !== 7) return fallback;\n  return [1, 3, 5].map(i => Math.pow(parseInt(resolved.slice(i, i + 2), 16) / 255, 2.2));\n};\n\nconst compile = (gl: WebGL2RenderingContext, type: number, source: string): WebGLShader | null => {\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\nexport default function HoloCard({\n  image,\n  backImage,\n  alt = '',\n  preset = 'bursts',\n  intensity = 0.85,\n  scale = 1,\n  edgeSparkle = 0.8,\n  frame = 4,\n  glare = 0.5,\n  foilColor = '#e2e6ec',\n  width = 320,\n  radius = 14,\n  tiltMax = 14,\n  hoverScale = 1.04,\n  idle = true,\n  shadow = true,\n  className = '',\n  style\n}: HoloCardProps) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const rotorRef = useRef<HTMLDivElement>(null);\n  const shadowRef = useRef<HTMLSpanElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const settingsRef = useRef<Settings | null>(null);\n  const pointerRef = useRef({ x: 0.5, y: 0.5, inside: false });\n  const wakeRef = useRef<(() => void) | null>(null);\n  const loadRef = useRef<((src?: string) => void) | null>(null);\n  const flipRef = useRef(false);\n  const [ratio, setRatio] = useState(CARD_RATIO);\n  const [ready, setReady] = useState(false);\n  const [flipped, setFlipped] = useState(false);\n  const flippable = Boolean(backImage);\n\n  settingsRef.current = {\n    preset: Math.max(0, PRESETS.indexOf(preset)),\n    intensity: clamp(intensity, 0, 1),\n    scale: clamp(scale, 0.25, 4),\n    edgeSparkle: clamp(edgeSparkle, 0, 1),\n    frame: clamp(frame, 0, 20) / 100,\n    glare: clamp(glare, 0, 1),\n    foil: parseColor(foilColor),\n    radius: Math.max(0, radius),\n    tiltMax: clamp(tiltMax, 0, 45),\n    hoverScale: clamp(hoverScale, 0.8, 1.3),\n    idle\n  };\n  flipRef.current = flippable && flipped;\n\n  useEffect(() => {\n    wakeRef.current?.();\n  });\n\n  useEffect(() => {\n    loadRef.current?.(image);\n  }, [image]);\n\n  useEffect(() => {\n    const root = rootRef.current;\n    const rotor = rotorRef.current;\n    const canvas = canvasRef.current;\n    if (!root || !rotor || !canvas) return undefined;\n\n    const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;\n    const gl = canvas.getContext('webgl2', { alpha: true, premultipliedAlpha: true, antialias: false });\n    let program: WebGLProgram | null = null;\n    let texture: WebGLTexture | null = null;\n    let buffer: WebGLBuffer | null = null;\n    const uniforms: Record<string, WebGLUniformLocation | null> = {};\n    let textureReady = false;\n\n    if (gl) {\n      const vertex = compile(gl, gl.VERTEX_SHADER, VERTEX);\n      const fragment = compile(gl, gl.FRAGMENT_SHADER, FRAGMENT);\n      if (vertex && fragment) {\n        program = gl.createProgram();\n        gl.attachShader(program, vertex);\n        gl.attachShader(program, fragment);\n        gl.bindAttribLocation(program, 0, 'position');\n        gl.linkProgram(program);\n        gl.deleteShader(vertex);\n        gl.deleteShader(fragment);\n        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n          gl.deleteProgram(program);\n          program = null;\n        }\n      }\n      if (program) {\n        buffer = gl.createBuffer();\n        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);\n        gl.enableVertexAttribArray(0);\n        gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n        texture = gl.createTexture();\n        gl.bindTexture(gl.TEXTURE_2D, texture);\n        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]));\n        for (const name of [\n          'uArt',\n          'uSize',\n          'uAspect',\n          'uTilt',\n          'uLight',\n          'uDistance',\n          'uPreset',\n          'uIntensity',\n          'uScale',\n          'uEdge',\n          'uFrame',\n          'uRadius',\n          'uGlare',\n          'uFoil',\n          'uReady'\n        ]) {\n          uniforms[name] = gl.getUniformLocation(program, name);\n        }\n      }\n    }\n\n    const state = {\n      tiltX: 0,\n      tiltY: 0,\n      tiltVX: 0,\n      tiltVY: 0,\n      lightX: 0.36,\n      lightY: 0.26,\n      lightVX: 0,\n      lightVY: 0,\n      lift: 1,\n      liftV: 0,\n      turn: 0,\n      turnV: 0,\n      clock: Math.random() * 40\n    };\n    let raf = 0;\n    let last = 0;\n    let visible = true;\n    let alive = true;\n    let calm = 0;\n\n    const draw = () => {\n      if (!gl || !program) return;\n      const s = settingsRef.current!;\n      const w = canvas.width;\n      const h = canvas.height;\n      if (!w || !h) return;\n      gl.viewport(0, 0, w, h);\n      gl.clearColor(0, 0, 0, 0);\n      gl.clear(gl.COLOR_BUFFER_BIT);\n      gl.useProgram(program);\n      gl.activeTexture(gl.TEXTURE0);\n      gl.bindTexture(gl.TEXTURE_2D, texture);\n      gl.uniform1i(uniforms.uArt, 0);\n      gl.uniform2f(uniforms.uSize, w, h);\n      gl.uniform1f(uniforms.uAspect, h / w);\n      gl.uniform2f(uniforms.uTilt, (state.tiltX * Math.PI) / 180, ((state.tiltY + state.turn) * Math.PI) / 180);\n      gl.uniform2f(uniforms.uLight, state.lightX, state.lightY);\n      gl.uniform1f(uniforms.uDistance, PERSPECTIVE / Math.max(1, canvas.clientWidth));\n      gl.uniform1i(uniforms.uPreset, s.preset);\n      gl.uniform1f(uniforms.uIntensity, s.intensity);\n      gl.uniform1f(uniforms.uScale, s.scale);\n      gl.uniform1f(uniforms.uEdge, s.edgeSparkle);\n      gl.uniform1f(uniforms.uFrame, s.frame);\n      gl.uniform1f(uniforms.uRadius, s.radius / Math.max(1, canvas.clientWidth));\n      gl.uniform1f(uniforms.uGlare, s.glare);\n      gl.uniform3f(uniforms.uFoil, s.foil[0], s.foil[1], s.foil[2]);\n      gl.uniform1f(uniforms.uReady, textureReady ? 1 : 0);\n      gl.drawArrays(gl.TRIANGLES, 0, 3);\n    };\n\n    const spring = (\n      value: number,\n      velocity: number,\n      target: number,\n      stiffness: number,\n      damping: number,\n      dt: number\n    ): [number, number] => {\n      const next = velocity + ((target - value) * stiffness - velocity * damping) * dt;\n      return [value + next * dt, next];\n    };\n\n    const tick = (now: number) => {\n      raf = 0;\n      if (!alive) return;\n      const dt = Math.min(1 / 30, Math.max(1 / 240, (now - last) / 1000));\n      last = now;\n      const s = settingsRef.current!;\n      const pointer = pointerRef.current;\n      let targetX = 0;\n      let targetY = 0;\n      let lightX = 0.36;\n      let lightY = 0.26;\n      let lift = 1;\n      const drifting = s.idle && !reduce && !pointer.inside;\n      if (pointer.inside && !reduce) {\n        targetX = (0.5 - pointer.y) * 2 * s.tiltMax;\n        targetY = (pointer.x - 0.5) * 2 * s.tiltMax;\n        lightX = pointer.x;\n        lightY = pointer.y;\n        lift = s.hoverScale;\n      } else if (drifting) {\n        state.clock += dt;\n        const t = state.clock;\n        targetX = Math.sin(t * 0.7) * s.tiltMax * 0.28;\n        targetY = Math.sin(t * 0.53 + 1.2) * s.tiltMax * 0.4;\n        lightX = 0.5 + Math.sin(t * 0.41) * 0.42;\n        lightY = 0.38 + Math.sin(t * 0.33 + 0.6) * 0.3;\n      }\n      const steps = Math.ceil(dt / (1 / 240));\n      const h = dt / steps;\n      for (let i = 0; i < steps; i++) {\n        [state.tiltX, state.tiltVX] = spring(state.tiltX, state.tiltVX, targetX, 150, 16, h);\n        [state.tiltY, state.tiltVY] = spring(state.tiltY, state.tiltVY, targetY, 150, 16, h);\n        [state.lightX, state.lightVX] = spring(state.lightX, state.lightVX, lightX, 220, 30, h);\n        [state.lightY, state.lightVY] = spring(state.lightY, state.lightVY, lightY, 220, 30, h);\n        [state.lift, state.liftV] = spring(state.lift, state.liftV, lift, 320, 30, h);\n        [state.turn, state.turnV] = reduce\n          ? [flipRef.current ? 180 : 0, 0]\n          : spring(state.turn, state.turnV, flipRef.current ? 180 : 0, 170, 20, h);\n      }\n      rotor.style.transform = `perspective(${PERSPECTIVE}px) scale(${state.lift}) rotateX(${state.tiltX}deg) rotateY(${state.tiltY + state.turn}deg)`;\n      if (shadowRef.current) {\n        shadowRef.current.style.transform = `translate(${-state.tiltY * 0.7}px, ${state.tiltX * 0.7}px) scale(${0.9 + (state.lift - 1) * 1.5})`;\n      }\n      if (Math.cos(((state.tiltY + state.turn) * Math.PI) / 180) > -0.05) draw();\n      const motion =\n        Math.abs(state.tiltVX) +\n        Math.abs(state.tiltVY) +\n        Math.abs(state.liftV) * 40 +\n        Math.abs(state.turnV) +\n        (Math.abs(state.lightVX) + Math.abs(state.lightVY)) * 60 +\n        Math.abs(state.tiltX - targetX) +\n        Math.abs(state.tiltY - targetY);\n      calm = motion > 0.02 ? 0 : calm + dt;\n      if (!visible || (!drifting && !pointer.inside && calm > 0.3)) return;\n      raf = requestAnimationFrame(tick);\n    };\n\n    const wake = () => {\n      if (raf || !alive) return;\n      calm = 0;\n      last = performance.now();\n      raf = requestAnimationFrame(tick);\n    };\n    wakeRef.current = wake;\n\n    const resize = () => {\n      const dpr = Math.min(window.devicePixelRatio || 1, 2);\n      const w = Math.max(1, Math.round(canvas.clientWidth * dpr));\n      const h = Math.max(1, Math.round(canvas.clientHeight * dpr));\n      if (canvas.width !== w || canvas.height !== h) {\n        canvas.width = w;\n        canvas.height = h;\n      }\n      draw();\n      wake();\n    };\n\n    loadRef.current = (src?: string) => {\n      textureReady = false;\n      setReady(false);\n      if (!src) return;\n      const img = new Image();\n      img.crossOrigin = 'anonymous';\n      img.decoding = 'async';\n      img.onload = () => {\n        if (!alive) return;\n        if (img.naturalWidth && img.naturalHeight) setRatio(img.naturalWidth / img.naturalHeight);\n        if (!gl || !program) return;\n        try {\n          gl.bindTexture(gl.TEXTURE_2D, texture);\n          gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n          gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n          gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);\n          gl.generateMipmap(gl.TEXTURE_2D);\n          textureReady = true;\n          setReady(true);\n          draw();\n          wake();\n        } catch {\n          textureReady = false;\n        }\n      };\n      img.src = src;\n    };\n    loadRef.current(image);\n\n    const resizeObserver = new ResizeObserver(resize);\n    resizeObserver.observe(canvas);\n    const visibility = new IntersectionObserver(entries => {\n      visible = entries.some(entry => entry.isIntersecting);\n      if (visible) wake();\n    });\n    visibility.observe(root);\n    resize();\n\n    return () => {\n      alive = false;\n      cancelAnimationFrame(raf);\n      wakeRef.current = null;\n      loadRef.current = null;\n      resizeObserver.disconnect();\n      visibility.disconnect();\n      if (gl) {\n        gl.deleteTexture(texture);\n        gl.deleteBuffer(buffer);\n        gl.deleteProgram(program);\n      }\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  const track = (e: ReactPointerEvent<HTMLDivElement>) => {\n    if (e.pointerType === 'touch' && e.type === 'pointermove' && !e.buttons) return;\n    const rect = e.currentTarget.getBoundingClientRect();\n    pointerRef.current = {\n      x: clamp((e.clientX - rect.left) / rect.width, 0, 1),\n      y: clamp((e.clientY - rect.top) / rect.height, 0, 1),\n      inside: true\n    };\n    wakeRef.current?.();\n  };\n\n  const leave = () => {\n    pointerRef.current = { ...pointerRef.current, inside: false };\n    wakeRef.current?.();\n  };\n\n  const toggle = () => {\n    if (flippable) setFlipped(value => !value);\n  };\n\n  return (\n    <div\n      ref={rootRef}\n      className={`holo-card${className ? ` ${className}` : ''}`}\n      role={flippable ? 'button' : 'img'}\n      tabIndex={flippable ? 0 : undefined}\n      aria-label={alt || undefined}\n      aria-pressed={flippable ? flipped : undefined}\n      data-ready={ready ? '' : undefined}\n      data-flippable={flippable ? '' : undefined}\n      onPointerEnter={track}\n      onPointerMove={track}\n      onPointerDown={track}\n      onPointerLeave={leave}\n      onPointerCancel={leave}\n      onClick={toggle}\n      onKeyDown={e => {\n        if (!flippable || (e.key !== 'Enter' && e.key !== ' ')) return;\n        e.preventDefault();\n        if (!e.repeat) toggle();\n      }}\n      style={\n        {\n          '--hc-w': `${width}px`,\n          '--hc-ratio': ratio,\n          '--hc-radius': `${radius}px`,\n          ...style\n        } as CSSProperties\n      }\n    >\n      {shadow ? <span ref={shadowRef} className=\"holo-card__shadow\" aria-hidden=\"true\" /> : null}\n      <div ref={rotorRef} className=\"holo-card__rotor\">\n        <div className=\"holo-card__face holo-card__face--front\">\n          {image ? <img src={image} alt=\"\" draggable={false} /> : null}\n          <canvas ref={canvasRef} />\n        </div>\n        {flippable ? (\n          <div className=\"holo-card__face holo-card__face--back\">\n            <img src={backImage} alt=\"\" draggable={false} />\n          </div>\n        ) : null}\n      </div>\n    </div>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}