# Metrics Dashboard (Lumen Halo): prompt.md (v1.0.0)

- id: `lumen-metrics-dashboard` · version 1.0.0 · block · free
- category: Data
- 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-metrics-dashboard.json`
- npm dependencies: recharts@^3
- registry dependencies: utils, @beautiful-ui/lumen-chart, @beautiful-ui/lumen-chart-shadcn, @beautiful-ui/lumen-stat, @beautiful-ui/lumen-tabs, https://beautiful-ui.dev/r/lumen-foundation.json
- docs: https://beautiful-ui.dev/components/lumen-metrics-dashboard
- The install command carries everything this item needs (files, CSS, tokens, npm and registry dependencies). Prefer it to copying source by hand.

A product's numbers at a glance: the range sets everything, the KPI tabs switch the chart's metric, the chart puts the range beside the same days before it (dashed), and two ranked lists read with proportional bars.

## Build it
- Stack: React 19 (`ref` is a plain prop), TypeScript, Tailwind CSS v4 utilities, a shadcn-initialised project with the `@/*` alias.
- Packages: `recharts@^3`.
- Files: `components/ui/lumen/blocks/metrics-dashboard.tsx`; shared code: `lib/beautiful-ui/lumen/metrics-dashboard-model.ts`.
- Registry dependencies, installed with it automatically: shadcn `utils` (cn), `lumen-chart`, `lumen-chart-shadcn`, `lumen-stat`, `lumen-tabs`, `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: `MetricsDashboard`, `RankedList`, 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 { MetricsDashboard } from "@/components/ui/lumen/blocks/metrics-dashboard";
```

## Parts

| Part | data-slot | What it is for |
|---|---|---|
| `MetricsDashboard` | `metrics-dashboard` | The whole block. |
| `RankedList` | `ranked-list` | A ranked list with proportional bars. |

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/blocks/metrics-dashboard.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
- metrics dashboard, KPI dashboard, product analytics overview, website analytics, top pages, traffic sources, period comparison, Lumen
- The first screen of an analytics product: a few KPIs, one chart, what's driving them
- A settings-free overview that reads well at a glance

### Not when
- Deep investigation with filters and linked views: use Customer Intelligence

## Mistakes
- Keep activation a rate (activated ÷ signups), not a sum
- Pass at least twice the range of days, or the comparison is empty

## Usage

```tsx
import { MetricsDashboard } from "@/components/ui/lumen/blocks/metrics-dashboard";

<MetricsDashboard days={days} lists={({ visitors }) => ({ pages: topPages(visitors), sources })} />
```

## Props

| Prop | Type | Default | What it does |
|---|---|---|---|
| `days` | `{ date, visitors, signups, activated, revenue }[]` | `180 days of Lumen's numbers` | Daily numbers, oldest first. Twice the longest range gives a full comparison; otherwise the comparison reads as missing. |
| `lists` | `({ visitors, signups }) => { pages, sources }` |  | The ranked lists for the range: rows of { id, label, value }. |
| `range / defaultRange / onRangeChange` | `"7" \| "30" \| "90"` | `"30"` | The range in days. |
| `metric / defaultMetric / onMetricChange` | `"visitors" \| "signups" \| "activation" \| "revenue"` |  | The KPI the chart shows. |
| `title` | `ReactNode` | `"Product analytics"` | The heading. |
| `RankedList` | `component` |  | The ranked list with proportional bars, for your own layouts. Pass total so shares are of the whole, not only the listed rows. |

Full docs: https://beautiful-ui.dev/components/lumen-metrics-dashboard

## 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 |
|---|---|
| ← / → | Range and KPI tabs |

## Motion inventory

| Interaction | What moves |
|---|---|
| Range | Values roll, the chart redraws, list bars grow from the left |

## Accessibility contract (preserve when editing)
- The KPIs are tabs; each tab's panel is its chart
- The chart is a labelled figure with a data table of every day, and Recharts' keyboard layer
- Ranked lists are ordered lists with the value and share in text

## Install

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

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

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

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

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

## Credits
- Built on shadcn/ui (https://ui.shadcn.com)

## Source (Base UI build)

### components/ui/lumen/blocks/metrics-dashboard.tsx

```tsx
"use client";

import * as React from "react";
import { cn } from "@/lib/utils";
import { CartesianGrid, Line, LineChart as RLineChart, XAxis, YAxis } from "recharts";
import { ChartSurface, ChartTable } from "@/components/ui/lumen/chart";
import { StatDelta, StatValue } from "@/components/ui/lumen/stat";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/lumen/tabs";
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@/components/ui/lumen/charts/chart-shadcn";
import { ChartMode, ChartModes } from "@/components/ui/lumen/chart-client";
import { comparisonRows, metricsSample, sampleLists, summarize, windows, withShare, type MetricsDay, type MetricsKey, type RankedRow } from "@/lib/beautiful-ui/lumen/metrics-dashboard-model";

/*
 * Metrics Dashboard: a product's numbers at a glance. The range sets everything; the KPI tabs are
 * the chart's metric switch; the chart puts the range beside the same days before it (dashed); two
 * ranked lists read with proportional bars. Pass your days (and lists), or keep the sample.
 *
 *   <MetricsDashboard days={days} lists={(range) => ({ pages, sources })} />
 */

const RANGES = [
  { id: "7", label: "7D", words: "7 days" },
  { id: "30", label: "30D", words: "30 days" },
  { id: "90", label: "90D", words: "90 days" },
] as const;

const KPIS: { id: MetricsKey; label: string; format: Parameters<typeof StatValue>[0]["format"] }[] = [
  { id: "visitors", label: "Visitors", format: { notation: "compact" } },
  { id: "signups", label: "Signups", format: { notation: "standard" } },
  { id: "activation", label: "Activation", format: { style: "percent" } },
  { id: "revenue", label: "New revenue", format: { style: "currency", notation: "compact" } },
];

const CONFIG: ChartConfig = {
  current: { label: "This period", theme: { light: "#5b63e8", dark: "#8b93ff" } },
  previous: { label: "Period before", theme: { light: "#9aa0ab", dark: "#6b7079" } },
};

export type MetricsDashboardProps = Omit<React.ComponentProps<"section">, "title"> & {
  /** Daily numbers, oldest first; at least twice the longest range for a full comparison. */
  days?: MetricsDay[] | undefined;
  /** The two ranked lists for a range (top pages by visitors, sources by signups). */
  lists?: ((totals: { visitors: number; signups: number }) => { pages: RankedRow[]; sources: RankedRow[] }) | undefined;
  range?: "7" | "30" | "90" | undefined;
  defaultRange?: "7" | "30" | "90" | undefined;
  onRangeChange?: ((range: "7" | "30" | "90") => void) | undefined;
  metric?: MetricsKey | undefined;
  defaultMetric?: MetricsKey | undefined;
  onMetricChange?: ((metric: MetricsKey) => void) | undefined;
  title?: React.ReactNode;
};

export function MetricsDashboard({
  days = metricsSample,
  lists = ({ visitors, signups }) => sampleLists(visitors, signups),
  range: rangeProp,
  defaultRange = "30",
  onRangeChange,
  metric: metricProp,
  defaultMetric = "visitors",
  onMetricChange,
  title = "Product analytics",
  className,
  ...props
}: MetricsDashboardProps) {
  const [ownRange, setOwnRange] = React.useState(defaultRange);
  const [ownMetric, setOwnMetric] = React.useState<MetricsKey>(defaultMetric);
  const range = rangeProp ?? ownRange;
  const metric = metricProp ?? ownMetric;
  const n = Number(range);
  const { cur, prev } = windows(days, n);
  const now = summarize(cur);
  const before = prev.length === n ? summarize(prev) : null;
  const rows = comparisonRows(days, n, metric);
  const { pages, sources } = lists({ visitors: now.visitors ?? 0, signups: now.signups ?? 0 });
  const words = RANGES.find((r) => r.id === range)!.words;
  const id = React.useId();
  const dayWords = (v: unknown) => new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", timeZone: "UTC" }).format(new Date(`${String(v)}T00:00:00Z`));
  return (
    <section aria-labelledby={id} data-slot="metrics-dashboard" className={cn(`lumen-metrics`, className)} {...props}>
      <header className={`lumen-metrics-head`}>
        <h2 id={id}>{title}</h2>
        <ChartModes
          value={range}
          onValueChange={(v) => {
            const r = v as typeof range;
            if (rangeProp === undefined) setOwnRange(r);
            onRangeChange?.(r);
          }}
          aria-label="Range"
        >
          {RANGES.map((r) => (
            <ChartMode key={r.id} value={r.id} aria-label={`Last ${r.words}`}>
              {r.label}
            </ChartMode>
          ))}
        </ChartModes>
      </header>
      <ChartSurface className={`lumen-metrics-card`}>
        {/* The KPIs are the chart's tabs: each tab is a metric, its panel the chart. */}
        <Tabs
          value={metric}
          onValueChange={(v) => {
            if (metricProp === undefined) setOwnMetric(v as MetricsKey);
            onMetricChange?.(v as MetricsKey);
          }}
        >
          <TabsList className={`lumen-metrics-kpis`} aria-label={`Metrics, vs the ${words} before`}>
            {KPIS.map((k) => (
              <TabsTrigger key={k.id} value={k.id} className={`lumen-metrics-kpi`}>
                <span className={`lumen-metrics-kpi-label`}>{k.label}</span>
                <StatValue className={`lumen-metrics-kpi-value`} value={now[k.id]} format={k.format} />
                <StatDelta value={now[k.id]} previous={before?.[k.id] ?? null} format={k.format} />
              </TabsTrigger>
            ))}
          </TabsList>
          {KPIS.map((k) => (
            <TabsContent key={k.id} value={k.id} className={`lumen-metrics-panel`}>
              {k.id === metric && <MetricChart rows={rows} metric={k} words={words} dayWords={dayWords} />}
            </TabsContent>
          ))}
        </Tabs>
      </ChartSurface>
      <div className={`lumen-metrics-lists`}>
        <RankedList title="Top pages" unit="visitors" rows={pages} total={now.visitors ?? undefined} />
        <RankedList title="Signups by source" unit="signups" rows={sources} total={now.signups ?? undefined} />
      </div>
    </section>
  );
}

/** The chart: the range beside the same days before it (dashed), on shadcn's chart API, with a data table. */
function MetricChart({ rows, metric, words, dayWords }: { rows: { date: string; current: number | null; previous: number | null }[]; metric: (typeof KPIS)[number]; words: string; dayWords: (v: unknown) => string }) {
  const rate = metric.id === "activation";
  const fmt = (v: number) => new Intl.NumberFormat("en-US", rate ? { style: "percent", maximumFractionDigits: 1 } : metric.id === "revenue" ? { style: "currency", currency: "USD", notation: "compact" } : { notation: "compact" }).format(v);
  const label = `${metric.label}, last ${words} against the ${words} before`;
  return (
    <figure className={`lumen-metrics-figure`} aria-label={label}>
      <ChartContainer config={CONFIG} className={`lumen-metrics-chart`}>
        <RLineChart accessibilityLayer data={rows} margin={{ left: 4, right: 12, top: 8 }}>
          <CartesianGrid vertical={false} strokeDasharray="2 4" />
          <XAxis dataKey="date" tickLine={false} axisLine={false} tickMargin={8} minTickGap={24} tickFormatter={dayWords} />
          {/* Rates sit on a fitted axis: from zero, a 3-point change would read flat. */}
          <YAxis tickLine={false} axisLine={false} width={48} tickFormatter={fmt} domain={rate ? ["auto", "auto"] : [0, "auto"]} />
          <ChartTooltip content={<ChartTooltipContent labelFormatter={(l) => dayWords(l)} formatter={(v, name) => `${CONFIG[String(name)]?.label as string}: ${typeof v === "number" ? fmt(v) : "—"}`} />} />
          <ChartLegend content={<ChartLegendContent />} />
          <Line dataKey="current" type="monotone" stroke="var(--color-current)" strokeWidth={2} dot={false} />
          <Line dataKey="previous" type="monotone" stroke="var(--color-previous)" strokeWidth={1.6} strokeDasharray="4 4" dot={false} />
        </RLineChart>
      </ChartContainer>
      <ChartTable caption={label} columns={["Day", "This period", "Period before"]} rows={rows.map((r) => [dayWords(r.date), r.current == null ? "—" : fmt(r.current), r.previous == null ? "—" : fmt(r.previous)])} />
    </figure>
  );
}

/** A ranked list with bars in proportion to the largest row. */
export function RankedList({ title, unit, rows, total: totalProp, className, ...props }: Omit<React.ComponentProps<"section">, "title"> & { title: string; unit: string; rows: readonly RankedRow[]; /** The whole the shares are of (all visitors, not only the listed pages). Default: the listed rows. */ total?: number | undefined }) {
  const id = React.useId();
  const nf = new Intl.NumberFormat("en-US");
  const total = totalProp ?? rows.reduce((s, r) => s + r.value, 0);
  return (
    <section aria-labelledby={id} data-slot="ranked-list" className={cn(`lumen-metrics-list`, className)} {...props}>
      <h3 id={id}>
        {title}
        <span>{unit}</span>
      </h3>
      <ol>
        {withShare(rows).map((r) => (
          <li key={r.id} style={{ "--share": r.share } as React.CSSProperties}>
            <span className={`lumen-metrics-list-label`}>{r.label}</span>
            <span className={`lumen-metrics-list-value`}>
              {nf.format(r.value)}
              <span>{total ? `${Math.round((r.value / total) * 100)}%` : "—"}</span>
            </span>
          </li>
        ))}
      </ol>
    </section>
  );
}
```

### lib/beautiful-ui/lumen/metrics-dashboard-model.ts

```tsx
/*
 * The Metrics Dashboard block's model and sample: daily product numbers, summed over a range and the
 * same-length range before it. Plain data (no React), seeded so server and client agree.
 */

export type MetricsDay = { date: string; visitors: number; signups: number; activated: number; revenue: number };
export type MetricsKey = "visitors" | "signups" | "activation" | "revenue";
export type RankedRow = { id: string; label: string; value: number };

/** Sums for a range of days: activation is a rate (activated ÷ signups), the rest are totals. */
export function summarize(days: readonly MetricsDay[]) {
  const sum = (k: keyof Omit<MetricsDay, "date">) => days.reduce((s, d) => s + d[k], 0);
  const signups = sum("signups");
  return { visitors: sum("visitors"), signups, activation: signups ? sum("activated") / signups : null, revenue: sum("revenue") } as Record<MetricsKey, number | null>;
}

/** The last `n` days and the `n` before them. */
export function windows<T>(days: readonly T[], n: number) {
  const cur = days.slice(-n);
  const prev = days.slice(-2 * n, -n);
  return { cur, prev };
}

/** One chart row per day of the range, with the same day of the previous range beside it. */
export function comparisonRows(days: readonly MetricsDay[], n: number, key: MetricsKey) {
  const { cur, prev: shortPrev } = windows(days, n);
  // A previous range shorter than the current one is missing its oldest days: pad it on the left so
  // each day still meets the day one range before it.
  const prev: (MetricsDay | undefined)[] = [...Array<undefined>(Math.max(0, cur.length - shortPrev.length)).fill(undefined), ...shortPrev];
  const pick = (d: MetricsDay | undefined) => (d ? (key === "activation" ? (d.signups ? d.activated / d.signups : null) : d[key]) : null);
  return cur.map((d, i) => ({ date: d.date, current: pick(d), previous: pick(prev[i]) }));
}

/** Rows with their share of the largest, for proportional bars. */
export function withShare(rows: readonly RankedRow[]) {
  const max = Math.max(1, ...rows.map((r) => r.value));
  return [...rows].sort((a, b) => b.value - a.value).map((r) => ({ ...r, share: r.value / max }));
}

// ------------------------------------------------------------------------------------------------
// The sample: Lumen's own site and product, 180 days to Sep 27, 2026.
// ------------------------------------------------------------------------------------------------

const END = Date.UTC(2026, 8, 27);
export const metricsSample: MetricsDay[] = Array.from({ length: 180 }, (_, i) => {
  const t = END - (179 - i) * 86_400_000;
  const dow = new Date(t).getUTCDay();
  const weekend = dow === 0 || dow === 6;
  const trend = 1 + i / 420;
  const wave = 1 + 0.08 * Math.sin(i / 6.5) + 0.04 * Math.sin(i * 1.7);
  const visitors = Math.round((weekend ? 1850 : 3100) * trend * wave);
  const signups = Math.round(visitors * (0.052 + 0.006 * Math.sin(i / 9)));
  const activated = Math.round(signups * (0.41 + i / 1400 + 0.03 * Math.sin(i / 5)));
  const revenue = Math.round(activated * (62 + 8 * Math.sin(i / 11)));
  return { date: new Date(t).toISOString().slice(0, 10), visitors, signups, activated, revenue };
});

/** Top pages and sources for the sample, scaled to a range's visitors and signups. */
export function sampleLists(visitors: number, signups: number) {
  const pages: [string, string, number][] = [
    ["/", "Home", 0.31],
    ["/pricing", "Pricing", 0.17],
    ["/charts/trend", "Trend chart", 0.11],
    ["/docs/install", "Install guide", 0.09],
    ["/blog/lit-cells", "Blog: lit cells", 0.06],
  ];
  const sources: [string, string, number][] = [
    ["search", "Search", 0.38],
    ["direct", "Direct", 0.24],
    ["referral", "Referrals", 0.18],
    ["social", "Social", 0.12],
    ["newsletter", "Newsletter", 0.08],
  ];
  return {
    pages: pages.map(([id, label, s]) => ({ id, label, value: Math.round(visitors * s) })),
    sources: sources.map(([id, label, s]) => ({ id, label, value: Math.round(signups * s) })),
  };
}
```

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

```css
@layer components {
  .lumen-metrics {
    container-type: inline-size;
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
    color: var(--gc-ink);
    font: 400 13.5px/1.5 var(--lumen-font-sans, var(--font-sans, var(--font-geist, var(--font-geist-sans, 'Geist')))), system-ui, sans-serif;
  }
  .lumen-metrics-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
  }
  .lumen-metrics-head h2 {
    margin: 0;
    font: 600 20px/1.2 var(--lumen-font-sans, var(--font-sans, var(--font-geist, var(--font-geist-sans, 'Geist')))), system-ui, sans-serif;
    letter-spacing: -.02em;
  }
  .lumen-metrics-card {
    gap: 12px;
  }
  .lumen-metrics .lumen-tabs-list.lumen-metrics-kpis {
    display: grid;
    grid-template-columns: repeat(4,minmax(0,1fr));
    width: 100%;
    height: auto;
    gap: 4px;
    padding: 4px;
  }
  .lumen-metrics .lumen-tabs-trigger.lumen-metrics-kpi {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 4px;
    height: auto;
    min-width: 0;
    padding: 12px 14px;
    text-align: start;
    white-space: normal;
  }
  .lumen-metrics-kpi-label {
    font: 500 10.5px/1.4 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--gc-sec);
  }
  .lumen-metrics .lumen-stat-value.lumen-metrics-kpi-value {
    font-size: 24px;
    line-height: 1.05;
  }
  .lumen-metrics .lumen-metrics-kpi:is([aria-selected="true"],[data-selected],[data-state="active"]) :is(.lumen-metrics-kpi-label,.lumen-metrics-kpi-value) {
    color: var(--gc-p-thumbInk);
  }
  .lumen-metrics-panel {
    padding-top: 12px;
  }
  .lumen-metrics-figure {
    margin: 0;
  }
  .lumen-metrics .lumen-chart-shadcn.lumen-metrics-chart {
    aspect-ratio: auto;
    height: 240px;
  }
  .lumen-metrics-lists {
    display: grid;
    grid-template-columns: repeat(2,minmax(0,1fr));
    gap: 16px;
  }
  @container (max-width:640px) {
    .lumen-metrics .lumen-tabs-list.lumen-metrics-kpis {
      grid-template-columns: repeat(2,minmax(0,1fr));
    }
    .lumen-metrics-lists {
      grid-template-columns: minmax(0,1fr);
    }
  }
  .lumen-metrics-list {
    padding: 16px;
    border-radius: calc(18px * var(--lumen-radius-k, 1));
    background: color-mix(in srgb,var(--gc-ink) 3%,transparent);
    box-shadow: inset 0 0 0 1px var(--gc-hair);
  }
  .lumen-metrics-list h3 {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin: 0 0 10px;
    font: 600 14px/1.3 var(--lumen-font-sans, var(--font-sans, var(--font-geist, var(--font-geist-sans, 'Geist')))), system-ui, sans-serif;
  }
  .lumen-metrics-list h3>span {
    font: 500 10.5px/1 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--gc-sec);
  }
  .lumen-metrics-list ol {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .lumen-metrics-list li {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 34px;
    padding: 0 10px;
    border-radius: calc(9px * var(--lumen-radius-k, 1));
    isolation: isolate;
  }
  .lumen-metrics-list li::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    width: calc(var(--share,0) * 100%);
    border-radius: inherit;
    background: color-mix(in srgb,var(--gc-acc) 16%,transparent);
    transform-origin: left;
    animation: lumen-metrics-bar calc(.6s * var(--gcp-k,1)) var(--gc-sp) both;
  }
  @keyframes lumen-metrics-bar {
    from {
      transform: scaleX(0);
    }
  }
  .lumen-metrics-list-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .lumen-metrics-list-value {
    display: inline-flex;
    gap: 10px;
    font: 500 12.5px/1 var(--lumen-font-mono, var(--font-mono, var(--font-geist-mono, 'Geist Mono'))), ui-monospace, monospace;
    font-variant-numeric: tabular-nums;
  }
  .lumen-metrics-list-value>span {
    width: 3ch;
    text-align: end;
    color: var(--gc-sec);
  }
  @media (prefers-reduced-motion:reduce) {
    .lumen-metrics-list li::before {
      animation: none;
    }
  }
}
```

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.
