# Usage dashboard

> The Agents screen of a team’s coding-agent app: a heatmap of every agent’s fullest plan window, a table of usage bars and lines, and a details panel.

Every coding agent of the workspace is signed in to a plan with a 5-hour and sometimes a weekly usage window. A heatmap shows each agent’s fullest window over the last day, week or month, violet by share and orange at the limit, with a tooltip on every cell; the table under it shows the windows as bars and the last readings as a line. A row opens the agent’s details beside it: windows, runtime details and resource charts. A sidebar with a workspace switcher, sections and chats with their tickets folds away. Light and dark.

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

Category: Dashboards.

## Anatomy

1. **Sidebar**: The workspace switcher, a search that focuses the filter (⌘K), the sections, the chats with their tickets, and who is signed in. Folds away from the window bar.
2. **Header**: The section’s name, which on Agents opens a sort menu for both lists, then Refresh and Copy link.
3. **Toolbar**: A field that filters the heatmap and the table together, with the count as its placeholder, and Add agent.
4. **Range bar**: The title, the No reading key and 24h / 7d / 30d, which switch the heatmap, its axis and the table’s line together.
5. **Legend**: Four violet bands and At limit. Hovering or pressing one fades every cell outside it.
6. **Heatmap**: A grid of every agent’s fullest window, 49 cells a row, merged into fewer when a cell would be narrower than 12.5 px. Arrow keys walk it; Enter opens the agent.
7. **Tooltip**: One tooltip placed from the hovered or focused cell, below it in the first rows and above it further down; the agent’s name lights.
8. **Agents table**: Each agent with its runtime, where it runs, a bar per window and the last readings as a line. A row opens its details.

## When to use

- A team’s pool of seats, keys or agents, each with a quota that resets on a window, where the question is who is close to the limit and when.
- One reading per item per time slot, best read as a grid: a heatmap shows quiet hours, bursts and hits at the limit across every row at a glance.
- A list that pairs the overview with the current numbers: bars for the windows now, a small line for the trend, and a details panel per row.
- An app screen with its own navigation: the sidebar, the chats and the workspace switcher come with it, and fold away when the data needs the room.

## When not to use

- Billing or spend in money. Use a cost dashboard with totals, a period picker and invoices; shares of a window say nothing about price.
- A single agent or key. Show its own page with its windows and resource charts, as the details panel does, without the heatmap.
- Readings finer than half an hour, or live streams. Use a time-series chart with zoom; the grid merges cells, it never adds them.
- Hundreds of rows. The heatmap stops reading past a screenful; group by team or plan, or rank the fullest and page the rest.
- Alerting. A row at the limit only turns orange; send limit warnings as notifications, not as a screen someone has to watch.

## Installation

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

```sh
npx shadcn@latest add https://brunodelorence.com/r/usage-dashboard.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 { UsageDashboard } from './usage-dashboard';

export function AgentsPage() {
  return (
    <UsageDashboard
      workspace="Kestrel Labs"
      user={{ name: 'Noa Castell' }}
      defaultRange="7d"
      onAgentOpen={(agent) => console.log('Opened', agent.name)}
      autoplay={false}
    />
  );
}
```

## States

- **Last day**: The default: 48 half hours and the current one per agent, violet by share, orange at the limit, grey with no reading.
- **Cell tooltip**: A half hour at the limit: the tooltip says the agent was paused until the reset, and the agent’s name lights in the grid.
- **Last week**: The `7d` range: each cell 3½ hours, the axis in days, the table’s line over the same week.
- **Last month**: The `30d` range: each cell 15 hours. The week and the month are seeded, so they are the same on every load.
- **Agent details**: A row opens its panel beside the main column: windows, runtime details, and resource charts over 1, 6 or 24 hours.
- **Agent with an error**: An agent that answered HTTP 429 instead of its usage: the error in place of its bars, gaps in its heatmap row, and its details still open.
- **Waiting for sign-in**: A freshly added agent: an empty row in the heatmap and Waiting for sign-in in the table until someone signs in on it.
- **Add agent**: A modal dialog over the window: a name, the runtime, where it runs and the plan. Add stays off until there is a name.
- **Sidebar folded**: The window bar’s button slides the sidebar out; the heatmap’s cells widen to the room it leaves.
- **Dark**: With `colorScheme="dark"` the window turns near-black, the sidebar cooler, and the cards a shade darker than the page.
- **Details over the column**: Under 1100 px of its own width the panel goes over the main column, with a scrim; the table has dropped its host and line.
- **Phone**: Under 720 px the sidebar is a drawer behind the menu button, the heatmap merges its cells, and the usage goes under each name.

## How it works

- The heatmap is an ARIA grid of 49 cells a row (48 half hours and the current one, or 3½ or 15 hours for the week and the month). A cell’s colour is color-mix(in oklab) between two tokens by its reading, so one custom property per cell draws every shade in both schemes; a reading at the limit is orange and a missing one a flat grey.
- The cells stretch to the card. When they would get narrower than 12.5 px the row is merged into fewer columns, each the fullest of the half hours it covers (the current cell stays alone), and the axis keeps only the labels that still fit, so a narrow card keeps readable squares instead of scrolling.
- Hovering or focusing a cell places one tooltip from the cell’s box (below it in the first rows, above it further down) and lights the agent’s name; the arrow keys, Home and End walk the grid and Enter opens the agent. Hovering a legend swatch fades every cell outside that band.
- 24h / 7d / 30d switch the readings, the axis and the table’s line together. The last day is written out by hand; the week and the month are a seeded random walk per agent, so they are the same on every load.
- Each usage bar is a full-width fill slid by translateX to its share, never narrower than a dot, so 4% still shows and a new value glides. The lines and the resource charts are SVG paths in a box of their own units, stretched to their slot with a stroke that does not scale; a missing reading breaks the line.
- A row opens the details panel: beside the main column from 1100 px, over it with a scrim below. Its menu restarts, copies the ID of or removes the agent, with Undo in a toast; Add agent opens a modal dialog, and the new agent waits for its sign-in with an empty row in the heatmap. Agents ⌄ sorts both lists and the toolbar’s field (or ⌘K) filters them.
- The window bar’s button folds the sidebar away: it slides out by transform while the toolbar and the content slide by its width with the Web Animations API. Under 720 px it is a drawer behind a menu button; the table drops its runtime, host and line columns when the main column is narrow, and on a phone the usage goes under each name.
- While on screen and untouched, a demo pointer hovers three cells and the At limit band, switches to the week and back, opens an agent and its resources over six hours, starts adding an agent and folds the sidebar. Any real input hands it over; it comes back after 9 s of quiet.
- Every colour is a light-dark() token on the root: a near-black window with a cooler sidebar and cards a shade darker than the page by night, a pale grey sidebar and white cards by day, the same violet a shade deeper.

## API reference

- **agents** (`Agent[]`, default `a team of eleven`): The agents listed: name, runtime, host, version, 5-hour and weekly usage (0–100), an error or a pending sign-in, and the runtime details the panel shows.
- **defaultRange** (`'24h' | '7d' | '30d'`, default `'24h'`): The heatmap’s range at first.
- **workspace** (`string`, default `'Kestrel Labs'`): The workspace’s name in the switcher.
- **user** (`{ name: string }`, default `{ name: 'Noa Castell' }`): Who is signed in, at the foot of the sidebar.
- **onAgentOpen** (`(agent: Agent) => void`): Called when an agent’s details open.
- **onAddAgent** (`(agent: Agent) => void`): Called with the new agent when the Add agent dialog is sent.
- **defaultState** (`UsageDashboardState`): Opens in this moment, held still: range, selected (an agent’s id, its details open), cell ({ agent, index }, a heatmap cell with its tooltip), collapsed (the sidebar folded away) and adding (the Add agent dialog open). Turns the demo off.
- **autoplay** (`boolean`, default `true`): While on screen and untouched, a demo pointer shows the main interactions. Any real input stops it.
- **colorScheme** (`'light' | 'dark' | 'system'`): Light or dark colours, or the reader’s; without it, the page’s color-scheme.
- **className** (`string`): Extra class for the root.

## Accessibility

- Landmarks and names: the sidebar is a navigation with aria-current on its section, the page a main with a heading, the details panel a complementary region named after the agent. The window bar’s toggle is a disclosure for the sidebar (a drawer on a phone); Pinned, Shared, Chats and chats with threads are disclosures too.
- The heatmap is a grid with one tab stop: each row has its agent as a row header, each cell is named with its span and reading (“Tomas (6.25x), 06:53 PM – 07:23 PM: At limit”), the arrows, Home and End move and Enter opens the agent. The legend’s swatches are toggle buttons.
- The ranges are radio groups (arrows move). The workspace, sort, help and agent menus close on Escape or a press elsewhere. Add agent is a modal dialog that traps the focus, closes on Escape and gives the focus back to its button; its choices are real radio buttons.
- Each table row opens through its agent’s name, a button stretched over the row with aria-expanded; the usage figures carry their window’s name for screen readers. Range and sort changes, removals, restarts and copies are announced in a polite live region.
- The lines and the resource charts are drawn for the eye; their figures are beside them. Under reduced motion nothing slides and the demo never plays.

## Source

### usage-dashboard/usage-dashboard.tsx

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

'use client';

/*
 * Usage dashboard: the Agents screen of a team's coding-agent app, where every agent is signed in
 * to a plan with a 5-hour (and sometimes a weekly) usage window. An app window with a sidebar
 * (workspace switcher, search, sections, pinned and shared items, chats with their tickets and pull
 * requests, the signed-in person), and a main column: a heatmap of the fullest plan window of
 * every agent over the last day, week or month, and a table of agents with their windows as bars
 * and their last readings as a line. A row opens the agent's details in a side panel.
 *
 * - The heatmap is a grid: hovering or focusing a cell shows its tooltip (agent, span, reading),
 *   the arrow keys walk it and Enter opens that agent. Its cells stretch to the card; when they
 *   would get narrower than 12.5 px they merge into fewer columns (the fullest wins), so a narrow card keeps
 *   readable squares. Hovering a legend swatch lights only the cells of that band.
 * - 24h / 7d / 30d switch the readings (49 cells: half hours, 3½ hours or 15 hours), the axis and
 *   the table's line. Agents ⌄ sorts both lists, the toolbar's field filters them.
 * - A row opens the details panel: plan windows, runtime details and resource charts with their
 *   own 1h / 6h / 24h, and a menu to restart, copy the ID of or remove the agent (with undo).
 *   Add agent opens a dialog; the new agent waits for its sign-in, with no readings yet.
 * - The sidebar folds away (and slides back) from the window bar; on a phone it is a drawer.
 * - While on screen and untouched, a demo pointer hovers cells and the legend, switches the range,
 *   opens an agent and its resource range, starts adding an agent and folds the sidebar. Any real
 *   input hands it over to the reader; it starts again after a while.
 * - Layout from its own width (container queries): the details panel beside the main column from
 *   1100 px and over it below, the sidebar a drawer under 720 px; the table drops its runtime,
 *   host and line columns when the main column is narrow.
 * - Light or dark: every colour is a light-dark() token, so the scheme is the page's
 *   color-scheme, or the one the colorScheme prop sets.
 */

import {
  useEffect,
  useEffectEvent,
  useId,
  useLayoutEffect,
  useRef,
  useState,
  type CSSProperties,
  type KeyboardEvent as ReactKeyboardEvent,
  type ReactNode,
} from 'react';
import { ResourceChart, Sparkline } from './charts';
import {
  AGENTS,
  CELLS,
  CHATS,
  HOSTS,
  PINNED,
  PLANS,
  RUNTIMES,
  SHARED,
  WORKSPACES,
  axisLabel,
  bucket,
  cellSpan,
  merged,
  columnOf,
  clockIn,
  readings,
  resources,
  type Agent,
  type Chat,
  type Chip,
  type Host,
  type Range,
  type ResourceRange,
  type Runtime,
} from './data';
import { DemoPointer, runDemo, type Point } from './demo-pointer';
import { RuntimeMark, icons, type IconName } from './icons';
import { useAutoplay } from './use-autoplay';
import styles from './usage-dashboard.module.css';

type Section = 'chat' | 'agents' | 'skills' | 'memories' | 'triggers' | 'connectors';
type Sort = 'default' | 'name' | 'five' | 'weekly';

const NAV: { id: Section; label: string; icon: IconName }[] = [
  { id: 'chat', label: 'New chat', icon: 'compose' },
  { id: 'agents', label: 'Agents', icon: 'agent' },
  { id: 'skills', label: 'Skills', icon: 'skills' },
  { id: 'memories', label: 'Memories', icon: 'memory' },
  { id: 'triggers', label: 'Triggers', icon: 'clock' },
  { id: 'connectors', label: 'Connectors', icon: 'cloud' },
];
const EMPTY: Record<Exclude<Section, 'agents'>, string> = {
  chat: 'Start a chat with any agent of the workspace.',
  skills: 'Skills teach every agent a task once. None written yet.',
  memories: 'What agents remember across chats shows here.',
  triggers: 'Run an agent on a schedule or on an event. None set up yet.',
  connectors: 'Connect the tools agents may read and write.',
};
const RANGES: Range[] = ['24h', '7d', '30d'];
const RESOURCE_RANGES: ResourceRange[] = ['1h', '6h', '24h'];
const SORTS: { id: Sort; label: string }[] = [
  { id: 'default', label: 'Default order' },
  { id: 'name', label: 'Name' },
  { id: 'five', label: '5-hour usage' },
  { id: 'weekly', label: 'Weekly usage' },
];
const LEGEND = ['Under 25%', '25–49%', '50–74%', '75–99%'];
const SPAN_NAME: Record<Range, string> = {
  '24h': 'last day',
  '7d': 'last week',
  '30d': 'last month',
};
/** The axis labels' cells (of 49): every quarter, then Now. */
const TICKS = [0, 12, 24, 36];

/** A moment to open in, held still: no demo. */
export type UsageDashboardState = {
  range?: Range;
  /** The agent whose details are open, by id. */
  selected?: string;
  /** A heatmap cell whose tooltip shows: the agent's id and the cell's index (0–48). */
  cell?: { agent: string; index: number };
  /** The sidebar folded away. */
  collapsed?: boolean;
  /** The Add agent dialog open. */
  adding?: boolean;
};

type Tip = { agent: string; index: number; x: number; y: number; below: boolean };

function percent(value: number | null) {
  if (value === null) return 'No reading';
  if (value >= 1) return 'At limit';
  return `${Math.round(value * 100)}%`;
}

function ChipIcon({ chip }: { chip: Chip }) {
  const Icon = icons[chip.kind === 'ticket' ? 'ticket' : chip.kind];
  return <Icon className={styles.chipIcon} />;
}

export function UsageDashboard({
  autoplay = true,
  defaultRange = '24h',
  agents: initialAgents = AGENTS,
  workspace: workspaceName,
  user = { name: 'Noa Castell' },
  onAgentOpen,
  onAddAgent,
  defaultState,
  colorScheme,
  className = '',
}: {
  /** While on screen and untouched, a demo pointer shows the main interactions. */
  autoplay?: boolean;
  /** The heatmap's range at first. */
  defaultRange?: Range;
  /** The agents listed (the default is a team of eleven). */
  agents?: Agent[];
  /** The workspace's name in the switcher (the default is Kestrel Labs). */
  workspace?: string;
  /** Who is signed in, at the foot of the sidebar. */
  user?: { name: string };
  /** Called when an agent's details open. */
  onAgentOpen?: (agent: Agent) => void;
  /** Called with a new agent from the Add agent dialog. */
  onAddAgent?: (agent: Agent) => void;
  /** Opens in a given moment, held still (no demo). */
  defaultState?: UsageDashboardState;
  /** Light or dark colours, or the reader's ('system'); without it, the page's color-scheme. */
  colorScheme?: 'light' | 'dark' | 'system';
  className?: string;
}) {
  const { ref: root, playing, take } = useAutoplay<HTMLDivElement>(9000);
  const pointer = useRef<HTMLSpanElement>(null);
  const id = useId();
  const held = defaultState !== undefined;
  const start = defaultState ?? {};

  const [agents, setAgents] = useState<Agent[]>(initialAgents);
  const [section, setSection] = useState<Section>('agents');
  const [back, setBack] = useState<Section[]>([]);
  const [forward, setForward] = useState<Section[]>([]);
  const [collapsed, setCollapsed] = useState(Boolean(start.collapsed));
  const [drawer, setDrawer] = useState(false);
  const [workspace, setWorkspace] = useState(0);
  const [open, setOpen] = useState<null | 'workspace' | 'sort' | 'help' | 'agent'>(null);
  const [groups, setGroups] = useState({ pinned: false, shared: false, chats: true });
  const [threads, setThreads] = useState<Record<string, boolean>>({ c4: true });
  const [activeChat, setActiveChat] = useState<string | null>(null);
  const [read, setRead] = useState<Set<string>>(() => new Set());
  const [helpSeen, setHelpSeen] = useState(false);
  const [sort, setSort] = useState<Sort>('default');
  const [query, setQuery] = useState('');
  const [range, setRange] = useState<Range>(start.range ?? defaultRange);
  const [tip, setTip] = useState<Tip | null>(null);
  const [focus, setFocus] = useState<[number, number]>([0, CELLS - 1]);
  const [band, setBand] = useState<number | null>(null);
  const [rowHover, setRowHover] = useState<string | null>(null);
  const [selected, setSelected] = useState<string | null>(start.selected ?? null);
  const [resourceRange, setResourceRange] = useState<ResourceRange>('1h');
  const [restarting, setRestarting] = useState<string | null>(null);
  const [adding, setAdding] = useState(Boolean(start.adding));
  const [draft, setDraft] = useState({
    name: '',
    runtime: 'ember' as Runtime,
    host: 'cloud' as Host,
    plan: '6.25x' as (typeof PLANS)[number],
  });
  const [toast, setToast] = useState<{ text: string; undo?: () => void } | null>(null);
  const [spin, setSpin] = useState(0);
  const [message, setMessage] = useState('');
  const [count, setCount] = useState(CELLS);
  const countRef = useRef(CELLS);
  useEffect(() => {
    countRef.current = count;
  });
  const cellsRef = useRef<HTMLDivElement>(null);
  const heatRef = useRef<HTMLDivElement>(null);
  const contentRef = useRef<HTMLDivElement>(null);
  const toolbarRef = useRef<HTMLDivElement>(null);
  const filterRef = useRef<HTMLInputElement>(null);
  const addButton = useRef<HTMLButtonElement>(null);
  const dialogRef = useRef<HTMLDivElement>(null);
  const toastTimer = useRef(0);
  const restartTimer = useRef(0);

  const announce = (text: string) => setMessage((now) => (now === text ? `${text} ` : text));
  const say = (text: string, undo?: () => void) => {
    setToast({ text, undo });
    announce(text);
    window.clearTimeout(toastTimer.current);
    toastTimer.current = window.setTimeout(() => setToast(null), undo ? 5000 : 2400);
  };
  useEffect(
    () => () => {
      window.clearTimeout(toastTimer.current);
      window.clearTimeout(restartTimer.current);
    },
    [],
  );

  // Cells merge into fewer columns when they would be too small to read.
  useLayoutEffect(() => {
    const element = cellsRef.current;
    if (!element) return;
    const measure = () => {
      const width = element.clientWidth;
      setCount(Math.max(7, Math.min(CELLS, Math.floor((width + 2.3) / 13.3))));
    };
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(element);
    return () => observer.disconnect();
  }, [section]);

  // Menus close on a press elsewhere or Escape; so do the drawer, the panel and the dialog.
  useEffect(() => {
    const element = root.current;
    if (!element) return;
    const away = (event: PointerEvent) => {
      const target = event.target as Element;
      if (open && !target.closest?.('[data-popover]')) setOpen(null);
    };
    const escape = (event: KeyboardEvent) => {
      if (event.key !== 'Escape') return;
      if (open) {
        setOpen(null);
        return;
      }
      if (adding) {
        closeDialog();
        return;
      }
      if (drawer) setDrawer(false);
      else if (selected) setSelected(null);
    };
    document.addEventListener('pointerdown', away);
    element.addEventListener('keydown', escape);
    return () => {
      document.removeEventListener('pointerdown', away);
      element.removeEventListener('keydown', escape);
    };
  });

  // The dialog takes the focus, and gives it back to Add agent.
  useEffect(() => {
    if (!adding) return;
    dialogRef.current?.querySelector<HTMLInputElement>('input')?.focus({ preventScroll: true });
  }, [adding]);

  // ⌘K / Ctrl+K, inside the dashboard, goes to the filter.
  const onRootKey = (event: ReactKeyboardEvent) => {
    if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {
      event.preventDefault();
      event.stopPropagation();
      goSearch();
    }
  };

  // The lists.
  const q = query.trim().toLowerCase();
  const sorted = [...agents].sort((a, b) => {
    if (sort === 'name') return a.name.localeCompare(b.name);
    if (sort === 'five') return b.fiveHour - a.fiveHour;
    if (sort === 'weekly') return (b.weekly ?? -1) - (a.weekly ?? -1);
    return 0;
  });
  const shown = sorted.filter(
    (agent) =>
      !q ||
      agent.name.toLowerCase().includes(q) ||
      RUNTIMES[agent.runtime].toLowerCase().includes(q) ||
      HOSTS[agent.host].toLowerCase().includes(q),
  );
  const rows = shown.map((agent) => merged(readings(agent, range), count));
  const columns = count;
  const ticks = (count >= 40 ? TICKS : count >= 16 ? [0, 24] : [0]).map((cell) =>
    columnOf(cell, count),
  );
  const current = agents.find((agent) => agent.id === selected) ?? null;

  // Actions, shared by the reader and the demo.
  const go = (next: Section) => {
    if (next === section) return;
    setBack((list) => [...list, section]);
    setForward([]);
    setSection(next);
    setDrawer(false);
    setTip(null);
  };
  const goBack = () => {
    const previous = back[back.length - 1];
    if (!previous) return;
    setBack((list) => list.slice(0, -1));
    setForward((list) => [section, ...list]);
    setSection(previous);
  };
  const goForward = () => {
    const next = forward[0];
    if (!next) return;
    setForward((list) => list.slice(1));
    setBack((list) => [...list, section]);
    setSection(next);
  };
  const goSearch = () => {
    if (section !== 'agents') go('agents');
    setDrawer(false);
    window.requestAnimationFrame(() => filterRef.current?.focus());
  };
  const toggleSidebar = () => {
    const wide = (root.current?.offsetWidth ?? 1200) >= 720;
    if (!wide) {
      setDrawer((now) => !now);
      return;
    }
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (!reduced) {
      // The toolbar and the content slide by the sidebar's width as it goes or comes back.
      const by = collapsed ? -285 : 285;
      for (const element of [toolbarRef.current, contentRef.current]) {
        element?.animate([{ transform: `translateX(${by}px)` }, { transform: 'none' }], {
          duration: 280,
          easing: 'cubic-bezier(0.3, 0, 0.2, 1)',
        });
      }
    }
    announce(collapsed ? 'Sidebar shown' : 'Sidebar hidden');
    setCollapsed(!collapsed);
  };
  const chooseRange = (next: Range) => {
    setRange(next);
    setTip(null);
    announce(`Usage over the ${SPAN_NAME[next]}`);
  };
  const openAgent = (agent: Agent) => {
    setSelected(agent.id);
    setOpen(null);
    setTip(null);
    onAgentOpen?.(agent);
  };
  const refresh = () => {
    setSpin((turns) => turns + 1);
    say('Usage refreshed');
  };
  const copy = (text: string, done: string) => {
    navigator.clipboard?.writeText(text).catch(() => {});
    say(done);
  };
  const removeAgent = (agent: Agent) => {
    const index = agents.findIndex((item) => item.id === agent.id);
    setAgents((list) => list.filter((item) => item.id !== agent.id));
    setSelected(null);
    setOpen(null);
    say(`${agent.name} removed`, () => {
      setAgents((list) => {
        if (list.some((item) => item.id === agent.id)) return list;
        const next = [...list];
        next.splice(index, 0, agent);
        return next;
      });
      setToast(null);
      announce(`${agent.name} restored`);
    });
  };
  const restart = (agent: Agent) => {
    setOpen(null);
    setRestarting(agent.id);
    announce(`Restarting ${agent.name}`);
    window.clearTimeout(restartTimer.current);
    restartTimer.current = window.setTimeout(() => {
      setRestarting(null);
      announce(`${agent.name} is online`);
    }, 1600);
  };
  const closeDialog = () => {
    setAdding(false);
    window.requestAnimationFrame(() => addButton.current?.focus({ preventScroll: true }));
  };
  const addAgent = () => {
    const name = draft.name.trim();
    if (!name) return;
    const agent: Agent = {
      id: `new-${Date.now()}`,
      name: `${name} (${draft.plan})`,
      runtime: draft.runtime,
      host: draft.host,
      version: draft.runtime === 'ember' ? '0.1.19' : '2.4.0',
      fiveHour: 0,
      pending: true,
      latency: 0,
      uptime: '—',
      conversations: 0,
      active: 0,
      linked: 'never',
      resetsIn: 300,
      digest: '0000000000000000000000000000000000',
      seed: name.length * 13,
    };
    setAgents((list) => [...list, agent]);
    setDraft((now) => ({ ...now, name: '' }));
    onAddAgent?.(agent);
    closeDialog();
    say(`${agent.name} added. Waiting for its sign-in`);
  };

  /** Shows the tooltip of a cell element, placed from its box. */
  const showTip = (cell: HTMLElement, agent: string, index: number) => {
    const card = heatRef.current;
    if (!card) return;
    const frame = card.getBoundingClientRect();
    const box = cell.getBoundingClientRect();
    const scale = frame.width / card.offsetWidth || 1;
    const top = (box.top - frame.top) / scale;
    const flip = top < 98;
    setTip({
      agent,
      index,
      x: (box.left + box.width / 2 - frame.left) / scale,
      y: flip ? (box.bottom - frame.top) / scale : top,
      below: flip,
    });
  };
  const cellFor = (agent: string, index: number) =>
    heatRef.current?.querySelector<HTMLElement>(`[data-cell="${agent}:${index}"]`) ?? null;

  // A held cell from defaultState.
  useLayoutEffect(() => {
    if (!start.cell) return;
    const cell = cellFor(start.cell.agent, start.cell.index);
    if (cell) showTip(cell, start.cell.agent, start.cell.index);
  }, []);

  const onCellKey = (event: ReactKeyboardEvent, row: number, col: number) => {
    const moves: Record<string, [number, number]> = {
      ArrowLeft: [0, -1],
      ArrowRight: [0, 1],
      ArrowUp: [-1, 0],
      ArrowDown: [1, 0],
    };
    let next: [number, number] | null = null;
    if (moves[event.key]) {
      const [dr, dc] = moves[event.key];
      next = [
        Math.max(0, Math.min(rows.length - 1, row + dr)),
        Math.max(0, Math.min(columns - 1, col + dc)),
      ];
    } else if (event.key === 'Home') next = [row, 0];
    else if (event.key === 'End') next = [row, columns - 1];
    else if (event.key === 'Enter' || event.key === ' ') {
      event.preventDefault();
      openAgent(shown[row]);
      return;
    }
    if (!next) return;
    event.preventDefault();
    setFocus(next);
    const agent = shown[next[0]].id;
    const cell = cellFor(agent, next[1]);
    cell?.focus();
    if (cell) showTip(cell, agent, next[1]);
  };

  // The demo.
  const demoAct = useEffectEvent((name: string, value?: unknown) => {
    switch (name) {
      case 'cell': {
        const [agent, index] = value as [string, number];
        const column = columnOf(index, count);
        const cell = cellFor(agent, column);
        if (cell) showTip(cell, agent, column);
        break;
      }
      case 'untip':
        setTip(null);
        break;
      case 'band':
        setBand(value as number | null);
        break;
      case 'range':
        chooseRange(value as Range);
        break;
      case 'row':
        setRowHover(value as string | null);
        break;
      case 'open': {
        const agent = agents.find((item) => item.id === value);
        if (agent) openAgent(agent);
        break;
      }
      case 'resources':
        setResourceRange(value as ResourceRange);
        break;
      case 'close':
        setSelected(null);
        break;
      case 'add':
        setAdding(true);
        setDraft((now) => ({ ...now, name: '' }));
        break;
      case 'name':
        setDraft((now) => ({ ...now, name: value as string }));
        break;
      case 'host':
        setDraft((now) => ({ ...now, host: value as Host }));
        break;
      case 'cancel':
        setAdding(false);
        setDraft((now) => ({ ...now, name: '', host: 'cloud' }));
        break;
      case 'sidebar':
        toggleSidebar();
        break;
      case 'reset':
        setTip(null);
        setBand(null);
        setRowHover(null);
        break;
    }
  });
  const demo = autoplay && !held && playing && section === 'agents';
  useEffect(() => {
    const stage = root.current;
    const arrow = pointer.current;
    if (!demo || !stage || !arrow) return;
    const centre = (name: string, dx = 0.5, dy = 0.5): Point | null => {
      const target = stage.querySelector<HTMLElement>(`[data-demo="${name}"]`);
      if (!target || !target.getClientRects().length) return null;
      const frame = stage.getBoundingClientRect();
      const scale = frame.width / stage.offsetWidth || 1;
      const rect = target.getBoundingClientRect();
      if (rect.width === 0) return null;
      return {
        x: (rect.left + rect.width * dx - frame.left) / scale,
        y: (rect.top + rect.height * dy - frame.top) / scale,
      };
    };
    const stop = runDemo(stage, arrow, async ({ wait, glide, glyph }) => {
      const to = async (name: string, ms = 620, dx?: number, dy?: number) => {
        const point = centre(name, dx, dy);
        if (point) await glide(point, ms);
        return Boolean(point);
      };
      const cell = async (agent: string, index: number) => {
        const point = centre(`cell-${agent}-${columnOf(index, countRef.current)}`);
        if (!point) return;
        await glide(point, 640);
        demoAct('cell', [agent, index]);
        await wait(1100);
      };
      for (;;) {
        demoAct('reset');
        await wait(700);
        // Hover the heatmap: two cells at the limit, one busy.
        glyph('arrow');
        await cell('tomas-625', 41);
        await cell('mara-625', 6);
        await cell('oren-20', 30);
        demoAct('untip');
        // Light the At limit band, then all of them again.
        if (await to('band-4', 620)) {
          demoAct('band', 4);
          await wait(1400);
          demoAct('band', null);
        }
        // The week, then back to the day.
        glyph('hand');
        if (await to('range-7d', 650)) {
          await wait(180);
          demoAct('range', '7d');
          await wait(1700);
          await to('range-24h', 380);
          await wait(180);
          demoAct('range', '24h');
          await wait(800);
        }
        // Open an agent and look at its resources over six hours.
        if (await to('row-tomas-625', 750, 0.3)) {
          demoAct('row', 'tomas-625');
          await wait(500);
          demoAct('open', 'tomas-625');
          demoAct('row', null);
          await wait(1500);
          if (await to('resources-6h', 700)) {
            await wait(180);
            demoAct('resources', '6h');
            await wait(1500);
            await to('resources-1h', 360);
            await wait(180);
            demoAct('resources', '1h');
            await wait(700);
          }
          if (await to('close-panel', 700)) {
            await wait(200);
            demoAct('close');
            await wait(700);
          }
        }
        // Start adding an agent, then think better of it.
        if (await to('add-agent', 700)) {
          await wait(200);
          demoAct('add');
          await wait(500);
          glyph('arrow');
          const name = 'Rhea';
          for (let length = 1; length <= name.length; length += 1) {
            demoAct('name', name.slice(0, length));
            await wait(110);
          }
          await wait(400);
          glyph('hand');
          if (await to('host-self', 600)) {
            await wait(180);
            demoAct('host', 'self');
            await wait(700);
          }
          if (await to('cancel-add', 600)) {
            await wait(200);
          }
          demoAct('cancel');
          await wait(700);
        }
        // Fold the sidebar away and bring it back.
        if (await to('sidebar-toggle', 800)) {
          await wait(200);
          demoAct('sidebar');
          await wait(1500);
          demoAct('sidebar');
          await wait(900);
        }
        glyph('arrow');
        await wait(1400);
      }
    });
    return () => {
      stop();
      demoAct('reset');
    };
  }, [demo, root]);

  // The sidebar.
  const chatRow = (chat: Chat, nested = false): ReactNode => {
    const unread = chat.unread && !read.has(chat.id);
    const hasThreads = Boolean(chat.children?.length);
    const expanded = Boolean(threads[chat.id]);
    return (
      <li key={chat.id} className={styles.chatItem} data-nested={nested || undefined}>
        <div className={styles.chatRow} data-active={activeChat === chat.id || undefined}>
          <button
            type="button"
            className={styles.chatButton}
            aria-current={activeChat === chat.id ? 'true' : undefined}
            onClick={() => {
              setActiveChat(chat.id);
              setRead((set) => new Set(set).add(chat.id));
              if (hasThreads) setThreads((now) => ({ ...now, [chat.id]: !now[chat.id] }));
            }}
            aria-expanded={hasThreads ? expanded : undefined}
          >
            <RuntimeMark runtime="ember" className={styles.chatMark} />
            <span className={styles.chatTitle}>{chat.title}</span>
            {hasThreads && !expanded && <icons.right className={styles.chatMore} />}
            <span className={styles.chatDots}>
              {chat.running && (
                <span className={styles.dot} data-tone="running">
                  <span className={styles.hidden}>, running</span>
                </span>
              )}
              {unread && (
                <span className={styles.dot} data-tone="unread">
                  <span className={styles.hidden}>, unread</span>
                </span>
              )}
            </span>
          </button>
          {chat.chips && (
            <span className={styles.chips}>
              {chat.chips.map((chip) => (
                <span key={chip.label} className={styles.chip} data-kind={chip.kind}>
                  <ChipIcon chip={chip} />
                  {chip.label}
                </span>
              ))}
            </span>
          )}
        </div>
        {hasThreads && expanded && (
          <ul className={styles.threads}>{chat.children!.map((child) => chatRow(child, true))}</ul>
        )}
      </li>
    );
  };

  const group = (
    key: 'pinned' | 'shared',
    label: string,
    items: { id: string; title: string }[],
  ) => (
    <div className={styles.group}>
      <button
        type="button"
        className={styles.groupHead}
        aria-expanded={groups[key]}
        aria-controls={`${id}-${key}`}
        onClick={() => setGroups((now) => ({ ...now, [key]: !now[key] }))}
      >
        {label}
        <icons.right className={styles.groupChevron} />
      </button>
      <ul id={`${id}-${key}`} className={styles.groupList} hidden={!groups[key]}>
        {items.map((item) => (
          <li key={item.id}>
            <button
              type="button"
              className={styles.pinnedItem}
              aria-current={activeChat === item.id ? 'true' : undefined}
              onClick={() => setActiveChat(item.id)}
            >
              {item.title}
            </button>
          </li>
        ))}
      </ul>
    </div>
  );

  const shownWorkspace = WORKSPACES[workspace];
  const workspaceLabel = workspace === 0 && workspaceName ? workspaceName : shownWorkspace.name;
  const sectionLabel = NAV.find((item) => item.id === section)?.label ?? 'Agents';
  const countLabel = `${agents.length} ${agents.length === 1 ? 'item' : 'items'}`;

  const sidebar = (
    <nav
      id={`${id}-sidebar`}
      className={styles.sidebar}
      aria-label="Workspace"
      data-part="sidebar"
      inert={(collapsed && !drawer) || undefined}
    >
      <div className={styles.titlebar} aria-hidden="true" />
      <div className={styles.workspace} data-popover>
        <button
          type="button"
          className={styles.workspaceButton}
          aria-haspopup="menu"
          aria-expanded={open === 'workspace'}
          onClick={() => setOpen(open === 'workspace' ? null : 'workspace')}
          data-part="workspace-switcher"
        >
          <span className={styles.workspaceLogo} aria-hidden="true">
            <span className={styles.workspaceMark} data-mark={shownWorkspace.mark} />
          </span>
          <span className={styles.workspaceName}>{workspaceLabel}</span>
          <icons.updown className={styles.workspaceChevron} />
        </button>
        {open === 'workspace' && (
          <div className={styles.menu} role="menu" aria-label="Workspaces" data-at="workspace">
            {WORKSPACES.map((item, index) => (
              <button
                key={item.id}
                type="button"
                role="menuitemradio"
                aria-checked={workspace === index}
                className={styles.menuItem}
                onClick={() => {
                  setWorkspace(index);
                  setOpen(null);
                  announce(`${index === 0 && workspaceName ? workspaceName : item.name} workspace`);
                }}
              >
                <span className={styles.menuMark} aria-hidden="true">
                  {item.mark}
                </span>
                {index === 0 && workspaceName ? workspaceName : item.name}
                {workspace === index && <icons.check className={styles.menuCheck} />}
              </button>
            ))}
          </div>
        )}
      </div>
      <button type="button" className={styles.search} onClick={goSearch} data-part="search">
        <icons.search className={styles.navIcon} />
        <span>Search</span>
        <kbd className={styles.kbd} aria-label="Command K">
          ⌘ K
        </kbd>
      </button>
      <ul className={styles.nav}>
        {NAV.map((item) => {
          const Icon = icons[item.icon];
          return (
            <li key={item.id}>
              <button
                type="button"
                className={styles.navItem}
                aria-current={section === item.id ? 'page' : undefined}
                onClick={() => go(item.id)}
                data-part="nav-item"
              >
                <Icon className={styles.navIcon} />
                {item.label}
              </button>
            </li>
          );
        })}
      </ul>
      <div className={styles.lists} data-part="chats">
        {group('pinned', 'Pinned', PINNED)}
        {group('shared', 'Shared', SHARED)}
        <div className={styles.group}>
          <button
            type="button"
            className={styles.groupHead}
            data-chats
            aria-expanded={groups.chats}
            aria-controls={`${id}-chats`}
            onClick={() => setGroups((now) => ({ ...now, chats: !now.chats }))}
          >
            Chats
            <icons.right className={styles.groupChevron} />
          </button>
          <ul id={`${id}-chats`} className={styles.chats} hidden={!groups.chats}>
            {CHATS.map((chat) => chatRow(chat))}
          </ul>
        </div>
      </div>
      <div className={styles.footer} data-popover data-part="account">
        <span className={styles.avatar} aria-hidden="true">
          {user.name
            .split(' ')
            .map((part) => part[0])
            .slice(0, 2)
            .join('')}
        </span>
        <span className={styles.userName}>{user.name}</span>
        <button
          type="button"
          className={styles.help}
          aria-label={helpSeen ? 'Help' : 'Help, 1 new'}
          aria-haspopup="menu"
          aria-expanded={open === 'help'}
          onClick={() => {
            setOpen(open === 'help' ? null : 'help');
            setHelpSeen(true);
          }}
        >
          <icons.help className={styles.helpIcon} />
          {!helpSeen && <span className={styles.helpDot} />}
        </button>
        {open === 'help' && (
          <div className={styles.menu} role="menu" aria-label="Help" data-at="help">
            {['What’s new: usage windows', 'Keyboard shortcuts', 'Status page'].map((label) => (
              <button
                key={label}
                type="button"
                role="menuitem"
                className={styles.menuItem}
                onClick={() => {
                  setOpen(null);
                  say(label === 'Keyboard shortcuts' ? '⌘K searches agents' : `${label} opened`);
                }}
              >
                {label}
              </button>
            ))}
          </div>
        )}
      </div>
    </nav>
  );

  // The heatmap card.
  const heatmap = (
    <section
      className={styles.heatCard}
      ref={heatRef}
      aria-labelledby={`${id}-heat`}
      data-part="heatmap"
    >
      <div className={styles.heatHead}>
        <h3 id={`${id}-heat`} className={styles.heatTitle}>
          Fullest plan window, every agent
        </h3>
        <div className={styles.legend} data-part="legend" onPointerLeave={() => setBand(null)}>
          <span className={styles.legendText}>0%</span>
          {LEGEND.map((label, index) => (
            <button
              key={label}
              type="button"
              className={styles.swatch}
              data-band={index}
              data-on={band === index || undefined}
              aria-pressed={band === index}
              aria-label={`Show only ${label}`}
              onPointerEnter={() => setBand(index)}
              onFocus={() => setBand(index)}
              onBlur={() => setBand(null)}
              onClick={() => setBand(band === index ? null : index)}
              data-demo={`band-${index}`}
            />
          ))}
          <span className={styles.legendText}>99%</span>
          <button
            type="button"
            className={styles.limitKey}
            aria-pressed={band === 4}
            onPointerEnter={() => setBand(4)}
            onFocus={() => setBand(4)}
            onBlur={() => setBand(null)}
            onClick={() => setBand(band === 4 ? null : 4)}
            data-demo="band-4"
          >
            <span className={styles.swatch} data-band={4} aria-hidden="true" />
            At limit
          </button>
        </div>
      </div>
      <div
        className={styles.heat}
        role="grid"
        aria-label={`Fullest plan window of every agent over the ${SPAN_NAME[range]}`}
        aria-rowcount={rows.length}
        data-band={band ?? undefined}
        style={{ '--columns': columns } as CSSProperties}
        onPointerLeave={() => setTip(null)}
      >
        {shown.map((agent, row) => (
          <div
            key={agent.id}
            className={styles.heatRow}
            role="row"
            data-lit={tip?.agent === agent.id || undefined}
          >
            <span className={styles.heatLabel} role="rowheader">
              <RuntimeMark runtime={agent.runtime} className={styles.heatMark} />
              <span className={styles.heatName}>{agent.name}</span>
            </span>
            <div className={styles.cells} ref={row === 0 ? cellsRef : undefined}>
              {rows[row].map((value, col) => {
                const focused =
                  Math.min(focus[0], shown.length - 1) === row &&
                  Math.min(focus[1], columns - 1) === col;
                return (
                  <span
                    key={col}
                    role="gridcell"
                    className={styles.cell}
                    data-level={bucket(value)}
                    data-hot={tip?.agent === agent.id && tip.index === col ? '' : undefined}
                    data-cell={`${agent.id}:${col}`}
                    data-demo={`cell-${agent.id}-${col}`}
                    style={
                      value !== null && value < 1 ? ({ '--v': value } as CSSProperties) : undefined
                    }
                    tabIndex={focused ? 0 : -1}
                    aria-label={`${agent.name}, ${cellSpan(range, col, count)}: ${percent(value)}`}
                    onPointerEnter={(event) => showTip(event.currentTarget, agent.id, col)}
                    onFocus={(event) => {
                      setFocus([row, col]);
                      showTip(event.currentTarget, agent.id, col);
                    }}
                    onBlur={() => setTip(null)}
                    onClick={() => openAgent(agent)}
                    onKeyDown={(event) => onCellKey(event, row, col)}
                  />
                );
              })}
            </div>
          </div>
        ))}
        {shown.length === 0 && <p className={styles.noMatch}>No agent matches “{query}”.</p>}
      </div>
      <div
        className={styles.axis}
        aria-hidden="true"
        style={{ '--columns': columns } as CSSProperties}
      >
        <span className={styles.axisSpacer} />
        <span className={styles.axisTrack}>
          {ticks.map((cell, index) =>
            index === 0 || ticks[index] !== ticks[index - 1] ? (
              <span
                key={index}
                className={styles.tick}
                style={{ '--at': cell / columns } as CSSProperties}
              >
                {axisLabel(range, cell, count)}
              </span>
            ) : null,
          )}
          <span className={styles.tickNow}>Now</span>
        </span>
      </div>
      {tip &&
        (() => {
          const agent = agents.find((item) => item.id === tip.agent);
          const row = shown.findIndex((item) => item.id === tip.agent);
          if (!agent || row < 0) return null;
          const value = rows[row][tip.index] ?? null;
          return (
            <div
              className={styles.tip}
              data-below={tip.below || undefined}
              data-part="tooltip"
              style={{ transform: `translate(${tip.x}px, ${tip.y}px)` }}
              aria-hidden="true"
            >
              <span className={styles.tipBox}>
                <span className={styles.tipName}>
                  <RuntimeMark runtime={agent.runtime} className={styles.tipMark} />
                  {agent.name}
                </span>
                <span className={styles.tipSpan}>{cellSpan(range, tip.index, count)}</span>
                <span className={styles.tipValue} data-level={bucket(value)}>
                  <i aria-hidden="true" />
                  {value === null
                    ? 'No reading'
                    : value >= 1
                      ? 'At limit, paused until reset'
                      : `${percent(value)} of its fullest window`}
                </span>
              </span>
            </div>
          );
        })()}
    </section>
  );

  // The table.
  const table = (
    <section className={styles.tableCard} aria-label="Agents" data-part="agents-table">
      <table className={styles.table}>
        <thead>
          <tr className={styles.headRow}>
            <th scope="col">Agent</th>
            <th scope="col" className={styles.colHost}>
              Runs on
            </th>
            <th scope="col">Usage</th>
            <th scope="col" className={styles.colSpark}>
              Last {range}
            </th>
            <th scope="col" className={styles.colGo}>
              <span className={styles.hidden}>Details</span>
            </th>
          </tr>
        </thead>
        <tbody>
          {shown.map((agent) => {
            const line = readings(agent, range);
            return (
              <tr
                key={agent.id}
                className={styles.row}
                data-selected={selected === agent.id || undefined}
                data-hover={rowHover === agent.id || undefined}
                data-demo={`row-${agent.id}`}
                data-part="agent-row"
              >
                <td className={styles.agentCell}>
                  <span className={styles.agentMark} aria-hidden="true">
                    <RuntimeMark runtime={agent.runtime} className={styles.agentGlyph} />
                  </span>
                  <button
                    type="button"
                    className={styles.agentOpen}
                    aria-expanded={selected === agent.id}
                    aria-controls={`${id}-panel`}
                    onClick={() => (selected === agent.id ? setSelected(null) : openAgent(agent))}
                  >
                    {agent.name}
                  </button>
                  <span className={styles.runtime}>{RUNTIMES[agent.runtime]}</span>
                </td>
                <td className={styles.colHost}>
                  <span className={styles.host}>
                    <i aria-hidden="true" data-host={agent.host} />
                    {HOSTS[agent.host]}
                  </span>
                </td>
                <td className={styles.usageCell}>
                  {agent.error || agent.pending ? (
                    <span className={styles.usageNote} data-error={agent.error ? '' : undefined}>
                      {agent.error ?? 'Waiting for sign-in'}
                    </span>
                  ) : (
                    <span className={styles.windows}>
                      {(
                        [
                          ['5-hour', agent.fiveHour],
                          ...(agent.weekly !== undefined ? [['Weekly', agent.weekly]] : []),
                        ] as [string, number][]
                      ).map(([label, value]) => (
                        <span key={label} className={styles.usageLine}>
                          <span className={styles.windowLabel}>{label}</span>
                          <span className={styles.bar} aria-hidden="true">
                            <span
                              className={styles.barFill}
                              data-zero={value === 0 || undefined}
                              style={{ '--share': value / 100 } as CSSProperties}
                            />
                          </span>
                          <span className={styles.windowValue}>
                            <span className={styles.hidden}>{label} window </span>
                            {value}%
                          </span>
                        </span>
                      ))}
                    </span>
                  )}
                </td>
                <td className={styles.colSpark}>
                  <Sparkline values={line} />
                </td>
                <td className={styles.colGo} aria-hidden="true">
                  <icons.right className={styles.rowChevron} />
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
      {shown.length === 0 && <p className={styles.noMatch}>Nothing to show.</p>}
    </section>
  );

  // The details panel.
  const panel = current
    ? (() => {
        const res = resources(current, resourceRange);
        const left = 100 - current.fiveHour;
        const status =
          restarting === current.id ? 'Restarting' : current.pending ? 'Waiting' : 'Online';
        return (
          <aside
            id={`${id}-panel`}
            className={styles.panel}
            aria-label={`${current.name} details`}
            data-part="details-panel"
          >
            <header className={styles.panelHead}>
              <button
                type="button"
                className={styles.iconButton}
                aria-label="Close details"
                onClick={() => setSelected(null)}
                data-demo="close-panel"
              >
                <icons.close className={styles.headIcon} />
              </button>
              <h3 className={styles.panelTitle}>Agent details</h3>
            </header>
            <div className={styles.panelScroll}>
              <div className={styles.panelAgent}>
                <span className={styles.panelMark} aria-hidden="true">
                  <RuntimeMark runtime={current.runtime} className={styles.panelGlyph} />
                </span>
                <span className={styles.panelWho}>
                  <span className={styles.panelName}>{current.name}</span>
                  <span className={styles.panelSub}>
                    {RUNTIMES[current.runtime]} · v{current.version}
                  </span>
                </span>
                <span className={styles.panelStatus} role="status">
                  <i data-status={status} aria-hidden="true" />
                  <span className={styles.statusWord}>{status}</span>
                  {status === 'Online' && (
                    <span className={styles.soft}>· {current.latency}ms</span>
                  )}
                  <span className={styles.miniBar} aria-hidden="true">
                    <span style={{ '--share': left / 100 } as CSSProperties} />
                  </span>
                  <span className={styles.soft}>{left}% left</span>
                </span>
                <span className={styles.panelMore} data-popover>
                  <button
                    type="button"
                    className={styles.iconButton}
                    aria-label="Agent actions"
                    aria-haspopup="menu"
                    aria-expanded={open === 'agent'}
                    onClick={() => setOpen(open === 'agent' ? null : 'agent')}
                  >
                    <icons.more className={styles.headIcon} />
                  </button>
                  {open === 'agent' && (
                    <div className={styles.menu} role="menu" aria-label="Agent" data-at="agent">
                      <button
                        type="button"
                        role="menuitem"
                        className={styles.menuItem}
                        onClick={() => restart(current)}
                      >
                        Restart agent
                      </button>
                      <button
                        type="button"
                        role="menuitem"
                        className={styles.menuItem}
                        onClick={() => {
                          setOpen(null);
                          copy(current.id, 'Agent ID copied');
                        }}
                      >
                        Copy agent ID
                      </button>
                      <button
                        type="button"
                        role="menuitem"
                        className={styles.menuItem}
                        data-danger
                        onClick={() => removeAgent(current)}
                      >
                        Remove agent
                      </button>
                    </div>
                  )}
                </span>
              </div>
              <p className={styles.panelNote}>
                {current.pending
                  ? 'Waiting for someone to sign in on the agent.'
                  : 'Signed in on the agent.'}
              </p>
              <div className={styles.panelSection}>
                <h4 className={styles.panelLabel}>Usage</h4>
                {[
                  [
                    '5-hour',
                    current.fiveHour,
                    `resets in ${Math.floor(current.resetsIn / 60)}h ${current.resetsIn % 60}m (${clockIn(current.resetsIn)})`,
                  ],
                  ...(current.weekly !== undefined
                    ? [['Weekly', current.weekly, 'resets Mon 12:00 AM']]
                    : []),
                ].map(([label, used, reset]) => (
                  <div key={label as string} className={styles.panelWindow}>
                    <span className={styles.panelWindowLabel}>{label}</span>
                    <span className={styles.panelWindowValue}>
                      {100 - (used as number)}% left <span className={styles.soft}>· {reset}</span>
                    </span>
                    <span className={styles.bigBar} aria-hidden="true">
                      <span
                        style={{ '--share': (100 - (used as number)) / 100 } as CSSProperties}
                      />
                    </span>
                  </div>
                ))}
              </div>
              <div className={styles.panelSection}>
                <h4 className={styles.panelLabel}>Runtime details</h4>
                <dl className={styles.facts}>
                  <div>
                    <dt>Reachability</dt>
                    <dd>
                      {status}{' '}
                      {status === 'Online' && (
                        <span className={styles.soft}>· {current.latency}ms</span>
                      )}
                    </dd>
                  </div>
                  <div>
                    <dt>Uptime</dt>
                    <dd>{current.uptime}</dd>
                  </div>
                  <div>
                    <dt>Conversations</dt>
                    <dd>
                      {current.conversations}{' '}
                      <span className={styles.soft}>· {current.active} active</span>
                    </dd>
                  </div>
                  <div>
                    <dt>Backend link</dt>
                    <dd>
                      {current.pending ? 'Not linked' : 'Connected'}{' '}
                      <span className={styles.soft}>· {current.linked}</span>
                    </dd>
                  </div>
                  <div className={styles.factWide}>
                    <dt>Configured image</dt>
                    <dd className={styles.mono}>
                      runtime:{current.version}-{current.runtime}-cli@sha256:{current.digest}
                    </dd>
                  </div>
                </dl>
              </div>
              <div className={styles.panelSection} data-last>
                <div className={styles.resourceHead}>
                  <span>
                    <h4 className={styles.panelLabel}>Resource usage</h4>
                    <span className={styles.resourceSpan}>{res.span}</span>
                  </span>
                  <Segmented
                    label="Resource range"
                    options={RESOURCE_RANGES}
                    value={resourceRange}
                    onChange={setResourceRange}
                    demo="resources"
                  />
                </div>
                <div className={styles.resources}>
                  <figure className={styles.resourceItem}>
                    <figcaption>
                      CPU <b>{res.cpuNow}</b>
                    </figcaption>
                    <ResourceChart kind="line" values={res.cpu} />
                  </figure>
                  <figure className={styles.resourceItem}>
                    <figcaption>
                      Memory <b>{res.memoryNow}</b>
                    </figcaption>
                    <ResourceChart kind="area" values={res.memory} />
                  </figure>
                  <figure className={styles.resourceItem}>
                    <figcaption>
                      Disk <b>26 / 29 GiB</b>
                    </figcaption>
                    <ResourceChart kind="limit" level={res.disk} />
                  </figure>
                  <figure className={styles.resourceItem}>
                    <figcaption>
                      Sessions <b>{res.sessionsNow}</b>
                    </figcaption>
                    <ResourceChart kind="area" values={res.sessions} />
                  </figure>
                </div>
              </div>
            </div>
          </aside>
        );
      })()
    : null;

  const SectionIcon = icons[NAV.find((item) => item.id === section)?.icon ?? 'agent'];

  return (
    <div
      ref={root}
      className={`${styles.root} ${className}`}
      data-part="dashboard"
      data-color-scheme={colorScheme}
      onPointerDownCapture={take}
      onKeyDownCapture={take}
      onWheelCapture={take}
      onPointerMove={(event) => {
        if (event.pointerType !== 'touch') take();
      }}
      onKeyDown={onRootKey}
    >
      <div
        className={styles.window}
        data-part="window"
        data-collapsed={collapsed || undefined}
        data-drawer={drawer || undefined}
        data-panel={current ? '' : undefined}
      >
        {sidebar}
        <button
          type="button"
          className={styles.scrim}
          tabIndex={-1}
          aria-hidden="true"
          onClick={() => setDrawer(false)}
        />

        {/* The window bar's controls: the lights, the sidebar's toggle, back and forward. */}
        <div className={styles.chrome} data-part="window-bar">
          <span className={styles.lights} aria-hidden="true">
            <i />
            <i />
            <i />
          </span>
          <button
            type="button"
            className={`${styles.iconButton} ${styles.toggle}`}
            aria-label={collapsed || drawer ? 'Show sidebar' : 'Hide sidebar'}
            aria-controls={`${id}-sidebar`}
            aria-expanded={drawer || !collapsed}
            onClick={toggleSidebar}
            data-demo="sidebar-toggle"
          >
            <icons.sidebar className={styles.headIcon} />
            <icons.menu className={`${styles.headIcon} ${styles.menuGlyph}`} />
          </button>
          <span className={styles.history}>
            <button
              type="button"
              className={styles.iconButton}
              aria-label="Back"
              disabled={back.length === 0}
              onClick={goBack}
            >
              <icons.left className={styles.headIcon} />
            </button>
            <button
              type="button"
              className={styles.iconButton}
              aria-label="Forward"
              disabled={forward.length === 0}
              onClick={goForward}
            >
              <icons.right className={styles.headIcon} />
            </button>
          </span>
        </div>

        <main className={styles.main} data-part="main">
          <header className={styles.header} data-part="header">
            <span className={styles.headerTitle} data-popover>
              <button
                type="button"
                className={styles.titleButton}
                aria-haspopup="menu"
                aria-expanded={open === 'sort'}
                disabled={section !== 'agents'}
                onClick={() => setOpen(open === 'sort' ? null : 'sort')}
              >
                <SectionIcon className={styles.titleIcon} />
                <h2 className={styles.title}>{sectionLabel}</h2>
                {section === 'agents' && <icons.down className={styles.titleChevron} />}
              </button>
              {open === 'sort' && (
                <div className={styles.menu} role="menu" aria-label="Sort agents" data-at="sort">
                  <span className={styles.menuLabel}>Sort by</span>
                  {SORTS.map((item) => (
                    <button
                      key={item.id}
                      type="button"
                      role="menuitemradio"
                      aria-checked={sort === item.id}
                      className={styles.menuItem}
                      onClick={() => {
                        setSort(item.id);
                        setOpen(null);
                        announce(`Sorted by ${item.label.toLowerCase()}`);
                      }}
                    >
                      {item.label}
                      {sort === item.id && <icons.check className={styles.menuCheck} />}
                    </button>
                  ))}
                </div>
              )}
            </span>
            <span className={styles.headerTools}>
              <button
                type="button"
                className={styles.iconButton}
                aria-label="Refresh usage"
                onClick={refresh}
              >
                <icons.refresh
                  key={spin}
                  className={`${styles.headIcon} ${spin ? styles.spinning : ''}`}
                />
              </button>
              <button
                type="button"
                className={styles.iconButton}
                aria-label="Copy link to this view"
                onClick={() =>
                  copy(
                    `https://agents.example/${shownWorkspace.id}/agents?range=${range}`,
                    'Link copied',
                  )
                }
              >
                <icons.copy className={styles.headIcon} />
              </button>
            </span>
          </header>

          {section === 'agents' ? (
            <>
              <div className={styles.toolbar} ref={toolbarRef} data-part="toolbar">
                <label className={styles.filter}>
                  <icons.search className={styles.filterIcon} />
                  <span className={styles.hidden}>Filter agents</span>
                  <input
                    ref={filterRef}
                    type="search"
                    className={styles.filterInput}
                    placeholder={countLabel}
                    value={query}
                    onChange={(event) => {
                      setQuery(event.target.value);
                      setTip(null);
                    }}
                    onKeyDown={(event) => {
                      if (event.key === 'Escape' && query) {
                        event.stopPropagation();
                        setQuery('');
                      }
                    }}
                  />
                </label>
                <button
                  ref={addButton}
                  type="button"
                  className={styles.addButton}
                  aria-haspopup="dialog"
                  onClick={() => setAdding(true)}
                  data-demo="add-agent"
                  data-part="add-agent"
                >
                  <icons.plus className={styles.addIcon} />
                  Add agent
                </button>
              </div>
              <div className={styles.scroll}>
                <div className={styles.content} ref={contentRef}>
                  <div className={styles.usageHead} data-part="usage-header">
                    <div>
                      <h3 className={styles.usageTitle}>Subscription usage</h3>
                      <p className={styles.usageSub}>
                        Fullest share of each plan window per half hour
                      </p>
                    </div>
                    <div className={styles.rangeBar}>
                      <span className={styles.noReading}>
                        <i aria-hidden="true" />
                        No reading
                      </span>
                      <Segmented
                        label="Range"
                        options={RANGES}
                        value={range}
                        onChange={chooseRange}
                        demo="range"
                      />
                    </div>
                  </div>
                  {heatmap}
                  {table}
                </div>
              </div>
            </>
          ) : (
            <div className={styles.empty} data-part="empty-state">
              <SectionIcon className={styles.emptyIcon} />
              <p className={styles.emptyTitle}>{sectionLabel}</p>
              <p className={styles.emptyText}>{EMPTY[section]}</p>
              <button type="button" className={styles.ghostButton} onClick={() => go('agents')}>
                Back to agents
              </button>
            </div>
          )}
        </main>

        {panel}
        {current && (
          <button
            type="button"
            className={styles.panelScrim}
            tabIndex={-1}
            aria-hidden="true"
            onClick={() => setSelected(null)}
          />
        )}

        {adding && (
          <div className={styles.dialogLayer}>
            <div className={styles.dialogScrim} aria-hidden="true" onClick={closeDialog} />
            <div
              ref={dialogRef}
              className={styles.dialog}
              role="dialog"
              aria-modal="true"
              aria-labelledby={`${id}-add`}
              data-part="add-dialog"
              onKeyDown={(event) => {
                if (event.key !== 'Tab') return;
                const items = [
                  ...(dialogRef.current?.querySelectorAll<HTMLElement>('input, button') ?? []),
                ].filter((item) => !(item as HTMLButtonElement).disabled);
                const first = items[0];
                const last = items[items.length - 1];
                if (event.shiftKey && document.activeElement === first) {
                  event.preventDefault();
                  last.focus();
                } else if (!event.shiftKey && document.activeElement === last) {
                  event.preventDefault();
                  first.focus();
                }
              }}
            >
              <form
                onSubmit={(event) => {
                  event.preventDefault();
                  addAgent();
                }}
              >
                <h3 id={`${id}-add`} className={styles.dialogTitle}>
                  Add agent
                </h3>
                <p className={styles.dialogText}>
                  It shows here once someone signs in on it with a plan.
                </p>
                <label className={styles.field}>
                  <span>Name</span>
                  <input
                    className={styles.input}
                    value={draft.name}
                    placeholder="Rhea"
                    autoComplete="off"
                    onChange={(event) => setDraft((now) => ({ ...now, name: event.target.value }))}
                  />
                </label>
                <fieldset className={styles.fieldset}>
                  <legend>Runtime</legend>
                  <Choices
                    name={`${id}-runtime`}
                    options={[
                      ['ember', RUNTIMES.ember],
                      ['knot', RUNTIMES.knot],
                    ]}
                    value={draft.runtime}
                    onChange={(runtime) => setDraft((now) => ({ ...now, runtime }))}
                  />
                </fieldset>
                <fieldset className={styles.fieldset}>
                  <legend>Runs on</legend>
                  <Choices
                    name={`${id}-host`}
                    options={[
                      ['cloud', HOSTS.cloud],
                      ['self', HOSTS.self],
                    ]}
                    value={draft.host}
                    onChange={(host) => setDraft((now) => ({ ...now, host }))}
                    demo="host"
                  />
                </fieldset>
                <fieldset className={styles.fieldset}>
                  <legend>Plan</legend>
                  <Choices
                    name={`${id}-plan`}
                    options={PLANS.map((plan) => [plan, plan] as const)}
                    value={draft.plan}
                    onChange={(plan) => setDraft((now) => ({ ...now, plan }))}
                  />
                </fieldset>
                <div className={styles.dialogActions}>
                  <button
                    type="button"
                    className={styles.ghostButton}
                    onClick={closeDialog}
                    data-demo="cancel-add"
                  >
                    Cancel
                  </button>
                  <button type="submit" className={styles.addButton} disabled={!draft.name.trim()}>
                    Add agent
                  </button>
                </div>
              </form>
            </div>
          </div>
        )}

        {toast && (
          <div className={styles.toast} data-part="toast">
            <span>{toast.text}</span>
            {toast.undo && (
              <button type="button" className={styles.toastUndo} onClick={toast.undo}>
                Undo
              </button>
            )}
          </div>
        )}
      </div>

      <p className={styles.hidden} aria-live="polite">
        {message}
      </p>

      <div className={styles.pointerLayer} aria-hidden="true">
        <DemoPointer
          ref={pointer}
          className={styles.pointer}
          arrowClass={styles.arrow}
          handClass={styles.hand}
        />
      </div>
    </div>
  );
}

/** A small segmented control: a radio group, arrows to move. */
function Segmented<T extends string>({
  label,
  options,
  value,
  onChange,
  demo,
}: {
  label: string;
  options: T[];
  value: T;
  onChange: (value: T) => void;
  demo?: string;
}) {
  const refs = useRef<(HTMLButtonElement | null)[]>([]);
  return (
    <span className={styles.segmented} role="radiogroup" aria-label={label} data-part="segmented">
      {options.map((option, index) => (
        <button
          key={option}
          ref={(element) => {
            refs.current[index] = element;
          }}
          type="button"
          role="radio"
          aria-checked={value === option}
          tabIndex={value === option ? 0 : -1}
          className={styles.segment}
          data-demo={demo ? `${demo}-${option}` : undefined}
          onClick={() => onChange(option)}
          onKeyDown={(event) => {
            const move =
              event.key === 'ArrowRight' || event.key === 'ArrowDown'
                ? 1
                : event.key === 'ArrowLeft' || event.key === 'ArrowUp'
                  ? -1
                  : 0;
            if (!move) return;
            event.preventDefault();
            const next = (index + move + options.length) % options.length;
            onChange(options[next]);
            refs.current[next]?.focus();
          }}
        >
          {option}
        </button>
      ))}
    </span>
  );
}

/** Radio buttons drawn as a row of pills. */
function Choices<T extends string>({
  name,
  options,
  value,
  onChange,
  demo,
}: {
  name: string;
  options: readonly (readonly [T, string])[];
  value: T;
  onChange: (value: T) => void;
  demo?: string;
}) {
  return (
    <span className={styles.choices}>
      {options.map(([option, label]) => (
        <label
          key={option}
          className={styles.choice}
          data-demo={demo ? `${demo}-${option}` : undefined}
        >
          <input
            type="radio"
            name={name}
            value={option}
            checked={value === option}
            onChange={() => onChange(option)}
          />
          <span>{label}</span>
        </label>
      ))}
    </span>
  );
}
```

### usage-dashboard/usage-dashboard.module.css

```css
/* Usage dashboard (usage-dashboard.tsx): a near-black app window, its sidebar a shade cooler than
   the main column, cards a shade darker than the page they sit on, violet for usage and a burnt
   orange for an agent at its limit; by day a pale grey sidebar, a white page and the same violet,
   a shade deeper.

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

   Sizes are those of the app at its own scale: a 267 px sidebar, 28 px rows in it, a 38 px window
   bar, 14 px text. Layout comes from the component's own width (container queries) and the main
   column's (the table's columns): the details panel beside the main column from 1100 px and over
   it below, the sidebar a drawer under 720 px.

   Memory. The sidebar folds and the panel, menus, tooltip and dialog come in by transform and
   opacity; the heatmap's cells are plain boxes with a colour, so a range switch only repaints. */

.root {
  --frame-edge: #d9d9de;
  --sidebar: #f3f3f5;
  --page: #fbfbfc;
  --card: #fff;
  --card-edge: #e6e6ea;
  --line: #ececf0;
  --line-strong: #e1e1e6;
  --ink: #18181b;
  --ink-soft: #3c3c43;
  --ink-2: #66666e;
  --ink-3: #8d8d95;
  --ink-4: #b4b4bb;
  --nav-on: #e5e5e9;
  --hover: #ebebee;
  --row-hover: #f7f7f9;
  --row-on: #f2edff;
  --control: #f1f1f4;
  --control-on: #fff;
  --control-shadow: 0 1px 2.1px rgb(20 20 30 / 0.12);
  --kbd: #fff;
  --accent: #5b17ec;
  --accent-hover: #4c0fd4;
  --on-accent: #fff;
  --violet: #7d4cec;
  --track: #ebe8f2;
  --heat-lo: #e7e0fb;
  --heat-hi: #7b47ee;
  --heat-none: #ededf0;
  --limit: #dd5a32;
  --host: #6a55ee;
  --running: #e09a1c;
  --unread: #5b17ec;
  --ticket: #a77a00;
  --pr: #7a55e6;
  --merged: #1d8a4f;
  --closed: #cc4646;
  --thread: #5468d8;
  --ember: #dd6f45;
  --knot: #74747c;
  --mark-bg: #fff;
  --mark-edge: #e3e3e8;
  --online: #2f9e67;
  --wait: #d18a10;
  --danger: #c93a3a;
  --light: #d0d0d5;
  --logo: #18181b;
  --logo-ink: #fff;
  --tip: #fff;
  --tip-edge: #e3e3e8;
  --shadow: 0 12.8px 32px rgb(20 20 30 / 0.12), 0 2.1px 6.4px rgb(20 20 30 / 0.06);
  --window-shadow: 0 25.6px 64px rgb(20 20 30 / 0.14), 0 2.1px 8.5px rgb(20 20 30 / 0.06);
  --scrim: rgb(20 20 30 / 0.28);
  --focus: #6b2cf5;
  --pointer: #1c1c1e;
  --pointer-edge: #fff;
  --pointer-shadow: rgb(0 0 0 / 0.25);
  --sans:
    -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', 'Segoe UI', system-ui,
    sans-serif;
  --mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
  position: relative;
  container-type: inline-size;
  width: 100%;
  min-width: 0;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.3;
  text-align: left;
  letter-spacing: normal;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}
@supports (color: light-dark(#fff, #000)) {
  .root {
    --frame-edge: light-dark(#d9d9de, #3a3b3f);
    --sidebar: light-dark(#f3f3f5, #1c1e21);
    --page: light-dark(#fbfbfc, #212224);
    --card: light-dark(#fff, #1d1e21);
    --card-edge: light-dark(#e6e6ea, #2a2b2f);
    --line: light-dark(#ececf0, #28292d);
    --line-strong: light-dark(#e1e1e6, #2c2d31);
    --ink: light-dark(#18181b, #ededef);
    --ink-soft: light-dark(#3c3c43, #c9cacd);
    --ink-2: light-dark(#66666e, #9c9da2);
    --ink-3: light-dark(#8d8d95, #76777c);
    --ink-4: light-dark(#b4b4bb, #55565b);
    --nav-on: light-dark(#e5e5e9, #2f3032);
    --hover: light-dark(#ebebee, #27282b);
    --row-hover: light-dark(#f7f7f9, #222327);
    --row-on: light-dark(#f2edff, #221c33);
    --control: light-dark(#f1f1f4, #252628);
    --control-on: light-dark(#fff, #353438);
    --control-shadow: light-dark(0 1px 2.1px rgb(20 20 30 / 0.12), none);
    --kbd: light-dark(#fff, #1c1e21);
    --accent: light-dark(#5b17ec, #5f10f6);
    --accent-hover: light-dark(#4c0fd4, #6c22ff);
    --violet: light-dark(#7d4cec, #9a6df7);
    --track: light-dark(#ebe8f2, #333236);
    --heat-lo: light-dark(#e7e0fb, #392c52);
    --heat-hi: light-dark(#7b47ee, #a272fb);
    --heat-none: light-dark(#ededf0, #26272a);
    --limit: light-dark(#dd5a32, #a8412a);
    --host: light-dark(#6a55ee, #7460f0);
    --running: light-dark(#e09a1c, #ebb146);
    --unread: light-dark(#5b17ec, #5a12f6);
    --ticket: light-dark(#a77a00, #e3bc45);
    --pr: light-dark(#7a55e6, #a98df5);
    --merged: light-dark(#1d8a4f, #5fca8a);
    --closed: light-dark(#cc4646, #ee8a86);
    --thread: light-dark(#5468d8, #8a95f2);
    --ember: light-dark(#dd6f45, #e48d64);
    --knot: light-dark(#74747c, #a2a2a8);
    --mark-bg: light-dark(#fff, #1a1b1e);
    --mark-edge: light-dark(#e3e3e8, #2c2d31);
    --online: light-dark(#2f9e67, #55bb89);
    --wait: light-dark(#d18a10, #e9b04a);
    --danger: light-dark(#c93a3a, #f08a84);
    --light: light-dark(#d0d0d5, #404145);
    --logo: light-dark(#18181b, #fff);
    --logo-ink: light-dark(#fff, #18181b);
    --tip: light-dark(#fff, #2a2b2f);
    --tip-edge: light-dark(#e3e3e8, #3a3b40);
    --shadow: light-dark(0 12.8px 32px rgb(20 20 30 / 0.12), 0 14.9px 38.4px rgb(0 0 0 / 0.5));
    --window-shadow: light-dark(0 25.6px 64px rgb(20 20 30 / 0.14), 0 25.6px 64px rgb(0 0 0 / 0.4));
    --scrim: light-dark(rgb(20 20 30 / 0.28), rgb(0 0 0 / 0.5));
    --focus: light-dark(#6b2cf5, #a98df5);
  }
}
.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;
}
.root :where(h2, h3, h4, p, ul, dl, dd, figure, fieldset) {
  margin: 0;
  padding: 0;
}
.root :where(ul) {
  list-style: none;
}
.root :where(fieldset) {
  border: 0;
  min-width: 0;
}
.root :where(button, input) {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
}
.root :where(button) {
  cursor: pointer;
  border: 0;
  background: none;
  padding: 0;
  text-align: inherit;
}
.root :where(button:disabled) {
  cursor: default;
}
.root :where(svg) {
  flex: none;
}
.root :where(a, button, input, [tabindex]):focus-visible {
  outline: 2.1px solid var(--focus);
  outline-offset: 1px;
}

/* The window: the main column (and the details panel) in a grid, the sidebar over its left. */
.window {
  --side: 285px;
  --panel-w: 0px;
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--panel-w);
  height: 870px;
  padding-left: var(--side);
  overflow: hidden;
  border-radius: 18.1px;
  background: var(--page);
  box-shadow:
    0 0 0 1px var(--frame-edge),
    var(--window-shadow);
  isolation: isolate;
}
.window[data-collapsed] {
  --side: 0px;
}
.window[data-panel] {
  --panel-w: 542px;
}

/* The sidebar. */
.sidebar {
  position: absolute;
  inset: 0 auto 0 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  width: 285px;
  padding: 0 9.6px;
  background: var(--sidebar);
  transition:
    transform 0.28s cubic-bezier(0.3, 0, 0.2, 1),
    opacity 0.28s ease;
}
.window[data-collapsed] .sidebar {
  transform: translateX(-100%);
  opacity: 0;
}
.titlebar {
  flex: none;
  height: 41.1px;
}
.workspace {
  position: relative;
  flex: none;
  margin-top: 4.3px;
}
.workspaceButton {
  display: flex;
  align-items: center;
  gap: 10.7px;
  width: 100%;
  height: 32px;
  padding: 0 8.5px;
  border-radius: 7.5px;
}
.workspaceButton:hover,
.workspaceButton[aria-expanded='true'] {
  background: var(--hover);
}
.workspaceLogo {
  display: grid;
  place-items: center;
  flex: none;
  width: 19.2px;
  height: 19.2px;
  border-radius: 5.3px;
  background: var(--logo);
}
/* The workspace's mark: two strokes, a light and a violet one. */
.workspaceMark {
  position: relative;
  width: 10.7px;
  height: 10.7px;
}
.workspaceMark::before,
.workspaceMark::after {
  content: '';
  position: absolute;
  left: 0;
  width: 10.7px;
  height: 3.2px;
  border-radius: 1px;
}
.workspaceMark::before {
  top: 1px;
  background: var(--logo-ink);
  transform: skewX(-25deg);
}
.workspaceMark::after {
  bottom: 1px;
  background: var(--violet);
  transform: skewX(-25deg);
}
.workspaceMark[data-mark='F']::after {
  background: var(--ember);
}
.workspaceName {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.workspaceChevron {
  width: 16px;
  height: 16px;
  color: var(--ink-2);
}
.search {
  font-size: 13px;
  display: flex;
  flex: none;
  align-items: center;
  gap: 8.5px;
  height: 32px;
  margin-top: 10.7px;
  padding: 0 6.4px 0 5.3px;
  border-radius: 7.5px;
  color: var(--ink-soft);
}
.search > span {
  flex: 1;
}
.search:hover {
  background: var(--hover);
}
.kbd {
  display: inline-grid;
  place-items: center;
  height: 19.2px;
  padding: 0 4.3px;
  border: 1px solid var(--line-strong);
  border-radius: 4.3px;
  background: var(--kbd);
  color: var(--ink-3);
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.02em;
}
.nav {
  display: grid;
  flex: none;
  gap: 2.8px;
  margin-top: 5.9px;
}
.navItem {
  display: flex;
  align-items: center;
  gap: 10.1px;
  width: 100%;
  height: 29.9px;
  padding: 0 7.5px;
  border-radius: 6.4px;
  color: var(--ink);
}
.navItem:hover {
  background: var(--hover);
}
.navItem[aria-current='page'] {
  background: var(--nav-on);
}
.navIcon {
  width: 17.1px;
  height: 17.1px;
  color: var(--ink-soft);
}
.lists {
  flex: 1;
  min-height: 0;
  margin-top: 20.8px;
  margin-right: -9.6px;
  padding-right: 9.6px;
  overflow: auto;
  scrollbar-width: none;
}
.group + .group {
  margin-top: 3px;
}
.groupHead {
  font-size: 13px;
  display: flex;
  align-items: center;
  gap: 5.3px;
  height: 29.9px;
  padding: 0 8.5px;
  border-radius: 6.4px;
  color: var(--ink-2);
}
.groupHead:hover {
  color: var(--ink);
}
.groupChevron {
  width: 12.8px;
  height: 12.8px;
  transition: transform 0.2s ease;
}
.groupHead[aria-expanded='true'] .groupChevron {
  transform: rotate(90deg);
}
.groupList {
  padding: 2.1px 0 4.3px;
}
.pinnedItem {
  display: block;
  width: 100%;
  height: 29.9px;
  padding: 0 8.5px 0 19.2px;
  border-radius: 6.4px;
  overflow: hidden;
  color: var(--ink-soft);
  white-space: nowrap;
  text-overflow: ellipsis;
}
.pinnedItem:hover,
.pinnedItem[aria-current='true'] {
  background: var(--hover);
}
.chats {
  padding-top: 0;
}
.chatItem {
  position: relative;
}
.chatRow {
  position: relative;
  padding-bottom: 0.7px;
  border-radius: 6.4px;
}
.chatRow:has(.chips) {
  padding-bottom: 7.1px;
}
.chatRow:hover,
.chatRow[data-active] {
  background: var(--hover);
}
.chatButton {
  display: flex;
  align-items: center;
  width: 100%;
  height: 32px;
  padding: 0 8.3px 0 7.9px;
  color: var(--ink-soft);
}
.chatRow[data-active] .chatButton {
  color: var(--ink);
}
.chatMark {
  width: 17.1px;
  height: 17.1px;
  margin-right: 9.5px;
  color: var(--ember);
}
.chatTitle {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.chatMore {
  width: 12.8px;
  height: 12.8px;
  margin: 0 12.8px 0 6.4px;
  color: var(--ink-3);
}
.chatDots {
  display: flex;
  gap: 9.9px;
  margin-left: 8.5px;
}
.chatDots:empty {
  display: none;
}
.dot {
  display: block;
  width: 6.4px;
  height: 6.4px;
  border-radius: 50%;
}
.dot[data-tone='running'] {
  background: var(--running);
}
.dot[data-tone='unread'] {
  background: var(--unread);
}
.chips {
  display: flex;
  gap: 12.3px;
  height: 19.2px;
  margin-top: -2.1px;
  padding-left: 33.4px;
  overflow: hidden;
  font-size: 12px;
  white-space: nowrap;
  pointer-events: none;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4.3px;
}
.chip[data-kind='ticket'] {
  color: var(--ticket);
}
.chip[data-kind='pr'] {
  color: var(--pr);
}
.chip[data-kind='merged'] {
  color: var(--merged);
}
.chip[data-kind='closed'] {
  color: var(--closed);
}
.chip[data-kind='thread'] {
  color: var(--ink-2);
}
.chip[data-kind='thread'] .chipIcon {
  color: var(--thread);
}
.chipIcon {
  width: 12.8px;
  height: 12.8px;
}
.threads {
  position: relative;
  padding-left: 16.4px;
}
/* The guide down the side of a chat's threads. */
.threads::before {
  content: '';
  position: absolute;
  top: -2.1px;
  bottom: 0;
  left: 17px;
  width: 1px;
  background: var(--line-strong);
}
.chatItem[data-nested] .chips {
  padding-left: 33.4px;
}
.footer {
  position: relative;
  display: flex;
  flex: none;
  align-items: center;
  gap: 10.1px;
  height: 48px;
  margin: 0 -9.6px;
  padding: 0 9.6px 0 13.3px;
  border-top: 1px solid var(--line);
}
.avatar {
  display: grid;
  place-items: center;
  flex: none;
  width: 22.4px;
  height: 22.4px;
  border-radius: 50%;
  background: linear-gradient(140deg, #c9b8a4, #5e6a7c);
  color: #fff;
  font-size: 8.5px;
  font-weight: 600;
}
.userName {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.help {
  position: relative;
  display: grid;
  place-items: center;
  width: 27.7px;
  height: 27.7px;
  margin-right: 1px;
  border-radius: 6.4px;
  color: var(--ink-2);
}
.help:hover {
  background: var(--hover);
}
.helpIcon {
  width: 18.1px;
  height: 18.1px;
}
.helpDot {
  position: absolute;
  top: 2.1px;
  right: 2.1px;
  width: 6.4px;
  height: 6.4px;
  border-radius: 50%;
  background: #e5484d;
}

/* The window bar's controls, over the sidebar's top. */
.chrome {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  width: 285px;
  height: 41.1px;
  padding-left: 13.9px;
}
.lights {
  display: flex;
  gap: 11.1px;
}
.lights i {
  display: block;
  width: 16.8px;
  height: 16.8px;
  border-radius: 50%;
  background: var(--light);
}
.toggle {
  margin-left: 17.6px;
}
.menuGlyph {
  display: none;
}
.history {
  display: flex;
  gap: 9.1px;
  margin-left: auto;
  margin-right: 17.6px;
}
.iconButton {
  display: grid;
  place-items: center;
  width: 28.8px;
  height: 28.8px;
  border-radius: 6.4px;
  color: var(--ink-2);
}
.iconButton:hover:not(:disabled) {
  background: var(--hover);
  color: var(--ink);
}
.iconButton:disabled {
  color: var(--ink-4);
}
.history .iconButton:not(:disabled) {
  color: var(--ink-soft);
}
.headIcon {
  width: 18.1px;
  height: 18.1px;
}
.spinning {
  animation: spin 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}

/* The main column. */
.main {
  container: main / inline-size;
  position: relative;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  min-width: 0;
  min-height: 0;
  border-left: 1px solid var(--line);
}
.window[data-collapsed] .main {
  border-left-color: transparent;
}
.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 41.1px;
  padding: 0 10.7px 0 16px;
  border-bottom: 1px solid var(--line);
  transition: padding 0s;
}
.window[data-collapsed] .header {
  padding-left: 268.9px;
}
.headerTitle {
  position: relative;
}
.titleButton {
  display: flex;
  align-items: center;
  gap: 5.9px;
  height: 28.8px;
  padding: 0 6.4px 0 4.3px;
  border-radius: 6.4px;
}
.titleButton:hover:not(:disabled),
.titleButton[aria-expanded='true'] {
  background: var(--hover);
}
.titleIcon {
  width: 14.9px;
  height: 14.9px;
  color: var(--ink-2);
}
.title {
  font-size: 12.5px;
  font-weight: 500;
}
.titleChevron {
  width: 12.8px;
  height: 12.8px;
  margin-left: 11.7px;
  color: var(--ink-2);
}
.headerTools {
  display: flex;
  gap: 3.2px;
}
.toolbar {
  display: flex;
  align-items: center;
  gap: 12.8px;
  height: 42.7px;
  padding: 0 12.3px 0 19.2px;
  border-bottom: 1px solid var(--line);
}
.filter {
  display: flex;
  flex: 1;
  align-items: center;
  gap: 4.8px;
  min-width: 0;
  height: 32px;
}
.filterIcon {
  width: 17.1px;
  height: 17.1px;
  color: var(--ink-2);
}
.filterInput {
  flex: 1;
  min-width: 0;
  height: 32px;
  padding: 0;
  border: 0;
  outline: none;
  background: none;
  font-size: 12.5px;
}
.filterInput::placeholder {
  color: var(--ink-2);
  opacity: 1;
}
.filterInput::-webkit-search-cancel-button {
  filter: grayscale(1);
}
.filter:has(.filterInput:focus-visible) {
  border-radius: 6.4px;
  outline: 2.1px solid var(--focus);
  outline-offset: 2.1px;
}
.addButton {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 6.4px;
  height: 30.9px;
  padding: 0 12.3px 0 10.7px;
  border-radius: 7.5px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  transition:
    background-color 0.15s ease,
    transform 0.15s ease;
}
.addButton:hover:not(:disabled) {
  background: var(--accent-hover);
}
.addButton:active:not(:disabled) {
  transform: scale(0.97);
}
.addButton:disabled {
  opacity: 0.45;
}
.addIcon {
  width: 16px;
  height: 16px;
  stroke-width: 2;
}
.scroll {
  min-height: 0;
  overflow: auto;
  scrollbar-width: thin;
}
.content {
  padding: 33px 45.9px 42.7px;
}

/* Subscription usage: its heading and the range. */
.usageHead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12.8px 21.3px;
  margin-bottom: 11px;
}
.usageTitle {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  line-height: 20.3px;
}
.usageSub {
  font-size: 12px;
  color: var(--ink-3);
  line-height: 20.3px;
}
.rangeBar {
  display: flex;
  flex: none;
  align-items: center;
  gap: 13.9px;
  margin-top: 1px;
}
.noReading {
  display: inline-flex;
  align-items: center;
  gap: 8.5px;
  color: var(--ink-2);
  font-size: 11px;
}
.noReading i {
  width: 10.7px;
  height: 10.7px;
  border-radius: 2.1px;
  background: var(--heat-none);
}
.segmented {
  display: inline-flex;
  padding: 2px;
  border-radius: 6.4px;
  background: var(--control);
  border: 1px solid var(--line);
}
.segment {
  height: 19px;
  padding: 0 7.6px;
  border-radius: 4.8px;
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 500;
  line-height: 19px;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}
.segment:hover {
  color: var(--ink);
}
.segment[aria-checked='true'] {
  background: var(--control-on);
  box-shadow: var(--control-shadow);
  color: var(--ink);
}

/* The heatmap card. */
.heatCard {
  position: relative;
  padding: 11.7px 16.4px 11.7px;
  border: 1px solid var(--card-edge);
  border-radius: 10.7px;
  background: var(--card);
}
.heatHead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 6.4px 17.1px;
  min-height: 20.3px;
}
.heatTitle {
  font-size: 12.75px;
  font-weight: 500;
}
.legend {
  display: flex;
  align-items: center;
  gap: 5.8px;
  color: var(--ink-2);
  font-size: 11px;
}
.legendText:first-child {
  margin-right: 3.2px;
}
.legendText + .limitKey {
  margin-left: 13.9px;
}
.legendText:nth-last-child(2) {
  margin-left: 3.2px;
}
.swatch {
  display: block;
  width: 11.1px;
  height: 11.1px;
  border-radius: 2.1px;
  transition: transform 0.15s ease;
}
.swatch[data-band='0'] {
  background: color-mix(in oklab, var(--heat-hi) 8%, var(--heat-lo));
}
.swatch[data-band='1'] {
  background: color-mix(in oklab, var(--heat-hi) 37%, var(--heat-lo));
}
.swatch[data-band='2'] {
  background: color-mix(in oklab, var(--heat-hi) 70%, var(--heat-lo));
}
.swatch[data-band='3'] {
  background: var(--heat-hi);
}
.swatch[data-band='4'] {
  background: var(--limit);
}
.swatch[data-on],
.limitKey[aria-pressed='true'] .swatch {
  transform: scale(1.25);
}
.limitKey {
  display: inline-flex;
  align-items: center;
  gap: 8.5px;
  color: var(--ink-2);
}
.heat {
  display: grid;
  gap: 5.4px;
  margin-top: 16px;
}
.heatRow {
  display: grid;
  grid-template-columns: clamp(117.4px, 17.4%, 192.1px) minmax(0, 1fr);
  align-items: center;
}
.heatLabel {
  display: flex;
  align-items: center;
  gap: 8.1px;
  min-width: 0;
  padding-right: 10.7px;
  color: var(--ink-soft);
  font-size: 11.25px;
  line-height: 14.6px;
}
.heatRow[data-lit] .heatLabel {
  color: var(--ink);
}
.heatMark {
  width: 12.8px;
  height: 12.8px;
  color: var(--ember);
}
.heatMark[data-runtime='knot'],
.tipMark[data-runtime='knot'],
.agentGlyph[data-runtime='knot'],
.panelGlyph[data-runtime='knot'] {
  color: var(--knot);
}
.heatName {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.cells {
  display: grid;
  grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
  gap: 2.3px;
}
.cell {
  display: block;
  height: 14.6px;
  border-radius: 2.7px;
  background: color-mix(in oklab, var(--heat-hi) calc(var(--v, 0) * 100%), var(--heat-lo));
  cursor: pointer;
  transition:
    opacity 0.18s ease,
    background-color 0.3s ease;
}
.cell[data-level='-1'] {
  background: var(--heat-none);
  cursor: default;
}
.cell[data-level='4'] {
  background: var(--limit);
}
.cell[data-hot] {
  box-shadow: 0 0 0 1.5px var(--ink);
}
.cell:focus-visible {
  outline-offset: 1px;
}
/* A legend band picked: the others fade back. */
.heat[data-band='0'] .cell:not([data-level='0']),
.heat[data-band='1'] .cell:not([data-level='1']),
.heat[data-band='2'] .cell:not([data-level='2']),
.heat[data-band='3'] .cell:not([data-level='3']),
.heat[data-band='4'] .cell:not([data-level='4']) {
  opacity: 0.18;
}
.noMatch {
  padding: 14.9px 0;
  color: var(--ink-2);
  font-size: 12px;
}
.axis {
  display: grid;
  grid-template-columns: clamp(117.4px, 17.4%, 192.1px) minmax(0, 1fr);
  margin-top: 8px;
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 10.5px;
  line-height: 13.9px;
}
.axisTrack {
  position: relative;
  height: 13.9px;
}
.tick {
  position: absolute;
  top: 0;
  left: calc(var(--at) * 100%);
  white-space: nowrap;
}
.tickNow {
  position: absolute;
  top: 0;
  right: 0;
}

/* The cell's tooltip: placed at the cell's top centre, its box above it (below for the first
   rows). */
.tip {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 4;
  width: 0;
  height: 0;
  pointer-events: none;
}
.tipBox {
  position: absolute;
  bottom: 7.5px;
  left: 0;
  display: grid;
  gap: 3.2px;
  min-width: 181.4px;
  padding: 8.5px 10.7px 9.6px;
  border: 1px solid var(--tip-edge);
  border-radius: 8.5px;
  background: var(--tip);
  box-shadow: var(--shadow);
  font-size: 11.5px;
  white-space: nowrap;
  transform: translateX(-50%);
  animation: rise 0.16s ease both;
}
.tip[data-below] .tipBox {
  top: 7.5px;
  bottom: auto;
}
.tipName {
  display: flex;
  align-items: center;
  gap: 6.4px;
  font-weight: 500;
  font-size: 12px;
}
.tipMark {
  width: 12.8px;
  height: 12.8px;
  color: var(--ember);
}
.tipSpan {
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 10.5px;
}
.tipValue {
  display: flex;
  align-items: center;
  gap: 6.4px;
  color: var(--ink-soft);
}
.tipValue i {
  width: 8.5px;
  height: 8.5px;
  border-radius: 2.1px;
  background: var(--violet);
}
.tipValue[data-level='4'] i {
  background: var(--limit);
}
.tipValue[data-level='-1'] i {
  background: var(--heat-none);
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

/* The agents table: rows as grids, so the columns can be dropped by the main column's width. */
.tableCard {
  margin-top: 17.1px;
  border: 1px solid var(--card-edge);
  border-radius: 10.7px;
  background: var(--card);
  overflow: hidden;
}
.table {
  display: block;
  width: 100%;
  border-collapse: collapse;
}
.table thead,
.table tbody {
  display: block;
}
.headRow,
.row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 141.9px 289.2px 143px 51.2px;
  align-items: center;
  padding-left: 16.4px;
}
.headRow {
  height: 33.6px;
  border-bottom: 1px solid var(--line);
}
.headRow th {
  padding: 0;
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-align: left;
  text-transform: uppercase;
}
.row {
  position: relative;
  min-height: 53.4px;
  border-bottom: 1px solid var(--line);
  transition: background-color 0.15s ease;
}
.row:last-child {
  border-bottom: 0;
}
.row:has(.usageLine + .usageLine) {
  min-height: 56.6px;
}
.row:hover,
.row[data-hover] {
  background: var(--row-hover);
}
.row[data-selected] {
  background: var(--row-on);
}
.row td {
  padding: 0;
}
.agentCell {
  display: flex;
  align-items: center;
  gap: 12.3px;
  min-width: 0;
  padding-right: 20.3px !important;
}
.agentMark {
  display: grid;
  place-items: center;
  flex: none;
  width: 33.1px;
  height: 33.1px;
  border: 1px solid var(--mark-edge);
  border-radius: 7.5px;
  background: var(--mark-bg);
}
.agentGlyph {
  width: 19.2px;
  height: 19.2px;
  color: var(--ember);
}
.agentOpen {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: var(--ink);
  font-size: 12.75px;
  font-weight: 500;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* The whole row opens the agent. */
.agentOpen::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}
.agentOpen:focus-visible {
  outline: none;
}
.agentOpen:focus-visible::after {
  outline: 2.1px solid var(--focus);
  outline-offset: -2.1px;
  border-radius: 2.1px;
}
.runtime {
  flex: none;
  color: var(--ink-2);
  font-size: 11px;
}
.host {
  display: inline-flex;
  align-items: center;
  gap: 7.5px;
  color: var(--ink-soft);
  font-size: 11.75px;
}
.host i {
  width: 6.4px;
  height: 6.4px;
  border-radius: 50%;
  background: var(--host);
}
.host i[data-host='self'] {
  background: var(--host);
}
.usageCell {
  padding-right: 20.5px !important;
}
.windows {
  display: grid;
  gap: 5.9px;
}
.usageLine {
  display: grid;
  grid-template-columns: 64.8px minmax(0, 1fr) 45.2px;
  align-items: center;
  height: 14.6px;
}
.windowLabel {
  color: var(--ink-2);
  font-size: 10.75px;
}
.bar {
  position: relative;
  height: 6.1px;
  border-radius: 3.2px;
  background: var(--track);
  overflow: hidden;
}
.barFill {
  position: absolute;
  inset: 0;
  border-radius: 3.2px;
  background: var(--violet);
  /* At least a dot, so 4% still shows; nothing at 0. */
  transform: translateX(calc(max(var(--share) * 100%, 6.1px) - 100%));
  transition: transform 0.5s cubic-bezier(0.3, 0, 0.2, 1);
}
.barFill[data-zero] {
  opacity: 0;
}
.windowValue {
  color: var(--ink);
  font-family: var(--mono);
  font-size: 11px;
  text-align: right;
}
.usageNote {
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: 11px;
}
.usageNote:not([data-error]) {
  font-family: var(--sans);
  font-size: 11.5px;
}
.spark {
  display: block;
  width: 143px;
  height: 32px;
  overflow: visible;
}
.spark path {
  fill: none;
  stroke: var(--violet);
  stroke-width: 1.5;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.colGo {
  display: grid;
  place-items: center start;
  padding-left: 18.1px !important;
}
.rowChevron {
  width: 16px;
  height: 16px;
  color: var(--ink-4);
  transition:
    color 0.15s ease,
    transform 0.15s ease;
}
.row:hover .rowChevron,
.row[data-hover] .rowChevron,
.row[data-selected] .rowChevron {
  color: var(--ink);
  transform: translateX(2.1px);
}

/* The details panel. */
.panel {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  min-height: 0;
  border-left: 1px solid var(--line);
  background: var(--page);
  animation: slide 0.28s cubic-bezier(0.3, 0, 0.2, 1) both;
}
.panelScrim {
  display: none;
}
.panelHead {
  display: flex;
  align-items: center;
  gap: 8.5px;
  height: 42px;
  padding: 0 12.8px 0 11.7px;
  border-bottom: 1px solid var(--line);
}
.panelTitle {
  color: var(--ink-2);
  font-size: 12.25px;
  font-weight: 400;
}
.panelScroll {
  overflow: auto;
  scrollbar-width: thin;
}
.panelAgent {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 23.6px 17.1px 22.4px 21.3px;
  border-bottom: 1px solid var(--line);
}
.panelMark {
  display: grid;
  place-items: center;
  flex: none;
  width: 41.1px;
  height: 41.1px;
  border: 1px solid var(--mark-edge);
  border-radius: 9.6px;
  background: var(--mark-bg);
}
.panelGlyph {
  width: 23.5px;
  height: 23.5px;
  color: var(--ember);
}
.panelWho {
  display: grid;
  flex: 1;
  gap: 3.2px;
  min-width: 0;
}
.panelName {
  overflow: hidden;
  font-size: 15.5px;
  font-weight: 500;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.panelSub {
  color: var(--ink-2);
  font-size: 12px;
}
.panelStatus {
  display: flex;
  align-items: center;
  gap: 5.3px;
  font-size: 12px;
  white-space: nowrap;
}
.panelStatus i {
  width: 6.4px;
  height: 6.4px;
  margin-right: 3.2px;
  border-radius: 50%;
  background: var(--online);
}
.panelStatus i[data-status='Restarting'],
.panelStatus i[data-status='Waiting'] {
  background: var(--wait);
  animation: blink 1s ease-in-out infinite;
}
.statusWord {
  color: var(--ink-soft);
}
.soft {
  color: var(--ink-2);
}
.miniBar {
  position: relative;
  width: 64px;
  height: 5.9px;
  margin: 0 3.2px 0 4.3px;
  border-radius: 3.2px;
  background: var(--track);
  overflow: hidden;
}
.miniBar span,
.bigBar span {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--accent);
  transform: translateX(calc((var(--share) - 1) * 100%));
  transition: transform 0.5s cubic-bezier(0.3, 0, 0.2, 1);
}
.panelMore {
  position: relative;
  margin-left: 2.1px;
}
.panelNote {
  padding: 17.4px 21.3px;
  border-bottom: 1px solid var(--line);
  color: var(--ink-2);
  font-size: 12px;
}
.panelSection {
  padding: 21.3px 21.3px 22.4px;
  border-bottom: 1px solid var(--line);
}
.panelSection[data-last] {
  padding-left: 17.1px;
  border-bottom: 0;
}
.panelLabel {
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.panelWindow {
  display: grid;
  gap: 3.2px;
  margin-top: 18.1px;
}
.panelWindow + .panelWindow {
  margin-top: 17.1px;
}
.panelWindowLabel {
  color: var(--ink-3);
  font-size: 11.5px;
}
.panelWindowValue {
  font-size: 13px;
}
.bigBar {
  position: relative;
  width: min(100%, 239px);
  height: 6.4px;
  margin-top: 8.5px;
  border-radius: 3.2px;
  background: var(--track);
  overflow: hidden;
}
.facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 15.5px 8.5px;
  margin-top: 18.1px;
}
.facts dt {
  color: var(--ink-3);
  font-size: 11.5px;
}
.facts dd {
  margin-top: 4.3px;
  overflow: hidden;
  font-size: 13px;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.factWide {
  grid-column: 1 / -1;
}
.mono {
  font-family: var(--mono);
  font-size: 11.5px !important;
}
.resourceHead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12.8px;
}
.resourceSpan {
  display: block;
  margin-top: 3.2px;
  color: var(--ink-3);
  font-size: 12px;
}
.resources {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16.4px;
  margin-top: 19.2px;
}
.resourceItem figcaption {
  display: flex;
  justify-content: space-between;
  gap: 6.4px;
  margin-bottom: 9.6px;
  color: var(--ink-3);
  font-size: 11.5px;
  white-space: nowrap;
}
.resourceItem b {
  color: var(--ink);
  font-family: var(--mono);
  font-weight: 400;
}
.resource {
  display: block;
  width: 100%;
  height: 57px;
  overflow: visible;
}
.resource[data-kind='limit'] {
  height: 62px;
}
.resourceLine {
  fill: none;
  stroke: var(--violet);
  stroke-width: 1.5;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.resourceFill {
  fill: color-mix(in oklab, var(--violet) 22%, var(--page));
  stroke: none;
}
.resourceLimit {
  fill: none;
  stroke: var(--ink-3);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}

/* Menus. */
.menu {
  position: absolute;
  top: calc(100% + 4.3px);
  left: 0;
  z-index: 20;
  display: grid;
  min-width: 213.4px;
  padding: 4.3px;
  border: 1px solid var(--tip-edge);
  border-radius: 9.6px;
  background: var(--tip);
  box-shadow: var(--shadow);
  animation: drop 0.16s ease both;
}
.menu[data-at='help'] {
  top: auto;
  bottom: calc(100% + 4.3px);
  left: auto;
  right: 8.5px;
}
.menu[data-at='agent'] {
  left: auto;
  right: 0;
  min-width: 181.4px;
}
.menuLabel {
  padding: 6.4px 8.5px 4.3px;
  color: var(--ink-3);
  font-size: 12px;
}
.menuItem {
  display: flex;
  align-items: center;
  gap: 8.5px;
  height: 32px;
  padding: 0 8.5px;
  border-radius: 6.4px;
  font-size: 13.5px;
  white-space: nowrap;
}
.menuItem:hover,
.menuItem:focus-visible {
  background: var(--hover);
  outline: none;
}
.menuItem[data-danger] {
  color: var(--danger);
}
.menuMark {
  display: grid;
  place-items: center;
  width: 19.2px;
  height: 19.2px;
  border-radius: 5.3px;
  background: var(--logo);
  color: var(--logo-ink);
  font-size: 10px;
  font-weight: 700;
}
.menuCheck {
  width: 14.9px;
  height: 14.9px;
  margin-left: auto;
  color: var(--violet);
}

/* Another section: an empty state. */
.empty {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 6.4px;
  grid-row: 2 / -1;
  padding: 42.7px 25.6px;
  text-align: center;
}
.emptyIcon {
  width: 29.9px;
  height: 29.9px;
  margin-bottom: 6.4px;
  color: var(--ink-3);
}
.emptyTitle {
  font-size: 15px;
  font-weight: 500;
}
.emptyText {
  max-width: 320.1px;
  color: var(--ink-2);
  font-size: 13px;
}
.ghostButton {
  height: 30.9px;
  margin-top: 8.5px;
  padding: 0 12.8px;
  border: 1px solid var(--line-strong);
  border-radius: 7.5px;
  font-size: 13.5px;
}
.ghostButton:hover {
  background: var(--hover);
}

/* The Add agent dialog, over the window. */
.dialogLayer {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: grid;
  place-items: center;
  padding: 17.1px;
}
.dialogScrim {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  animation: fade 0.2s ease both;
}
.dialog {
  position: relative;
  width: min(426.8px, 100%);
  padding: 21.3px;
  border: 1px solid var(--tip-edge);
  border-radius: 12.8px;
  background: var(--tip);
  box-shadow: var(--shadow);
  animation: pop 0.22s cubic-bezier(0.3, 0, 0.2, 1.2) both;
}
.dialogTitle {
  font-size: 16px;
  font-weight: 600;
}
.dialogText {
  margin-top: 4.3px;
  color: var(--ink-2);
  font-size: 13px;
}
.field {
  display: grid;
  gap: 6.4px;
  margin-top: 17.1px;
  color: var(--ink-2);
  font-size: 12.5px;
}
.input {
  height: 34.1px;
  padding: 0 10.7px;
  border: 1px solid var(--line-strong);
  border-radius: 7.5px;
  background: var(--page);
  color: var(--ink);
  font-size: 14px;
}
.input:focus-visible {
  outline: 2.1px solid var(--focus);
  outline-offset: 0;
}
.fieldset {
  margin-top: 14.9px;
}
.fieldset legend {
  margin-bottom: 6.4px;
  padding: 0;
  color: var(--ink-2);
  font-size: 12.5px;
}
.choices {
  display: flex;
  flex-wrap: wrap;
  gap: 6.4px;
}
.choice {
  position: relative;
}
.choice input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.choice span {
  display: inline-grid;
  place-items: center;
  height: 29.9px;
  padding: 0 11.7px;
  border: 1px solid var(--line-strong);
  border-radius: 7.5px;
  color: var(--ink-soft);
  font-size: 13px;
  cursor: pointer;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}
.choice input:checked + span {
  border-color: var(--violet);
  background: var(--row-on);
  color: var(--ink);
}
.choice input:focus-visible + span {
  outline: 2.1px solid var(--focus);
  outline-offset: 1px;
}
.dialogActions {
  display: flex;
  justify-content: flex-end;
  gap: 8.5px;
  margin-top: 21.3px;
}
.dialogActions .ghostButton {
  margin-top: 0;
}

/* A short note at the foot of the window. */
.toast {
  position: absolute;
  bottom: 19.2px;
  left: calc(var(--side) + (100% - var(--side)) / 2);
  z-index: 25;
  display: flex;
  align-items: center;
  gap: 12.8px;
  max-width: calc(100% - 34.1px);
  padding: 9.6px 14.9px;
  border: 1px solid var(--tip-edge);
  border-radius: 9.6px;
  background: var(--tip);
  box-shadow: var(--shadow);
  font-size: 13px;
  white-space: nowrap;
  transform: translateX(-50%);
  animation: toast 0.22s ease both;
}
.toastUndo {
  color: var(--violet);
  font-weight: 600;
}

.scrim {
  display: none;
}

/* The demo pointer, over everything; one of its glyphs shows at a time. */
.pointerLayer {
  position: absolute;
  inset: 0;
  z-index: 40;
  overflow: hidden;
  border-radius: 18.1px;
  pointer-events: none;
}
.pointer {
  width: 22px;
  height: 22px;
}
.hand {
  visibility: hidden;
}
.pointer[data-glyph='hand'] .arrow {
  visibility: hidden;
}
.pointer[data-glyph='hand'] .hand {
  visibility: visible;
}

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

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
@keyframes blink {
  50% {
    opacity: 0.35;
  }
}
@keyframes rise {
  from {
    opacity: 0;
    translate: 0 3.2px;
  }
}
@keyframes drop {
  from {
    opacity: 0;
    transform: translateY(-4.3px);
  }
}
@keyframes slide {
  from {
    opacity: 0;
    transform: translateX(25.6px);
  }
}
@keyframes fade {
  from {
    opacity: 0;
  }
}
@keyframes pop {
  from {
    opacity: 0;
    transform: translateY(8.5px) scale(0.98);
  }
}
@keyframes toast {
  from {
    opacity: 0;
    translate: 0 8.5px;
  }
}

/* The main column narrower than the table: the runtime, the host and the line go. */
@container main (max-width: 819px) {
  .content {
    padding: 33px 33.1px 34.1px;
  }
  .headRow,
  .row {
    grid-template-columns: minmax(0, 1fr) 216.5px 52px;
  }
  .usageCell {
    padding-right: 0 !important;
  }
  .colHost,
  .colSpark,
  .runtime {
    display: none !important;
  }
}
@container main (max-width: 560px) {
  .content {
    padding: 19.2px 14.9px 29.9px;
  }
  .usageHead {
    flex-direction: column;
  }
  .heatHead {
    flex-direction: column;
    align-items: flex-start;
  }
  /* A phone: the usage goes under the agent's name. */
  .headRow,
  .row {
    grid-template-columns: minmax(0, 1fr) 36px;
    padding-left: 12.8px;
  }
  .headRow th:nth-child(3) {
    display: none;
  }
  .row {
    padding-block: 10px;
  }
  .agentCell {
    gap: 9.6px;
    padding-right: 8px !important;
  }
  .agentMark {
    width: 29.9px;
    height: 29.9px;
  }
  .usageCell {
    grid-row: 2;
    grid-column: 1;
    padding: 6px 4px 0 39.5px !important;
  }
  .colGo {
    grid-row: 1 / span 2;
    grid-column: 2;
    padding-left: 8.5px !important;
  }
}

/* Narrower than the main column and the panel side by side: the panel goes over the column. */
@container (max-width: 1099px) {
  .window[data-panel] {
    --panel-w: 0px;
  }
  .panel {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 12;
    width: min(490.9px, 100%);
    box-shadow: var(--shadow);
  }
  .panelScrim {
    position: absolute;
    inset: 0;
    z-index: 11;
    display: block;
    background: var(--scrim);
    animation: fade 0.2s ease both;
  }
  .facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .resources {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* A phone: the sidebar is a drawer behind the menu button, and the window bar is plain. */
@container (max-width: 719px) {
  .panelAgent {
    flex-wrap: wrap;
  }
  .panelStatus {
    order: 3;
    width: 100%;
    padding-left: 57px;
  }
  .window {
    --side: 0px;
    height: 811px;
    border-radius: 14.9px;
  }
  .pointerLayer {
    border-radius: 14.9px;
  }
  .sidebar {
    z-index: 15;
    width: min(298.8px, 86%);
    box-shadow: var(--shadow);
    transform: translateX(-104%);
    visibility: hidden;
    transition:
      transform 0.28s cubic-bezier(0.3, 0, 0.2, 1),
      visibility 0s 0.28s;
  }
  .window[data-collapsed] .sidebar {
    opacity: 1;
  }
  .window[data-drawer] .sidebar {
    transform: none;
    visibility: visible;
    transition: transform 0.28s cubic-bezier(0.3, 0, 0.2, 1);
  }
  .scrim {
    position: absolute;
    inset: 0;
    z-index: 14;
    display: block;
    background: var(--scrim);
    opacity: 0;
    visibility: hidden;
    transition:
      opacity 0.25s ease,
      visibility 0s 0.25s;
  }
  .window[data-drawer] .scrim {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.25s ease;
  }
  .chrome {
    width: auto;
    padding-left: 6.4px;
  }
  .window[data-drawer] .chrome {
    z-index: 16;
  }
  .lights,
  .history {
    display: none;
  }
  .toggle {
    margin-left: 0;
  }
  .toggle .headIcon {
    display: none;
  }
  .toggle .menuGlyph {
    display: block;
  }
  .main {
    border-left: 0;
  }
  .header,
  .window[data-collapsed] .header {
    padding-left: 38.4px;
  }
  .toolbar {
    padding-left: 14.9px;
  }
  .toast {
    left: 50%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .root *,
  .root *::before,
  .root *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
```

### usage-dashboard/charts.tsx

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

/*
 * The small charts: a row's last-day line (a sparkline with gaps where nothing was read) and the
 * details panel's resource charts (a line, two areas and a filled bar under a dashed limit). All
 * are SVG drawn in a box of their own units and stretched to their slot with a stroke that does
 * not scale, so they stay crisp at any width. They are decorative: their figures are in the text
 * beside them.
 */

import styles from './usage-dashboard.module.css';

const W = 134;
const H = 30;

/** The path of `values` (0–1, null for a gap) across a W × H box, `pad` units off top and bottom. */
function line(values: (number | null)[], width = W, height = H, pad = 2) {
  const step = width / Math.max(1, values.length - 1);
  let d = '';
  let open = false;
  values.forEach((value, index) => {
    if (value === null) {
      open = false;
      return;
    }
    const x = (index * step).toFixed(2);
    const y = (height - pad - Math.min(1, value) * (height - pad * 2)).toFixed(2);
    d += `${open ? 'L' : 'M'}${x} ${y}`;
    open = true;
  });
  return d;
}

export function Sparkline({ values }: { values: (number | null)[] }) {
  return (
    <svg
      className={styles.spark}
      viewBox={`0 0 ${W} ${H}`}
      preserveAspectRatio="none"
      aria-hidden="true"
      data-part="sparkline"
    >
      <path d={line(values)} />
    </svg>
  );
}

/** A resource chart: a line, a line over an area, or a level under a dashed limit. */
export function ResourceChart({
  kind,
  values,
  level = 0,
}: {
  kind: 'line' | 'area' | 'limit';
  values?: number[];
  level?: number;
}) {
  const width = 108;
  const height = kind === 'limit' ? 56 : 44;
  if (kind === 'limit') {
    const top = height - level * (height - 6);
    return (
      <svg
        className={styles.resource}
        data-kind={kind}
        viewBox={`0 0 ${width} ${height}`}
        preserveAspectRatio="none"
        aria-hidden="true"
      >
        <rect className={styles.resourceFill} x="0" y={top} width={width} height={height - top} />
        <path className={styles.resourceLimit} d={`M0 4H${width}`} />
        <path className={styles.resourceLine} d={`M0 ${top}H${width}`} />
      </svg>
    );
  }
  const d = line(values ?? [], width, height, 1.5);
  return (
    <svg
      className={styles.resource}
      data-kind={kind}
      viewBox={`0 0 ${width} ${height}`}
      preserveAspectRatio="none"
      aria-hidden="true"
    >
      {kind === 'area' && (
        <path className={styles.resourceFill} d={`${d}L${width} ${height}L0 ${height}Z`} />
      )}
      <path className={styles.resourceLine} d={d} />
    </svg>
  );
}
```

### usage-dashboard/data.ts

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

/*
 * The dashboard's made-up data: a workspace of coding agents, each signed in to a plan with a
 * 5-hour and sometimes a weekly usage window, and what each window read every half hour.
 *
 * A reading is the share of the agent's fullest plan window, 0 to 1 (1 is at the limit), or null
 * when the agent sent nothing. The last day is written out by hand, one character a cell: '.' no
 * reading, 'X' at the limit, and 0–9 then a–j for twentieths. The week and the month are worked out
 * from a seeded random walk per agent, so they are the same on every load.
 */

export type Range = '24h' | '7d' | '30d';
export type Runtime = 'ember' | 'knot';
export type Host = 'cloud' | 'self';

export type Agent = {
  id: string;
  name: string;
  runtime: Runtime;
  host: Host;
  version: string;
  /** Share of the 5-hour window used, 0–100. */
  fiveHour: number;
  /** Share of the weekly window used, 0–100, for plans that have one. */
  weekly?: number;
  /** An error the agent answered with instead of its usage. */
  error?: string;
  /** Freshly added: no sign-in yet, so no readings. */
  pending?: boolean;
  latency: number;
  uptime: string;
  conversations: number;
  active: number;
  linked: string;
  /** Minutes until the 5-hour window resets. */
  resetsIn: number;
  digest: string;
  seed: number;
};

export const RUNTIMES: Record<Runtime, string> = { ember: 'Ember CLI', knot: 'Knot CLI' };
export const HOSTS: Record<Host, string> = { cloud: 'Cloud', self: 'Self-hosted' };
export const PLANS = ['5x', '6.25x', '20x'] as const;

export const AGENTS: Agent[] = [
  {
    id: 'ines-20-vps',
    name: 'Ines (20x) on Ines’s VPS',
    runtime: 'ember',
    host: 'self',
    version: '0.1.19',
    fiveHour: 0,
    weekly: 18,
    latency: 31,
    uptime: '11d 2h',
    conversations: 112,
    active: 0,
    linked: '2d ago',
    resetsIn: 244,
    digest: '9c41e0b7a2d35f18c6e0a94b27d1f03e',
    seed: 11,
  },
  {
    id: 'tomas-625',
    name: 'Tomas (6.25x)',
    runtime: 'ember',
    host: 'cloud',
    version: '0.1.19',
    fiveHour: 17,
    latency: 23,
    uptime: '3d 4h',
    conversations: 47,
    active: 1,
    linked: '19h ago',
    resetsIn: 99,
    digest: '1e07a3cd53a1f123e022a13e09a8e41d',
    seed: 23,
  },
  {
    id: 'mara-625',
    name: 'Mara (6.25x)',
    runtime: 'ember',
    host: 'cloud',
    version: '0.1.19',
    fiveHour: 27,
    latency: 26,
    uptime: '3d 4h',
    conversations: 63,
    active: 2,
    linked: '19h ago',
    resetsIn: 152,
    digest: '5b2f88c01d6e4a3790be1c25f4a06d9b',
    seed: 37,
  },
  {
    id: 'oren-625',
    name: 'Oren (6.25x)',
    runtime: 'ember',
    host: 'cloud',
    version: '0.1.18',
    fiveHour: 4,
    latency: 22,
    uptime: '6h 12m',
    conversations: 29,
    active: 0,
    linked: '6h ago',
    resetsIn: 281,
    digest: 'e8d1046f3ab27c95d0e4f61a8b3c27d5',
    seed: 41,
  },
  {
    id: 'lucia-625',
    name: 'Lucia (6.25x)',
    runtime: 'ember',
    host: 'cloud',
    version: '0.1.19',
    fiveHour: 0,
    latency: 24,
    uptime: '3d 4h',
    conversations: 8,
    active: 0,
    linked: '19h ago',
    resetsIn: 300,
    digest: '07c3be9a41f2d685e1a0c94b3d72e1f6',
    seed: 53,
  },
  {
    id: 'oren-20',
    name: 'Oren (20x)',
    runtime: 'ember',
    host: 'cloud',
    version: '0.1.19',
    fiveHour: 13,
    weekly: 78,
    latency: 25,
    uptime: '9d 7h',
    conversations: 214,
    active: 3,
    linked: '1d ago',
    resetsIn: 37,
    digest: 'b4a90e2c7d13f6085e9c1a2b4f7d30e8',
    seed: 67,
  },
  {
    id: 'ines-625',
    name: 'Ines (6.25x)',
    runtime: 'ember',
    host: 'cloud',
    version: '0.1.19',
    fiveHour: 0,
    error: 'HTTP 429',
    latency: 48,
    uptime: '3d 4h',
    conversations: 17,
    active: 0,
    linked: '19h ago',
    resetsIn: 12,
    digest: '3f6d02a8e1b97c45d0a2e8f13c6b94d7',
    seed: 71,
  },
  {
    id: 'ines-20',
    name: 'Ines (20x)',
    runtime: 'knot',
    host: 'cloud',
    version: '2.4.0',
    fiveHour: 9,
    weekly: 22,
    latency: 29,
    uptime: '5d 1h',
    conversations: 54,
    active: 1,
    linked: '3d ago',
    resetsIn: 188,
    digest: 'a1c8e27f0d4b93e6c5f2a01d8b7e46c3',
    seed: 83,
  },
  {
    id: 'tomas-5',
    name: 'Tomas (5x)',
    runtime: 'knot',
    host: 'cloud',
    version: '2.4.0',
    fiveHour: 6,
    latency: 27,
    uptime: '5d 1h',
    conversations: 21,
    active: 0,
    linked: '3d ago',
    resetsIn: 205,
    digest: 'd60b3e9f12a84c7e5b0d1f2a93c6e48b',
    seed: 97,
  },
  {
    id: 'lucia-5',
    name: 'Lucia (5x)',
    runtime: 'knot',
    host: 'cloud',
    version: '2.3.2',
    fiveHour: 41,
    weekly: 64,
    latency: 33,
    uptime: '1d 9h',
    conversations: 76,
    active: 2,
    linked: '1d ago',
    resetsIn: 71,
    digest: '6e2a9d14c07fb35e8a1d0c4f92b7e63a',
    seed: 101,
  },
  {
    id: 'oren-20-knot',
    name: 'Oren (20x)',
    runtime: 'knot',
    host: 'cloud',
    version: '2.4.0',
    fiveHour: 3,
    latency: 21,
    uptime: '5d 1h',
    conversations: 12,
    active: 0,
    linked: '3d ago',
    resetsIn: 266,
    digest: 'f9b05c3a2e18d74c6b0a9e1f3d52c87a',
    seed: 113,
  },
];

/** The last day, half hour by half hour (48 cells and the current one). */
const DAY: Record<string, string> = {
  'ines-20-vps': 'figjjihfghijhhiihhhihh003333132335878822215878993',
  'tomas-625': 'X000000222512556556451100001111129cdffijjX1433433',
  'mara-625': '788djXXXX000000000100000...1010101000110011003355',
  'oren-625': '477acgiiih111110110011022459gXXXXX111376bfgjjj111',
  'lucia-625': '3222200000000000000000000000000102450000001001000',
  'oren-20': '798bcbca9bccbbcabdfdddcedddfffeffffgfffffehgfggff',
  'ines-625': '......11010000011100110.....00010000000000100....',
  'ines-20': '1101011001101111110111011111110011101112242223322',
  'tomas-5': '1000101110100013222322232222222222222222233223322',
  'lucia-5': '......3222455547776678888bcbabbbabbbabbbacbbdheff',
  'oren-20-knot': '0001000110000001000000000000222222222222212223322',
};

const CODES = '0123456789abcdefghij';

/** How many cells a row has, the current one included. */
export const CELLS = 49;

function decode(row: string): (number | null)[] {
  return [...row].map((char) => {
    if (char === '.') return null;
    if (char === 'X') return 1;
    return (CODES.indexOf(char) / 20) * 0.99 + 0.02;
  });
}

/** A small seeded generator, so the week and the month are the same on every load. */
function random(seed: number) {
  let state = seed * 2654435761;
  return () => {
    state |= 0;
    state = (state + 0x6d2b79f5) | 0;
    let t = Math.imul(state ^ (state >>> 15), 1 | state);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

/** A longer range: a walk around the agent's own level, with quiet spells and the odd limit. */
function walk(agent: Agent, range: Range): (number | null)[] {
  const next = random(agent.seed + (range === '7d' ? 1 : 2));
  const day = decode(DAY[agent.id] ?? '');
  const known = day.filter((value): value is number => value !== null);
  const mean = known.length ? known.reduce((sum, value) => sum + value, 0) / known.length : 0.1;
  let level = mean;
  const out: (number | null)[] = [];
  for (let i = 0; i < CELLS; i += 1) {
    const roll = next();
    if (agent.id === 'ines-625' && roll < 0.22) {
      out.push(null);
      continue;
    }
    level += (mean - level) * 0.25 + (next() - 0.5) * (range === '7d' ? 0.34 : 0.26);
    level = Math.max(0.02, Math.min(0.97, level));
    out.push(roll > 0.965 && mean > 0.12 ? 1 : level);
  }
  return out;
}

const cache = new Map<string, (number | null)[]>();

/** An agent's readings over a range, oldest first. */
export function readings(agent: Agent, range: Range): (number | null)[] {
  if (agent.pending) return Array.from({ length: CELLS }, () => null);
  const key = `${agent.id}:${range}`;
  let row = cache.get(key);
  if (!row) {
    row = range === '24h' ? decode(DAY[agent.id] ?? '') : walk(agent, range);
    cache.set(key, row);
  }
  return row;
}

/** The fixed "now" of the screen: 10:23 PM on Tuesday 14 October. */
const NOW = new Date(2026, 9, 14, 22, 23);
const STEP: Record<Range, number> = { '24h': 30, '7d': 210, '30d': 900 };

function clock(date: Date) {
  const hours = date.getHours() % 12 || 12;
  const minutes = String(date.getMinutes()).padStart(2, '0');
  return `${String(hours).padStart(2, '0')}:${minutes} ${date.getHours() < 12 ? 'AM' : 'PM'}`;
}
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

/** When cell `index` (of CELLS) began; the last one began now. */
function cellStart(range: Range, index: number) {
  return new Date(NOW.getTime() - (CELLS - 1 - index) * STEP[range] * 60_000);
}

/**
 * The cells (of CELLS) that column `column` of `count` covers: the past splits evenly over all
 * the columns but the last, which is the current cell.
 */
export function span(column: number, count: number): [number, number] {
  const past = CELLS - 1;
  if (column >= count - 1) return [past, CELLS];
  const per = past / (count - 1);
  return [Math.round(column * per), Math.round((column + 1) * per)];
}

/** The column of `count` that holds cell `index`. */
export function columnOf(index: number, count: number) {
  for (let column = 0; column < count; column += 1) {
    if (index < span(column, count)[1]) return column;
  }
  return count - 1;
}

/** The axis label under a column. */
export function axisLabel(range: Range, column: number, count: number) {
  const date = cellStart(range, span(column, count)[0]);
  if (range === '24h') return clock(date);
  if (range === '7d') return `${DAYS[date.getDay()]} ${clock(date).replace(':00', '')}`;
  return `${MONTHS[date.getMonth()]} ${date.getDate()}`;
}

/** The span a column covers, for its tooltip. */
export function cellSpan(range: Range, column: number, count: number) {
  const [from, to] = span(column, count);
  const start = cellStart(range, from);
  if (from === CELLS - 1) return `${clock(start)} – now`;
  const end = cellStart(range, to);
  if (range === '24h') return `${clock(start)} – ${clock(end)}`;
  const day = (date: Date) => `${DAYS[date.getDay()]} ${MONTHS[date.getMonth()]} ${date.getDate()}`;
  return `${day(start)}, ${clock(start)} – ${clock(end)}`;
}

/** A row in `count` columns, each the fullest of its cells; the current cell stays alone. */
export function merged(row: (number | null)[], count: number) {
  if (count >= CELLS) return row;
  return Array.from({ length: count }, (_, column) => {
    const [from, to] = span(column, count);
    const part = row.slice(from, to).filter((value): value is number => value !== null);
    return part.length ? Math.max(...part) : null;
  });
}

/** The legend's bucket for a reading: 0–3 the purples, 4 at the limit, -1 none. */
export function bucket(value: number | null) {
  if (value === null) return -1;
  if (value >= 1) return 4;
  return Math.min(3, Math.floor(value * 4));
}

export const NOW_LABEL = clock(NOW);

/** The resource charts of the details panel: 60 points over the chosen span. */
export type ResourceRange = '1h' | '6h' | '24h';
export function resources(agent: Agent, range: ResourceRange) {
  const next = random(agent.seed * 7 + (range === '1h' ? 0 : range === '6h' ? 1 : 2));
  const points = 60;
  const cpu: number[] = [];
  const memory: number[] = [];
  const sessions: number[] = [];
  let mem = 0.55 + next() * 0.1;
  let ses = 0.78;
  for (let i = 0; i < points; i += 1) {
    const spike = next();
    cpu.push(spike > 0.93 ? 0.35 + next() * 0.6 : 0.04 + next() * 0.06);
    if (next() > 0.9) mem = Math.min(0.95, mem + next() * 0.09);
    memory.push(mem + (next() - 0.5) * 0.01);
    if (next() > 0.94) ses = Math.min(0.92, ses + 0.03);
    sessions.push(ses);
  }
  const span = { '1h': 60, '6h': 360, '24h': 1440 }[range];
  const from = new Date(NOW.getTime() - span * 60_000);
  return {
    cpu,
    memory,
    sessions,
    disk: 26 / 29,
    cpuNow: `${Math.round(40 + cpu[points - 1] * 900)}m`,
    memoryNow: `${Math.round(1200 + mem * 1700)} MiB`,
    sessionsNow: String(agent.conversations),
    span: `${clock(from)} – ${clock(NOW)}`,
  };
}

/** The sidebar's chats: a title, the work items they touch, and whether they are running. */
export type Chip = { kind: 'ticket' | 'pr' | 'merged' | 'closed' | 'thread'; label: string };
export type Chat = {
  id: string;
  title: string;
  chips?: Chip[];
  running?: boolean;
  unread?: boolean;
  children?: Chat[];
};

export const CHATS: Chat[] = [
  {
    id: 'c1',
    title: 'Redesigning the agents page with usage',
    running: true,
    chips: [
      { kind: 'ticket', label: 'ORB-1131' },
      { kind: 'merged', label: '#306' },
    ],
  },
  {
    id: 'c2',
    title: 'Halyard PR review helper',
    running: true,
    unread: true,
    children: [
      { id: 'c2a', title: '#301 Retry budget per agent', chips: [{ kind: 'pr', label: '#301' }] },
      { id: 'c2b', title: '#299 Flaky usage poller', chips: [{ kind: 'pr', label: '#299' }] },
    ],
  },
  {
    id: 'c3',
    title: 'Certificate renewal alert in staging',
    chips: [
      { kind: 'thread', label: '<support-th…' },
      { kind: 'pr', label: '#2841' },
      { kind: 'ticket', label: 'OPS-2963' },
    ],
  },
  {
    id: 'c4',
    title: 'Halyard engineering lead',
    unread: true,
    children: [
      {
        id: 'c4a',
        title: '#284 #289 #294 #305 Disk pressure on runners',
        running: true,
        chips: [
          { kind: 'ticket', label: 'ORB-1095' },
          { kind: 'pr', label: '#284' },
          { kind: 'pr', label: '#289' },
          { kind: 'ticket', label: 'ORB-1102' },
        ],
      },
      {
        id: 'c4b',
        title: '#273 #290 #295 Device sign-in loop',
        running: true,
        chips: [
          { kind: 'pr', label: '#273' },
          { kind: 'ticket', label: 'ORB-1114' },
          { kind: 'closed', label: '#286' },
          { kind: 'pr', label: '#290' },
        ],
      },
      {
        id: 'c4c',
        title: '#287 Turns killed by runtime timeout',
        unread: true,
        chips: [
          { kind: 'pr', label: '#287' },
          { kind: 'ticket', label: 'ORB-1128' },
        ],
      },
      {
        id: 'c4d',
        title: '#283 Pass Ember CLI slash commands through',
        running: true,
        chips: [
          { kind: 'ticket', label: 'ORB-1090' },
          { kind: 'pr', label: '#283' },
          { kind: 'pr', label: '#280' },
        ],
      },
    ],
  },
];

export const PINNED = [
  { id: 'p1', title: 'Weekly usage review' },
  { id: 'p2', title: 'Runner image upgrades' },
];
export const SHARED = [{ id: 's1', title: 'On-call handbook draft' }];

export const WORKSPACES = [
  { id: 'kestrel', name: 'Kestrel Labs', mark: 'K' },
  { id: 'fieldnote', name: 'Fieldnote Studio', mark: 'F' },
];

/** The clock time `minutes` from now, for a window's reset. */
export function clockIn(minutes: number) {
  return clock(new Date(NOW.getTime() + minutes * 60_000));
}
```

### usage-dashboard/icons.tsx

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

/*
 * The dashboard's icons, drawn on a 24-unit grid with round 1.7-unit strokes. They are decorative
 * (aria-hidden); their size comes from the stylesheet, their colour from currentColor. The two
 * runtime marks are filled shapes in their own colours.
 */

import type { ReactNode } from 'react';

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

type Props = { className?: string };

export const icons = {
  sidebar: ({ className }: Props) => (
    <Icon className={className}>
      <rect x="3.5" y="4.5" width="17" height="15" rx="3" />
      <path d="M9.5 4.5v15" />
    </Icon>
  ),
  left: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M14.5 6.5 9 12l5.5 5.5" />
    </Icon>
  ),
  right: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M9.5 6.5 15 12l-5.5 5.5" />
    </Icon>
  ),
  down: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M6.5 9.5 12 15l5.5-5.5" />
    </Icon>
  ),
  updown: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M8 9.5 12 5.5l4 4M8 14.5l4 4 4-4" />
    </Icon>
  ),
  search: ({ className }: Props) => (
    <Icon className={className}>
      <circle cx="11" cy="11" r="6.5" />
      <path d="m16 16 4 4" />
    </Icon>
  ),
  compose: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M11 4.5H7a2.5 2.5 0 0 0-2.5 2.5v10A2.5 2.5 0 0 0 7 19.5h10a2.5 2.5 0 0 0 2.5-2.5v-4" />
      <path d="M17.6 3.9a1.9 1.9 0 0 1 2.7 2.7L12.5 14.4l-3.4.7.7-3.4z" />
    </Icon>
  ),
  agent: ({ className }: Props) => (
    <Icon className={className}>
      <rect x="4.5" y="8" width="15" height="11" rx="3" />
      <path d="M12 8V4.5M10 4.5h4M2.5 13v2M21.5 13v2M9.5 12.5v2M14.5 12.5v2" />
    </Icon>
  ),
  skills: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M10 6.5c.6 3.4 2.1 4.9 5.5 5.5-3.4.6-4.9 2.1-5.5 5.5-.6-3.4-2.1-4.9-5.5-5.5 3.4-.6 4.9-2.1 5.5-5.5z" />
      <path d="M17.5 3c.3 1.5.9 2.2 2.5 2.5-1.6.3-2.2.9-2.5 2.5-.3-1.6-.9-2.2-2.5-2.5 1.6-.3 2.2-1 2.5-2.5zM5.5 17.5v3M4 19h3" />
    </Icon>
  ),
  memory: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M12 5.5v13M12 5.5a3 3 0 0 0-5.3 1.4A3 3 0 0 0 4.6 12a3 3 0 0 0 2 4.8A3 3 0 0 0 12 18.5M12 5.5a3 3 0 0 1 5.3 1.4 3 3 0 0 1 2.1 5.1 3 3 0 0 1-2 4.8A3 3 0 0 1 12 18.5" />
      <path d="M8 10.5c1.2 0 2 .7 2 1.8M16 10.5c-1.2 0-2 .7-2 1.8" />
    </Icon>
  ),
  clock: ({ className }: Props) => (
    <Icon className={className}>
      <circle cx="12" cy="12" r="8" />
      <path d="M12 7.5V12l3 2" />
    </Icon>
  ),
  cloud: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M7.5 18.5a4 4 0 0 1-.6-8 5.5 5.5 0 0 1 10.6 1.3 3.4 3.4 0 0 1-.5 6.7z" />
    </Icon>
  ),
  help: ({ className }: Props) => (
    <Icon className={className}>
      <circle cx="12" cy="12" r="8.5" />
      <path d="M9.7 9.6a2.4 2.4 0 0 1 4.6.8c0 1.6-2.3 2-2.3 3.4M12 16.6v.1" />
    </Icon>
  ),
  refresh: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M19 12a7 7 0 0 1-12.2 4.7M5 12a7 7 0 0 1 12.2-4.7" />
      <path d="M17.5 3.5v4h-4M6.5 20.5v-4h4" />
    </Icon>
  ),
  copy: ({ className }: Props) => (
    <Icon className={className}>
      <rect x="8.5" y="8.5" width="11" height="11" rx="2.5" />
      <path d="M15.5 5.5a2 2 0 0 0-2-1.5H7a2.5 2.5 0 0 0-2.5 2.5v6.5a2 2 0 0 0 1.5 2" />
    </Icon>
  ),
  plus: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M12 5v14M5 12h14" />
    </Icon>
  ),
  close: ({ className }: Props) => (
    <Icon className={className}>
      <path d="m6.5 6.5 11 11M17.5 6.5l-11 11" />
    </Icon>
  ),
  more: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M5.5 12h.01M12 12h.01M18.5 12h.01" strokeWidth="2.6" />
    </Icon>
  ),
  check: ({ className }: Props) => (
    <Icon className={className}>
      <path d="m5.5 12.5 4 4 9-9" />
    </Icon>
  ),
  menu: ({ className }: Props) => (
    <Icon className={className}>
      <path d="M4.5 7h15M4.5 12h15M4.5 17h15" />
    </Icon>
  ),
  ticket: ({ className }: Props) => (
    <Icon className={className}>
      <circle cx="12" cy="12" r="8" strokeWidth="2" />
      <path d="M12 6.5a5.5 5.5 0 0 1 0 11z" fill="currentColor" stroke="none" />
    </Icon>
  ),
  pr: ({ className }: Props) => (
    <Icon className={className}>
      <circle cx="7" cy="6" r="2.2" />
      <circle cx="7" cy="18" r="2.2" />
      <circle cx="17" cy="18" r="2.2" />
      <path d="M7 8.2v7.6M7 11.5c0 2.5 2 3.5 4.5 3.5h3" />
    </Icon>
  ),
  merged: ({ className }: Props) => (
    <Icon className={className}>
      <circle cx="7" cy="6" r="2.2" />
      <circle cx="7" cy="18" r="2.2" />
      <circle cx="17" cy="6" r="2.2" />
      <path d="M7 8.2v7.6M17 8.2v3.3c0 2.2-1.8 3.5-4 3.5H9" />
    </Icon>
  ),
  closed: ({ className }: Props) => (
    <Icon className={className}>
      <circle cx="7" cy="6" r="2.2" />
      <circle cx="7" cy="18" r="2.2" />
      <path d="M7 8.2v7.6M15 4l4 4M19 4l-4 4M17 11.5v4.3" />
      <circle cx="17" cy="18" r="2.2" />
    </Icon>
  ),
  thread: ({ className }: Props) => (
    <Icon className={className}>
      <path
        d="M5 6.5A2.5 2.5 0 0 1 7.5 4h9A2.5 2.5 0 0 1 19 6.5v7a2.5 2.5 0 0 1-2.5 2.5H11l-4.5 4v-4A2.5 2.5 0 0 1 5 13.5z"
        fill="currentColor"
        stroke="none"
      />
    </Icon>
  ),
};

export type IconName = keyof typeof icons;

/** Twelve rounded rays of different lengths: the orange runtime's mark. */
const EMBER = Array.from({ length: 12 }, (_, i) => {
  const angle = (i / 12) * Math.PI * 2 + 0.13;
  const r = i % 2 ? 7.4 : 9.6;
  const x = 12 + Math.cos(angle) * r;
  const y = 12 + Math.sin(angle) * r;
  return `M12 12L${x.toFixed(2)} ${y.toFixed(2)}`;
}).join('');

/** The two runtimes' marks: an orange burst and a grey knot of three loops. */
export function RuntimeMark({
  runtime,
  className,
}: {
  runtime: 'ember' | 'knot';
  className?: string;
}) {
  return (
    <svg
      className={className}
      viewBox="0 0 24 24"
      aria-hidden="true"
      focusable="false"
      data-runtime={runtime}
    >
      {runtime === 'ember' ? (
        <path d={EMBER} fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
      ) : (
        <g fill="none" stroke="currentColor" strokeWidth="1.5">
          <circle cx="12" cy="8.6" r="4.6" />
          <circle cx="8.9" cy="14" r="4.6" />
          <circle cx="15.1" cy="14" r="4.6" />
        </g>
      )}
    </svg>
  );
}
```

### usage-dashboard/demo-pointer.tsx

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

'use client';

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

/*
 * The demo's pointer, and the steps that move it. A demo is a script of awaited steps: wait, or
 * glide the pointer somewhere. 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.
 *
 * Each glide is a curved flight played by the Web Animations API, so nothing runs frame by frame.
 * The points of the flight are handed to whoever wants to move along with it. The arrow and the hand are HTML boxes
 * that swap by their visibility: a change of style inside an SVG would lay the SVG out again.
 */

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

/** A glide's path and timing, for an animation that plays along with it. */
export type Flight = { points: Point[]; timing: KeyframeAnimationOptions };

export type Demo = {
  /** Resolves after `ms`. */
  wait: (ms: number) => Promise<void>;
  /** Glides the pointer to `to`, in the stage's own px, in `ms`; `along` gets the flight. */
  glide: (to: Point, ms: number, along?: (flight: Flight) => void) => Promise<void>;
  /** Shows the arrow, or the hand a link shows. */
  glyph: (name: 'arrow' | 'hand') => void;
};

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

/** Where the arrow's tip is, as shares of its box: the point the pointer stands on. */
const TIP: Point = { x: 0.15, y: 0.1 };

/** How many points a flight is drawn through. */
const SAMPLES = 24;

/** Plays `script` with the pointer over `stage`; returns the function that stops it. */
export function runDemo(
  stage: HTMLElement,
  pointer: HTMLElement,
  script: (demo: Demo) => Promise<void>,
): () => void {
  let alive = true;
  let flight: Animation | null = null;
  // The stylesheet sizes the pointer from the stage's width; its tip is where it stands.
  const tip = { x: pointer.offsetWidth * TIP.x, y: pointer.offsetHeight * TIP.y };
  const place = ({ x, y }: Point) => `translate(${x - tip.x}px, ${y - tip.y}px)`;
  const timers = new Set<number>();
  const pending = new Set<() => void>();
  // The pointer comes in from the lower right.
  let at: Point = { x: stage.offsetWidth * 0.8, 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);
      }),
    );

  const glide: Demo['glide'] = (to, ms, along) => {
    const from = at;
    at = to;
    const dx = to.x - from.x;
    const dy = to.y - from.y;
    // A gentle curve, as a hand moves: the control point sits off the straight line.
    const bend = { x: (from.x + to.x) / 2 - dy * 0.18, y: (from.y + to.y) / 2 + dx * 0.18 };
    const points: Point[] = [];
    for (let i = 0; i <= SAMPLES; i += 1) {
      const t = i / SAMPLES;
      const u = 1 - t;
      points.push({
        x: u * u * from.x + 2 * u * t * bend.x + t * t * to.x,
        y: u * u * from.y + 2 * u * t * bend.y + t * t * to.y,
      });
    }
    const timing: KeyframeAnimationOptions = {
      duration: ms,
      easing: 'cubic-bezier(0.45, 0, 0.25, 1)',
    };
    flight?.cancel();
    // Where it rests once the flight is over.
    pointer.style.transform = place(to);
    flight = pointer.animate(
      points.map((point) => ({ transform: place(point) })),
      timing,
    );
    along?.({ points, timing });
    return step(flight.finished);
  };

  const glyph: Demo['glyph'] = (name) => {
    pointer.dataset.glyph = name;
  };

  pointer.dataset.glyph = 'arrow';
  pointer.style.transform = place(at);
  pointer.style.opacity = '1';
  script({ wait, glide, glyph }).catch((error: unknown) => {
    if (!(error instanceof Stopped)) throw error;
  });

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

/**
 * The pointer's box: it moves, on a layer of its own. Its size comes from its class, its colours
 * from the stage's tokens (usage-dashboard.module.css).
 */
const POINTER: CSSProperties = {
  position: 'absolute',
  top: 0,
  left: 0,
  zIndex: 2,
  display: 'block',
  pointerEvents: 'none',
  opacity: 0,
  transition: 'opacity 0.3s ease',
  filter: 'drop-shadow(0 1px 1.5px var(--pointer-shadow))',
  willChange: 'transform',
};

/** One glyph over the other, each filling the box. */
const GLYPH: CSSProperties = { position: 'absolute', inset: 0, display: 'block' };
const DRAWING: CSSProperties = {
  display: 'block',
  width: '100%',
  height: '100%',
  overflow: 'visible',
};
/** The arrow is dark on a white edge, the hand white on a dark one, as the system draws them. */
const ARROW: CSSProperties = { fill: 'var(--pointer)', stroke: 'var(--pointer-edge)' };
const HAND: CSSProperties = { fill: 'var(--pointer-edge)', stroke: 'var(--pointer)' };

/**
 * The pointer the demo moves around: an arrow, or a pointing hand over a control. It is
 * decorative: what it does shows on the dashboard itself. The stylesheet shows one glyph at a time,
 * from the box's data-glyph.
 */
export function DemoPointer({
  ref,
  className,
  arrowClass,
  handClass,
}: {
  ref: Ref<HTMLSpanElement>;
  className?: string;
  arrowClass?: string;
  handClass?: string;
}) {
  return (
    <span ref={ref} className={className} style={POINTER} aria-hidden="true">
      <span className={arrowClass} style={GLYPH}>
        <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 className={handClass} style={GLYPH}>
        <svg viewBox="0 0 20 20" style={DRAWING}>
          {/* The tip of the finger sits where the arrow's tip is. */}
          <path
            d="M6.4 3.3c0-.8.6-1.4 1.3-1.4S9 2.5 9 3.3v5.5c.2-.5.6-.8 1.2-.8.7 0 1.2.5 1.2 1.2v.5c.2-.5.6-.8 1.2-.8.7 0 1.2.5 1.2 1.2v.7c.2-.4.6-.7 1.1-.7.7 0 1.2.5 1.2 1.2v3.5c0 2.8-1.9 4.9-4.6 4.9h-1.2c-1.8 0-3.1-.8-4.1-2.3l-2.6-3.9c-.4-.6-.2-1.3.4-1.7.6-.3 1.3-.2 1.7.4l.6.9z"
            style={HAND}
            strokeWidth="1.1"
            strokeLinejoin="round"
            transform="translate(-4.7 0.1)"
          />
        </svg>
      </span>
    </span>
  );
}
```

### usage-dashboard/use-autoplay.ts

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

'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 };
}
```