# Metric picker

> A dashboard’s metric picker whose submenus hold while the pointer cuts across other sources, with a prediction cone whose apex steps back as it nears them.

The metric control of an analytics dashboard: sources of data in groups on the left, the metrics of the lit one on the right. A pointer heading diagonally for a metric crosses other sources on the way. A cone from just behind the pointer to the metrics’ edge tells the picker it is on its way, so the submenu holds. Every box is measured once, when the popover opens.

A React component by Bruno Delorence: https://brunodelorence.com/components/metric-picker. MIT License, Copyright (c) 2026 Bruno Delorence.

Category: Inputs.

## Anatomy

1. **Trigger**: Names the pick as source and metric, with the source’s icon. A click or ↓ opens the popover; it is a combobox’s button.
2. **Popover**: Opens over the chart in 140 ms: the search on top, the sources at the left, the lit source’s metrics at the right.
3. **Search**: Filters both columns as you type: a source stays with all its metrics when its name matches, or with the metrics that do. Esc closes.
4. **Source row**: Hovering lights it and opens its metrics at once; the lit row shows its chevron.
5. **Prediction cone**: A triangle from just behind the pointer to the metrics’ near edge. While the pointer stays inside it heading right, the sources it crosses are left alone; a 320 ms rest hands over.
6. **Metrics**: The lit source’s metrics, under its name. Pointing at a row lights it; a click picks it and closes.
7. **Metric row**: One metric, lit under the pointer or the arrow keys. Picking it swaps the chart’s line and calls `onChange`.
8. **Cone switch**: Draws the cone or hides it. It works either way; hidden, the picker looks like any menu.

## When to use

- Picking what a chart plots when the choices come in two levels: a source of data, then one of its metrics.
- Six to a few dozen sources in a handful of groups, each with its own short list of metrics.
- A dashboard toolbar, where the pick has to read as one phrase (“Requests Count”) beside the other tools.
- Pointer users who cross the list on a diagonal to reach a metric: the cone keeps the submenu they aimed at open.
- Keyboard users too: one search field filters both columns, and the arrow keys walk the sources and the metrics.

## When not to use

- A flat list of a few metrics with no source behind them. Use a select or a segmented control.
- Several metrics plotted at once. Use a multi-select with checkboxes, or a legend with toggles.
- Sources with hundreds of metrics each: the submenu doesn’t scroll. Use a search-first command palette.
- Touch-only screens, where nothing hovers: the cone does nothing there. Use a two-step sheet (source, then metric).
- Navigation between pages. Use a menu or links; this picks a value and closes.

## Installation

Install it with the shadcn CLI, into components/metric-picker of any React project:

```sh
npx shadcn@latest add https://brunodelorence.com/r/metric-picker.json
```

Or copy all the source files below into one folder of your project. Either way it needs React 19 and a bundler with CSS Modules (Next.js, Vite).

It has a dark colour scheme. Its colours are CSS light-dark() pairs, kept inside @supports so that builds which rewrite light-dark() for older browsers leave them alone: it follows your page’s color-scheme (light unless the page sets one) or its colorScheme prop, with nothing to configure. Browsers without light-dark() show it light.

## Usage

```tsx
import { MetricPicker } from './metric-picker';
import type { MetricValue } from './metrics';

export function UsagePanel({ onMetric }: { onMetric: (value: MetricValue) => void }) {
  return (
    <MetricPicker
      defaultValue={{ source: 'function-calls', metric: 'duration' }}
      defaultShowCone={false}
      onChange={onMetric}
      autoplay={false}
    />
  );
}
```

## States

- **Closed**: The trigger names the pick; the chart under it draws that metric’s line.
- **Open**: The picked source is lit and its metrics show, the picked metric among them.
- **On its way**: The pointer rests on Image Resizes heading for its metrics: the cone is drawn, and the rows below wait.
- **Cone hidden**: The same moment with the switch off: the cone still keeps the submenu, unseen.
- **Keyboard in the metrics**: → moves into the metrics and lights one; ← goes back, Enter picks it.
- **Filtered**: “duration” keeps four sources, each with just its matching metrics.
- **No match**: Nothing left in either column: a quiet line says so, and Enter does nothing.

## How it works

- When the popover opens, and again when a search changes its rows, it reads its boxes once: the list’s rows, where the metrics begin and the popover’s place on screen. From then on, following the pointer is arithmetic on those numbers plus the list’s scroll offset, kept from its scroll events, with no read of the page.
- Each move tests the new point against the cone drawn at the last one: a triangle from just behind the pointer to the top of the first metric and the bottom of the column. Inside it and still heading right, the pointer leaves the sources it crosses alone; outside it, or heading back, the source under the pointer takes over at once.
- The apex steps back behind the pointer by a buffer that grows, eased in, as the pointer nears the metrics: nothing at the far side of the list, most of a row’s height at the edge, where the cone would otherwise be a sliver a shaky hand slips out of.
- Resting 320 ms on another source inside the cone hands the submenu to it, so a reader who changes their mind halfway is not left with the wrong metrics.
- The cone is a plain box over the popover, cut to the triangle by a clip-path set on every move, and React renders only when the lit source or metric changes. Unticking Show cone hides the drawing; the behaviour stays on.
- Every colour is a light-dark() token on the root, the chart’s series and the cone included, so it follows the page’s color-scheme, or the colorScheme prop, with no colour read in script.

## API reference

- **sources** (`SourceGroup[]`, default `SOURCES`): The sources of data in labelled groups. Each source has an id, a label, an icon and the metrics it can chart.
- **label** (`string`, default `'Metric'`): The control’s label, above the toolbar.
- **defaultValue** (`MetricValue`, default `{ source: 'requests', metric: 'count' }`): The metric picked at first.
- **onChange** (`(value: MetricValue) => void`): Called when the reader picks a metric.
- **defaultShowCone** (`boolean`, default `true`): Whether the prediction cone is drawn at first. It works either way.
- **defaultState** (`MetricPickerState`): Opens in a state, with the demo off: the popover open, a search typed (query), the lit source and metric (active, sub, column), and a pointer resting on a source with its cone drawn (pointer).
- **autoplay** (`boolean`, default `true`): While on screen and untouched, a demo pointer opens the picker, travels diagonally to the metrics of two sources across the others, and picks one.
- **colorScheme** (`'light' | 'dark' | 'system'`): Its colours: light, dark, or the reader’s system setting. Without it the component follows the page’s color-scheme, which is light unless the page sets one.
- **className** (`string`): Extra class for the root, a dashboard stage of its own.

## Accessibility

- The trigger names its pick (“Metric Requests Count”) and says whether the popover is open. The search field inside is a combobox that owns two listboxes, the sources in labelled groups and the lit source’s metrics, and aria-activedescendant follows the lit option.
- ↑ and ↓ move through the sources or the metrics, and the list scrolls to the lit row; → goes into the metrics once the caret is at the end of the text, and ← comes back. Enter picks, Escape closes and returns the focus to the trigger, typing filters both columns, and ↓ on the trigger opens it.
- A pick is announced (“Metric: Gateway Calls Duration”). The cone, the chart and the toolbar’s other controls are decorative and hidden from screen readers. On touch, a tap lights a source and a tap on a metric picks it.
- Under reduced motion the popover appears at once and the demo does not play.

## Source

### metric-picker/metric-picker.tsx

```tsx
// Copyright (c) 2026 Bruno Delorence. MIT License.

'use client';

import {
  useEffect,
  useId,
  useLayoutEffect,
  useRef,
  useState,
  type KeyboardEvent,
  type PointerEvent,
} from 'react';
import { Chart, seriesOf } from './chart';
import { coneAt, inTriangle, locate, type Layout, type Point } from './cone';
import { DemoPointer, runDemo } from './demo-pointer';
import { Calendar, ChevronRight, Plus, Selector, SourceIcon } from './icons';
import { SOURCES, filterSources, type MetricValue, type Source, type SourceGroup } from './metrics';
import { useAutoplay } from './use-autoplay';
import styles from './metric-picker.module.css';

/*
 * Metric picker: the metric control of an analytics dashboard. The trigger opens a popover with a
 * search field, the sources of data in groups, and at their right the metrics of the lit source.
 *
 * - Hovering a source opens its metrics. To reach one, a pointer moving diagonally crosses the
 *   sources in between, and each would take over on the way. The prediction cone prevents it
 *   (cone.ts): a triangle from just behind the pointer to the metrics' near edge. While the next
 *   position is inside it and still heading right, the sources crossed are left alone; resting on
 *   one for a moment, or turning back, hands over to the source under the pointer.
 * - The cone's apex steps back behind the pointer as it nears the metrics, so the cone stays wide
 *   around it where it would otherwise be a sliver.
 * - Every box is measured once, when the popover opens (and again when a search changes its rows).
 *   Following the pointer is arithmetic on those numbers, and the cone is a box whose clip-path
 *   is set on each move; React renders only when the lit source or metric changes.
 * - Keyboard: a combobox. The arrow keys move through the sources, → goes into the metrics and ←
 *   back, Enter picks, Escape closes, and typing filters both columns.
 * - While on screen and untouched, a demo pointer opens the picker and travels diagonally to the
 *   metrics of two sources, across the others, then picks one (demo-pointer.tsx).
 * - Light or dark: every colour is a light-dark() token in the CSS, so the scheme is the page's
 *   color-scheme, or the one the colorScheme prop sets.
 */

/** What is lit: the source whose metrics show, the metric lit among them, the arrow keys' column. */
type View = { active: string; sub: number; column: 'list' | 'sub' };

/**
 * The cone of the last move, that move's point (in the popover's px), and how far the list is
 * scrolled, kept by its scroll events.
 */
type Zone = {
  cone: [Point, Point, Point] | null;
  last: Point | null;
  timer: number;
  scroll: number;
};

/** How long the pointer may rest on another source inside the cone before it takes over, in ms. */
const PAUSE = 320;

const allSources = (groups: SourceGroup[]) => groups.flatMap((group) => group.sources);

/**
 * A state to open in, for docs and tests: the popover open or not, a search typed, the lit source
 * and metric, and a pointer on its way from a source to the metrics, with its cone drawn. Giving
 * one turns the demo off.
 */
export type MetricPickerState = {
  /** The popover open. */
  open?: boolean;
  /** The search typed in the popover. */
  query?: string;
  /** The source lit, whose metrics show; the picked one when left out. */
  active?: string;
  /** The metric lit among them, by index. */
  sub?: number;
  /** Where the arrow keys are: 'sub' lights the metric as the keyboard would. */
  column?: 'list' | 'sub';
  /**
   * A pointer resting on this source's row, at this share of the list's width (0.5 when left
   * out), on its way to the metrics: its cone is drawn (when the cone is shown).
   */
  pointer?: { source: string; x?: number };
};

export function MetricPicker({
  sources = SOURCES,
  label = 'Metric',
  defaultValue = { source: 'requests', metric: 'count' },
  onChange,
  defaultShowCone = true,
  autoplay = true,
  colorScheme,
  defaultState,
  className = '',
}: {
  /** The sources of data, in groups, each with the metrics it can chart. */
  sources?: SourceGroup[];
  /** The control's label, above the toolbar. */
  label?: string;
  /** The metric picked at first. */
  defaultValue?: MetricValue;
  /** Called when the reader picks a metric. */
  onChange?: (value: MetricValue) => void;
  /** Whether the prediction cone is drawn at first. It works either way. */
  defaultShowCone?: boolean;
  /** While on screen and untouched, a demo pointer opens the picker and travels to the metrics. */
  autoplay?: boolean;
  /** Light or dark colours, or 'system' for the reader's; without it, the page's color-scheme. */
  colorScheme?: 'light' | 'dark' | 'system';
  /** Opens in this state (the popover, a search, the lit rows, a cone), with the demo off. */
  defaultState?: MetricPickerState;
  className?: string;
}) {
  const { ref: root, playing, take } = useAutoplay<HTMLDivElement>(6000);
  const [open, setOpen] = useState(defaultState?.open ?? false);
  const [value, setValue] = useState(defaultValue);
  const [query, setQuery] = useState(defaultState?.query ?? '');
  const [view, setView] = useState<View>({
    active: defaultState?.active ?? defaultValue.source,
    sub: defaultState?.sub ?? 0,
    column: defaultState?.column ?? 'list',
  });
  const [showCone, setShowCone] = useState(defaultShowCone);
  const [focused, setFocused] = useState(false);
  const [message, setMessage] = useState('');
  const id = useId();

  const stage = useRef<HTMLDivElement>(null);
  const popover = useRef<HTMLDivElement>(null);
  const trigger = useRef<HTMLButtonElement>(null);
  const input = useRef<HTMLInputElement>(null);
  const cone = useRef<HTMLDivElement>(null);
  const pointer = useRef<HTMLSpanElement>(null);
  /** A box in the middle of the cone, kept in step with it (a target for the docs' anatomy). */
  const coneMiddle = useRef<HTMLSpanElement>(null);
  /** The pointer of defaultState, drawn once, when the popover first opens. */
  const startPointer = useRef(defaultState?.pointer);
  /** The popover's boxes, measured when it opens; null while it is closed. */
  const layout = useRef<Layout | null>(null);
  /** The stage on screen and the popover in it, also read when it opens (and on scroll). */
  const frame = useRef({ left: 0, top: 0, scale: 1, x: 0, y: 0 });
  const zone = useRef<Zone>({ cone: null, last: null, timer: 0, scroll: 0 });
  /** The state as of the last change, for handlers and timers that run between renders. */
  const live = useRef({ view, open, value, shown: allSources(sources) });
  /** Whether the next open moves the focus into the search field: not when the demo opens it. */
  const focusOnOpen = useRef(false);
  const stopDemo = useRef<(() => void) | null>(null);

  const groups = filterSources(sources, query);
  const shown = allSources(groups);
  const activeSource = shown.find((source) => source.id === view.active) ?? shown[0];
  const metrics = activeSource?.metrics ?? [];
  const sub = Math.max(0, Math.min(view.sub, metrics.length - 1));
  const picked = allSources(sources).find((source) => source.id === value.source);
  const pickedMetric = picked?.metrics.find((entry) => entry.id === value.metric);

  const listId = `${id}-list`;
  const subId = `${id}-metrics`;
  const optionId = (source: string) => `${id}-source-${source}`;
  const metricId = (index: number) => `${id}-metric-${index}`;

  useLayoutEffect(() => {
    live.current.shown = shown;
  });

  /** Repeats are made to differ, so screen readers announce them again. */
  const announce = (text: string) => setMessage((now) => (now === text ? `${text}\u00a0` : text));

  const set = (next: Partial<View>) => {
    const now = live.current.view;
    const view = { ...now, ...next };
    if (view.active === now.active && view.sub === now.sub && view.column === now.column) return;
    live.current.view = view;
    setView(view);
  };

  const metricsOf = (source: string) =>
    live.current.shown.find((entry) => entry.id === source)?.metrics ?? [];

  /** Draws the cone, or hides it. */
  const draw = (points: [Point, Point, Point] | null) => {
    const box = cone.current;
    if (!box) return;
    if (!points) {
      box.style.visibility = 'hidden';
      return;
    }
    box.style.clipPath = `polygon(${points.map((p) => `${p.x.toFixed(1)}px ${p.y.toFixed(1)}px`).join(', ')})`;
    box.style.visibility = 'visible';
    // A box around the triangle's centroid, a third of its width and height: inside it, so the
    // docs' anatomy can point into the cone.
    const middle = coneMiddle.current;
    if (middle) {
      const [a, b, c] = points;
      const w = (Math.max(a.x, b.x, c.x) - Math.min(a.x, b.x, c.x)) / 3;
      const h = (Math.max(a.y, b.y, c.y) - Math.min(a.y, b.y, c.y)) / 3;
      middle.style.left = `${(a.x + b.x + c.x) / 3 - w / 2}px`;
      middle.style.top = `${(a.y + b.y + c.y) / 3 - h / 2}px`;
      middle.style.width = `${w}px`;
      middle.style.height = `${h}px`;
    }
  };

  /** Drops the cone; the next move starts afresh. */
  const endCone = () => {
    window.clearTimeout(zone.current.timer);
    zone.current.cone = null;
    draw(null);
  };

  /**
   * Reads the boxes the pointer is followed with: the stage on screen, the popover in it, the
   * rows of the list and the start of the metrics. Offsets are layout values, so the popover's
   * entrance (a transform) does not skew them.
   */
  const measure = () => {
    const box = stage.current;
    const panel = popover.current;
    if (!box || !panel) return;
    const rect = box.getBoundingClientRect();
    frame.current = {
      left: rect.left,
      top: rect.top,
      scale: rect.width / box.offsetWidth || 1,
      x: panel.offsetLeft + panel.clientLeft,
      y: panel.offsetTop + panel.clientTop,
    };
    const list = panel.querySelector<HTMLElement>('[data-list]');
    const column = panel.querySelector<HTMLElement>('[data-sub-column]');
    const head = panel.querySelector<HTMLElement>('[data-sub-head]');
    const rows = Array.from(panel.querySelectorAll<HTMLElement>('[data-row]'));
    if (!list || !column) return;
    const rowHeight = rows[0]?.offsetHeight ?? 35;
    const bottom = list.offsetTop + Math.max(list.offsetHeight, column.offsetHeight);
    layout.current = {
      listLeft: list.offsetLeft,
      listTop: list.offsetTop,
      subLeft: column.offsetLeft,
      subTop: head ? head.offsetTop + head.offsetHeight : column.offsetTop,
      subRight: column.offsetLeft + column.offsetWidth,
      bottom: bottom - rowHeight * 0.27,
      subRow: rowHeight,
      rows: rows.map((row) => ({
        id: row.dataset.row ?? '',
        top: row.offsetTop,
        bottom: row.offsetTop + row.offsetHeight,
      })),
    };
  };

  /**
   * Follows the pointer, at `p` in the popover's px. In the metrics, the row under it lights up.
   * In the list, the source under it takes over, unless the pointer is still inside the last
   * cone and heading right: then it is on its way, and only a pause hands over.
   */
  const track = (p: Point) => {
    const box = layout.current;
    if (!box || !live.current.open) return;
    const state = zone.current;
    const last = state.last;
    state.last = p;
    const where = locate(p, box, metricsOf(live.current.view.active).length, state.scroll);
    if (!where) {
      endCone();
      return;
    }
    if (where.column === 'sub') {
      endCone();
      if (where.row !== null) set({ sub: where.row, column: 'sub' });
      return;
    }
    window.clearTimeout(state.timer);
    const under = where.row;
    if (under && under !== live.current.view.active) {
      const onItsWay = state.cone && last && p.x >= last.x - 0.5 && inTriangle(p, state.cone);
      if (onItsWay)
        state.timer = window.setTimeout(
          () => set({ active: under, sub: 0, column: 'list' }),
          PAUSE,
        );
      else set({ active: under, sub: 0, column: 'list' });
    } else if (under) set({ column: 'list' });
    state.cone = coneAt(p, box);
    draw(state.cone);
  };

  /** A pointer on screen (client px) in the popover's px. */
  const fromScreen = (x: number, y: number): Point => {
    const f = frame.current;
    return { x: (x - f.left) / f.scale - f.x, y: (y - f.top) / f.scale - f.y };
  };

  const openPicker = (byReader: boolean) => {
    if (live.current.open) return;
    const now = live.current.value;
    const source = allSources(sources).find((entry) => entry.id === now.source);
    const index = source?.metrics.findIndex((entry) => entry.id === now.metric) ?? 0;
    live.current.open = true;
    focusOnOpen.current = byReader;
    zone.current.last = null;
    setQuery('');
    set({ active: now.source, sub: Math.max(0, index), column: 'list' });
    setOpen(true);
  };

  const close = () => {
    if (!live.current.open) return;
    // Focus in the popover goes back to the trigger: the popover is about to become inert.
    const hadFocus = popover.current?.contains(document.activeElement) ?? false;
    live.current.open = false;
    layout.current = null;
    endCone();
    zone.current.last = null;
    setOpen(false);
    if (hadFocus) trigger.current?.focus();
  };

  const pick = (source: Source, index: number, byReader: boolean) => {
    const entry = source.metrics[index];
    if (!entry) return;
    const next = { source: source.id, metric: entry.id };
    live.current.value = next;
    setValue(next);
    close();
    if (!byReader) return;
    onChange?.(next);
    announce(`${label}: ${source.label} ${entry.label}`);
  };

  /** Back to rest: the popover closed, no cone. */
  const rest = () => {
    close();
    setQuery('');
  };

  /** Keeps the list's scroll for the pointer, and drops its fade once a long list is at its end. */
  const onListScroll = (list: HTMLElement) => {
    zone.current.scroll = list.scrollTop;
    const scrolls = list.scrollHeight > list.clientHeight + 1;
    list.toggleAttribute(
      'data-end',
      scrolls && list.scrollTop + list.clientHeight >= list.scrollHeight - 1,
    );
  };

  // Open: measure, and move the focus into the search field when the reader opened it. A search
  // changes the rows, so they are measured again, and the list starts from its top.
  useLayoutEffect(() => {
    if (!open) return;
    const list = popover.current?.querySelector<HTMLElement>('[data-list]');
    if (list) {
      list.scrollTop = 0;
      onListScroll(list);
    }
    measure();
    const start = startPointer.current;
    const box = layout.current;
    if (start && box) {
      startPointer.current = undefined;
      const row = box.rows.find((entry) => entry.id === start.source);
      if (row) {
        const p = {
          x: box.listLeft + (box.subLeft - box.listLeft) * (start.x ?? 0.5),
          y: (row.top + row.bottom) / 2,
        };
        zone.current.last = p;
        zone.current.cone = coneAt(p, box);
        draw(zone.current.cone);
      }
    }
    if (focusOnOpen.current) {
      focusOnOpen.current = false;
      input.current?.focus();
    }
  }, [open, query]);

  // While open: a scroll moves the stage on screen, a resize its boxes; a press outside closes.
  useEffect(() => {
    if (!open) return;
    const box = stage.current;
    const onScroll = () => {
      if (!box) return;
      const rect = box.getBoundingClientRect();
      frame.current.left = rect.left;
      frame.current.top = rect.top;
    };
    const onPress = (event: globalThis.PointerEvent) => {
      const target = event.target as Node;
      if (popover.current?.contains(target) || trigger.current?.contains(target)) return;
      close();
    };
    const resize = new ResizeObserver(() => measure());
    if (box) resize.observe(box);
    window.addEventListener('scroll', onScroll, { capture: true, passive: true });
    document.addEventListener('pointerdown', onPress);
    return () => {
      resize.disconnect();
      window.removeEventListener('scroll', onScroll, { capture: true });
      document.removeEventListener('pointerdown', onPress);
    };
  }, [open]);

  useEffect(() => () => window.clearTimeout(zone.current.timer), []);

  const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
    const list = live.current.shown;
    const { active, column } = live.current.view;
    const count = metricsOf(active).length;
    const field = event.currentTarget;
    switch (event.key) {
      case 'ArrowDown':
      case 'ArrowUp': {
        event.preventDefault();
        endCone();
        const step = event.key === 'ArrowDown' ? 1 : -1;
        if (column === 'sub' && count) set({ sub: (sub + step + count) % count });
        else if (list.length) {
          const index =
            (list.findIndex((source) => source.id === active) + step + list.length) % list.length;
          set({ active: list[index].id, sub: 0 });
          // The lit row comes into view; the first and the last take the list to its ends.
          const box = popover.current?.querySelector<HTMLElement>('[data-list]');
          if (box && index === 0) box.scrollTop = 0;
          else if (box && index === list.length - 1) box.scrollTop = box.scrollHeight;
          else
            document.getElementById(optionId(list[index].id))?.scrollIntoView({ block: 'nearest' });
        }
        break;
      }
      case 'ArrowRight':
        // Into the metrics, once the caret has nothing left to move past.
        if (column === 'list' && count && field.selectionStart === field.value.length) {
          event.preventDefault();
          set({ column: 'sub' });
        }
        break;
      case 'ArrowLeft':
        if (column === 'sub') {
          event.preventDefault();
          set({ column: 'list' });
        }
        break;
      case 'Enter':
        event.preventDefault();
        if (activeSource) pick(activeSource, sub, true);
        break;
      case 'Escape':
        event.preventDefault();
        close();
        break;
      case 'Tab':
        close();
        break;
    }
  };

  // The demo calls the latest handlers.
  const actions = useRef({ track, openPicker, pick, rest });
  useLayoutEffect(() => {
    actions.current = { track, openPicker, pick, rest };
  });

  // The demo: the pointer opens the picker, rests on Image Resizes and heads diagonally down to
  // Size Change, across Cache Revalidations; back on Gateway Calls, it heads up to Duration,
  // across Function Calls, and picks it. The next round goes back to Requests Count.
  const demo = autoplay && !defaultState && playing && !focused;
  useEffect(() => {
    const box = stage.current;
    const arrow = pointer.current;
    if (!demo || !box || !arrow) return;
    const find = (selector: string) => box.querySelector(selector);
    const row = (source: string) => find(`[data-row="${source}"]`);
    const metric = (index: number) => find(`[data-sub="${index}"]`);
    const sourceOf = (source: string) => allSources(sources).find((entry) => entry.id === source);
    const toPopover = (p: Point) => ({ x: p.x - frame.current.x, y: p.y - frame.current.y });
    const stopRun = runDemo(
      box,
      arrow,
      (p) => actions.current.track(toPopover(p)),
      async ({ wait, pointOn, glide, press, leave, jump }) => {
        await wait(600);
        for (let round = 0; ; round += 1) {
          const first = round % 2 === 0;
          actions.current.rest();
          jump(pointOn(find('[data-trigger]'), 1.5, 3.2));
          await glide(pointOn(find('[data-trigger]'), 0.62, 0.55), 900, -0.08);
          await wait(240);
          await press(() => actions.current.openPicker(false));
          await wait(560);
          const [from, to, back, target] = first
            ? (['image-resizes', 4, 'gateway-calls', 1] as const)
            : (['outbound-apis', 1, 'requests', 0] as const);
          await glide(pointOn(row(from), 0.3, 0.55), 760, 0.06);
          await wait(520);
          await glide(pointOn(row(from), 0.46, 0.5), 300, 0);
          await wait(200);
          // Diagonally to a metric, across the sources in between.
          await glide(pointOn(metric(to), 0.32, 0.5), 980, 0.04);
          await wait(900);
          // Back across the list, which follows the pointer at once.
          await glide(pointOn(row(back), 0.38, 0.5), 820, -0.05);
          await wait(520);
          await glide(pointOn(metric(target), 0.3, 0.55), 920, 0.04);
          await wait(560);
          const source = sourceOf(back);
          await press(() => {
            if (source) actions.current.pick(source, target, false);
          });
          await wait(1300);
          await leave();
          await wait(1400);
        }
      },
    );
    let stopped = false;
    const stop = () => {
      if (stopped) return;
      stopped = true;
      stopRun();
      actions.current.rest();
    };
    stopDemo.current = stop;
    return stop;
  }, [demo, sources]);

  /** The reader takes over: the demo stops at once and waits until they leave it alone. */
  const readerActs = () => {
    stopDemo.current?.();
    stopDemo.current = null;
    take();
  };

  const onPopoverMove = (event: PointerEvent<HTMLDivElement>) => {
    if (event.pointerType === 'touch') return;
    track(fromScreen(event.clientX, event.clientY));
  };

  return (
    <div
      ref={root}
      className={`${styles.root} ${className}`}
      data-color-scheme={colorScheme}
      data-cone={showCone || undefined}
      onPointerDownCapture={readerActs}
      onKeyDownCapture={readerActs}
      onPointerMove={(event) => {
        if (event.pointerType !== 'touch') readerActs();
      }}
      onFocus={() => setFocused(true)}
      onBlur={(event) => {
        if (!event.currentTarget.contains(event.relatedTarget)) setFocused(false);
      }}
    >
      <div ref={stage} className={styles.stage}>
        <div className={styles.head} data-part="head">
          <span id={`${id}-label`} className={styles.label}>
            {label}
          </span>
          <label className={styles.toggle} data-part="cone-switch">
            <input
              type="checkbox"
              className={styles.check}
              checked={showCone}
              onChange={(event) => setShowCone(event.target.checked)}
            />
            Show cone
          </label>
        </div>

        <div className={styles.toolbar} data-part="toolbar">
          <button
            ref={trigger}
            type="button"
            className={`${styles.tool} ${styles.trigger}`}
            aria-haspopup="listbox"
            aria-expanded={open}
            aria-controls={`${id}-popover`}
            aria-labelledby={`${id}-label ${id}-trigger`}
            id={`${id}-trigger`}
            data-trigger=""
            data-part="trigger"
            onClick={() => (live.current.open ? close() : openPicker(true))}
            onKeyDown={(event) => {
              if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
              event.preventDefault();
              openPicker(true);
            }}
          >
            <span className={styles.triggerIcons} aria-hidden="true">
              {allSources(sources).map((source) => (
                <span key={source.id} data-shown={source.id === value.source || undefined}>
                  <SourceIcon name={source.icon} className={styles.toolIcon} />
                </span>
              ))}
            </span>
            <span className={styles.triggerText}>
              <span className={styles.toolStrong}>{picked?.label}</span> {pickedMetric?.label}
            </span>
            <Selector className={styles.selector} />
          </button>
          <span className={`${styles.tool} ${styles.sum}`} aria-hidden="true" data-part="sum">
            <span className={styles.mono}>N</span>
            <span className={styles.toolStrong}>Sum</span>
            <Selector className={styles.selector} />
          </span>
          <span className={styles.groupBy} aria-hidden="true">
            <Plus className={styles.toolIcon} />
            <span>Group by</span>
          </span>
          <span className={styles.range} aria-hidden="true">
            <span className={styles.rangeIcon}>
              <Calendar className={styles.toolIcon} />
            </span>
            <span className={styles.rangeText}>Last 12 hours</span>
          </span>
        </div>

        <Chart shown={seriesOf(`${value.source}.${value.metric}`)} />

        <div
          ref={popover}
          id={`${id}-popover`}
          className={styles.popover}
          data-part="popover"
          data-open={open || undefined}
          inert={!open}
          onPointerMove={onPopoverMove}
          onPointerLeave={() => {
            endCone();
            zone.current.last = null;
          }}
        >
          <div className={styles.search} data-part="search">
            <input
              ref={input}
              className={styles.input}
              type="text"
              role="combobox"
              aria-label="Find metric"
              aria-expanded={open}
              aria-controls={`${listId} ${subId}`}
              aria-autocomplete="list"
              aria-activedescendant={
                !activeSource
                  ? undefined
                  : view.column === 'sub'
                    ? metricId(sub)
                    : optionId(activeSource.id)
              }
              placeholder="Find metric…"
              autoComplete="off"
              spellCheck={false}
              value={query}
              onChange={(event) => {
                const text = event.target.value;
                // The lit source stays lit while the search keeps it; otherwise the first one.
                const left = allSources(filterSources(sources, text)).map((source) => source.id);
                const now = live.current.view.active;
                setQuery(text);
                endCone();
                set(
                  left.includes(now)
                    ? { column: 'list' }
                    : { active: left[0] ?? now, sub: 0, column: 'list' },
                );
              }}
              onKeyDown={onKeyDown}
            />
            <kbd className={styles.kbd} aria-hidden="true" data-part="key">
              Esc
            </kbd>
          </div>

          <div className={styles.columns}>
            <div
              id={listId}
              className={styles.list}
              role="listbox"
              aria-label="Sources"
              data-list=""
              data-part="list"
              onScroll={(event) => onListScroll(event.currentTarget)}
            >
              {groups.map((group) => (
                <div
                  key={group.id}
                  className={styles.group}
                  role="group"
                  aria-labelledby={`${id}-group-${group.id}`}
                >
                  <div
                    id={`${id}-group-${group.id}`}
                    className={styles.groupLabel}
                    data-part="group-label"
                  >
                    {group.label}
                  </div>
                  {group.sources.map((source) => (
                    <div
                      key={source.id}
                      id={optionId(source.id)}
                      className={styles.row}
                      role="option"
                      aria-selected={source.id === activeSource?.id}
                      data-row={source.id}
                      data-part="source"
                      data-active={source.id === activeSource?.id || undefined}
                      onClick={() => set({ active: source.id, sub: 0, column: 'list' })}
                    >
                      <SourceIcon name={source.icon} className={styles.rowIcon} />
                      <span className={styles.rowText}>{source.label}</span>
                      <ChevronRight className={styles.chevron} />
                    </div>
                  ))}
                </div>
              ))}
              {!activeSource && (
                <div className={styles.empty} data-part="empty">
                  No metrics found.
                </div>
              )}
            </div>

            <div className={styles.sub} data-sub-column="" data-part="metrics">
              <div id={`${id}-metrics-label`} className={styles.groupLabel} data-sub-head="">
                <span>{activeSource ? `Metrics for ${activeSource.label}` : 'Metrics'}</span>
              </div>
              <div id={subId} role="listbox" aria-labelledby={`${id}-metrics-label`}>
                {activeSource?.metrics.map((entry, index) => (
                  <div
                    key={entry.id}
                    id={metricId(index)}
                    className={styles.row}
                    role="option"
                    aria-selected={view.column === 'sub' && index === sub}
                    data-sub={index}
                    data-part="metric"
                    data-active={index === sub || undefined}
                    onClick={() => pick(activeSource, index, true)}
                  >
                    <span className={styles.rowText}>{entry.label}</span>
                  </div>
                ))}
              </div>
            </div>
          </div>
          <div ref={cone} className={styles.cone} aria-hidden="true" data-part="cone">
            <span ref={coneMiddle} className={styles.coneMiddle} data-part="cone-middle" />
          </div>
        </div>

        <DemoPointer ref={pointer} />
      </div>
      <p className={styles.hidden} role="status">
        {message}
      </p>
    </div>
  );
}
```

### metric-picker/metric-picker.module.css

```css
/* Metric picker (metric-picker.tsx): a dashboard stage, the label and the toolbar on top, a
   faint line chart under them, and the picker's popover over the chart. Sizes are in em of the
   stage's text, 14px wherever the popover fits at full width, down to 11px on a phone.

   The stage is one layout boundary of a set height: what changes while the demo plays (the lit
   rows, the submenu's rows, the trigger's words) lays out the stage alone. The cone is a plain box
   cut by a clip-path, and the pointer moves on a small layer of its own.

   Colours are the tokens below, light-dark(light, dark) where the two differ: the component takes
   the color-scheme it inherits from the page (light unless the page says otherwise), or the one
   its colorScheme prop sets (data-color-scheme). */

.root {
  /* The stage, which the tools share, its edge, and the popover raised over it (and its key). */
  --surface: #fff;
  --edge: rgb(0 0 0 / 0.06);
  --surface-2: #fff;
  /* The trigger hovered or open, and the lit row in the popover. */
  --hover: #fafafa;
  --lit: #f0f0f0;
  /* The words: the picks and values; the tools' words and icons; the quiet ones (the axis, the
     groups, the placeholder); the label; the rows' icons and the key's word. */
  --ink: #171717;
  --ink-soft: #666;
  --ink-muted: #8f8f8f;
  --label: #737373;
  --icon: #4d4d4d;
  /* Hairlines: the popover's and its dividers, the tools', the chart's rule and its grid. */
  --line: #ebebeb;
  --tool-line: #ededed;
  --rule: #eee;
  --plot: #f4f4f4;
  /* The popover's lift, and the near shadow under it and under the key. */
  --shadow: rgb(0 0 0 / 0.08);
  --shadow-near: rgb(0 0 0 / 0.03);
  /* The checkbox: its edge, then checked, its fill and its tick. */
  --check-line: #d4d4d4;
  --accent: #171717;
  --accent-ink: #fff;
  /* The chart's line, the cone, the focus ring. */
  --series: #e05478;
  --cone: rgb(56 132 255 / 0.16);
  --focus: #3b82f6;
  /* The demo's pointer, the same on both schemes: it looks like the system's. */
  --pointer: #111;
  --pointer-edge: #fff;
  --pointer-shadow: rgb(0 0 0 / 0.25);
  position: relative;
  container-type: inline-size;
  overflow: hidden;
  overflow: clip;
  border-radius: 18px;
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--edge);
  color: var(--ink);
  font-family:
    Inter,
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    sans-serif;
  letter-spacing: -0.015em;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}
@supports (color: light-dark(#fff, #000)) {
  .root {
    --surface: light-dark(#fff, #111);
    --edge: light-dark(rgb(0 0 0 / 0.06), rgb(255 255 255 / 0.07));
    --surface-2: light-dark(#fff, #1a1a1a);
    --hover: light-dark(#fafafa, #181818);
    --lit: light-dark(#f0f0f0, #2a2a2a);
    --ink: light-dark(#171717, #ededed);
    --ink-soft: light-dark(#666, #a1a1a1);
    --ink-muted: light-dark(#8f8f8f, #8c8c8c);
    --label: light-dark(#737373, #a1a1a1);
    --icon: light-dark(#4d4d4d, #b4b4b4);
    --line: light-dark(#ebebeb, #2e2e2e);
    --tool-line: light-dark(#ededed, #2a2a2a);
    --rule: light-dark(#eee, #262626);
    --plot: light-dark(#f4f4f4, #1d1d1d);
    --shadow: light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.5));
    --shadow-near: light-dark(rgb(0 0 0 / 0.03), rgb(0 0 0 / 0.3));
    --check-line: light-dark(#d4d4d4, #5e5e5e);
    --accent: light-dark(#171717, #ededed);
    --accent-ink: light-dark(#fff, #111);
    --series: light-dark(#e05478, #f0658a);
    --cone: light-dark(rgb(56 132 255 / 0.16), rgb(77 148 255 / 0.2));
  }
}
/* The colorScheme prop. Without it, nothing is set: color-scheme is inherited from the page. */
.root[data-color-scheme='light'] {
  color-scheme: light;
}
.root[data-color-scheme='dark'] {
  color-scheme: dark;
}
.root[data-color-scheme='system'] {
  color-scheme: light dark;
}
.root *,
.root *::before,
.root *::after {
  box-sizing: border-box;
}
/* Buttons and inputs take the stage's type; the classes below set their own on top. */
.root :where(button, input) {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}

.stage {
  --pad-x: clamp(14px, 4.2cqi, 34px);
  --pad-y: clamp(16px, 3.7cqi, 30px);
  position: relative;
  height: calc(2 * var(--pad-y) + 32.69em);
  padding: var(--pad-y) var(--pad-x);
  contain: layout;
  font-size: clamp(11px, calc((100cqi - 2 * var(--pad-x)) / 37.47), 14px);
  line-height: 1.25;
}

/* The label, and the switch for the cone at the other end. */
.head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 1.324em;
  margin-bottom: 0.464em;
}
.label {
  color: var(--label);
}
.toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.46em;
  color: var(--ink-soft);
  font-size: 0.86em;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}
.check {
  appearance: none;
  display: grid;
  place-items: center;
  width: 1.006em;
  height: 1.006em;
  border: 1px solid var(--check-line);
  border-radius: 0.284em;
  background: var(--surface);
  cursor: pointer;
}
.check::after {
  content: '';
  width: 0.533em;
  height: 0.292em;
  margin-top: -0.103em;
  border: solid var(--accent-ink);
  border-width: 0 0 1.6px 1.6px;
  transform: rotate(-45deg);
  visibility: hidden;
}
.check:checked {
  border-color: var(--accent);
  background: var(--accent);
}
.check:checked::after {
  visibility: visible;
}
.check:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* The toolbar: the trigger, the aggregation, grouping and the time range. */
.toolbar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.602em;
  height: 2.385em;
}
.tool {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 0.791em;
  height: 2.385em;
  padding: 0 0.791em;
  border: 1px solid var(--tool-line);
  border-radius: 0.464em;
  background: var(--surface);
  white-space: nowrap;
}
.toolIcon {
  flex: none;
  width: 1.27em;
  height: 1.27em;
  color: var(--ink-soft);
}
.toolStrong {
  font-weight: 500;
}
.selector {
  flex: none;
  width: 1.15em;
  height: 1.15em;
  margin-left: 0.1em;
  color: var(--ink-soft);
}
.trigger {
  flex: 0 1 auto;
  min-width: 0;
  gap: 0.67em;
  padding: 0 0.736em 0 0.67em;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.trigger:hover,
.trigger[aria-expanded='true'] {
  background: var(--hover);
}
.trigger:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
/* Every source's icon is there; the one of the pick shows. */
.triggerIcons {
  display: grid;
  flex: none;
}
.triggerIcons > span {
  grid-area: 1 / 1;
  visibility: hidden;
}
.triggerIcons > span[data-shown] {
  visibility: visible;
}
.triggerText {
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink-soft);
}
.triggerText .toolStrong {
  color: var(--ink);
}
.sum {
  gap: 0.67em;
  padding: 0 0.736em 0 0.93em;
}
.mono {
  margin-right: 0.49em;
  color: var(--ink-muted);
  font-family: 'JetBrains Mono', JetBrains, Menlo, Consolas, monospace;
  font-size: 0.86em;
}
.groupBy {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 0.76em;
  padding: 0 0.258em 0 0.6em;
  color: var(--ink-soft);
  white-space: nowrap;
}
.groupBy .toolIcon {
  width: 1.07em;
  height: 1.07em;
}
/* The time range is the taller control, and starts where the popover ends; on a narrow stage it
   keeps to the right edge. */
.range {
  --range-w: 11.2em;
  position: absolute;
  top: 50%;
  left: min(37.59em, 100% - var(--range-w));
  display: inline-flex;
  align-items: center;
  height: 3em;
  border: 1px solid var(--tool-line);
  border-radius: 0.464em;
  white-space: nowrap;
  transform: translateY(-50%);
}
.rangeIcon {
  display: grid;
  place-items: center;
  width: 2.886em;
  height: 100%;
  border-right: 1px solid var(--tool-line);
}
.rangeIcon .toolIcon {
  width: 1.32em;
  height: 1.32em;
}
.rangeText {
  padding: 0 0.96em;
  font-weight: 500;
}

/* The chart, under the toolbar: the time step over a rule, the plot and its times. The open
   popover hangs past the times, over the stage. */
.chart {
  position: absolute;
  top: calc(var(--pad-y) + 4.652em);
  right: var(--pad-x);
  left: var(--pad-x);
}
.chartHead {
  display: flex;
  align-items: flex-start;
  height: 5.643em;
  padding-top: 2.07em;
  border-bottom: 1px solid var(--rule);
}
.granularity {
  gap: 0.76em;
  margin-left: min(38.17em, 100% - 7.8em);
  padding-left: 0.41em;
}
.granularity .toolIcon {
  width: 1.3em;
  height: 1.3em;
}
.plot {
  position: relative;
  height: 15.64em;
}
.grid,
.series,
.series svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.grid {
  overflow: visible;
  stroke: var(--plot);
  stroke-width: 1;
}
.series {
  visibility: hidden;
}
.series[data-shown] {
  visibility: visible;
}
.series svg {
  overflow: hidden;
  fill: none;
  stroke: var(--series);
  stroke-width: 1.5;
  stroke-linejoin: round;
}
.axis {
  position: relative;
  height: 2.4em;
  color: var(--ink-muted);
  font-size: 0.857em;
}
.axis span {
  position: absolute;
  top: 0.86em;
  transform: translateX(-50%);
}
.axis span:nth-child(1) {
  left: 20%;
}
.axis span:nth-child(2) {
  left: 49%;
}
.axis span:nth-child(3) {
  left: 78%;
}

/* The popover: the search, then two columns, the list and the submenu of its lit row. */
.popover {
  position: absolute;
  top: calc(var(--pad-y) + 4.652em);
  left: var(--pad-x);
  z-index: 2;
  width: min(37.474em, 100% - 2 * var(--pad-x));
  border: 1px solid var(--line);
  border-radius: 0.791em;
  background: var(--surface-2);
  box-shadow:
    0 6px 18px -4px var(--shadow),
    0 2px 5px var(--shadow-near);
  visibility: hidden;
  opacity: 0;
  transform: translateY(-0.258em);
  transition:
    opacity 0.14s ease,
    transform 0.14s ease,
    visibility 0s 0.14s;
}
.popover[data-open] {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity 0.14s ease,
    transform 0.14s ease;
}
.search {
  display: flex;
  align-items: center;
  gap: 0.662em;
  height: 3.3em;
  padding: 0 0.928em 0 1.16em;
  border-bottom: 1px solid var(--line);
}
.input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
}
.input::placeholder {
  color: var(--ink-muted);
  opacity: 1;
}
.kbd {
  display: grid;
  flex: none;
  place-items: center;
  height: 1.7em;
  padding: 0 0.42em;
  border: 1px solid var(--line);
  border-radius: 0.4em;
  background: var(--surface-2);
  box-shadow: 0 1px 1px var(--shadow-near);
  color: var(--icon);
  font: inherit;
  font-size: 0.9em;
  font-weight: 500;
}
.columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  height: 24.53em;
}
/* The list ends at the popover's edge, its last row faded; it scrolls when its sources outgrow
   the popover, and the fade lifts at the end of the scroll or while the last row is lit. */
.list {
  padding: 0.22em 0.662em 0 0.619em;
  overflow-y: auto;
  scrollbar-width: none;
  -webkit-mask-image: linear-gradient(#000 calc(100% - 2.2em), transparent);
  mask-image: linear-gradient(#000 calc(100% - 2.2em), transparent);
}
.list::-webkit-scrollbar {
  display: none;
}
.list[data-end],
.list:has(.group:last-child .row:last-child[data-active]) {
  -webkit-mask-image: none;
  mask-image: none;
}
.group + .group {
  margin-top: 0.25em;
}
.sub {
  padding: 0.266em 0.464em 0.533em 0.576em;
  border-left: 1px solid var(--line);
}
.groupLabel {
  display: flex;
  align-items: center;
  height: 2.07em;
  padding: 0 0.62em;
  overflow: hidden;
  color: var(--ink-muted);
  font-size: 0.857em;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.groupLabel span {
  overflow: hidden;
  text-overflow: ellipsis;
}
.row {
  display: flex;
  align-items: center;
  gap: 0.662em;
  height: 2.3em;
  padding: 0 0.533em;
  border-radius: 0.395em;
  white-space: nowrap;
  cursor: default;
}
.row[data-active] {
  background: var(--lit);
}
.list .groupLabel {
  padding-left: 0.48em;
}
.list .row {
  gap: 0.57em;
  padding-left: 0.36em;
}
.rowIcon {
  flex: none;
  width: 1.27em;
  height: 1.27em;
  color: var(--icon);
}
.rowText {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chevron {
  flex: none;
  width: 1.2em;
  height: 1.2em;
  color: var(--icon);
  visibility: hidden;
}
.row[data-active] .chevron {
  visibility: visible;
}
.empty {
  padding: 0.86em 0.533em;
  color: var(--ink-muted);
}
/* The cone: a plain box over the columns, cut to the triangle by the component. */
.cone {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  background: var(--cone);
  pointer-events: none;
  visibility: hidden;
}
/* A box in the middle of the cone, kept in step with it: nothing is drawn in it. */
.coneMiddle {
  position: absolute;
}
.root:not([data-cone]) .cone {
  display: none;
}

.hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Narrower: the toolbar keeps its icons, and the list its words. */
@container (max-width: 640px) {
  .groupBy span,
  .rangeText {
    display: none;
  }
  .range {
    --range-w: 3.03em;
  }
}
@container (max-width: 460px) {
  .rowIcon,
  .chevron {
    display: none;
  }
  .row[data-active] .chevron {
    display: block;
  }
  .list .row {
    padding-left: 0.533em;
  }
  .sum {
    padding: 0 0.533em;
  }
  .mono {
    display: none;
  }
}
@container (max-width: 340px) {
  .sum {
    display: none;
  }
}
```

### metric-picker/cone.ts

```tsx
/*
 * The prediction cone, in the popover's own px. While the pointer is in the list, the cone runs
 * from just behind it to the submenu's near edge: the top of its first row and the bottom of the
 * column. A pointer whose next position is still inside the cone, and still heading right, is on
 * its way to the submenu, so the rows it crosses are left alone.
 *
 * Near the submenu the cone is thin around the pointer, and a hand that drifts a pixel up or down
 * would leave it. So its apex steps back behind the pointer, by a buffer that grows as the
 * pointer closes in: nothing at the far side of the list, most of a row's height at the edge.
 *
 * Every box comes from the layout measured once when the popover opens (or when a search changes
 * its rows): following the pointer is arithmetic on those numbers, never a read of the page.
 */

export type Point = { x: number; y: number };

/** The popover's boxes, in its own px, as measured when it opened. */
export type Layout = {
  /** The list's column: its left edge, and where the rows start. */
  listLeft: number;
  listTop: number;
  /** The submenu's column: its near edge, the top of its first row, its right edge. */
  subLeft: number;
  subTop: number;
  subRight: number;
  /** Where both columns end, a little above the popover's bottom. */
  bottom: number;
  /** A submenu row's height: the submenu's rows follow one another from `subTop`. */
  subRow: number;
  /** The list's rows, top to bottom. */
  rows: { id: string; top: number; bottom: number }[];
};

/** The largest step back, as a share of a row's height. */
const BUFFER = 0.9;

const clamp = (value: number) => Math.min(1, Math.max(0, value));

/** How far behind the pointer the apex sits: it grows, eased in, as the pointer nears the edge. */
export function bufferAt(p: Point, layout: Layout) {
  const reach = layout.subLeft - layout.listLeft;
  const near = clamp(1 - (layout.subLeft - p.x) / reach);
  return BUFFER * layout.subRow * near * near;
}

/** The cone for the pointer at `p`: its apex, then its corners on the submenu's near edge. */
export function coneAt(p: Point, layout: Layout): [Point, Point, Point] {
  return [
    { x: p.x - bufferAt(p, layout), y: p.y },
    { x: layout.subLeft, y: layout.subTop },
    { x: layout.subLeft, y: layout.bottom },
  ];
}

/** Whether `p` lies in the triangle a, b, c, edges included. */
export function inTriangle(p: Point, [a, b, c]: [Point, Point, Point]) {
  const side = (u: Point, v: Point) => (v.x - u.x) * (p.y - u.y) - (v.y - u.y) * (p.x - u.x);
  const ab = side(a, b);
  const bc = side(b, c);
  const ca = side(c, a);
  return !((ab < 0 || bc < 0 || ca < 0) && (ab > 0 || bc > 0 || ca > 0));
}

/**
 * Where the pointer is: in the list (and on which row), in the submenu (and on which row). The
 * list's rows were measured unscrolled, so `scroll` is how far the list has scrolled since.
 */
export function locate(p: Point, layout: Layout, subCount: number, scroll = 0) {
  if (p.y < layout.listTop || p.y >= layout.bottom || p.x < layout.listLeft) return null;
  if (p.x >= layout.subLeft) {
    if (p.x >= layout.subRight || p.y < layout.subTop) return { column: 'sub' as const, row: null };
    const index = Math.floor((p.y - layout.subTop) / layout.subRow);
    return { column: 'sub' as const, row: index < subCount ? index : null };
  }
  const y = p.y + scroll;
  const row = layout.rows.find((entry) => y >= entry.top && y < entry.bottom);
  return { column: 'list' as const, row: row?.id ?? null };
}
```

### metric-picker/metrics.ts

```tsx
/*
 * What the picker offers: sources of data in groups, and the metrics each source can chart. A
 * source opens its metrics at the right of the list; the trigger names the pick as "<source>
 * <metric>", say "Requests Count".
 */

export type IconName =
  'globe' | 'image' | 'revalidate' | 'function' | 'gateway' | 'outbound' | 'blocked' | 'firewall';

export type Metric = {
  /** Unique within its source. */
  id: string;
  label: string;
};

export type Source = {
  /** Unique across the picker. */
  id: string;
  label: string;
  icon: IconName;
  metrics: Metric[];
};

export type SourceGroup = {
  id: string;
  label: string;
  sources: Source[];
};

/** A pick: a source and one of its metrics. */
export type MetricValue = { source: string; metric: string };

const metric = (label: string): Metric => ({
  id: label
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/-$/, ''),
  label,
});

export const SOURCES: SourceGroup[] = [
  {
    id: 'network',
    label: 'Network',
    sources: [
      {
        id: 'requests',
        label: 'Requests',
        icon: 'globe',
        metrics: [
          'Count',
          'Data Transfer (Incoming)',
          'Data Transfer (Outgoing)',
          'Data Transfer (Total)',
          'Request Duration',
        ].map(metric),
      },
      {
        id: 'image-resizes',
        label: 'Image Resizes',
        icon: 'image',
        metrics: [
          'Count',
          'Compression Ratio',
          'Optimized Size',
          'Request Duration',
          'Size Change',
          'Source Size',
        ].map(metric),
      },
      {
        id: 'cache-revalidations',
        label: 'Cache Revalidations',
        icon: 'revalidate',
        metrics: ['Read Bandwidth', 'Write Bandwidth', 'Read Units', 'Write Units'].map(metric),
      },
    ],
  },
  {
    id: 'compute',
    label: 'Compute',
    sources: [
      {
        id: 'function-calls',
        label: 'Function Calls',
        icon: 'function',
        metrics: ['Count', 'Duration', 'Active CPU Time', 'Memory Used', 'Time to First Byte'].map(
          metric,
        ),
      },
      {
        id: 'gateway-calls',
        label: 'Gateway Calls',
        icon: 'gateway',
        metrics: ['Count', 'Duration', 'Active CPU Time', 'Time to First Byte'].map(metric),
      },
      {
        id: 'outbound-apis',
        label: 'Outbound APIs',
        icon: 'outbound',
        metrics: ['Count', 'Time to First Byte'].map(metric),
      },
    ],
  },
  {
    id: 'security',
    label: 'Security',
    sources: [
      {
        id: 'blocked-connections',
        label: 'Blocked Connections',
        icon: 'blocked',
        metrics: ['Count'].map(metric),
      },
      {
        id: 'firewall-actions',
        label: 'Firewall Actions',
        icon: 'firewall',
        metrics: ['Count'].map(metric),
      },
    ],
  },
];

const includes = (text: string, query: string) => text.toLowerCase().includes(query);

/**
 * The groups as the query leaves them. A source stays with all its metrics when its name or its
 * group's matches, or with the metrics that match; "requests count" matches across both.
 */
export function filterSources(groups: SourceGroup[], query: string): SourceGroup[] {
  const q = query.trim().toLowerCase().replace(/\s+/g, ' ');
  if (!q) return groups;
  return groups
    .map((group) => ({
      ...group,
      sources: group.sources.flatMap((source) => {
        if (includes(source.label, q) || includes(group.label, q)) return [source];
        const metrics = source.metrics.filter(
          (entry) => includes(entry.label, q) || includes(`${source.label} ${entry.label}`, q),
        );
        return metrics.length ? [{ ...source, metrics }] : [];
      }),
    }))
    .filter((group) => group.sources.length > 0);
}
```

### metric-picker/chart.tsx

```tsx
import { Clock } from './icons';
import styles from './metric-picker.module.css';

/*
 * The faint line chart under the picker. It is scenery, so it is hidden from screen readers. Each
 * metric draws one of a few series, all drawn up front from a fixed seed (the server and the page
 * draw the same); picking a metric swaps the series on show by its visibility, and nothing inside
 * an SVG changes.
 */

const SERIES = 5;
const POINTS = 72;

/** A small seeded generator: the same numbers on every run. */
function seeded(seed: number) {
  let state = seed;
  return () => {
    state = (state * 1664525 + 1013904223) % 4294967296;
    return state / 4294967296;
  };
}

/** A jagged line in the upper part of a 100 × 100 box. */
function series(seed: number) {
  const random = seeded(seed * 7919 + 101);
  const level = 9 + random() * 9;
  let y = level;
  const points: string[] = [];
  for (let i = 0; i < POINTS; i += 1) {
    y += (random() - 0.5) * 6;
    y += (level - y) * 0.35;
    points.push(`${((i / (POINTS - 1)) * 100).toFixed(2)} ${y.toFixed(2)}`);
  }
  return `M${points.join('L')}`;
}

const PATHS = Array.from({ length: SERIES }, (_, i) => series(i));

/** Which series a metric draws. */
export function seriesOf(key: string) {
  let hash = 0;
  for (const char of key) hash = (hash * 31 + char.charCodeAt(0)) % 9973;
  return hash % SERIES;
}

export function Chart({ shown }: { shown: number }) {
  return (
    <div className={styles.chart} aria-hidden="true" data-part="chart">
      <div className={styles.chartHead}>
        <span className={`${styles.tool} ${styles.granularity}`}>
          <Clock className={styles.toolIcon} />
          <span className={styles.toolStrong}>5 minutes</span>
        </span>
      </div>
      <div className={styles.plot}>
        <svg className={styles.grid} viewBox="0 0 100 100" preserveAspectRatio="none">
          {[8.4, 39.2, 69.6, 100].map((y) => (
            <path key={y} d={`M0 ${y}H100`} vectorEffect="non-scaling-stroke" />
          ))}
        </svg>
        {PATHS.map((d, i) => (
          <div key={i} className={styles.series} data-shown={i === shown || undefined}>
            <svg viewBox="0 0 100 100" preserveAspectRatio="none">
              <path d={d} vectorEffect="non-scaling-stroke" />
            </svg>
          </div>
        ))}
      </div>
      <div className={styles.axis}>
        <span>9h ago</span>
        <span>6h ago</span>
        <span>3h ago</span>
      </div>
    </div>
  );
}
```

### metric-picker/icons.tsx

```tsx
import type { ReactNode } from 'react';
import type { IconName } from './metrics';

/*
 * The picker's line icons, drawn on a 16 px grid with the text's colour. They are decorative:
 * every one sits next to the words it stands for.
 */

function Icon({ className, children }: { className?: string; children: ReactNode }) {
  return (
    <svg
      className={className}
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      strokeWidth="1.2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      {children}
    </svg>
  );
}

const SOURCE_ICONS: Record<IconName, ReactNode> = {
  globe: (
    <>
      <circle cx="8" cy="8" r="6.25" />
      <ellipse cx="8" cy="8" rx="2.6" ry="6.25" />
      <path d="M1.9 6.1h12.2M1.9 9.9h12.2" />
    </>
  ),
  image: (
    <>
      <rect x="1.75" y="2.75" width="12.5" height="10.5" rx="1.4" />
      <path d="m3.6 11 2.7-3 2 1.9 2.4-3.2 1.8 2.1M10.6 4.9h1.6v1.6" />
    </>
  ),
  revalidate: (
    <>
      <path d="M5.75 3.25V2.9c0-.6.5-1.15 1.15-1.15h3.9l3.1 3.1v8.2c0 .6-.5 1.15-1.15 1.15H6.9c-.6 0-1.15-.5-1.15-1.15" />
      <path d="M10.6 1.9v3.1h3.1M2.25 8.9a3.2 3.2 0 0 1 5.6-1.8M8.2 5.5v1.9H6.3" />
    </>
  ),
  function: (
    <>
      <rect x="2" y="2" width="12" height="12" rx="1.6" />
      <path d="M9.9 4.6c-1.3-.3-2 .2-2.25 1.4l-.95 4.2c-.25 1.2-.95 1.6-2.2 1.3M5.9 7.2h3.6" />
    </>
  ),
  gateway: (
    <>
      <rect x="2" y="2" width="12" height="12" rx="1.6" />
      <path d="M5.3 5v6M5.3 8h5.4m-1.8-1.9L10.7 8l-1.8 1.9" />
    </>
  ),
  outbound: (
    <>
      <circle cx="11" cy="5" r="2.75" />
      <path d="M8.25 5h5.5M11 2.25c-.75.75-.75 4.75 0 5.5" />
      <path d="M7.4 8.6 2.6 13.4m0-3.2v3.2h3.2" />
    </>
  ),
  blocked: (
    <>
      <circle cx="8" cy="8" r="6.25" />
      <path d="M8 4.75v3.9" />
      <circle cx="8" cy="11.1" r="0.2" strokeWidth="1.4" />
    </>
  ),
  firewall: (
    <>
      <rect x="1.75" y="2.75" width="12.5" height="10.5" rx="1.4" />
      <path d="M1.75 6.25h12.5M1.75 9.75h8M8 2.75v3.5M5 6.25v3.5M11 6.25v3.5M5 9.75v3.5" />
      <circle cx="12.4" cy="11.5" r="0.2" strokeWidth="1.4" />
    </>
  ),
};

/** A source's icon. */
export function SourceIcon({ name, className }: { name: IconName; className?: string }) {
  return <Icon className={className}>{SOURCE_ICONS[name]}</Icon>;
}

export function ChevronRight({ className }: { className?: string }) {
  return (
    <Icon className={className}>
      <path d="m6.25 3.75 4.25 4.25-4.25 4.25" />
    </Icon>
  );
}

/** The two chevrons of a select: up and down. */
export function Selector({ className }: { className?: string }) {
  return (
    <Icon className={className}>
      <path d="M5.25 6.25 8 3.5l2.75 2.75M5.25 9.75 8 12.5l2.75-2.75" />
    </Icon>
  );
}

export function Plus({ className }: { className?: string }) {
  return (
    <Icon className={className}>
      <path d="M8 2.5v11M2.5 8h11" />
    </Icon>
  );
}

export function Calendar({ className }: { className?: string }) {
  return (
    <Icon className={className}>
      <rect x="2" y="2.75" width="12" height="11.25" rx="1.6" />
      <path d="M2 6.5h12M5.25 1.5v2.5M10.75 1.5v2.5" />
    </Icon>
  );
}

export function Clock({ className }: { className?: string }) {
  return (
    <Icon className={className}>
      <circle cx="8" cy="8" r="6.25" />
      <path d="M8 4.75V8l2.1 1.5" />
    </Icon>
  );
}
```

### metric-picker/demo-pointer.tsx

```tsx
'use client';

import type { CSSProperties, Ref } from 'react';

/*
 * The demo's pointer, and the steps that move it. The picker follows the pointer on every frame,
 * as it follows a mouse, so each glide is a requestAnimationFrame loop that places the arrow and
 * hands the picker its position on screen; nothing runs between steps. Stopping the demo (the
 * reader takes over, or it scrolls away) ends the step in progress at once: its promise rejects,
 * which ends the script right there, and the pointer fades out.
 *
 * The arrow is an HTML box around a still drawing that moves by its transform: a change of style
 * on an SVG element would lay that SVG out again on every frame.
 */

export type Point = { x: number; y: number };

export type Demo = {
  /** Resolves after `ms`. */
  wait: (ms: number) => Promise<void>;
  /** The point at a share of `target`'s width and height, in the stage's px. */
  pointOn: (target: Element | null | undefined, x: number, y: number) => Point | null;
  /**
   * Glides the pointer to `to` (in the stage's px) over `ms`, along a path bent sideways by
   * `bend` (a share of its length; 0 is a straight line).
   */
  glide: (to: Point | null, ms: number, bend?: number) => Promise<void>;
  /** Presses and lets go; `action` runs as the pointer goes down. */
  press: (action: () => void) => Promise<void>;
  /** Moves the pointer off, down and to the right, and fades it out. */
  leave: () => Promise<void>;
  /** Puts the pointer, out of sight, where the next glide starts. */
  jump: (to: Point | null) => void;
};

/** What a stopped demo's pending step rejects with. */
class Stopped extends Error {}

/** The arrow's box, in px, and where its tip is in that box (its drawing is 20 × 20). */
const SIZE = 16;
const TIP: Point = { x: (3 * SIZE) / 20, y: (2 * SIZE) / 20 };

const place = ({ x, y }: Point) => `translate(${x - TIP.x}px, ${y - TIP.y}px)`;
const clamp = (value: number) => Math.min(1, Math.max(0, value));

/** Slow at both ends, as a hand moves. */
const easeInOut = (k: number) => (k < 0.5 ? 4 * k * k * k : 1 - (-2 * k + 2) ** 3 / 2);

/**
 * Plays `script` with the pointer over `stage`; `onMove` gets the pointer's position (in the
 * stage's px) on every frame it moves. Returns the function that stops the demo.
 */
export function runDemo(
  stage: HTMLElement,
  pointer: HTMLElement,
  onMove: (point: Point) => void,
  script: (demo: Demo) => Promise<void>,
): () => void {
  let alive = true;
  let frame = 0;
  const timers = new Set<number>();
  const pending = new Set<() => void>();
  let at: Point = { x: stage.offsetWidth * 0.86, y: stage.offsetHeight * 0.9 };

  /** Settles like `promise`, or rejects as soon as the demo stops. */
  const step = (promise: Promise<unknown>) =>
    new Promise<void>((resolve, reject) => {
      if (!alive) return reject(new Stopped());
      const cancel = () => reject(new Stopped());
      pending.add(cancel);
      promise.then(
        () => {
          pending.delete(cancel);
          if (alive) resolve();
        },
        () => {
          pending.delete(cancel);
          reject(new Stopped());
        },
      );
    });

  const wait = (ms: number) =>
    step(
      new Promise<void>((resolve) => {
        const timer = window.setTimeout(() => {
          timers.delete(timer);
          resolve();
        }, ms);
        timers.add(timer);
      }),
    );

  /** Screen px to the stage's own, should the stage sit in a scaled container. */
  const frameOf = () => {
    const box = stage.getBoundingClientRect();
    return { box, scale: box.width / stage.offsetWidth || 1 };
  };

  const pointOn: Demo['pointOn'] = (target, x, y) => {
    if (!target) return null;
    const { box, scale } = frameOf();
    const rect = target.getBoundingClientRect();
    return {
      x: (rect.left + rect.width * x - box.left) / scale,
      y: (rect.top + rect.height * y - box.top) / scale,
    };
  };

  /** Puts the arrow at `point`; `tell` hands the picker its place too. */
  const show = (point: Point, tell: boolean) => {
    at = point;
    pointer.style.transform = place(point);
    if (tell) onMove(point);
  };

  /** A requestAnimationFrame loop over `ms` that hands `draw` the eased share of the way. */
  const animate = (ms: number, draw: (t: number) => void) =>
    step(
      new Promise<void>((resolve) => {
        const began = performance.now();
        const tick = (now: number) => {
          // A frame's time can come before `began`: the share is clamped at both ends.
          const t = easeInOut(clamp((now - began) / ms));
          draw(t);
          if (t < 1) frame = requestAnimationFrame(tick);
          else resolve();
        };
        frame = requestAnimationFrame(tick);
      }),
    );

  const glide: Demo['glide'] = (to, ms, bend = 0.12) => {
    if (!to) return step(Promise.resolve());
    const from = at;
    const dx = to.x - from.x;
    const dy = to.y - from.y;
    // The curve's control point sits off the straight line, as a hand drifts.
    const control = { x: (from.x + to.x) / 2 - dy * bend, y: (from.y + to.y) / 2 + dx * bend };
    pointer.style.opacity = '1';
    return animate(ms, (t) => {
      const u = 1 - t;
      show(
        {
          x: u * u * from.x + 2 * u * t * control.x + t * t * to.x,
          y: u * u * from.y + 2 * u * t * control.y + t * t * to.y,
        },
        true,
      );
    });
  };

  const press: Demo['press'] = async (action) => {
    // The arrow dips towards its tip.
    const dip = pointer.firstElementChild?.animate(
      [
        { transform: 'scale(1)' },
        { transform: 'scale(0.84)', offset: 0.35 },
        { transform: 'scale(1)' },
      ],
      { duration: 260, easing: 'ease-in-out' },
    );
    await wait(90);
    action();
    if (dip) await step(dip.finished);
  };

  const leave: Demo['leave'] = async () => {
    pointer.style.opacity = '0';
    const from = at;
    // Off the picker now: the arrow drifts away without touching it.
    await animate(420, (t) => show({ x: from.x + 40 * t, y: from.y + 56 * t }, false));
  };

  const jump: Demo['jump'] = (to) => {
    if (to) show(to, false);
  };

  pointer.style.transform = place(at);
  script({ wait, pointOn, glide, press, leave, jump }).catch((error: unknown) => {
    if (!(error instanceof Stopped)) throw error;
  });

  return () => {
    if (!alive) return;
    alive = false;
    cancelAnimationFrame(frame);
    timers.forEach((timer) => window.clearTimeout(timer));
    pending.forEach((cancel) => cancel());
    pointer.style.opacity = '0';
  };
}

/** The pointer's box: it moves, on a small layer of its own. */
const POINTER: CSSProperties = {
  position: 'absolute',
  top: 0,
  left: 0,
  zIndex: 10,
  display: 'block',
  width: SIZE,
  height: SIZE,
  pointerEvents: 'none',
  opacity: 0,
  transition: 'opacity 0.3s ease',
  filter: 'drop-shadow(0 1px 1.5px var(--pointer-shadow))',
  willChange: 'transform',
};

/** Inside it, what dips towards the tip when the pointer presses. */
const PRESS: CSSProperties = {
  display: 'block',
  width: SIZE,
  height: SIZE,
  transform: 'scale(1)',
  transformOrigin: `${TIP.x}px ${TIP.y}px`,
};

const DRAWING: CSSProperties = { display: 'block', width: SIZE, height: SIZE, overflow: 'visible' };
/** The arrow's colours, like its shadow's, are the stage's tokens (metric-picker.module.css). */
const ARROW: CSSProperties = { fill: 'var(--pointer)', stroke: 'var(--pointer-edge)' };

/** The arrow the demo moves around. It is decorative: the demo stays silent. */
export function DemoPointer({ ref }: { ref: Ref<HTMLSpanElement> }) {
  return (
    <span ref={ref} style={POINTER} aria-hidden="true">
      <span style={PRESS}>
        <svg viewBox="0 0 20 20" style={DRAWING}>
          <path
            d="M3 2v14.5l3.6-3.3 2.6 5.6 2.4-1.1-2.6-5.5h5.2z"
            style={ARROW}
            strokeWidth="1.3"
            strokeLinejoin="round"
          />
        </svg>
      </span>
    </span>
  );
}
```

### metric-picker/use-autoplay.ts

```tsx
'use client';

import { useCallback, useEffect, useRef, useState } from 'react';

/*
 * When a demo may play on its own: while part of it is on screen and the tab is visible, never
 * under reduced motion, and not while the reader is using it. `take()` hands it to the reader; it
 * takes back over `idle` ms after their last input.
 */
export function useAutoplay<T extends HTMLElement>(idle = 6000) {
  const ref = useRef<T>(null);
  const [visible, setVisible] = useState(false);
  const [reduced, setReduced] = useState(true);
  const [taken, setTaken] = useState(false);
  const timer = useRef(0);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    let onScreen = false;
    const update = () => setVisible(onScreen && !document.hidden);
    const observer = new IntersectionObserver(
      ([entry]) => {
        onScreen = entry.isIntersecting;
        update();
      },
      { threshold: 0.3 },
    );
    observer.observe(element);
    document.addEventListener('visibilitychange', update);
    const motion = window.matchMedia('(prefers-reduced-motion: reduce)');
    const readMotion = () => setReduced(motion.matches);
    readMotion();
    motion.addEventListener('change', readMotion);
    return () => {
      observer.disconnect();
      document.removeEventListener('visibilitychange', update);
      motion.removeEventListener('change', readMotion);
    };
  }, []);

  const take = useCallback(() => {
    setTaken(true);
    window.clearTimeout(timer.current);
    timer.current = window.setTimeout(() => setTaken(false), idle);
  }, [idle]);
  useEffect(() => () => window.clearTimeout(timer.current), []);

  return { ref, take, playing: visible && !reduced && !taken };
}
```