# Nav (Lumen Halo): prompt.md (v1.4.0)

- id: `glass-nav` · version 1.4.0 · component · free
- category: Navigation
- build: Base UI (one set of files; its dependencies follow the build)
- install (this build): `npx shadcn@latest add https://beautiful-ui.dev/r/glass-nav.json`
- npm dependencies: motion@^13, lucide-react@^1, @web-kits/audio@^0.2
- registry dependencies: https://beautiful-ui.dev/r/beautiful-ui.json
- docs: https://beautiful-ui.dev/components/glass-nav
- The install command carries everything this item needs (files, CSS, tokens, npm and registry dependencies). Prefer it to copying source by hand.

A floating liquid-glass navigation bar in dark and light, with a lens that glides under links, a product mega-menu and a condensed scroll state.

## Build it
- Stack: React 19 (`ref` is a plain prop), TypeScript, Tailwind CSS v4 utilities, a shadcn-initialised project with the `@/*` alias.
- Packages: `motion`, `lucide-react`, `@web-kits/audio`.
- Source: `components/beautiful-ui/glass-nav.tsx`, shared code in `lib/beautiful-ui/`.
- Exports to keep: `GlassNav`, and every exported type.
- CSS: none to add. Themes, tokens and keyframes are inlined by the component (a deduped `<style>` built with `lumenThemeCss` from `lib/beautiful-ui/glass.tsx`); retheme through the `--glass-*` variables, never with Tailwind colour classes inside the component.

```tsx
import { GlassNav, type GlassNavItem } from "@/components/beautiful-ui/glass-nav";
```

## Parts

| Part | data-slot | What it is for |
|---|---|---|
| `GlassNav` | — | The glass top bar with mega-menus, the call to action and the phone sheet. |

## Sound
- Keep every `data-slot` and `data-sound` attribute: the sound layer reads them.
- Installing this item adds no audio. Nothing plays until the app mounts `GlassSoundProvider` once (install: `npx shadcn@latest add https://beautiful-ui.dev/r/glass-sound.json`, import from `@/components/beautiful-ui/glass-sound`); `GlassSoundToggle` is its mute control. Without a provider the audio engine never loads; `sound={false}` silences one instance.
- This item: select (pitched by position) on items, menu rows and phone rows; open / close for the menu opened by tap or keyboard and the phone sheet (its button, scrim and Escape); toggleOn / toggleOff for phone submenus; a quiet tick on arrow keys in the menu; the CTA taps. Menus opened by hover stay silent; needs a GlassSoundProvider; sound={false} silences this instance.

## Match the original
- Read `components/beautiful-ui/glass-nav.tsx` as the reference implementation before changing or recreating anything, and match it: sizes, colours per theme, motion timings, copy and behaviour.
- If you deviate (a prop you can't honour, a style you changed, a dependency you swapped), say so in your reply, part by part.
- Keep the accessibility contract, the keyboard map and the motion levels listed below.

## Use it when
- top navigation bar, site header, marketing nav with a product mega-menu, mobile menu
- The top navigation of a marketing site or product landing page
- When the nav should feel like a crafted object: a gliding lens, pointer spotlight and a mega-menu for the product area
- Sites that want the bar to tighten while scrolling (condenseOnScroll)

### Not when
- App navigation with many sections or nested levels: use a sidebar or tabs

## Mistakes
- Don't put GlassNav inside another fixed/sticky header wrapper that clips it: the mega-menu and the phone sheet render below and over the page
- On phones the component is position: fixed at the top; add top padding (about 72px) to the page instead of placing it in the page flow
- Only the first item with a menu opens the mega-menu on desktop (later menu items act as plain links there, and expand one at a time in the phone sheet); don't nest menus inside menu items
- Don't restyle it with Tailwind colour classes; use theme="dark" | "light", --glass-accent / --glass-nav-cta-fg, or the --lg-* variables through style
- With Next.js or another router, pass linkComponent={Link}; otherwise every link is a plain <a> and navigates with a full page load. A custom link must forward ref to its <a> or the lens cannot find the link
- With client-side links the mega-menu closes on a row click, but a controlled sheetOpen must be closed by you (onSheetOpenChange fires with false when a link in the sheet is clicked)
- The phone pill and its scrim are position: fixed (z-index 50 / 40, see zIndex). Inside a preview or modal give the container a transform so fixed children stay inside it, and use --glass-safe-top / --glass-vh for its inset and height
- The layout switch is viewport based (breakpoint, default 720). In a narrow column on a wide screen the desktop bar can overflow: force layout="mobile" there, or raise breakpoint
- The open phone sheet sets document.body.style.overflow = "hidden" below the breakpoint and restores it on close; iOS may still rubber-band behind it
- ref, className and style land on the root, which is display: contents in the phone layout (the pill is fixed); measure the <nav> inside it instead
- onActiveChange is deprecated: use onActiveIdChange (both are called)

## Usage

```tsx
import Link from "next/link";
import { usePathname } from "next/navigation";
import { GlassNav, type GlassNavItem } from "@/components/beautiful-ui/glass-nav";

// Works as is with the designed copy:
<GlassNav />

// A Next.js app: client-side links, the lens follows the route, analytics on menu rows.
const ITEMS: GlassNavItem[] = [
  {
    id: "product",
    label: "Product",
    menu: {
      heading: "Product",
      items: [
        { id: "editor", title: "Editor", description: "Write and review together", href: "/editor" },
        { id: "api", title: "API", description: "Everything, programmable", href: "/api" },
      ],
    },
  },
  { id: "/pricing", label: "Pricing", href: "/pricing" },
  { id: "/docs", label: "Docs", href: "/docs" },
];

export function SiteHeader() {
  const pathname = usePathname();
  return (
    <GlassNav
      as="header"
      linkComponent={Link}
      items={ITEMS}
      activeId={ITEMS.some((i) => i.id === pathname) ? pathname : undefined}
      onMenuItemSelect={(row) => track("nav_menu", row.id)}
      brand={{ name: "Kitelabs", href: "/" }}
      cta={{ label: "Get started", href: "/signup" }}
      labels={{ openMenu: "Menü öffnen", closeMenu: "Menü schließen" }}
      className="fixed left-1/2 top-12 -translate-x-1/2"
      style={{ "--glass-accent": "#6366f1", "--glass-nav-cta-fg": "#fff" } as React.CSSProperties}
    />
  );
}

// Your own brand, links, menu and call to action:
<header className="fixed inset-x-0 top-12 z-50 flex justify-center">
  <GlassNav
    brand={{ name: "Kitelabs", href: "/" }}
    defaultActiveId="pricing"
    items={[
      {
        id: "product",
        label: "Product",
        menu: {
          heading: "Product",
          items: [
            { id: "editor", title: "Editor", description: "Write and review together", href: "/editor" },
            { id: "api", title: "API", description: "Everything, programmable", href: "/api" },
          ],
          feature: { badge: "NEW", title: "Realtime sync", subtitle: "Read the announcement →", href: "/blog/sync" },
        },
      },
      { id: "pricing", label: "Pricing", href: "/pricing" },
      { id: "docs", label: "Docs", href: "/docs" },
    ]}
    secondary={{ label: "Sign in", href: "/login" }}
    cta={{ label: "Get started", href: "/signup" }}
  />
</header>
```

## Props

| Prop | Type | Default | What it does |
|---|---|---|---|
| `brand` | `{ name: string; href: string; mark?: ReactNode }` | `{ name: "Lumen", href: "/" }` | Wordmark and link. The default mark is the eclipse; pass any 26px element to replace it. |
| `items` | `GlassNavItem[]` | `Product, Customers, Pricing, Docs, Changelog` | Links in order: { id, label, href?, menu? }. The first item with a menu opens the mega-menu: { heading?, items: { id, title, description, href, icon? }[], feature?: { badge, title, subtitle, href, art? } }. |
| `activeId` | `string` |  | Controlled active link. The lens rests on it while nothing is hovered. |
| `defaultActiveId` | `string` | `"pricing" with the default items, otherwise none` | Uncontrolled initial active link. |
| `onActiveIdChange` | `(id: string, event?: MouseEvent) => void` |  | Called with the item id when a top-level link is clicked (bar and phone sheet). Update activeId from it when controlled. |
| `onActiveChange` | `(id: string) => void` |  | Deprecated alias of onActiveIdChange (still called). |
| `onMenuItemSelect` | `(item: GlassNavMenuItem, parent: GlassNavItem, event: MouseEvent) => void` |  | Called when a mega-menu row is clicked (desktop panel or phone accordion), for analytics or custom routing (event.preventDefault() stops the link). The menu closes after a row or the feature card is clicked. |
| `menuOpen / defaultMenuOpen / onMenuOpenChange` | `boolean / boolean / (open: boolean) => void` | `uncontrolled, closed` | Open state of the desktop mega-menu. Hover, keyboard, focus leaving the bar and a row click all go through onMenuOpenChange. The panel still hides while the bar is condensed. |
| `sheetOpen / defaultSheetOpen / onSheetOpenChange` | `boolean / boolean / (open: boolean) => void` | `uncontrolled, closed` | Open state of the phone sheet (menu button, links, Escape, the scrim). Close it on route change when you use client-side links. |
| `secondary` | `{ label: string; href: string } \| null` | `{ label: "Sign in", href: "#" }` | Quiet link before the call to action. Pass null to hide it. |
| `cta` | `{ label: string; href?: string; onClick?: (e: MouseEvent) => void } \| null` | `{ label: "Start free", href: "#" }` | Primary pill with the arrow chip. Collapses to just the arrow when condensed. onClick receives the event. Pass null to hide it. |
| `condensed` | `boolean` |  | Controlled condensed state; overrides condenseOnScroll. |
| `condenseOnScroll` | `boolean \| { threshold: number; target?: RefObject<HTMLElement> }` | `{ threshold: 40 }` | Condense after scrolling past the threshold, on window or a scroll container. false disables. |
| `motion` | `"full" \| "subtle" \| "off"` | `"full"` | full: the designed motion. subtle: calm curve, shorter, no staggers. off: instant. prefers-reduced-motion is always respected (150ms fades). "smooth" and "calm" from 1.0 still work. |
| `layout` | `"auto" \| "desktop" \| "mobile"` | `"auto"` | Below the breakpoint (720px) the bar becomes a phone pill (brand, compact CTA, menu button) that grows into a full-height glass sheet with the links, the Product accordion, Sign in and the CTA. auto switches in CSS, so phones never flash the desktop bar. |
| `mobile` | `{ menuFooter?: ReactNode }` |  | Phone sheet options. menuFooter replaces the status line under the buttons; null hides it. |
| `sound` | `boolean \| "subtle"` | `true` | true plays Lumen cues when a GlassSoundProvider enables sound; false silences this instance; "subtle" plays at 55%. Without a provider nothing plays and the engine never loads. |
| `theme` | `"system" \| "dark" \| "light"` | `"system"` | system follows a .dark / .light class or data-theme on an ancestor (next-themes, shadcn), else the OS setting. Both themes share every size and timing. |
| `breakpoint` | `number` | `720` | Viewport width (px) where layout="auto" switches to the phone pill. The desktop bar also hides its link row below it, and the open sheet locks page scroll only below it. Viewport based on purpose (the pill is fixed page chrome); for a narrow column force layout="mobile" or "desktop". |
| `menuWidth` | `number \| string` | `560` | Width of the desktop mega-menu. The panel centres under the bar and slides to stay 8px inside the viewport; it never gets wider than the viewport minus 16px. |
| `zIndex` | `number` | `50` | z-index of the fixed phone pill; the scrim sits 10 below. The desktop bar is z-10 (override with className). |
| `linkComponent` | `React.ElementType` | `"a"` | Renders every link (brand, items, menu rows, feature card, Sign in, an href CTA) for client-side routing, e.g. Next.js Link. It receives href, className, style, children, ref, onClick, onMouseEnter, onFocus, tabIndex and aria-* props and must render an <a> that forwards ref (the lens measures links). |
| `labels` | `Partial<GlassNavLabels>` | `defaultGlassNavLabels` | Built-in strings: nav (landmark name, "Main"), openMenu, closeMenu (phone menu button), status ("ALL SYSTEMS NORMAL" in the sheet). Unset keys keep the defaults. |
| `aria-label` | `string` | `"Main"` | Accessible name of the nav landmark(s). Same as labels.nav; this wins. |
| `as` | `"div" \| "header"` | `"div"` | Root element. "header" makes the root a banner landmark. |
| `className / style` | `string / CSSProperties` |  | Applied to the root (the bar's box at desktop widths). Use style for CSS variables such as --glass-accent or --lg-* tokens. Below the breakpoint the root is display: contents, so box styles only apply to the desktop bar. |
| `ref` | `Ref<HTMLElement>` |  | The root element (React 19 ref prop). In the phone layout it has no box of its own (display: contents). |
| `...rest` | `HTMLAttributes` |  | id, data-*, aria-* and on* handlers go to the root. |
| `CSS: --glass-accent (-dark / -light)` | `CSS variable` | `unset (monochrome CTA gradient)` | Fill of the call to action (bar and sheet) and the active-link dot in the sheet. Set on the root or any ancestor. |
| `CSS: --glass-nav-cta-fg (-dark / -light)` | `CSS variable` | `unset (#0b0c10 dark, #f5f6f8 light)` | Text colour of the call to action; set it with --glass-accent to keep 4.5:1 contrast. |
| `CSS: --glass-good (-dark / -light)` | `CSS variable` | `unset (white / ink)` | Dot of the status line in the phone sheet. |
| `CSS: --glass-safe-top / --glass-vh` | `CSS variable` | `env(safe-area-inset-top) / 100dvh` | Top inset and viewport height the fixed phone pill and sheet use. Override inside previews or custom shells. |
| `CSS: --lg-* tokens` | `CSS variables` |  | Every surface, text and shadow token (--lg-ctaBg, --lg-barBg, --lg-muted …). They are declared on the root per theme, so override them through style or a selector on the root itself. |

Full docs: https://beautiful-ui.dev/components/glass-nav

## Customising
- Theme: `theme="system"` (default) follows a `.dark` / `.light` class or `data-theme` on an ancestor, else the OS. `"dark"` / `"light"` pin one.
- Motion: `motion="full"` (default) | `"subtle"` | `"off"`. prefers-reduced-motion is always respected.
- Phones: below 720px it switches to its phone layout in CSS. Force one with `layout="desktop"` or `layout="mobile"`.
- Colours: the family variables (`--glass-accent`, `--glass-good`, `--glass-series-1` …) on the component or any ancestor; the neutral glass is `--lg-*` in `lib/beautiful-ui/glass.tsx`.
- Update later by re-running the install with `--overwrite` (review the diff if you edited it). Changelog: https://beautiful-ui.dev/r/changelog.json

## Keyboard

| Keys | Action |
|---|---|
| Tab / Shift+Tab | Move between links; the lens follows focus |
| Enter, Space or ↓ on Product | Open the mega-menu and focus its first row |
| ↑ / ↓, Home / End | Move between menu rows |
| Escape | Close the menu or the phone sheet and return focus to its trigger |

## Motion inventory

| Interaction | What moves |
|---|---|
| Sound | select (pitched by position) on items, menu rows and phone rows; open / close for the menu opened by tap or keyboard and the phone sheet (its button, scrim and Escape); toggleOn / toggleOff for phone submenus; a quiet tick on arrow keys in the menu; the CTA taps. Menus opened by hover stay silent; needs a GlassSoundProvider; sound={false} silences this instance |
| Hover or focus a link | Lens glides to it (600ms), link lifts 1px |
| Open the mega-menu | Panel scales and unblurs in (500ms); rows stagger 35ms |
| Scroll past the threshold | Bar condenses: height, padding and labels (500ms) |
| Phone: open the menu | Pill grows into the full-height sheet (620ms); links stagger 45ms from 140ms |
| motion="subtle" / "off" / reduced motion | Subtle: calm curve, shorter, no stagger. Off: instant. Reduced: 150ms fades only |

## Accessibility contract (preserve when editing)
- A real <nav> landmark with <a>/<button> links; focusing a link moves the lens to it
- The menu trigger has aria-haspopup, aria-expanded and aria-controls; Enter, Space or ArrowDown opens it and focuses the first row
- Arrow keys, Home and End move between menu rows with the highlight following focus; Escape closes and returns focus to the trigger
- The closed panel is inert, so hidden links are never tabbable
- Pill-shaped focus rings on every control; prefers-reduced-motion drops movement, blur and the spotlight and keeps short opacity fades
- Phones: the menu button has aria-expanded and aria-controls; the open sheet traps focus, locks page scroll, and Escape or the scrim closes it and returns focus. Touch targets are 44px or larger

## Install

```bash
npx shadcn@latest add https://beautiful-ui.dev/r/glass-nav.json
```

## Credits
- Sound by @web-kits/audio (https://www.npmjs.com/package/@web-kits/audio)

## Source (Base UI build)

### components/beautiful-ui/glass-nav.tsx

```tsx
"use client";

/**
 * Nav (Lumen Halo) v1.4.0 · Beautiful UI
 * Docs: https://beautiful-ui.dev/components/glass-nav · AI guide: ./glass-nav.AI.md
 * Licensed to the purchaser under the Beautiful UI license: https://beautiful-ui.dev/license
 */

import * as React from "react";
import { Activity, Blocks, LayoutDashboard, Workflow } from "lucide-react";
import { cn } from "@/lib/beautiful-ui/utils";
import { glassSoundRootAttrs, useGlassSound, type GlassSoundProp } from "@/lib/beautiful-ui/glass-sound";
import {
  EclipseMark,
  GLASS_FOCUS,
  GLASS_FONT,
  GLASS_HIGHLIGHT_STYLE,
  GLASS_LENS_STYLE,
  GLASS_MONO,
  GLASS_ROOT_STYLE,
  GLASS_TEXT,
  G,
  GLASS_SAFE_TOP,
  GLASS_VH,
  GlassLayers,
  GlassRim,
  GlassThemeStyle,
  GlassLensSheen,
  glassSurfaceStyle,
  layoutClasses,
  lumenThemeCss,
  mergeLabels,
  pickRootAttrs,
  trapFocus,
  useGlassTransitions,
  type GlassLayout,
  type GlassMotion,
  type GlassRootProps,
  type GlassTheme,
  type GlassThemeMode,
  type GlassTx,
} from "@/lib/beautiful-ui/glass";

/*
 * Glass Nav: a floating, monochrome "liquid glass" navigation bar with a gliding lens,
 * a Product mega-menu and a condensed state. Every colour, shadow, size and timing below
 * comes from the design handoff and is intentional: change them only with design.
 */

// ---------------------------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------------------------

export interface GlassNavMenuItem {
  id: string;
  title: string;
  description: string;
  href: string;
  /** 16px, 1.5 stroke, currentColor. Falls back to a small abstract glyph. */
  icon?: React.ReactNode;
}

export interface GlassNavFeature {
  badge: string;
  title: string;
  subtitle: string;
  href: string;
  /** Centre artwork. Defaults to a 68px eclipse. */
  art?: React.ReactNode;
}

export interface GlassNavItem {
  id: string;
  label: string;
  href?: string;
  menu?: {
    heading?: string;
    items: GlassNavMenuItem[];
    feature?: GlassNavFeature;
  };
}

export interface GlassNavBrand {
  name: string;
  href: string;
  /** 26px mark. Defaults to the eclipse. */
  mark?: React.ReactNode;
}

export interface GlassNavLink {
  label: string;
  href: string;
}

export interface GlassNavCta {
  label: string;
  /** Receives the click event (call `preventDefault()` to stop an `href` navigation). */
  onClick?: (event: React.MouseEvent<HTMLElement>) => void;
  href?: string;
}

/**
 * Every string the nav renders itself (content such as link labels and the brand name are props).
 * Override any subset through `labels`.
 */
export const defaultGlassNavLabels = {
  /** Accessible name of the `<nav>` landmark (the `aria-label` prop wins when set). */
  nav: "Main",
  /** Phone menu button, closed. */
  openMenu: "Open menu",
  /** Phone menu button, open. */
  closeMenu: "Close menu",
  /** The status line at the bottom of the phone sheet (replace the whole line with `mobile.menuFooter`). */
  status: "ALL SYSTEMS NORMAL",
};
export type GlassNavLabels = typeof defaultGlassNavLabels;

/**
 * Props a `linkComponent` receives. It must render an `<a>` (spreading these props) and forward
 * `ref` to it: the lens measures links, and the menu focuses rows. Next.js `Link` does both.
 */
export interface GlassNavLinkComponentProps {
  href: string;
  className?: string;
  style?: React.CSSProperties;
  children?: React.ReactNode;
  ref?: React.Ref<HTMLElement>;
  onClick?: React.MouseEventHandler<HTMLElement>;
  onMouseEnter?: React.MouseEventHandler<HTMLElement>;
  onFocus?: React.FocusEventHandler<HTMLElement>;
  tabIndex?: number;
  "aria-current"?: "page";
  "aria-hidden"?: boolean;
  "aria-label"?: string;
}

export interface GlassNavOwnProps {
  /** Defaults to the "Lumen" wordmark with the eclipse mark. */
  brand?: GlassNavBrand;
  /** Defaults to Product (with mega-menu), Customers, Pricing, Docs, Changelog. */
  items?: GlassNavItem[];
  /** Controlled active link. The lens rests here while nothing is hovered. */
  activeId?: string;
  /** Uncontrolled initial active link. Default: "pricing" with the default items, otherwise none. */
  defaultActiveId?: string;
  /** Called with the item id when a top-level link is clicked (desktop bar and phone sheet), plus the click event. */
  onActiveIdChange?: (id: string, event?: React.MouseEvent<HTMLElement>) => void;
  /** @deprecated Use `onActiveIdChange` (same signature). Still called. */
  onActiveChange?: (id: string) => void;
  /**
   * Called when a mega-menu row is clicked (desktop panel and phone accordion), with its parent item
   * and the click event (call `event.preventDefault()` to route yourself).
   */
  onMenuItemSelect?: (item: GlassNavMenuItem, parent: GlassNavItem, event: React.MouseEvent<HTMLElement>) => void;
  /** Controlled open state of the desktop mega-menu. */
  menuOpen?: boolean;
  /** Uncontrolled initial open state of the desktop mega-menu. Default false. */
  defaultMenuOpen?: boolean;
  /** Called whenever the desktop mega-menu opens or closes (hover, keyboard, focus leaving, a row click). */
  onMenuOpenChange?: (open: boolean) => void;
  /** Controlled open state of the phone sheet. */
  sheetOpen?: boolean;
  /** Uncontrolled initial open state of the phone sheet. Default false. */
  defaultSheetOpen?: boolean;
  /** Called whenever the phone sheet opens or closes (menu button, a link, Escape, the scrim). */
  onSheetOpenChange?: (open: boolean) => void;
  /** Quiet link before the CTA. Pass null to hide it. */
  secondary?: GlassNavLink | null;
  /** Primary pill with the arrow chip. Pass null to hide it. */
  cta?: GlassNavCta | null;
  /** Controlled condensed state. Overrides condenseOnScroll. */
  condensed?: boolean;
  /** Condense after scrolling past a threshold. Default { threshold: 40 } on window. */
  condenseOnScroll?: boolean | { threshold: number; target?: React.RefObject<HTMLElement | null> };
  /** "full" (default) | "subtle" | "off". "smooth" and "calm" from 1.0 still work. Reduced motion is always respected. */
  motion?: GlassMotion;
  /** true plays Lumen cues when a GlassSoundProvider enables sound; false silences this instance; "subtle" plays at 55%. Default true. */
  sound?: GlassSoundProp;
  /** "system" (default) follows the page's `.dark` class or `data-theme`, else the OS. */
  theme?: GlassTheme;
  /** "auto" (default) uses the phone layout below `breakpoint`; "desktop" / "mobile" force one. */
  layout?: GlassLayout;
  /**
   * Viewport width (px) below which `layout="auto"` shows the phone pill, the desktop bar hides its
   * link row, and the open sheet locks page scroll. Default 720. Viewport based on purpose: the bar
   * is page chrome (see the docs for putting it in a narrow column).
   */
  breakpoint?: number;
  /** Width of the desktop mega-menu (px or any CSS length). Default 560. Always kept 8px inside the viewport. */
  menuWidth?: number | string;
  /** z-index of the fixed phone pill (the scrim sits 10 below it). Default 50. */
  zIndex?: number;
  /**
   * Renders every link (brand, items, menu rows, feature card, Sign in, an `href` CTA). Pass your
   * router's link, e.g. Next.js `Link`, for client-side navigation. Default "a".
   */
  linkComponent?: React.ElementType;
  /** Override any built-in string (see `defaultGlassNavLabels`). */
  labels?: Partial<GlassNavLabels>;
  /** Root element. "header" makes the root a banner landmark. Default "div". */
  as?: "div" | "header";
  /** Phone layout options. menuFooter replaces the status line at the bottom of the sheet (null hides it). */
  mobile?: { menuFooter?: React.ReactNode };
  /** Accessible name of the nav landmark. Same as `labels.nav`; this wins. */
  "aria-label"?: string;
}

/**
 * `className`, `style`, `ref`, `id`, `data-*`, `aria-*` and `on*` handlers go to the root element
 * (below the breakpoint the root is `display: contents`, since the phone pill is fixed).
 */
export type GlassNavProps = GlassNavOwnProps & Omit<GlassRootProps<HTMLElement>, keyof GlassNavOwnProps>;

// ---------------------------------------------------------------------------------------------
// Defaults: the complete design works out of the box; every string is editable through props.
// ---------------------------------------------------------------------------------------------

export const defaultGlassNavItems: GlassNavItem[] = [
  {
    id: "product",
    label: "Product",
    menu: {
      heading: "Product",
      items: [
        { id: "dashboards", title: "Dashboards", description: "Every metric, one live board", href: "#", icon: <LayoutDashboard /> },
        { id: "automations", title: "Automations", description: "Rules that run themselves", href: "#", icon: <Workflow /> },
        { id: "insights", title: "Insights", description: "Live metrics with no setup", href: "#", icon: <Activity /> },
        { id: "integrations", title: "Integrations", description: "120+ tools, one click each", href: "#", icon: <Blocks /> },
      ],
      feature: { badge: "NEW · 2.0", title: "Multiplayer dashboards", subtitle: "Read the release notes →", href: "#" },
    },
  },
  { id: "customers", label: "Customers", href: "#" },
  { id: "pricing", label: "Pricing", href: "#" },
  { id: "docs", label: "Docs", href: "#" },
  { id: "changelog", label: "Changelog", href: "#" },
];

const DEFAULT_BRAND = { name: "Lumen", href: "/" };
const DEFAULT_SECONDARY = { label: "Sign in", href: "#" };
const DEFAULT_CTA = { label: "Start free", href: "#" };

// ---------------------------------------------------------------------------------------------
// Local design values (shared material, motion and type come from @/lib/beautiful-ui/glass)
// ---------------------------------------------------------------------------------------------

const FONT = GLASS_FONT;
const MONO = GLASS_MONO;
const FOCUS_RING = GLASS_FOCUS;
const TEXT = { ...GLASS_TEXT, signIn: GLASS_TEXT.row };
const ROW_H = 60;

/** Placeholder glyph shapes used when a menu item has no icon (from the prototype). */
const GLYPH_SHAPES = ["3px", "50%", "2px 10px", "50% 2px"];

type Tx = GlassTx;

const DEFAULT_BREAKPOINT = 720;
const cleanBreakpoint = (bp: number | undefined) =>
  typeof bp === "number" && Number.isFinite(bp) && bp > 0 ? Math.round(bp) : DEFAULT_BREAKPOINT;

type ShowProps = { className: string; "data-gn-hide"?: string; "data-gn-contents"?: string };

/**
 * Show/hide wiring for the layout switch. At the default 720px these are the original Tailwind
 * classes; any other breakpoint uses data attributes and {@link glassNavBreakpointCss}.
 * Parts: `root` (a box at desktop widths, `display: contents` below, since the phone pill is
 * fixed), the `desktop` and `mobile` variant wrappers, and `narrow` (desktop pieces that hide below
 * the breakpoint even when `layout="desktop"` is forced).
 */
export function glassNavLayout(layout: GlassLayout, breakpoint = DEFAULT_BREAKPOINT) {
  const bp = cleanBreakpoint(breakpoint);
  const custom = bp !== DEFAULT_BREAKPOINT;
  const below = `below-${bp}`;
  const above = `above-${bp}`;
  const box = "relative z-10 w-max";
  const root: ShowProps =
    layout === "mobile"
      ? { className: "contents" }
      : layout === "desktop"
        ? { className: box }
        : custom
          ? { className: box, "data-gn-contents": below }
          : { className: `${box} max-[719px]:contents` };
  const variant = (v: "desktop" | "mobile"): ShowProps =>
    layout !== "auto" || !custom
      ? { className: layoutClasses(layout, v) }
      : { className: "contents", "data-gn-hide": v === "desktop" ? below : above };
  const narrow: ShowProps = custom ? { className: "", "data-gn-hide": below } : { className: "max-[719px]:hidden" };
  return { breakpoint: bp, custom, root, desktop: variant("desktop"), mobile: variant("mobile"), narrow };
}

/** The media rules behind a non-default breakpoint (rendered once per breakpoint). */
export function glassNavBreakpointCss(breakpoint: number) {
  const bp = cleanBreakpoint(breakpoint);
  return (
    `@media (width < ${bp}px){[data-gn-hide~="below-${bp}"]{display:none}[data-gn-contents="below-${bp}"]{display:contents}}` +
    `@media (width >= ${bp}px){[data-gn-hide~="above-${bp}"]{display:none}}`
  );
}

// Colour hooks. The public --glass-* names are never declared here, so a buyer's CSS on the root
// or any ancestor wins; unset, each falls back to the designed monochrome token (no visual change).
const colorDecls = (mode: GlassThemeMode) =>
  [
    `--gn-accent:var(--glass-accent-${mode},var(--glass-accent,var(--lg-ctaBg)))`,
    `--gn-accent-fg:var(--glass-nav-cta-fg-${mode},var(--glass-nav-cta-fg,var(--lg-ctaFg)))`,
    `--gn-dot:var(--glass-accent-${mode},var(--glass-accent,var(--lg-dotOn)))`,
    `--gn-status:var(--glass-good-${mode},var(--glass-good,var(--lg-dotOn)))`,
  ].join(";");
/** Scoped to GlassNav roots (`[data-gn]`), so the private --gn-* names never land on other components. */
export const GLASS_NAV_COLOR_CSS = lumenThemeCss(colorDecls("dark"), colorDecls("light")).replaceAll(
  "[data-lumen=",
  "[data-gn][data-lumen=",
);
const C = {
  accent: "var(--gn-accent)",
  accentFg: "var(--gn-accent-fg)",
  dot: "var(--gn-dot)",
  status: "var(--gn-status)",
};

/**
 * How far (px) to slide a panel centred at `center` so it stays `margin` px inside a viewport of
 * width `viewport`. 0 when it already fits; centres it when it is wider than the viewport.
 */
export function clampMenuShift(center: number, width: number, viewport: number, margin = 8): number {
  if (!(width > 0) || !(viewport > 0)) return 0;
  if (width > viewport - margin * 2) return Math.round(viewport / 2 - center);
  const left = center - width / 2;
  const right = center + width / 2;
  if (left < margin) return Math.round(margin - left);
  if (right > viewport - margin) return Math.round(viewport - margin - right);
  return 0;
}

/** Controlled/uncontrolled state: `onChange` fires only when the value actually changes. */
function useControllable<T>(value: T | undefined, defaultValue: T, onChange?: (v: T) => void) {
  const [inner, setInner] = React.useState(defaultValue);
  const controlled = value !== undefined;
  const current = controlled ? (value as T) : inner;
  const latest = React.useRef(current);
  React.useLayoutEffect(() => {
    latest.current = current;
  });
  const set = React.useCallback(
    (next: T | ((prev: T) => T)) => {
      const prev = latest.current;
      const v = typeof next === "function" ? (next as (p: T) => T)(prev) : next;
      if (Object.is(v, prev)) return;
      if (!controlled) {
        latest.current = v;
        setInner(v);
      }
      onChange?.(v);
    },
    [controlled, onChange],
  );
  return [current, set] as const;
}

/** True while the viewport is narrower than `bp` (false on the server and first render). */
function useBelowBreakpoint(bp: number) {
  const query = `(width < ${bp}px)`;
  const subscribe = React.useCallback(
    (cb: () => void) => {
      const mq = window.matchMedia(query);
      mq.addEventListener("change", cb);
      return () => mq.removeEventListener("change", cb);
    },
    [query],
  );
  return React.useSyncExternalStore(subscribe, () => window.matchMedia(query).matches, () => false);
}

/** Locks page scroll while the phone sheet is open, only on viewports below the breakpoint. */
function useSheetScrollLock(active: boolean, bp: number) {
  const below = useBelowBreakpoint(bp);
  React.useEffect(() => {
    if (!active || !below) return;
    const { overflow } = document.body.style;
    document.body.style.overflow = "hidden";
    return () => {
      document.body.style.overflow = overflow;
    };
  }, [active, below]);
}

function FeatureEclipse() {
  return (
    <span
      aria-hidden
      className="absolute left-1/2 top-1/2 block overflow-hidden rounded-full"
      style={{
        width: 68,
        height: 68,
        margin: "-34px 0 0 -34px",
        background: G.featMarkBg,
        boxShadow: G.featMarkShadow,
      }}
    >
      <span
        className="absolute rounded-full"
        style={{ top: -6, left: 20, width: 68, height: 68, background: G.markDisc }}
      />
    </span>
  );
}

// ---------------------------------------------------------------------------------------------
// Small icons
// ---------------------------------------------------------------------------------------------

function Chevron({ open, tx }: { open: boolean; tx: Tx }) {
  return (
    <svg
      width="10"
      height="10"
      viewBox="0 0 10 10"
      aria-hidden
      style={{ transform: open ? "rotate(180deg)" : "none", transition: tx([["transform", 350]]) }}
    >
      <path d="M2 3.5 5 6.5 8 3.5" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function Arrow({ strokeWidth = 1.6 }: { strokeWidth?: number }) {
  return (
    <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden>
      <path
        d="M3 7h8M7.5 3.5 11 7l-3.5 3.5"
        fill="none"
        stroke="currentColor"
        strokeWidth={strokeWidth}
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

// ---------------------------------------------------------------------------------------------
// Hooks
// ---------------------------------------------------------------------------------------------

function useScrollCondense(option: GlassNavProps["condenseOnScroll"], controlled: boolean | undefined) {
  const [scrolled, setScrolled] = React.useState(false);
  const enabled = controlled === undefined && option !== false;
  const threshold = typeof option === "object" ? option.threshold : 40;
  const targetRef = typeof option === "object" ? option.target : undefined;

  React.useEffect(() => {
    if (!enabled) return;
    const el = targetRef?.current ?? null;
    const read = () => (el ? el.scrollTop : window.scrollY);
    const onScroll = () => setScrolled(read() > threshold);
    onScroll();
    const source: HTMLElement | Window = el ?? window;
    source.addEventListener("scroll", onScroll, { passive: true });
    return () => source.removeEventListener("scroll", onScroll);
  }, [enabled, threshold, targetRef]);

  return controlled ?? (enabled ? scrolled : false);
}

// ---------------------------------------------------------------------------------------------
// GlassNav
// ---------------------------------------------------------------------------------------------

type NavVariantProps = {
  brand?: GlassNavBrand;
  items: GlassNavItem[];
  activeId?: string;
  onActiveChange: (id: string, event?: React.MouseEvent<HTMLElement>) => void;
  onMenuItemSelect?: GlassNavOwnProps["onMenuItemSelect"];
  secondary?: GlassNavLink | null;
  cta?: GlassNavCta | null;
  motion?: GlassMotion;
  ariaLabel: string;
  Link: React.ElementType;
  labels: GlassNavLabels;
  breakpoint: number;
  layout: GlassLayout;
  show: ReturnType<typeof glassNavLayout>;
  sfx: ReturnType<typeof useGlassSound>["play"];
};

type DesktopNavProps = NavVariantProps & {
  condensed?: boolean;
  condenseOnScroll?: GlassNavOwnProps["condenseOnScroll"];
  menuOpen?: boolean;
  defaultMenuOpen?: boolean;
  onMenuOpenChange?: (open: boolean) => void;
  menuWidth?: number | string;
};

function DesktopNav({
  brand = DEFAULT_BRAND,
  items,
  activeId,
  onActiveChange,
  onMenuItemSelect,
  secondary = DEFAULT_SECONDARY,
  cta = DEFAULT_CTA,
  condensed: condensedProp,
  condenseOnScroll = { threshold: 40 },
  motion = "full",
  ariaLabel,
  Link,
  show,
  menuOpen: menuOpenProp,
  defaultMenuOpen = false,
  onMenuOpenChange,
  menuWidth = 560,
  sfx,
}: DesktopNavProps) {
  const uid = React.useId();
  const { tx, reduced } = useGlassTransitions(motion);
  const condensed = useScrollCondense(condenseOnScroll, condensedProp);

  // --- state -------------------------------------------------------------------------------
  const [hoverId, setHoverId] = React.useState<string | null>(null);
  const [menuOpen, setMenuOpen] = useControllable(menuOpenProp, defaultMenuOpen, onMenuOpenChange);
  const [menuHover, setMenuHover] = React.useState<number | null>(null);
  const [rects, setRects] = React.useState<Record<string, { x: number; w: number }>>({});
  // Natural widths of the collapsible labels, so condensing starts moving immediately.
  const [natural, setNatural] = React.useState({ word: 80, sign: 56, cta: 90 });
  // The lens appears in place (no slide) the first time and whenever it was hidden.
  const [lensInstant, setLensInstant] = React.useState(true);

  const menuItem = items.find((i) => i.menu);
  const menu = menuItem?.menu;
  const panelId = `${uid}-menu`;

  // --- refs ----------------------------------------------------------------------------------
  const trackRef = React.useRef<HTMLDivElement>(null);
  const linkRefs = React.useRef(new Map<string, HTMLElement>());
  const wordRef = React.useRef<HTMLSpanElement>(null);
  const signTextRef = React.useRef<HTMLSpanElement>(null);
  const ctaLabelRef = React.useRef<HTMLSpanElement>(null);
  const spotRef = React.useRef<HTMLSpanElement>(null);
  const triggerRef = React.useRef<HTMLButtonElement>(null);
  const pointerType = React.useRef<string>("mouse");
  const rowRefs = React.useRef<(HTMLElement | null)[]>([]);

  // --- measuring -----------------------------------------------------------------------------
  const measure = React.useCallback(() => {
    const next: Record<string, { x: number; w: number }> = {};
    linkRefs.current.forEach((el, id) => (next[id] = { x: el.offsetLeft, w: el.offsetWidth }));
    setRects(next);
    setNatural({
      word: wordRef.current?.scrollWidth ?? 80,
      sign: signTextRef.current?.scrollWidth ?? 56,
      cta: ctaLabelRef.current?.scrollWidth ?? 90,
    });
  }, []);

  // ResizeObserver reports once on observe (before paint), then on every size change;
  // fonts.ready re-measures once Geist has swapped in.
  React.useLayoutEffect(() => {
    let alive = true;
    document.fonts?.ready.then(() => alive && measure());
    const ro = new ResizeObserver(() => measure());
    if (trackRef.current) ro.observe(trackRef.current);
    return () => {
      alive = false;
      ro.disconnect();
    };
  }, [measure]);
  // Labels changed after mount: re-measure the collapsible widths (the observer only sees the track).
  const brandName = brand.name;
  const secondaryLabel = secondary?.label;
  const ctaLabel = cta?.label;
  React.useEffect(() => {
    const raf = requestAnimationFrame(measure);
    return () => cancelAnimationFrame(raf);
  }, [measure, brandName, secondaryLabel, ctaLabel]);

  // --- lens target -----------------------------------------------------------------------------
  const lensId = hoverId ?? activeId ?? null;
  const lensRect = lensId ? rects[lensId] : undefined;
  const lensVisible = Boolean(lensRect);

  // Hidden → shown: place instantly, then allow glides again on the next frame.
  React.useEffect(() => {
    let raf2 = 0;
    const raf = requestAnimationFrame(() => {
      if (!lensVisible) setLensInstant(true);
      else if (lensInstant) raf2 = requestAnimationFrame(() => setLensInstant(false));
    });
    return () => {
      cancelAnimationFrame(raf);
      cancelAnimationFrame(raf2);
    };
  }, [lensVisible, lensInstant]);

  // --- behaviour -----------------------------------------------------------------------------
  const closeMenu = React.useCallback(() => {
    setMenuOpen(false);
    setMenuHover(null);
  }, [setMenuOpen]);

  // Condensing always closes the panel.
  const panelOpen = menuOpen && !condensed;

  // Keep the panel inside the viewport: slide it off-centre when the bar sits near an edge.
  const panelRef = React.useRef<HTMLDivElement>(null);
  const [shift, setShift] = React.useState(0);
  React.useLayoutEffect(() => {
    if (!panelOpen) return;
    const place = () => {
      const panel = panelRef.current;
      const box = panel?.offsetParent;
      if (!panel || !box) return;
      const r = box.getBoundingClientRect();
      setShift(clampMenuShift(r.left + r.width / 2, panel.offsetWidth, document.documentElement.clientWidth));
    };
    place();
    window.addEventListener("resize", place);
    return () => window.removeEventListener("resize", place);
  }, [panelOpen, menuWidth]);

  // Pointer: hovering the menu item opens it, any other link closes it.
  const moveTo = (item: GlassNavItem) => {
    setHoverId(item.id);
    setMenuOpen(Boolean(item.menu) && !condensed);
    if (!item.menu) setMenuHover(null);
  };

  // Keyboard focus only moves the lens (Enter / Space / ArrowDown open the menu), so returning
  // focus to the trigger after Escape never re-opens it.
  const focusTo = (item: GlassNavItem) => {
    setHoverId(item.id);
    if (!item.menu) closeMenu();
  };

  const activate = (item: GlassNavItem, e?: React.MouseEvent<HTMLElement>) => onActiveChange?.(item.id, e);

  const leaveAll = () => {
    setHoverId(null);
    closeMenu();
    if (spotRef.current) spotRef.current.style.opacity = "0";
  };

  const focusRow = (index: number) => {
    const count = menu?.items.length ?? 0;
    if (!count) return;
    const i = (index + count) % count;
    setMenuHover(i);
    rowRefs.current[i]?.focus();
  };

  const openFromKeyboard = () => {
    if (!menuItem) return;
    if (!panelOpen) sfx("open");
    setHoverId(menuItem.id);
    setMenuOpen(true);
    // Wait for the panel to become interactive before moving focus into it.
    requestAnimationFrame(() => focusRow(0));
  };

  const onTriggerKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
      e.preventDefault();
      openFromKeyboard();
    }
  };

  const onPanelKeyDown = (e: React.KeyboardEvent) => {
    const current = menuHover ?? 0;
    if (e.key === "ArrowDown") {
      e.preventDefault();
      focusRow(current + 1);
      sfx("tick");
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      focusRow(current - 1);
      sfx("tick");
    } else if (e.key === "Home") {
      e.preventDefault();
      focusRow(0);
      sfx("tick");
    } else if (e.key === "End") {
      e.preventDefault();
      focusRow((menu?.items.length ?? 1) - 1);
      sfx("tick");
    }
  };

  const onWrapperKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Escape" && panelOpen) {
      e.preventDefault();
      sfx("close");
      closeMenu();
      triggerRef.current?.focus();
    }
  };

  // Focus leaving the whole nav (bar + panel) resets it like the pointer leaving.
  const onWrapperBlur = (e: React.FocusEvent) => {
    if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {
      setHoverId(null);
      closeMenu();
    }
  };

  // Pointer spotlight: CSS variable + direct style, so moving the pointer never re-renders.
  const onPointerMove = (e: React.PointerEvent<HTMLElement>) => {
    if (reduced || e.pointerType !== "mouse" || !spotRef.current) return;
    const box = e.currentTarget.getBoundingClientRect();
    spotRef.current.style.setProperty("--mx", `${e.clientX - box.left}px`);
    spotRef.current.style.opacity = "1";
  };

  // --- derived sizes (full / condensed) ------------------------------------------------------
  const c = condensed;
  const size = {
    navH: c ? 54 : 64,
    padL: c ? 10 : 14,
    padR: c ? 6 : 8,
    brandGap: c ? 4 : 12,
    rightGap: c ? 4 : 10,
    signPad: c ? 0 : 12,
    ctaH: c ? 42 : 46,
    ctaPadL: c ? 5 : 18,
    ctaGap: c ? 0 : 10,
  };

  const CtaTag = cta?.href ? Link : "button";
  const narrow = show.narrow;

  return (
    <div
      className={show.desktop.className}
      data-gn-hide={show.desktop["data-gn-hide"]}
      onMouseLeave={leaveAll}
      onKeyDown={onWrapperKeyDown}
      onBlur={onWrapperBlur}
    >
      {/* Ground shadow */}
      <span
        aria-hidden
        className="pointer-events-none absolute"
        style={{
          left: "12%",
          right: "12%",
          bottom: -22,
          height: 26,
          borderRadius: "50%",
          background: G.ground,
          filter: "blur(22px)",
          opacity: 0.9,
        }}
      />

      <nav
        aria-label={ariaLabel}
        onPointerMove={onPointerMove}
        className="relative flex items-center"
        style={{
          ...glassSurfaceStyle("bar"),
          gap: 10,
          height: size.navH,
          padding: `0 ${size.padR}px 0 ${size.padL}px`,
          borderRadius: 999,
          transition: tx([
            ["height", 500],
            ["padding", 500],
          ]),
        }}
      >
        <GlassLayers variant="bar" />
        {/* Bottom caustic */}
        <span
          aria-hidden
          className="pointer-events-none absolute bottom-0 h-px"
          style={{ left: "34%", right: "34%", background: `linear-gradient(90deg, transparent, ${G.causticBar}, transparent)` }}
        />
        {/* Pointer spotlight */}
        {!reduced && (
          <span
            ref={spotRef}
            aria-hidden
            className="pointer-events-none absolute inset-0"
            style={{
              borderRadius: 999,
              background: `radial-gradient(160px 90px at var(--mx, 50%) 0px, ${G.spotBar}, transparent 70%)`,
              opacity: 0,
              transition: "opacity 600ms ease",
            }}
          />
        )}

        {/* Brand */}
        <Link
          href={brand.href}
          aria-label={brand.name}
          className={cn("relative flex items-center rounded-full", FOCUS_RING)}
          style={{ gap: 10, paddingRight: size.brandGap, transition: tx([["padding", 500]]) }}
        >
          {brand.mark ?? <EclipseMark />}
          <span
            ref={wordRef}
            aria-hidden
            className="overflow-hidden whitespace-nowrap"
            style={{
              fontWeight: 600,
              fontSize: 16.5,
              letterSpacing: "-0.035em",
              color: TEXT.wordmark,
              maxWidth: c ? 0 : natural.word,
              opacity: c ? 0 : 1,
              transition: tx([
                ["max-width", 500],
                ["opacity", 300, "ease"],
              ]),
            }}
          >
            {brand.name}
          </span>
        </Link>

        {/* Link track (offset parent for the lens) */}
        {/* Below 720px the full link row cannot fit; the mobile layout is pending design, so links hide rather than overflow. */}
        <div ref={trackRef} className={`relative flex items-center ${narrow.className}`.trimEnd()} data-gn-hide={narrow["data-gn-hide"]}>
          <span
            aria-hidden
            className="pointer-events-none absolute left-0 top-1/2"
            style={{
              height: 38,
              marginTop: -19,
              borderRadius: 999,
              width: lensRect?.w ?? 0,
              transform: `translateX(${lensRect?.x ?? 0}px)`,
              opacity: lensVisible ? 1 : 0,
              ...GLASS_LENS_STYLE,
              transition: lensInstant
                ? tx([["opacity", 300, "ease"]])
                : tx([
                    ["transform", 600],
                    ["width", 600],
                    ["opacity", 300, "ease"],
                  ]),
            }}
          >
            <GlassLensSheen />
          </span>

          {items.map((item, index) => {
            const lensed = item.id === lensId;
            const common = {
              ref: (el: HTMLElement | null) => {
                if (el) linkRefs.current.set(item.id, el);
                else linkRefs.current.delete(item.id);
              },
              onMouseEnter: () => moveTo(item),
              onFocus: () => focusTo(item),
              className: cn("relative z-[1] flex cursor-pointer items-center rounded-full border-none bg-transparent", FOCUS_RING),
              style: {
                height: 38,
                padding: "0 15px",
                gap: 5,
                fontFamily: FONT,
                fontWeight: 500,
                fontSize: 14,
                letterSpacing: "-0.01em",
                color: lensed ? TEXT.active : TEXT.muted,
                transform: lensed && !reduced ? "translateY(-1px)" : "none",
                transition: tx([
                  ["color", 250, "ease"],
                  ["transform", 400],
                ]),
              } as React.CSSProperties,
            };

            // Only the first item with a menu opens the mega-menu; later ones behave as plain links.
            if (item.menu && item === menuItem) {
              return (
                <button
                  key={item.id}
                  {...common}
                  ref={(el) => {
                    common.ref(el);
                    triggerRef.current = el;
                  }}
                  type="button"
                  aria-haspopup="true"
                  aria-expanded={panelOpen}
                  aria-controls={panelId}
                  data-sound="none"
                  onKeyDown={onTriggerKeyDown}
                  onPointerDown={(e) => {
                    pointerType.current = e.pointerType;
                  }}
                  onClick={(e) => {
                    activate(item, e);
                    setHoverId(item.id);
                    // Mouse users already opened it by hovering; touch has no hover, so a tap toggles, and so
                    // does a click without a pointer (detail 0: assistive technology).
                    if (pointerType.current !== "mouse" || e.detail === 0) {
                      if (!condensed) sfx(panelOpen ? "close" : "open");
                      setMenuOpen((o) => !o && !condensed);
                    } else sfx("select", { index });
                  }}
                >
                  {item.label}
                  <Chevron open={panelOpen} tx={tx} />
                </button>
              );
            }
            const Tag = item.href ? Link : "button";
            return (
              <Tag
                key={item.id}
                {...common}
                {...(item.href ? { href: item.href } : { type: "button" as const })}
                aria-current={item.id === activeId ? "page" : undefined}
                data-sound="select"
                data-sound-index={index}
                onClick={(e: React.MouseEvent<HTMLElement>) => activate(item, e)}
              >
                {item.label}
              </Tag>
            );
          })}
        </div>

        {/* Right cluster */}
        <div
          className="relative flex items-center"
          style={{ gap: 4, marginLeft: size.rightGap, transition: tx([["margin", 500]]) }}
        >
          {secondary && (
            <Link
              href={secondary.href}
              tabIndex={c ? -1 : undefined}
              aria-hidden={c || undefined}
              data-gn-hide={narrow["data-gn-hide"]}
              className={cn("overflow-hidden whitespace-nowrap rounded-full", narrow.className, FOCUS_RING)}
              style={{
                fontWeight: 500,
                fontSize: 14,
                color: TEXT.signIn,
                maxWidth: c ? 0 : natural.sign + 24,
                padding: `0 ${size.signPad}px`,
                opacity: c ? 0 : 1,
                transition: tx([
                  ["max-width", 500],
                  ["padding", 500],
                  ["opacity", 300, "ease"],
                ]),
              }}
            >
              {/* inline-block: inline boxes report no scroll width to measure. */}
              <span ref={signTextRef} className="inline-block">
                {secondary.label}
              </span>
            </Link>
          )}

          {cta && (
          <CtaTag
            {...(cta.href ? { href: cta.href } : { type: "button" as const })}
            onClick={cta.onClick}
            aria-label={c ? cta.label : undefined}
            className={cn(
              "flex cursor-pointer items-center rounded-full border-none",
              !reduced && "hover:-translate-y-px active:scale-95",
              FOCUS_RING,
            )}
            style={{
              height: size.ctaH,
              padding: `0 5px 0 ${size.ctaPadL}px`,
              gap: size.ctaGap,
              background: C.accent,
              color: C.accentFg,
              fontFamily: FONT,
              fontWeight: 500,
              fontSize: 14,
              boxShadow: G.ctaShadow,
              transition: reduced
                ? "none"
                : tx([
                    ["height", 500],
                    ["padding", 500],
                    ["gap", 500],
                    ["translate", 350],
                    ["scale", 350],
                  ]),
            }}
          >
            <span
              ref={ctaLabelRef}
              className="overflow-hidden whitespace-nowrap"
              style={{ maxWidth: c ? 0 : natural.cta, transition: tx([["max-width", 500]]) }}
            >
              {cta.label}
            </span>
            <span
              aria-hidden
              className="flex shrink-0 items-center justify-center rounded-full"
              style={{
                width: 32,
                height: 32,
                color: G.ctaDiscFg,
                background: G.ctaDiscBg,
                boxShadow: G.ctaDiscShadow,
              }}
            >
              <Arrow />
            </span>
          </CtaTag>
          )}
        </div>
      </nav>

      {menu && (
        <MegaMenu
          id={panelId}
          menu={menu}
          open={panelOpen}
          panelRef={panelRef}
          width={menuWidth}
          shift={shift}
          Link={Link}
          onSelect={(row, e) => {
            if (row && menuItem) onMenuItemSelect?.(row, menuItem, e);
            closeMenu();
            // Keyboard activation: the panel turns inert, so give focus back to its trigger.
            if (e.detail === 0) triggerRef.current?.focus();
          }}
          hoverIndex={menuHover}
          onHoverIndex={setMenuHover}
          onKeyDown={onPanelKeyDown}
          rowRefs={rowRefs}
          tx={tx}
          reduced={reduced}
        />
      )}
    </div>
  );
}

// ---------------------------------------------------------------------------------------------
// GlassNav: desktop bar, and the phone pill that grows into a sheet below 720px
// ---------------------------------------------------------------------------------------------

export function GlassNav({
  layout = "auto",
  mobile,
  items = defaultGlassNavItems,
  activeId: activeProp,
  // The default menu rests on Pricing, as designed; custom items start with no active link.
  defaultActiveId = items === defaultGlassNavItems ? "pricing" : undefined,
  onActiveIdChange,
  onActiveChange,
  onMenuItemSelect,
  brand,
  secondary,
  cta,
  condensed,
  condenseOnScroll,
  motion,
  sound = true,
  theme = "system",
  breakpoint,
  menuOpen,
  defaultMenuOpen,
  onMenuOpenChange,
  sheetOpen,
  defaultSheetOpen,
  onSheetOpenChange,
  menuWidth,
  zIndex = 50,
  linkComponent,
  labels,
  as: Root = "div",
  "aria-label": ariaLabelProp,
  className,
  style,
  ref,
  ...rest
}: GlassNavProps) {
  const [innerActive, setInnerActive] = React.useState(defaultActiveId);
  const activeId = activeProp ?? innerActive;
  const activate = (id: string, event?: React.MouseEvent<HTMLElement>) => {
    if (activeProp === undefined) setInnerActive(id);
    onActiveIdChange?.(id, event);
    onActiveChange?.(id);
  };
  const L = React.useMemo(() => mergeLabels(defaultGlassNavLabels, labels), [labels]);
  const show = glassNavLayout(layout, breakpoint);
  const sfx = useGlassSound(sound).play;
  const shared: NavVariantProps = {
    brand,
    items,
    activeId,
    onActiveChange: activate,
    onMenuItemSelect,
    secondary,
    cta,
    motion,
    ariaLabel: ariaLabelProp ?? L.nav,
    Link: linkComponent ?? "a",
    labels: L,
    breakpoint: show.breakpoint,
    layout,
    show,
    sfx,
  };
  return (
    <Root
      {...pickRootAttrs(rest)}
      ref={ref as React.Ref<HTMLDivElement>}
      {...glassSoundRootAttrs(sound)}
      data-lumen={theme}
      data-gn=""
      data-gn-contents={show.root["data-gn-contents"]}
      className={cn(show.root.className, className)}
      style={style ? { ...GLASS_ROOT_STYLE, ...style } : GLASS_ROOT_STYLE}
    >
      <GlassThemeStyle />
      <style href="beautiful-ui-glass-nav-colors" precedence="medium">
        {GLASS_NAV_COLOR_CSS}
      </style>
      {show.custom && (
        <style href={`beautiful-ui-glass-nav-bp-${show.breakpoint}`} precedence="medium">
          {glassNavBreakpointCss(show.breakpoint)}
        </style>
      )}
      <DesktopNav
        {...shared}
        condensed={condensed}
        condenseOnScroll={condenseOnScroll}
        menuOpen={menuOpen}
        defaultMenuOpen={defaultMenuOpen}
        onMenuOpenChange={onMenuOpenChange}
        menuWidth={menuWidth}
      />
      <MobileNav
        {...shared}
        menuFooter={mobile?.menuFooter}
        open={sheetOpen}
        defaultOpen={defaultSheetOpen}
        onOpenChange={onSheetOpenChange}
        zIndex={zIndex}
      />
    </Root>
  );
}

function StatusLine({ label }: { label: React.ReactNode }) {
  return (
    <div
      className="flex items-center justify-center"
      style={{ gap: 8, marginTop: 6, fontFamily: MONO, fontWeight: 500, fontSize: 11, letterSpacing: ".08em", color: TEXT.label }}
    >
      <span className="rounded-full" style={{ width: 6, height: 6, background: C.status, boxShadow: G.dotGlow }} />
      {label}
    </div>
  );
}

function MobileNav({
  brand = DEFAULT_BRAND,
  items,
  activeId,
  onActiveChange,
  onMenuItemSelect,
  secondary = DEFAULT_SECONDARY,
  cta = DEFAULT_CTA,
  motion = "full",
  ariaLabel,
  Link,
  labels: L,
  breakpoint,
  layout,
  show,
  menuFooter,
  open: openProp,
  defaultOpen = false,
  onOpenChange,
  zIndex,
  sfx,
}: NavVariantProps & {
  menuFooter?: React.ReactNode;
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean) => void;
  zIndex: number;
}) {
  const uid = React.useId();
  const { tx, reduced } = useGlassTransitions(motion);
  const [open, setOpen] = useControllable(openProp, defaultOpen, onOpenChange);
  // Id of the expanded menu item in the sheet (one at a time).
  const [subOpen, setSubOpen] = React.useState<string | null>(null);
  const pillRef = React.useRef<HTMLElement>(null);
  const buttonRef = React.useRef<HTMLButtonElement>(null);
  const sheetId = `${uid}-sheet`;
  const below = useBelowBreakpoint(breakpoint);
  // Lock page scroll only while the sheet can be on screen as the phone layout (never for layout="desktop").
  useSheetScrollLock(open && layout !== "desktop", breakpoint);
  // layout="auto": growing past the breakpoint hides the sheet, so close it rather than resurrect it later.
  const wasBelow = React.useRef(below);
  React.useEffect(() => {
    if (layout === "auto" && wasBelow.current && !below && open) setOpen(false);
    wasBelow.current = below;
  }, [layout, below, open, setOpen]);
  const footer = menuFooter === undefined ? <StatusLine label={L.status} /> : menuFooter;

  const close = (restoreFocus: boolean) => {
    setOpen(false);
    if (restoreFocus) buttonRef.current?.focus();
  };

  // Escape closes the sheet wherever focus is.
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e: KeyboardEvent) => {
      if (e.key !== "Escape") return;
      e.preventDefault();
      sfx("close");
      setOpen(false);
      buttonRef.current?.focus();
    };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open, setOpen, sfx]);

  const top = `calc(${GLASS_SAFE_TOP} + 8px)`;
  const CtaTag = cta?.href ? Link : "button";
  const ctaProps = cta?.href ? { href: cta.href } : { type: "button" as const };
  // Keyboard activation (detail 0) of a link that keeps the page: return focus to the menu button.
  const closeFrom = (e: React.MouseEvent) => close(e.detail === 0);

  return (
    <div className={show.mobile.className} data-gn-hide={show.mobile["data-gn-hide"]}>
      {/* Scrim */}
      <div
        aria-hidden
        data-sound="close"
        onClick={() => close(false)}
        className="fixed inset-0"
        style={{
          zIndex: zIndex - 10,
          background: G.scrim,
          backdropFilter: reduced ? "none" : "blur(8px)",
          WebkitBackdropFilter: reduced ? "none" : "blur(8px)",
          opacity: open ? 1 : 0,
          pointerEvents: open ? "auto" : "none",
          transition: tx([["opacity", 500]]),
        }}
      />

      {/* The pill, which grows into the sheet */}
      <nav
        ref={pillRef}
        aria-label={ariaLabel}
        onKeyDown={(e) => {
          if (open) trapFocus(e, pillRef.current);
        }}
        className="fixed overflow-hidden"
        style={{
          zIndex,
          left: 12,
          right: 12,
          top,
          height: open ? `calc(${GLASS_VH} - ${top} - 56px)` : 56,
          borderRadius: 28,
          background: G.sheetBody,
          backdropFilter: G.barBlur,
          WebkitBackdropFilter: G.barBlur,
          boxShadow: G.barShadow,
          transition: tx([["height", 620]]),
        }}
      >
        <GlassRim rim={open ? G.barRimTall : G.barRim} />
        <span
          aria-hidden
          className="pointer-events-none absolute"
          style={{ left: 1, right: 1, top: 1, height: 40, borderRadius: "27px 27px 0 0", background: G.bandSpec }}
        />

        {/* Header row */}
        <div className="relative flex items-center" style={{ height: 56, gap: 10, padding: "0 8px 0 14px" }}>
          <Link href={brand.href} onClick={() => open && close(false)} className={cn("flex min-w-0 flex-1 items-center rounded-full", FOCUS_RING)} style={{ gap: 10 }}>
            {brand.mark ?? <EclipseMark />}
            <span className="truncate" style={{ fontWeight: 600, fontSize: 16.5, letterSpacing: "-0.035em", color: TEXT.wordmark }}>
              {brand.name}
            </span>
          </Link>
          {cta && (
            <CtaTag
              {...ctaProps}
              onClick={cta.onClick}
              tabIndex={open ? -1 : undefined}
              aria-hidden={open || undefined}
              className={cn("flex shrink-0 cursor-pointer items-center rounded-full border-none", FOCUS_RING)}
              style={{
                height: 40,
                padding: "0 4px 0 14px",
                gap: 8,
                background: C.accent,
                color: C.accentFg,
                fontFamily: FONT,
                fontWeight: 500,
                fontSize: 14,
                boxShadow: G.ctaMiniShadow,
                opacity: open ? 0 : 1,
                transform: open && !reduced ? "scale(.9)" : "none",
                pointerEvents: open ? "none" : "auto",
                transition: tx([
                  ["opacity", 300, "ease"],
                  ["transform", 450],
                ]),
              }}
            >
              {cta.label}
              <span
                aria-hidden
                className="flex shrink-0 items-center justify-center rounded-full"
                style={{ width: 32, height: 32, color: G.ctaDiscFg, background: G.ctaDiscBg, boxShadow: G.ctaDiscShadow }}
              >
                <svg width="13" height="13" viewBox="0 0 14 14">
                  <path d="M3 7h8M7.5 3.5 11 7l-3.5 3.5" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
              </span>
            </CtaTag>
          )}
          <button
            ref={buttonRef}
            type="button"
            aria-label={open ? L.closeMenu : L.openMenu}
            aria-expanded={open}
            aria-controls={sheetId}
            data-sound={open ? "close" : "open"}
            onClick={() => setOpen((o) => !o)}
            className={cn("relative shrink-0 cursor-pointer rounded-full border-none", !reduced && "active:scale-[.94]", FOCUS_RING)}
            style={{ width: 44, height: 44, background: G.btnBg, boxShadow: G.btnShadow, transition: tx([["scale", 300]]) }}
          >
            {[1, -1].map((sign) => (
              <span
                key={sign}
                aria-hidden
                className="absolute top-1/2"
                style={{
                  left: 14,
                  right: 14,
                  height: 1.6,
                  marginTop: -0.8,
                  borderRadius: 2,
                  background: G.btnFg,
                  transform: open ? `rotate(${45 * sign}deg)` : `translateY(${-3.5 * sign}px)`,
                  transition: tx([["transform", 500]]),
                }}
              />
            ))}
          </button>
        </div>

        {/* Sheet body */}
        <div
          id={sheetId}
          inert={!open}
          className="absolute inset-x-0 bottom-0 flex flex-col overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
          style={{ top: 56, padding: "6px 18px 18px", overscrollBehavior: "contain" }}
        >
          <span
            aria-hidden
            className="block shrink-0"
            style={{
              height: 1,
              margin: "0 -2px 4px",
              background: `linear-gradient(90deg, transparent, ${G.divider}, transparent)`,
              opacity: open ? 1 : 0,
              transition: tx([["opacity", 300, "ease"]]),
            }}
          />
          {items.map((item, i) => {
            const delay = open && !reduced ? 140 + i * 45 : 0;
            const active = item.id === activeId;
            const po = open && subOpen === item.id;
            const subId = `${uid}-sub-${item.id}`;
            const RowTag = item.href ? Link : "button";
            const rowStyle: React.CSSProperties = {
              height: 58,
              padding: "0 4px",
              gap: 10,
              fontFamily: FONT,
              fontWeight: 500,
              fontSize: 23,
              letterSpacing: "-0.035em",
              color: active ? TEXT.active : G.link,
              boxShadow: G.rowLine,
            };
            const label = (
              <span className="flex items-center" style={{ gap: 10 }}>
                {item.label}
                {active && <span className="rounded-full" style={{ width: 6, height: 6, background: C.dot, boxShadow: G.dotGlow }} />}
              </span>
            );
            return (
              <div
                key={item.id}
                className="flex flex-col"
                style={{
                  opacity: open ? 1 : 0,
                  transform: open || reduced ? "translateY(0)" : "translateY(10px)",
                  transition: tx([
                    ["opacity", 450, "spring", delay],
                    ["transform", 550, "spring", delay],
                  ]),
                }}
              >
                {item.menu ? (
                  <>
                    <button
                      type="button"
                      aria-expanded={po}
                      aria-controls={subId}
                      data-sound={po ? "toggleOff" : "toggleOn"}
                      onClick={() => setSubOpen((o) => (o === item.id ? null : item.id))}
                      className={cn("flex w-full cursor-pointer items-center justify-between border-none bg-transparent text-start", FOCUS_RING)}
                      style={rowStyle}
                    >
                      {label}
                      <span
                        aria-hidden
                        className="flex items-center justify-center rounded-full"
                        style={{ width: 32, height: 32, boxShadow: `inset 0 0 0 1px ${G.ring}`, color: G.tag }}
                      >
                        <svg width="12" height="12" viewBox="0 0 16 16" style={{ transform: po ? "rotate(180deg)" : "none", transition: tx([["transform", 450]]) }}>
                          <path d="M4 6l4 4 4-4" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
                        </svg>
                      </span>
                    </button>
                    <div
                      id={subId}
                      inert={!po}
                      className="grid"
                      style={{ gridTemplateRows: po ? "1fr" : "0fr", transition: tx([["grid-template-rows", 550]]) }}
                    >
                      <div className="min-h-0 overflow-hidden">
                        <div className="flex flex-col" style={{ padding: "8px 0 10px", gap: 2 }}>
                          {item.menu.items.map((row, j) => {
                            const d = po && !reduced ? 60 + j * 40 : 0;
                            return (
                              <Link
                                key={row.id}
                                href={row.href}
                                data-sound="select"
                                data-sound-index={j}
                                onClick={(e: React.MouseEvent<HTMLElement>) => {
                                  onMenuItemSelect?.(row, item, e);
                                  closeFrom(e);
                                }}
                                className={cn("flex items-center active:bg-[var(--lg-pressBg)]", FOCUS_RING)}
                                style={{
                                  height: 56,
                                  gap: 12,
                                  padding: "0 10px",
                                  borderRadius: 16,
                                  opacity: po ? 1 : 0,
                                  transform: po || reduced ? "translateY(0)" : "translateY(6px)",
                                  transition: tx([
                                    ["opacity", 400, "spring", d],
                                    ["transform", 500, "spring", d],
                                    ["background", 200, "ease"],
                                  ]),
                                }}
                              >
                                <span
                                  aria-hidden
                                  className="flex shrink-0 items-center justify-center"
                                  style={{ width: 36, height: 36, borderRadius: 11, background: G.tileBg, boxShadow: G.tileFlatShadow, color: G.tileGlyph }}
                                >
                                  {row.icon ? (
                                    <span className="flex [&_svg]:size-4 [&_svg]:[stroke-width:1.5]">{row.icon}</span>
                                  ) : (
                                    <span style={{ width: 11, height: 11, borderRadius: GLYPH_SHAPES[j % GLYPH_SHAPES.length], background: "currentColor" }} />
                                  )}
                                </span>
                                <span className="flex min-w-0 flex-col" style={{ gap: 2 }}>
                                  <span style={{ fontWeight: 500, fontSize: 15, color: TEXT.primary }}>{row.title}</span>
                                  <span className="truncate" style={{ fontWeight: 400, fontSize: 12.5, color: TEXT.subtle }}>
                                    {row.description}
                                  </span>
                                </span>
                              </Link>
                            );
                          })}
                        </div>
                      </div>
                    </div>
                  </>
                ) : (
                  <RowTag
                    {...(item.href ? { href: item.href } : { type: "button" as const })}
                    aria-current={active ? "page" : undefined}
                    data-sound="select"
                    data-sound-index={i}
                    onClick={(e: React.MouseEvent<HTMLElement>) => {
                      onActiveChange?.(item.id, e);
                      closeFrom(e);
                    }}
                    className={cn(
                      "flex items-center justify-between",
                      !item.href && "w-full cursor-pointer border-none bg-transparent text-start",
                      FOCUS_RING,
                    )}
                    style={rowStyle}
                  >
                    {label}
                    <span aria-hidden className="flex" style={{ color: G.arrowFaint, marginRight: 9 }}>
                      <Arrow strokeWidth={1.5} />
                    </span>
                  </RowTag>
                )}
              </div>
            );
          })}

          {/* Footer */}
          <div
            className="mt-auto flex flex-col"
            style={{
              paddingTop: 24,
              gap: 10,
              opacity: open ? 1 : 0,
              transform: open || reduced ? "translateY(0)" : "translateY(14px)",
              transition: tx([
                ["opacity", 450, "spring", open && !reduced ? 380 : 0],
                ["transform", 550, "spring", open && !reduced ? 380 : 0],
              ]),
            }}
          >
            {secondary && (
              <Link
                href={secondary.href}
                onClick={closeFrom}
                className={cn("flex items-center justify-center rounded-full", !reduced && "active:scale-[.98]", FOCUS_RING)}
                style={{ height: 52, background: G.ghostBg, boxShadow: G.ghostShadow, color: TEXT.primary, fontWeight: 500, fontSize: 15 }}
              >
                {secondary.label}
              </Link>
            )}
            {cta && (
              <CtaTag
                {...ctaProps}
                onClick={(e: React.MouseEvent<HTMLElement>) => {
                  cta.onClick?.(e);
                  closeFrom(e);
                }}
                className={cn("flex cursor-pointer items-center justify-center rounded-full border-none", !reduced && "active:scale-[.98]", FOCUS_RING)}
                style={{ height: 52, gap: 10, background: C.accent, color: C.accentFg, fontFamily: FONT, fontWeight: 500, fontSize: 15, boxShadow: G.ctaBigShadow }}
              >
                {cta.label}
                <Arrow />
              </CtaTag>
            )}
            {footer}
          </div>
        </div>
      </nav>
    </div>
  );
}

// ---------------------------------------------------------------------------------------------
// MegaMenu
// ---------------------------------------------------------------------------------------------

function MegaMenu({
  id,
  menu,
  open,
  panelRef,
  width,
  shift,
  Link,
  onSelect,
  hoverIndex,
  onHoverIndex,
  onKeyDown,
  rowRefs,
  tx,
  reduced,
}: {
  id: string;
  menu: NonNullable<GlassNavItem["menu"]>;
  open: boolean;
  panelRef: React.RefObject<HTMLDivElement | null>;
  width: number | string;
  shift: number;
  Link: React.ElementType;
  /** A row (or null for the feature card) was clicked. */
  onSelect: (row: GlassNavMenuItem | null, event: React.MouseEvent<HTMLElement>) => void;
  hoverIndex: number | null;
  onHoverIndex: (i: number | null) => void;
  onKeyDown: (e: React.KeyboardEvent) => void;
  rowRefs: React.RefObject<(HTMLElement | null)[]>;
  tx: Tx;
  reduced: boolean;
}) {
  const feature = menu.feature;
  return (
    <div
      ref={panelRef}
      id={id}
      inert={!open}
      aria-hidden={!open || undefined}
      onKeyDown={onKeyDown}
      className="absolute left-1/2 top-full"
      style={{
        marginTop: 12,
        width,
        maxWidth: "calc(100vw - 16px)",
        transformOrigin: "top center",
        transform: `${shift ? `translateX(calc(-50% + ${shift}px))` : "translateX(-50%)"} ${open || reduced ? "translateY(0) scale(1)" : "translateY(-6px) scale(.97)"}`,
        opacity: open ? 1 : 0,
        filter: reduced ? "none" : open ? "blur(0px)" : "blur(6px)",
        pointerEvents: open ? "auto" : "none",
        transition: tx([
          ["transform", 500],
          ["opacity", 350],
          ["filter", 400],
        ]),
      }}
    >
      {/* Hover bridge across the gap */}
      <span aria-hidden className="absolute inset-x-0" style={{ top: -14, height: 14 }} />

      <div
        className="relative grid"
        style={{
          ...glassSurfaceStyle("panel"),
          padding: 8,
          borderRadius: 24,
          gridTemplateColumns: feature ? "minmax(0,1fr) 188px" : "minmax(0,1fr)",
          gap: 8,
        }}
      >
        <GlassLayers variant="panel" />

        {/* Left column */}
        <div className="relative flex flex-col" style={{ padding: "4px 0" }} onMouseLeave={() => onHoverIndex(null)}>
          {menu.heading && (
            <div
              style={{
                padding: "6px 12px 8px",
                fontFamily: MONO,
                fontWeight: 500,
                fontSize: 10.5,
                letterSpacing: ".14em",
                textTransform: "uppercase",
                color: TEXT.label,
              }}
            >
              {menu.heading}
            </div>
          )}
          <div className="relative">
            {/* Row highlight: one pill that slides between rows */}
            <span
              aria-hidden
              className="pointer-events-none absolute inset-x-0 top-0"
              style={{
                height: ROW_H,
                borderRadius: 16,
                transform: `translateY(${(hoverIndex ?? 0) * ROW_H}px)`,
                opacity: hoverIndex == null ? 0 : 1,
                ...GLASS_HIGHLIGHT_STYLE,
                transition: tx([
                  ["transform", 450],
                  ["opacity", 250, "ease"],
                ]),
              }}
            />
            {menu.items.map((row, i) => {
              const hot = hoverIndex === i;
              const delay = open && !reduced ? 40 + i * 35 : 0;
              return (
                <Link
                  key={row.id}
                  ref={(el: HTMLElement | null) => {
                    rowRefs.current[i] = el;
                  }}
                  href={row.href}
                  data-sound="select"
                  data-sound-index={i}
                  onClick={(e: React.MouseEvent<HTMLElement>) => onSelect(row, e)}
                  onMouseEnter={() => onHoverIndex(i)}
                  onFocus={() => onHoverIndex(i)}
                  className="relative flex items-center outline-none focus-visible:shadow-[0_0_0_2px_var(--lg-focus)]"
                  style={{
                    height: ROW_H,
                    gap: 12,
                    padding: "0 12px",
                    borderRadius: 16,
                    opacity: open ? 1 : 0,
                    transform: open || reduced ? "translateY(0)" : "translateY(6px)",
                    transition: tx([
                      ["opacity", 400, "spring", delay],
                      ["transform", 500, "spring", delay],
                    ]),
                  }}
                >
                  <span
                    aria-hidden
                    className="flex shrink-0 items-center justify-center"
                    style={{
                      width: 36,
                      height: 36,
                      borderRadius: 11,
                      background: G.tileBg,
                      boxShadow: G.tileShadow,
                      color: hot ? G.active : G.tileFg,
                      transition: tx([["color", 300, "ease"]]),
                    }}
                  >
                    {row.icon ? (
                      <span className="flex [&_svg]:size-4 [&_svg]:[stroke-width:1.5]">{row.icon}</span>
                    ) : (
                      <span
                        style={{
                          width: 11,
                          height: 11,
                          borderRadius: GLYPH_SHAPES[i % GLYPH_SHAPES.length],
                          background: "currentColor",
                        }}
                      />
                    )}
                  </span>
                  <span className="flex min-w-0 flex-1 flex-col" style={{ gap: 2 }}>
                    <span style={{ fontWeight: 500, fontSize: 14, letterSpacing: "-0.01em", color: TEXT.primary }}>{row.title}</span>
                    <span
                      className="overflow-hidden text-ellipsis whitespace-nowrap"
                      style={{ fontWeight: 400, fontSize: 12.5, color: TEXT.secondary }}
                    >
                      {row.description}
                    </span>
                  </span>
                  <span
                    aria-hidden
                    className="flex shrink-0"
                    style={{
                      color: G.tag,
                      opacity: hot ? 1 : 0,
                      transform: hot || reduced ? "translateX(0)" : "translateX(-6px)",
                      transition: tx([
                        ["opacity", 250, "ease"],
                        ["transform", 400],
                      ]),
                    }}
                  >
                    <Arrow strokeWidth={1.5} />
                  </span>
                </Link>
              );
            })}
          </div>
        </div>

        {/* Feature card */}
        {feature && (
          <Link
            href={feature.href}
            onClick={(e: React.MouseEvent<HTMLElement>) => onSelect(null, e)}
            className="relative flex flex-col justify-end overflow-hidden outline-none focus-visible:shadow-[0_0_0_2px_var(--lg-focus)]"
            style={{
              borderRadius: 18,
              padding: 14,
              background: G.featBg,
              boxShadow: G.featShadow,
              opacity: open ? 1 : 0,
              transform: open || reduced ? "translateY(0) scale(1)" : "translateY(8px) scale(.98)",
              transition: tx([
                ["opacity", 450, "spring", reduced ? 0 : 80],
                ["transform", 550, "spring", 80],
              ]),
            }}
          >
            <span
              className="absolute"
              style={{
                top: 14,
                left: 14,
                fontFamily: MONO,
                fontWeight: 500,
                fontSize: 10,
                letterSpacing: ".12em",
                color: G.icon,
                padding: "3px 7px",
                borderRadius: 6,
                boxShadow: `inset 0 0 0 1px ${G.tagRing}`,
              }}
            >
              {feature.badge}
            </span>
            {feature.art ?? <FeatureEclipse />}
            <span className="relative" style={{ fontWeight: 500, fontSize: 14, letterSpacing: "-0.01em", color: TEXT.primary }}>
              {feature.title}
            </span>
            <span className="relative" style={{ fontWeight: 400, fontSize: 12, color: TEXT.secondary, marginTop: 3 }}>
              {feature.subtitle}
            </span>
          </Link>
        )}
      </div>
    </div>
  );
}

export { EclipseMark, GlassLayers, glassSurfaceStyle };
export default GlassNav;
```

### lib/beautiful-ui/glass-sound.tsx

```tsx
"use client";

/*
 * Lumen sound: the voice of the glass family. A thin layer over a Web Audio synthesiser
 * (@web-kits/audio today). Components never import the engine; they call play(cue) or mark
 * elements with data-sound, and this file decides what that sounds like.
 *
 *   - No provider, no sound: play() is a silent no-op and the engine is never loaded.
 *   - Nothing plays, and no AudioContext exists, before the first user gesture.
 *   - Mute persists in localStorage ("beautiful-ui-sound"), SSR safe, never throws.
 *   - The click layer: inside the provider every click on something interactive answers with a
 *     cue. data-sound="<cue>" picks a richer one, data-sound="none" means the element plays its
 *     own, data-sound="off" silences a whole subtree.
 *
 * The voice: sine glints with a quiet inharmonic partial (×2.756, a struck glass bar), 2ms
 * attacks, no reverb, all in D major pentatonic around D6. Short, soft, never harsh.
 */

import * as React from "react";
import type { Layer, PlayOptions, SoundDefinition } from "@web-kits/audio";
import { GLASS_FOCUS, GlassThemeStyle, mergeLabels, type GlassLabels, type GlassTheme } from "@/lib/beautiful-ui/glass";

// ---------------------------------------------------------------------------------------------
// Vocabulary
// ---------------------------------------------------------------------------------------------

/** Every cue in the Lumen vocabulary, grouped as they are documented. */
export const GLASS_SOUND_GROUPS = {
  presses: ["tap", "select", "destructive"],
  toggles: ["toggleOn", "toggleOff"],
  surfaces: ["open", "close", "swoosh"],
  values: ["tick", "sliderTick", "key"],
  outcomes: ["success", "error", "warning"],
  named: ["copy", "notification", "chirp", "command", "blocked", "lock", "halt"],
} as const;

export type GlassSoundGroup = keyof typeof GLASS_SOUND_GROUPS;
export type GlassSoundCue = (typeof GLASS_SOUND_GROUPS)[GlassSoundGroup][number];
export const GLASS_SOUND_CUES = Object.values(GLASS_SOUND_GROUPS).flat() as GlassSoundCue[];

export function isGlassSoundCue(value: unknown): value is GlassSoundCue {
  return typeof value === "string" && (GLASS_SOUND_CUES as string[]).includes(value);
}

/** One sound, as the engine describes it (layers, envelope, filters). */
export type GlassSoundDefinition = SoundDefinition;

/** What a cue can be told when it plays. */
export interface GlassSoundPlayOptions {
  /** Position in a group (tabs, options, OTP digits): select and key step up D major pentatonic. */
  index?: number;
  /** Pitch shift in semitones (can be fractional). */
  pitch?: number;
  /** Volume multiplier, 0–1. */
  volume?: number;
  /** Stereo position, −1 (left) to 1 (right). */
  pan?: number;
  /** Skip the per-cue throttle (for sequences you schedule yourself). */
  force?: boolean;
  /** Which theme's patch plays the cue. Default "lumen"; the click layer reads data-sound-voice. */
  voice?: GlassSoundVoice;
}

/**
 * A theme's voice: the same cue vocabulary, patched in that theme's sound. Components mark their
 * root with data-sound-voice, so one provider plays each theme in its own voice.
 */
export type GlassSoundVoice = "lumen" | "analogue";
export const GLASS_SOUND_VOICES: readonly GlassSoundVoice[] = ["lumen", "analogue"];
export const isGlassSoundVoice = (value: unknown): value is GlassSoundVoice => value === "lumen" || value === "analogue";

/** A component's `sound` prop: true (default) plays when a provider enables sound, false never, "subtle" at 55%. */
export type GlassSoundProp = boolean | "subtle";

// ---------------------------------------------------------------------------------------------
// The Lumen voice
// ---------------------------------------------------------------------------------------------

const GLASS_RATIO = 2.756; // first overtone of a free glass bar: what makes a sine sound struck
type Freq = number | { start: number; end: number };
const scale = (f: Freq, k: number): Freq => (typeof f === "number" ? f * k : { start: f.start * k, end: f.end * k });

/** A struck glass note: the fundamental and a quiet, faster-fading overtone. */
function glint(freq: Freq, decay: number, gain: number, { delay = 0, attack = 0.002, ring = 0.16 } = {}): Layer[] {
  const layers: Layer[] = [{ source: { type: "sine", frequency: freq }, envelope: { attack, decay }, gain, delay }];
  if (ring > 0) layers.push({ source: { type: "sine", frequency: scale(freq, GLASS_RATIO) }, envelope: { attack, decay: decay * 0.42 }, gain: gain * ring, delay });
  return layers;
}

/** A breath of filtered noise: the air a surface moves. */
function air(from: number, to: number, attack: number, decay: number, gain: number, delay = 0): Layer {
  return {
    source: { type: "noise", color: "pink" },
    filter: { type: "bandpass", frequency: from, resonance: 1.1, envelope: { attack, peak: to, decay } },
    envelope: { attack, decay },
    gain,
    delay,
  };
}

/** A soft, rounded low tone (errors, blocked, destructive): triangle through a lowpass. */
function knock(freq: Freq, decay: number, gain: number, delay = 0, cutoff = 1500): Layer {
  return { source: { type: "triangle", frequency: freq }, filter: { type: "lowpass", frequency: cutoff, resonance: 0.4 }, envelope: { attack: 0.003, decay }, gain, delay };
}

// D major pentatonic around D6: the key every Lumen cue lives in.
const D5 = 587.33, E5 = 659.26, A5 = 880, B5 = 987.77, D6 = 1174.66, E6 = 1318.51, Fs6 = 1479.98, A6 = 1760, D7 = 2349.32, E7 = 2637.02;

/** The designed Lumen patch. Levels are before the provider volume (default 0.8). */
export const GLASS_SOUND_PATCH: Record<GlassSoundCue, GlassSoundDefinition> = {
  // Presses
  tap: { layers: [...glint({ start: 1540, end: Fs6 }, 0.034, 0.05, { ring: 0.12 }), { source: { type: "noise", color: "white" }, filter: { type: "highpass", frequency: 6000 }, envelope: { attack: 0.001, decay: 0.006 }, gain: 0.006 }] },
  select: { layers: glint(D6, 0.055, 0.045, { ring: 0.2 }) },
  destructive: { layers: [knock({ start: E5, end: D5 }, 0.1, 0.07), ...glint(A5 / 2, 0.08, 0.03, { ring: 0 })] },
  // Toggles
  toggleOn: { layers: [...glint(B5, 0.038, 0.04), ...glint(E6, 0.07, 0.045, { delay: 0.048 })] },
  toggleOff: { layers: [...glint(E6, 0.038, 0.04), ...glint(B5, 0.07, 0.04, { delay: 0.048 })] },
  // Surfaces
  open: { layers: [{ source: { type: "sine", frequency: { start: 740, end: D6 } }, envelope: { attack: 0.012, decay: 0.11 }, gain: 0.035 }, air(2200, 5200, 0.02, 0.09, 0.012)] },
  close: { layers: [{ source: { type: "sine", frequency: { start: D6, end: 700 } }, envelope: { attack: 0.006, decay: 0.09 }, gain: 0.03 }, air(3600, 1600, 0.008, 0.08, 0.01)] },
  swoosh: { layers: [air(900, 4200, 0.05, 0.17, 0.045), ...glint(A6, 0.09, 0.012, { delay: 0.07, ring: 0 })] },
  // Values
  tick: { layers: glint(D7, 0.012, 0.016, { ring: 0 }) },
  sliderTick: { layers: glint(E7, 0.008, 0.009, { ring: 0 }) },
  key: { layers: glint(A5 * 1.5, 0.035, 0.03, { ring: 0.12 }) },
  // Outcomes
  success: { layers: [...glint(D6, 0.12, 0.04), ...glint(A6, 0.26, 0.045, { delay: 0.075, ring: 0.22 })] },
  error: { layers: [knock(E5, 0.09, 0.05, 0, 1600), knock(D5 * 0.89, 0.15, 0.05, 0.085, 1400)] },
  warning: { layers: [...glint(A5, 0.06, 0.04), ...glint(A5, 0.08, 0.035, { delay: 0.1 })] },
  // Named
  copy: { layers: [...glint(A6, 0.045, 0.035), ...glint(D7, 0.09, 0.03, { delay: 0.035 })] },
  notification: { layers: [...glint(D6, 0.5, 0.028, { attack: 0.004 }), ...glint(Fs6, 0.45, 0.02, { delay: 0.02, attack: 0.004 }), ...glint(A6, 0.5, 0.018, { delay: 0.04, attack: 0.004 })] },
  chirp: { layers: [{ source: { type: "sine", frequency: { start: 1300, end: 2200 } }, envelope: { attack: 0.004, decay: 0.05 }, gain: 0.028 }] },
  command: { layers: [...glint(A5, 0.08, 0.035), ...glint(E6, 0.08, 0.025, { ring: 0 }), ...glint(A6, 0.14, 0.032, { delay: 0.045 })] },
  blocked: { layers: [knock(330, 0.05, 0.055, 0, 900), knock(330, 0.06, 0.04, 0.07, 900)] },
  // A mechanical seat (Analogue plate presses) and a stopped run (Analogue signal outcomes), in glass.
  lock: { layers: [...glint(Fs6, 0.02, 0.04, { ring: 0 }), ...glint(A5, 0.04, 0.04, { delay: 0.02, ring: 0.1 })] },
  halt: { layers: [knock(D5 / 2, 0.06, 0.06, 0, 1000), knock(D5 / 2.2, 0.07, 0.05, 0.07, 1000)] },
};

// ---------------------------------------------------------------------------------------------
// The Analogue voice: dry, mechanical, single partials. Sine sweeps and 10ms noise ticks with a 3ms
// linear attack and an exponential release, peaking at −18 dBFS (the Analogue Button handoff).
// ---------------------------------------------------------------------------------------------

const sweep = (from: number, to: number, len: number, gain: number, delay = 0): Layer => ({
  source: { type: "sine", frequency: from === to ? from : { start: from, end: to } },
  envelope: { attack: 0.003, decay: len },
  gain,
  delay,
});
const noiseTick = (gain: number, delay = 0): Layer => ({
  source: { type: "noise", color: "white" },
  filter: { type: "highpass", frequency: 3200 },
  envelope: { attack: 0.001, decay: 0.01 },
  gain,
  delay,
});

/** The Analogue patch. Cues it does not define fall back to the Lumen patch. */
export const ANALOGUE_SOUND_PATCH: Partial<Record<GlassSoundCue, GlassSoundDefinition>> = {
  // tap: 1200→1150 Hz, 25ms. tick: 10ms noise through a 3.2 kHz highpass.
  tap: { layers: [sweep(1200, 1150, 0.025, 0.1)] },
  select: { layers: [noiseTick(0.28)] },
  tick: { layers: [noiseTick(0.28)] },
  sliderTick: { layers: [noiseTick(0.16)] },
  key: { layers: [noiseTick(0.22)] },
  // low tap: 520→440 Hz, 45ms (stop and destroy).
  destructive: { layers: [sweep(520, 440, 0.045, 0.12)] },
  // click-lock: 1.4 kHz for 12ms, then 900 Hz for 20ms at +20ms (the plate seating).
  lock: { layers: [sweep(1400, 1300, 0.012, 0.08), sweep(900, 880, 0.02, 0.08, 0.02)] },
  toggleOn: { layers: [sweep(900, 880, 0.02, 0.07), sweep(1400, 1300, 0.012, 0.07, 0.02)] },
  toggleOff: { layers: [sweep(1400, 1300, 0.012, 0.07), sweep(900, 880, 0.02, 0.07, 0.02)] },
  // rise: 330→990 Hz, 120ms (work finished).
  success: { layers: [sweep(330, 990, 0.12, 0.05)] },
  // low double: 196→180 Hz, 50ms, twice, 70ms apart (a run stopped, something failed).
  halt: { layers: [sweep(196, 180, 0.05, 0.12), sweep(196, 180, 0.05, 0.12, 0.07)] },
  error: { layers: [sweep(196, 180, 0.05, 0.12), sweep(196, 180, 0.05, 0.12, 0.07)] },
  blocked: { layers: [sweep(260, 240, 0.03, 0.07)] },
  // pulse: 523 then 784 Hz, 50ms each (a message arrived).
  notification: { layers: [sweep(523, 523, 0.05, 0.06), sweep(784, 784, 0.05, 0.06, 0.06)] },
};

/** Minimum gap between two plays of the same cue (ms). Repeats inside it are dropped. */
export const GLASS_SOUND_THROTTLE: Record<GlassSoundCue, number> = {
  tap: 35, select: 35, destructive: 80, toggleOn: 60, toggleOff: 60, open: 80, close: 80, swoosh: 120,
  tick: 40, sliderTick: 45, key: 25, success: 150, error: 150, warning: 150, copy: 80, notification: 200,
  chirp: 60, command: 100, blocked: 90, lock: 40, halt: 150,
};

/** Tiny per-play variation so repeats never feel mechanical: [cents, volume]. */
const JITTER: Partial<Record<GlassSoundCue, [number, number]>> = {
  tap: [40, 0.08], select: [8, 0.06], tick: [14, 0.1], sliderTick: [14, 0.1], key: [6, 0.06], chirp: [20, 0.05],
};

const PENTATONIC = [0, 2, 4, 7, 9]; // semitones: D E F# A B

/** Semitones for the nth step of the pentatonic scale (wraps up an octave every 5, max two octaves). */
export function glassPentatonicStep(index: number): number {
  const i = Math.max(0, Math.min(10, Math.floor(Number.isFinite(index) ? index : 0)));
  return PENTATONIC[i % 5] + 12 * Math.floor(i / 5);
}

/** The engine call a cue resolves to: definition, detune (cents), volume and pan. Pure, for tests. */
export function resolveGlassCue(
  cue: GlassSoundCue,
  opts: GlassSoundPlayOptions = {},
  { overrides, volume = 0.8 }: { overrides?: GlassSoundOverrides; volume?: number } = {},
): { definition: GlassSoundDefinition; options: PlayOptions } | null {
  const override = overrides?.[cue];
  if (override === false) return null;
  const analogue = opts.voice === "analogue";
  const definition = override ?? (analogue ? ANALOGUE_SOUND_PATCH[cue] : undefined) ?? GLASS_SOUND_PATCH[cue];
  if (!definition) return null;
  let cents = (opts.pitch ?? 0) * 100;
  // Analogue is mechanical: no pentatonic steps and no jitter.
  if (!analogue && opts.index !== undefined && (cue === "select" || cue === "key")) cents += glassPentatonicStep(opts.index) * 100;
  const v = clamp01(volume) * clamp01(opts.volume ?? 1);
  const jitter = analogue ? undefined : JITTER[cue];
  const options: PlayOptions = { volume: v };
  if (cents) options.detune = cents;
  if (opts.pan) options.pan = Math.max(-1, Math.min(1, opts.pan));
  if (jitter) options.jitter = { detune: jitter[0], volume: jitter[1] };
  return { definition, options };
}

const clamp01 = (n: number) => (Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : 1);

// ---------------------------------------------------------------------------------------------
// Engine: lazily loaded, swappable
// ---------------------------------------------------------------------------------------------

/** The audio backend. Swap it (tests, another synth) through the provider's `engine` prop. */
export interface GlassSoundEngine {
  /** Start loading code (no audio). Called when the page is idle. */
  preload?(): void;
  /** Create or resume the audio context. Only ever called inside a user gesture. */
  unlock(): void;
  /** Play a definition. Must not throw. */
  play(definition: GlassSoundDefinition, options: PlayOptions): void;
}

type WebKits = typeof import("@web-kits/audio");

/** The default engine: @web-kits/audio, imported on demand so pages without sound never load it. */
export function createWebKitsEngine(): GlassSoundEngine {
  let mod: WebKits | null = null;
  let loading: Promise<WebKits | null> | null = null;
  let unlocked = false;
  // The first cue can arrive before the engine has loaded: keep the latest one and play it when ready.
  let pending: [GlassSoundDefinition, PlayOptions] | null = null;
  const players = new WeakMap<GlassSoundDefinition, (o?: PlayOptions) => unknown>();
  const playNow = (m: WebKits, definition: GlassSoundDefinition, options: PlayOptions) => {
    try {
      let p = players.get(definition);
      if (!p) {
        p = m.defineSound(definition);
        players.set(definition, p);
      }
      p(options);
    } catch {}
  };
  const flush = (m: WebKits) => {
    const next = pending;
    pending = null;
    if (next) playNow(m, next[0], next[1]);
  };
  const load = () =>
    (loading ??= import("@web-kits/audio")
      .then((m) => (mod = m))
      .catch(() => null));
  const ready = (m: WebKits) => {
    try {
      m.ensureReady().catch(() => {});
    } catch {}
  };
  return {
    preload() {
      if (hasWebAudio()) void load();
    },
    unlock() {
      if (unlocked || !hasWebAudio()) return;
      unlocked = true;
      if (mod) ready(mod);
      else void load().then((m) => m && ready(m));
    },
    play(definition, options) {
      if (!unlocked) return;
      if (!mod) {
        pending = [definition, options];
        void load().then((m) => {
          if (!m) return;
          try {
            m.ensureReady().then(() => flush(m), () => flush(m));
          } catch {
            flush(m);
          }
        });
        return;
      }
      playNow(mod, definition, options);
    },
  };
}

export function hasWebAudio(): boolean {
  try {
    return typeof window !== "undefined" && typeof window.AudioContext === "function";
  } catch {
    return false;
  }
}

// ---------------------------------------------------------------------------------------------
// Controller: gesture gate, throttle, rapid-fire damping. Framework free, for tests.
// ---------------------------------------------------------------------------------------------

export interface GlassSoundControllerOptions {
  engine: GlassSoundEngine;
  now?: () => number;
}

export interface GlassSoundController {
  /** Call from a user gesture: unlocks the engine. Plays before it are dropped. */
  unlock(): void;
  readonly unlocked: boolean;
  play(cue: GlassSoundCue, opts?: GlassSoundPlayOptions, config?: { overrides?: GlassSoundOverrides; volume?: number }): boolean;
}

export function createGlassSoundController({ engine, now = () => Date.now() }: GlassSoundControllerOptions): GlassSoundController {
  let unlocked = false;
  const last = new Map<string, number>();
  const recent: number[] = [];
  return {
    get unlocked() {
      return unlocked;
    },
    unlock() {
      if (unlocked) return;
      unlocked = true;
      try {
        engine.unlock();
      } catch {}
    },
    play(cue, opts = {}, config = {}) {
      if (!unlocked || !isGlassSoundCue(cue)) return false;
      const t = now();
      const key = opts.index === undefined ? cue : `${cue}:${opts.index}`;
      if (!opts.force && t - (last.get(key) ?? -Infinity) < GLASS_SOUND_THROTTLE[cue]) return false;
      last.set(key, t);
      // Rapid sequences get quieter: every play in the last 600ms takes 12% off, down to 45%.
      while (recent.length && t - recent[0] > 600) recent.shift();
      const damp = Math.max(0.45, 1 - 0.12 * recent.length);
      recent.push(t);
      const resolved = resolveGlassCue(cue, { ...opts, volume: (opts.volume ?? 1) * damp }, config);
      if (!resolved) return false;
      try {
        engine.play(resolved.definition, resolved.options);
      } catch {}
      return true;
    },
  };
}

// ---------------------------------------------------------------------------------------------
// Mute persistence
// ---------------------------------------------------------------------------------------------

export const GLASS_SOUND_STORAGE_KEY = "beautiful-ui-sound";
const muteListeners = new Set<() => void>();

/** The saved choice: true (muted), false (on) or null (never chosen). Never throws. */
export function readGlassSoundMuted(key: string | null = GLASS_SOUND_STORAGE_KEY): boolean | null {
  if (!key) return null;
  try {
    const v = globalThis.localStorage?.getItem(key);
    return v === "off" ? true : v === "on" ? false : null;
  } catch {
    return null;
  }
}

/** Saves the choice ("on" / "off") and tells every provider on the page. Never throws. */
export function writeGlassSoundMuted(muted: boolean, key: string | null = GLASS_SOUND_STORAGE_KEY) {
  if (key) {
    try {
      globalThis.localStorage?.setItem(key, muted ? "off" : "on");
    } catch {}
  }
  for (const l of muteListeners) l();
}

function subscribeMuted(listener: () => void, key: string | null = GLASS_SOUND_STORAGE_KEY) {
  muteListeners.add(listener);
  // Another tab changed this provider's key (or cleared storage).
  const onStorage = (e: StorageEvent) => {
    if (!e.key || e.key === key) listener();
  };
  try {
    window.addEventListener("storage", onStorage);
  } catch {}
  return () => {
    muteListeners.delete(listener);
    try {
      window.removeEventListener("storage", onStorage);
    } catch {}
  };
}

// ---------------------------------------------------------------------------------------------
// The click layer
// ---------------------------------------------------------------------------------------------

/** What the click layer treats as pressable. Anything with data-sound counts too. */
export const GLASS_SOUND_INTERACTIVE =
  'button,a[href],summary,input[type="checkbox"],input[type="radio"],input[type="button"],input[type="submit"],input[type="reset"],select,[role="button"],[role="tab"],[role="option"],[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"],[role="checkbox"],[role="switch"],[role="radio"],[role="link"],[role="gridcell"][tabindex],[data-sound]';

/** "all": every interactive element; "components": only inside Lumen components; "off": only cues components play. */
export type GlassSoundClicks = "all" | "components" | "off";

/**
 * The cue a press on `target` should play, or null. Pure DOM reads, for tests and custom layers.
 * data-sound="off" on an ancestor silences, "none" means the element plays its own cue,
 * data-sound-index gives select / key their pitch.
 */
export function glassSoundForElement(
  target: Element | null,
  clicks: GlassSoundClicks = "all",
): { cue: GlassSoundCue; index?: number; el: Element; subtle: boolean; voice?: GlassSoundVoice } | null {
  if (!target || clicks === "off" || typeof target.closest !== "function") return null;
  if (target.closest('[data-sound="off"]')) return null;
  const el = target.closest(GLASS_SOUND_INTERACTIVE);
  if (!el) return null;
  // Hidden proxy inputs (Base UI and Radix click one for form state after the visible control) never
  // sound: the control the person pressed already did.
  if (el.matches('input[aria-hidden="true"]')) return null;
  if (clicks === "components" && !el.closest("[data-lumen],[data-analogue],[data-sound]")) return null;
  const subtle = Boolean(el.closest('[data-sound-level="subtle"]'));
  const voiceAttr = el.closest("[data-sound-voice]")?.getAttribute("data-sound-voice");
  const voice = isGlassSoundVoice(voiceAttr) ? voiceAttr : undefined;
  const marked = el.getAttribute("data-sound");
  if (marked === "off") return null;
  // A disabled control answers "blocked" even when it normally plays its own cue (a radio disabled by its group).
  const disabled = el.matches(":disabled") || el.getAttribute("aria-disabled") === "true" || el.hasAttribute("data-disabled");
  if (disabled) return { cue: "blocked", el, subtle, voice };
  if (marked === "none") return null;
  // A busy button ignores the press, so it stays silent too.
  if (el.getAttribute("aria-busy") === "true" || el.hasAttribute("data-inert")) return null;
  if (marked && isGlassSoundCue(marked)) {
    const idx = Number(el.getAttribute("data-sound-index"));
    return { cue: marked, index: el.hasAttribute("data-sound-index") && Number.isFinite(idx) ? idx : undefined, el, subtle, voice };
  }
  // Text fields and selects answer to typing, not to being clicked into.
  if (el.matches("select")) return null;
  return { cue: "tap", el, subtle, voice };
}

/** Attributes for a component root: data-sound="off" when sound={false}, the subtle level when "subtle". */
export function glassSoundRootAttrs(sound: GlassSoundProp | undefined): Record<string, string> {
  if (sound === false) return { "data-sound": "off" };
  if (sound === "subtle") return { "data-sound-level": "subtle" };
  return {};
}

// ---------------------------------------------------------------------------------------------
// React
// ---------------------------------------------------------------------------------------------

/** Replace any cue with your own definition, or silence it with false. */
export type GlassSoundOverrides = Partial<Record<GlassSoundCue, GlassSoundDefinition | false>>;

interface GlassSoundContextValue {
  play: (cue: GlassSoundCue, opts?: GlassSoundPlayOptions) => boolean;
  /** Unlock inside a gesture and play even while muted (the toggle's own answer). */
  confirm: (cue: GlassSoundCue) => void;
  muted: boolean;
  enabled: boolean;
  setMuted: (muted: boolean) => void;
}

const GlassSoundContext = React.createContext<GlassSoundContextValue | null>(null);

export interface GlassSoundProviderProps {
  children?: React.ReactNode;
  /** Master switch. false: nothing plays and the engine never loads. Default true. */
  enabled?: boolean;
  /** 0–1. Default 0.8. */
  volume?: number;
  /** Controlled mute (with onMutedChange). Uncontrolled, the saved choice or defaultMuted is used. */
  muted?: boolean;
  /** Muted until the visitor turns sound on. Default false. */
  defaultMuted?: boolean;
  onMutedChange?: (muted: boolean) => void;
  /** localStorage key for the mute choice. null: don't persist. Default "beautiful-ui-sound". */
  storageKey?: string | null;
  /** Replace or silence cues: { tap: myDefinition, tick: false }. */
  cues?: GlassSoundOverrides;
  /** The click layer: "all" (every button, link, tab… inside), "components" (only inside Lumen components) or "off". Default "all". */
  clicks?: GlassSoundClicks;
  /** Pan clicks slightly toward where they happened on screen (±0.25). Default true. */
  spatial?: boolean;
  /** Swap the audio backend. Default: @web-kits/audio, loaded on idle. */
  engine?: GlassSoundEngine;
}

/**
 * Turns sound on for everything inside. Renders one `display: contents` element to scope the
 * click layer, so layout is untouched.
 */
export function GlassSoundProvider({
  children,
  enabled = true,
  volume = 0.8,
  muted: mutedProp,
  defaultMuted = false,
  onMutedChange,
  storageKey = GLASS_SOUND_STORAGE_KEY,
  cues,
  clicks = "all",
  spatial = true,
  engine: engineProp,
}: GlassSoundProviderProps) {
  const [controller] = React.useState(() => {
    const engine = engineProp ?? createWebKitsEngine();
    return { engine, ctl: createGlassSoundController({ engine }) };
  });
  const subscribe = React.useCallback((l: () => void) => subscribeMuted(l, storageKey), [storageKey]);
  const stored = React.useSyncExternalStore(
    subscribe,
    () => readGlassSoundMuted(storageKey),
    () => null,
  );
  // Without a storage key the choice lives here (uncontrolled), so the toggle still works.
  const [own, setOwn] = React.useState<boolean | null>(null);
  const muted = mutedProp ?? (storageKey ? stored : own) ?? defaultMuted;
  const active = enabled && !muted;

  const config = React.useRef({ cues, volume, active, clicks, spatial });
  React.useEffect(() => {
    config.current = { cues, volume, active, clicks, spatial };
  });

  // Unlock on the first gesture anywhere on the page; preload the engine when idle.
  React.useEffect(() => {
    if (!active || typeof window === "undefined") return;
    const { ctl, engine } = controller;
    let idle: number | undefined;
    try {
      const ric = (window as Window & { requestIdleCallback?: (cb: () => void, o?: { timeout: number }) => number }).requestIdleCallback;
      idle = ric ? ric(() => engine.preload?.(), { timeout: 3000 }) : window.setTimeout(() => engine.preload?.(), 1200);
    } catch {}
    if (ctl.unlocked) return;
    const events = ["pointerdown", "keydown", "touchend"] as const;
    const onGesture = (e: Event) => {
      if (!e.isTrusted) return;
      ctl.unlock();
      for (const ev of events) window.removeEventListener(ev, onGesture, true);
    };
    for (const ev of events) window.addEventListener(ev, onGesture, true);
    return () => {
      for (const ev of events) window.removeEventListener(ev, onGesture, true);
      try {
        const cic = (window as Window & { cancelIdleCallback?: (id: number) => void }).cancelIdleCallback;
        if (idle !== undefined) (cic ?? window.clearTimeout)(idle);
      } catch {}
    };
  }, [active, controller]);

  const play = React.useCallback(
    (cue: GlassSoundCue, opts?: GlassSoundPlayOptions) => {
      const c = config.current;
      if (!c.active) return false;
      try {
        if (typeof document !== "undefined" && document.visibilityState === "hidden") return false;
        return controller.ctl.play(cue, opts, { overrides: c.cues, volume: c.volume });
      } catch {
        return false;
      }
    },
    [controller],
  );

  const setMuted = React.useCallback(
    (next: boolean) => {
      // Controlled: the parent owns it. Uncontrolled: persist it, or keep it here without a key.
      if (mutedProp === undefined) {
        if (storageKey) writeGlassSoundMuted(next, storageKey);
        else setOwn(next);
      }
      onMutedChange?.(next);
    },
    [mutedProp, storageKey, onMutedChange],
  );

  const confirm = React.useCallback(
    (cue: GlassSoundCue) => {
      const c = config.current;
      if (!enabled) return;
      try {
        controller.ctl.unlock();
        controller.ctl.play(cue, { force: true }, { overrides: c.cues, volume: c.volume });
      } catch {}
    },
    [controller, enabled],
  );

  const value = React.useMemo<GlassSoundContextValue>(() => ({ play, confirm, muted, enabled, setMuted }), [play, confirm, muted, enabled, setMuted]);

  // The click layer. Mouse and pen answer on press (tactile, no latency); touch waits for the
  // click so scrolling stays silent; keyboard activation (click with detail 0) answers too.
  const pressed = React.useRef<Element | null>(null);
  const wrap = React.useRef<HTMLDivElement>(null);
  // Nested providers: the innermost one around the target answers (a portal outside every
  // provider's DOM goes to the first that sees it). The event is marked so nothing plays twice.
  const owns = (e: React.SyntheticEvent) => {
    const ne = e.nativeEvent as Event & { __glassSound?: boolean };
    const t = e.target as Element | null;
    const nearest = t && typeof t.closest === "function" ? t.closest("[data-glass-sound]") : null;
    if ((nearest && nearest !== wrap.current) || ne.__glassSound) return false;
    ne.__glassSound = true;
    return true;
  };
  const fire = (target: EventTarget | null, x?: number) => {
    const c = config.current;
    if (!c.active || c.clicks === "off") return;
    const hit = glassSoundForElement(target as Element | null, c.clicks);
    if (!hit) return;
    let pan: number | undefined;
    if (c.spatial && typeof window !== "undefined") {
      try {
        const cx = x ?? (() => {
          const r = hit.el.getBoundingClientRect();
          return r.left + r.width / 2;
        })();
        pan = ((cx / Math.max(1, window.innerWidth)) * 2 - 1) * 0.25;
      } catch {}
    }
    play(hit.cue, { index: hit.index, pan, volume: hit.subtle ? 0.55 : 1, voice: hit.voice });
  };

  // Outcome cues from components without audio code: they dispatch a bubbling `beautiful-ui:sound`
  // event ({ cue }) and the provider around them plays it, in the voice their root declares.
  React.useEffect(() => {
    const el = wrap.current;
    if (!el) return;
    const onCue = (e: Event) => {
      const ev = e as CustomEvent<{ cue?: unknown; index?: unknown }> & { __glassSound?: boolean };
      const target = e.target as Element | null;
      const nearest = target && typeof target.closest === "function" ? target.closest("[data-glass-sound]") : null;
      if ((nearest && nearest !== el) || ev.__glassSound) return;
      ev.__glassSound = true;
      const cue = ev.detail?.cue;
      if (!isGlassSoundCue(cue) || !target || target.closest('[data-sound="off"]')) return;
      const voiceAttr = target.closest("[data-sound-voice]")?.getAttribute("data-sound-voice");
      const subtle = Boolean(target.closest('[data-sound-level="subtle"]'));
      // index pitches "select" and "key" by position (a radio's option, a list's row).
      const index = typeof ev.detail?.index === "number" && Number.isFinite(ev.detail.index) ? ev.detail.index : undefined;
      play(cue, { voice: isGlassSoundVoice(voiceAttr) ? voiceAttr : undefined, volume: subtle ? 0.55 : 1, index });
    };
    el.addEventListener("beautiful-ui:sound", onCue);
    // A component portalled outside every provider (a dialog, a popover) still sounds: the first
    // mounted provider answers events whose target has no provider around it.
    ORPHAN_OWNERS.push(el);
    const onOrphan = (e: Event) => {
      const target = e.target as Element | null;
      if (ORPHAN_OWNERS[0] !== el || !target || typeof target.closest !== "function" || target.closest("[data-glass-sound]")) return;
      onCue(e);
    };
    document.addEventListener("beautiful-ui:sound", onOrphan);
    return () => {
      el.removeEventListener("beautiful-ui:sound", onCue);
      document.removeEventListener("beautiful-ui:sound", onOrphan);
      const i = ORPHAN_OWNERS.indexOf(el);
      if (i >= 0) ORPHAN_OWNERS.splice(i, 1);
    };
  }, [play]);

  return (
    <GlassSoundContext.Provider value={value}>
      <div
        ref={wrap}
        data-glass-sound=""
        style={{ display: "contents" }}
        onPointerDownCapture={(e) => {
          pressed.current = null;
          if (e.pointerType === "touch" || e.button !== 0 || !config.current.active || !owns(e)) return;
          controller.ctl.unlock();
          pressed.current = e.target as Element;
          fire(e.target, e.clientX);
        }}
        onClickCapture={(e) => {
          const wasPressed = pressed.current !== null;
          pressed.current = null;
          // Mouse clicks already sounded on press. Touch taps (detail 1, no press) and keyboard (detail 0) sound here.
          if ((wasPressed && e.detail > 0) || !owns(e)) return;
          fire(e.target, e.detail > 0 ? e.clientX : undefined);
        }}
      >
        {children}
      </div>
    </GlassSoundContext.Provider>
  );
}

const NOOP = () => false;
/** Mounted providers in mount order: the first answers cues from portalled components. */
const ORPHAN_OWNERS: HTMLElement[] = [];

/**
 * Play Lumen cues from a component. Without a provider (or with sound off, muted, or
 * `sound={false}`) play() is a silent no-op.
 *
 * @example
 * const sfx = useGlassSound(sound);
 * sfx.play("success");
 * sfx.play("select", { index: 2 });
 */
export function useGlassSound(sound: GlassSoundProp = true, voice?: GlassSoundVoice) {
  const ctx = React.useContext(GlassSoundContext);
  const playCtx = ctx?.play;
  const play = React.useMemo(() => {
    if (!playCtx || sound === false) return NOOP as (cue: GlassSoundCue, opts?: GlassSoundPlayOptions) => boolean;
    const k = sound === "subtle" ? 0.55 : 1;
    return (cue: GlassSoundCue, opts?: GlassSoundPlayOptions) =>
      playCtx(cue, { voice, ...opts, ...(k === 1 ? null : { volume: (opts?.volume ?? 1) * k }) });
  }, [playCtx, sound, voice]);
  return {
    play,
    /** A provider is present. */
    available: ctx !== null,
    /** Sound would play right now (provider enabled, not muted, this instance not silenced). */
    enabled: Boolean(ctx?.enabled && !ctx.muted && sound !== false),
    muted: ctx?.muted ?? true,
    setMuted: ctx?.setMuted ?? (() => {}),
  };
}

/**
 * Plays `cue` when `value` changes to something that matches (not on mount). For outcomes that
 * arrive from props: an error message, a success status.
 */
export function useGlassSoundOnChange<T>(
  value: T,
  cue: GlassSoundCue | ((next: T, prev: T) => GlassSoundCue | null),
  sound: GlassSoundProp = true,
  voice?: GlassSoundVoice,
) {
  const { play } = useGlassSound(sound, voice);
  const prev = React.useRef(value);
  React.useEffect(() => {
    if (Object.is(prev.current, value)) return;
    const before = prev.current;
    prev.current = value;
    const c = typeof cue === "function" ? cue(value, before) : cue;
    if (c) play(c);
  }, [value, cue, play]);
}

// ---------------------------------------------------------------------------------------------
// The toggle
// ---------------------------------------------------------------------------------------------

export interface GlassSoundToggleLabels {
  /** The button's accessible name (it is a toggle: aria-pressed says whether sound is on). */
  sound: string;
  on: string;
  off: string;
}

export const defaultGlassSoundToggleLabels: GlassSoundToggleLabels = { sound: "Sound effects", on: "Sound on", off: "Sound off" };

export interface GlassSoundToggleState {
  muted: boolean;
  toggle: () => void;
  /** A provider is present and enabled. */
  available: boolean;
}

export interface GlassSoundToggleProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
  /** Render your own control. Gets { muted, toggle, available }. */
  render?: (state: GlassSoundToggleState) => React.ReactNode;
  /** Diameter in px. Default 32. */
  size?: number;
  theme?: GlassTheme;
  labels?: GlassLabels<GlassSoundToggleLabels>;
  ref?: React.Ref<HTMLButtonElement>;
}

const TOGGLE_CSS =
  ".gsnd{position:relative;display:inline-grid;place-items:center;flex:none;border:0;padding:0;margin:0;border-radius:999px;cursor:pointer;color:var(--lg-strong);background:var(--lg-cardBg);box-shadow:var(--lg-cardShadow);transition:color .2s,background .2s,transform .35s cubic-bezier(.34,1.5,.64,1);-webkit-tap-highlight-color:transparent}" +
  ".gsnd:hover{color:var(--lg-active)}.gsnd:active{transform:scale(.94)}" +
  ".gsnd .gsnd-w{transition:opacity .3s cubic-bezier(.22,1,.36,1),transform .3s cubic-bezier(.22,1,.36,1);transform-origin:6px 8px}" +
  ".gsnd[aria-pressed=false] .gsnd-w{opacity:0;transform:scale(.6)}.gsnd .gsnd-x{transition:opacity .25s;opacity:0}.gsnd[aria-pressed=false] .gsnd-x{opacity:1}" +
  "@media (prefers-reduced-motion:reduce){.gsnd,.gsnd .gsnd-w,.gsnd .gsnd-x{transition:none}}";

/** The speaker: cell body, two waves when on, a small cross when off. 16px grid, currentColor. */
export function GlassSoundIcon({ muted, size = 16 }: { muted: boolean; size?: number }) {
  const st = { stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", fill: "none" } as const;
  return (
    <svg width={size} height={size} viewBox="0 0 16 16" aria-hidden focusable={false} style={{ display: "block" }} data-muted={muted ? "" : undefined}>
      <path d="M2.5 6.2h2.2L8 3.4v9.2L4.7 9.8H2.5z" {...st} fill="currentColor" fillOpacity={0.18} />
      <g className="gsnd-w">
        <path d="M10.3 6.1a2.7 2.7 0 0 1 0 3.8" {...st} />
        <path d="M12.2 4.3a5.3 5.3 0 0 1 0 7.4" {...st} opacity={0.6} />
      </g>
      <g className="gsnd-x">
        <path d="M10.6 6.3l3.4 3.4M14 6.3l-3.4 3.4" {...st} />
      </g>
    </svg>
  );
}

/**
 * The mute button, in glass. Renders nothing without a provider (there is nothing to toggle).
 * Turning sound on answers with a swoosh; turning it off is silent.
 */
export function GlassSoundToggle({ render, size = 32, theme = "system", labels, className, style, onClick, ref, ...rest }: GlassSoundToggleProps) {
  const ctx = React.useContext(GlassSoundContext);
  const L = React.useMemo(() => mergeLabels(defaultGlassSoundToggleLabels, labels), [labels]);
  const muted = ctx?.muted ?? true;
  const available = Boolean(ctx?.enabled);
  const setMuted = ctx?.setMuted;
  const confirm = ctx?.confirm;
  const toggle = React.useCallback(() => {
    if (!setMuted) return;
    const next = !muted;
    setMuted(next);
    if (!next) confirm?.("swoosh");
  }, [muted, setMuted, confirm]);
  if (!ctx) return null;
  if (render) return <>{render({ muted, toggle, available })}</>;
  return (
    <button
      {...rest}
      ref={ref}
      type="button"
      data-lumen={theme}
      data-sound="none"
      className={className ? `gsnd ${GLASS_FOCUS} ${className}` : `gsnd ${GLASS_FOCUS}`}
      style={{ width: size, height: size, ...style }}
      aria-label={rest["aria-label"] ?? L.sound}
      aria-pressed={!muted}
      title={rest.title ?? (muted ? L.off : L.on)}
      disabled={!available || rest.disabled}
      onClick={(e) => {
        onClick?.(e);
        if (!e.defaultPrevented) toggle();
      }}
    >
      <GlassThemeStyle />
      <style href="beautiful-ui-glass-sound" precedence="medium">
        {TOGGLE_CSS}
      </style>
      <GlassSoundIcon muted={muted} size={Math.round(size / 2)} />
    </button>
  );
}
```
