# Customer Swarm (Lumen Halo): prompt.md (v1.0.0)

- id: `lumen-customer-swarm` · version 1.0.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/lumen-customer-swarm.json`
- npm dependencies: none
- registry dependencies: utils, @beautiful-ui/lumen-chart, @beautiful-ui/lumen-stat, https://beautiful-ui.dev/r/lumen-foundation.json
- docs: https://beautiful-ui.dev/components/lumen-customer-swarm
- The install command carries everything this item needs (files, CSS, tokens, npm and registry dependencies). Prefer it to copying source by hand.

Every customer on one axis, so you can see where the revenue really sits. Tiles stack where each customer lands, lanes split by plan or region, and a drag across the axis tells you which plan earns more than its share. In parts, with the finished card as the recipe.

## Build it
- Stack: React 19 (`ref` is a plain prop), TypeScript, Tailwind CSS v4 utilities, a shadcn-initialised project with the `@/*` alias.
- Packages: none beyond React.
- Files: `components/ui/lumen/customer-swarm.tsx`; shared code: `lib/beautiful-ui/lumen/customer-swarm-model.ts`, `lib/beautiful-ui/lumen/customer-swarm-sample.ts`, `lib/beautiful-ui/lumen/stat-format.ts`, `lib/beautiful-ui/lumen/chart-config.ts`, `lib/beautiful-ui/lumen/sound.ts`.
- Registry dependencies, installed with it automatically: shadcn `utils` (cn), `lumen-chart`, `lumen-stat`, `lumen-foundation`.
- Builds: one set of files for both, but its dependencies come in Base UI and Radix builds. Install the one that matches the project (see Install): a free item's bare URL installs the Base UI build of it and its dependencies.
- Exports to keep: `CustomerSwarm`, `CustomerSwarmPlot`, `CustomerSwarmMetrics / CustomerSwarmGroups`, `CustomerSwarmInsight`, `CustomerSwarmLegend`, `CustomerSwarmSummary / CustomerSwarmSplit`, `CustomerSwarmKey`, `CustomerSwarmTable`, `CustomerSwarmCard`, `useCustomerSwarm`, and every exported type.
- CSS: the install merges this item's rules (the registry `css` field) into your global stylesheet, in `@layer components`, and adds the lumen foundation (tokens, keyframes, motion levels) once. Nothing to import by hand.
- Re-running `add` (or `--overwrite`) re-applies those rules: put overrides in your own CSS, never in the installed rules.
- Tokens: retheme with the `--lumen-*` custom properties (`--lumen-accent`, `--lumen-accent-text`, `--lumen-bad`, `--lumen-bad-text`, `--lumen-focus`, `--lumen-good`, `--lumen-good-text`, `--lumen-hairline`, `--lumen-ink`, `--lumen-muted-ink`, `--lumen-series-1`, `--lumen-series-2`, `--lumen-series-3`, `--lumen-series-4`, `--lumen-series-5`, `--lumen-series-6`, `--lumen-warn`, `--lumen-warn-text`); this item's CSS also reads `--lumen-font-mono`, `--lumen-font-sans`, `--lumen-radius-k`. Never add Tailwind colour classes inside the component.

```tsx
import { CustomerSwarm, CustomerSwarmInsight, CustomerSwarmMetrics, CustomerSwarmPlot } from "@/components/ui/lumen/customer-swarm";
```

## Parts

| Part | data-slot | What it is for |
|---|---|---|
| `CustomerSwarm` | — | Holds data, metric, grouping, range, hidden plans and hover. |
| `CustomerSwarmPlot` | `customer-swarm-plot` | Tiles, lanes, brackets, brush, crosshair, tooltip. |
| `CustomerSwarmMetrics / CustomerSwarmGroups` | — | The metric and grouping switches. |
| `CustomerSwarmInsight` | — | The sentence the selection tells. |
| `CustomerSwarmLegend` | `chart-legend` | Plan chips with counts; hide or show. |
| `CustomerSwarmSummary / CustomerSwarmSplit` | `customer-swarm-summary` | Count and revenue; the split bars. |
| `CustomerSwarmKey` | `customer-swarm-key` | Bracket key, hint, scale, Clear. |
| `CustomerSwarmTable` | — | Every customer, on demand. |
| `CustomerSwarmCard` | — | The recipe on the sample. |
| `useCustomerSwarm` | — | The state, for parts of your own. |

Style a part with `[data-slot="<slot>"]` selectors or its `className`; keep the attributes when editing.

## 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.

## Match the original
- Read `components/ui/lumen/customer-swarm.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
- customer distribution, revenue concentration, MRR by plan, seats, tenure, beeswarm, dot plot, segment comparison, story chart, Lumen
- Where the revenue sits: a few big accounts or a long tail, per plan or region
- Comparing segments on one metric with their middle half and median
- Letting people select a range ("customers paying $1K–$10K") and read the mix

### Not when
- Trends over time: use a trend chart
- More than a few thousand customers: tiles overlap into a solid bar

## Mistakes
- plan must match a config key
- Log scales need domain[0] > 0
- Pass numbers, not formatted strings, in metric fields

## Usage

```tsx
import { CustomerSwarm, CustomerSwarmInsight, CustomerSwarmMetrics, CustomerSwarmPlot } from "@/components/ui/lumen/customer-swarm";

<CustomerSwarm customers={accounts} config={{ pro: { label: "Pro" }, team: { label: "Team" } }} metrics={[{ key: "mrr", label: "MRR", scale: "log", domain: [10, 50000], format: { style: "currency" } }]}>
  <CustomerSwarmInsight />
  <CustomerSwarmMetrics />
  <CustomerSwarmPlot />
</CustomerSwarm>

// Or the finished card, on the sample data:
<CustomerSwarmCard />
```

## Props

| Prop | Type | Default | What it does |
|---|---|---|---|
| `CustomerSwarm customers` | `{ id; name; plan; region?; [metric]: number }[]` |  | plan is a config key; metrics read numeric fields by key. |
| `config` | `ChartConfig` |  | Plans in lane order, with labels and colours (shadcn's shape). |
| `metrics` | `{ key; label; scale?; domain; ticks?; format? }[]` |  | One tab each. format is a serialisable StatFormat; log scales need a positive minimum. |
| `metric / group / selection / hiddenPlans` | `controlled or default*` |  | Each with an on…Change. group is "all" \| "plan" \| "region"; selection is [low, high] in metric units. |
| `revenueKey / revenueFormat` | `string / StatFormat` |  | What the totals, lanes and split bars sum, and how they read. |
| `onCustomerClick / onCustomerHover` | `(id) => void` |  | A tile clicked or Enter pressed; the hovered or focused customer changed. |
| `labels / motion` | `Partial<CustomerSwarmLabels> / "full" \| "subtle" \| "off"` |  | Every word ({placeholders}); the intro rain plays only at full. |

Full docs: https://beautiful-ui.dev/components/lumen-customer-swarm

## Customising
- Colours: set the `--lumen-*` tokens on `:root`, or on a container with the `lumen-scope` class to retheme one area. Add the `lumen-inherit` class to follow your shadcn palette instead (`--chart-N`, `--destructive`).
- Dark mode follows the `.dark` class on an ancestor (the shadcn and next-themes convention).
- 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 |
|---|---|
| ← → | Previous or next customer along the axis |
| Shift + ← → | Select a range |
| Enter | Open the customer (onCustomerClick) |
| Escape | Clear the range |

## Motion inventory

| Interaction | What moves |
|---|---|
| First view | Tiles rain into their stacks, left to right |
| Metric or grouping change | Every tile glides to its new place; brackets follow |
| Brush | Tiles outside the range dim; counts and split bars update |
| Subtle, off, reduced motion | No rain; glides on the calm curve or none |

## Accessibility contract (preserve when editing)
- The plot is one tab stop: ← → walk customers with the tooltip, Shift extends a range, Enter opens, Escape clears
- The insight is announced once the view settles; lanes and chips speak their counts
- Every customer is in a table behind "View as table"
- Plan chips say shown or hidden; the last visible plan can't hide

## Install

Base UI project (a base-* style in components.json):

```bash
npx shadcn@latest add https://beautiful-ui.dev/r/lumen-customer-swarm.json
```

Radix project (a radix-*, new-york or default style):

```bash
npx shadcn@latest add https://beautiful-ui.dev/r/radix-nova/lumen-customer-swarm.json
```

Or add the `@beautiful-ui` registry to components.json and run `npx shadcn@latest add @beautiful-ui/lumen-customer-swarm`: the CLI picks the build from your style.

## Source (Base UI build)

### components/ui/lumen/customer-swarm.tsx

```tsx
"use client";

/**
 * Customer Swarm (Lumen Halo) v1.0.0 · Beautiful UI
 * Docs: https://beautiful-ui.dev/components/lumen-customer-swarm · Agent prompt: https://beautiful-ui.dev/md/lumen-customer-swarm.md
 * Licensed to the purchaser under the Beautiful UI license: https://beautiful-ui.dev/license
 */

import * as React from "react";
import { cn } from "@/lib/utils";
import { ChartContainer, ChartDescription, ChartEyebrow, ChartHeader, ChartLive, ChartSurface, ChartTable, ChartTitle } from "@/components/ui/lumen/chart";
import { ChartMode, ChartModes, ChartTooltip, useChartSelection } from "@/components/ui/lumen/chart-client";
import { StatValue } from "@/components/ui/lumen/stat";
import { chartKey, type ChartConfig } from "@/lib/beautiful-ui/lumen/chart-config";
import {
  SWARM,
  customerSwarmLabels,
  normalizeSwarmSelection,
  quantile,
  swarmFormat,
  swarmInsight,
  swarmLaneKeys,
  swarmLayout,
  valueOf,
  type CustomerSwarmLabels,
  type SwarmCustomer,
  type SwarmGroup,
  type SwarmMetric,
  type SwarmSelection,
} from "@/lib/beautiful-ui/lumen/customer-swarm-model";
import { sampleSwarmConfig, sampleSwarmCustomers, sampleSwarmMetrics, sampleSwarmRegions } from "@/lib/beautiful-ui/lumen/customer-swarm-sample";
import { formatStatValue } from "@/lib/beautiful-ui/lumen/stat-format";
import { emitSound, useMergedRef } from "@/lib/beautiful-ui/lumen/sound";

/*
 * Lumen Customer Swarm. Every customer is a tile on one metric's axis, stacked where they land, in
 * lanes by plan or region; each lane shows its middle half and median. Drag across the axis to
 * select a range and the insight line says which plan carries more revenue than its share of
 * customers. In parts: CustomerSwarm holds the data and state; the Plot, Metrics, Groups, Insight,
 * Legend, Summary, Split, Key and Table read it. CustomerSwarmCard is the recipe on the sample.
 */

const fillT = (t: string, v: Record<string, string | number>) => t.replace(/\{(\w+)\}/g, (m, k: string) => (k in v ? String(v[k]) : m));
/** A plan's colour: the config's --color-<plan>, else the family series for its position. */
/**
 * list[i] for an index the component took from that same list (or from a list built one-for-one
 * from it, like the layout's positions): selected, lanes' members, the keyboard order, the hover.
 */
const inBounds = <T,>(list: readonly T[], i: number): T => list[i]!;

const planColor = (key: string, i: number) => `var(--color-${chartKey(key)}, var(--lg-c-series-${(i % 6) + 1}))`;

type Motion = "full" | "subtle" | "off";
type Phase = "intro" | "in" | "live";

type Ctx = {
  customers: readonly SwarmCustomer[];
  plans: { key: string; label: string; color: string }[];
  regions: string[];
  metrics: SwarmMetric[];
  metric: SwarmMetric;
  metricIndex: number;
  setMetric: (key: string) => void;
  group: SwarmGroup;
  setGroup: (g: SwarmGroup) => void;
  hidden: ReadonlySet<string>;
  togglePlan: (key: string) => void;
  selection: SwarmSelection | null;
  commit: (r: SwarmSelection | null) => void;
  draft: SwarmSelection | null;
  setDraft: (r: SwarmSelection | null) => void;
  hoverId: string | null;
  setHoverId: (id: string | null) => void;
  layout: ReturnType<typeof swarmLayout>;
  laneKeys: string[];
  size: { W: number; PH: number };
  setSize: React.Dispatch<React.SetStateAction<{ W: number; PH: number }>>;
  /** Selected (in brush and shown) customer indexes. */
  selected: number[];
  revenueKey: string;
  revenueFormat: (v: number) => string;
  revenueOf: (c: SwarmCustomer) => number;
  insight: string;
  L: CustomerSwarmLabels;
  title: string;
  phase: Phase;
  quiet: boolean;
  onCustomerClick?: ((id: string) => void) | undefined;
};

const SwarmContext = React.createContext<Ctx | null>(null);

/** The chart's state, for parts of your own inside <CustomerSwarm>. */
export function useCustomerSwarm() {
  const c = React.useContext(SwarmContext);
  if (!c) throw new Error("Customer Swarm parts must be inside <CustomerSwarm>.");
  return c;
}

const reducedQuery = () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

export type CustomerSwarmProps = Omit<React.ComponentProps<"div">, "title"> & {
  customers: readonly SwarmCustomer[];
  /** Plans by key (the customers' `plan`), with labels and colours, in lane order. */
  config: ChartConfig;
  metrics: SwarmMetric[];
  /** Region lanes in order. Default: the order they appear in the data. */
  regions?: string[] | undefined;
  /** The field the revenue totals, lanes and split bars sum. Default: the first metric's key. */
  revenueKey?: string | undefined;
  /** How revenue totals read. Default: compact currency. */
  revenueFormat?: Parameters<typeof formatStatValue>[1] | undefined;
  title?: string | undefined;
  labels?: Partial<CustomerSwarmLabels> | undefined;
  metric?: string | undefined;
  defaultMetric?: string | undefined;
  onMetricChange?: ((key: string) => void) | undefined;
  group?: SwarmGroup | undefined;
  defaultGroup?: SwarmGroup | undefined;
  onGroupChange?: ((group: SwarmGroup) => void) | undefined;
  selection?: SwarmSelection | null | undefined;
  defaultSelection?: SwarmSelection | null | undefined;
  /** A drag or Shift+arrow selection ended, or it was cleared (null). */
  onSelectionChange?: ((range: SwarmSelection | null, customers: SwarmCustomer[]) => void) | undefined;
  /** Hidden plans, by key. At least one plan always stays visible. */
  hiddenPlans?: string[] | undefined;
  defaultHiddenPlans?: string[] | undefined;
  onHiddenPlansChange?: ((hidden: string[]) => void) | undefined;
  onCustomerClick?: ((id: string) => void) | undefined;
  onCustomerHover?: ((id: string | null) => void) | undefined;
  /** "full" (default) · "subtle" · "off". Reduced motion is always respected. */
  motion?: Motion | undefined;
};

export function CustomerSwarm({
  customers,
  config,
  metrics,
  regions: regionsProp,
  revenueKey: revenueKeyProp,
  revenueFormat,
  title = "Customer swarm",
  labels,
  metric: metricProp,
  defaultMetric,
  onMetricChange,
  group: groupProp,
  defaultGroup = "region",
  onGroupChange,
  selection: selectionProp,
  defaultSelection = null,
  onSelectionChange,
  hiddenPlans: hiddenProp,
  defaultHiddenPlans = [],
  onHiddenPlansChange,
  onCustomerClick,
  onCustomerHover,
  motion = "full",
  className,
  children,
  ref,
  onKeyDown,
  ...props
}: CustomerSwarmProps) {
  const L = React.useMemo(() => ({ ...customerSwarmLabels, ...labels }), [labels]);
  const plans = React.useMemo(() => Object.entries(config).map(([key, c], i) => ({ key, label: c.label ?? key, color: planColor(key, i) })), [config]);
  const planKeys = React.useMemo(() => plans.map((p) => p.key), [plans]);
  const regions = React.useMemo(() => swarmLaneKeys(customers, "region", [], regionsProp), [regionsProp, customers]);
  const safeMetrics = metrics.length ? metrics : [{ key: "value", label: "Value", domain: [0, 1] as [number, number] }];
  const [metricKey, setMetricKey] = useChartSelection<string>({ value: metricProp, defaultValue: defaultMetric ?? inBounds(safeMetrics, 0).key, onValueChange: onMetricChange });
  const metricIndex = Math.max(0, safeMetrics.findIndex((m) => m.key === metricKey));
  const metric = inBounds(safeMetrics, metricIndex);
  const [group, setGroupRaw] = useChartSelection<SwarmGroup>({ value: groupProp, defaultValue: defaultGroup, onValueChange: onGroupChange });
  const [hiddenList, setHiddenList] = useChartSelection<string[]>({ value: hiddenProp, defaultValue: defaultHiddenPlans, onValueChange: onHiddenPlansChange });
  const hiddenKey = hiddenList.join("\n");
  const hidden = React.useMemo(() => {
    const set = new Set(hiddenKey ? hiddenKey.split("\n") : []);
    // At least one plan always shows, whatever comes in.
    return planKeys.length && planKeys.every((k) => set.has(k)) ? new Set<string>() : set;
  }, [hiddenKey, planKeys]);
  const [selRaw, setSelRaw] = useChartSelection<SwarmSelection | null>({ value: selectionProp, defaultValue: normalizeSwarmSelection(defaultSelection), onValueChange: undefined });
  const selection = normalizeSwarmSelection(selRaw);
  const [draft, setDraft] = React.useState<SwarmSelection | null>(null);
  const [hoverId, setHoverIdRaw] = React.useState<string | null>(null);
  const [size, setSize] = React.useState({ W: 800, PH: SWARM.PH as number });
  const revenueKey = revenueKeyProp ?? inBounds(safeMetrics, 0).key;
  const revenueOf = React.useCallback((c: SwarmCustomer) => {
    const v = valueOf(c, revenueKey);
    return Number.isFinite(v) ? v : 0;
  }, [revenueKey]);
  const fmtRev = React.useCallback((v: number) => formatStatValue(v, revenueFormat ?? { style: "currency", currency: "USD", notation: "compact" }), [revenueFormat]);

  const laneKeys = React.useMemo(() => swarmLaneKeys(customers, group, planKeys, regions), [customers, group, planKeys, regions]);
  const layout = React.useMemo(() => swarmLayout(customers, metric, laneKeys, group, planKeys, hidden, size.W, size.PH), [customers, metric, laneKeys, group, planKeys, hidden, size]);

  // The brush in px (the drag in progress, else the committed selection) and who is inside it.
  const brush = draft ?? selection;
  const px = brush ? ([layout.s.f(brush[0]), layout.s.f(brush[1])] as const) : null;
  const inBrush = (x: number) => !px || (x >= px[0] - SWARM.BW / 2 && x <= px[1] + SWARM.BW / 2);
  const selected = customers.map((_, i) => i).filter((i) => !hidden.has(inBounds(customers, i).plan) && !inBounds(layout.pos, i).gone && inBrush(inBounds(layout.pos, i).x));
  const insight = swarmInsight(selected.map((i) => inBounds(customers, i)), plans, revenueKey, !!px, L);

  const commit = (range: SwarmSelection | null) => {
    const next = normalizeSwarmSelection(range);
    if (!next && !selection) return;
    if (next && selection && next[0] === selection[0] && next[1] === selection[1]) return;
    setSelRaw(next);
    const p = next ? ([layout.s.f(next[0]), layout.s.f(next[1])] as const) : null;
    const picked = next ? customers.filter((c, i) => !hidden.has(c.plan) && !inBounds(layout.pos, i).gone && inBounds(layout.pos, i).x >= p![0] - SWARM.BW / 2 && inBounds(layout.pos, i).x <= p![1] + SWARM.BW / 2) : [];
    onSelectionChange?.(next, picked);
  };

  // A new metric makes a range in the old units meaningless: drop it.
  const [seenMetric, setSeenMetric] = React.useState(metric.key);
  if (seenMetric !== metric.key) {
    setSeenMetric(metric.key);
    setDraft(null);
    if (selection) setSelRaw(null);
  }
  const lastMetric = React.useRef(metric.key);
  const lastSelection = React.useRef(selection);
  React.useEffect(() => {
    if (lastMetric.current === metric.key) {
      lastSelection.current = selection;
      return;
    }
    lastMetric.current = metric.key;
    const had = lastSelection.current;
    lastSelection.current = null;
    if (had) onSelectionChange?.(null, []);
    // Only a metric change reports; the selection is remembered for it.
    // eslint-disable-next-line -- (Beautiful UI's lint: react-hooks/exhaustive-deps)
  }, [metric.key, selection]);

  // Hover is reported once per change, after it commits.
  const setHoverId = setHoverIdRaw;
  const onHover = React.useRef(onCustomerHover);
  React.useEffect(() => {
    onHover.current = onCustomerHover;
  });
  // The hover that still exists (a customer can leave the data under the pointer).
  const liveHover = hoverId != null && customers.some((c) => c.id === hoverId) ? hoverId : null;
  const told = React.useRef<string | null>(null);
  React.useEffect(() => {
    if (told.current === liveHover) return;
    told.current = liveHover;
    onHover.current?.(liveHover);
  }, [liveHover]);

  // The intro: tiles rain in once the chart is first on screen; subtle, off and reduced motion skip it.
  const root = React.useRef<HTMLDivElement>(null);
  const merged = useMergedRef(root, ref);
  const quiet = motion !== "full";
  // Quiet at mount: the live layout from the start, so a later switch to full never shows a hidden intro.
  const [phase, setPhase] = React.useState<Phase>(() => (quiet ? "live" : "intro"));
  React.useEffect(() => {
    const el = root.current;
    if (!el || phase !== "intro") return;
    if (reducedQuery()) {
      const t = window.setTimeout(() => setPhase("live"), 0);
      return () => window.clearTimeout(t);
    }
    let t1 = 0;
    let t2 = 0;
    const io = new IntersectionObserver(([e]) => {
      if (!e?.isIntersecting) return;
      io.disconnect();
      t1 = window.setTimeout(() => setPhase("in"), 120);
      t2 = window.setTimeout(() => setPhase("live"), 1900);
    });
    io.observe(el);
    return () => {
      io.disconnect();
      window.clearTimeout(t1);
      window.clearTimeout(t2);
    };
    // Once, on mount.
    // eslint-disable-next-line -- (Beautiful UI's lint: react-hooks/exhaustive-deps)
  }, []);

  // Announce the insight once the view settles (not on every step of a drag, not on mount).
  const [said, setSaid] = React.useState("");
  const heard = React.useRef<string | null>(null);
  React.useEffect(() => {
    if (draft) return;
    if (heard.current === null) {
      heard.current = insight;
      return;
    }
    if (heard.current === insight) return;
    const t = window.setTimeout(() => {
      heard.current = insight;
      setSaid(insight);
    }, 600);
    return () => window.clearTimeout(t);
  }, [insight, draft]);

  const ctx: Ctx = {
    customers,
    plans,
    regions,
    metrics: safeMetrics,
    metric,
    metricIndex,
    setMetric: (key) => {
      if (key === metric.key) return;
      setMetricKey(key);
      setHoverId(null);
    },
    group,
    setGroup: (g) => {
      if (g === group) return;
      setGroupRaw(g);
      setHoverId(null);
    },
    hidden,
    togglePlan: (key) => {
      const next = hidden.has(key) ? hiddenList.filter((k) => k !== key) : [...hiddenList, key];
      if (planKeys.every((k) => next.includes(k))) return; // at least one plan stays visible
      setHiddenList(next);
      setHoverId(null);
    },
    selection,
    commit,
    draft,
    setDraft,
    hoverId,
    setHoverId,
    layout,
    laneKeys,
    size,
    setSize,
    selected,
    revenueKey,
    revenueFormat: fmtRev,
    revenueOf,
    insight,
    L,
    title,
    // Subtle, off and reduced motion show the live layout from the start.
    phase: quiet ? "live" : phase,
    quiet,
    onCustomerClick,
  };
  return (
    <SwarmContext.Provider value={ctx}>
      <ChartContainer
        {...props}
        ref={merged}
        config={config}
        data-motion={motion}
        className={cn(`lumen-customer-swarm lumen-control`, className)}
        onKeyDown={(e) => {
          onKeyDown?.(e);
          // Escape anywhere inside clears the range (scoped: never a document listener).
          if (!e.defaultPrevented && e.key === "Escape" && selection) {
            emitSound(e.currentTarget, "close");
            commit(null);
          }
        }}
      >
        {children}
        <ChartLive>{said}</ChartLive>
      </ChartContainer>
    </SwarmContext.Provider>
  );
}

// ---------------------------------------------------------------------------------------------
// The plot: tiles (CSS transitions only), lanes, brackets, the brush and the crosshair
// ---------------------------------------------------------------------------------------------

export function CustomerSwarmPlot({ className, height, style, ref, ...props }: React.ComponentProps<"div"> & { /** The tiles' height in px, or any CSS length. Axis labels add 30px. Default 348. */ height?: number | string | undefined }) {
  const { customers, plans, metrics, metric, group, hidden, selection, commit, draft, setDraft, hoverId, setHoverId, layout: Lay, size, setSize, selected, revenueFormat, revenueOf, L, phase, quiet, onCustomerClick } = useCustomerSwarm();
  const { W } = size;
  // Many lanes grow the plot past the asked height (each keeps room for its label and bracket).
  const PH = Lay.PH;
  const plot = React.useRef<HTMLDivElement>(null);
  const mergedPlot = useMergedRef(plot, ref);
  const cross = React.useRef<HTMLDivElement>(null);
  const crossLabel = React.useRef<HTMLSpanElement>(null);
  const tipId = React.useId();
  const fixedPH = typeof height === "number" && Number.isFinite(height) ? Math.max(SWARM.MIN_PH, height) : null;
  const cssHeight = typeof height === "string" && height.trim() ? height : null;

  React.useLayoutEffect(() => {
    const el = plot.current;
    if (!el) return;
    const read = () => {
      const h = fixedPH ?? (cssHeight ? Math.max(SWARM.MIN_PH, Math.round(el.clientHeight) - SWARM.AXIS_H) : SWARM.PH);
      setSize((s) => {
        const w = Math.round(el.clientWidth) || s.W;
        return w === s.W && h === s.PH ? s : { W: w, PH: h };
      });
    };
    read();
    const ro = new ResizeObserver(read);
    ro.observe(el);
    return () => ro.disconnect();
  }, [fixedPH, cssHeight, setSize]);

  const idx = React.useMemo(() => new Map(customers.map((c, i) => [c.id, i])), [customers]);
  const hover = hoverId != null ? (idx.get(hoverId) ?? null) : null;
  const planIndex = React.useMemo(() => new Map(plans.map((p, i) => [p.key, i])), [plans]);
  const colorOf = (plan: string) => plans[planIndex.get(plan) ?? 0]?.color ?? "var(--lg-c-series-1)";
  const brush = draft ?? selection;
  const b = brush ? ([Lay.s.f(brush[0]), Lay.s.f(brush[1])] as [number, number]) : null;
  const clamp = (x: number) => Math.max(SWARM.PADL, Math.min(W - SWARM.PADR, x));
  const fmt = (v: number) => swarmFormat(metric, v);

  // --- pointer ---------------------------------------------------------------------------------
  const drag = React.useRef<{ anchor: number; tick: number } | null>(null);
  const kbAnchor = React.useRef<number | null>(null);
  // A cleared range or a new metric starts the next Shift selection afresh.
  React.useEffect(() => {
    if (!selection) kbAnchor.current = null;
  }, [selection]);
  React.useEffect(() => {
    kbAnchor.current = null;
  }, [metric.key]);
  const local = (e: React.PointerEvent) => {
    const r = plot.current!.getBoundingClientRect();
    return [e.clientX - r.left, e.clientY - r.top] as const;
  };
  const nearest = (x: number, y: number) => {
    let best: number | null = null;
    let bd = 49;
    customers.forEach((c, i) => {
      const p = inBounds(Lay.pos, i);
      if (p.gone || hidden.has(c.plan)) return;
      const d = (p.x - x) ** 2 + (p.y - y) ** 2;
      if (d < bd) {
        bd = d;
        best = i;
      }
    });
    return best;
  };
  const showCross = (x: number | null) => {
    const el = cross.current;
    if (!el) return;
    if (x === null) {
      el.style.opacity = "0";
      return;
    }
    el.style.opacity = "1";
    el.style.transform = `translateX(${x}px)`;
    if (crossLabel.current) crossLabel.current.textContent = fmt(Lay.s.inv(x));
  };
  const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
    if (e.button !== 0) return;
    const [x] = local(e);
    e.currentTarget.setPointerCapture(e.pointerId);
    drag.current = { anchor: x, tick: x };
    const u = Lay.s.inv(x);
    setDraft([u, u]);
    setHoverId(null);
  };
  const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
    const [x, y] = local(e);
    const d = drag.current;
    if (d) {
      const cx = clamp(x);
      if (Math.abs(cx - d.tick) >= 12) {
        d.tick = cx;
        emitSound(e.currentTarget, "sliderTick");
      }
      const a = Lay.s.inv(d.anchor);
      const z = Lay.s.inv(clamp(x));
      const next: SwarmSelection = a <= z ? [a, z] : [z, a];
      if (!draft || draft[0] !== next[0] || draft[1] !== next[1]) setDraft(next);
      showCross(null);
      return;
    }
    const best = nearest(x, y);
    const id = best != null ? inBounds(customers, best).id : null;
    if (id !== hoverId) setHoverId(id);
    showCross(best === null && y <= PH ? x : null);
  };
  const onPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
    const d = drag.current;
    if (!d) return;
    drag.current = null;
    setDraft(null);
    const [x, y] = local(e);
    const end = clamp(x);
    if (Math.abs(end - d.anchor) > 4) {
      emitSound(e.currentTarget, "select");
      commit([Lay.s.inv(d.anchor), Lay.s.inv(end)]);
    } else {
      // A click, not a drag: clear any range, and open the customer under the pointer.
      const hit = nearest(x, y);
      if (hit !== null) {
        emitSound(e.currentTarget, "tap");
        onCustomerClick?.(inBounds(customers, hit).id);
      } else if (selection) emitSound(e.currentTarget, "tick");
      commit(null);
    }
  };
  const cancelDrag = () => {
    if (!drag.current) return;
    drag.current = null;
    setDraft(null);
    showCross(null);
  };

  // --- keyboard: ← → walk the tiles left to right, Shift extends a range, Enter opens, Escape leaves
  const order = React.useMemo(
    () =>
      customers
        .map((_, i) => i)
        .filter((i) => !inBounds(Lay.pos, i).gone && !hidden.has(inBounds(customers, i).plan))
        .sort((x, y) => inBounds(Lay.pos, x).x - inBounds(Lay.pos, y).x || inBounds(Lay.pos, y).y - inBounds(Lay.pos, x).y),
    [customers, Lay, hidden],
  );
  const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
    const rtl = getComputedStyle(e.currentTarget).direction === "rtl";
    if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
      e.preventDefault();
      if (!order.length) return;
      const at = hover === null ? -1 : order.indexOf(hover);
      const step = (e.key === "ArrowRight") !== rtl ? 1 : -1;
      const next = inBounds(order, Math.max(0, Math.min(order.length - 1, at + step)));
      const val = (i: number) => valueOf(inBounds(customers, i), metric.key);
      if (e.shiftKey) {
        kbAnchor.current ??= val(hover ?? next);
        commit([kbAnchor.current, val(next)]);
      } else kbAnchor.current = null;
      if (next !== hover) emitSound(e.currentTarget, "tick");
      setHoverId(inBounds(customers, next).id);
      showCross(null);
    } else if (e.key === "Enter" && hover !== null) {
      e.preventDefault();
      emitSound(e.currentTarget, "tap");
      onCustomerClick?.(inBounds(customers, hover).id);
    } else if (e.key === "Escape" && hover !== null && !selection) setHoverId(null);
  };

  // --- tiles: re-render only when the layout, phase or brush change -------------------------------
  const brushKey = b ? `${b[0]}:${b[1]}` : "";
  const tiles = React.useMemo(
    () =>
      customers.map((c, i) => {
        const p = inBounds(Lay.pos, i);
        const gone = p.gone || hidden.has(c.plan);
        const out = b ? !(p.x >= b[0] - SWARM.BW / 2 && p.x <= b[1] + SWARM.BW / 2) : false;
        let y = p.y;
        let sc = 1;
        let d = 0;
        if (phase === "intro") {
          y = p.y - 70;
          sc = 0.4;
        } else if (!quiet) d = phase === "in" ? Math.round((p.x / W) * 520 + p.r * 38) : Math.round((p.x / W) * 240 + p.r * 16);
        if (gone) {
          y = p.y + 2;
          sc = 0;
        }
        return (
          <div
            key={c.id}
            className={`lumen-cs-abs lumen-cs-tile`}
            style={{ "--cs-c": colorOf(c.plan), "--d": d, transform: `translate(${(p.x - 3).toFixed(1)}px, ${(y - SWARM.TH / 2).toFixed(1)}px) scale(${sc})`, opacity: phase === "intro" || gone ? 0 : out ? 0.14 : 1 } as React.CSSProperties}
          />
        );
      }),
    // b follows brushKey.
    // eslint-disable-next-line -- (Beautiful UI's lint: react-hooks/exhaustive-deps)
    [customers, Lay, hidden, phase, brushKey, W, quiet, plans],
  );

  // --- lanes -------------------------------------------------------------------------------------
  const laneName = (k: string) => (group === "all" ? L.allLane : group === "plan" ? (plans.find((p) => p.key === k)?.label ?? k) : k || L.noRegion);
  const selTotal = selected.reduce((a, i) => a + revenueOf(inBounds(customers, i)), 0);
  const ticks = (metric.ticks ?? []).map((t) => ({ x: Lay.s.f(t.value), label: t.label }));

  // --- tooltip -----------------------------------------------------------------------------------
  let tip: React.ReactNode = null;
  let tipAt = { x: 0, y: 0 };
  const hovered = hover !== null && !draft ? customers[hover] : undefined;
  if (hover !== null && hovered) {
    const c = hovered;
    const p = inBounds(Lay.pos, hover);
    tipAt = { x: p.x, y: p.y };
    const peers = customers.filter((o) => o.plan === c.plan && Number.isFinite(valueOf(o, metric.key))).sort((x, y) => valueOf(y, metric.key) - valueOf(x, metric.key));
    const pct = Math.max(1, Math.round(((peers.indexOf(c) + 1) / peers.length) * 100));
    const outside = Lay.s.outside(valueOf(c, metric.key));
    const planLabel = plans[planIndex.get(c.plan) ?? 0]?.label ?? c.plan;
    tip = (
      <>
        <div className={`lumen-cs-tip-name`}>{c.name || c.id}</div>
        <div className={`lumen-cs-tip-meta`} style={{ "--cs-c": colorOf(c.plan) } as React.CSSProperties}>
          <i aria-hidden />
          {planLabel}
          {c.region ? ` · ${c.region}` : ""}
        </div>
        {metrics.map((mm) => (
          <div key={mm.key} className={`lumen-cs-tip-row`} data-on={mm.key === metric.key ? "" : undefined}>
            <span>{mm.label}</span>
            <span>{Number.isFinite(valueOf(c, mm.key)) ? swarmFormat({ ...mm, format: { ...mm.format, notation: "standard" } }, valueOf(c, mm.key)) : "—"}</span>
          </div>
        ))}
        <div className={`lumen-cs-tip-note`}>
          {fillT(L.topPercent, { n: pct, plan: planLabel, metric: metric.label })}
          {outside && (
            <>
              <br />
              {outside === "nonPositive" ? L.outsideNonPositive : outside === "below" ? L.outsideBelow : L.outsideAbove}
            </>
          )}
        </div>
      </>
    );
  }
  const plotLabel = b ? fillT(L.plotRange, { n: order.length, metric: metric.label, from: fmt(Lay.s.inv(b[0])), to: fmt(Lay.s.inv(b[1])) }) : fillT(L.plot, { n: order.length, metric: metric.label });

  return (
    <div
      {...props}
      ref={mergedPlot}
      tabIndex={0}
      role="group"
      aria-label={plotLabel}
      aria-describedby={tip ? tipId : undefined}
      data-slot="customer-swarm-plot"
      data-sound="none"
      className={cn(`lumen-cs-plot`, className)}
      style={{ height: cssHeight ? `calc(${cssHeight} + ${SWARM.AXIS_H}px)` : PH + SWARM.AXIS_H, ...style }}
      onKeyDown={onKeyDown}
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      onPointerUp={onPointerUp}
      onPointerCancel={cancelDrag}
      onLostPointerCapture={cancelDrag}
      onPointerLeave={() => {
        if (!drag.current) {
          setHoverId(null);
          showCross(null);
        }
      }}
      onBlur={() => setHoverId(null)}
    >
      {ticks.map((t, i) => (
        <React.Fragment key={i}>
          <div aria-hidden className={`lumen-cs-abs lumen-cs-grid`} style={{ height: PH, transform: `translateX(${t.x}px)`, opacity: phase === "intro" ? 0 : 1 }} />
          <div aria-hidden className={`lumen-cs-abs lumen-cs-tick`} style={{ top: PH + 8, transform: `translateX(${t.x}px) translateX(-50%)`, opacity: phase === "intro" ? 0 : 1 }}>
            {t.label}
          </div>
        </React.Fragment>
      ))}
      <div aria-hidden className={`lumen-cs-axis`} style={{ top: PH }} />
      {Lay.lanes.map((ln) => {
        const v = ln.values;
        const has = v.length > 2 && phase !== "intro";
        const a = has ? Lay.s.f(quantile(v, 0.25)) : W / 2;
        const z = has ? Lay.s.f(quantile(v, 0.75)) : W / 2;
        const md = has ? Lay.s.f(quantile(v, 0.5)) : W / 2;
        const c = group === "plan" ? colorOf(ln.key) : "var(--cs-neutral)";
        return (
          <React.Fragment key={`${group}-${ln.key}`}>
            <div aria-hidden className={`lumen-cs-abs lumen-cs-baseline`} style={{ transform: `translateY(${ln.base}px)`, opacity: group !== "all" ? 1 : 0 }} />
            <div aria-hidden className={`lumen-cs-abs lumen-cs-bracket`} style={{ "--cs-c": c, width: Math.max(6, z - a + 6), transform: `translate(${a - 3}px, ${ln.base + 5}px)`, opacity: has ? 1 : 0 } as React.CSSProperties}>
              <i />
              <i />
              <i />
              <i style={{ transform: `translateX(${md - a + 3}px)` }} />
              <span>{has ? `${fmt(quantile(v, 0.25))}–${fmt(quantile(v, 0.75))}` : ""}</span>
            </div>
          </React.Fragment>
        );
      })}
      {tiles}
      {b && (
        <div aria-hidden className={`lumen-cs-abs lumen-cs-brush`} style={{ height: PH, left: b[0], width: b[1] - b[0] }}>
          <span className={`lumen-cs-chiplabel`} style={{ left: 0, bottom: -26, transform: "translateX(-50%)" }}>
            {fmt(Lay.s.inv(b[0]))}
          </span>
          <span className={`lumen-cs-chiplabel`} style={{ right: 0, bottom: -26, transform: "translateX(50%)" }}>
            {fmt(Lay.s.inv(b[1]))}
          </span>
        </div>
      )}
      <div ref={cross} aria-hidden className={`lumen-cs-abs lumen-cs-cross`} style={{ height: PH }}>
        <span className={`lumen-cs-chiplabel`} style={{ left: 0, top: PH + 4, transform: "translateX(-50%)" }}>
          <span ref={crossLabel} />
        </span>
      </div>
      {Lay.lanes.map((ln) => {
        const inLane = ln.members.filter((k) => selected.includes(k));
        const rev = inLane.reduce((a, k) => a + revenueOf(inBounds(customers, k)), 0);
        return (
          <div key={`label-${group}-${ln.key}`} className={`lumen-cs-abs lumen-cs-lane`} style={{ transform: `translateY(${ln.top + 4}px)` }}>
            <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
              <b>{laneName(ln.key)}</b>
              <span>
                {inLane.length}
                <span className="sr-only"> {inLane.length === 1 ? L.customer : L.customers}</span>
              </span>
            </div>
            <span>
              <em>{revenueFormat(rev)}</em> {selTotal ? Math.round((rev / selTotal) * 100) : 0}%
            </span>
          </div>
        );
      })}
      {hover !== null && tip && <div aria-hidden className={`lumen-cs-abs lumen-cs-ringed`} style={{ "--cs-c": colorOf(inBounds(customers, hover).plan), left: tipAt.x, top: tipAt.y } as React.CSSProperties} />}
      <ChartTooltip id={tipId} open={tip != null} x={tipAt.x} y={tipAt.y - 6} style={{ width: 196 }}>
        {tip}
      </ChartTooltip>
    </div>
  );
}

/** The metric list, for custom tooltips and tables. */
export function useCustomerSwarmMetrics() {
  return useCustomerSwarm().metrics;
}

// ---------------------------------------------------------------------------------------------
// The other parts
// ---------------------------------------------------------------------------------------------

export function CustomerSwarmMetrics(props: Omit<React.ComponentProps<typeof ChartModes>, "value" | "onValueChange" | "defaultValue">) {
  const { metrics, metric, setMetric, L } = useCustomerSwarm();
  return (
    <ChartModes value={metric.key} onValueChange={setMetric} aria-label={L.metricTabs} {...props}>
      {metrics.map((m) => (
        <ChartMode key={m.key} value={m.key}>
          {m.label}
        </ChartMode>
      ))}
    </ChartModes>
  );
}

export function CustomerSwarmGroups(props: Omit<React.ComponentProps<typeof ChartModes>, "value" | "onValueChange" | "defaultValue">) {
  const { group, setGroup, L } = useCustomerSwarm();
  return (
    <ChartModes value={group} onValueChange={(v) => setGroup(v as SwarmGroup)} aria-label={L.groupTabs} {...props}>
      <ChartMode value="all">{L.groupAll}</ChartMode>
      <ChartMode value="plan">{L.groupPlan}</ChartMode>
      <ChartMode value="region">{L.groupRegion}</ChartMode>
    </ChartModes>
  );
}

/** The sentence the selection tells: which plan carries more revenue than its share of customers. */
export function CustomerSwarmInsight(props: React.ComponentProps<"p">) {
  const { insight } = useCustomerSwarm();
  return <ChartDescription {...props}>{insight}</ChartDescription>;
}

/** Plan chips with counts; a press hides or shows the plan (the last one can't hide). */
export function CustomerSwarmLegend({ className, ...props }: React.ComponentProps<"div">) {
  const { plans, hidden, togglePlan, customers, selected, L } = useCustomerSwarm();
  const shown = plans.filter((p) => !hidden.has(p.key)).length;
  return (
    <div data-slot="chart-legend" className={cn(`lumen-cs-chips`, className)} {...props}>
      {plans.map((p) => {
        const on = !hidden.has(p.key);
        const n = selected.filter((i) => inBounds(customers, i).plan === p.key).length;
        return (
          <button
            key={p.key}
            type="button"
            className={`lumen-cs-chip`}
            aria-pressed={on}
            aria-label={fillT(L.legend, { plan: p.label, n, state: on ? L.shown : L.hidden })}
            data-sound={!on ? "toggleOn" : shown <= 1 ? "blocked" : "toggleOff"}
            style={{ "--cs-c": p.color } as React.CSSProperties}
            onClick={() => togglePlan(p.key)}
          >
            <i aria-hidden />
            {p.label}
            <b aria-hidden>{n}</b>
          </button>
        );
      })}
    </div>
  );
}

/** "86 customers · $214K MRR" for the view, rolling. */
export function CustomerSwarmSummary({ className, ...props }: React.ComponentProps<"div">) {
  const { selected, customers, revenueOf, revenueFormat, L, metrics, revenueKey } = useCustomerSwarm();
  const total = selected.reduce((a, i) => a + revenueOf(inBounds(customers, i)), 0);
  const revLabel = metrics.find((m) => m.key === revenueKey)?.label ?? "";
  return (
    <div data-slot="customer-swarm-summary" className={cn(`lumen-cs-summary`, className)} {...props}>
      <StatValue>{String(selected.length)}</StatValue>
      <span>{selected.length === 1 ? L.customer : L.customers} ·</span>
      <StatValue>{revenueFormat(total)}</StatValue>
      <span>{revLabel}</span>
    </div>
  );
}

/** Customers and revenue by plan, as two split bars. */
export function CustomerSwarmSplit({ className, ...props }: React.ComponentProps<"div">) {
  const { plans, selected, customers, revenueOf, L } = useCustomerSwarm();
  const cnt = plans.map((p) => selected.filter((i) => inBounds(customers, i).plan === p.key).length);
  const rev = plans.map((p) => selected.filter((i) => inBounds(customers, i).plan === p.key).reduce((a, i) => a + revenueOf(inBounds(customers, i)), 0));
  const rows = [
    { key: "c", label: L.splitCustomers, vals: cnt, total: selected.length },
    { key: "r", label: L.splitRevenue, vals: rev, total: rev.reduce((a, b) => a + b, 0) },
  ];
  return (
    <div data-slot="customer-swarm-split" className={cn(`lumen-cs-split`, className)} {...props}>
      {rows.map((row) => {
        const segs = row.vals.map((v, i) => {
          const p = row.total ? v / row.total : 0;
          return { i, p, txt: `${Math.round(p * 100)}%` };
        });
        return (
          <React.Fragment key={row.key}>
            <span>{row.label}</span>
            <div role="img" aria-label={`${row.label}: ${segs.map((s) => `${inBounds(plans, s.i).label} ${s.txt}`).join(", ")}`} className={`lumen-cs-bar`}>
              {segs.map((s) => (
                <div key={s.i} className={`lumen-cs-seg`} style={{ "--cs-c": inBounds(plans, s.i).color, flex: `${(s.p * 1000).toFixed(1)} 1 0px`, padding: `0 ${s.p > 0.001 ? 7 : 0}px` } as React.CSSProperties}>
                  <span aria-hidden style={{ opacity: s.p > 0.06 ? 1 : 0 }}>
                    {s.txt}
                  </span>
                </div>
              ))}
            </div>
          </React.Fragment>
        );
      })}
    </div>
  );
}

/** The bracket key, the hint, the scale and, with a range, Clear. */
export function CustomerSwarmKey({ className, ...props }: React.ComponentProps<"div">) {
  const { selection, commit, metric, L } = useCustomerSwarm();
  return (
    <div data-slot="customer-swarm-key" className={cn(`lumen-cs-foot`, className)} {...props}>
      <div>
        <span className={`lumen-cs-key`}>
          <i aria-hidden />
          {L.bracketKey}
        </span>
        <span>{selection ? L.hintClear : L.hintDrag}</span>
      </div>
      <div>
        <span>{metric.scale === "log" ? L.logScale : L.linearScale}</span>
        {selection && (
          <button type="button" className={`lumen-cs-clear`} data-sound="close" onClick={() => commit(null)}>
            {L.clear}
          </button>
        )}
      </div>
    </div>
  );
}

/** Every customer with plan, region and metrics (a "View as table" disclosure at the sample's size). */
export function CustomerSwarmTable(props: Omit<React.ComponentProps<typeof ChartTable>, "caption" | "columns" | "rows">) {
  const { customers, plans, metrics, L, title } = useCustomerSwarm();
  // Built once per data change, not on every hover.
  const { columns, rows } = React.useMemo(() => {
    const label = new Map(plans.map((p) => [p.key, p.label]));
    const cell = (c: SwarmCustomer, m: SwarmMetric) => (Number.isFinite(valueOf(c, m.key)) ? swarmFormat({ ...m, format: { ...m.format, notation: "standard" } }, valueOf(c, m.key)) : "—");
    return {
      columns: [L.tableCustomer, L.tablePlan, L.tableRegion, ...metrics.map((m) => m.label)],
      rows: customers.map((c) => [c.name || c.id, label.get(c.plan) ?? c.plan, c.region || L.noRegion, ...metrics.map((m) => cell(c, m))]),
    };
  }, [customers, plans, metrics, L]);
  return <ChartTable caption={fillT(L.tableCaption, { title })} columns={columns} rows={rows} {...props} />;
}

/** Each lane's count, middle half, median and revenue for the current metric: what the brackets show, for screen readers. */
export function CustomerSwarmLaneTable(props: Omit<React.ComponentProps<typeof ChartTable>, "caption" | "columns" | "rows">) {
  const { layout, group, plans, metric, customers, selected, revenueOf, revenueFormat, L, title } = useCustomerSwarm();
  const name = (k: string) => (group === "all" ? L.allLane : group === "plan" ? (plans.find((p) => p.key === k)?.label ?? k) : k || L.noRegion);
  const fmt = (v: number) => swarmFormat(metric, v);
  const rows = layout.lanes.map((ln) => {
    const inLane = ln.members.filter((k) => selected.includes(k));
    const v = ln.values;
    return [name(ln.key), inLane.length, v.length ? fmt(quantile(v, 0.25)) : "—", v.length ? fmt(quantile(v, 0.5)) : "—", v.length ? fmt(quantile(v, 0.75)) : "—", revenueFormat(inLane.reduce((a, k) => a + revenueOf(inBounds(customers, k)), 0))];
  });
  return <ChartTable caption={fillT(L.summaryCaption, { title, metric: metric.label })} columns={[L.summaryLane, L.summaryCount, L.summaryLow, L.summaryMedian, L.summaryHigh, L.summaryRevenue]} rows={rows} {...props} />;
}

// ---------------------------------------------------------------------------------------------
// The recipe: the finished card, on the sample data. Copy it and rearrange.
// ---------------------------------------------------------------------------------------------

export function CustomerSwarmCard({
  customers = sampleSwarmCustomers,
  config,
  metrics,
  regions,
  eyebrow,
  title,
  className,
  ...props
}: Partial<CustomerSwarmProps> & { eyebrow?: string | undefined }) {
  // The sample's plans, metrics, regions and words apply only to the sample, never to your data.
  const sample = customers === sampleSwarmCustomers;
  title ??= sample ? "Where Lumen's revenue comes from" : "Customers";
  eyebrow ??= sample ? "Customer swarm · Lumen" : undefined;
  return (
    <CustomerSwarm
      {...props}
      customers={customers}
      config={config ?? (sample ? sampleSwarmConfig : {})}
      metrics={metrics ?? (sample ? sampleSwarmMetrics : [])}
      regions={regions ?? (sample ? sampleSwarmRegions : undefined)}
      title={title}
      className={className}
    >
      <ChartSurface aria-label={title}>
        <div className={`lumen-cs-head`}>
          <ChartHeader>
            {eyebrow && <ChartEyebrow live>{eyebrow}</ChartEyebrow>}
            <ChartTitle>{title}</ChartTitle>
            <CustomerSwarmInsight />
          </ChartHeader>
          <div className={`lumen-cs-tabs`}>
            <CustomerSwarmMetrics />
            <CustomerSwarmGroups />
          </div>
        </div>
        <div className={`lumen-cs-row`}>
          <CustomerSwarmLegend />
          <CustomerSwarmSummary />
        </div>
        <CustomerSwarmSplit />
        <CustomerSwarmPlot />
        <div aria-hidden className={`lumen-cs-rule`} />
        <CustomerSwarmKey />
        <CustomerSwarmLaneTable />
        <CustomerSwarmTable />
      </ChartSurface>
    </CustomerSwarm>
  );
}

export type { SwarmCustomer, SwarmMetric, SwarmGroup, SwarmSelection, CustomerSwarmLabels };
```

### lib/beautiful-ui/lumen/customer-swarm-model.ts

```tsx
import { formatStatValue, type StatFormat } from "@/lib/beautiful-ui/lumen/stat-format";

/*
 * Customer Swarm's model: every customer as a tile on one metric's axis, binned and stacked into
 * lanes (all, by plan, by region), the middle half and median per lane, and the insight sentence.
 * Plain data in and out (no React, no functions in the data), so a Server Component can prepare it.
 */

/** A customer. `plan` is a config key (its label and colour); metrics are numeric fields named by the metrics' keys. */
export type SwarmCustomer = { id: string; name: string; plan: string; region?: string } & Record<string, string | number | undefined>;

export type SwarmMetric = {
  /** The customer field it reads, e.g. "mrr". */
  key: string;
  /** Tab label, e.g. "MRR". */
  label: string;
  scale?: "log" | "linear";
  /** Axis range; log scales need a positive minimum. */
  domain: [number, number];
  /** Gridlines. */
  ticks?: { value: number; label: string }[];
  /** How values read on the axis, brush and tooltip. */
  format?: StatFormat;
};

export type SwarmGroup = "all" | "plan" | "region";
export type SwarmSelection = [number, number];

// Geometry, px (the design's).
export const SWARM = { PH: 348, MIN_PH: 180, MIN_LANE: 72, AXIS_H: 30, PADL: 20, PADR: 20, BW: 7, TH: 4.5, PITCH: 5.6, TOPPAD: 30, BOTPAD: 24 } as const;

const fin = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
export const valueOf = (c: SwarmCustomer, key: string) => {
  const v = c[key];
  return fin(v) ? v : Number.NaN;
};

export type SwarmOutside = "below" | "above" | "nonPositive" | null;

/** The axis for a metric at a plot width: `f` value → x px, `inv` x px → value, `outside` where a value falls. */
export function swarmScale(m: SwarmMetric, W: number) {
  const log = (m.scale ?? "linear") === "log";
  // A usable domain whatever comes in: finite, ordered, non-degenerate, positive on a log scale.
  let [a, b] = m.domain.map(Number) as [number, number];
  if (!Number.isFinite(a) || !Number.isFinite(b)) [a, b] = log ? [1, 10] : [0, 1];
  if (a > b) [a, b] = [b, a];
  if (log && !(b > 0)) [a, b] = [1, 10];
  if (log && !(a > 0)) a = Math.min(1, b / 10);
  if (a === b) b = log ? a * 10 : a + 1;
  const span = Math.max(1, W - SWARM.PADL - SWARM.PADR);
  const la = Math.log(a);
  const lb = Math.log(b);
  const t = (v: number) => {
    if (!Number.isFinite(v) || (log && !(v > 0))) return 0;
    const r = log ? (Math.log(v) - la) / (lb - la) : (v - a) / (b - a);
    return Math.max(0, Math.min(1, r));
  };
  const inv = (x: number) => {
    const r = Math.max(0, Math.min(1, (x - SWARM.PADL) / span));
    return log ? Math.exp(la + r * (lb - la)) : a + r * (b - a);
  };
  const outside = (v: number): SwarmOutside => (log && !(v > 0) ? "nonPositive" : v < a ? "below" : v > b ? "above" : null);
  return { f: (v: number) => SWARM.PADL + t(v) * span, inv, outside, log };
}

/** The p-quantile of an ascending, non-empty list (NaN for an empty one). */
export const quantile = (sorted: readonly number[], p: number) => sorted[Math.min(sorted.length - 1, Math.max(0, Math.round((sorted.length - 1) * p)))] ?? NaN;

export type SwarmPos = { x: number; y: number; r: number; gone?: boolean };
export type SwarmLane = { key: string; top: number; base: number; members: number[]; values: number[] };

/** The lanes for a grouping: one "all" lane, one per plan (config order), or one per region (data order). */
export function swarmLaneKeys(customers: readonly SwarmCustomer[], group: SwarmGroup, plans: readonly string[], regions?: readonly string[]) {
  if (group === "all") return ["all"];
  if (group === "plan") return [...plans];
  // The given order first, then any region the data has that it misses ("" for customers without one).
  const seen = new Set(regions ?? []);
  const out = [...(regions ?? [])];
  for (const c of customers) {
    const r = c.region ?? "";
    if (!seen.has(r)) {
      seen.add(r);
      out.push(r);
    }
  }
  return out;
}

/** Tile positions and lanes for one metric, grouping, width and plot height. Hidden plans' tiles are `gone`. */
export function swarmLayout(customers: readonly SwarmCustomer[], m: SwarmMetric, laneKeys: readonly string[], group: SwarmGroup, plans: readonly string[], hidden: ReadonlySet<string>, W: number, PH: number = SWARM.PH) {
  const s = swarmScale(m, W);
  const nL = Math.max(1, laneKeys.length);
  // Each lane keeps room for its label, a few rows and its bracket: many lanes grow the plot.
  const lh = Math.max(PH / nL, SWARM.MIN_LANE);
  PH = lh * nL;
  const laneOf = (c: SwarmCustomer) => (group === "all" ? 0 : laneKeys.indexOf(group === "plan" ? c.plan : (c.region ?? "")));
  const planRank = new Map(plans.map((p, i) => [p, i]));
  // Indexes into customers, read safely (noUncheckedIndexedAccess); every index here is in range.
  const valAt = (i: number) => {
    const c = customers[i];
    return c ? valueOf(c, m.key) : Number.NaN;
  };
  const rankAt = (i: number) => {
    const c = customers[i];
    return (c && planRank.get(c.plan)) ?? 99;
  };
  const pos: SwarmPos[] = new Array(customers.length);
  const lanes: SwarmLane[] = laneKeys.map((key, L) => {
    const top = lh * L;
    const base = top + lh - SWARM.BOTPAD;
    const members = customers.flatMap((c, i) => (laneOf(c) === L && !hidden.has(c.plan) && Number.isFinite(valueOf(c, m.key)) ? [i] : []));
    const bins = new Map<number, number[]>();
    for (const i of members) {
      const b = Math.round((s.f(valAt(i)) - SWARM.PADL) / SWARM.BW);
      bins.set(b, [...(bins.get(b) ?? []), i]);
    }
    let maxN = 1;
    for (const v of bins.values()) maxN = Math.max(maxN, v.length);
    const pitch = Math.max(0.5, Math.min(SWARM.PITCH, (lh - SWARM.BOTPAD - SWARM.TOPPAD) / maxN));
    for (const [b, idx] of bins)
      idx
        .sort((x, y) => rankAt(x) - rankAt(y) || valAt(y) - valAt(x))
        .forEach((i, r) => (pos[i] = { x: SWARM.PADL + b * SWARM.BW, y: base - 3 - r * pitch, r }));
    const values = members.map(valAt).sort((x, y) => x - y);
    return { key, top, base, members, values };
  });
  customers.forEach((c, i) => {
    if (!pos[i]) {
      const L = laneOf(c);
      pos[i] = { x: s.f(valueOf(c, m.key)), y: (L >= 0 ? lanes[L]?.base : undefined) ?? PH / 2, r: 0, gone: true };
    }
  });
  return { pos, lanes, s, PH };
}

/** "$4.2K", "12 seats"… through the metric's StatFormat. */
export const swarmFormat = (m: SwarmMetric, v: number) => formatStatValue(v, m.format ?? {});

// ---------------------------------------------------------------------------------------------
// Words: plain strings with {placeholders}
// ---------------------------------------------------------------------------------------------

export const customerSwarmLabels = {
  metricTabs: "Metric",
  groupTabs: "Group by",
  groupAll: "All",
  groupPlan: "Plan",
  groupRegion: "Region",
  allLane: "All customers",
  empty: "No customers in this range.",
  insightOnly: "{plan} only in this view, {n} customers.",
  insightShare: "{plan} is {customers}% of customers and {revenue}% of revenue.",
  insightShareRange: "{plan} is {customers}% of customers and {revenue}% of revenue in this range.",
  customer: "customer",
  customers: "customers",
  splitCustomers: "Customers",
  splitRevenue: "Revenue",
  topPercent: "Top {n}% of {plan} by {metric}",
  outsideNonPositive: "0 or less can't sit on a log scale: shown at the left edge",
  outsideBelow: "Below the axis range: shown at the left edge",
  outsideAbove: "Above the axis range: shown at the right edge",
  noRegion: "No region",
  summaryCaption: "{title}: {metric} by lane, middle half and median.",
  summaryLane: "Lane",
  summaryCount: "Customers",
  summaryLow: "25th percentile",
  summaryMedian: "Median",
  summaryHigh: "75th percentile",
  summaryRevenue: "Revenue",
  bracketKey: "Middle half of customers · marker is the median",
  hintClear: "Esc to clear",
  hintDrag: "Drag to select a range",
  logScale: "Log scale",
  linearScale: "Linear scale",
  clear: "Clear",
  plot: "{n} customers by {metric}. Arrow keys move between customers, Shift plus arrows selects a range, Escape clears.",
  plotRange: "{n} customers by {metric}, selected {from} to {to}. Arrow keys move between customers, Shift plus arrows selects a range, Escape clears.",
  legend: "{plan}, {n} customers, {state}",
  shown: "shown",
  hidden: "hidden",
  tableCaption: "{title}: every customer with plan, region and metrics.",
  tableCustomer: "Customer",
  tablePlan: "Plan",
  tableRegion: "Region",
};
export type CustomerSwarmLabels = typeof customerSwarmLabels;

/**
 * The insight: the plan whose share of customers and share of revenue differ most in the view
 * ("Enterprise is 12% of customers and 64% of revenue.").
 */
export function swarmInsight(selected: readonly SwarmCustomer[], plans: readonly { key: string; label: string }[], revenueKey: string, inRange: boolean, L: CustomerSwarmLabels = customerSwarmLabels) {
  if (!selected.length) return L.empty;
  const rev = (c: SwarmCustomer) => {
    const v = valueOf(c, revenueKey);
    return Number.isFinite(v) ? v : 0;
  };
  const total = selected.reduce((a, c) => a + rev(c), 0);
  let best = -1;
  let bd = -1;
  const cnt = plans.map((p) => selected.filter((c) => c.plan === p.key).length);
  const rv = plans.map((p) => selected.filter((c) => c.plan === p.key).reduce((a, c) => a + rev(c), 0));
  plans.forEach((_, i) => {
    const n = cnt[i] ?? 0;
    const v = rv[i] ?? 0;
    if (!n && !v) return;
    const d = Math.abs(n / selected.length - v / (total || 1));
    if (d > bd) {
      bd = d;
      best = i;
    }
  });
  const bestPlan = plans[best];
  if (best < 0 || !bestPlan) return L.empty;
  const cp = Math.round(((cnt[best] ?? 0) / selected.length) * 100);
  const rp = Math.round(((rv[best] ?? 0) / (total || 1)) * 100);
  const fill = (t: string, v: Record<string, string | number>) => t.replace(/\{(\w+)\}/g, (m, k: string) => (k in v ? String(v[k]) : m));
  if (cp === 100) return fill(L.insightOnly, { plan: bestPlan.label, n: selected.length });
  return fill(inRange ? L.insightShareRange : L.insightShare, { plan: bestPlan.label, customers: cp, revenue: rp });
}

/** A selection with finite ends, low to high, or null. */
export function normalizeSwarmSelection(r: readonly number[] | null | undefined): SwarmSelection | null {
  if (!r || r.length < 2) return null;
  const a = Number(r[0]);
  const b = Number(r[1]);
  if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
  return a <= b ? [a, b] : [b, a];
}
```

### lib/beautiful-ui/lumen/customer-swarm-sample.ts

```tsx
import type { ChartConfig } from "@/lib/beautiful-ui/lumen/chart-config";
import type { SwarmCustomer, SwarmMetric } from "@/lib/beautiful-ui/lumen/customer-swarm-model";

/*
 * Customer Swarm's sample: 300 seeded Lumen customers (35 Enterprise, 79 Mid-market, 186
 * Self-serve), the same on the server and in every browser. Only the recipe card and the demo import
 * it; your own customers never mix with it.
 */

export const sampleSwarmConfig = {
  enterprise: { label: "Enterprise", theme: { light: "#6B74F5", dark: "#8B93FF" } },
  midMarket: { label: "Mid-market", theme: { light: "#17A673", dark: "#4ADE9B" } },
  selfServe: { label: "Self-serve", theme: { light: "#E08A12", dark: "#FFB547" } },
} satisfies ChartConfig;

export const sampleSwarmRegions = ["North America", "Europe", "Asia Pacific"];

export const sampleSwarmMetrics: SwarmMetric[] = [
  { key: "mrr", label: "MRR", scale: "log", domain: [12, 62000], ticks: [{ value: 100, label: "$100" }, { value: 1000, label: "$1K" }, { value: 10000, label: "$10K" }], format: { style: "currency", currency: "USD" } },
  { key: "seats", label: "Seats", scale: "log", domain: [0.8, 3200], ticks: [{ value: 10, label: "10 seats" }, { value: 100, label: "100" }, { value: 1000, label: "1K" }], format: { style: "number" } },
  { key: "tenure", label: "Tenure", scale: "linear", domain: [0, 62], ticks: [{ value: 12, label: "1 yr" }, { value: 24, label: "2 yr" }, { value: 36, label: "3 yr" }, { value: 48, label: "4 yr" }], format: { style: "unit", unit: "month", unitDisplay: "long", maximumFractionDigits: 0 } },
];

export const sampleSwarmCustomers: SwarmCustomer[] = (() => {
  const shapes: { plan: string; n: number; mrr: [number, number]; seats: [number, number]; tp: number }[] = [
    { plan: "enterprise", n: 35, mrr: [7000, 48000], seats: [150, 2400], tp: 0.6 },
    { plan: "midMarket", n: 79, mrr: [650, 6500], seats: [12, 180], tp: 0.85 },
    { plan: "selfServe", n: 186, mrr: [15, 480], seats: [1, 14], tp: 1.15 },
  ];
  const PRE = ["North", "Blue", "Lumen", "Atlas", "Kite", "Vanta", "Orbit", "Pine", "Harbor", "Nova", "Quill", "Ember", "Tidal", "Cedar", "Echo", "Fable", "Grove", "Halo", "Iris", "Juniper", "Keel", "Lark", "Mosaic", "Nimbus", "Opal", "Prism", "Quartz", "Ridge", "Sable", "Terra"];
  const SUF = ["wind", "labs", "works", "ly", "stack", "forge", "scale", "loop", "field", "base", "cloud", "craft", "point", "path", "mint"];
  let a = 7;
  const R = () => {
    a |= 0;
    a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
  const lg = (lo: number, hi: number, t: number) => Math.exp(Math.log(lo) + (Math.log(hi) - Math.log(lo)) * t);
  const out: SwarmCustomer[] = [];
  for (const p of shapes)
    for (let k = 0; k < p.n; k++) {
      const t = (R() + R() + R()) / 3;
      const r = R();
      const i = out.length;
      out.push({
        id: `c${i}`,
        name: `${PRE[(i * 11) % 30] ?? ""}${SUF[(i * 7 + Math.floor(i / 30)) % 15] ?? ""}`,
        plan: p.plan,
        region: sampleSwarmRegions[r < 0.43 ? 0 : r < 0.78 ? 1 : 2] ?? "",
        mrr: Math.round(lg(p.mrr[0], p.mrr[1], t)),
        seats: Math.max(1, Math.round(lg(p.seats[0], p.seats[1], t * 0.6 + R() * 0.4))),
        tenure: Math.max(1, Math.round(Math.pow(R(), p.tp) * 60)),
      });
    }
  return out;
})();
```

### lib/beautiful-ui/lumen/stat-format.ts

```tsx
/*
 * Lumen Stat's maths: number formats, the change and its tone, and the spark's cells. Plain,
 * serialisable inputs and no React, so a Server Component can format a KPI and pass the result on.
 */

/** How a value is written. Plain data, so it crosses the server/client boundary. */
export type StatFormat = {
  /**
   * "number" (default) · "currency" · "percent" (a fraction: 0.018 → "1.8%") · "unit" (with
   * `unit`, e.g. "millisecond") · "duration" (seconds: 252 → "4m 12s").
   */
  style?: "number" | "currency" | "percent" | "unit" | "duration" | undefined;
  /** ISO code for "currency". Default "USD". */
  currency?: string | undefined;
  /** An Intl unit for "unit", e.g. "millisecond", "gigabyte". */
  unit?: string | undefined;
  /** How a unit reads: "short" (default: "12 mths"), "long" ("12 months") or "narrow" ("12m"). */
  unitDisplay?: "short" | "long" | "narrow" | undefined;
  /** "compact" (default for currency and numbers of 10,000 and up: "$2.41M", "18.2K") or "standard". */
  notation?: "compact" | "standard" | undefined;
  minimumFractionDigits?: number | undefined;
  maximumFractionDigits?: number | undefined;
  /** Default "en-US". */
  locale?: string | undefined;
};

/** good / bad from the direction of the change and whether up is good; flat when it rounds to zero; none without a comparison. */
export type StatTone = "good" | "bad" | "flat" | "none";

const MINUS = "−";
const isNum = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);

function durationText(sec: number) {
  const s = Math.round(sec);
  if (s < 60) return `${s}s`;
  const m = Math.floor(s / 60);
  if (m < 60) return `${m}m ${s % 60}s`;
  const h = Math.floor(m / 60);
  if (h < 24) return `${h}h ${m % 60}m`;
  return `${Math.floor(h / 24)}d ${h % 24}h`;
}

const clampDigits = (n: number | undefined) => (n == null || !Number.isFinite(n) ? undefined : Math.min(20, Math.max(0, Math.round(n))));

function intlOptions(abs: number, f: StatFormat): Intl.NumberFormatOptions {
  const style = f.style ?? "number";
  const compact = f.notation ? f.notation === "compact" : style === "currency" ? abs >= 1000 : style === "number" && abs >= 10000;
  const opts: Intl.NumberFormatOptions = { style: style === "duration" || style === "number" ? "decimal" : style, notation: compact ? "compact" : "standard" };
  if (style === "currency") {
    opts.currency = f.currency ?? "USD";
    opts.currencyDisplay = "narrowSymbol";
  }
  if (style === "unit") {
    opts.unit = f.unit ?? "percent";
    if (f.unitDisplay) opts.unitDisplay = f.unitDisplay;
  }
  const min = clampDigits(f.minimumFractionDigits);
  const maxSet = clampDigits(f.maximumFractionDigits);
  // The design's precision: compact figures keep three significant digits ("$2.41M", "$612K", "18.2K").
  if (compact && min == null && maxSet == null) opts.maximumSignificantDigits = 3;
  else {
    const max = Math.max(maxSet ?? (style === "percent" ? 1 : style === "currency" ? (abs < 100 ? 2 : 0) : abs < 10 ? 2 : abs < 100 ? 1 : 0), min ?? 0);
    opts.maximumFractionDigits = max;
    opts.minimumFractionDigits = min ?? 0;
  }
  return opts;
}

function intl(abs: number, f: StatFormat, latin = false) {
  const opts = intlOptions(abs, f);
  if (latin) opts.numberingSystem = "latn";
  try {
    return new Intl.NumberFormat(f.locale ?? "en-US", opts).format(abs);
  } catch {
    // A bad currency, unit or locale never throws in render: the plain number instead.
    return new Intl.NumberFormat("en-US", { maximumFractionDigits: opts.maximumFractionDigits ?? 2 }).format(abs);
  }
}

/** Whether a magnitude shows as zero in this format (checked in Latin digits, whatever the locale's). */
const showsZero = (abs: number, f: StatFormat) => !/[1-9]/.test(f.style === "duration" ? durationText(abs) : intl(abs, f, true));

/** "$2.41M", "8,940", "1.8%", "4m 12s"; "—" when there is no value; a real minus for negatives, never "−0". */
export function formatStatValue(value: number | null | undefined, format: StatFormat = {}) {
  if (!isNum(value)) return "—";
  const abs = Math.abs(value);
  const s = format.style === "duration" ? durationText(abs) : intl(abs, format);
  return value < 0 && !showsZero(abs, format) ? MINUS + s : s;
}

/** The locale's ten digits, 0 to 9 ("٠…٩" for ar-EG), so rolling reels show the same digits as the text. */
export function statDigits(locale?: string): string[] {
  try {
    const nf = new Intl.NumberFormat(locale ?? "en-US", { useGrouping: false });
    const d = Array.from({ length: 10 }, (_, i) => nf.format(i));
    return d.every((x) => Array.from(x).length === 1) ? d : "0123456789".split("");
  } catch {
    return "0123456789".split("");
  }
}

export type StatChange = {
  tone: StatTone;
  /** "+10.6%", "−0.3 pts", "−1m 6s", "+$231K"; "—" or "New" without a comparison. */
  text: string;
  /** "up" · "down" · "flat" · "none". */
  direction: "up" | "down" | "flat" | "none";
  /** value − previous (0 without both). */
  difference: number;
};

const num = (v: number, locale: string | undefined, digits: number) => {
  try {
    return new Intl.NumberFormat(locale ?? "en-US", { minimumFractionDigits: digits, maximumFractionDigits: digits }).format(v);
  } catch {
    return v.toFixed(digits);
  }
};

/**
 * The change and its tone. Percents change in points, durations by the difference, everything
 * else relatively (by the difference when the previous value is 0). Under 0.05% is flat. Numbers
 * follow the format's locale.
 */
export function statChange(value: number | null | undefined, previous: number | null | undefined, options: { format?: StatFormat | undefined; goodDirection?: "up" | "down" | undefined; mode?: "relative" | "absolute" | undefined } = {}): StatChange {
  const f = options.format ?? {};
  if (!isNum(value)) return { tone: "none", text: "—", direction: "none", difference: 0 };
  if (!isNum(previous)) return { tone: "none", text: "New", direction: "none", difference: 0 };
  const d = value - previous;
  if (!Number.isFinite(d)) return { tone: "none", text: "—", direction: "none", difference: 0 };
  const pct = previous !== 0 ? (d / Math.abs(previous)) * 100 : null;
  const style = f.style ?? "number";
  const relative = (options.mode ?? "relative") === "relative" && pct != null && style !== "percent" && style !== "duration";
  const ad = Math.abs(d);
  const pts = ad * 100;
  // No change, in the change's own unit: "0%", "0 pts", "0s", "$0".
  const flatText = relative ? `${num(0, f.locale, 0)}%` : style === "percent" ? `${num(0, f.locale, 0)} pts` : formatStatValue(0, f);
  const flat: StatChange = { tone: "flat", text: flatText, direction: "flat", difference: d };
  if (relative ? Math.abs(pct!) < 0.05 : style === "percent" ? pts < 0.005 : d === 0 || showsZero(ad, f)) return flat;
  let core: string;
  if (relative) {
    const p = Math.abs(pct!);
    core = `${num(p, f.locale, p >= 1000 ? 0 : 1)}%`;
  } else if (style === "percent") core = `${num(pts, f.locale, pts < 0.1 ? 2 : 1)} pts`;
  else core = formatStatValue(ad, f);
  const up = d > 0;
  const good = options.goodDirection === "down" ? !up : up;
  return { tone: good ? "good" : "bad", text: (up ? "+" : MINUS) + core, direction: up ? "up" : "down", difference: d };
}

/**
 * The spark's columns: the last `columns` points, padded on the left with gaps, each as a count of
 * lit cells (1…rows, the lowest always 1; 0 for a gap). Empty when there is nothing to draw.
 */
export function statCells(data: readonly (number | null | undefined)[] | undefined, rows = 5, columns = 12): number[] {
  if (!Array.isArray(data)) return [];
  const t = data.slice(-columns).map((v) => (isNum(v) ? v : null));
  const fin = t.filter((v): v is number => v != null);
  if (!fin.length) return [];
  const mn = Math.min(...fin);
  const mx = Math.max(...fin);
  const cells = t.map((v) => (v == null ? 0 : 1 + Math.round(((v - mn) / (mx - mn || 1)) * (rows - 1))));
  return [...Array<number>(columns - cells.length).fill(0), ...cells];
}
```

### lib/beautiful-ui/lumen/chart-config.ts

```tsx
/*
 * Lumen Chart's core: shadcn's ChartConfig, its colours as CSS variables, data adapters and the
 * table policy. Plain data in and out, no React, so a Server Component can prepare a chart.
 */

/** shadcn's ChartConfig: one entry per series key. Plain data, so it crosses the server/client boundary. */
export type ChartConfig = Record<
  string,
  {
    /** Shown in legends, tooltips and the table. */
    label?: string;
  } & ({ color?: string; theme?: never } | { color?: never; theme: { light: string; dark: string } })
>;

// Keys become CSS custom property names and ids go in a selector inside a <style>: both are escaped
// (injectively, so "a.b" and "a?b" stay two series), and values that could end a rule are dropped.
/** A key as a CSS identifier fragment, like CSS.escape: word characters and "-" as they are, the rest escaped. */
export function chartKey(k: string) {
  return Array.from(k)
    .map((ch) => (/[\w-]/.test(ch) && ch.charCodeAt(0) < 128 ? ch : `\\${ch.codePointAt(0)!.toString(16)} `))
    .join("");
}
/** A string as a quoted CSS string that can't close the string, the rule or the <style>. */
const cssString = (v: string) => `"${Array.from(v).map((ch) => (/["\\<>\n\r\f]/.test(ch) ? `\\${ch.codePointAt(0)!.toString(16)} ` : ch)).join("")}"`;
const safeValue = (v: string) => (/[;{}<>\\]/.test(v) ? "" : v.trim());

/** The CSS for a chart's colours: `--color-<key>` on `[data-chart=<id>]`, light by default and dark under `.dark` or `[data-theme="dark"]`. */
export function chartStyleCss(id: string, config: ChartConfig): string {
  const sel = `[data-chart=${cssString(id)}]`;
  const decls = (mode: "light" | "dark") =>
    Object.entries(config)
      .map(([k, c]) => {
        const v = c.theme ? c.theme[mode] : c.color;
        const s = v ? safeValue(v) : "";
        return s ? `--color-${chartKey(k)}:${s};` : "";
      })
      .join("");
  const light = decls("light");
  const dark = decls("dark");
  // Dark: shadcn's .dark class, or a data-theme="dark" attribute (next-themes' other strategy).
  return [light && `${sel}{${light}}`, dark && dark !== light && `:where(.dark,[data-theme="dark"]) ${sel}{${dark}}`].filter(Boolean).join("\n");
}

/** The colour of a series, as the variable ChartContainer sets: `var(--color-<key>)`. */
export const chartColor = (key: string) => `var(--color-${chartKey(key)})`;

/** A series' label from the config, else the key itself. */
export const chartLabel = (config: ChartConfig, key: string) => config[key]?.label ?? key;

/** "{n} of {total} markets" → "14 of 20 markets". Unknown placeholders stay as written. */
export function interpolate(template: string, values: Record<string, string | number>) {
  return template.replace(/\{(\w+)\}/g, (m, k: string) => (k in values ? String(values[k]) : m));
}

export type LongPoint = { x: string | number; series: string; value: number };

/** shadcn's rows (`{ month: "Jan", desktop: 186, mobile: 80 }`) to long points, skipping values that aren't numbers. */
export function fromRows(rows: readonly Record<string, unknown>[], xKey: string, keys?: readonly string[]): LongPoint[] {
  const out: LongPoint[] = [];
  for (const row of rows) {
    const x = row[xKey];
    if (typeof x !== "string" && typeof x !== "number") continue;
    for (const k of keys ?? Object.keys(row).filter((k) => k !== xKey)) {
      const v = row[k];
      if (typeof v === "number" && Number.isFinite(v)) out.push({ x, series: k, value: v });
    }
  }
  return out;
}

/** A matrix (rows × columns) to sparse cells, skipping nulls and values that aren't numbers. */
export function fromMatrix<R extends string | number, C extends string | number>(matrix: readonly (readonly (number | null | undefined)[])[], rows: readonly R[], columns: readonly C[]): { row: R; column: C; value: number }[] {
  const out: { row: R; column: C; value: number }[] = [];
  matrix.forEach((line, i) =>
    line.forEach((v, j) => {
      const row = rows[i];
      const column = columns[j];
      if (row !== undefined && column !== undefined && typeof v === "number" && Number.isFinite(v)) out.push({ row, column, value: v });
    }),
  );
  return out;
}

/** Up to this many cells, ChartTable is rendered for screen readers; above it, on demand. */
export const CHART_TABLE_LIMIT = 500;

const warned = new Set<string>();
/** Warns once per message in development; never throws. */
export function chartWarn(message: string) {
  // Read through globalThis: Vite apps have no Node types (and no process at runtime).
  const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.NODE_ENV;
  if (env === "production" || warned.has(message)) return;
  warned.add(message);
  console.warn(`[lumen-chart] ${message}`);
}

/** Finite numbers only (and non-negative where negatives are impossible); warns once about what it drops. */
export function cleanValues<T>(items: readonly T[], value: (item: T) => unknown, opts: { name: string; nonNegative?: boolean }): T[] {
  let dropped = 0;
  const out = items.filter((it) => {
    const v = value(it);
    const ok = typeof v === "number" && Number.isFinite(v) && (!opts.nonNegative || v >= 0);
    if (!ok) dropped++;
    return ok;
  });
  if (dropped) chartWarn(`${opts.name}: skipped ${dropped} invalid point${dropped === 1 ? "" : "s"}`);
  return out;
}
```

### lib/beautiful-ui/lumen/sound.ts

```tsx
"use client";

import * as React from "react";

/**
 * Asks the page's sound layer (beautiful-ui-sound) to play `cue`. Silent when nothing listens, and inside
 * anything marked data-sound="off". `index` pitches select cues by position. No audio code ships in
 * the components themselves.
 */
export function emitSound(el: Element | null, cue: string, force?: boolean, index?: number) {
  if (!el || typeof CustomEvent === "undefined" || el.closest('[data-sound="off"]')) return;
  el.dispatchEvent(new CustomEvent("beautiful-ui:sound", { bubbles: true, detail: { cue, force, index } }));
}

/**
 * Plays the cue `pick` returns whenever `value` changes (never on mount): an error when a field
 * turns invalid, a success when a check passes.
 */
export function useCueOnChange<T>(ref: React.RefObject<Element | null>, value: T, pick: (next: T, prev: T) => string | null) {
  const prev = React.useRef(value);
  const pickRef = React.useRef(pick);
  React.useEffect(() => {
    pickRef.current = pick;
  });
  React.useEffect(() => {
    if (Object.is(prev.current, value)) return;
    const before = prev.current;
    prev.current = value;
    const cue = pickRef.current(value, before);
    if (cue) emitSound(ref.current, cue);
  }, [ref, value]);
}

/** Points one ref (callback or object) at `node`; returns how to let go of it. */
function assign<T>(r: React.Ref<T> | undefined, node: T | null): () => void {
  if (typeof r === "function") {
    const cleanup = r(node);
    return typeof cleanup === "function" ? cleanup : () => r(null);
  }
  if (r) {
    (r as React.RefObject<T | null>).current = node;
    return () => ((r as React.RefObject<T | null>).current = null);
  }
  return () => {};
}

/**
 * One stable callback ref for the component's own ref and the caller's. It changes only when one of
 * them does, so a caller's callback ref isn't detached and reattached on every render, and it
 * honours React 19 ref cleanups.
 */
export function useMergedRef<T>(own: React.Ref<T> | undefined, theirs: React.Ref<T> | undefined): React.RefCallback<T> {
  return React.useCallback(
    (node: T | null) => {
      const release = [assign(own, node), assign(theirs, node)];
      return () => release.forEach((f) => f());
    },
    [own, theirs],
  );
}

/** True when `el` sits in an input group whose status part speaks for it (one cue per change). */
export const statusSpeaksFor = (el: Element | null) => Boolean(el?.closest('[data-slot="input-group"]')?.querySelector('[data-slot="input-group-status"]'));
```

### CSS (the registry `css` / `cssVars`, merged into the global stylesheet by the shadcn CLI)

```css
:root {
  --cs-grid: rgba(20,24,40,.08);
  --cs-axis: rgba(20,24,40,.14);
  --cs-baseline: rgba(20,24,40,.1);
  --cs-halo: #fff;
  --cs-neutral: rgba(24,28,38,.6);
  --cs-brushEdge: rgba(20,24,40,.35);
  --cs-brushBg: color-mix(in srgb,#6B74F5 7%,transparent);
  --cs-chip: #14161b;
  --cs-chipSh: 0 2px 6px rgba(20,24,40,.25);
  --cs-cross: rgba(20,24,40,.4);
  --cs-ring: 0 0 0 1.5px #14161b,0 0 0 4px rgba(255,255,255,.9);
  --cs-spot: rgba(255,255,255,.6);
  --cs-splitInk: rgba(8,9,12,.85);
  --cs-chipOn: rgba(255,255,255,.8);
  --cs-chipOnSh: 0 0 0 1px rgba(20,24,40,.09),0 1px 2px rgba(20,24,40,.06);
  --cs-chipOffSh: inset 0 0 0 1px rgba(20,24,40,.07);
  --cs-clear: #fff;
  --cs-clearSh: 0 0 0 1px rgba(20,24,40,.1),0 1px 2px rgba(20,24,40,.08);
}
.dark, [data-theme="dark"] {
  --cs-grid: rgba(255,255,255,.07);
  --cs-axis: rgba(255,255,255,.12);
  --cs-baseline: rgba(255,255,255,.08);
  --cs-halo: rgba(12,13,15,.9);
  --cs-neutral: rgba(230,234,242,.7);
  --cs-brushEdge: rgba(255,255,255,.35);
  --cs-brushBg: rgba(255,255,255,.05);
  --cs-chip: rgba(40,42,48,.95);
  --cs-chipSh: inset 0 0 0 1px rgba(255,255,255,.16);
  --cs-cross: rgba(255,255,255,.4);
  --cs-ring: 0 0 0 1.5px #fff;
  --cs-spot: rgba(255,255,255,.06);
  --cs-splitInk: rgba(8,9,12,.85);
  --cs-chipOn: rgba(255,255,255,.05);
  --cs-chipOnSh: inset 0 0 0 1px rgba(255,255,255,.1);
  --cs-chipOffSh: inset 0 0 0 1px rgba(255,255,255,.06);
  --cs-clear: rgba(255,255,255,.06);
  --cs-clearSh: inset 0 0 0 1px rgba(255,255,255,.12);
}
@layer components {
  .lumen-customer-swarm {
    --cs-sp: cubic-bezier(.22,1,.36,1);
    position: relative;
    min-width: 0;
    container: cs-root / inline-size;
  }
  .lumen-customer-swarm[data-motion="subtle"],.lumen-customer-swarm[data-motion="off"] {
    --cs-sp: cubic-bezier(.4,0,.2,1);
  }
  @media (prefers-reduced-motion: reduce) {
    .lumen-customer-swarm {
      --cs-sp: cubic-bezier(.4,0,.2,1);
    }
  }
  .lumen-cs-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
  }
  .lumen-cs-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
  }
  .lumen-cs-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
  }
  .lumen-cs-summary {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font: 500 12.5px/1.3 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    color: var(--gc-ink);
    white-space: nowrap;
  }
  .lumen-cs-summary .lumen-stat-value {
    --st-fs: 12.5px;
    font-family: var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    font-weight: 500;
    letter-spacing: 0;
  }
  .lumen-cs-summary>span {
    color: var(--gc-sec);
  }
  .lumen-cs-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
  }
  .lumen-cs-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 30px;
    margin: 0;
    padding: 0 12px 0 10px;
    border: 0;
    border-radius: calc(999px * var(--lumen-radius-k, 1));
    background: var(--cs-chipOn);
    box-shadow: var(--cs-chipOnSh);
    color: var(--gc-ink);
    font: 500 12.5px/1 var(--lumen-font-sans, var(--font-sans, var(--font-geist, var(--font-geist-sans, 'Geist')))), system-ui, sans-serif;
    white-space: nowrap;
    cursor: pointer;
    transition: background min(0.25s, var(--gcp-cm)) ease,color min(0.25s, var(--gcp-cm)) ease,box-shadow min(0.25s, var(--gcp-cm)) ease;
  }
  .lumen-cs-chip[aria-pressed="false"] {
    background: transparent;
    box-shadow: var(--cs-chipOffSh);
    color: var(--gc-m45);
  }
  .lumen-cs-chip:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--gc-gap), 0 0 0 4px var(--gc-p-focus), var(--cs-chipOnSh);
  }
  .lumen-cs-chip>i {
    width: 9px;
    height: 7px;
    border-radius: calc(2px * var(--lumen-radius-k, 1));
    background: var(--cs-c);
    transition: opacity min(0.25s, var(--gcp-cm)) ease;
  }
  .lumen-cs-chip[aria-pressed="false"]>i {
    opacity: .3;
  }
  .lumen-cs-chip>b {
    font: 500 11.5px/1 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    color: var(--gc-sec);
  }
  .lumen-cs-split {
    display: grid;
    grid-template-columns: 78px minmax(0,1fr);
    align-items: center;
    gap: 8px 12px;
  }
  .lumen-cs-split>span {
    font: 400 11px/1.3 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    letter-spacing: .04em;
    color: var(--gc-sec);
  }
  .lumen-cs-bar {
    display: flex;
    gap: 2px;
    height: 20px;
  }
  .lumen-cs-seg {
    display: flex;
    align-items: center;
    min-width: 0;
    overflow: hidden;
    border-radius: calc(6px * var(--lumen-radius-k, 1));
    background: var(--cs-c);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.35);
    transition: flex calc(.9s * var(--gcp-k)) var(--cs-sp),padding min(0.5s, var(--gcp-cm)) ease;
  }
  .lumen-cs-seg>span {
    font: 500 10.5px/1 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    color: var(--cs-splitInk);
    white-space: nowrap;
    transition: opacity min(0.3s, var(--gcp-cm)) ease;
  }
  .lumen-cs-plot {
    position: relative;
    border-radius: calc(10px * var(--lumen-radius-k, 1));
    outline: none;
    cursor: crosshair;
    touch-action: pan-y;
    user-select: none;
    -webkit-user-select: none;
  }
  .lumen-cs-plot:focus-visible {
    box-shadow: 0 0 0 2px var(--gc-gap), 0 0 0 4px var(--gc-p-focus), 0 0 #0000;
  }
  .lumen-cs-abs {
    position: absolute;
    left: 0;
    top: 0;
    pointer-events: none;
  }
  .lumen-cs-grid {
    width: 1px;
    background: linear-gradient(180deg,transparent,var(--cs-grid) 20%,var(--cs-grid) 80%,transparent);
    transition: transform calc(.9s * var(--gcp-k)) var(--cs-sp),opacity min(0.4s, var(--gcp-cm)) ease;
  }
  .lumen-cs-tick {
    font: 400 11px/1 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    color: var(--gc-sec);
    white-space: nowrap;
    transition: transform calc(.9s * var(--gcp-k)) var(--cs-sp),opacity min(0.4s, var(--gcp-cm)) ease;
  }
  .lumen-cs-axis {
    position: absolute;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg,transparent,var(--cs-axis),transparent);
    pointer-events: none;
  }
  .lumen-cs-baseline {
    right: 0;
    height: 1px;
    background: linear-gradient(90deg,transparent,var(--cs-baseline) 15%,var(--cs-baseline) 85%,transparent);
    transition: transform calc(.9s * var(--gcp-k)) var(--cs-sp),opacity min(0.5s, var(--gcp-cm)) ease;
  }
  .lumen-cs-bracket {
    height: 14px;
    transition: transform calc(.9s * var(--gcp-k)) var(--cs-sp),width calc(.9s * var(--gcp-k)) var(--cs-sp),opacity min(0.4s, var(--gcp-cm)) ease;
  }
  .lumen-cs-bracket>i {
    position: absolute;
    background: var(--cs-c);
  }
  .lumen-cs-bracket>i:nth-child(1) {
    left: 0;
    right: 0;
    top: 6px;
    height: 2px;
    border-radius: calc(2px * var(--lumen-radius-k, 1));
    opacity: .55;
  }
  .lumen-cs-bracket>i:nth-child(2) {
    left: 0;
    top: 3px;
    width: 1.5px;
    height: 8px;
    border-radius: calc(1px * var(--lumen-radius-k, 1));
  }
  .lumen-cs-bracket>i:nth-child(3) {
    right: 0;
    top: 3px;
    width: 1.5px;
    height: 8px;
    border-radius: calc(1px * var(--lumen-radius-k, 1));
  }
  .lumen-cs-bracket>i:nth-child(4) {
    left: 0;
    top: 2px;
    width: 6px;
    height: 10px;
    margin-left: -3px;
    border-radius: calc(2px * var(--lumen-radius-k, 1));
    box-shadow: 0 0 0 2px var(--cs-halo);
    transition: transform calc(.9s * var(--gcp-k)) var(--cs-sp);
  }
  .lumen-cs-bracket>span {
    position: absolute;
    left: 100%;
    top: 0;
    margin-left: 8px;
    font: 400 10.5px/14px var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    color: var(--gc-sec);
    white-space: nowrap;
  }
  .lumen-cs-tile {
    width: 6px;
    height: 4.5px;
    border-radius: calc(1.5px * var(--lumen-radius-k, 1));
    background: var(--cs-c);
    transition: transform calc(.9s * var(--gcp-k)) var(--cs-sp) calc(var(--d,0) * 1ms * var(--gcp-k)),opacity calc(.45s * var(--gcp-k)) ease calc(var(--d,0) * 1ms * var(--gcp-k));
  }
  .lumen-cs-brush {
    border-radius: calc(10px * var(--lumen-radius-k, 1));
    background: var(--cs-brushBg);
    box-shadow: inset 1px 0 0 var(--cs-brushEdge),inset -1px 0 0 var(--cs-brushEdge);
  }
  .lumen-cs-chiplabel {
    position: absolute;
    display: flex;
    align-items: center;
    height: 20px;
    padding: 0 7px;
    border-radius: calc(999px * var(--lumen-radius-k, 1));
    background: var(--cs-chip);
    box-shadow: var(--cs-chipSh);
    color: #fff;
    font: 500 10.5px/1 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    white-space: nowrap;
  }
  .lumen-cs-cross {
    width: 1px;
    opacity: 0;
    background: linear-gradient(180deg,transparent,var(--cs-cross));
  }
  .lumen-cs-ringed {
    width: 14px;
    height: 12px;
    margin: -6px 0 0 -7px;
    border-radius: calc(4px * var(--lumen-radius-k, 1));
    box-shadow: var(--cs-ring),0 0 14px var(--cs-c);
  }
  .lumen-cs-lane {
    right: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    transition: transform calc(.9s * var(--gcp-k)) var(--cs-sp),opacity min(0.4s, var(--gcp-cm)) ease;
  }
  .lumen-cs-lane b {
    font: 500 12.5px/1.2 var(--lumen-font-sans, var(--font-sans, var(--font-geist, var(--font-geist-sans, 'Geist')))), system-ui, sans-serif;
    color: var(--gc-ink);
    white-space: nowrap;
  }
  .lumen-cs-lane span {
    font: 400 11px/1.2 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    color: var(--gc-sec);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
  }
  .lumen-cs-lane span>em {
    font-style: normal;
    color: var(--gc-ink);
  }
  .lumen-cs-tip-name {
    font: 600 14px/1.3 var(--lumen-font-sans, var(--font-sans, var(--font-geist, var(--font-geist-sans, 'Geist')))), system-ui, sans-serif;
    letter-spacing: -.01em;
    color: var(--gc-ink);
  }
  .lumen-cs-tip-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 2px 0 8px;
    font: 400 10.5px/1.3 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    color: var(--gc-sec);
    white-space: nowrap;
  }
  .lumen-cs-tip-meta>i {
    width: 8px;
    height: 6px;
    border-radius: calc(2px * var(--lumen-radius-k, 1));
    background: var(--cs-c);
  }
  .lumen-cs-tip-row {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    font: 400 11.5px/1.6 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
    color: var(--gc-sec);
  }
  .lumen-cs-tip-row[data-on] {
    color: var(--gc-ink);
  }
  .lumen-cs-tip-note {
    margin-top: 8px;
    font: 400 10px/1.4 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    color: var(--gc-sec);
  }
  .lumen-cs-rule {
    height: 1px;
    background: linear-gradient(90deg,transparent,var(--gc-hair),transparent);
  }
  .lumen-cs-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    font: 400 11px/1.4 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    color: var(--gc-sec);
  }
  .lumen-cs-foot>div {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
  }
  .lumen-cs-key {
    display: inline-flex;
    align-items: center;
    gap: 7px;
  }
  .lumen-cs-key>i {
    position: relative;
    flex: none;
    width: 22px;
    height: 10px;
    background: linear-gradient(var(--gc-sec),var(--gc-sec)) 0 4px / 100% 2px no-repeat,linear-gradient(var(--gc-sec),var(--gc-sec)) 0 1px / 1.5px 8px no-repeat,linear-gradient(var(--gc-sec),var(--gc-sec)) 100% 1px / 1.5px 8px no-repeat;
  }
  .lumen-cs-key>i::after {
    content: "";
    position: absolute;
    left: 8px;
    top: 0;
    width: 6px;
    height: 10px;
    border-radius: calc(2px * var(--lumen-radius-k, 1));
    background: var(--cs-neutral);
  }
  .lumen-cs-clear {
    height: 28px;
    margin: 0;
    padding: 0 11px;
    border: 0;
    border-radius: calc(999px * var(--lumen-radius-k, 1));
    background: var(--cs-clear);
    box-shadow: var(--cs-clearSh);
    color: var(--gc-ink);
    font: 500 11.5px/1 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    cursor: pointer;
  }
  .lumen-cs-clear:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--gc-gap), 0 0 0 4px var(--gc-p-focus), var(--cs-clearSh);
  }
}
```

The lumen foundation (the tokens listed above, keyframes and motion levels) installs once with the first component; its CSS is public at https://beautiful-ui.dev/r/lumen-foundation.json.
