{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glass-tabs",
  "title": "Glass Tabs",
  "description": "Base UI tabs with a liquid-glass selection indicator: a spring-driven lens glides between options, deforming like a droplet with velocity and refracting the highlighted option beneath it.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://loreglasses.com/r/glass.json"
  ],
  "files": [
    {
      "path": "registry/default/glass-tabs/glass-tabs.tsx",
      "content": "\"use client\";\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport { Glass, isSafariBrowser, useGlassDark } from \"@/components/ui/glass\";\nimport {\n  MotionValue,\n  prefersReducedMotion,\n  SpringDriver,\n} from \"@/components/ui/glass-motion\";\nimport { cn } from \"@/lib/utils\";\nimport { useCallback, useEffect, useRef } from \"react\";\n\nconst PAD_X = 40;\nconst PAD_Y = 80;\nconst INDICATOR_SPRING = { stiffness: 50, damping: 13 };\nconst DEFORM_SPRING = { stiffness: 66, damping: 9 };\nconst VELOCITY_SCALE = 0.134;\nconst DEFORM_CLAMP = 0.3;\nconst SQUEEZE_X = 0.3;\nconst STRETCH_X = 2;\nconst RATIO_Y = 4;\n\nconst BG1_LIGHT = \"#faf9f9\";\nconst BG1_DARK = \"#100f0f\";\nconst BORDER1_LIGHT = \"#2e0f0f14\";\nconst BORDER1_DARK = \"#ffffff14\";\nconst BODY_LIGHT = \"#fff\";\nconst BODY_DARK = \"#100f0f\";\n\nfunction Tabs({ className, ...props }: TabsPrimitive.Root.Props) {\n  return (\n    <TabsPrimitive.Root\n      className={cn(\"flex flex-col gap-3\", className)}\n      data-slot=\"tabs\"\n      {...props}\n    />\n  );\n}\n\ninterface TabsListProps extends TabsPrimitive.List.Props {\n  tint?: number;\n}\n\nfunction TabsList({\n  className,\n  children,\n  tint = 0,\n  ...props\n}: TabsListProps) {\n  const dark = useGlassDark();\n  const safari = isSafariBrowser();\n  const listRef = useRef<HTMLDivElement | null>(null);\n  const markerRef = useRef<HTMLDivElement | null>(null);\n\n  const cx = useRef(new MotionValue(0));\n  const cy = useRef(new MotionValue(0));\n  const hw = useRef(new MotionValue(0));\n  const hh = useRef(new MotionValue(0));\n  const deform = useRef(new MotionValue(0));\n  const initialized = useRef(false);\n  const frame = useRef(0);\n\n  const apply = useCallback(() => {\n    frame.current = 0;\n    const marker = markerRef.current;\n    if (!marker) {\n      return;\n    }\n    const q = Math.max(0, deform.current.get());\n    const widthFactor = 1 - q * (q > 0 ? SQUEEZE_X : STRETCH_X);\n    const heightFactor = 1 + q * RATIO_Y;\n    const w = Math.max(hw.current.get() * 2 * widthFactor, 0);\n    const h = Math.max(hh.current.get() * 2 * heightFactor, 0);\n    marker.style.width = `${w}px`;\n    marker.style.height = `${h}px`;\n    marker.style.borderRadius = `${Math.min(w, h) / 2}px`;\n    marker.style.left = `${PAD_X + cx.current.get() - w / 2}px`;\n    marker.style.top = `${PAD_Y + cy.current.get() - h / 2}px`;\n  }, []);\n\n  const schedule = useCallback(() => {\n    if (frame.current === 0) {\n      frame.current = requestAnimationFrame(apply);\n    }\n  }, [apply]);\n\n  const springTargets = useRef({ cx: 0, cy: 0, hw: 0, hh: 0 });\n  const springDrivers = useRef<SpringDriver[] | null>(null);\n  if (!springDrivers.current) {\n    springDrivers.current = [\n      new SpringDriver(cx.current, INDICATOR_SPRING, () => springTargets.current.cx),\n      new SpringDriver(cy.current, INDICATOR_SPRING, () => springTargets.current.cy),\n      new SpringDriver(hw.current, INDICATOR_SPRING, () => springTargets.current.hw),\n      new SpringDriver(hh.current, INDICATOR_SPRING, () => springTargets.current.hh),\n    ];\n  }\n\n  const deformDriver = useRef<SpringDriver | null>(null);\n  if (!deformDriver.current) {\n    deformDriver.current = new SpringDriver(\n      deform.current,\n      DEFORM_SPRING,\n      () => {\n        const list = listRef.current;\n        const width = list\n          ? list.getBoundingClientRect().width + 2 * PAD_X\n          : 1;\n        const vNorm = Math.abs(cx.current.getVelocity()) / Math.max(width, 1);\n        return Math.min(DEFORM_CLAMP, Math.sqrt(vNorm) * VELOCITY_SCALE);\n      },\n      () => Math.abs(cx.current.getVelocity()) < 0.005\n    );\n  }\n\n  const measure = useCallback(() => {\n    const list = listRef.current;\n    if (!list) {\n      return;\n    }\n    const active = list.querySelector<HTMLElement>('[aria-selected=\"true\"]');\n    if (!active) {\n      return;\n    }\n    const listRect = list.getBoundingClientRect();\n    const rect = active.getBoundingClientRect();\n    const targets = springTargets.current;\n    targets.cx = rect.left - listRect.left + rect.width / 2;\n    targets.cy = rect.top - listRect.top + rect.height / 2;\n    targets.hw = rect.width / 2;\n    targets.hh = rect.height / 2;\n    if (!initialized.current || prefersReducedMotion()) {\n      initialized.current = true;\n      cx.current.jump(targets.cx);\n      cy.current.jump(targets.cy);\n      hw.current.jump(targets.hw);\n      hh.current.jump(targets.hh);\n      apply();\n      return;\n    }\n    for (const driver of springDrivers.current ?? []) {\n      driver.start();\n    }\n    deformDriver.current?.start();\n  }, [apply]);\n\n  useEffect(() => {\n    const subs = [\n      cx.current.on(() => {\n        schedule();\n        deformDriver.current?.start();\n      }),\n      cy.current.on(schedule),\n      hw.current.on(schedule),\n      hh.current.on(schedule),\n      deform.current.on(schedule),\n    ];\n    measure();\n    const list = listRef.current;\n    const observer = new MutationObserver(measure);\n    if (list) {\n      observer.observe(list, {\n        subtree: true,\n        attributes: true,\n        attributeFilter: [\"aria-selected\"],\n      });\n    }\n    const resize = new ResizeObserver(() => {\n      initialized.current = false;\n      measure();\n    });\n    if (list) {\n      resize.observe(list);\n    }\n    return () => {\n      for (const off of subs) {\n        off();\n      }\n      observer.disconnect();\n      resize.disconnect();\n      cancelAnimationFrame(frame.current);\n      for (const driver of springDrivers.current ?? []) {\n        driver.stop();\n      }\n      deformDriver.current?.stop();\n    };\n  }, [measure, schedule]);\n\n  const config = dark\n    ? {\n        brightness: 0.06,\n        specularAngle: 45,\n        glow: 0.5,\n        glowSpread: 0.3,\n        glowExponent: 1.5,\n        edgeHighlight: 0.6,\n        edgeWidth: 1,\n        edgeExponent: 1.5,\n        specularDark: false,\n      }\n    : {\n        brightness: -0.04,\n        specularAngle: 28,\n        glow: 0,\n        glowSpread: 0.5,\n        glowExponent: 3,\n        edgeHighlight: 0.15,\n        edgeWidth: 1.5,\n        edgeExponent: 1,\n        specularDark: true,\n      };\n\n  const pillStyle = {\n    background: dark ? BG1_DARK : BG1_LIGHT,\n    border: `1px solid ${dark ? BORDER1_DARK : BORDER1_LIGHT}`,\n  };\n\n  return (\n    <TabsPrimitive.List\n      className={cn(\"relative inline-flex rounded-full p-[2px]\", className)}\n      data-slot=\"tabs-list\"\n      ref={listRef}\n      style={pillStyle}\n      {...props}\n    >\n      <Glass\n        chroma={0.1}\n        className=\"pointer-events-none absolute z-0\"\n        depth={2.5}\n        domeDepth={0}\n        edgeExponent={config.edgeExponent}\n        edgeHighlight={config.edgeHighlight}\n        edgeWidth={config.edgeWidth}\n        glow={config.glow}\n        glowExponent={config.glowExponent}\n        glowSpread={config.glowSpread}\n        lens={\n          <div className=\"absolute\" data-glass-lens ref={markerRef}>\n            <div\n              className=\"absolute inset-0 rounded-[inherit]\"\n              style={{\n                background: config.brightness >= 0 ? \"#fff\" : \"#000\",\n                opacity: Math.abs(config.brightness),\n              }}\n            />\n          </div>\n        }\n        reveal\n        scaleX={0.045}\n        scaleY={safari ? 0.075 : 0.025}\n        specularAngle={config.specularAngle}\n        specularDark={config.specularDark}\n        specularStrength={1}\n        splay={1}\n        tint={tint}\n        style={{\n          left: -PAD_X,\n          top: -PAD_Y,\n          width: `calc(100% + ${2 * PAD_X}px)`,\n          height: `calc(100% + ${2 * PAD_Y}px)`,\n        }}\n      >\n        <div\n          className=\"absolute inset-0\"\n          style={{ background: dark ? BODY_DARK : BODY_LIGHT }}\n        />\n      </Glass>\n      <div className=\"relative z-10 flex\">{children}</div>\n    </TabsPrimitive.List>\n  );\n}\n\nfunction TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {\n  return (\n    <TabsPrimitive.Tab\n      className={cn(\n        \"inline-flex cursor-pointer items-center gap-[6px] whitespace-nowrap rounded-full px-[13px] py-[10px] font-medium text-[#727274] text-sm leading-none transition-colors hover:text-[#5a5858] focus-visible:shadow-[0_0_0_2px_#9896ff] focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:text-black dark:text-[#8f8e8e] dark:hover:text-[#bcbbbb] dark:data-[active]:text-white\",\n        className\n      )}\n      data-slot=\"tabs-trigger\"\n      {...props}\n    />\n  );\n}\n\nfunction TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {\n  return (\n    <TabsPrimitive.Panel\n      className={cn(\n        \"ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n        className\n      )}\n      data-slot=\"tabs-content\"\n      {...props}\n    />\n  );\n}\n\nexport { Tabs, TabsContent, TabsList, TabsTrigger };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Base UI tabs with a glass selection indicator. Import the parts from \"@/components/ui/glass-tabs\" and compose GlassTabs / GlassTabsList / GlassTabsTab / GlassTabsPanel. The lens springs between tabs and sits behind the labels so they stay legible. Accepts `tint` (0 clear to 1 tinted).",
  "type": "registry:ui"
}