{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dock",
  "type": "registry:component",
  "title": "Dock",
  "description": "A bar of icons that magnify under the cursor, on glass.",
  "author": "Marcelo Treviño Velázquez <https://furin.dev>",
  "dependencies": [],
  "categories": [
    "Navigation"
  ],
  "docs": "Installed in components/furin/dock/. Docs, live controls and props table: https://furin.dev/components/dock/",
  "files": [
    {
      "path": "registry/dock/Dock.tsx",
      "content": "\"use client\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport \"./Dock.css\";\n\n/* ---------- Refracción real (solo Chromium): mapa de desplazamiento generado en canvas ---------- */\n\n/** Solo Chromium acepta url(#filtro) dentro de backdrop-filter; el resto cae al desenfoque simple. */\nfunction canRefract(): boolean {\n  if (typeof CSS === \"undefined\" || typeof navigator === \"undefined\") return false;\n  return /Chrome\\//.test(navigator.userAgent) && CSS.supports(\"backdrop-filter\", \"url(#a)\");\n}\n\ninterface RefractionMap { url: string; scale: number; w: number; h: number }\n\n/** Mapa de desplazamiento: en el bisel la luz se curva hacia adentro y, con zoom > 1, el centro también amplía. */\nfunction buildRefractionMap(w: number, h: number, radius: number, bezel: number, zoom = 1): RefractionMap | null {\n  if (w < 4 || h < 4) return null;\n  const c = document.createElement(\"canvas\");\n  c.width = w;\n  c.height = h;\n  const ctx = c.getContext(\"2d\");\n  if (!ctx) return null;\n  const img = ctx.createImageData(w, h);\n  const vx = new Float32Array(w * h);\n  const vy = new Float32Array(w * h);\n  const hw = w / 2, hh = h / 2, r = Math.min(radius, hw, hh);\n  const k = 1 - 1 / zoom;\n  let max = 0.001;\n  for (let y = 0; y < h; y++) {\n    for (let x = 0; x < w; x++) {\n      const px = x + 0.5 - hw, py = y + 0.5 - hh;\n      const qx = Math.abs(px) - (hw - r), qy = Math.abs(py) - (hh - r);\n      const sdf = Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0) - r;\n      if (sdf > 0) continue;\n      let nx: number, ny: number;\n      if (qx > 0 && qy > 0) { const l = Math.hypot(qx, qy) || 1; nx = (Math.sign(px) * qx) / l; ny = (Math.sign(py) * qy) / l; }\n      else if (qx > qy) { nx = Math.sign(px); ny = 0; }\n      else { nx = 0; ny = Math.sign(py); }\n      const t = Math.min(1, -sdf / bezel);\n      const m = Math.pow(1 - t, 2.2) * bezel;\n      const dx = -nx * m - px * k, dy = -ny * m - py * k;\n      const i = y * w + x;\n      vx[i] = dx;\n      vy[i] = dy;\n      max = Math.max(max, Math.abs(dx), Math.abs(dy));\n    }\n  }\n  const d = img.data;\n  for (let i = 0; i < w * h; i++) {\n    d[i * 4] = Math.round(127.5 + (127.5 * vx[i]) / max);\n    d[i * 4 + 1] = Math.round(127.5 + (127.5 * vy[i]) / max);\n    d[i * 4 + 2] = 128;\n    d[i * 4 + 3] = 255;\n  }\n  ctx.putImageData(img, 0, 0);\n  return { url: c.toDataURL(\"image/png\"), scale: max * 2, w, h };\n}\n\n/** Mide el elemento y mantiene su mapa de refracción al día (solo si el navegador lo soporta). */\nfunction useRefractionMap(ref: React.RefObject<HTMLElement | null>, radius: number, bezel: number, zoom = 1): RefractionMap | null {\n  const [mapa, setMapa] = useState<RefractionMap | null>(null);\n  useEffect(() => {\n    if (!canRefract()) return;\n    const el = ref.current;\n    if (!el) return;\n    let raf = 0;\n    const run = () => {\n      const m = buildRefractionMap(el.offsetWidth, el.offsetHeight, radius, bezel, zoom);\n      if (m) setMapa((p) => (p && p.w === m.w && p.h === m.h ? p : m));\n    };\n    const ro = new ResizeObserver(() => { cancelAnimationFrame(raf); raf = requestAnimationFrame(run); });\n    ro.observe(el);\n    run();\n    return () => { ro.disconnect(); cancelAnimationFrame(raf); };\n  }, [ref, radius, bezel, zoom]);\n  return mapa;\n}\n\n/** Filtro SVG: desenfoque, desplazamiento por el mapa y saturación; con dispersión separa los canales RGB como un cristal real. */\nfunction RefractionFilter({ id, mapa, strength, blur = 0, saturate = 1.6, dispersion = 0 }: { id: string; mapa: RefractionMap; strength: number; blur?: number; saturate?: number; dispersion?: number }) {\n  const s = mapa.scale * strength;\n  const disp = (scale: number, result: string) => (\n    <feDisplacementMap in=\"src\" in2=\"mapa\" scale={scale} xChannelSelector=\"R\" yChannelSelector=\"G\" result={result} />\n  );\n  return (\n    <svg width=\"0\" height=\"0\" style={{ position: \"absolute\" }} aria-hidden=\"true\" focusable=\"false\">\n      <filter id={id} x=\"0\" y=\"0\" width={mapa.w} height={mapa.h} filterUnits=\"userSpaceOnUse\" colorInterpolationFilters=\"sRGB\">\n        <feImage href={mapa.url} x=\"0\" y=\"0\" width={mapa.w} height={mapa.h} preserveAspectRatio=\"none\" result=\"mapa\" />\n        <feGaussianBlur in=\"SourceGraphic\" stdDeviation={Math.max(blur, 0.01)} result=\"src\" />\n        {dispersion > 0 ? (\n          <>\n            {disp(s * (1 + dispersion), \"dr\")}\n            <feColorMatrix in=\"dr\" type=\"matrix\" values=\"1 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1 0\" result=\"r\" />\n            {disp(s, \"dg\")}\n            <feColorMatrix in=\"dg\" type=\"matrix\" values=\"0 0 0 0 0  0 1 0 0 0  0 0 0 0 0  0 0 0 1 0\" result=\"g\" />\n            {disp(s * (1 - dispersion), \"db\")}\n            <feColorMatrix in=\"db\" type=\"matrix\" values=\"0 0 0 0 0  0 0 0 0 0  0 0 1 0 0  0 0 0 1 0\" result=\"b\" />\n            <feBlend in=\"r\" in2=\"g\" mode=\"screen\" result=\"rg\" />\n            <feBlend in=\"rg\" in2=\"b\" mode=\"screen\" result=\"rgb\" />\n          </>\n        ) : (\n          disp(s, \"rgb\")\n        )}\n        <feColorMatrix in=\"rgb\" type=\"saturate\" values={String(saturate)} />\n      </filter>\n    </svg>\n  );\n}\n\n/** Valor de backdrop-filter: el filtro SVG si hay refracción; desenfoque + saturación si no. */\nfunction glassBackdrop(id: string, mapa: RefractionMap | null, blur: number, saturate = 1.6): string {\n  return mapa ? `url(#${id})` : `blur(${blur}px) saturate(${saturate}) brightness(1.06)`;\n}\n\n\nexport type DockIconName = \"home\" | \"search\" | \"mail\" | \"music\" | \"camera\" | \"sliders\" | \"chat\" | \"compass\";\n\nexport interface DockItem {\n  /** Nombre del ícono (también su etiqueta accesible y su globo). */\n  label: string;\n  /** Ícono propio o un nodo SVG tuyo. */\n  icon: DockIconName | ReactNode;\n  /** Colores del degradado del ícono: arriba y abajo. */\n  colors?: [string, string];\n  /** Muestra el puntito de \"abierto\" debajo. */\n  active?: boolean;\n}\n\nexport interface DockProps {\n  /** Íconos del dock. */\n  items?: DockItem[];\n  /** Tamaño base de cada ícono, en px. */\n  size?: number;\n  /** Cuánto crece el ícono bajo el cursor (1 = no crece). */\n  magnify?: number;\n  /** Distancia, en px, a la que el cursor todavía agranda íconos. */\n  reach?: number;\n  /** Desenfoque del fondo detrás del vidrio, en px. */\n  blur?: number;\n  /** Fuerza de la refracción en el borde del dock (0 = sin curvar la luz). */\n  refraction?: number;\n  /** Pinta una escena de color animada detrás (para verlo refractar). */\n  scene?: boolean;\n  /** Se llama al pulsar un ícono. */\n  onSelect?: (label: string) => void;\n  /** Clase extra para el contenedor. */\n  className?: string;\n  /** Estilos extra para el contenedor. */\n  style?: CSSProperties;\n}\n\nconst PALETA: [string, string][] = [\n  [\"#8fc2ff\", \"#3d63ff\"],\n  [\"#ffa6cd\", \"#f0408d\"],\n  [\"#7ae8ff\", \"#1f86f0\"],\n  [\"#ffb48f\", \"#ff3f6c\"],\n  [\"#c3a6ff\", \"#6a3df0\"],\n  [\"#c8d2e4\", \"#5d6b86\"],\n];\n\nconst ITEMS: DockItem[] = [\n  { label: \"Home\", icon: \"home\", active: true },\n  { label: \"Search\", icon: \"search\" },\n  { label: \"Mail\", icon: \"mail\", active: true },\n  { label: \"Music\", icon: \"music\" },\n  { label: \"Camera\", icon: \"camera\" },\n  { label: \"Settings\", icon: \"sliders\" },\n];\n\n/** Glifos propios, trazo blanco de 1.8 en una cuadrícula de 24. */\nfunction Glifo({ name }: { name: DockIconName }) {\n  const p = { fill: \"none\", stroke: \"currentColor\", strokeWidth: 1.8, strokeLinecap: \"round\", strokeLinejoin: \"round\" } as const;\n  return (\n    <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n      {name === \"home\" ? <path {...p} d=\"M4 11.2 12 4.5l8 6.7V19a1 1 0 0 1-1 1h-4.2v-5.6H9.2V20H5a1 1 0 0 1-1-1z\" /> : null}\n      {name === \"search\" ? (<><circle {...p} cx=\"10.8\" cy=\"10.8\" r=\"6\" /><path {...p} d=\"m15.3 15.3 4.7 4.7\" /></>) : null}\n      {name === \"mail\" ? (<><rect {...p} x=\"3.5\" y=\"5.8\" width=\"17\" height=\"12.4\" rx=\"2.6\" /><path {...p} d=\"m4.4 7.6 7.6 5.6 7.6-5.6\" /></>) : null}\n      {name === \"music\" ? (<><path {...p} d=\"M9.2 17.6V6.4l9.6-2v11.2\" /><circle {...p} cx=\"7\" cy=\"17.8\" r=\"2.2\" /><circle {...p} cx=\"16.6\" cy=\"15.8\" r=\"2.2\" /></>) : null}\n      {name === \"camera\" ? (<><path {...p} d=\"M4.5 8h2.6l1.4-2.2h7l1.4 2.2h2.6a1 1 0 0 1 1 1v8.5a1 1 0 0 1-1 1h-15a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z\" /><circle {...p} cx=\"12\" cy=\"13\" r=\"3.3\" /></>) : null}\n      {name === \"sliders\" ? (<><path {...p} d=\"M4 7h7M15 7h5M4 12h3M11 12h9M4 17h9M17 17h3\" /><circle {...p} cx=\"13\" cy=\"7\" r=\"2\" /><circle {...p} cx=\"9\" cy=\"12\" r=\"2\" /><circle {...p} cx=\"15\" cy=\"17\" r=\"2\" /></>) : null}\n      {name === \"chat\" ? <path {...p} d=\"M4.5 6.6A2.6 2.6 0 0 1 7.1 4h9.8a2.6 2.6 0 0 1 2.6 2.6v6.8a2.6 2.6 0 0 1-2.6 2.6H11l-4.6 3.6V16h-.3a1.6 1.6 0 0 1-1.6-1.6z\" /> : null}\n      {name === \"compass\" ? (<><circle {...p} cx=\"12\" cy=\"12\" r=\"8.2\" /><path {...p} d=\"m15.6 8.4-2 5.2-5.2 2 2-5.2z\" /></>) : null}\n    </svg>\n  );\n}\n\n/** Dock de vidrio: los íconos crecen bajo el cursor con un resorte y el panel curva la luz en sus bordes. */\nexport function Dock({\n  items = ITEMS,\n  size = 46,\n  magnify = 1.75,\n  reach = 110,\n  blur = 1.5,\n  refraction = 1,\n  scene = false,\n  onSelect,\n  className = \"\",\n  style,\n}: DockProps) {\n  const panel = useRef<HTMLDivElement>(null);\n  const celdas = useRef<(HTMLButtonElement | null)[]>([]);\n  const filtro = useRef<SVGFilterElement>(null);\n  const imagen = useRef<SVGFEImageElement>(null);\n  const fid = \"furin-dock-\" + useId().replace(/[^a-zA-Z0-9]/g, \"\");\n  const [ajuste, setAjuste] = useState(1);\n  const tam = Math.round(size * ajuste);\n  const gap = Math.round(tam * 0.22);\n  const mapa = useRefractionMap(panel, 30, 22);\n\n  // En contenedores angostos los íconos se achican para que el dock siga cabiendo aun con la ampliación.\n  useEffect(() => {\n    const padre = panel.current?.parentElement?.parentElement;\n    if (!padre) return;\n    const mide = () => {\n      const libre = padre.clientWidth;\n      if (!libre) return;\n      const n = items.length;\n      const reposo = n * size + (n - 1) * Math.round(size * 0.22) + 24;\n      const extra = size * (magnify - 1) * 2.2;\n      const sig = Math.max(0.55, Math.min(1, (libre - 12) / (reposo + extra)));\n      setAjuste((a) => (Math.abs(sig - a) > 0.01 ? sig : a));\n    };\n    const ro = new ResizeObserver(mide);\n    ro.observe(padre);\n    mide();\n    return () => ro.disconnect();\n  }, [items.length, size, magnify]);\n  const [rebota, setRebota] = useState(-1);\n\n  // Física: cada ícono es un resorte hacia su escala objetivo; el bucle duerme cuando el cursor se va y todo se asienta.\n  useEffect(() => {\n    const root = panel.current;\n    if (!root) return;\n    const quieto = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n    const n = items.length;\n    const st = Array.from({ length: n }, () => ({ s: 1, v: 0 }));\n    const sigma = reach / 2.1;\n    let px: number | null = null;\n    let raf = 0;\n    let t0 = 0;\n    const tick = (t: number) => {\n      const dt = Math.min(0.032, (t - t0) / 1000 || 0.016);\n      t0 = t;\n      const b = root.getBoundingClientRect();\n      const centro = b.left + b.width / 2;\n      let activo = px !== null;\n      let mejor = -1, mejorS = 1.35;\n      for (let i = 0; i < n; i++) {\n        const el = celdas.current[i];\n        if (!el) continue;\n        const reposo = (i - (n - 1) / 2) * (tam + gap);\n        const d = px === null ? 1e4 : Math.abs(px - centro - reposo);\n        const objetivo = 1 + (magnify - 1) * Math.exp(-(d * d) / (2 * sigma * sigma));\n        const o = st[i];\n        if (quieto) { o.s = objetivo; o.v = 0; }\n        else {\n          for (let k = 0; k < 2; k++) {\n            const a = 260 * (objetivo - o.s) - 21 * o.v;\n            o.v += a * (dt / 2);\n            o.s += o.v * (dt / 2);\n          }\n        }\n        if (Math.abs(objetivo - o.s) > 0.002 || Math.abs(o.v) > 0.01) activo = true;\n        const px2 = tam * o.s;\n        el.style.width = el.style.height = `${px2.toFixed(2)}px`;\n        if (o.s > mejorS) { mejor = i; mejorS = o.s; }\n      }\n      for (let i = 0; i < n; i++) celdas.current[i]?.classList.toggle(\"is-hot\", i === mejor);\n      // El filtro de refracción sigue el ancho vivo del panel; el mapa se estira, no se regenera.\n      const w = root.offsetWidth, h = root.offsetHeight;\n      const f = filtro.current, im = imagen.current;\n      if (f && im && w) {\n        f.setAttribute(\"width\", String(w));\n        f.setAttribute(\"height\", String(h));\n        im.setAttribute(\"width\", String(w));\n        im.setAttribute(\"height\", String(h));\n      }\n      raf = activo ? requestAnimationFrame(tick) : 0;\n    };\n    const wake = () => { if (!raf) { t0 = performance.now(); raf = requestAnimationFrame(tick); } };\n    const move = (e: PointerEvent) => { px = e.clientX; wake(); };\n    const leave = () => { px = null; wake(); };\n    root.addEventListener(\"pointermove\", move);\n    root.addEventListener(\"pointerdown\", move);\n    root.addEventListener(\"pointerleave\", leave);\n    root.addEventListener(\"pointercancel\", leave);\n    wake();\n    return () => {\n      root.removeEventListener(\"pointermove\", move);\n      root.removeEventListener(\"pointerdown\", move);\n      root.removeEventListener(\"pointerleave\", leave);\n      root.removeEventListener(\"pointercancel\", leave);\n      cancelAnimationFrame(raf);\n    };\n  }, [items.length, tam, gap, magnify, reach]);\n\n  const vars = { \"--furin-dock-size\": `${tam}px`, \"--furin-dock-gap\": `${gap}px` } as CSSProperties;\n  const back = glassBackdrop(fid, mapa, blur, 1.5);\n  return (\n    <>\n      {scene ? <Scene /> : null}\n      {mapa ? <RefractionFilter id={fid} mapa={mapa} strength={refraction} blur={blur} saturate={1.5} dispersion={0.1} /> : null}\n      <div className={`furin-dock ${className}`} style={{ ...vars, ...style }} role=\"toolbar\" aria-label=\"Dock\">\n        <div ref={panel} className=\"furin-dock__panel\">\n          <span className=\"furin-dock__cristal\" style={{ backdropFilter: back, WebkitBackdropFilter: back }} />\n          <span className=\"furin-dock__rim\" />\n          <div className=\"furin-dock__fila\">\n            {items.map((it, i) => {\n              const [c1, c2] = it.colors ?? PALETA[i % PALETA.length];\n              return (\n                <button\n                  key={it.label}\n                  ref={(el) => { celdas.current[i] = el; }}\n                  type=\"button\"\n                  className={`furin-dock__item${rebota === i ? \" is-bounce\" : \"\"}`}\n                  aria-label={it.label}\n                  style={{ width: tam, height: tam, \"--furin-dock-c1\": c1, \"--furin-dock-c2\": c2 } as CSSProperties}\n                  onClick={() => { setRebota(i); onSelect?.(it.label); }}\n                  onAnimationEnd={() => setRebota(-1)}\n                >\n                  <span className=\"furin-dock__icono\">{typeof it.icon === \"string\" ? <Glifo name={it.icon as DockIconName} /> : it.icon}</span>\n                  <span className=\"furin-dock__tip\" aria-hidden=\"true\">{it.label}</span>\n                  {it.active ? <span className=\"furin-dock__punto\" aria-hidden=\"true\" /> : null}\n                </button>\n              );\n            })}\n          </div>\n        </div>\n      </div>\n    </>\n  );\n}\n\nfunction Scene() {\n  return (\n    <div className=\"furin-dock-scene\" aria-hidden=\"true\">\n      <i /><i /><i /><i /><b />\n    </div>\n  );\n}\n\nexport default Dock;\n",
      "type": "registry:component",
      "target": "components/furin/dock/Dock.tsx"
    },
    {
      "path": "registry/dock/Dock.css",
      "content": ".furin-dock {\n  position: relative;\n  z-index: 1;\n  display: grid;\n  font-family: Geist, system-ui, -apple-system, sans-serif;\n  margin-top: 40px;\n}\n.furin-dock__panel {\n  position: relative;\n  border-radius: 30px;\n  padding: 12px;\n  touch-action: none;\n}\n.furin-dock__cristal {\n  position: absolute;\n  inset: 0;\n  border-radius: inherit;\n  background:\n    linear-gradient(160deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.03) 48%, rgba(255, 255, 255, 0.09)),\n    rgba(255, 255, 255, 0.06);\n  box-shadow:\n    0 24px 30px -14px rgba(5, 8, 30, 0.55),\n    0 4px 8px -2px rgba(5, 8, 30, 0.3),\n    inset 0 -10px 18px -8px rgba(255, 255, 255, 0.2),\n    inset 0 12px 16px -12px rgba(255, 255, 255, 0.42);\n}\n.furin-dock__rim {\n  position: absolute;\n  inset: 0;\n  border-radius: inherit;\n  padding: 1.4px;\n  pointer-events: none;\n  background: linear-gradient(140deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.1) 28%, rgba(255, 255, 255, 0.04) 62%, rgba(255, 255, 255, 0.55));\n  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);\n  -webkit-mask-composite: xor;\n  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);\n}\n.furin-dock__fila {\n  position: relative;\n  display: flex;\n  align-items: flex-end;\n  gap: var(--furin-dock-gap);\n  height: var(--furin-dock-size);\n}\n.furin-dock__item {\n  position: relative;\n  flex: none;\n  padding: 0;\n  border: 0;\n  background: none;\n  cursor: pointer;\n  outline: none;\n  -webkit-tap-highlight-color: transparent;\n}\n.furin-dock__icono {\n  position: absolute;\n  inset: 0;\n  display: grid;\n  place-items: center;\n  border-radius: 24%;\n  color: #fff;\n  background:\n    linear-gradient(180deg, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 46%),\n    linear-gradient(180deg, var(--furin-dock-c1), var(--furin-dock-c2));\n  box-shadow:\n    inset 0 1px 0 rgba(255, 255, 255, 0.7),\n    inset 0 -2px 3px rgba(0, 0, 20, 0.18),\n    0 0 0 0.5px rgba(0, 0, 30, 0.25),\n    0 6px 10px -4px rgba(5, 8, 40, 0.55);\n}\n.furin-dock__icono svg { width: 54%; height: 54%; filter: drop-shadow(0 1px 1.5px rgba(0, 0, 40, 0.35)); }\n.furin-dock__item:focus-visible .furin-dock__icono { box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.85); }\n.furin-dock__item.is-bounce .furin-dock__icono { animation: furin-dock-rebote 0.6s cubic-bezier(0.3, 0, 0.4, 1); }\n@keyframes furin-dock-rebote { 0%, 100% { transform: translateY(0); } 30% { transform: translateY(-16px); } 55% { transform: translateY(0); } 72% { transform: translateY(-6px); } }\n.furin-dock__tip {\n  position: absolute;\n  left: 50%;\n  bottom: calc(100% + 10px);\n  padding: 6px 11px;\n  border-radius: 999px;\n  font-size: 12.5px;\n  font-weight: 600;\n  color: #fff;\n  white-space: nowrap;\n  background: rgba(20, 24, 60, 0.55);\n  -webkit-backdrop-filter: blur(10px) saturate(1.5);\n  backdrop-filter: blur(10px) saturate(1.5);\n  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25), 0 6px 14px -6px rgba(5, 8, 30, 0.6);\n  opacity: 0;\n  transform: translate(-50%, 6px) scale(0.92);\n  transition: opacity 0.18s, transform 0.28s cubic-bezier(0.3, 1.5, 0.5, 1);\n  pointer-events: none;\n}\n.furin-dock__item.is-hot .furin-dock__tip, .furin-dock__item:focus-visible .furin-dock__tip { opacity: 1; transform: translate(-50%, 0) scale(1); }\n.furin-dock__punto {\n  position: absolute;\n  left: 50%;\n  bottom: -8px;\n  width: 4px;\n  height: 4px;\n  margin-left: -2px;\n  border-radius: 50%;\n  background: rgba(255, 255, 255, 0.9);\n  box-shadow: 0 0 6px rgba(255, 255, 255, 0.8);\n}\n@media (prefers-reduced-motion: reduce) { .furin-dock__item.is-bounce .furin-dock__icono { animation: none; } .furin-dock__tip { transition: none; } }\n\n/* Escena de color: formas con borde nítido para que la refracción se note (solo se pinta con scene). */\n.furin-dock-scene { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; background: linear-gradient(160deg, #0d1236 0%, #1b0f3d 55%, #0b2335 100%); }\n.furin-dock-scene i { position: absolute; border-radius: 50%; animation: furin-dock-flota var(--t, 14s) ease-in-out infinite alternate; }\n.furin-dock-scene i:nth-child(1) { --t: 13s; --dx: 80px; --dy: 40px; width: 190px; height: 190px; left: 6%; top: 10%; background: radial-gradient(circle at 32% 28%, #ffd0ea 0, #ff5aa5 42%, #c21f78 100%); }\n.furin-dock-scene i:nth-child(2) { --t: 17s; --dx: -70px; --dy: -50px; width: 230px; height: 230px; right: 0%; top: 28%; background: radial-gradient(circle at 34% 30%, #bcd3ff, #4f7dff 45%, #2437c9); }\n.furin-dock-scene i:nth-child(3) { --t: 11s; --dx: 90px; --dy: -60px; width: 150px; height: 150px; left: 30%; bottom: -4%; background: radial-gradient(circle at 34% 30%, #d3fff3, #37e0b9 45%, #0a8e86); }\n.furin-dock-scene i:nth-child(4) { --t: 9s; --dx: -60px; --dy: 70px; width: 90px; height: 90px; right: 28%; top: 4%; background: radial-gradient(circle at 34% 30%, #fff2cc, #ffb347 50%, #e6700f); }\n.furin-dock-scene b { position: absolute; inset: -10% -14%; background: repeating-linear-gradient(105deg, transparent 0 44px, rgba(255, 255, 255, 0.22) 44px 47px); animation: furin-dock-lineas 12s ease-in-out infinite alternate; }\n@keyframes furin-dock-flota { to { transform: translate(var(--dx), var(--dy)) scale(1.1); } }\n@keyframes furin-dock-lineas { to { transform: translateX(-70px); } }\n@media (prefers-reduced-motion: reduce) { .furin-dock-scene i, .furin-dock-scene b { animation: none; } }\n",
      "type": "registry:file",
      "target": "components/furin/dock/Dock.css"
    }
  ]
}
