{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glass-dock",
  "title": "Glass Dock",
  "description": "A macOS-style liquid-glass application dock built on the refraction engine: an always-glass bar that refracts the content behind it, with crisp icons on top. The selected item rests as a soft grey pill; tap another item or grab and drag the pill and it lifts into a glass lens that refracts and magnifies each icon and label it travels over, then settles back into a pill. The selection glass blends seamlessly into the bar (no rectangular box). Only the selected pill is draggable; tapping another item glides the lens to it.",
  "dependencies": [
    "html2canvas-pro"
  ],
  "registryDependencies": [
    "https://loreglasses.com/r/glass.json"
  ],
  "files": [
    {
      "path": "registry/default/glass-dock/glass-dock.tsx",
      "content": "\"use client\";\n\nimport { Glass, isSafariBrowser, useHydrated } from \"@/components/ui/glass\";\nimport {\n  cubicBezier,\n  glassEase,\n  MotionValue,\n  prefersReducedMotion,\n  SpringDriver,\n  tween,\n} from \"@/components/ui/glass-motion\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  type PointerEvent,\n  type ReactNode,\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\n\ntype GlassMode = \"individual\" | \"combined\";\ntype GlassSpecular = \"inside\" | \"outside\" | \"none\";\ntype ShadowTone = \"neutral\" | \"warm\" | \"cool\";\n\ninterface LiquidGlassConfig {\n  blur: { enabled: boolean; value: number };\n  mode: GlassMode;\n  refraction: { amount: number; enabled: boolean; thickness: number };\n  shadow: { tone: ShadowTone; value: number };\n  specular: GlassSpecular;\n  translucency: { enabled: boolean; value: number };\n}\n\nconst DEFAULT_LIQUID_GLASS: LiquidGlassConfig = {\n  mode: \"individual\",\n  specular: \"inside\",\n  blur: { enabled: false, value: 0 },\n  refraction: { enabled: true, amount: 20.2, thickness: 15.9 },\n  translucency: { enabled: true, value: 0 },\n  shadow: { tone: \"neutral\", value: 22 },\n};\n\ninterface ResolvedGlass {\n  chroma: number;\n  edgeHighlight: number;\n  glow: number;\n  scale: number;\n  tint: number;\n}\n\nfunction resolveGlass(config: LiquidGlassConfig): ResolvedGlass {\n  const refr = config.refraction.enabled ? config.refraction.amount / 100 : 0;\n  const specOn = config.specular !== \"none\";\n  return {\n    scale: refr * 0.5,\n    chroma: config.refraction.enabled ? Math.min(0.5, refr * 1.5) : 0,\n    tint: config.translucency.enabled ? config.translucency.value / 100 : 0,\n    edgeHighlight: specOn ? 0.3 : 0,\n    glow: specOn ? 0.12 : 0,\n  };\n}\n\nconst DOCK_W = 392;\nconst DOCK_H = 84;\nconst CAP_SCALE = 2;\nconst BOTTOM_INSET = 18;\nconst PAD_X = 10;\nconst PAD_Y = 9;\nconst ITEM_GAP = 4;\nconst COL = (DOCK_W - 2 * PAD_X - 3 * ITEM_GAP) / 4;\nconst LENS_W = COL - 4;\nconst LENS_H = 64;\nconst PILL_H = 64;\nconst PILL_RADIUS = 32;\nconst BAR_RADIUS = 42;\nconst BAR_GLASS_TRIM = 42;\nconst BAR_GLASS_MUL = 0.7;\nconst POS_SPRING = { stiffness: 90, damping: 18, restDelta: 0.5, restSpeed: 6 };\nconst MAX_MUL = 0.66;\nconst LIFT_UP = 0.16;\nconst LIFT_DOWN = 0.3;\nconst LENS_GROW = 0.2;\nconst LENS_TINT = 0.35;\nconst ARRIVE = 16;\nconst FADE_EASE = cubicBezier(0, 0, 0.58, 1);\n\nconst ACCENT = \"#58a6ff\";\nconst IDLE = \"rgba(255,255,255,0.82)\";\nconst HOVER = \"#ffffff\";\nconst BADGE_BG = \"#ff514c\";\nconst PILL_GREY = \"rgba(255,255,255,0.16)\";\nconst PILL_SHADOW =\n  \"inset 0 1px 1px rgba(255,255,255,0.22), 0 1px 3px -1px rgba(0,0,0,0.3)\";\n\nconst GRID_STYLE = {\n  top: PAD_Y,\n  right: PAD_X,\n  bottom: PAD_Y,\n  left: PAD_X,\n  gridTemplateColumns: \"repeat(4, 1fr)\",\n  gap: ITEM_GAP,\n} as const;\n\nfunction itemCenter(index: number): number {\n  return PAD_X + index * (COL + ITEM_GAP) + COL / 2;\n}\n\nfunction clamp(value: number, lo: number, hi: number): number {\n  return value < lo ? lo : value > hi ? hi : value;\n}\n\ninterface GlassDockItem {\n  badge?: ReactNode;\n  icon: ReactNode;\n  id: string;\n  label: string;\n}\n\ninterface GlassDockProps {\n  accentColor?: string;\n  align?: \"bottom\" | \"center\";\n  \"aria-label\"?: string;\n  children?: ReactNode;\n  className?: string;\n  defaultValue?: string;\n  glass?: LiquidGlassConfig;\n  hoverColor?: string;\n  idleColor?: string;\n  items: GlassDockItem[];\n  onValueChange?: (id: string) => void;\n  pillColor?: string;\n  tintBlur?: number;\n  tintColor?: string;\n  value?: string;\n}\n\nfunction GlassDock({\n  accentColor,\n  align = \"center\",\n  \"aria-label\": ariaLabel = \"Primary navigation\",\n  children,\n  className,\n  defaultValue,\n  glass,\n  hoverColor,\n  idleColor,\n  items,\n  onValueChange,\n  pillColor,\n  tintBlur,\n  tintColor,\n  value,\n}: GlassDockProps) {\n  const safari = isSafariBrowser();\n  const renderSafari = useHydrated() && safari;\n  const resolved = resolveGlass(glass ?? DEFAULT_LIQUID_GLASS);\n  const accent = accentColor ?? ACCENT;\n  const idle = idleColor ?? IDLE;\n  const hoverCol = hoverColor ?? HOVER;\n  const pillFill = pillColor ?? PILL_GREY;\n\n  const [internal, setInternal] = useState(() => defaultValue ?? items[0]?.id);\n  const [hover, setHover] = useState<number | null>(null);\n  const active = value ?? internal;\n  const activeIndex = Math.max(\n    0,\n    items.findIndex((item) => item.id === active)\n  );\n  const activeIndexRef = useRef(activeIndex);\n  activeIndexRef.current = activeIndex;\n\n  const rootRef = useRef<HTMLDivElement | null>(null);\n  const contentRef = useRef<HTMLDivElement | null>(null);\n  const overlayRef = useRef<HTMLDivElement | null>(null);\n  const barBoxRef = useRef<HTMLDivElement | null>(null);\n  const barMarkerRef = useRef<HTMLDivElement | null>(null);\n  const barGlassRef = useRef<HTMLDivElement | null>(null);\n  const lensMarkerRef = useRef<HTMLDivElement | null>(null);\n  const pillRef = useRef<HTMLDivElement | null>(null);\n\n  const appWrapRef = useRef<HTMLDivElement | null>(null);\n  const barWrapRef = useRef<HTMLDivElement | null>(null);\n  const barCanvasRef = useRef<HTMLCanvasElement | null>(null);\n  const fullCanvasRef = useRef<HTMLCanvasElement | null>(null);\n  const scrollElRef = useRef<HTMLElement | null>(null);\n  const capturedRef = useRef(false);\n  const captureWidthRef = useRef(0);\n  const recaptureTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(\n    undefined\n  );\n  const dockPos = useRef({ x: 0, y: 0 });\n\n  const cx = useRef(new MotionValue(itemCenter(activeIndex)));\n  const lift = useRef(new MotionValue(0));\n  const frame = useRef(0);\n  const initialized = useRef(false);\n  const pointerDown = useRef(false);\n  const dragAllowed = useRef(false);\n  const draggingRef = useRef(false);\n  const startX = useRef(0);\n  const liftPending = useRef(false);\n  const dropping = useRef(false);\n\n  const liftUp = useCallback(() => {\n    liftPending.current = false;\n    dropping.current = false;\n    if (prefersReducedMotion()) {\n      lift.current.jump(1);\n      return;\n    }\n    tween(lift.current, 1, LIFT_UP, glassEase);\n  }, []);\n\n  const liftDown = useCallback(() => {\n    liftPending.current = false;\n    dropping.current = true;\n    if (prefersReducedMotion()) {\n      lift.current.jump(0);\n      return;\n    }\n    tween(lift.current, 0, LIFT_DOWN, FADE_EASE);\n  }, []);\n\n  const placeStatic = useCallback(() => {\n    const root = rootRef.current;\n    const box = barBoxRef.current;\n    const mark = barMarkerRef.current;\n    if (!(root && box && mark)) {\n      return;\n    }\n    const base = root.getBoundingClientRect();\n    const r = box.getBoundingClientRect();\n    const bx = r.left - base.left;\n    const by = r.top - base.top;\n    mark.style.left = `${bx}px`;\n    mark.style.top = `${by}px`;\n    mark.style.width = `${r.width}px`;\n    mark.style.height = `${r.height}px`;\n    mark.style.borderRadius = `${BAR_RADIUS}px`;\n    const barGlass = barGlassRef.current;\n    if (barGlass) {\n      barGlass.style.left = `${bx}px`;\n      barGlass.style.top = `${by}px`;\n      barGlass.style.width = `${r.width - BAR_GLASS_TRIM}px`;\n      barGlass.style.height = `${r.height}px`;\n      barGlass.style.borderRadius = `${BAR_RADIUS}px`;\n    }\n  }, []);\n\n  const apply = useCallback(() => {\n    frame.current = 0;\n    placeStatic();\n    const lens = lensMarkerRef.current;\n    if (!lens) {\n      return;\n    }\n    const cxNow = cx.current.get();\n    if (\n      liftPending.current &&\n      Math.abs(cxNow - itemCenter(activeIndexRef.current)) <= ARRIVE\n    ) {\n      liftDown();\n    }\n    const l = clamp(lift.current.get(), 0, 1);\n    const grow = 1 + l * LENS_GROW;\n    const lensW = LENS_W * grow;\n    const lensH = LENS_H * grow;\n    lens.style.left = `${cxNow - lensW / 2}px`;\n    lens.style.top = `${(DOCK_H - lensH) / 2}px`;\n    lens.style.width = `${lensW}px`;\n    lens.style.height = `${lensH}px`;\n    lens.style.borderRadius = `${lensH / 2}px`;\n    lens.dataset.glassMul = String(l * MAX_MUL);\n    lens.dataset.glassTint = String(l * LENS_TINT);\n    const pill = pillRef.current;\n    if (pill) {\n      pill.style.left = `${cxNow - LENS_W / 2}px`;\n      pill.style.top = `${(DOCK_H - PILL_H) / 2}px`;\n      pill.style.opacity = dropping.current ? \"1\" : `${1 - l}`;\n    }\n  }, [liftDown, placeStatic]);\n\n  const schedule = useCallback(() => {\n    if (frame.current === 0) {\n      frame.current = requestAnimationFrame(apply);\n    }\n  }, [apply]);\n\n  const driver = useRef<SpringDriver | null>(null);\n  if (!driver.current) {\n    driver.current = new SpringDriver(cx.current, POS_SPRING, () =>\n      itemCenter(activeIndexRef.current)\n    );\n  }\n\n  const glide = useCallback(() => {\n    if (prefersReducedMotion()) {\n      cx.current.jump(itemCenter(activeIndexRef.current));\n      lift.current.jump(0);\n      apply();\n      return;\n    }\n    driver.current?.start();\n  }, [apply]);\n\n  const redrawBar = useCallback(() => {\n    const full = fullCanvasRef.current;\n    const bar = barCanvasRef.current;\n    const root = rootRef.current;\n    const scrollEl = scrollElRef.current;\n    if (!(full && bar && root && scrollEl)) {\n      return;\n    }\n    if (bar.width !== DOCK_W * CAP_SCALE) {\n      bar.width = DOCK_W * CAP_SCALE;\n      bar.height = DOCK_H * CAP_SCALE;\n    }\n    const ctx = bar.getContext(\"2d\");\n    if (!ctx) {\n      return;\n    }\n    const rootRect = root.getBoundingClientRect();\n    const sRect = scrollEl.getBoundingClientRect();\n    const srcX =\n      (dockPos.current.x - (sRect.left - rootRect.left) + scrollEl.scrollLeft) *\n      CAP_SCALE;\n    const srcY =\n      (dockPos.current.y - (sRect.top - rootRect.top) + scrollEl.scrollTop) *\n      CAP_SCALE;\n    ctx.clearRect(0, 0, bar.width, bar.height);\n    ctx.drawImage(\n      full,\n      srcX,\n      srcY,\n      DOCK_W * CAP_SCALE,\n      DOCK_H * CAP_SCALE,\n      0,\n      0,\n      bar.width,\n      bar.height\n    );\n  }, []);\n\n  const captureApp = useCallback(() => {\n    const wrap = appWrapRef.current;\n    if (!(renderSafari && wrap) || capturedRef.current) {\n      return;\n    }\n    captureWidthRef.current = wrap.clientWidth;\n    let scrollEl: HTMLElement = wrap;\n    for (const el of wrap.querySelectorAll<HTMLElement>(\"*\")) {\n      const oy = getComputedStyle(el).overflowY;\n      if (\n        (oy === \"auto\" || oy === \"scroll\") &&\n        el.scrollHeight > el.clientHeight + 4\n      ) {\n        scrollEl = el;\n        break;\n      }\n    }\n    scrollElRef.current = scrollEl;\n    capturedRef.current = true;\n    requestAnimationFrame(() => {\n      const fullHeight = scrollEl.scrollHeight;\n      import(\"html2canvas-pro\")\n        .then(({ default: html2canvas }) =>\n          html2canvas(scrollEl, {\n            scale: CAP_SCALE,\n            backgroundColor: null,\n            logging: false,\n            height: fullHeight,\n            windowHeight: fullHeight,\n            onclone: (_doc, clone) => {\n              clone.style.overflow = \"visible\";\n              clone.style.height = `${fullHeight}px`;\n            },\n          })\n        )\n        .then((canvas) => {\n          fullCanvasRef.current = canvas;\n          redrawBar();\n        })\n        .catch(() => {\n          capturedRef.current = false;\n        });\n    });\n  }, [redrawBar, renderSafari]);\n\n  const measure = useCallback(() => {\n    const root = rootRef.current;\n    if (!root) {\n      return;\n    }\n    if (!initialized.current) {\n      initialized.current = true;\n      const r = root.getBoundingClientRect();\n      const x = clamp((r.width - DOCK_W) / 2, 0, Math.max(0, r.width - DOCK_W));\n      const y = clamp(\n        align === \"bottom\"\n          ? r.height - DOCK_H - BOTTOM_INSET\n          : r.height * 0.6 - DOCK_H / 2,\n        0,\n        Math.max(0, r.height - DOCK_H)\n      );\n      dockPos.current = { x, y };\n      for (const el of [\n        contentRef.current,\n        overlayRef.current,\n        barWrapRef.current,\n      ]) {\n        if (el) {\n          el.style.left = `${x}px`;\n          el.style.top = `${y}px`;\n        }\n      }\n      redrawBar();\n    }\n    apply();\n  }, [apply, align, redrawBar]);\n\n  useEffect(() => {\n    const off = cx.current.on(schedule);\n    const offLift = lift.current.on(schedule);\n    cx.current.jump(itemCenter(activeIndexRef.current));\n    initialized.current = false;\n    measure();\n    captureApp();\n    const root = rootRef.current;\n    const onScroll = () => redrawBar();\n    const resize = new ResizeObserver(() => {\n      initialized.current = false;\n      measure();\n      if (renderSafari && root && root.clientWidth !== captureWidthRef.current) {\n        capturedRef.current = false;\n        if (recaptureTimerRef.current !== undefined) {\n          clearTimeout(recaptureTimerRef.current);\n        }\n        recaptureTimerRef.current = setTimeout(captureApp, 200);\n      }\n    });\n    if (root) {\n      resize.observe(root);\n      root.addEventListener(\"scroll\", onScroll, true);\n    }\n    return () => {\n      off();\n      offLift();\n      resize.disconnect();\n      root?.removeEventListener(\"scroll\", onScroll, true);\n      if (recaptureTimerRef.current !== undefined) {\n        clearTimeout(recaptureTimerRef.current);\n      }\n      cancelAnimationFrame(frame.current);\n      driver.current?.stop();\n    };\n  }, [captureApp, measure, redrawBar, renderSafari, schedule]);\n\n  const select = useCallback(\n    (index: number) => {\n      const item = items[index];\n      if (!item) {\n        return;\n      }\n      if (value === undefined) {\n        setInternal(item.id);\n      }\n      onValueChange?.(item.id);\n    },\n    [items, onValueChange, value]\n  );\n\n  const barLocalX = useCallback(\n    (clientX: number): number => {\n      const box = barBoxRef.current;\n      if (!box) {\n        return cx.current.get();\n      }\n      const r = box.getBoundingClientRect();\n      return clamp(clientX - r.left, itemCenter(0), itemCenter(items.length - 1));\n    },\n    [items.length]\n  );\n\n  const nearest = useCallback(\n    (localX: number): number => {\n      let best = 0;\n      let min = Number.POSITIVE_INFINITY;\n      for (let i = 0; i < items.length; i++) {\n        const dist = Math.abs(localX - itemCenter(i));\n        if (dist < min) {\n          min = dist;\n          best = i;\n        }\n      }\n      return best;\n    },\n    [items.length]\n  );\n\n  const goTo = useCallback(\n    (index: number) => {\n      if (index !== activeIndexRef.current) {\n        liftUp();\n      }\n      select(index);\n      liftPending.current = true;\n      glide();\n    },\n    [glide, liftUp, select]\n  );\n\n  const onPointerDown = (event: PointerEvent<HTMLDivElement>) => {\n    pointerDown.current = true;\n    draggingRef.current = false;\n    startX.current = event.clientX;\n    dragAllowed.current = nearest(barLocalX(event.clientX)) === activeIndex;\n    if (dragAllowed.current) {\n      overlayRef.current?.setPointerCapture(event.pointerId);\n      driver.current?.stop();\n      liftUp();\n    }\n  };\n\n  const onPointerMove = (event: PointerEvent<HTMLDivElement>) => {\n    if (!(pointerDown.current && dragAllowed.current)) {\n      return;\n    }\n    if (!draggingRef.current && Math.abs(event.clientX - startX.current) > 4) {\n      draggingRef.current = true;\n    }\n    if (!draggingRef.current) {\n      return;\n    }\n    const x = barLocalX(event.clientX);\n    cx.current.set(x);\n    setHover(nearest(x));\n  };\n\n  const onPointerUp = (event: PointerEvent<HTMLDivElement>) => {\n    if (!pointerDown.current) {\n      return;\n    }\n    pointerDown.current = false;\n    if (draggingRef.current) {\n      draggingRef.current = false;\n      setHover(null);\n      select(nearest(barLocalX(event.clientX)));\n      liftDown();\n      glide();\n      return;\n    }\n    goTo(nearest(barLocalX(startX.current)));\n  };\n\n  const iconNodes = items.map((item, index) => {\n    const isActive = index === activeIndex;\n    const isHover = index === hover;\n    const iconColor = isActive ? accent : isHover ? hoverCol : idle;\n    const labelColor = isActive ? idle : isHover ? hoverCol : idle;\n    return (\n      <div\n        className=\"relative grid place-items-center content-center\"\n        key={item.id}\n        style={{ gap: 5, padding: \"10px 5px 9px\", color: labelColor }}\n      >\n        <span\n          className=\"grid size-[28px] place-items-center\"\n          style={{ color: iconColor }}\n        >\n          {item.icon}\n        </span>\n        <span\n          className=\"max-w-full overflow-hidden text-ellipsis whitespace-nowrap font-[650] text-[12px] leading-none\"\n          style={{ color: labelColor }}\n        >\n          {item.label}\n        </span>\n        {item.badge == null ? null : (\n          <span\n            className=\"absolute grid place-items-center text-[10px] text-white\"\n            style={{\n              top: 4,\n              right: 5,\n              minWidth: 20,\n              height: 18,\n              padding: \"0 5px\",\n              borderRadius: 999,\n              background: BADGE_BG,\n              fontWeight: 750,\n            }}\n          >\n            {item.badge}\n          </span>\n        )}\n      </div>\n    );\n  });\n\n  const dark =\n    typeof document !== \"undefined\" &&\n    document.documentElement.classList.contains(\"dark\");\n  const barTintRGB = tintColor ?? (dark ? \"58,58,62\" : \"255,255,255\");\n  const barTintEff = 0.5 * resolved.tint;\n  const barFrostBlur = barTintEff * (tintBlur ?? 12);\n  const barFrostFilter =\n    barFrostBlur > 0.05\n      ? `blur(${barFrostBlur}px) saturate(${1 + 0.5 * barTintEff})`\n      : \"none\";\n  const barFrostBg =\n    barTintEff > 0.001\n      ? `rgba(${barTintRGB},${Math.round(barTintEff * 700) / 1000})`\n      : \"transparent\";\n\n  return (\n    <div\n      className={cn(\"relative overflow-hidden\", className)}\n      onDragStart={(event) => event.preventDefault()}\n      ref={rootRef}\n    >\n      {renderSafari ? (\n        <>\n          <div className=\"absolute inset-0\" ref={appWrapRef}>\n            {children}\n          </div>\n          <div\n            className=\"pointer-events-none absolute\"\n            ref={barWrapRef}\n            style={{ width: DOCK_W, height: DOCK_H }}\n          >\n            <Glass\n              chroma={0.12}\n              className=\"absolute inset-0\"\n              depth={12}\n              domeDepth={12}\n              edgeHighlight={0}\n              glow={0}\n              lens={\n                <div\n                  className=\"absolute inset-0\"\n                  data-glass-lens\n                  style={{ borderRadius: BAR_RADIUS }}\n                />\n              }\n              reveal\n              scaleX={resolved.scale}\n              scaleY={resolved.scale * 1.1}\n              tint={0}\n            >\n              <canvas\n                className=\"absolute inset-0 h-full w-full\"\n                ref={barCanvasRef}\n              />\n            </Glass>\n            <div\n              className=\"absolute inset-0\"\n              style={{\n                borderRadius: BAR_RADIUS,\n                background: barFrostBg,\n                backdropFilter: barFrostFilter,\n                WebkitBackdropFilter: barFrostFilter,\n              }}\n            />\n          </div>\n        </>\n      ) : (\n        <Glass\n          chroma={0.12}\n          className=\"pointer-events-none absolute inset-0\"\n          depth={12}\n          domeDepth={12}\n          edgeHighlight={0}\n          glow={0}\n          lens={\n            <>\n              <div\n                className=\"absolute\"\n                data-glass-edge-highlight={0}\n                data-glass-glow={0}\n                data-glass-lens\n                data-glass-mul={0}\n                ref={barMarkerRef}\n              />\n              <div\n                className=\"absolute\"\n                data-glass-edge-highlight={0}\n                data-glass-glow={0}\n                data-glass-lens\n                data-glass-mul={BAR_GLASS_MUL}\n                data-glass-tint={0}\n                ref={barGlassRef}\n              />\n            </>\n          }\n          scaleX={resolved.scale}\n          scaleY={resolved.scale}\n          tint={resolved.tint}\n          tintBlur={tintBlur ?? 12}\n          tintColor={tintColor}\n        >\n          {children}\n        </Glass>\n      )}\n      <div\n        className=\"pointer-events-none absolute\"\n        ref={contentRef}\n        style={{ width: DOCK_W, height: DOCK_H }}\n      >\n        <Glass\n          chroma={0.5}\n          className=\"absolute inset-0\"\n          depth={14}\n          domeDepth={20}\n          edgeHighlight={resolved.edgeHighlight}\n          glow={resolved.glow}\n          lens={\n            <div\n              className=\"absolute\"\n              data-glass-dome-depth={20}\n              data-glass-lens\n              data-glass-mul={0}\n              ref={lensMarkerRef}\n            />\n          }\n          scaleX={resolved.scale}\n          scaleY={safari ? resolved.scale * 1.1 : resolved.scale}\n          tint={0}\n          tintBlur={4}\n        >\n          <div className=\"absolute inset-0 overflow-hidden\">\n            <div\n              className=\"pointer-events-none absolute\"\n              ref={pillRef}\n              style={{\n                width: LENS_W,\n                height: PILL_H,\n                borderRadius: PILL_RADIUS,\n                background: pillFill,\n                boxShadow: PILL_SHADOW,\n              }}\n            />\n            <div className=\"absolute grid text-white\" style={GRID_STYLE}>\n              {iconNodes}\n            </div>\n          </div>\n        </Glass>\n      </div>\n      <div\n        aria-label={ariaLabel}\n        className=\"absolute touch-none select-none\"\n        onPointerCancel={onPointerUp}\n        onPointerDown={onPointerDown}\n        onPointerMove={onPointerMove}\n        onPointerUp={onPointerUp}\n        ref={overlayRef}\n        style={{ width: DOCK_W, height: DOCK_H }}\n      >\n        <div className=\"relative size-full\" ref={barBoxRef}>\n          <div className=\"absolute grid\" style={GRID_STYLE}>\n            {items.map((item, index) => (\n              <button\n                aria-current={index === activeIndex ? \"page\" : undefined}\n                aria-label={item.label}\n                className={cn(\n                  \"rounded-[38px] outline-none\",\n                  index === activeIndex\n                    ? \"cursor-grab active:cursor-grabbing\"\n                    : \"cursor-pointer\"\n                )}\n                key={item.id}\n                onClick={(event) => {\n                  if (event.detail === 0) {\n                    goTo(index);\n                  }\n                }}\n                onPointerEnter={() => {\n                  if (!draggingRef.current) {\n                    setHover(index);\n                  }\n                }}\n                onPointerLeave={() => {\n                  if (!draggingRef.current) {\n                    setHover((h) => (h === index ? null : h));\n                  }\n                }}\n                type=\"button\"\n              />\n            ))}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport { DEFAULT_LIQUID_GLASS, GlassDock, resolveGlass };\nexport type {\n  GlassDockItem,\n  GlassDockProps,\n  GlassMode,\n  GlassSpecular,\n  LiquidGlassConfig,\n  ShadowTone,\n};\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Import { GlassDock } from \"@/components/ui/glass-dock\". The always-on bar marker refracts the backdrop (children). The selection is a second always-mounted lens marker whose glass intensity is driven by a `lift` motion value (0 at rest = a crisp grey pill, no glass). Selecting an item (tap, keyboard, or drag-release) lifts the glass to full, springs the lens to the new item while it refracts and magnifies whatever it slides over, then settles back to a pill (grey -> glass -> grey). The icons render in two crisp/refracted copies that cross-fade with `lift`. The lens marker sets `data-glass-blend=\"1\"` so its refraction alpha-blends into the bar instead of overwriting it (no seam/box); both markers stay mounted so the glass never blanks. A transparent button layer handles clicks/hover/keyboard. Pass `items` as an array of { id, label, icon, badge? }; control selection with value / defaultValue / onValueChange; optionally pass screen content as children. Drag uses pointer capture so the grab cursor holds, and native image-drag is disabled. Tune the material with the optional `glass` prop (a LiquidGlassConfig: mode, specular, blur, refraction { amount, thickness }, translucency, shadow); DEFAULT_LIQUID_GLASS is exported.",
  "type": "registry:ui"
}