# Stat Cards (Lumen Halo): prompt.md (v1.2.0)

- id: `glass-stat-cards` · version 1.2.0 · component · free
- category: Charts
- 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-stat-cards.json`
- npm dependencies: @web-kits/audio@^0.2
- registry dependencies: https://beautiful-ui.dev/r/beautiful-ui.json
- docs: https://beautiful-ui.dev/components/glass-stat-cards
- The install command carries everything this item needs (files, CSS, tokens, npm and registry dependencies). Prefer it to copying source by hand.

Liquid-glass KPI cards in dark and light, in hero, default and compact sizes: the number, whether the change is good or bad, and whether the data can be trusted, with a headline that names what moved the wrong way, period tabs, rolling digits, cell trends and a drill-in.

## Build it
- Stack: React 19 (`ref` is a plain prop), TypeScript, Tailwind CSS v4 utilities, a shadcn-initialised project with the `@/*` alias.
- Packages: `@web-kits/audio`.
- Source: `components/beautiful-ui/glass-stat-cards.tsx`, shared code in `lib/beautiful-ui/`.
- Exports to keep: `GlassStatCards`, `GlassStatCard`, 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 { GlassStatCards, GlassStatCard } from "@/components/beautiful-ui/glass-stat-cards";
```

## Parts

| Part | data-slot | What it is for |
|---|---|---|
| `GlassStatCards` | — | The KPI board: header, period tabs, hero, default and compact rows, and the drill-in. |
| `GlassStatCard` | — | One stat card on its own, at hero, default or compact size. |

## 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: open when a card drills in, close when it closes (the card again, the × or Escape), select (pitched by position) on the metric tabs by click or arrow keys. Nothing on hover or mount. Needs a GlassSoundProvider; sound={false} silences this instance; motion settings do not affect sound.

## Match the original
- Read `components/beautiful-ui/glass-stat-cards.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
- KPI cards, stat tiles, metric row, dashboard summary, MRR / revenue / churn / active users at a glance, period comparison, stale or missing data
- A row of 3–6 headline metrics at the top of a dashboard, each compared with the previous period
- When good or bad depends on the metric: churn and reply time going down is good (goodDirection -1)
- When trust matters: stale data turns amber ("6 h ago · stale") and a missing value says since when
- A single hero metric with a goal (revenue to target), or compact tiles in a sidebar
- Next to Glass Trend, Glass Cell Bars and Glass Market Breadth, which share the 12-column cell rhythm

### Not when
- Showing a trend in detail or reading exact past values: use Glass Trend
- Comparing many categories at once: use Glass Cell Bars
- Units other than money, counts, percents and durations need your own formatting (they render as counts)
- More than about 8 metrics in one row: split them into groups

## Mistakes
- goodDirection -1 for metrics where down is good (churn, reply time, error rate, cost); otherwise tone and the headline are inverted
- Percent values are percents (1.8 for 1.8%), not fractions; their change reads in points
- Durations are seconds; currency is whole units (dollars), not cents
- Don't pre-format values or pass strings: formatting, rounding and signs are part of the design
- Pass updatedAt (and now for server-rendered, deterministic output); without updatedAt no freshness note or staleness is shown
- Labels are shown uppercase; set name for sentences when sentence case would break it ("NPS", "API calls")
- Currency defaults to dollars: pass currency="EUR" (or a format) for any other currency; unknown units format as counts unless the metric has format / suffix
- openId is controlled when set: update it in onOpenChange or the drill-in will not open or close
- max-width comes from var(--glass-max-width, 1120px): set the variable or pass a max-w-* class to fill a wider dashboard
- Recolour through --glass-good / --glass-bad / --glass-accent on an ancestor, not the private --sc-* variables (they are internal and may change)

## Usage

```tsx
import { GlassStatCards, GlassStatCard } from "@/components/beautiful-ui/glass-stat-cards";

// Works as is with the designed data:
<GlassStatCards />

// Your metrics, per period (trend: up to 12 points, oldest first):
<GlassStatCards
  eyebrow="LUMEN · WORKSPACE PULSE"
  metrics={[
    { id: "mrr", label: "MRR", unit: "currency", updatedAt: syncedAt,
      data: { "30D": { value: 241200, previous: 218100, trend: mrr12, goal: 260000 } } },
    { id: "churn", label: "LOGO CHURN", unit: "percent", goodDirection: -1, updatedAt: syncedAt,
      data: { "30D": { value: 1.8, previous: 1.5, trend: churn12 } } },
  ]}
  periods={["30D"]}
  onOpenChange={(id) => track("drill", id)}
/>

// In euros, with the drill-in in the URL:
<GlassStatCards
  metrics={metrics}
  currency="EUR"
  openId={searchParams.get("metric")}
  onOpenChange={(id) => setParam("metric", id)}
  labels={{ vsPrev: (p) => `vs last ${p}` }}
/>

// One card, custom unit:
<GlassStatCard label="P95 LATENCY" value={182} previous={210} goodDirection={-1} format={(v) => Math.round(v).toString()} suffix=" ms" updatedAt={at} />
```

## Props

| Prop | Type | Default | What it does |
|---|---|---|---|
| `metrics` | `GlassStatMetric[]` | `Lumen workspace pulse` | { id, label, name?, unit?, format?, suffix?, goodDirection?, updatedAt?, trackingSince?, staleAfterMs?, data: { [periodId]: { value, previous?, trend?, goal?, updatedAt? } } }. value null reads "—". format / suffix cover custom units (ms, GB, NPS points). |
| `periods` | `(string \| { id; label?; name? })[]` | `["7D", "30D", "90D"]` | Period tabs. name ("30 days") is derived from the label when omitted. |
| `period / defaultPeriod` | `string` | `"30D"` | Controlled or starting period id. |
| `onPeriodChange` | `(period: string) => void` |  | A tab was picked. |
| `hero` | `string \| null` | `first metric with a goal` | Metric id shown as the hero card; null for none. |
| `sizes` | `("hero" \| "default" \| "compact")[]` | `all three` | Which renditions to show: the hero, a row of default cards, a compact row. |
| `eyebrow` | `string` | `"WORKSPACE · PULSE"` | Mono label above the headline; the section's accessible name. The headline is computed from the tones. |
| `openId / defaultOpenId` | `string \| null` | `null` | Controlled or starting drill-in (a metric id, or null for closed). Open a metric from a URL, close it from outside. |
| `onOpenChange` | `(id: string \| null) => void` |  | The user opened a card's drill-in or closed it (×, Escape, the card again). |
| `formatValue` | `(value: number, metric: GlassStatMetric) => string` |  | Formats every metric without its own format (the value, absolute changes, goal and drill-in values). |
| `currency` | `string` | `"USD" ($)` | ISO code for unit "currency"; its narrow symbol replaces $ ("EUR" → €2.41M). |
| `locale` | `string` | `"en-US"` | Grouping in counts and the currency symbol's locale. Compact forms (2.41M) keep the design's notation; pass formatValue for fully localised numbers. |
| `formatDate` | `(date: Date, style: "time" \| "day") => string` | `"09:00" / "Sep 4"` | Dates in the notes: No data since …, Tracking since …. |
| `labels` | `Partial<GlassStatCardsLabels>` | `defaultGlassStatCardsLabels` | Every string for i18n: verdicts, change line (vsPrev, points), headline sentences, freshness notes, goal notes, the hero sentence (heroSentence gets { tone, up, unit, amount, prior }), aria text and the drill-in. The defaults are the design's wording. |
| `now` | `Date \| number \| string` | `the browser clock after mount` | Clock for "Updated 2 min ago" and staleness. Pass it for deterministic renders; the designed data brings its own. |
| `motion` | `"full" \| "subtle" \| "off"` | `"full"` | full: the designed motion. subtle: 30% shorter, calm curve, no staggers. off: instant. prefers-reduced-motion: 150ms fades, no rolling digits. |
| `layout` | `"auto" \| "desktop" \| "mobile"` | `"auto"` | auto goes 2×2 and stacks the hero in narrow containers; desktop / mobile force one. |
| `theme` | `"system" \| "dark" \| "light"` | `"system"` | system follows a .dark / .light class or data-theme on an ancestor (next-themes, shadcn), else the OS. |
| `sound` | `boolean \| "subtle"` | `true` | Built-in sound: true plays Lumen cues when a GlassSoundProvider (glass-sound) enables sound; false silences this instance (its cues and its clicks); "subtle" plays at 55%. Without a provider it is silent. |
| `className / style` | `string / CSSProperties` |  | On the root. The section is width 100% up to max-width var(--glass-max-width, 1120px): override with the variable, style or a max-w-* class. |
| `ref` | `Ref<HTMLDivElement>` |  | The root element (React 19 ref prop). |
| `...rest` | `HTML attributes` |  | id, data-*, aria-* and event handlers go to the root; aria-label names the section instead of the eyebrow; onKeyDown runs before the built-in Escape (preventDefault skips it). |
| `CSS variables` | `--glass-accent, --glass-good, --glass-bad, --glass-warn (+ -text, -dark / -light), --glass-max-width` |  | Set on the cards or any ancestor to recolour the accent (goal bar, selection, focus), good / bad tones (fills, glows; -text for the change text) and stale amber. --glass-max-width caps the section width. |
| `GlassStatCard` | `{ label, value, previous?, unit?, format?, suffix?, locale?, currency?, formatDate?, labels?, goodDirection?, periodLabel?, periodName?, trend?, goal?, updatedAt?, trackingSince?, staleAfterMs?, now?, size?, selected?, onOpen?, controls?, motion?, layout?, theme?, className?, style?, ref? }` |  | One card on its own, with the same format and label overrides. onOpen makes it a button (selected drives the ring and aria-expanded). HTML attributes go to its root. |

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

## 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 | Move through the period tabs, the cards and the drill-in's close button |
| ← / → · Home / End (tabs) | Switch the period |
| Enter / Space (card) | Open its drill-in, or close it if open |
| Escape | Close the drill-in and return focus to its card |

## Motion inventory

| Interaction | What moves |
|---|---|
| Sound | open when a card drills in, close when it closes (the card again, the × or Escape), select (pitched by position) on the metric tabs by click or arrow keys. Nothing on hover or mount. Needs a GlassSoundProvider; sound={false} silences this instance; motion settings do not affect sound |
| Entrance | Once the section is 35% on screen, cards rise 12px and fade in 60ms apart (700ms, cubic-bezier(.22,1,.36,1)); their trend cells ripple in at col × 30ms + row × 20ms, goal cells fill 14ms apart and the glow fades up. Values appear with their card; they never count up from 0 |
| Period switch | The tab thumb slides (600ms); each digit rolls on a 0–9 column to its new value (900ms, 35ms per character); trend cells fade in and out left to right; goal cells re-fill; tone colours ease over 500ms and the glow cross-fades over 800ms |
| Hover and drill-in | Default cards lift 2px and brighten (500ms); the drill-in grows with grid-template-rows 0fr→1fr (600ms) and its 12 columns ripple in; hover a column to read its value |
| motion="subtle" / "off" / reduced motion | Subtle: 30% shorter on the calm curve, no staggers. Off: instant. Reduced motion: the text swaps with a 150ms fade, no movement |

## Accessibility contract (preserve when editing)
- Each card is a button with a full label ("LOGO CHURN 1.8%, +0.3 pts vs prev 30D. Needs a look. Updated 1 h ago.") and aria-expanded for its drill-in; cards without onOpen are plain text
- Rolling digits are hidden from assistive tech; the final value is exposed as text
- Tone is never colour alone: the change carries its sign and the status names the tone; stale and missing notes say so in words
- Period tabs are a tablist with aria-selected, roving focus and arrow, Home and End keys; the new headline is announced politely
- The drill-in is a labelled region: focus moves into it on open and back to the card on close (×, Escape or the card again)
- Every grey text reaches 4.5:1 on the card in both themes (the missing "—" is large text at 3:1); light tone text uses deeper variants of the family hues; visible focus rings
- prefers-reduced-motion: no rolling digits, rises or ripples, 150ms fades only

## Install

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

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

## Source (Base UI build)

### components/beautiful-ui/glass-stat-cards.tsx

```tsx
"use client";

/**
 * Stat Cards (Lumen Halo) v1.2.0 · Beautiful UI
 * Docs: https://beautiful-ui.dev/components/glass-stat-cards · AI guide: ./glass-stat-cards.AI.md
 * Licensed to the purchaser under the Beautiful UI license: https://beautiful-ui.dev/license
 */

import * as React from "react";
import { cn } from "@/lib/beautiful-ui/utils";
import {
  GLASS_MONO,
  GLASS_ROOT_STYLE,
  lumenThemeCss,
  mergeLabels,
  normalizeGlassMotion,
  pickRootAttrs,
  usePrefersReducedMotion,
  useSeenOnce,
  type GlassLabels,
  type GlassLayout,
  type GlassMotion,
  type GlassRootProps,
  type GlassTheme,
} from "@/lib/beautiful-ui/glass";
import { glassSoundRootAttrs, useGlassSound, type GlassSoundProp } from "@/lib/beautiful-ui/glass-sound";

/*
 * Glass Stat Cards: KPI tiles for the Glass family. In one second a PM sees the number, whether the
 * change is good or bad, and whether the data can be trusted. One card comes in three sizes (hero,
 * default, compact); the section adds the headline sentence, period tabs and a drill-in panel.
 *
 * The wording and number formats carry the design: currency, counts, percents (changes in points)
 * and durations each have their own rules, tone comes from the sign of the change times the
 * metric's good direction, and a stale or missing value always says so in amber. Every colour, size
 * and timing is from the design handoff (dark and light) on the Lumen Halo family hues; change them
 * only with design. Digits roll like an odometer on change (never counting up from 0), trend cells
 * ripple left to right, and nothing re-renders per frame: all motion is CSS transitions.
 */

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

export type GlassStatUnit = "currency" | "count" | "percent" | "duration";
export type GlassStatSize = "compact" | "default" | "hero";
export type GlassStatTone = "good" | "bad" | "flat" | "none";
/** "full" (default) | "subtle" | "off" (the family's GlassMotion; "smooth" and "calm" also work). */
export type GlassStatMotion = GlassMotion;
/** A moment: a Date, epoch milliseconds or an ISO string. */
export type GlassStatTime = Date | number | string;

/** How a date is shown: "time" ("09:00", today) or "day" ("Sep 4"). */
export type GlassStatDateStyle = "time" | "day";

/** Number format overrides. The built-in formats (the design) apply when none is given. */
export interface GlassStatFormatOptions {
  /** Locale for grouping and the default currency symbol. Default "en-US". */
  locale?: string;
  /** ISO currency code for "currency" values, e.g. "EUR" (its narrow symbol replaces "$"). */
  currency?: string;
  /** Formats a value (and absolute amounts in the change line and hero sentence) instead of the unit's format. */
  format?: (value: number) => string;
  /** Appended to every formatted value, e.g. " ms" or " GB". */
  suffix?: string;
}

export interface GlassStatCardOwnProps {
  /** Rendered uppercase in mono. */
  label: string;
  /** null shows "—" with "No data since …" in amber. */
  value: number | null;
  /** undefined or null reads "No previous period yet". */
  previous?: number | null;
  /** Default "count". Unknown units format as counts. */
  unit?: GlassStatUnit;
  /** Formats the value instead of the unit's format (custom units, locales). */
  format?: (value: number) => string;
  /** Appended to every formatted value, e.g. " ms". */
  suffix?: string;
  /** Locale for the built-in number formats. Default "en-US". */
  locale?: string;
  /** ISO currency code for unit "currency", e.g. "EUR". Default: dollars. */
  currency?: string;
  /** Formats dates in the notes ("No data since 09:00", "Tracking since Sep 4"). */
  formatDate?: (date: Date, style: GlassStatDateStyle) => string;
  /** Every UI string, for i18n. */
  labels?: GlassLabels<GlassStatCardsLabels>;
  /** 1 when up is good (revenue), -1 when down is good (churn, reply time). Default 1. */
  goodDirection?: 1 | -1;
  /** Short period name for the change line, e.g. "30D". */
  periodLabel?: string;
  /** Long period name for the hero sentence, e.g. "30 days". Default: derived from periodLabel. */
  periodName?: string;
  /** Up to 12 points, oldest first; the last is the current value. Longer series keep the last 12. */
  trend?: (number | null)[];
  /** Hero only: a target to reach (or to stay under when goodDirection is -1). */
  goal?: number;
  /** When the value was last refreshed. */
  updatedAt?: GlassStatTime;
  /** Shown as "Tracking since …" while there is no previous period. */
  trackingSince?: GlassStatTime;
  /** Older data is marked stale in amber. Default 2 hours. */
  staleAfterMs?: number;
  /** The clock for "Updated 2 min ago" and staleness. Default: the browser's, after mount. */
  now?: GlassStatTime;
  size?: GlassStatSize;
  /** The indigo ring and tint of an open drill-in. */
  selected?: boolean;
  /** Makes the card a button (with aria-expanded from `selected`). */
  onOpen?: () => void;
  /** With onOpen: the id of the region the card opens (aria-controls). */
  controls?: string;
  /** "full" (default) | "subtle" | "off". prefers-reduced-motion always wins (150ms fades only). */
  motion?: GlassStatMotion;
  /** Hero only. "auto" stacks the two columns below 340px each; "desktop" keeps them side by side; "mobile" stacks. */
  layout?: GlassLayout;
  /** "system" (default) follows a .dark / .light class or data-theme on an ancestor, else the OS. */
  theme?: GlassTheme;
  /** true plays Lumen cues when a GlassSoundProvider enables sound; false silences this instance; "subtle" plays at 55%. Default true. */
  sound?: GlassSoundProp;
}

/** Props: the own props plus className, style, ref, id, aria-* and data-* on the root. */
export type GlassStatCardProps = GlassStatCardOwnProps & Omit<GlassRootProps, keyof GlassStatCardOwnProps>;

export interface GlassStatPoint {
  value: number | null;
  previous?: number | null;
  trend?: (number | null)[];
  /** Shown on the hero. */
  goal?: number;
  /** Overrides the metric's updatedAt for this period. */
  updatedAt?: GlassStatTime;
}

export interface GlassStatMetric {
  id: string;
  /** Uppercase mono label, e.g. "LOGO CHURN". */
  label: string;
  /** Name in sentences ("Logo churn moved the wrong way."). Default: the label in sentence case. */
  name?: string;
  unit?: GlassStatUnit;
  /** Formats this metric's values instead of the unit's format (custom units: ms, GB, NPS points). */
  format?: (value: number) => string;
  /** Appended to every formatted value, e.g. " ms". */
  suffix?: string;
  goodDirection?: 1 | -1;
  updatedAt?: GlassStatTime;
  trackingSince?: GlassStatTime;
  staleAfterMs?: number;
  /** One entry per period id. A missing period reads as no value. */
  data: Record<string, GlassStatPoint>;
}

export interface GlassStatPeriod {
  id: string;
  /** Tab text and change line, e.g. "30D". Default: the id. */
  label?: string;
  /** e.g. "30 days". Default: derived from the label. */
  name?: string;
}

export interface GlassStatCardsOwnProps {
  metrics?: GlassStatMetric[];
  periods?: (string | GlassStatPeriod)[];
  /** Controlled period id. */
  period?: string;
  /** Uncontrolled starting period. Default "30D" when present, else the first. */
  defaultPeriod?: string;
  onPeriodChange?: (period: string) => void;
  /** Metric id for the hero card, or null for no hero. Default: the first metric with a goal, else the first. */
  hero?: string | null;
  /** Which renditions to show. Default all three: the hero, a row of default cards and a compact row. */
  sizes?: GlassStatSize[];
  eyebrow?: string;
  /** Controlled drill-in: the open metric id, or null for closed. */
  openId?: string | null;
  /** Uncontrolled starting drill-in. Default null. */
  defaultOpenId?: string | null;
  /** A card's drill-in panel opened (its metric id) or closed (null) by the user. */
  onOpenChange?: (id: string | null) => void;
  /** Formats values of every metric without its own `format`. Default: the unit's format. */
  formatValue?: (value: number, metric: GlassStatMetric) => string;
  /** Locale for the built-in number formats. Default "en-US". */
  locale?: string;
  /** ISO currency code for unit "currency", e.g. "EUR". Default: dollars. */
  currency?: string;
  /** Formats dates in the notes ("No data since 09:00", "Tracking since Sep 4"). */
  formatDate?: (date: Date, style: GlassStatDateStyle) => string;
  /** Every UI string, for i18n. */
  labels?: GlassLabels<GlassStatCardsLabels>;
  /** The clock for "Updated …" and staleness. Default: the browser's, after mount (the designed data has its own). */
  now?: GlassStatTime;
  motion?: GlassStatMotion;
  /** "auto" (default) goes 2×2 and stacks the hero in narrow containers; "desktop" / "mobile" force one. */
  layout?: GlassLayout;
  theme?: GlassTheme;
  /** true plays Lumen cues when a GlassSoundProvider enables sound; false silences this instance; "subtle" plays at 55%. Default true. */
  sound?: GlassSoundProp;
}

/** Props: the own props plus className, style, ref, id, aria-* and data-* on the root (aria-label names the section; default the eyebrow). */
export type GlassStatCardsProps = GlassStatCardsOwnProps & Omit<GlassRootProps, keyof GlassStatCardsOwnProps>;

// ---------------------------------------------------------------------------------------------
// Wording and formats (the handoff's fmt and delta, with their edges closed)
// ---------------------------------------------------------------------------------------------

const MINUS = "−";
const isNum = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
const pad2 = (n: number) => String(n).padStart(2, "0");
const trimDec = (s: string) => (s.includes(".") ? s.replace(/\.?0+$/, "") : s);
const UNITS: readonly GlassStatUnit[] = ["currency", "count", "percent", "duration"];
const unitOf = (u: unknown): GlassStatUnit => (UNITS.includes(u as GlassStatUnit) ? (u as GlassStatUnit) : "count");

const symbols = new Map<string, string>();
/** The narrow symbol of an ISO currency ("EUR" → "€"); the code itself when Intl does not know it. */
function currencySymbol(currency: string, locale = "en-US") {
  const key = `${locale}|${currency}`;
  let sym = symbols.get(key);
  if (sym === undefined) {
    try {
      sym = new Intl.NumberFormat(locale, { style: "currency", currency, currencyDisplay: "narrowSymbol" }).formatToParts(0).find((x) => x.type === "currency")?.value ?? currency;
    } catch {
      sym = currency;
    }
    symbols.set(key, sym);
  }
  return sym;
}
function money(a: number, sym = "$"): string {
  if (a >= 1e15) return sym + a.toExponential(1);
  if (a >= 1e12 || +(a / 1e9).toFixed(1) >= 1000) return sym + (a / 1e12).toFixed(1) + "T";
  if (a >= 1e9 || +(a / 1e6).toFixed(2) >= 1000) return sym + (a / 1e9).toFixed(1) + "B";
  if (a >= 1e6 || Math.round(a / 1e3) >= 1000) return sym + trimDec((a / 1e6).toFixed(2)) + "M";
  if (a >= 1e5 || +(a / 1e3).toFixed(1) >= 100) return sym + Math.round(a / 1e3) + "K";
  if (a >= 1e3 || Math.round(a) >= 1000) return sym + (a / 1e3).toFixed(1) + "K";
  return sym + Math.round(a);
}
function countFmt(a: number, locale = "en-US"): string {
  if (a >= 1e15) return a.toExponential(1);
  if (a >= 1e12 || +(a / 1e9).toFixed(1) >= 1000) return (a / 1e12).toFixed(1) + "T";
  if (a >= 1e9 || +(a / 1e6).toFixed(1) >= 1000) return (a / 1e9).toFixed(1) + "B";
  if (a >= 1e6 || Math.round(a) >= 1e6) return (a / 1e6).toFixed(1) + "M";
  return Math.round(a).toLocaleString(locale);
}
function percentFmt(a: number, locale = "en-US"): string {
  if (a >= 1e15) return a.toExponential(1) + "%";
  if (a >= 1000) return a.toLocaleString(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + "%";
  return (a < 0.1 ? a.toFixed(2) : a.toFixed(1)) + "%";
}
function durationFmt(a: number) {
  const t = Math.round(a);
  if (t >= 3600) {
    const h = Math.floor(t / 3600);
    const m = Math.round((t % 3600) / 60);
    return m === 60 ? `${h + 1}h 00m` : `${h}h ${pad2(m)}m`;
  }
  return `${Math.floor(t / 60)}m ${pad2(t % 60)}s`;
}
/** An absolute amount in the unit's format (or the custom format), plus the suffix. */
function absFmt(a: number, u: GlassStatUnit, o?: GlassStatFormatOptions) {
  if (o?.format) return o.format(a) + (o.suffix ?? "");
  const s =
    u === "currency"
      ? money(a, o?.currency ? currencySymbol(o.currency, o.locale) : "$")
      : u === "percent"
        ? percentFmt(a, o?.locale)
        : u === "duration"
          ? durationFmt(a)
          : countFmt(a, o?.locale);
  return s + (o?.suffix ?? "");
}

/**
 * "$2.41M", "8,940", "1.8%", "4m 12s"; "—" when missing; a real minus for negatives. Pass
 * `options` for a locale, a currency, a custom format (which gets the signed value) or a suffix.
 */
export function formatGlassStatValue(v: number | null | undefined, unit: GlassStatUnit = "count", options?: GlassStatFormatOptions) {
  if (!isNum(v)) return "—";
  if (options?.format) return options.format(v) + (options.suffix ?? "");
  const u = unitOf(unit);
  const s = absFmt(Math.abs(v), u, options);
  // No "−0": a value that rounds to zero reads without a sign.
  return v < 0 && /[1-9]/.test(s) ? MINUS + s : s;
}

const WORDS = ["", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
/** "A", "A and B", "A, B and C". */
export function glassStatList(names: string[]) {
  if (names.length < 2) return names[0] ?? "";
  return names.slice(0, -1).join(", ") + " and " + names[names.length - 1];
}
const cap1 = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);

/** What the hero sentence is about: see `labels.heroSentence`. */
export interface GlassStatHeroContext {
  tone: "flat" | "good" | "bad";
  /** The value went up. */
  up: boolean;
  unit: GlassStatUnit;
  /** The absolute change, formatted, when it is not already the coloured change (relative changes only); else null. */
  amount: string | null;
  /** labels.prior(periodName), e.g. "the prior 30 days". */
  prior: string;
}

/**
 * Every string the stat cards show or announce, for i18n. Functions get the interpolated parts.
 * The defaults are the design's wording.
 */
export const defaultGlassStatCardsLabels = {
  onTrack: "On track",
  needsLook: "Needs a look",
  steady: "Steady",
  noData: "No data",
  newMetric: "New metric",
  noValue: "No value this period",
  noPrevious: "No previous period yet",
  outOfRange: "Change out of range",
  noChange: "0% · no change",
  /** The short change of a metric with no previous period. */
  newShort: "New",
  /** The change line's period part, e.g. "vs prev 30D". */
  vsPrev: (period: string) => `vs prev ${period}`,
  /** A change of a percent metric, in points. */
  points: (value: string) => `${value} pts`,
  /** "A, B and C". */
  list: (names: string[]) => glassStatList(names),
  noMetrics: "No metrics yet.",
  needLook: (count: number) => (count === 1 ? "One metric needs a look." : `${WORDS[count] ?? count} metrics need a look.`),
  wrongWay: (names: string) => cap1(`${names} moved the wrong way.`),
  noDataYet: "No data yet.",
  lastPeriod: (periodName: string) => `Last ${periodName}.`,
  nothingWrong: "Nothing moved the wrong way.",
  missingData: (names: string, count: number) => cap1(`${names} ${count === 1 ? "has" : "have"} no data this period.`),
  allOnTrack: "Everything is on track.",
  /** "2 min ago", "6 h ago", "3 d ago", "just now". */
  ago: (ms: number) => formatGlassStatAgo(ms),
  updated: (ago: string) => `Updated ${ago}`,
  stale: (ago: string) => `${ago} · stale`,
  staleSpoken: (ago: string) => `Stale: updated ${ago}`,
  noDataSince: (when: string) => `No data since ${when}`,
  noDataNote: "No data yet",
  trackingSince: (day: string) => `Tracking since ${day}`,
  goal: (goal: string) => `Goal ${goal}`,
  goalProgress: (pct: number, goal: string) => `${pct}% of ${goal} goal`,
  /** A card's accessible name when its value is missing. */
  missingAria: (label: string) => `${label}: no value this period.`,
  prior: (periodName: string) => `the prior ${periodName}`,
  /** The hero's sentence after the coloured change. */
  heroSentence: ({ tone, up, unit, amount, prior }: GlassStatHeroContext) => {
    if (tone === "flat") return `Same as ${prior}.`;
    if (unit === "percent") return `${up ? "Higher" : "Lower"} than ${prior}.`;
    if (unit === "duration") return `${up ? "Longer" : "Shorter"} than ${prior}.`;
    if (unit === "count") return amount ? `${amount} ${up ? "more" : "fewer"} than ${prior}.` : `${up ? "More" : "Fewer"} than ${prior}.`;
    return amount ? `${amount} ${up ? "more" : "less"} than ${prior}.` : `${up ? "More" : "Less"} than ${prior}.`;
  },
  /** "30D" → "30 days" (used when a period has no name). */
  periodName: (label: string) => glassStatPeriodName(label),
  /** aria-label of the period tablist. */
  periodTabs: "Period",
  /** Screen-reader text of a missing value. */
  noValueSr: "No value",
  intervals: (count: number) => `${count} interval${count === 1 ? "" : "s"}`,
  now: "Now",
  interval: (n: number) => `Interval ${n}`,
  noIntervals: "No intervals for this period yet.",
  closeDetails: "Close details",
};
export type GlassStatCardsLabels = typeof defaultGlassStatCardsLabels;
type Labels = GlassStatCardsLabels;

export interface GlassStatDelta {
  tone: GlassStatTone;
  /** The change line, e.g. "+10.6% vs prev 30D". */
  text: string;
  /** The change alone, e.g. "+10.6%" (compact cards and the hero). */
  short: string;
  /** "On track", "Needs a look", "Steady", "No data" or "New metric". */
  verdict: string;
  /** current − previous (0 when either is missing). */
  d: number;
  /** The period part of the change line ("vs prev 30D"), when there is one. */
  vs?: string;
  /** The change is relative (a % of the previous value). */
  relative?: boolean;
}

/**
 * The change and its tone. Percents change in points, durations by the absolute difference,
 * everything else relatively (absolutely when the previous value is 0). Under 0.05% is flat.
 */
export function glassStatDelta(
  cur: number | null | undefined,
  prev: number | null | undefined,
  unit: GlassStatUnit = "count",
  goodDirection: 1 | -1 = 1,
  periodLabel = "30D",
  options?: GlassStatFormatOptions & { labels?: GlassLabels<Labels> },
): GlassStatDelta {
  const L = mergeLabels(defaultGlassStatCardsLabels, options?.labels);
  const u = unitOf(unit);
  const dir = goodDirection === -1 ? -1 : 1;
  if (!isNum(cur)) return { tone: "none", text: L.noValue, short: "—", verdict: L.noData, d: 0 };
  if (!isNum(prev)) return { tone: "none", text: L.noPrevious, short: L.newShort, verdict: L.newMetric, d: 0 };
  const d = cur - prev;
  if (!Number.isFinite(d)) return { tone: "none", text: L.outOfRange, short: "—", verdict: L.noData, d: 0 };
  const p0 = prev !== 0 ? (d / Math.abs(prev)) * 100 : null;
  const pct = p0 != null && Number.isFinite(p0) ? p0 : null;
  if (pct != null ? Math.abs(pct) < 0.05 : d === 0) return { tone: "flat", text: L.noChange, short: "0%", verdict: L.steady, d };
  const ad = Math.abs(d);
  let t: string;
  // The number that decides "rounds to zero" (a custom format may add digits of its own).
  let core: string;
  if (u === "percent" && !options?.format) {
    const s = ad.toFixed(ad < 0.1 ? 2 : 1);
    core = +s === 0 ? "<0.01" : s;
    t = L.points(core);
  } else if (u === "duration" && !options?.format) t = core = durationFmt(ad).replace(/^0m /, "");
  else if (pct == null || u === "percent" || u === "duration") t = core = absFmt(ad, u, options);
  else {
    const p = Math.abs(pct);
    t = core = (p >= 1000 ? Math.round(p).toLocaleString(options?.locale ?? "en-US") : p.toFixed(1)) + "%";
  }
  // A change that rounds to zero in its own format is flat, not a signed "−0".
  if (!/[1-9]/.test(core)) return { tone: "flat", text: L.noChange, short: "0%", verdict: L.steady, d };
  const tone: GlassStatTone = Math.sign(d) === dir ? "good" : "bad";
  const sg = d > 0 ? "+" : MINUS;
  const vs = L.vsPrev(periodLabel);
  const relative = pct != null && u !== "percent" && u !== "duration";
  return { tone, text: `${sg}${t} ${vs}`, short: sg + t, verdict: tone === "good" ? L.onTrack : L.needsLook, d, vs, relative };
}

/** The row's headline and its muted second sentence. */
export function glassStatHeadline(
  items: { name: string; tone: GlassStatTone; missing?: boolean }[],
  periodName = "30 days",
  labels?: GlassLabels<Labels>,
): [string, string] {
  const L = mergeLabels(defaultGlassStatCardsLabels, labels);
  if (!items.length) return [L.noMetrics, ""];
  const bad = items.filter((i) => i.tone === "bad");
  if (bad.length) return [L.needLook(bad.length), L.wrongWay(L.list(bad.map((b) => b.name)))];
  const miss = items.filter((i) => i.missing);
  if (miss.length === items.length) return [L.noDataYet, L.lastPeriod(periodName)];
  if (miss.length) return [L.nothingWrong, L.missingData(L.list(miss.map((m) => m.name)), miss.length)];
  return [L.allOnTrack, L.lastPeriod(periodName)];
}

const PERIOD_WORD: Record<string, string> = { D: "day", W: "week", M: "month", Q: "quarter", Y: "year" };
/** "30D" → "30 days", "12W" → "12 weeks", "1Y" → "year"; anything else as given. */
export function glassStatPeriodName(label: string) {
  const m = /^(\d+)\s*([dwmqy])$/i.exec(label.trim());
  if (!m) return label;
  const n = +m[1];
  const w = PERIOD_WORD[m[2].toUpperCase()];
  return n === 1 ? w : `${n} ${w}s`;
}

/** "2 min ago", "6 h ago", "3 d ago"; "just now" under a minute (or in the future). */
export function formatGlassStatAgo(ms: number) {
  if (!(ms >= 60000)) return "just now";
  const m = Math.floor(ms / 60000);
  if (m < 60) return `${m} min ago`;
  const h = Math.floor(m / 60);
  if (h < 24) return `${h} h ago`;
  return `${Math.floor(h / 24)} d ago`;
}

/** Filled cells per column: normalised to 1…rows (the minimum is always 1 cell); 0 for a gap. */
export function glassStatCells(values: readonly (number | null | undefined)[], rows: number) {
  const fin = values.filter(isNum);
  if (!fin.length) return values.map(() => 0);
  const mn = Math.min(...fin);
  const mx = Math.max(...fin);
  return values.map((v) => (isNum(v) ? 1 + Math.round(((v - mn) / (mx - mn || 1)) * (rows - 1)) : 0));
}

/** Always 12 columns: the last 12 points, padded on the left with gaps; null when there is nothing to draw. */
function trend12(trend: readonly (number | null | undefined)[] | undefined): (number | null)[] | null {
  if (!Array.isArray(trend)) return null;
  const t = trend.slice(-12).map((v) => (isNum(v) ? v : null));
  if (!t.some((v) => v != null)) return null;
  return [...Array<null>(12 - t.length).fill(null), ...t];
}

const MON = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function toMs(t: GlassStatTime | null | undefined) {
  if (t == null) return null;
  // An ISO date-time without an offset is read as UTC, so the server and every browser agree.
  const v = t instanceof Date ? t.getTime() : typeof t === "number" ? t : Date.parse(/T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(t.trim()) ? t.trim() + "Z" : t);
  return Number.isFinite(v) ? v : null;
}
/** "09:00" (time) or "Sep 4" (day), in local time. */
export function formatGlassStatDate(date: Date, style: GlassStatDateStyle) {
  return style === "time" ? `${pad2(date.getHours())}:${pad2(date.getMinutes())}` : MON[date.getMonth()] + " " + date.getDate();
}
type DateFmt = (date: Date, style: GlassStatDateStyle) => string;
/** "09:00" today, else "Sep 4" (local time: only after hydration). */
function clockOf(ms: number, now: number, fd: DateFmt) {
  const a = new Date(ms);
  const b = new Date(now);
  const same = a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
  return fd(a, same ? "time" : "day");
}
/** "LOGO CHURN" → "Logo churn". */
const sentenceCase = (s: string) => (s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : s);

// ---------------------------------------------------------------------------------------------
// Default data: Lumen, the analytics workspace for product teams
// ---------------------------------------------------------------------------------------------

type Seed = { id: string; label: string; unit: GlassStatUnit; dir: 1 | -1; v: Record<string, [number, number]>; goal?: Record<string, number>; ago: number };
const MIN = 60000;
const HOUR = 60 * MIN;
const SEEDS: Seed[] = [
  { id: "rev", label: "REVENUE", unit: "currency", dir: 1, v: { "7D": [612400, 588100], "30D": [2412000, 2181000], "90D": [6980000, 6240000] }, goal: { "7D": 650000, "30D": 2600000, "90D": 7500000 }, ago: 2 * MIN },
  { id: "teams", label: "ACTIVE TEAMS", unit: "count", dir: 1, v: { "7D": [8940, 8902], "30D": [8940, 8610], "90D": [8940, 7980] }, ago: 2 * MIN },
  { id: "churn", label: "LOGO CHURN", unit: "percent", dir: -1, v: { "7D": [0.42, 0.4], "30D": [1.8, 1.5], "90D": [4.9, 5.6] }, ago: HOUR },
  { id: "resp", label: "FIRST REPLY", unit: "duration", dir: -1, v: { "7D": [252, 252], "30D": [252, 318], "90D": [270, 344] }, ago: 6 * HOUR },
];
/** The designed 12-point series: from last period's value to this one's, with seeded wobble. */
function seedSeries(id: string, per: string, c: number, p: number) {
  let s = 7 + id.length * 13 + per.length;
  const r = () => (s = (s * 16807) % 2147483647) / 2147483647;
  return Array.from({ length: 12 }, (_, i) => (i === 11 ? c : p + (c - p) * (i / 11) + (r() - 0.5) * Math.abs(c - p || c * 0.04) * 0.9));
}

/** The reference clock of the designed data, so it always reads "Updated 2 min ago". */
export const GLASS_STAT_DEMO_NOW = Date.UTC(2026, 8, 24, 16, 0);

export const defaultGlassStatPeriods: GlassStatPeriod[] = [
  { id: "7D", name: "7 days" },
  { id: "30D", name: "30 days" },
  { id: "90D", name: "90 days" },
];

export const defaultGlassStatMetrics: GlassStatMetric[] = SEEDS.map((m) => ({
  id: m.id,
  label: m.label,
  unit: m.unit,
  goodDirection: m.dir,
  updatedAt: GLASS_STAT_DEMO_NOW - m.ago,
  data: Object.fromEntries(Object.entries(m.v).map(([per, [c, p]]) => [per, { value: c, previous: p, trend: seedSeries(m.id, per, c, p), goal: m.goal?.[per] }])),
}));

// ---------------------------------------------------------------------------------------------
// Theme tokens (dark and light from the handoff, on the Lumen Halo family hues)
// ---------------------------------------------------------------------------------------------

/*
 * Secondary text: the handoff's greys (230,234,242 on dark, 40,44,56 on light at α .4–.7) are lifted
 * so every one reads at 4.5:1 or better on the card, hovered card, tab well and drill-in, keeping
 * their order (the same maps as Glass Cell Bars): dark α → .55 + (α − .4) × .625, light α → .68 +
 * (α − .4) × .3. The missing value's "—" is large text (30px+) and is lifted to 3:1. Handoff α in
 * each comment. Tone hues are fills; where a light hue is used as small text it has a deeper
 * text-only variant (good, bad, stale), and fills stay on the family hue.
 */
const tone = (hex: string, rgb: string, text: string) => ({ f: hex, t: text, g10: `rgba(${rgb},.1)`, g14: `rgba(${rgb},.14)`, dot: `rgba(${rgb},.6)`, sh: `rgba(${rgb},.45)` });
const grey = (rgb: string, a: number, text: string) => ({ f: `rgba(${rgb},${a})`, t: text, g10: `rgba(${rgb},.05)`, g14: `rgba(${rgb},.07)`, dot: `rgba(${rgb},.3)`, sh: `rgba(${rgb},${a * 0.45})` });
const toneDecls = (T: Record<GlassStatTone, ReturnType<typeof tone>>) =>
  Object.fromEntries(Object.entries(T).flatMap(([k, v]) => Object.entries(v).map(([p, c]) => [`${k}-${p}`, c])));

const DARK = {
  ink: "#f2f4f7",
  inkRgb: "230,234,242",
  body: "linear-gradient(180deg, rgba(34,36,41,.8) 0%, rgba(12,13,15,.86) 40%, rgba(10,11,13,.88) 100%)",
  shadow: "0 0 0 1px rgba(0,0,0,.85), 0 1px 1px rgba(0,0,0,.5), 0 16px 32px -16px rgba(0,0,0,.8), 0 40px 80px -30px rgba(0,0,0,.95)",
  rim: "linear-gradient(180deg, rgba(255,255,255,.24) 0%, rgba(255,255,255,.08) 8%, rgba(255,255,255,.03) 36%, rgba(255,255,255,.03) 86%, rgba(255,255,255,.08) 100%)",
  live: "#4ADE9B",
  liveRing: "rgba(74,222,155,.16)",
  eyebrow: "rgba(230,234,242,.64)", // .55
  headMuted: "rgba(230,234,242,.56)", // .42
  well: "rgba(255,255,255,.04)",
  wellShadow: "inset 0 0 0 1px rgba(255,255,255,.08), inset 0 1px 2px rgba(0,0,0,.35)",
  thumb: "linear-gradient(180deg,#fff,#d9dce3)",
  thumbShadow: "inset 0 1px 0 #fff, 0 6px 16px -8px rgba(255,255,255,.5)",
  tabOn: "#0b0c10",
  tabOff: "rgba(230,234,242,.68)", // .6
  hero: "rgba(255,255,255,.028)",
  heroRing: "inset 0 0 0 1px rgba(255,255,255,.07), inset 0 1px 0 rgba(255,255,255,.09)",
  heroHover: "rgba(255,255,255,.045)",
  heroHoverRing: "inset 0 0 0 1px rgba(255,255,255,.12), inset 0 1px 0 rgba(255,255,255,.12)",
  card: "rgba(255,255,255,.03)",
  cardRing: "inset 0 0 0 1px rgba(255,255,255,.07), inset 0 1px 0 rgba(255,255,255,.08)",
  cardHover: "rgba(255,255,255,.05)",
  compact: "rgba(255,255,255,.022)",
  compactRing: "inset 0 0 0 1px rgba(255,255,255,.06)",
  compactHover: "rgba(255,255,255,.05)",
  sel: "rgba(139,147,255,.07)",
  selRing: "inset 0 0 0 1px rgba(139,147,255,.4), inset 0 1px 0 rgba(255,255,255,.1)",
  label: "rgba(230,234,242,.68)", // .6
  labelSm: "rgba(230,234,242,.64)", // .55
  sentence: "rgba(230,234,242,.68)", // .6
  goalNote: "rgba(230,234,242,.61)", // .5
  note: "rgba(230,234,242,.55)", // .4
  dash: "rgba(230,234,242,.42)", // .35
  empty: "rgba(230,234,242,.06)",
  goalOff: "rgba(255,255,255,.07)",
  drill: "rgba(0,0,0,.2)",
  drillRing: "inset 0 0 0 1px rgba(255,255,255,.06), inset 0 1px 0 rgba(255,255,255,.04)",
  drillMuted: "rgba(230,234,242,.58)", // .45
  drillVal: "rgba(230,234,242,.61)", // .5
  drillX: "rgba(230,234,242,.55)", // .4
  close: "rgba(255,255,255,.06)",
  closeHover: "rgba(255,255,255,.11)",
  closeFg: "rgba(230,234,242,.74)", // .7
  focus: "rgba(139,147,255,.6)",
  accent: "#8B93FF",
  accentGlow: "rgba(139,147,255,.6)",
  goodGlow: "rgba(74,222,155,.6)",
  stale: "#FFB547",
  staleT: "#FFB547",
  ...toneDecls({
    good: tone("#4ADE9B", "74,222,155", "#4ADE9B"),
    bad: tone("#FF6B7A", "255,107,122", "#FF6B7A"),
    flat: grey("230,234,242", 0.55, "rgba(230,234,242,.64)"), // .55
    none: grey("230,234,242", 0.4, "rgba(230,234,242,.55)"), // .4
  }),
};

const LIGHT: typeof DARK = {
  ink: "#14161b",
  inkRgb: "40,44,56",
  body: "linear-gradient(180deg, rgba(255,255,255,.94) 0%, rgba(250,251,253,.88) 40%, rgba(244,246,250,.92) 100%)",
  shadow: "0 0 0 1px rgba(20,24,40,.07), 0 1px 2px rgba(20,24,40,.06), 0 16px 32px -16px rgba(20,24,40,.18), 0 40px 80px -30px rgba(20,24,40,.28)",
  rim: "linear-gradient(180deg, rgba(255,255,255,.95), rgba(255,255,255,.4) 40%, rgba(255,255,255,.7))",
  live: "#1FB87A",
  liveRing: "rgba(31,184,122,.16)",
  eyebrow: "rgba(40,44,56,.73)", // .55
  headMuted: "rgba(40,44,56,.69)", // .42
  well: "rgba(20,24,40,.04)",
  wellShadow: "inset 0 0 0 1px rgba(20,24,40,.06), inset 0 1px 2px rgba(20,24,40,.06)",
  thumb: "linear-gradient(180deg,#2a2d35,#14161b)",
  thumbShadow: "inset 0 1px 0 rgba(255,255,255,.15), 0 6px 16px -8px rgba(20,24,40,.5)",
  tabOn: "#ffffff",
  tabOff: "rgba(40,44,56,.74)", // .6
  hero: "rgba(255,255,255,.7)",
  heroRing: "0 0 0 1px rgba(20,24,40,.06), 0 1px 2px rgba(20,24,40,.04), inset 0 1px 0 #fff",
  heroHover: "#fff",
  heroHoverRing: "0 0 0 1px rgba(20,24,40,.1), 0 8px 20px -10px rgba(20,24,40,.2), inset 0 1px 0 #fff",
  card: "rgba(255,255,255,.7)",
  cardRing: "0 0 0 1px rgba(20,24,40,.06), 0 1px 2px rgba(20,24,40,.04), inset 0 1px 0 #fff",
  cardHover: "#fff",
  compact: "rgba(255,255,255,.55)",
  compactRing: "0 0 0 1px rgba(20,24,40,.06)",
  compactHover: "#fff",
  sel: "rgba(107,116,245,.07)",
  selRing: "0 0 0 1px rgba(107,116,245,.4), inset 0 1px 0 #fff",
  label: "rgba(40,44,56,.74)", // .6
  labelSm: "rgba(40,44,56,.73)", // .55
  sentence: "rgba(40,44,56,.74)", // .6
  goalNote: "rgba(40,44,56,.71)", // .5
  note: "rgba(40,44,56,.68)", // .4
  dash: "rgba(40,44,56,.55)", // .35
  empty: "rgba(40,44,56,.06)",
  goalOff: "rgba(20,24,40,.08)",
  drill: "rgba(20,24,40,.03)",
  drillRing: "inset 0 0 0 1px rgba(20,24,40,.06), inset 0 1px 0 rgba(255,255,255,.7)",
  drillMuted: "rgba(40,44,56,.7)", // .45
  drillVal: "rgba(40,44,56,.71)", // .5
  drillX: "rgba(40,44,56,.68)", // .4
  close: "rgba(20,24,40,.06)",
  closeHover: "rgba(20,24,40,.1)",
  closeFg: "rgba(40,44,56,.77)", // .7
  focus: "rgba(107,116,245,.5)",
  accent: "#6B74F5",
  accentGlow: "rgba(107,116,245,.6)",
  goodGlow: "rgba(31,184,122,.6)",
  stale: "#F2981C",
  // Text-only variants: the family hue deepened until it reads at 4.5:1 on the card and tab well.
  staleT: "#9A5B00",
  ...toneDecls({
    good: tone("#1FB87A", "31,184,122", "#117A50"),
    bad: tone("#E5485A", "229,72,90", "#C42D45"),
    flat: grey("40,44,56", 0.55, "rgba(40,44,56,.73)"), // .55
    none: grey("40,44,56", 0.4, "rgba(40,44,56,.68)"), // .4
  }),
};

/*
 * Family colour API: accent, good, bad and stale (warn) read --glass-accent, --glass-good,
 * --glass-bad and --glass-warn (per theme with -dark / -light, text variants with -text) from the
 * root or any ancestor, falling back to the designed hues. Their glows and tints mix from them.
 */
const fam = (n: string, mode: "dark" | "light", def: string) => `var(--glass-${n}-${mode},var(--glass-${n},${def}))`;
const famText = (n: string, mode: "dark" | "light", def: string) => `var(--glass-${n}-text-${mode},var(--glass-${n}-text,${fam(n, mode, def)}))`;
const mix = (c: string, a: number) => `color-mix(in srgb, ${c} ${Math.round(a * 1000) / 10}%, transparent)`;
function withFamily(t: typeof DARK, mode: "dark" | "light"): typeof DARK {
  const acc = fam("accent", mode, t.accent);
  const accRgb = mode === "dark" ? "139,147,255" : "107,116,245";
  const out: Record<string, string> = { ...t };
  const src = t as unknown as Record<string, string>;
  out.accent = acc;
  out.accentGlow = mix(acc, 0.6);
  out.sel = mix(acc, 0.07);
  out.selRing = t.selRing.replace(`rgba(${accRgb},.4)`, mix(acc, 0.4));
  out.focus = mix(acc, mode === "dark" ? 0.6 : 0.5);
  out.stale = fam("warn", mode, t.stale);
  out.staleT = famText("warn", mode, t.staleT);
  for (const k of ["good", "bad"] as const) {
    const f = fam(k, mode, src[`${k}-f`]);
    out[`${k}-f`] = f;
    out[`${k}-t`] = famText(k, mode, src[`${k}-t`]);
    out[`${k}-g10`] = mix(f, 0.1);
    out[`${k}-g14`] = mix(f, 0.14);
    out[`${k}-dot`] = mix(f, 0.6);
    out[`${k}-sh`] = mix(f, 0.45);
  }
  out.goodGlow = mix(out["good-f"], 0.6);
  return out as typeof DARK;
}

const V = (k: keyof typeof DARK) => `var(--sc-${k})`;
type ToneVar = "f" | "t" | "g10" | "g14" | "dot" | "sh";
const TV = (t: GlassStatTone, k: ToneVar) => `var(--sc-${t}-${k})`;
const decls = (t: typeof DARK) =>
  Object.entries(t)
    .map(([k, v]) => `--sc-${k}:${v}`)
    .join(";");

const HOVER = "@media (hover:hover){";
const STAT_CSS =
  lumenThemeCss(decls(withFamily(DARK, "dark")), decls(withFamily(LIGHT, "light"))) +
  // A registered colour, so the tone glow really cross-fades (gradients alone would jump).
  `@property --sc-glow{syntax:'<color>';inherits:false;initial-value:transparent}` +
  `[data-gsc-card]{-webkit-tap-highlight-color:transparent}` +
  `[data-gsc-card="hero"]{background:var(--sc-hero);box-shadow:var(--sc-heroRing)}` +
  `[data-gsc-card="default"]{background:var(--sc-card);box-shadow:var(--sc-cardRing)}` +
  `[data-gsc-card="compact"]{background:var(--sc-compact);box-shadow:var(--sc-compactRing)}` +
  `[data-gsc-card][data-gsc-sel]{background:var(--sc-sel);box-shadow:var(--sc-selRing)}` +
  HOVER +
  `button[data-gsc-card="hero"]:hover{background:var(--sc-heroHover);box-shadow:var(--sc-heroHoverRing)}` +
  `button[data-gsc-card="default"]:hover{background:var(--sc-cardHover);transform:translateY(-2px)}` +
  `button[data-gsc-card="compact"]:hover{background:var(--sc-compactHover)}` +
  `button[data-gsc-card][data-gsc-sel]:hover{background:var(--sc-sel)}` +
  `:is([data-gsc-motion="reduced"],[data-gsc-motion="off"]) button[data-gsc-card]:hover{transform:none}` +
  `[data-gsc-close]:hover{background:var(--sc-closeHover)}` +
  `[data-gsc-col]:hover [data-gsc-colv]{opacity:1!important}` +
  `}` +
  `[data-gsc-focus]:focus-visible{outline:none;box-shadow:0 0 0 2px var(--sc-focus)!important}` +
  `[data-gsc-panel]:focus{outline:none}` +
  // The opened drill-in takes focus: an inset ring (the grow wrapper clips anything outside).
  `[data-gsc-panel]:focus-visible>[data-gsc-drill-body]{box-shadow:inset 0 0 0 2px var(--sc-focus),var(--sc-drillRing)!important}` +
  // Card-width rules (container sizes are the content box), so every card in a row reads the same.
  // Default: under 150px of content (a 182px card) the change line always splits, "vs prev 30D" on
  // its own line, and a sentence wraps instead of truncating. Compact: at least 10px between value
  // and change; under 130px of content (a 156px card) the change moves under the value, left-aligned.
  `[data-gsc-card="default"],[data-gsc-card="compact"]{container:gscc/inline-size}` +
  `@container gscc (max-width:149.98px){[data-gsc-vs]{display:block}[data-gsc-delta]{white-space:normal!important}}` +
  `@container gscc (max-width:260px){[data-gsc-cline]{gap:10px!important}}` +
  `@container gscc (max-width:129.98px){[data-gsc-cline]{flex-direction:column;align-items:flex-start!important;gap:3px!important}}` +
  `[data-gsc-grid="row"]{grid-template-columns:repeat(auto-fit,minmax(min(100%,154px),1fr))}` +
  `[data-gsc-grid="compact"]{grid-template-columns:repeat(auto-fit,minmax(min(100%,150px),1fr))}` +
  `[data-gsc-hero-grid]{grid-template-columns:repeat(auto-fit,minmax(min(100%,340px),1fr))}` +
  `[data-gsc-layout="desktop"] [data-gsc-hero-grid]{grid-template-columns:repeat(2,minmax(0,1fr))}` +
  `[data-gsc-layout="mobile"] [data-gsc-hero-grid]{grid-template-columns:minmax(0,1fr)}` +
  // Phone: rows go 2×2 (four across needs 652px), the change line wraps instead of truncating, the
  // compact change drops under its value when needed, and the shell and hero pad tighter.
  (() => {
    const two =
      `[data-gsc-grid]{grid-template-columns:repeat(2,minmax(0,1fr))}` +
      `[data-gsc-note],[data-gsc-note]>span{white-space:normal!important;overflow:visible!important}` +
      `[data-gsc-note]{align-items:flex-start!important}[data-gsc-note]>[data-gsc-sdot]{margin-top:4px}` +
      `[data-gsc-goalrow]{flex-wrap:wrap;row-gap:4px!important}[data-gsc-goalrow]>span{overflow:visible!important}` +
      `[data-gsc-drill-cols] [data-gsc-first]{align-items:flex-start!important}` +
      `[data-gsc-drill-cols] [data-gsc-last]{align-items:flex-end!important}`;
    const tight = `[data-gsc-shell],[data-gsc-body]{gap:18px!important}[data-gsc-shell]{padding:18px!important}[data-gsc-card="hero"]{padding:22px 20px 20px!important}[data-gsc-drill-body]{padding:16px 16px 14px!important}`;
    const scope = (css: string, sel: string) => css.replace(/(^|})([^{}]+)\{/g, (_, a: string, s: string) => `${a}${s.split(",").map((x) => `${sel} ${x.trim()}`).join(",")}{`);
    return (
      `@container gsc (max-width:707.98px){${scope(two, '[data-gsc-layout="auto"]')}}` +
      `@container gsc (max-width:519.98px){${scope(tight, '[data-gsc-layout="auto"]')}}` +
      scope(two + tight, '[data-gsc-layout="mobile"]')
    );
  })() +
  `@keyframes gsc-fade{from{opacity:0}to{opacity:1}}`;

// ---------------------------------------------------------------------------------------------
// Motion
// ---------------------------------------------------------------------------------------------

type Level = GlassStatMotion | "reduced";
const SPRING = "cubic-bezier(.22,1,.36,1)";
const CALM = "cubic-bezier(.4,0,.2,1)";
type Part = [prop: string, ms: number, ease?: "spring" | "ease" | "linear", delay?: number];
/** Transition strings per level: subtle is 30% shorter on the calm curve with no staggers; off is none; reduced keeps 150ms opacity fades only. */
function mkTx(level: Level) {
  return (parts: Part[]) => {
    if (level === "off") return "none";
    if (level === "reduced") return parts.some((p) => p[0] === "opacity") ? "opacity 150ms linear" : "none";
    const sub = level === "subtle";
    return parts
      .map(([p, ms, e = "spring", d = 0]) => {
        const wait = sub ? 0 : Math.round(d);
        return `${p} ${sub ? Math.round(ms * 0.7) : ms}ms ${e === "spring" ? (sub ? CALM : SPRING) : e}${wait ? ` ${wait}ms` : ""}`;
      })
      .join(", ");
  };
}
type Tx = ReturnType<typeof mkTx>;

function useLevel(motion: GlassStatMotion): Level {
  const reduced = usePrefersReducedMotion();
  return reduced ? "reduced" : normalizeGlassMotion(motion);
}

const noopSubscribe = () => () => {};
const nullSnap = () => null;
const minuteListeners = new Set<() => void>();
let minuteTimer = 0;
const subscribeMinute = (cb: () => void) => {
  minuteListeners.add(cb);
  if (!minuteTimer) minuteTimer = window.setInterval(() => !document.hidden && minuteListeners.forEach((f) => f()), 15000);
  return () => {
    minuteListeners.delete(cb);
    if (!minuteListeners.size) {
      window.clearInterval(minuteTimer);
      minuteTimer = 0;
    }
  };
};
const minuteNow = () => Math.floor(Date.now() / MIN) * MIN;
/** The given clock, else a minute clock that starts after hydration (the server renders no relative times). */
function useNow(now: GlassStatTime | undefined) {
  const given = toMs(now);
  const live = React.useSyncExternalStore(given == null ? subscribeMinute : noopSubscribe, given == null ? minuteNow : nullSnap, nullSnap);
  return given ?? live;
}
/** False on the server and the hydration render: local dates and times wait for it. */
const useHydrated = () =>
  React.useSyncExternalStore(
    noopSubscribe,
    () => true,
    () => false,
  );

// ---------------------------------------------------------------------------------------------
// The card model
// ---------------------------------------------------------------------------------------------

type StatInput = Pick<
  GlassStatCardOwnProps,
  "label" | "value" | "previous" | "unit" | "format" | "suffix" | "locale" | "currency" | "goodDirection" | "periodLabel" | "periodName" | "trend" | "goal" | "updatedAt" | "trackingSince" | "staleAfterMs"
>;
type StatCtx = { L: Labels; formatDate: DateFmt };
type Stat = ReturnType<typeof statModel>;
const NBSP = " ";

function statModel(p: StatInput, now: number | null, hydrated: boolean, { L, formatDate: fd }: StatCtx) {
  const unit = unitOf(p.unit);
  const dir: 1 | -1 = p.goodDirection === -1 ? -1 : 1;
  const per = p.periodLabel ?? "30D";
  const perName = p.periodName ?? L.periodName(per);
  const value = isNum(p.value) ? p.value : null;
  const missing = value == null;
  const fo: GlassStatFormatOptions = { format: p.format, suffix: p.suffix, locale: p.locale, currency: p.currency };
  const dl = glassStatDelta(value, p.previous, unit, dir, per, { ...fo, labels: L });
  const val = formatGlassStatValue(value, unit, fo);
  const up = toMs(p.updatedAt);
  const staleAfter = isNum(p.staleAfterMs) && p.staleAfterMs > 0 ? p.staleAfterMs : 2 * HOUR;
  const stale = !missing && now != null && up != null && now - up > staleAfter;
  const since = toMs(p.trackingSince);
  // Missing beats stale beats "Tracking since" beats "Updated"; local times wait for hydration.
  let note = NBSP;
  let spoken = "";
  if (missing) {
    note = up == null ? L.noDataNote : hydrated && now != null ? L.noDataSince(clockOf(up, now, fd)) : NBSP;
    spoken = note === NBSP ? "" : note;
  } else if (stale && up != null && now != null) {
    const ago = L.ago(now - up);
    note = L.stale(ago);
    spoken = L.staleSpoken(ago);
  } else if (dl.tone === "none" && since != null) {
    note = hydrated ? L.trackingSince(fd(new Date(since), "day")) : NBSP;
    spoken = note === NBSP ? "" : note;
  } else if (up != null && now != null) {
    note = L.updated(L.ago(now - up));
    spoken = note;
  }
  const label = String(p.label ?? "");
  const tr = missing ? null : trend12(p.trend);
  // Goal: a target to reach, or to stay under when down is good.
  const goal = isNum(p.goal) && p.goal > 0 ? p.goal : null;
  let gp = 0;
  if (goal != null && value != null) gp = dir === 1 ? Math.max(0, value / goal) : value <= goal ? 1 : Math.max(0, goal / value);
  const reached = goal != null && value != null && (dir === 1 ? value >= goal : value <= goal);
  const gc = goal != null ? Math.max(0, Math.min(30, Math.round(gp * 30))) : 0;
  const goalNote = goal == null ? "" : value == null ? L.goal(formatGlassStatValue(goal, unit, fo)) : L.goalProgress(Math.round((dir === 1 ? value / goal : gp) * 100), formatGlassStatValue(goal, unit, fo));
  const aria = missing
    ? `${L.missingAria(label)}${spoken ? ` ${spoken}.` : ""}`
    : `${label} ${val}, ${dl.text}. ${dl.verdict}.${goalNote ? ` ${goalNote}.` : ""}${spoken ? ` ${spoken}.` : ""}`;
  return { unit, dir, per, perName, value, missing, dl, tone: dl.tone, val, note, amber: missing || stale, stale, label, trend: tr, goal, gc, reached, goalNote, aria, fo, L };
}

// ---------------------------------------------------------------------------------------------
// Pieces
// ---------------------------------------------------------------------------------------------

const DIG = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
/** The handoff's character windows (em): digits .6, separators .28, units and signs their own. */
function charW(ch: string) {
  if (/\d/.test(ch)) return 0.6;
  switch (ch) {
    case ",":
    case ".":
      return 0.28;
    case " ":
      return 0.25;
    case "m":
      return 0.9;
    case "M":
      return 0.88;
    case "K":
    case "B":
    case "T":
      return 0.68;
    case "$":
      return 0.62;
    case "%":
      return 0.84;
    case "s":
      return 0.54;
    case "h":
      return 0.6;
    case MINUS:
    case "+":
      return 0.62;
    default:
      return 0.64;
  }
}
const emWidth = (s: string) => Array.from(s).reduce((w, ch) => w + charW(ch), 0);

/**
 * Rolling digits: each digit is a one-line window over a 0–9 column that rolls to its new value
 * (.9s, 35ms apart), only when the value changes. Reduced motion swaps the text with a 150ms fade.
 */
function Roll({ text, level, tx }: { text: string; level: Level; tx: Tx }) {
  if (level === "reduced")
    return (
      <span key={text} aria-hidden style={{ display: "block", whiteSpace: "nowrap", animation: "gsc-fade 150ms linear" }}>
        {text}
      </span>
    );
  return (
    <span aria-hidden style={{ display: "flex" }}>
      {Array.from(text).map((ch, i) => {
        const dig = /\d/.test(ch);
        return (
          <span key={i} style={{ display: "block", height: "1em", overflow: "hidden", width: `${charW(ch)}em`, textAlign: "center", transition: tx([["width", 500]]) }}>
            <span style={{ display: "flex", flexDirection: "column", transform: `translateY(${dig ? -Number(ch) : 0}em)`, transition: tx([["transform", 900, "spring", i * 35]]) }}>
              {(dig ? DIG : [ch]).map((g) => (
                <span key={g} style={{ display: "block", height: "1em" }}>
                  {g}
                </span>
              ))}
            </span>
          </span>
        );
      })}
    </span>
  );
}

/** The value: fits its column (never overflows a phone card), exposed to screen readers as text. */
function Value({ s, size, level, tx, color }: { s: Stat; size: GlassStatSize; level: Level; tx: Tx; color: string }) {
  const px = size === "hero" ? 64 : size === "default" ? 30 : 19;
  const ls = size === "hero" ? "-.035em" : size === "default" ? "-.03em" : "-.02em";
  const font: React.CSSProperties = { fontWeight: 600, lineHeight: 1, letterSpacing: ls, color, fontVariantNumeric: "tabular-nums" };
  const inner = s.missing ? (
    <span aria-hidden style={{ display: "block", whiteSpace: "nowrap", transition: tx([["color", 500, "ease"]]) }}>
      —
    </span>
  ) : (
    <Roll text={s.val} level={level} tx={tx} />
  );
  const sr = <span className="sr-only">{s.missing ? s.L.noValueSr : s.val}</span>;
  if (size === "compact")
    return (
      <span style={{ display: "flex", flex: "none", fontSize: px, ...font }}>
        {sr}
        {inner}
      </span>
    );
  return (
    <span style={{ display: "block", width: "100%", containerType: "inline-size" }}>
      <span style={{ display: "flex", flex: "none", fontSize: `min(${px}px, calc(100cqi / ${Math.max(1, emWidth(s.missing ? "—" : s.val)).toFixed(3)}))`, ...font }}>
        {sr}
        {inner}
      </span>
    </span>
  );
}

type Intro = { shown: boolean; delay: number };

/** 12 columns of cells, the last in the tone colour. */
function Cells({ s, rows, cell, radius, gap, colGap, glow, intro, tx, ms, height }: { s: Stat; rows: number; cell: number; radius: number; gap: number; colGap: number; glow: boolean; intro: Intro; tx: Tx; ms: number; height?: number }) {
  const t = s.trend!;
  const n = glassStatCells(t, rows);
  return (
    <span aria-hidden style={{ display: "flex", alignItems: "flex-end", gap: colGap, width: "100%", height }}>
      {t.map((_, i) => {
        const last = i === t.length - 1;
        return (
          <span key={i} style={{ flex: 1, display: "flex", flexDirection: "column-reverse", gap }}>
            {Array.from({ length: rows }, (_, j) => {
              const on = intro.shown && j < n[i];
              return (
                <span
                  key={j}
                  style={{
                    height: cell,
                    borderRadius: radius,
                    background: last ? TV(s.tone, "f") : `rgba(${V("inkRgb")},${(0.14 + i * 0.018).toFixed(3)})`,
                    opacity: on ? 1 : 0,
                    boxShadow: glow && last && j < n[i] && s.tone !== "flat" ? `0 0 8px ${TV(s.tone, "sh")}` : "none",
                    transition: tx([
                      ["opacity", ms, "ease", intro.delay + i * 30 + j * 20],
                      ["background", 500, "ease"],
                    ]),
                  }}
                />
              );
            })}
          </span>
        );
      })}
    </span>
  );
}

/** No trend: one row of 12 cells (the ink ramp, the last in the tone colour); empty at α .06 when the value is missing. */
function Strip({ s, cell, radius, intro, tx }: { s: Stat; cell: number; radius: number; intro: Intro; tx: Tx }) {
  const lit = s.tone === "good" || s.tone === "flat" || s.tone === "bad";
  return (
    <span aria-hidden style={{ position: "relative", display: "flex", gap: 2, height: cell, width: "100%" }}>
      {Array.from({ length: 12 }, (_, i) => (
        <span
          key={i}
          style={{
            flex: 1,
            borderRadius: radius,
            background: s.missing ? V("empty") : i === 11 && lit ? TV(s.tone, "f") : `rgba(${V("inkRgb")},${(0.14 + i * 0.018).toFixed(3)})`,
            opacity: intro.shown ? 1 : 0,
            transition: tx([
              ["opacity", 300, "ease", intro.delay + i * 30],
              ["background", 500, "ease"],
            ]),
          }}
        />
      ))}
    </span>
  );
}

function Glow({ s, hero, intro, tx }: { s: Stat; hero: boolean; intro: Intro; tx: Tx }) {
  return (
    <span
      aria-hidden
      style={
        {
          position: "absolute",
          ...(hero ? { left: "-10%", right: "-10%", top: -120, height: 220 } : { left: 0, right: 0, top: -70, height: 120 }),
          "--sc-glow": intro.shown ? TV(s.tone, hero ? "g14" : "g10") : "transparent",
          background: `radial-gradient(50% 50% at ${hero ? "30%" : "50%"} 50%, var(--sc-glow), transparent 70%)`,
          transition: tx([["--sc-glow", 800, "ease", intro.delay]]),
          pointerEvents: "none",
        } as Record<string, string | number> as React.CSSProperties
      }
    />
  );
}

function Note({ s, gap = 6 }: { s: Stat; gap?: number }) {
  return (
    <span data-gsc-note="" className="flex items-center" style={{ gap, fontFamily: GLASS_MONO, fontWeight: 400, fontSize: 11, color: s.amber ? V("staleT") : V("note"), whiteSpace: "nowrap", overflow: "hidden", minWidth: 0 }}>
      {s.amber && <span data-gsc-sdot="" aria-hidden style={{ width: 5, height: 5, flex: "none", borderRadius: "50%", background: V("stale") }} />}
      <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{s.note.replace(/(\d+) (min|h|d) ago/, "$1\u00a0$2\u00a0ago")}</span>
    </span>
  );
}

/** "$231K more than the prior 30 days." */
function heroSentence(s: Stat) {
  if (s.tone === "none") return "";
  // The coloured change already is the amount for points, durations and changes from zero: don't repeat it.
  const amount = s.dl.relative && s.unit !== "percent" && s.unit !== "duration" ? absFmt(Math.abs(s.dl.d), s.unit, s.fo) : null;
  return s.L.heroSentence({ tone: s.tone, up: s.dl.d >= 0, unit: s.unit, amount, prior: s.L.prior(s.perName) });
}

// ---------------------------------------------------------------------------------------------
// Card views (shared by GlassStatCard and GlassStatCards)
// ---------------------------------------------------------------------------------------------

type ViewProps = {
  s: Stat;
  level: Level;
  tx: Tx;
  intro: Intro;
  selected?: boolean;
  expanded?: boolean;
  controls?: string;
  onOpen?: (el: HTMLElement) => void;
  className?: string;
};

function Box({
  kind,
  s,
  onOpen,
  selected,
  expanded,
  controls,
  style,
  className,
  children,
}: {
  kind: GlassStatSize;
  s: Stat;
  onOpen?: (el: HTMLElement) => void;
  selected?: boolean;
  expanded?: boolean;
  controls?: string;
  style: React.CSSProperties;
  className?: string;
  children: React.ReactNode;
}) {
  const common = { "data-gsc-card": kind, "data-gsc-sel": selected ? "" : undefined, "data-gsc-hero-grid": kind === "hero" ? "" : undefined, className };
  if (onOpen)
    return (
      <button
        type="button"
        {...common}
        data-gsc-focus=""
        aria-label={s.aria}
        aria-expanded={!!expanded}
        aria-controls={controls}
        data-sound={expanded ? "close" : "open"}
        onClick={(e) => onOpen(e.currentTarget)}
        style={{ border: "none", margin: 0, font: "inherit", textAlign: "left", color: "inherit", cursor: "pointer", appearance: "none", ...style }}
      >
        {children}
      </button>
    );
  return (
    <div {...common} style={style}>
      {children}
    </div>
  );
}

const cardEnter = (intro: Intro, level: Level): React.CSSProperties => (intro.shown ? {} : level === "reduced" ? { opacity: 0 } : { opacity: 0, translate: "0 12px" });
const enterTx = (intro: Intro): Part[] => [
  ["opacity", 500, "ease", intro.delay],
  ["translate", 700, "spring", intro.delay],
];
const mono = (wt: number, size: number, extra?: React.CSSProperties): React.CSSProperties => ({ fontFamily: GLASS_MONO, fontWeight: wt, fontSize: size, ...extra });

function HeroView({ s, level, tx, intro, selected, expanded, controls, onOpen, className }: ViewProps) {
  const sentence = heroSentence(s);
  const fill = s.reached ? TV("good", "f") : V("accent");
  return (
    <Box
      kind="hero"
      s={s}
      onOpen={onOpen}
      selected={selected}
      expanded={expanded}
      controls={controls}
      className={className}
      style={{
        position: "relative",
        display: "grid",
        alignItems: "end",
        gap: "24px 40px",
        padding: "26px 28px 24px",
        borderRadius: 24,
        overflow: "hidden",
        boxSizing: "border-box",
        minWidth: 0,
        ...cardEnter(intro, level),
        transition: tx([["background", 300, "ease"], ["box-shadow", 300, "ease"], ...enterTx(intro)]),
      }}
    >
      <Glow s={s} hero intro={intro} tx={tx} />
      <span className="relative flex flex-col" style={{ gap: 14, minWidth: 0 }}>
        <span className="flex flex-wrap items-center" style={{ gap: 10 }}>
          <span style={mono(500, 10.5, { letterSpacing: ".14em", color: V("label"), textTransform: "uppercase" })}>{s.label}</span>
          <span className="inline-flex items-center" style={{ gap: 6, fontWeight: 500, fontSize: 11.5, whiteSpace: "nowrap", color: TV(s.tone, "t"), transition: tx([["color", 500, "ease"]]) }}>
            <span aria-hidden style={{ width: 6, height: 6, borderRadius: 2, background: TV(s.tone, "f"), transition: tx([["background", 500, "ease"]]) }} />
            {s.dl.verdict}
          </span>
        </span>
        <Value s={s} size="hero" level={level} tx={tx} color={s.missing ? V("dash") : V("ink")} />
        <span style={{ fontWeight: 400, fontSize: 14, lineHeight: 1.45, color: V("sentence"), textWrap: "pretty" }}>
          <span style={{ fontWeight: 500, color: TV(s.tone, "t"), fontVariantNumeric: "tabular-nums", transition: tx([["color", 500, "ease"]]) }}>{s.tone === "none" ? s.dl.text : s.dl.short}</span>
          {sentence && " " + sentence}
        </span>
      </span>
      <span className="relative flex flex-col" style={{ gap: 16, minWidth: 0 }}>
        {s.trend ? (
          <Cells s={s} rows={7} cell={8.5} radius={2.5} gap={2} colGap={4} glow intro={intro} tx={tx} ms={350} height={62} />
        ) : (
          <span className="flex items-end" style={{ height: 62 }}>
            <Strip s={s} cell={8.5} radius={2.5} intro={intro} tx={tx} />
          </span>
        )}
        <span className="flex flex-col" style={{ gap: 8 }}>
          {s.goal != null && (
            <span aria-hidden className="flex" style={{ gap: 2, height: 8 }}>
              {Array.from({ length: 30 }, (_, i) => {
                const on = intro.shown && i < s.gc;
                return (
                  <span
                    key={i}
                    style={{
                      flex: 1,
                      borderRadius: 2,
                      background: on ? fill : V("goalOff"),
                      boxShadow: on && i === s.gc - 1 ? `0 0 8px ${s.reached ? V("goodGlow") : V("accentGlow")}` : "none",
                      transition: tx([
                        ["background", 400, "ease", intro.delay + i * 14],
                        ["box-shadow", 400, "ease", intro.delay + i * 14],
                      ]),
                    }}
                  />
                );
              })}
            </span>
          )}
          <span data-gsc-goalrow="" className="flex justify-between" style={mono(400, 11, { gap: 12, color: V("goalNote"), whiteSpace: "nowrap", minWidth: 0 })}>
            <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{s.goalNote}</span>
            {s.amber ? (
              <Note s={s} />
            ) : (
              <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{s.note}</span>
            )}
          </span>
        </span>
      </span>
    </Box>
  );
}

function DefaultView({ s, level, tx, intro, selected, expanded, controls, onOpen, className }: ViewProps) {
  return (
    <Box
      kind="default"
      s={s}
      onOpen={onOpen}
      selected={selected}
      expanded={expanded}
      controls={controls}
      className={className}
      style={{
        position: "relative",
        display: "flex",
        flexDirection: "column",
        gap: 12,
        minWidth: 0,
        padding: "16px 16px 14px",
        borderRadius: 20,
        overflow: "hidden",
        boxSizing: "border-box",
        ...cardEnter(intro, level),
        transition: tx([["background", 300, "ease"], ["box-shadow", 300, "ease"], ["transform", 500], ...enterTx(intro)]),
      }}
    >
      <Glow s={s} hero={false} intro={intro} tx={tx} />
      <span className="relative flex items-center justify-between" style={{ gap: 8, minWidth: 0 }}>
        <span style={mono(500, 10, { letterSpacing: ".13em", color: V("label"), whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", textTransform: "uppercase" })}>{s.label}</span>
        <span
          title={s.dl.verdict}
          aria-hidden
          style={{ width: 6, height: 6, flex: "none", borderRadius: 2, background: TV(s.tone, "f"), boxShadow: `0 0 8px ${TV(s.tone, "dot")}`, transition: tx([["background", 500, "ease"], ["box-shadow", 500, "ease"]]) }}
        />
      </span>
      <span className="relative flex flex-col items-start" style={{ gap: 12 }}>
        <Value s={s} size="default" level={level} tx={tx} color={s.missing ? V("dash") : V("ink")} />
        {s.trend ? <Cells s={s} rows={5} cell={3.5} radius={1.5} gap={2} colGap={2} glow={false} intro={intro} tx={tx} ms={300} /> : <Strip s={s} cell={3.5} radius={1.5} intro={intro} tx={tx} />}
      </span>
      <span className="relative flex flex-col" style={{ gap: 3, minWidth: 0 }}>
        <span data-gsc-delta="" style={{ fontWeight: 500, fontSize: 12.5, color: TV(s.tone, "t"), whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", fontVariantNumeric: "tabular-nums", transition: tx([["color", 500, "ease"]]) }}>
          {s.dl.vs ? (
            <>
              {s.dl.short}
              <span data-gsc-vs="">{" " + s.dl.vs.replace(/ /g, "\u00a0")}</span>
            </>
          ) : (
            s.dl.text
          )}
        </span>
        <Note s={s} />
      </span>
      {!onOpen && <span className="sr-only">{s.dl.verdict}.</span>}
    </Box>
  );
}

function CompactView({ s, level, tx, intro, selected, expanded, controls, onOpen, className }: ViewProps) {
  return (
    <Box
      kind="compact"
      s={s}
      onOpen={onOpen}
      selected={selected}
      expanded={expanded}
      controls={controls}
      className={className}
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 6,
        minWidth: 0,
        padding: "11px 13px",
        borderRadius: 14,
        boxSizing: "border-box",
        ...cardEnter(intro, level),
        transition: tx([["background", 250, "ease"], ...enterTx(intro)]),
      }}
    >
      <span style={mono(500, 9.5, { letterSpacing: ".13em", color: V("labelSm"), whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", textTransform: "uppercase" })}>{s.label}</span>
      <span data-gsc-cline="" className="flex items-baseline justify-between" style={{ gap: 8, minWidth: 0 }}>
        <Value s={s} size="compact" level={level} tx={tx} color={s.missing ? V("dash") : V("ink")} />
        <span style={{ fontWeight: 500, fontSize: 11.5, color: TV(s.tone, "t"), whiteSpace: "nowrap", fontVariantNumeric: "tabular-nums", transition: tx([["color", 500, "ease"]]) }}>{s.dl.short}</span>
      </span>
      {!onOpen && <span className="sr-only">{`${s.dl.text}. ${s.dl.verdict}.`}</span>}
    </Box>
  );
}

const VIEWS = { hero: HeroView, default: DefaultView, compact: CompactView } as const;

function rootStyle(extra?: React.CSSProperties): React.CSSProperties {
  return { ...GLASS_ROOT_STYLE, color: V("ink"), containerType: "inline-size", containerName: "gsc", ...extra };
}

// ---------------------------------------------------------------------------------------------
// GlassStatCard
// ---------------------------------------------------------------------------------------------

/** One KPI tile in any size. Pass onOpen to make it a button (selected drives aria-expanded). */
export function GlassStatCard({
  label,
  value,
  previous,
  unit,
  format,
  suffix,
  locale,
  currency,
  formatDate,
  labels,
  goodDirection,
  periodLabel,
  periodName,
  trend,
  goal,
  updatedAt,
  trackingSince,
  staleAfterMs,
  now,
  motion = "full",
  theme = "system",
  layout = "auto",
  className,
  style,
  ref,
  selected,
  onOpen,
  controls,
  size = "default",
  sound = true,
  ...rest
}: GlassStatCardProps) {
  const level = useLevel(motion);
  const rootRef = React.useRef<HTMLDivElement>(null);
  const setRoot = React.useCallback(
    (el: HTMLDivElement | null) => {
      rootRef.current = el;
      if (typeof ref === "function") return ref(el);
      if (ref) ref.current = el;
    },
    [ref],
  );
  const seen = useSeenOnce(rootRef);
  const clock = useNow(now);
  const hydrated = useHydrated();
  const L = React.useMemo(() => mergeLabels(defaultGlassStatCardsLabels, labels), [labels]);
  const s = statModel(
    { label, value, previous, unit, format, suffix, locale, currency, goodDirection, periodLabel, periodName, trend, goal, updatedAt, trackingSince, staleAfterMs },
    clock,
    hydrated,
    { L, formatDate: formatDate ?? formatGlassStatDate },
  );
  const tx = mkTx(level);
  const View = VIEWS[size] ?? DefaultView;
  const intro: Intro = { shown: seen || level === "off", delay: 0 };
  return (
    <div
      {...pickRootAttrs(rest)}
      {...glassSoundRootAttrs(sound)}
      ref={setRoot}
      data-lumen={theme}
      data-gsc-layout={layout}
      data-gsc-motion={level}
      className={cn("relative w-full", className)}
      style={rootStyle(style)}
    >
      <style href="beautiful-ui-lumen-stat-cards" precedence="medium">
        {STAT_CSS}
      </style>
      <View s={s} level={level} tx={tx} intro={intro} selected={selected} expanded={selected} controls={onOpen ? controls : undefined} onOpen={onOpen ? () => onOpen() : undefined} />
    </div>
  );
}

// ---------------------------------------------------------------------------------------------
// GlassStatCards
// ---------------------------------------------------------------------------------------------

const STAGGER = 60;
const TAB_W = 58;

/** The KPI row: headline, period tabs, hero, default and compact cards, and the drill-in panel. */
export function GlassStatCards({
  metrics,
  periods = defaultGlassStatPeriods,
  period,
  defaultPeriod,
  onPeriodChange,
  hero,
  sizes = ["hero", "default", "compact"],
  eyebrow = "WORKSPACE · PULSE",
  openId: openIdProp,
  defaultOpenId = null,
  onOpenChange,
  formatValue,
  locale,
  currency,
  formatDate,
  labels,
  now,
  motion = "full",
  layout = "auto",
  theme = "system",
  sound = true,
  className,
  style,
  ref,
  ...rest
}: GlassStatCardsProps) {
  const level = useLevel(motion);
  const sfx = useGlassSound(sound);
  const L = React.useMemo(() => mergeLabels(defaultGlassStatCardsLabels, labels), [labels]);
  const ctx: StatCtx = { L, formatDate: formatDate ?? formatGlassStatDate };
  const tx = mkTx(level);
  const list = React.useMemo(() => (Array.isArray(metrics) ? metrics : defaultGlassStatMetrics).filter((m) => m && typeof m.id === "string"), [metrics]);
  // The designed data carries its own clock, so it always reads "Updated 2 min ago".
  const clock = useNow(now ?? (metrics === undefined ? GLASS_STAT_DEMO_NOW : undefined));
  const hydrated = useHydrated();
  const pers = React.useMemo(
    () =>
      (Array.isArray(periods) && periods.length ? periods : [...new Set(list.flatMap((m) => Object.keys(m.data ?? {})))])
        .map((p) => (typeof p === "string" ? { id: p } : p))
        .filter((p): p is GlassStatPeriod => !!p && typeof p.id === "string")
        .map((p) => ({ id: p.id, label: p.label ?? p.id, name: p.name ?? L.periodName(p.label ?? p.id) })),
    [periods, list, L],
  );
  const [inner, setInner] = React.useState(() => defaultPeriod ?? (pers.some((p) => p.id === "30D") ? "30D" : pers[0]?.id ?? ""));
  const want = period ?? inner;
  const cur = pers.find((p) => p.id === want) ?? pers[0] ?? { id: "", label: "", name: "" };
  const pi = Math.max(0, pers.indexOf(cur as (typeof pers)[number]));

  const rootRef = React.useRef<HTMLDivElement>(null);
  const setRoot = React.useCallback(
    (el: HTMLDivElement | null) => {
      rootRef.current = el;
      if (typeof ref === "function") return ref(el);
      if (ref) ref.current = el;
    },
    [ref],
  );
  const seen = useSeenOnce(rootRef);
  const shown = seen || level === "off";
  // The entrance staggers cards (and their cells) once; later period switches ripple together.
  const [introDone, setIntroDone] = React.useState(false);
  React.useEffect(() => {
    if (!shown || introDone) return;
    const t = window.setTimeout(() => setIntroDone(true), 1800);
    return () => window.clearTimeout(t);
  }, [shown, introDone]);

  const statsFor = (per: (typeof pers)[number]) =>
    list.map((m) => {
      const d = m.data?.[per.id];
      return {
        m,
        s: statModel(
          {
            label: m.label,
            value: d?.value ?? null,
            previous: d?.previous,
            unit: m.unit,
            format: m.format ?? (formatValue ? (v: number) => formatValue(v, m) : undefined),
            suffix: m.suffix,
            locale,
            currency,
            goodDirection: m.goodDirection,
            periodLabel: per.label,
            periodName: per.name,
            trend: d?.trend,
            goal: d?.goal,
            updatedAt: d?.updatedAt ?? m.updatedAt,
            trackingSince: m.trackingSince,
            staleAfterMs: m.staleAfterMs,
          },
          clock,
          hydrated,
          ctx,
        ),
      };
    });
  const stats = statsFor(cur);
  const nameOf = (m: GlassStatMetric) => m.name ?? sentenceCase(m.label);
  const headFor = (st: typeof stats, per: (typeof pers)[number]) => glassStatHeadline(st.map((x) => ({ name: nameOf(x.m), tone: x.s.tone, missing: x.s.missing })), per.name, L);
  const [headA, headB] = headFor(stats, cur);

  const heroId = hero === null ? null : (hero ?? (list.find((m) => Object.values(m.data ?? {}).some((d) => isNum(d?.goal))) ?? list[0])?.id ?? null);
  const heroStat = heroId != null ? stats.find((x) => x.m.id === heroId) : undefined;
  const show = (k: GlassStatSize) => sizes.includes(k);

  // --- drill-in ---
  const [innerOpen, setInnerOpen] = React.useState<string | null>(defaultOpenId);
  const controlledOpen = openIdProp !== undefined;
  const wanted = controlledOpen ? openIdProp : innerOpen;
  // An id that is not (or no longer) in the data reads as closed.
  const openId = wanted != null && list.some((m) => m.id === wanted) ? wanted : null;
  const setOpenId = (id: string | null) => {
    if (!controlledOpen) setInnerOpen(id);
  };
  const [lastId, setLastId] = React.useState<string | null>(null);
  if (openId && openId !== lastId) setLastId(openId);
  // The open metric went away with new data: forget it (adjusted during render).
  if (!controlledOpen && innerOpen && !openId) setInnerOpen(null);
  const [armed, setArmed] = React.useState(false);
  if (!openId && armed) setArmed(false);
  React.useEffect(() => {
    if (!openId) return;
    // Two frames: the cells render empty first, so they ripple in.
    let r2 = 0;
    const r1 = requestAnimationFrame(() => (r2 = requestAnimationFrame(() => setArmed(true))));
    return () => {
      cancelAnimationFrame(r1);
      cancelAnimationFrame(r2);
    };
  }, [openId]);
  const opener = React.useRef<HTMLElement | null>(null);
  const panelRef = React.useRef<HTMLDivElement>(null);
  const wantFocus = React.useRef(false);
  const uid = React.useId();
  const panelId = `${uid}-drill`;
  const titleId = `${uid}-drill-title`;
  const toggle = (id: string, el: HTMLElement) => {
    const next = openId === id ? null : id;
    opener.current = el;
    wantFocus.current = next != null;
    setOpenId(next);
    onOpenChange?.(next);
  };
  const close = () => {
    if (!openId) return;
    setOpenId(null);
    onOpenChange?.(null);
    const el = opener.current;
    if (el?.isConnected) el.focus();
  };
  React.useEffect(() => {
    if (!openId || !wantFocus.current) return;
    wantFocus.current = false;
    const p = panelRef.current;
    if (!p) return;
    p.focus({ preventScroll: true });
    // Once grown, bring the panel into view if it opened below the fold.
    const instant = level === "off" || level === "reduced";
    const t = window.setTimeout(
      () => {
        if (p.isConnected && p.getBoundingClientRect().bottom > window.innerHeight) p.scrollIntoView({ block: "nearest", behavior: instant ? "auto" : "smooth" });
      },
      instant ? 0 : 620,
    );
    return () => window.clearTimeout(t);
  }, [openId, level]);
  const drillStat = stats.find((x) => x.m.id === (openId ?? lastId));

  // --- period ---
  const [said, setSaid] = React.useState("");
  const pick = (i: number) => {
    const p = pers[i];
    if (!p || p.id === cur.id) return;
    if (period === undefined) setInner(p.id);
    onPeriodChange?.(p.id);
    const [a, b] = headFor(statsFor(p), p);
    setSaid(`${cap1(p.name)}. ${a} ${b}`);
  };
  const tabW = Math.max(TAB_W, ...pers.map((p) => Math.ceil(p.label.length * 7.4 + 22)));
  const onTabKey = (ev: React.KeyboardEvent<HTMLDivElement>) => {
    const n = pers.length;
    if (!n) return;
    let k = -1;
    if (ev.key === "ArrowRight") k = (pi + 1) % n;
    else if (ev.key === "ArrowLeft") k = (pi + n - 1) % n;
    else if (ev.key === "Home") k = 0;
    else if (ev.key === "End") k = n - 1;
    if (k < 0) return;
    ev.preventDefault();
    if (k !== pi) sfx.play("select", { index: k });
    pick(k);
    ev.currentTarget.querySelectorAll<HTMLElement>("[role=tab]")[k]?.focus();
  };

  const { "aria-label": ariaLabel, ...rootRest } = rest;

  // Entrance order: hero, then the row, then the compact row, 60ms apart.
  let order = 0;
  const introAt = (): Intro => ({ shown, delay: introDone ? 0 : order++ * STAGGER });

  return (
    <div
      {...pickRootAttrs(rootRest)}
      {...glassSoundRootAttrs(sound)}
      ref={setRoot}
      data-lumen={theme}
      data-gsc-layout={layout}
      data-gsc-motion={level}
      className={cn("relative w-full max-w-[var(--glass-max-width,1120px)]", className)}
      style={rootStyle(style)}
      onKeyDown={(e) => {
        rootRest.onKeyDown?.(e);
        if (e.defaultPrevented) return;
        if (e.key === "Escape" && openId) {
          e.preventDefault();
          sfx.play("close");
          close();
        }
      }}
    >
      <style href="beautiful-ui-lumen-stat-cards" precedence="medium">
        {STAT_CSS}
      </style>
      <section
        aria-label={ariaLabel ?? eyebrow}
        data-gsc-shell=""
        className="relative flex flex-col"
        style={{ boxSizing: "border-box", padding: 28, gap: 22, borderRadius: 30, background: V("body"), backdropFilter: "blur(24px)", WebkitBackdropFilter: "blur(24px)", boxShadow: V("shadow") }}
      >
        <span
          aria-hidden
          className="pointer-events-none absolute inset-0"
          style={{ borderRadius: 30, padding: 1, background: V("rim"), WebkitMask: "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)", WebkitMaskComposite: "xor", maskComposite: "exclude" }}
        />

        {/* Header */}
        <div className="relative flex flex-wrap items-start justify-between" style={{ gap: "16px 24px" }}>
          <div className="flex flex-col" style={{ gap: 10, flex: "1 1 420px", minWidth: 0 }}>
            <div className="flex items-center" style={mono(500, 10.5, { gap: 9, letterSpacing: ".14em", color: V("eyebrow"), whiteSpace: "nowrap", minWidth: 0 })}>
              <span aria-hidden style={{ flex: "none", width: 6, height: 6, borderRadius: "50%", background: V("live"), boxShadow: `0 0 0 3px ${V("liveRing")}, 0 0 10px ${V("live")}` }} />
              <span style={{ overflow: "hidden", textOverflow: "ellipsis" }}>{eyebrow}</span>
            </div>
            <h2 style={{ margin: 0, fontWeight: 600, fontSize: 26, lineHeight: 1.25, letterSpacing: "-.02em", color: V("ink"), textWrap: "pretty" }}>
              {headA}
              {headB && (
                <>
                  {" "}
                  <span style={{ color: V("headMuted") }}>{headB}</span>
                </>
              )}
            </h2>
          </div>
          {pers.length > 1 && (
            // Tabs keep their designed 58px and share the width when a narrow container can't fit them.
            <div
              role="tablist"
              aria-label={L.periodTabs}
              onKeyDown={onTabKey}
              className="relative grid"
              style={{ flex: "0 1 auto", minWidth: 0, maxWidth: "100%", gridTemplateColumns: `repeat(${pers.length}, minmax(0, ${tabW}px))`, padding: 3, borderRadius: 18, background: V("well"), boxShadow: V("wellShadow") }}
            >
              <span
                aria-hidden
                className="pointer-events-none absolute"
                style={{ left: 3, top: 3, width: `calc((100% - 6px) / ${pers.length})`, height: 30, borderRadius: 15, background: V("thumb"), boxShadow: V("thumbShadow"), transform: `translateX(${pi * 100}%)`, transition: tx([["transform", 600]]) }}
              />
              {pers.map((p, i) => (
                <button
                  key={p.id}
                  type="button"
                  role="tab"
                  id={`${uid}-tab-${i}`}
                  aria-controls={`${uid}-body`}
                  data-gsc-focus=""
                  aria-selected={i === pi}
                  tabIndex={i === pi ? 0 : -1}
                  data-sound="select"
                  data-sound-index={i}
                  onClick={() => pick(i)}
                  style={{
                    position: "relative",
                    minWidth: 0,
                    height: 30,
                    padding: 0,
                    border: "none",
                    margin: 0,
                    background: "none",
                    borderRadius: 15,
                    fontFamily: "inherit",
                    fontWeight: 500,
                    fontSize: 12.5,
                    lineHeight: "normal",
                    whiteSpace: "nowrap",
                    color: i === pi ? V("tabOn") : V("tabOff"),
                    cursor: "pointer",
                    transition: tx([["color", 300, "ease"]]),
                  }}
                >
                  {p.label}
                </button>
              ))}
            </div>
          )}
        </div>

        {/* The cards: the tabs' panel (same flex column and gap as the shell) */}
        <div id={`${uid}-body`} role={pers.length > 1 ? "tabpanel" : undefined} aria-labelledby={pers.length > 1 ? `${uid}-tab-${pi}` : undefined} data-gsc-body="" className="relative flex flex-col" style={{ gap: 22 }}>
          {/* Hero */}
          {show("hero") && heroStat && (
            <HeroView s={heroStat.s} level={level} tx={tx} intro={introAt()} expanded={openId === heroStat.m.id} controls={panelId} onOpen={(el) => toggle(heroStat.m.id, el)} />
          )}

          {/* Default row */}
          {show("default") && stats.length > 0 && (
            <div data-gsc-grid="row" className="relative grid" style={{ gap: 12 }}>
              {stats.map(({ m, s }) => (
                <DefaultView key={m.id} s={s} level={level} tx={tx} intro={introAt()} selected={openId === m.id} expanded={openId === m.id} controls={panelId} onOpen={(el) => toggle(m.id, el)} />
              ))}
            </div>
          )}

          {/* Drill-in */}
          <div
            style={{
              display: "grid",
              gridTemplateRows: openId ? "1fr" : "0fr",
              opacity: openId ? 1 : 0,
              marginTop: -10,
              transition: tx([
                ["grid-template-rows", 600],
                ["opacity", 400, "ease"],
              ]),
            }}
          >
            <div style={{ overflow: "hidden", minHeight: 0 }}>
              <div
                ref={panelRef}
                id={panelId}
                role="region"
                aria-labelledby={titleId}
                tabIndex={-1}
                data-gsc-panel=""
                inert={!openId}
                style={{ marginTop: 10 }}
              >
                {drillStat && <Drill s={drillStat.s} name={nameOf(drillStat.m)} live={!!openId && (armed || level === "off" || level === "reduced")} tx={tx} titleId={titleId} onClose={close} />}
              </div>
            </div>
          </div>

          {/* Compact row */}
          {show("compact") && stats.length > 0 && (
            <div data-gsc-grid="compact" className="relative grid" style={{ gap: 8 }}>
              {stats.map(({ m, s }) => (
                <CompactView key={m.id} s={s} level={level} tx={tx} intro={introAt()} expanded={openId === m.id} controls={panelId} onOpen={(el) => toggle(m.id, el)} />
              ))}
            </div>
          )}

        </div>

        <span className="sr-only" aria-live="polite">
          {said}
        </span>
      </section>
    </div>
  );
}

/** The drill-in: 12 intervals as 12-row cell columns, the first and last labelled (hover a column for its value). */
function Drill({ s, name, live, tx, titleId, onClose }: { s: Stat; name: string; live: boolean; tx: Tx; titleId: string; onClose: () => void }) {
  const t = s.trend;
  const n = t ? glassStatCells(t, 12) : [];
  const firstI = t ? t.findIndex((v) => v != null) : -1;
  const count = t ? t.length - firstI : 0;
  return (
    <div
      data-gsc-drill-body=""
      className="flex flex-col"
      style={{ gap: 16, padding: "20px 22px 18px", borderRadius: 20, background: V("drill"), boxShadow: V("drillRing"), boxSizing: "border-box" }}
    >
      <div className="flex items-center justify-between" style={{ gap: 12 }}>
        <span id={titleId} style={{ fontWeight: 500, fontSize: 13.5, color: V("ink"), minWidth: 0 }}>
          {name} <span style={{ color: V("drillMuted") }}>{`· ${s.perName}, ${s.L.intervals(count)}`}</span>
        </span>
        <button
          type="button"
          data-gsc-focus=""
          data-gsc-close=""
          data-sound="close"
          aria-label={s.L.closeDetails}
          onClick={onClose}
          style={{
            width: 28,
            height: 28,
            flex: "none",
            padding: 0,
            border: "none",
            margin: 0,
            borderRadius: 14,
            background: V("close"),
            color: V("closeFg"),
            fontFamily: "inherit",
            fontSize: 15,
            lineHeight: 1,
            cursor: "pointer",
            transition: tx([["background", 200, "ease"]]),
          }}
        >
          ×
        </button>
      </div>
      {t ? (
        <>
          <div data-gsc-drill-cols="" aria-hidden className="grid" style={{ gridTemplateColumns: "repeat(12, minmax(0,1fr))", gap: 6, alignItems: "end", height: 132 }}>
            {t.map((v, i) => {
              const last = i === t.length - 1;
              const first = i === firstI;
              return (
                <div key={i} data-gsc-col={v != null ? "" : undefined} data-gsc-first={first ? "" : undefined} data-gsc-last={last ? "" : undefined} className="flex flex-col items-center" style={{ gap: 6, minWidth: 0 }}>
                  <span
                    data-gsc-colv=""
                    style={mono(500, 10, { color: last ? TV(s.tone, "t") : V("drillVal"), whiteSpace: "nowrap", opacity: first || last ? 1 : 0, transition: tx([["opacity", 200, "ease"]]) })}
                  >
                    {v != null ? formatGlassStatValue(v, s.unit, s.fo) : NBSP}
                  </span>
                  <div className="flex flex-col-reverse" style={{ width: "100%", maxWidth: 26, gap: 2 }}>
                    {Array.from({ length: 12 }, (_, j) => (
                      <span
                        key={j}
                        style={{
                          height: 7,
                          borderRadius: 2,
                          background: last ? TV(s.tone, "f") : `rgba(${V("inkRgb")},${(0.14 + i * 0.018).toFixed(3)})`,
                          opacity: live && j < n[i] ? 1 : 0,
                          transition: tx([
                            ["opacity", 300, "ease", i * 30 + j * 20],
                            ["background", 500, "ease"],
                          ]),
                        }}
                      />
                    ))}
                  </div>
                  <span style={mono(400, 10, { color: V("drillX") })}>{v == null ? NBSP : last ? s.L.now : String(i - firstI + 1)}</span>
                </div>
              );
            })}
          </div>
          <ol className="sr-only">
            {t.map((v, i) => (v != null ? <li key={i}>{`${i === t.length - 1 ? s.L.now : s.L.interval(i - firstI + 1)}: ${formatGlassStatValue(v, s.unit, s.fo)}`}</li> : null))}
          </ol>
        </>
      ) : (
        <div style={mono(400, 11, { color: V("drillX"), padding: "8px 0 4px" })}>{s.L.noIntervals}</div>
      )}
    </div>
  );
}

export default GlassStatCards;
```

### 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>
  );
}
```
