{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "liquid-glass-navbar",
  "title": "Liquid Glass Navbar",
  "author": "STARCK",
  "description": "SessionWatcher's responsive navbar with expandable or fixed width, real Chromium refraction, and a blur fallback.",
  "dependencies": [
    "framer-motion@^11.0.0"
  ],
  "registryDependencies": [
    "https://ui.starck.studio/r/blue-cta-button.json"
  ],
  "files": [
    {
      "path": "registry/default/liquid-glass-navbar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  motion,\n  useReducedMotion,\n  useScroll,\n  useTransform,\n} from \"framer-motion\"\n\nimport {\n  AppleMark,\n  blueCtaClassName,\n  blueCtaStyle,\n} from \"@/components/blue-cta-button\"\nimport { createLiquidGlass } from \"@/lib/liquid-glass\"\n\ntype NavLink = {\n  href: string\n  label: string\n}\n\ntype NavbarWidthBehavior = \"expand\" | \"fixed\"\n\ntype LiquidGlassNavbarProps = {\n  brand?: string\n  version?: string\n  logoSrc?: string\n  logoAlt?: string\n  links?: readonly NavLink[]\n  ctaHref?: string\n  ctaLabel?: string\n  widthBehavior?: NavbarWidthBehavior\n  contained?: boolean\n  scrollContainerRef?: React.RefObject<HTMLElement | null>\n  className?: string\n}\n\nconst defaultLinks: NavLink[] = [\n  { href: \"#pricing\", label: \"Pricing\" },\n  { href: \"#features\", label: \"Features\" },\n  { href: \"#faq\", label: \"FAQ\" },\n]\n\nfunction BrandLogo({\n  src,\n  alt,\n  className,\n}: {\n  src?: string\n  alt: string\n  className: string\n}) {\n  if (src) {\n    return (\n      // This registry component is framework-neutral, so Next Image is not used.\n      // eslint-disable-next-line @next/next/no-img-element\n      <img\n        src={src}\n        alt={alt}\n        className={`${className} rounded-[9px] object-cover`}\n      />\n    )\n  }\n\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={`flex ${className} items-center justify-center rounded-[9px] bg-zinc-950 text-xs font-semibold text-white dark:bg-white dark:text-zinc-950`}\n    >\n      S\n    </span>\n  )\n}\n\nfunction LiquidGlassNavbar({\n  brand = \"SessionWatcher\",\n  version = \"v7.3.0\",\n  logoSrc = \"https://sessionwatcher.com/img/NewLogoModernBlack.png\",\n  logoAlt,\n  links = defaultLinks,\n  ctaHref = \"#\",\n  ctaLabel = \"Get SessionWatcher\",\n  widthBehavior = \"expand\",\n  contained = false,\n  scrollContainerRef,\n  className,\n}: LiquidGlassNavbarProps) {\n  const prefersReducedMotion = useReducedMotion()\n  const { scrollY } = useScroll({\n    container: scrollContainerRef,\n    layoutEffect: false,\n  })\n  const animatedWidth = useTransform(scrollY, [0, 400], [\"50%\", \"120%\"])\n  const containedWidth = useTransform(scrollY, [0, 320], [\"76%\", \"100%\"])\n  const mobileNav = React.useRef<HTMLElement>(null)\n  const desktopNav = React.useRef<HTMLElement>(null)\n\n  React.useEffect(() => {\n    let active = true\n    let instance: ReturnType<typeof createLiquidGlass> | null = null\n    const media = window.matchMedia(\"(min-width: 640px)\")\n\n    const mount = () => {\n      instance?.destroy()\n      instance = null\n      const element = media.matches ? desktopNav.current : mobileNav.current\n\n      if (active && element && element.offsetWidth > 0) {\n        instance = createLiquidGlass(element, {\n          borderRadius: Math.round(element.offsetHeight / 2),\n          blur: 9,\n          scale: -110,\n          aberration: [0, 6, 12],\n          saturation: 1.3,\n          frost: 0.05,\n        })\n      }\n    }\n\n    media.addEventListener(\"change\", mount)\n    mount()\n\n    return () => {\n      active = false\n      instance?.destroy()\n      media.removeEventListener(\"change\", mount)\n    }\n  }, [widthBehavior])\n\n  const positioning = contained\n    ? \"sticky top-4 inset-x-0 h-0\"\n    : \"fixed top-4 inset-x-0\"\n\n  return (\n    <header\n      role=\"banner\"\n      className={[\n        positioning,\n        \"z-50 flex items-start justify-center px-4 transition-opacity duration-300\",\n        className,\n      ]\n        .filter(Boolean)\n        .join(\" \")}\n    >\n      <nav\n        ref={mobileNav}\n        aria-label=\"Main navigation\"\n        className=\"block w-full max-w-4xl rounded-full px-4 py-3 backdrop-blur-md sm:hidden\"\n      >\n        <div className=\"flex items-center justify-between\">\n          <a href=\"#\" className=\"flex shrink-0 items-center gap-1.5\">\n            <BrandLogo\n              src={logoSrc}\n              alt={logoAlt ?? brand}\n              className=\"size-6\"\n            />\n            <span className=\"relative text-lg leading-6 font-bold tracking-tighter whitespace-nowrap\">\n              {brand}\n              {version ? (\n                <span className=\"absolute -top-2 -right-2 rounded-full border border-border/60 bg-foreground/[0.04] px-2 py-0.5 text-[11px] leading-none font-medium text-muted-foreground\">\n                  {version}\n                </span>\n              ) : null}\n            </span>\n          </a>\n          <a\n            href={ctaHref}\n            className={`${blueCtaClassName} px-3.5 py-1.5 pl-2.5 text-xs`}\n            style={blueCtaStyle}\n          >\n            <AppleMark className=\"size-3.5\" />\n            {ctaLabel}\n          </a>\n        </div>\n      </nav>\n\n      <motion.nav\n        ref={desktopNav}\n        aria-label=\"Main navigation\"\n        className=\"hidden max-w-[1200px] rounded-full px-3 py-3 pl-4 backdrop-blur-md sm:block\"\n        style={{\n          minWidth: contained ? \"min(100%, 44rem)\" : undefined,\n          width:\n            widthBehavior === \"fixed\"\n              ? \"100%\"\n              : contained\n                ? prefersReducedMotion\n                  ? \"100%\"\n                  : containedWidth\n                : prefersReducedMotion\n                  ? \"85%\"\n                  : animatedWidth,\n        }}\n      >\n        <div className=\"relative flex items-center justify-between\">\n          <a href=\"#\" className=\"flex shrink-0 items-center gap-2\">\n            <BrandLogo\n              src={logoSrc}\n              alt={logoAlt ?? brand}\n              className=\"size-8\"\n            />\n            <span className=\"relative text-2xl leading-8 font-bold tracking-tighter whitespace-nowrap\">\n              {brand}\n              {version ? (\n                <span className=\"absolute -top-2.5 -right-2 rounded-full border border-border/60 bg-foreground/[0.04] px-2 py-0.5 text-[12px] leading-none font-medium text-muted-foreground\">\n                  {version}\n                </span>\n              ) : null}\n            </span>\n          </a>\n          <div className=\"absolute left-1/2 flex -translate-x-1/2 items-center gap-5\">\n            {links.map((link) => (\n              <a\n                key={`${link.href}-${link.label}`}\n                href={link.href}\n                className=\"text-sm font-medium text-muted-foreground transition-colors hover:text-foreground\"\n              >\n                {link.label}\n              </a>\n            ))}\n          </div>\n          <a\n            href={ctaHref}\n            className={`${blueCtaClassName} shrink-0`}\n            style={blueCtaStyle}\n          >\n            <AppleMark className=\"size-[15px]\" />\n            {ctaLabel}\n          </a>\n        </div>\n      </motion.nav>\n    </header>\n  )\n}\n\nexport { LiquidGlassNavbar }\nexport type { LiquidGlassNavbarProps, NavbarWidthBehavior, NavLink }\n",
      "type": "registry:component",
      "target": "components/liquid-glass-navbar.tsx"
    },
    {
      "path": "registry/default/liquid-glass.ts",
      "content": "/**\n * Liquid Glass: optical refraction for the web.\n * Adapted from https://github.com/rizzytoday/liquid-glass\n * Chromium uses SVG displacement; other browsers receive backdrop blur.\n *\n * MIT License\n * Copyright (c) 2026 Riz Roze\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\ntype LiquidGlassOptions = {\n  width?: number\n  height?: number\n  borderRadius?: number\n  scale?: number\n  border?: number\n  lightness?: number\n  alpha?: number\n  blur?: number\n  aberration?: [number, number, number]\n  frost?: number\n  saturation?: number\n  displaceBlur?: number\n  fallbackFilter?: string\n  filterId?: string\n}\n\ntype LiquidGlassConfig = {\n  width: number\n  height: number\n  radius: number\n  scale: number\n  border: number\n  lightness: number\n  alpha: number\n  blur: number\n  r: number\n  g: number\n  b: number\n  frost: number\n  saturation: number\n  displace: number\n}\n\ntype FilterRefs = {\n  svg: SVGSVGElement\n  filter: SVGFilterElement\n  image: SVGFEImageElement\n  red: SVGFEDisplacementMapElement\n  green: SVGFEDisplacementMapElement\n  blue: SVGFEDisplacementMapElement\n  blur: SVGFEGaussianBlurElement\n}\n\ntype LiquidGlassInstance = {\n  isActive: boolean\n  filterElement: SVGSVGElement\n  update: (options: Partial<LiquidGlassOptions>) => void\n  destroy: () => void\n}\n\nconst isChromium =\n  typeof navigator !== \"undefined\" && /Chrome\\//.test(navigator.userAgent)\n\nconst mapCache = new Map<string, string>()\nlet instanceCount = 0\n\nfunction resolveConfig(\n  element: HTMLElement,\n  options: LiquidGlassOptions\n): LiquidGlassConfig {\n  const rect = element.getBoundingClientRect()\n  const aberration = options.aberration ?? [0, 10, 20]\n\n  return {\n    width: options.width ?? Math.round(rect.width),\n    height: options.height ?? Math.round(rect.height),\n    radius: options.borderRadius ?? 50,\n    scale: options.scale ?? -180,\n    border: options.border ?? 0.07,\n    lightness: options.lightness ?? 50,\n    alpha: options.alpha ?? 0.93,\n    blur: options.blur ?? 11,\n    r: aberration[0],\n    g: aberration[1],\n    b: aberration[2],\n    frost: options.frost ?? 0,\n    saturation: options.saturation ?? 1,\n    displace: options.displaceBlur ?? 0,\n  }\n}\n\nfunction buildDisplacementMap(config: LiquidGlassConfig) {\n  const key = [\n    config.width,\n    config.height,\n    config.radius,\n    config.scale,\n    config.border,\n    config.blur,\n    config.lightness,\n    config.alpha,\n  ].join(\":\")\n  const cached = mapCache.get(key)\n  if (cached) return cached\n\n  const maxDisplace = Math.max(Math.abs(config.scale) * 0.5, 20)\n  const padding = Math.ceil(maxDisplace)\n  const canvas = document.createElement(\"canvas\")\n  canvas.width = config.width + padding * 2\n  canvas.height = config.height + padding * 2\n  const context = canvas.getContext(\"2d\")\n  if (!context) throw new Error(\"Liquid Glass needs Canvas 2D support.\")\n\n  context.fillStyle = \"rgb(128, 128, 128)\"\n  context.fillRect(0, 0, canvas.width, canvas.height)\n  context.save()\n  context.beginPath()\n  context.roundRect(\n    padding,\n    padding,\n    config.width,\n    config.height,\n    config.radius\n  )\n  context.clip()\n  context.fillStyle = \"#000\"\n  context.fillRect(padding, padding, config.width, config.height)\n\n  const red = context.createLinearGradient(\n    padding + config.width,\n    padding,\n    padding,\n    padding\n  )\n  red.addColorStop(0, \"#000\")\n  red.addColorStop(1, \"#f00\")\n  context.fillStyle = red\n  context.fillRect(padding, padding, config.width, config.height)\n\n  context.globalCompositeOperation = \"difference\"\n  const blue = context.createLinearGradient(\n    padding,\n    padding,\n    padding,\n    padding + config.height\n  )\n  blue.addColorStop(0, \"#000\")\n  blue.addColorStop(1, \"#00f\")\n  context.fillStyle = blue\n  context.fillRect(padding, padding, config.width, config.height)\n\n  context.globalCompositeOperation = \"source-over\"\n  const border = Math.min(config.width, config.height) * (config.border * 0.5)\n  context.filter = `blur(${config.blur}px)`\n  context.fillStyle = `hsla(0, 0%, ${config.lightness}%, ${config.alpha})`\n  context.beginPath()\n  context.roundRect(\n    padding + border,\n    padding + border,\n    config.width - border * 2,\n    config.height - border * 2,\n    config.radius\n  )\n  context.fill()\n  context.restore()\n\n  const uri = canvas.toDataURL()\n  mapCache.set(key, uri)\n  return uri\n}\n\nfunction createFilterSvg(id: string): FilterRefs {\n  const svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\")\n  svg.setAttribute(\"xmlns\", \"http://www.w3.org/2000/svg\")\n  svg.style.cssText = \"position:absolute;width:0;height:0;pointer-events:none\"\n  svg.innerHTML = `\n    <defs>\n      <filter id=\"${id}\" color-interpolation-filters=\"sRGB\">\n        <feImage result=\"map\" preserveAspectRatio=\"none\" />\n        <feDisplacementMap in=\"SourceGraphic\" in2=\"map\" xChannelSelector=\"R\" yChannelSelector=\"B\" result=\"dispRed\" data-channel=\"red\" />\n        <feColorMatrix in=\"dispRed\" type=\"matrix\" values=\"1 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1 0\" result=\"red\" />\n        <feDisplacementMap in=\"SourceGraphic\" in2=\"map\" xChannelSelector=\"R\" yChannelSelector=\"B\" result=\"dispGreen\" data-channel=\"green\" />\n        <feColorMatrix in=\"dispGreen\" type=\"matrix\" values=\"0 0 0 0 0  0 1 0 0 0  0 0 0 0 0  0 0 0 1 0\" result=\"green\" />\n        <feDisplacementMap in=\"SourceGraphic\" in2=\"map\" xChannelSelector=\"R\" yChannelSelector=\"B\" result=\"dispBlue\" data-channel=\"blue\" />\n        <feColorMatrix in=\"dispBlue\" type=\"matrix\" values=\"0 0 0 0 0  0 0 0 0 0  0 0 1 0 0  0 0 0 1 0\" result=\"blue\" />\n        <feBlend in=\"red\" in2=\"green\" mode=\"screen\" result=\"rg\" />\n        <feBlend in=\"rg\" in2=\"blue\" mode=\"screen\" result=\"output\" />\n        <feGaussianBlur in=\"output\" stdDeviation=\"0\" />\n      </filter>\n    </defs>`\n\n  const get = <T extends Element>(selector: string) => {\n    const node = svg.querySelector<T>(selector)\n    if (!node) throw new Error(`Liquid Glass filter is missing ${selector}.`)\n    return node\n  }\n\n  return {\n    svg,\n    filter: get<SVGFilterElement>(\"filter\"),\n    image: get<SVGFEImageElement>(\"feImage\"),\n    red: get<SVGFEDisplacementMapElement>('[data-channel=\"red\"]'),\n    green: get<SVGFEDisplacementMapElement>('[data-channel=\"green\"]'),\n    blue: get<SVGFEDisplacementMapElement>('[data-channel=\"blue\"]'),\n    blur: get<SVGFEGaussianBlurElement>(\"feGaussianBlur\"),\n  }\n}\n\nfunction applyConfig(config: LiquidGlassConfig, refs: FilterRefs) {\n  const uri = buildDisplacementMap(config)\n  const maxDisplace = Math.max(Math.abs(config.scale) * 0.5, 20)\n  const x = Math.ceil((maxDisplace / config.width) * 100)\n  const y = Math.ceil((maxDisplace / config.height) * 100)\n\n  refs.filter.setAttribute(\"x\", `-${x}%`)\n  refs.filter.setAttribute(\"y\", `-${y}%`)\n  refs.filter.setAttribute(\"width\", `${100 + x * 2}%`)\n  refs.filter.setAttribute(\"height\", `${100 + y * 2}%`)\n  refs.image.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", uri)\n  refs.image.setAttribute(\"href\", uri)\n  refs.red.setAttribute(\"scale\", String(config.scale + config.r))\n  refs.green.setAttribute(\"scale\", String(config.scale + config.g))\n  refs.blue.setAttribute(\"scale\", String(config.scale + config.b))\n  refs.blur.setAttribute(\"stdDeviation\", String(config.displace))\n}\n\nfunction createLiquidGlass(\n  element: HTMLElement,\n  options: LiquidGlassOptions = {}\n): LiquidGlassInstance {\n  const fallback = options.fallbackFilter ?? \"blur(12px)\"\n\n  if (!isChromium) {\n    const previous = element.style.backdropFilter\n    const previousWebkit = element.style.getPropertyValue(\n      \"-webkit-backdrop-filter\"\n    )\n    element.style.backdropFilter = fallback\n    element.style.setProperty(\"-webkit-backdrop-filter\", fallback)\n    const placeholder = document.createElementNS(\n      \"http://www.w3.org/2000/svg\",\n      \"svg\"\n    )\n\n    return {\n      isActive: false,\n      filterElement: placeholder,\n      update() {},\n      destroy() {\n        element.style.backdropFilter = previous\n        element.style.setProperty(\"-webkit-backdrop-filter\", previousWebkit)\n      },\n    }\n  }\n\n  const id = options.filterId ?? `liquid-glass-${++instanceCount}`\n  const refs = createFilterSvg(id)\n  document.body.appendChild(refs.svg)\n  let currentOptions = { ...options }\n  let config = resolveConfig(element, currentOptions)\n\n  const applyStyles = (nextConfig: LiquidGlassConfig) => {\n    const filter = `url(#${id}) saturate(${nextConfig.saturation})`\n    element.style.backdropFilter = filter\n    element.style.setProperty(\"-webkit-backdrop-filter\", filter)\n    if (nextConfig.frost > 0) {\n      element.style.background = `hsl(0 0% 0% / ${nextConfig.frost})`\n    }\n  }\n\n  applyConfig(config, refs)\n  applyStyles(config)\n\n  let resizeFrame = 0\n  const observer = new ResizeObserver(() => {\n    cancelAnimationFrame(resizeFrame)\n    resizeFrame = requestAnimationFrame(() => {\n      if (currentOptions.width == null || currentOptions.height == null) {\n        config = resolveConfig(element, currentOptions)\n        applyConfig(config, refs)\n        applyStyles(config)\n      }\n    })\n  })\n  observer.observe(element)\n\n  return {\n    isActive: true,\n    filterElement: refs.svg,\n    update(nextOptions) {\n      currentOptions = { ...currentOptions, ...nextOptions }\n      config = resolveConfig(element, currentOptions)\n      applyConfig(config, refs)\n      applyStyles(config)\n    },\n    destroy() {\n      observer.disconnect()\n      cancelAnimationFrame(resizeFrame)\n      refs.svg.remove()\n      element.style.backdropFilter = \"\"\n      element.style.setProperty(\"-webkit-backdrop-filter\", \"\")\n      element.style.background = \"\"\n    },\n  }\n}\n\nexport { createLiquidGlass, isChromium }\nexport type { LiquidGlassInstance, LiquidGlassOptions }\n",
      "type": "registry:lib",
      "target": "lib/liquid-glass.ts"
    }
  ],
  "type": "registry:component"
}