# Workforce dashboard

> A time-tracking product’s analytics home: nine live cards (area, donut, stacked bars, a beating health heart, lines and bars) under a top bar with a running tracker and filters.

The analytics home of an invented time-tracking product. A dark top bar searches with ⌘K, runs a time tracker you can pause and opens notifications; an icon sidebar shows tooltips; filter chips come off and reset. Nine cards answer the pointer and the keyboard: active employees over the year, employees by group in a donut you can regroup, the time bank by month in stacked bars, the total time bank per colleague with a metric picker, a company health score in a beating heart filling with liquid, the monthly employee, the time bank by department with legend toggles, a monthly line and the positive time bank by employee. Picking a colleague filters every card that knows them.

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

Category: Dashboards.

## Anatomy

1. **Top bar**: Search (⌘K focuses it), a running time tracker you can pause, notifications and the account. Narrow, it wraps to two rows and gains a menu button.
2. **Sidebar**: Icon buttons with tooltips on hover and focus; the current page carries a bar. Narrow, it becomes a drawer behind the menu button.
3. **Filter bar**: The period and scope as chips. Each comes off with its ×, and once none is left Reset filters brings them all back.
4. **Area chart card**: Active employees over the year. The line draws in, and the pointer or the arrow keys sweep a tooltip along it.
5. **Donut card**: Employees by group, regrouped from its menu (department or unit); the legend toggles each segment.
6. **Stacked bars card**: The time bank by month, split by department, shift or contract. A press pins a bar’s tooltip.
7. **Colleague picker**: The total time bank, counting up to each colleague’s balance. Its chips and pager set the shared colleague the other cards follow.
8. **Health score**: A heart that beats and fills with liquid to the company’s score. The beat pauses off screen and under reduced motion.

## When to use

- The home screen of an HR, payroll or time-tracking product, where managers check hours, balances and headcount at a glance.
- A handful of metrics that belong together and should answer one shared filter: pick a colleague once and every card that knows them follows.
- A screen that must look the same at every width: wide, the whole design scales as one; narrow, the cards stack and keep their own drawing.
- A product tour or a landing section, where the virtual cursor shows the cards working before anyone touches them.

## When not to use

- A single metric or chart on its own. Take the one card you need, or use a stat card.
- Data to read row by row, sort or export. Use a table, such as the records table.
- Live operations that change by the second (queues, incidents). The cards draw a period, not a stream; use a monitoring view.
- Inside another app’s chrome: the dashboard brings its own top bar and sidebar. Use its cards in your own layout instead.

## Installation

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

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

export function TeamPage() {
  return (
    <WorkforceDashboard
      autoplay={false}
      defaultPerson="mateo"
      onPersonChange={(person) => console.log('Filtered to', person)}
    />
  );
}
```

## States

- **Overview**: All employees, three filters, the tracker running. The colour scheme is the page’s.
- **Filtered to a colleague**: `defaultPerson="mateo"`: the total, departments, monthly and per-employee cards follow him.
- **Notifications open**: The popover under the bell, three unread, with Mark all as read.
- **Filters cleared**: Every chip removed: the bar shows Reset filters, which brings them back.
- **Dark**: `colorScheme="dark"` on any page; every colour is a light-dark() token.

## How it works

- The dashboard is one 1526 × 881 design whose regions place themselves by left and top. On the wide layout (880 px and up) it is scaled to the root’s width in CSS alone: transform: scale(tan(atan2(100cqw, 1548px))) turns a length over a length into a number, so it is the right size from the first paint, without a script, and never lays out again as the page resizes.
- Under 880 px a container query reflows it: the frame becomes a grid of one column (two from 600 px), the top bar wraps into two rows, the sidebar becomes a drawer behind a menu button, and each card keeps its own design and is scaled to its column the same way. The time bank chart, twice as wide as the others, keeps a 420-unit window and scrolls sideways inside its card.
- Charts are SVG and CSS: the area and line draw in with stroke-dashoffset on a pathLength of 1, the donut’s segments sweep in, bars grow by transform. Every chart maps the pointer through its own bounding box, so hover still lands on the right point at any scale. Arrow keys, Home and End walk the points of a focused chart.
- The total time bank counts up with registered @property integers: the values transition and a CSS counter prints them, so no frame is rendered by script. The health heart beats with a CSS animation (paused off screen) while three waves of liquid rise inside a heart-shaped mask.
- The colleague picked in the total time bank card is shared state: the departments, monthly and per-employee cards follow it, and clicking a colleague’s bar clears it again.
- A virtual cursor plays the dashboard while it is on screen and untouched. It sends real pointer and mouse events at its coordinates and clicks real buttons, so every hover state and tooltip is the one a reader gets: it picks colleagues, sweeps the area chart, toggles a legend, hovers bars and rows, removes and resets the filters, then drags pairs of cards into each other’s places (FLIP, with the Web Animations API) and, over three cycles, back. On the narrow layout it skips the swaps and plays only the scenes on screen.
- Any real pointer, touch, key or focus inside hands control to the reader, and anything the demo left half-done is undone; it resumes five seconds after they leave. It stops off screen, in a hidden tab, under reduced motion and with autoplay={false}.
- Icons, logo, avatars and the heart are inline SVG or CSS, so the component needs no images. Every colour is a light-dark() token on the root, so it follows the page’s color-scheme or the colorScheme prop.

## API reference

- **autoplay** (`boolean`, default `true`): Plays the virtual-cursor demo while the dashboard is on screen and untouched. Never under reduced motion.
- **colorScheme** (`'light' | 'dark' | 'system'`): Forces a colour scheme. Without it the dashboard follows the page’s color-scheme.
- **defaultPerson** (`'all' | 'mateo' | 'ava' | 'rosana'`, default `'all'`): The colleague the dashboard opens filtered to.
- **onPersonChange** (`(person: Person) => void`): Called when a colleague is picked (by the reader or the demo), with ‘all’ when the filter is cleared.
- **defaultFilters** (`Filter[]`, default `initialFilters`): The filter chips ({ id, label }) it opens with: country, subsidiary and period by default; [] shows Reset filters. Giving it turns the demo off.
- **defaultNotificationsOpen** (`boolean`, default `false`): Opens with the notifications popover showing; true turns the demo off.
- **className** (`string`): Added to the root, to size or place the dashboard.

## Accessibility

- The sidebar is a labelled navigation whose icon buttons carry their names (shown as tooltips on hover and focus); on the narrow layout the menu button reports aria-expanded and Escape closes the drawer and returns focus.
- Each card is a group named by its title. Charts are focusable with a spoken description of the point in view; arrow keys, Home and End move through the points and Escape leaves.
- Card menus, the grouping pickers and the metric picker follow the menu button pattern: arrow keys open and move, Escape closes and returns focus. Legend toggles and colleague chips are toggle buttons with aria-pressed.
- Removing a filter chip moves focus to the next chip, or to Reset once none is left. The notifications popover is a labelled dialog that Escape closes.
- Changes the reader makes are announced through polite live regions; the demo’s own changes are not. A visually hidden note tells screen reader users about the demo, and the virtual cursor is hidden from assistive technology.
- Reduced motion stops the demo, the heartbeat, the count-ups and every entrance; the dashboard then shows its final state.

## Source

### workforce-dashboard/workforce-dashboard.tsx

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

'use client';

import { useCallback, useState } from 'react';
import { DashboardShell } from './shell';
import { ActivityCards } from './activity-cards';
import { TeamCards } from './team-cards';
import { HoursCards } from './hours-cards';
import { TrendCards } from './trend-cards';
import { DashboardShowcase } from './showcase';
import { initialFilters, type Filter, type Person } from './types';
import styles from './workforce-dashboard.module.css';

export type { Filter, Person } from './types';

/*
 * Workforce dashboard: the analytics home of an invented time-tracking product ("Hourline"). A
 * dark top bar (search with ⌘K, a running time tracker, notifications), an icon sidebar, a
 * filter bar and nine cards: active employees (an area chart), employees by group (a donut), the
 * time bank by month (stacked bars), the total time bank per colleague, a company health score
 * (a beating heart filling with liquid), the monthly employee, the time bank by department, the
 * monthly time bank (a line) and the positive time bank by employee (bars).
 *
 * - Everything is coded: charts in SVG and CSS, icons inline, no images. Every card answers
 *   hover with a tooltip and the keyboard with arrow keys; menus, legends, chips, pickers and
 *   pagers work. Picking a colleague in one card filters the others (shared `person` state).
 * - Wide (the root 880 px and up), the dashboard is one 1526 × 881 design scaled to the root's
 *   width with CSS alone (tan(atan2()) turns 100cqw over 1526px into a number), so it looks the
 *   same at every size. Narrow, a container query reflows it: the top bar wraps, the sidebar
 *   becomes a drawer behind a menu button, the cards stack one or two to a row, each scaled to
 *   its column, and the wide time bank chart scrolls sideways inside its card.
 * - While on screen and untouched, a virtual cursor plays the dashboard: it picks colleagues,
 *   sweeps the area chart, toggles a legend, hovers bars and rows, removes and resets the
 *   filters, then (wide) drags cards into each other's places and back over three cycles. Any
 *   real input hands control to the reader; it resumes five seconds after they leave. Reduced
 *   motion, or autoplay={false}, turns it off.
 * - Light or dark: every colour is a light-dark() token on the root, so the scheme is the
 *   page's color-scheme, or the one the colorScheme prop sets.
 */

export type WorkforceDashboardProps = {
  className?: string;
  /** Plays the virtual-cursor demo while on screen and untouched. */
  autoplay?: boolean;
  /** Forces a colour scheme; without it, the page's color-scheme decides. */
  colorScheme?: 'light' | 'dark' | 'system';
  /** The colleague the dashboard opens filtered to. */
  defaultPerson?: Person;
  /** Called when the reader (or the demo) picks a colleague, 'all' for everyone. */
  onPersonChange?: (person: Person) => void;
  /** The filter chips the dashboard opens with ([] shows Reset); given, the demo stays off. */
  defaultFilters?: Filter[];
  /** Opens with the notifications popover showing; true keeps the demo off. */
  defaultNotificationsOpen?: boolean;
};

export function WorkforceDashboard({
  className,
  autoplay = true,
  colorScheme,
  defaultPerson = 'all',
  onPersonChange,
  defaultFilters,
  defaultNotificationsOpen = false,
}: WorkforceDashboardProps) {
  const [filters, setFilters] = useState(defaultFilters ?? initialFilters);
  // Opening in a given state holds it: the demo would play it away.
  const demo = autoplay && defaultFilters === undefined && !defaultNotificationsOpen;
  const [person, setPerson] = useState<Person>(defaultPerson);
  const resetFilters = useCallback(() => setFilters(initialFilters), []);
  const changePerson = useCallback(
    (next: Person) => {
      setPerson(next);
      onPersonChange?.(next);
    },
    [onPersonChange],
  );
  return (
    <div
      className={`${styles.root} ${className ?? ''}`}
      data-color-scheme={colorScheme}
      data-part="root"
    >
      <div className={styles.viewport}>
        <div className={styles.canvas}>
          <div className={styles.ring}>
            <div className={styles.frame} data-part="frame">
              <DashboardShowcase enabled={demo} onRestoreFilters={resetFilters} />
              <DashboardShell
                filters={filters}
                defaultNotificationsOpen={defaultNotificationsOpen}
                onRemoveFilter={(id) =>
                  setFilters((list) => list.filter((filter) => filter.id !== id))
                }
                onResetFilters={resetFilters}
              />
              <ActivityCards />
              <TeamCards person={person} onPersonChange={changePerson} />
              <HoursCards />
              <TrendCards person={person} onPersonChange={changePerson} />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
```

### workforce-dashboard/workforce-dashboard.module.css

```css
/* Workforce dashboard (workforce-dashboard.tsx): the root, its colour tokens and its two layouts.

   Colours are the tokens below, light-dark(light, dark): 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). Every region's stylesheet uses only these tokens.

   Wide (the viewport 880 px and up): the canvas is the 1548 × 903 design (the 1526 × 881 frame
   and its 11-unit ring) scaled to the root's width by transform, so it never lays out again as
   the page resizes. Narrow: a container query turns the frame into a grid of one or two columns
   and scales each card to its column. */

/* Inter as a variable font (300–700), so 300 and 600 are real weights, not synthesised. */
@font-face {
  font-family: WorkforceInter;
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
  src:
    local('Inter Variable'),
    url('/assets/inter-latin-wght-3100e775.woff2') format('woff2');
}

.root {
  --wf-canvas: #f2f4f7;
  --wf-ring: #fff;
  --wf-surface: #fff;
  --wf-surface-2: #f5f7fa;
  --wf-surface-blue: #f1f6fe;
  --wf-line: #e1e4ea;
  --wf-placeholder: #e5e8ec;
  --wf-grid: #f1f4f9;
  --wf-grid-2: #eef1f6;
  --wf-grid-line: #cfd1d3;
  --wf-grid-line-2: #e4e5e7;
  --wf-track: #e7ebf2;
  --wf-divider: #e9f1ff;
  --wf-ink: #0e121b;
  --wf-ink-2: #525866;
  --wf-ink-3: #525f7f;
  --wf-ink-4: #485471;
  --wf-muted: #99a0ae;
  --wf-muted-2: #7e90ad;
  --wf-muted-3: #a1b2cf;
  --wf-muted-4: #cacfd8;
  --wf-muted-5: #b8c2d3;
  --wf-dots: #6d809b;
  --wf-accent: #335cff;
  --wf-accent-soft: #c0d5ff;
  --wf-accent-wash: #ebf1ff;
  --wf-sky: #1e97f7;
  --wf-sky-strong: #0b7fdc;
  --wf-topbar: #25272f;
  --wf-topbar-field: #363945;
  --wf-tooltip: #0e121b;
  --wf-tooltip-muted: #cacfd8;
  --wf-heart-blush: #ffebec;
  --wf-heart-grid: #717784;
  --wf-pink: #ffc0c5;
  --wf-pink-ink: #b8323f;
  position: relative;
  width: 100%;
  -webkit-tap-highlight-color: transparent;
}
@supports (color: light-dark(#fff, #000)) {
  .root {
    --wf-canvas: light-dark(#f2f4f7, #0e1014);
    --wf-ring: light-dark(#fff, #1b1e25);
    --wf-surface: light-dark(#fff, #171a20);
    --wf-surface-2: light-dark(#f5f7fa, #1f232b);
    --wf-surface-blue: light-dark(#f1f6fe, #1a2130);
    --wf-line: light-dark(#e1e4ea, #2a2f39);
    --wf-placeholder: light-dark(#e5e8ec, #191c23);
    --wf-grid: light-dark(#f1f4f9, #222731);
    --wf-grid-2: light-dark(#eef1f6, #262b35);
    --wf-grid-line: light-dark(#cfd1d3, #3a3f4a);
    --wf-grid-line-2: light-dark(#e4e5e7, #2a2e37);
    --wf-track: light-dark(#e7ebf2, #2b303b);
    --wf-divider: light-dark(#e9f1ff, #2b3446);
    --wf-ink: light-dark(#0e121b, #eef1f6);
    --wf-ink-2: light-dark(#525866, #aab1bf);
    --wf-ink-3: light-dark(#525f7f, #b3bdd3);
    --wf-ink-4: light-dark(#485471, #c0c8da);
    --wf-muted: light-dark(#99a0ae, #7c8494);
    --wf-muted-2: light-dark(#7e90ad, #8593ab);
    --wf-muted-3: light-dark(#a1b2cf, #66738c);
    --wf-muted-4: light-dark(#cacfd8, #4b525f);
    --wf-muted-5: light-dark(#b8c2d3, #5a6375);
    --wf-dots: light-dark(#6d809b, #8090a8);
    --wf-accent: light-dark(#335cff, #6b8aff);
    --wf-accent-soft: light-dark(#c0d5ff, #2a3b66);
    --wf-accent-wash: light-dark(#ebf1ff, #1e2945);
    --wf-sky: light-dark(#1e97f7, #3aa5ff);
    --wf-sky-strong: light-dark(#0b7fdc, #5cb6ff);
    --wf-topbar: light-dark(#25272f, #0b0c10);
    --wf-topbar-field: light-dark(#363945, #1d1f26);
    --wf-tooltip: light-dark(#0e121b, #2c313c);
    --wf-heart-blush: light-dark(#ffebec, #3b2226);
    --wf-heart-grid: light-dark(#717784, #8a91a0);
    --wf-pink: light-dark(#ffc0c5, #4a2a30);
    --wf-pink-ink: light-dark(#b8323f, #ff9aa6);
  }
}
/* The colorScheme prop. Without it, nothing is set: color-scheme is inherited from the page. */
.root[data-color-scheme='light'] {
  color-scheme: light;
}
.root[data-color-scheme='dark'] {
  color-scheme: dark;
}
.root[data-color-scheme='system'] {
  color-scheme: light dark;
}

/* The box the layout answers to (container queries and units). */
.viewport {
  width: 100%;
  container: workforce / inline-size;
}

/* Holds the scaled design's place: its proportions at the viewport's width. */
.canvas {
  position: relative;
  aspect-ratio: 1548 / 903;
}

/* The design with its ring, scaled to the viewport's width. */
.ring {
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: border-box;
  width: 1548px;
  height: 903px;
  padding: 11px;
  border-radius: 26px;
  background: var(--wf-ring);
  transform: scale(tan(atan2(100cqw, 1548px)));
  transform-origin: 0 0;
}

/* The 1526 × 881 design space; every region positions itself inside it. */
.frame {
  position: relative;
  isolation: isolate;
  width: 1526px;
  height: 881px;
  overflow: hidden;
  border-radius: 15px;
  color: var(--wf-ink);
  background: var(--wf-canvas);
  font-family:
    WorkforceInter,
    Inter,
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    Arial,
    sans-serif;
  font-size: 16px;
  font-weight: 400;
  line-height: normal;
  letter-spacing: normal;
  text-align: left;
  -webkit-font-smoothing: antialiased;
}
:where(.frame) *,
:where(.frame) *::before,
:where(.frame) *::after {
  box-sizing: border-box;
}
:where(.frame) :where(p, h1, h2, h3, h4, figure, ul, ol) {
  margin: 0;
  padding: 0;
  font: inherit;
}
:where(.frame) :where(ul, ol) {
  list-style: none;
}
:where(.frame) :where(button) {
  appearance: none;
  margin: 0;
  padding: 0;
  border: 0;
  color: inherit;
  background: transparent;
  font: inherit;
  letter-spacing: inherit;
  text-align: inherit;
  cursor: pointer;
}
:where(.frame) :where(svg) {
  display: block;
  max-width: none;
}

/* ------------------------------------------------------------------ narrow layout */

@container workforce (width < 880px) {
  .canvas {
    aspect-ratio: auto;
  }
  .ring {
    position: relative;
    width: 100%;
    height: auto;
    padding: 6px;
    border-radius: 18px;
    transform: none;
  }
  .frame {
    /* One column under 600 px, two above; each card is scaled to fit a column. */
    --gap: 12px;
    --cols: 1;
    --col: calc(100cqw - 12px - 2 * var(--gap));
    display: grid;
    grid-template-columns: repeat(var(--cols), var(--col));
    align-items: start;
    gap: var(--gap);
    width: auto;
    height: auto;
    padding: 0 var(--gap) var(--gap);
    overflow: clip;
    border-radius: 13px;
  }
  /* Every card: its own design size (--w × --h), scaled to the column. A card wider than 420
     units keeps a 420-unit window and scrolls sideways inside it. */
  .root .frame [data-card] {
    --view: min(var(--w), 420px);
    --s: tan(atan2(var(--col), var(--view)));
    position: relative !important;
    top: auto !important;
    left: auto !important;
    width: var(--view);
    height: var(--h);
    margin: 0 0 calc(var(--h) * (var(--s) - 1));
    transform: scale(var(--s));
    transform-origin: 0 0;
  }
  .root .frame [data-card='active'],
  .root .frame [data-card='groups'] {
    --w: 360.714px;
    --h: 209.533px;
  }
  .root .frame [data-card='bank-total'] {
    --w: 413.292px;
    --h: 309.969px;
  }
  .root .frame [data-card='health'] {
    --w: 244.796px;
    --h: 309.969px;
  }
  .root .frame [data-card='bank'] {
    --w: 729.619px;
    --h: 209.825px;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
  }
  .root .frame [data-card='employee'] {
    --w: 244.796px;
    --h: 316.327px;
  }
  .root .frame [data-card='departments'] {
    --w: 413.292px;
    --h: 317.917px;
  }
  .root .frame [data-card='monthly-bank'],
  .root .frame [data-card='people'] {
    --w: 360.835px;
    --h: 196.314px;
  }
}
@container workforce (600px <= width < 880px) {
  .frame {
    --cols: 2;
    --col: calc((100cqw - 12px - 3 * var(--gap)) / 2);
  }
}
```

### workforce-dashboard/shell.tsx

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

'use client';

import {
  useCallback,
  useEffect,
  useId,
  useRef,
  useState,
  type MouseEvent,
  type RefObject,
} from 'react';
import { initialFilters, PRODUCT, type Filter, type FilterProps } from './types';
import {
  BellIcon,
  CalendarIcon,
  CloseIcon,
  Glyph,
  LogoMark,
  MenuIcon,
  SearchIcon,
  SwitchIcon,
  type GlyphName,
} from './icons';
import styles from './shell.module.css';

/*
 * The dashboard's chrome: the dark top bar (search with ⌘K, a running time tracker, the
 * notifications popover, the account), the icon sidebar with its tooltips, the filter bar's
 * removable chips and the next row's placeholders. On the wide layout every part sits at its
 * place in the 1526×881 design; on the narrow one (a container query in the CSS) the top bar
 * wraps, the sidebar opens as a drawer from a menu button and the filter chips wrap.
 */

const HOVER_ON = [
  'pointerover',
  'pointerenter',
  'pointermove',
  'mouseover',
  'mouseenter',
  'mousemove',
];
const HOVER_OFF = ['pointerleave', 'pointerout', 'mouseleave', 'mouseout'];

/**
 * Ref callback mirroring hover into `data-hover`. CSS :hover ignores events dispatched from script,
 * so this lets the autoplay light up the same states as a real cursor.
 */
function trackHover(node: HTMLElement | null) {
  if (!node) return;
  const show = () => node.toggleAttribute('data-hover', true);
  const hide = (event: Event) => {
    const next = (event as PointerEvent).relatedTarget;
    // `out` also fires when the pointer moves onto a child; only leaving the element counts.
    if (event.type.endsWith('out') && next instanceof Node && node.contains(next)) return;
    node.toggleAttribute('data-hover', false);
  };
  HOVER_ON.forEach((type) => node.addEventListener(type, show));
  HOVER_OFF.forEach((type) => node.addEventListener(type, hide));
  return () => {
    HOVER_ON.forEach((type) => node.removeEventListener(type, show));
    HOVER_OFF.forEach((type) => node.removeEventListener(type, hide));
    node.removeAttribute('data-hover');
  };
}

/** Keeps a ref to the element and tracks its hover. */
function useHoverRef<T extends HTMLElement>() {
  const ref = useRef<T | null>(null);
  const callback = useCallback((node: T | null) => {
    ref.current = node;
    return trackHover(node);
  }, []);
  return [ref, callback] as const;
}

/** 02:10:37, where the tracker's count starts. */
const TRACKED_SECONDS = 2 * 3600 + 10 * 60 + 37;

const navGroups: { id: string; items: { id: string; icon: GlyphName }[] }[] = [
  {
    id: 'main',
    items: [
      { id: 'dashboard', icon: 'grid' },
      { id: 'hour-bank', icon: 'card' },
      { id: 'time-entries', icon: 'arrows' },
      { id: 'history', icon: 'history' },
      { id: 'reports', icon: 'bill' },
      { id: 'adjustments', icon: 'exchange' },
    ],
  },
  {
    id: 'others',
    items: [
      { id: 'settings', icon: 'settings' },
      { id: 'support', icon: 'headphone' },
    ],
  },
];

const COPY = {
  groups: { main: 'Main', others: 'Others' } as Record<string, string>,
  items: {
    dashboard: 'Dashboard',
    'hour-bank': 'Time bank',
    'time-entries': 'Time entries',
    history: 'History',
    reports: 'Reports',
    adjustments: 'Adjustments',
    settings: 'Settings',
    support: 'Support',
  } as Record<string, string>,
  notifications: [
    { id: 'clock-in', who: 'Rosana', text: 'clocked in at 08:02', time: '5 min ago' },
    { id: 'adjustment', who: 'Ava', text: 'requested a time adjustment', time: '20 min ago' },
    { id: 'report', who: 'December', text: 'time bank report is ready', time: 'Yesterday' },
  ],
  search: 'Search',
  searchPlaceholder: 'Clock in',
  task: 'What are you working on?',
  today: 'Today',
  pause: 'Pause timer',
  resume: 'Resume timer',
  bell: 'Notifications',
  unread: 'Notifications, 3 unread',
  caughtUp: 'All caught up',
  markRead: 'Mark all as read',
  sidebar: 'Main navigation',
  menu: 'Menu',
  account: 'Mateo Serrano',
  filters: 'Filters',
  remove: 'Remove filter',
  reset: 'Reset filters',
};

/** Partially visible cards of the next dashboard row (wide layout only). */
const placeholderCards = [
  { id: 'p1', left: 89.017, width: 360.835 },
  { id: 'p2', left: 457.8, width: 360.835 },
  { id: 'p3', left: 828.968, width: 413.292 },
  { id: 'p4', left: 1255.771, width: 243.206 },
];

/**
 * The chip labels' text boxes, in whole pixels, so the chips keep their designed widths
 * (63.4 / 104.4 / 154.4 px) instead of drifting with the font's rounding.
 */
const chipLabelWidths: Record<string, number> = { country: 30, subsidiary: 71, period: 121 };

function formatClock(total: number) {
  const parts = [Math.floor(total / 3600), Math.floor(total / 60) % 60, total % 60];
  return parts.map((part) => String(part).padStart(2, '0')).join(':');
}

/**
 * Top bar, sidebar, filter bar and the next row's placeholders. The DOM (and so the Tab order)
 * follows the visual order; the stylesheet's z-index keeps the top bar above the sidebar.
 */
export function DashboardShell({
  defaultNotificationsOpen = false,
  ...props
}: FilterProps & { defaultNotificationsOpen?: boolean }) {
  const [navOpen, setNavOpen] = useState(false);
  const menuRef = useRef<HTMLButtonElement>(null);
  const sidebarId = useId();
  const closeNav = useCallback((returnFocus: boolean) => {
    setNavOpen(false);
    if (returnFocus) menuRef.current?.focus();
  }, []);
  return (
    <>
      <div className={styles.decor} data-layer="decor" aria-hidden="true">
        {placeholderCards.map((card) => (
          <div
            key={card.id}
            className={styles.placeholder}
            style={{ left: card.left, width: card.width }}
          />
        ))}
      </div>
      <TopBar
        menuRef={menuRef}
        navOpen={navOpen}
        sidebarId={sidebarId}
        defaultNotificationsOpen={defaultNotificationsOpen}
        onMenu={() => setNavOpen((open) => !open)}
      />
      <Sidebar id={sidebarId} open={navOpen} menuRef={menuRef} onClose={closeNav} />
      <FilterBar {...props} />
    </>
  );
}

function TopBar({
  menuRef,
  navOpen,
  sidebarId,
  defaultNotificationsOpen,
  onMenu,
}: {
  menuRef: RefObject<HTMLButtonElement | null>;
  navOpen: boolean;
  sidebarId: string;
  defaultNotificationsOpen: boolean;
  onMenu: () => void;
}) {
  const t = COPY;
  const barRef = useRef<HTMLDivElement>(null);
  const searchRef = useRef<HTMLInputElement>(null);
  const visible = useRef(false);

  // ⌘K / Ctrl+K focuses "Clock in", but only while this dashboard is on screen.
  useEffect(() => {
    const bar = barRef.current;
    if (!bar) return;
    const observer = new IntersectionObserver(([entry]) => {
      visible.current = entry.isIntersecting;
    });
    observer.observe(bar);
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key.toLowerCase() !== 'k' || !(event.metaKey || event.ctrlKey)) return;
      if (event.altKey || event.shiftKey) return;
      const input = searchRef.current;
      if (!input || !visible.current || input.closest('[inert], [aria-hidden="true"]')) return;
      event.preventDefault();
      input.focus();
      input.select();
    };
    window.addEventListener('keydown', onKeyDown);
    return () => {
      observer.disconnect();
      window.removeEventListener('keydown', onKeyDown);
    };
  }, []);

  return (
    <div ref={barRef} className={styles.topBar} data-layer="topbar" data-part="top-bar">
      <button
        ref={menuRef}
        type="button"
        className={styles.menuButton}
        data-part="menu-button"
        aria-label={t.menu}
        aria-expanded={navOpen}
        aria-controls={sidebarId}
        onClick={onMenu}
      >
        <MenuIcon className={styles.menuIcon} />
      </button>
      <span className={styles.logo} role="img" aria-label={PRODUCT} data-part="logo">
        <LogoMark className={styles.logoMark} />
      </span>
      <label className={styles.search} data-part="search">
        <input
          ref={searchRef}
          className={`${styles.field} ${styles.searchField}`}
          type="search"
          placeholder={t.searchPlaceholder}
          aria-label={t.search}
          aria-keyshortcuts="Meta+K Control+K"
          autoComplete="off"
          spellCheck={false}
        />
        <kbd className={styles.shortcut} aria-hidden="true">
          ⌘K
        </kbd>
      </label>
      <TimeTracker />
      <button
        ref={trackHover}
        type="button"
        className={`${styles.iconButton} ${styles.searchButton}`}
        data-demo="search"
        aria-label={t.search}
        onClick={() => searchRef.current?.focus()}
      >
        <SearchIcon className={styles.iconButtonArt} />
      </button>
      <Notifications defaultOpen={defaultNotificationsOpen} />
      <span className={styles.avatar} role="img" aria-label={t.account} data-part="account">
        <span aria-hidden="true">MS</span>
      </span>
    </div>
  );
}

function TimeTracker() {
  const t = COPY;
  const [elapsed, setElapsed] = useState(TRACKED_SECONDS);
  const [running, setRunning] = useState(true);
  const trackerRef = useRef<HTMLDivElement>(null);
  const elapsedRef = useRef(TRACKED_SECONDS);

  useEffect(() => {
    if (!running) return;
    // Measured against the wall clock, so the count is right whenever the display catches up.
    const anchor = Date.now() - elapsedRef.current * 1000;
    const read = () => Math.floor((Date.now() - anchor) / 1000);
    let timer = 0;
    let inView = false;
    // One tick per second, just after each whole second of the count.
    const tick = () => {
      const next = read();
      elapsedRef.current = next;
      setElapsed(next);
      timer = window.setTimeout(tick, 1010 - ((Date.now() - anchor) % 1000));
    };
    // Ticks only while the clock is on screen and the tab is visible.
    const sync = () => {
      const active = inView && !document.hidden;
      if (active && !timer) tick();
      if (!active && timer) {
        window.clearTimeout(timer);
        timer = 0;
      }
    };
    const node = trackerRef.current;
    let observer: IntersectionObserver | null = null;
    if (node && typeof IntersectionObserver !== 'undefined') {
      observer = new IntersectionObserver(([entry]) => {
        inView = !!entry?.isIntersecting;
        sync();
      });
      observer.observe(node);
    } else {
      inView = true;
    }
    document.addEventListener('visibilitychange', sync);
    sync();
    return () => {
      observer?.disconnect();
      document.removeEventListener('visibilitychange', sync);
      window.clearTimeout(timer);
      // Paused: resuming continues from the count at this moment.
      elapsedRef.current = read();
    };
  }, [running]);

  return (
    <div ref={trackerRef} className={styles.tracker} data-running={running} data-part="tracker">
      <label className={styles.task}>
        <SwitchIcon className={styles.taskIcon} />
        <input
          className={`${styles.field} ${styles.taskField}`}
          type="text"
          placeholder={t.task}
          aria-label={t.task}
          autoComplete="off"
        />
      </label>
      <div className={styles.clock}>
        <CalendarIcon className={styles.calendar} />
        <span className={styles.today}>{t.today}</span>
        <span className={styles.time} role="timer">
          {formatClock(elapsed)}
        </span>
      </div>
      <button
        ref={trackHover}
        type="button"
        className={styles.toggle}
        data-demo="timer-toggle"
        aria-label={t.pause}
        aria-pressed={!running}
        title={running ? t.pause : t.resume}
        onClick={() => setRunning((value) => !value)}
      >
        {running ? (
          <span className={styles.stopIcon} />
        ) : (
          <svg className={styles.playIcon} viewBox="0 0 10 10" aria-hidden="true">
            <path d="M2.4 1.3v7.4a.5.5 0 0 0 .76.43l6-3.7a.5.5 0 0 0 0-.86l-6-3.7a.5.5 0 0 0-.76.43Z" />
          </svg>
        )}
      </button>
    </div>
  );
}

function Notifications({ defaultOpen = false }: { defaultOpen?: boolean }) {
  const t = COPY;
  const [open, setOpen] = useState(defaultOpen);
  const [read, setRead] = useState(false);
  const wrapRef = useRef<HTMLDivElement>(null);
  const [buttonRef, bellRef] = useHoverRef<HTMLButtonElement>();
  const panelId = useId();

  useEffect(() => {
    if (!open) return;
    const onPointerDown = (event: PointerEvent) => {
      if (!wrapRef.current?.contains(event.target as Node)) setOpen(false);
    };
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key !== 'Escape') return;
      setOpen(false);
      buttonRef.current?.focus();
    };
    document.addEventListener('pointerdown', onPointerDown);
    document.addEventListener('keydown', onKeyDown);
    return () => {
      document.removeEventListener('pointerdown', onPointerDown);
      document.removeEventListener('keydown', onKeyDown);
    };
  }, [open, buttonRef]);

  return (
    <div
      ref={wrapRef}
      className={styles.bell}
      data-part="notifications"
      onBlur={(event) => {
        const next = event.relatedTarget as Node | null;
        if (next && !event.currentTarget.contains(next)) setOpen(false);
      }}
    >
      <button
        ref={bellRef}
        type="button"
        className={styles.iconButton}
        data-demo="bell"
        data-read={read}
        aria-label={read ? t.bell : t.unread}
        aria-haspopup="dialog"
        aria-expanded={open}
        aria-controls={panelId}
        onClick={() => setOpen((value) => !value)}
      >
        <BellIcon className={styles.iconButtonArt} />
        {/* Covers the unread dot once everything has been read. */}
        <span className={styles.dotCover} aria-hidden="true" />
      </button>
      <div
        id={panelId}
        className={styles.popover}
        role="dialog"
        aria-label={t.bell}
        hidden={!open}
        data-part="notifications-panel"
      >
        <div className={styles.popoverHead}>
          <p className={styles.popoverTitle}>{t.bell}</p>
          <button
            ref={trackHover}
            type="button"
            className={styles.markRead}
            data-demo="notifications-read"
            disabled={read}
            onClick={() => setRead(true)}
          >
            {read ? t.caughtUp : t.markRead}
          </button>
        </div>
        <ul className={styles.notices}>
          {t.notifications.map((notice) => (
            <li key={notice.id} className={styles.notice} data-unread={!read}>
              <span className={styles.noticeDot} aria-hidden="true" />
              <p className={styles.noticeText}>
                <strong>{notice.who}</strong> {notice.text}
              </p>
              <p className={styles.noticeTime}>{notice.time}</p>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

function Sidebar({
  id,
  open,
  menuRef,
  onClose,
}: {
  id: string;
  open: boolean;
  menuRef: RefObject<HTMLButtonElement | null>;
  onClose: (returnFocus: boolean) => void;
}) {
  const t = COPY;
  const [active, setActive] = useState('dashboard');
  const [changed, setChanged] = useState(false);
  const ref = useRef<HTMLElement>(null);

  // As a drawer (narrow layout): Escape or a press outside closes it.
  useEffect(() => {
    if (!open) return;
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape') onClose(true);
    };
    const onPointerDown = (event: PointerEvent) => {
      const target = event.target as Node;
      if (ref.current?.contains(target) || menuRef.current?.contains(target)) return;
      onClose(false);
    };
    document.addEventListener('keydown', onKeyDown);
    document.addEventListener('pointerdown', onPointerDown);
    return () => {
      document.removeEventListener('keydown', onKeyDown);
      document.removeEventListener('pointerdown', onPointerDown);
    };
  }, [open, onClose, menuRef]);

  return (
    <nav
      ref={ref}
      id={id}
      className={styles.sidebar}
      aria-label={t.sidebar}
      data-layer="sidebar"
      data-part="sidebar"
      data-open={open || undefined}
    >
      {navGroups.map((group) => (
        <div key={group.id} className={styles.navGroup}>
          <p className={styles.divider}>{t.groups[group.id]}</p>
          <ul className={styles.navList}>
            {group.items.map((item) => {
              const current = item.id === active;
              return (
                <li key={item.id}>
                  <button
                    ref={trackHover}
                    type="button"
                    className={styles.navItem}
                    data-demo={`nav-${item.id}`}
                    aria-label={t.items[item.id]}
                    aria-current={current ? 'page' : undefined}
                    onClick={() => {
                      setActive(item.id);
                      setChanged(true);
                      if (open) onClose(true);
                    }}
                  >
                    <Glyph name={item.icon} className={styles.navIcon} />
                    {current && (
                      <span className={styles.indicator} data-animate={changed || undefined} />
                    )}
                    <span className={styles.tooltip} aria-hidden="true">
                      {t.items[item.id]}
                    </span>
                    <span className={styles.navLabel} aria-hidden="true">
                      {t.items[item.id]}
                    </span>
                  </button>
                </li>
              );
            })}
          </ul>
        </div>
      ))}
    </nav>
  );
}

function FilterBar({ filters, onRemoveFilter, onResetFilters }: FilterProps) {
  const t = COPY;
  const [ownFilters, setOwnFilters] = useState<Filter[]>(initialFilters);
  const [restored, setRestored] = useState(false);
  const list = filters ?? ownFilters;
  const titleId = useId();
  const chipsRef = useRef<HTMLUListElement>(null);
  const [resetRef, resetHoverRef] = useHoverRef<HTMLButtonElement>();
  // Where keyboard focus goes once the chip it was on disappears.
  const pendingFocus = useRef<string | null>(null);

  useEffect(() => {
    const target = pendingFocus.current;
    if (target === null) return;
    pendingFocus.current = null;
    if (target === 'reset') {
      resetRef.current?.focus();
      return;
    }
    const buttons = chipsRef.current?.querySelectorAll<HTMLButtonElement>('[data-filter]') ?? [];
    const match = Array.from(buttons).find((button) => button.dataset.filter === target);
    (match ?? buttons[0])?.focus();
  }, [list, resetRef]);

  const remove = (id: string, event: MouseEvent<HTMLButtonElement>) => {
    // Only the reader's own press moves focus; the autoplay clicks from script.
    if (event.isTrusted && event.currentTarget === document.activeElement) {
      const index = list.findIndex((filter) => filter.id === id);
      const next = list[index + 1] ?? list[index - 1];
      pendingFocus.current = next ? next.id : 'reset';
    }
    setRestored(false);
    onRemoveFilter?.(id);
    if (filters === undefined) setOwnFilters((current) => current.filter((f) => f.id !== id));
  };

  const reset = () => {
    // The autoplay never has focus here while it plays; when it stops, it presses a Reset the
    // reader focused (instead of restoring the chips under it), so focus moves on here too.
    if (document.activeElement === resetRef.current) pendingFocus.current = 'first';
    setRestored(true);
    onResetFilters?.();
    if (filters === undefined) setOwnFilters(initialFilters);
  };

  return (
    <div className={styles.filters} data-layer="filters" data-part="filters">
      <p id={titleId} className={styles.filtersTitle}>
        {t.filters}
      </p>
      {list.length > 0 ? (
        <ul ref={chipsRef} className={styles.chips} aria-labelledby={titleId}>
          {list.map((filter, index) => (
            <li
              key={filter.id}
              className={styles.chip}
              data-part="filter-chip"
              data-restored={restored || undefined}
              style={restored ? { animationDelay: `${index * 45}ms` } : undefined}
            >
              <span style={{ minWidth: chipLabelWidths[filter.id] }}>{filter.label}</span>
              <button
                ref={trackHover}
                type="button"
                className={styles.chipRemove}
                data-filter={filter.id}
                data-demo={`filter-remove-${filter.id}`}
                aria-label={`${t.remove}: ${filter.label}`}
                onClick={(event) => remove(filter.id, event)}
              >
                <CloseIcon className={styles.chipRemoveIcon} />
              </button>
            </li>
          ))}
        </ul>
      ) : (
        <div className={styles.chips}>
          <button
            ref={resetHoverRef}
            type="button"
            className={styles.reset}
            data-demo="filter-reset"
            onClick={reset}
          >
            {t.reset}
          </button>
        </div>
      )}
    </div>
  );
}
```

### workforce-dashboard/shell.module.css

```css
/*
 * Dashboard chrome (shell.tsx): top bar, sidebar, filter bar and the next row's placeholder
 * cards. On the wide layout every left/top is relative to the 1526×881 frame; the narrow layout
 * (the container query at the end) puts them in the frame's grid.
 *
 * Hover states match both :hover and [data-hover]; the latter is set from pointer/mouse
 * events (see trackHover) so script-dispatched events show them too.
 */

/* ---------- Top bar ---------- */
.topBar {
  position: absolute;
  /* Above the sidebar (20), which now follows it in the DOM. */
  z-index: 21;
  top: 0;
  left: 0;
  width: 1526px;
  height: 58.02px;
  color: color(display-p3 0.565 0.612 0.694);
  background: var(--wf-topbar);
}
.topBar :focus-visible {
  outline: 1.59px solid color(display-p3 0.592 0.729 1);
  outline-offset: 1.59px;
}
.logo {
  position: absolute;
  top: 13.574px;
  left: 25.566px;
  width: 23.27px;
  height: 31.027px;
  color: #fff;
}
.logoMark {
  width: 100%;
  height: 100%;
}
/* Only on the narrow layout, where the sidebar is a drawer. */
.menuButton {
  display: none;
}
.search,
.tracker {
  position: absolute;
  top: 12.717px;
  display: flex;
  align-items: center;
  height: 31.792px;
  padding: 3.179px 9.537px 3.179px 8.743px;
  background: var(--wf-topbar-field);
  cursor: text;
  transition: box-shadow 0.18s ease;
}
.search:focus-within,
.tracker:has(.field:focus) {
  box-shadow: 0 0 0 1.59px color(display-p3 0.592 0.729 1);
}
.search {
  left: 67.557px;
  width: 131.935px;
  border-radius: 37.355px;
}
.field {
  min-width: 0;
  height: 16px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  color: #fff;
  background: transparent;
  font: inherit;
  font-size: 11.127px;
  font-weight: 600;
  /* the design's 15.896px line sits in a 16px text box; Blink floors half-leading, so 15.896px would
     lift the glyphs ~1px. 16px keeps the design baseline (text top + 12px). */
  line-height: 16px;
  appearance: none;
}
.topBar .field:focus-visible {
  outline: none;
}
.field::placeholder {
  color: color(display-p3 0.565 0.612 0.694);
  opacity: 1;
}
.field::-webkit-search-decoration,
.field::-webkit-search-cancel-button {
  appearance: none;
}
.searchField {
  flex: none;
  width: 91.401px;
  padding-left: 2.3px;
}
.shortcut {
  flex: none;
  width: 20.665px;
  color: color(display-p3 0.565 0.612 0.694);
  font: inherit;
  font-size: 11.127px;
  font-weight: 600;
  line-height: 16px;
  text-align: right;
}

.tracker {
  left: 473.696px;
  width: 578.608px;
  gap: 9.537px;
  border-radius: 7947.917px;
}
.task {
  display: flex;
  flex: none;
  align-items: center;
  gap: 12.717px;
  /* 8px narrower than the design's 404.549px, handed to the clock so "Today" fits its label box. */
  width: 396.549px;
  cursor: text;
}
.taskIcon {
  flex: none;
  color: color(display-p3 0.565 0.612 0.694);
  width: 15.896px;
  height: 15.896px;
}
.taskField {
  flex: 1;
}
.clock {
  display: flex;
  flex: none;
  align-items: center;
  gap: 12.717px;
  width: 125.629px;
  cursor: default;
  font-size: 11.127px;
  font-weight: 600;
  line-height: 16px;
  white-space: nowrap;
}
.calendar {
  flex: none;
  color: color(display-p3 0.565 0.612 0.694);
  width: 14.306px;
  height: 14.306px;
}
.today {
  flex: none;
  width: 33px;
}
.time {
  color: #fff;
  font-weight: 700;
  transition: color 0.2s ease;
}
.tracker[data-running='false'] .time {
  color: color(display-p3 0.565 0.612 0.694);
}
.toggle {
  display: grid;
  flex: none;
  place-items: center;
  width: 23.844px;
  height: 23.844px;
  border-radius: 11.922px;
  background: color(display-p3 0.925 0.392 0.369);
  transition:
    background-color 0.2s ease,
    transform 0.15s ease;
}
.toggle:hover,
.toggle[data-hover] {
  background: color(display-p3 0.941 0.471 0.435);
}
.toggle:active {
  transform: scale(0.9);
}
.toggle[aria-pressed='true'] {
  background: color(display-p3 0.122 0.757 0.42);
}
.toggle[aria-pressed='true']:hover,
.toggle[aria-pressed='true'][data-hover] {
  background: color(display-p3 0.176 0.816 0.478);
}
.stopIcon {
  width: 9.743px;
  height: 9.743px;
  border-radius: 1.538px;
  background: var(--wf-surface);
  animation: iconIn 0.2s ease-out;
}
.playIcon {
  width: 10px;
  height: 10px;
  margin-left: 1px;
  fill: var(--wf-surface);
  animation: iconIn 0.2s ease-out;
}

.iconButton {
  position: relative;
  width: 31.792px;
  height: 31.792px;
  border-radius: 50%;
}
.iconButton > * {
  transition:
    filter 0.15s ease,
    opacity 0.2s ease,
    transform 0.2s ease;
}
.iconButtonArt {
  width: 31.792px;
  height: 31.792px;
}
.iconButton:hover > *,
.iconButton[data-hover] > *,
.iconButton[aria-expanded='true'] > * {
  filter: brightness(1.25);
}
/* Button-coloured disc over the exported unread dot (cx 20.873, cy 11.127, r 1.59). */
.dotCover {
  position: absolute;
  top: 9.127px;
  left: 18.873px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--wf-topbar-field);
  opacity: 0;
  transform: scale(0);
}
.iconButton[data-read='true'] .dotCover {
  opacity: 1;
  transform: none;
}
.searchButton {
  position: absolute;
  top: 13.512px;
  left: 1373.4px;
}
.bell {
  position: absolute;
  top: 13.512px;
  left: 1414.729px;
  width: 31.792px;
  height: 31.792px;
}
.avatar {
  position: absolute;
  top: 9.538px;
  left: 1462.417px;
  width: 38.15px;
  height: 38.15px;
  overflow: hidden;
  border-radius: 50%;
  background: var(--wf-accent-soft);
}
.avatar {
  display: grid;
  place-items: center;
  color: var(--wf-accent);
  font-size: 12.717px;
  font-weight: 600;
  letter-spacing: -0.02em;
}

/* Notification popover, anchored under the bell. */
.popover {
  position: absolute;
  top: calc(100% + 11px);
  right: -8px;
  width: 268px;
  padding: 11.127px;
  border-radius: 11.127px;
  color: var(--wf-ink);
  background: var(--wf-surface);
  box-shadow:
    0 0 0 0.795px var(--wf-line),
    0 18px 36px -14px color(display-p3 0.055 0.071 0.106 / 0.24);
  cursor: default;
  transform-origin: calc(100% - 24px) 0;
  animation: popoverIn 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.popover[hidden] {
  display: none;
}
.popover::before {
  position: absolute;
  top: -5px;
  right: 18.9px;
  width: 10px;
  height: 10px;
  border-radius: 2px 0 0;
  background: var(--wf-surface);
  box-shadow: -0.795px -0.795px 0 var(--wf-line);
  content: '';
  transform: rotate(45deg);
}
.popoverHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 1.59px 6.358px 4.769px;
}
.popoverTitle {
  font-size: 12.717px;
  font-weight: 600;
  line-height: 15.896px;
}
.markRead {
  padding: 1.59px 3.179px;
  border-radius: 4px;
  color: var(--wf-accent);
  font-size: 11.127px;
  font-weight: 500;
  line-height: 15.896px;
}
.markRead:hover:not(:disabled),
.markRead[data-hover]:not(:disabled) {
  background: var(--wf-surface-2);
}
.markRead:disabled {
  color: var(--wf-muted);
  cursor: default;
}
.notices {
  display: grid;
  gap: 1.59px;
}
.notice {
  display: grid;
  grid-template-columns: 6.358px 1fr;
  gap: 0 9.537px;
  padding: 7.948px;
  border-radius: 7.948px;
  transition: background-color 0.2s ease;
}
.notice[data-unread='true'] {
  background: var(--wf-surface-2);
}
.noticeDot {
  width: 6.358px;
  height: 6.358px;
  margin-top: 4.769px;
  border-radius: 50%;
  background: var(--wf-accent);
  transition:
    opacity 0.2s ease,
    transform 0.2s ease;
}
.notice[data-unread='false'] .noticeDot {
  opacity: 0;
  transform: scale(0);
}
.noticeText {
  color: var(--wf-ink-2);
  font-size: 11.127px;
  font-weight: 500;
  line-height: 15.896px;
}
.noticeText strong {
  color: var(--wf-ink);
  font-weight: 600;
}
.noticeTime {
  grid-column: 2;
  color: var(--wf-muted);
  font-size: 9.537px;
  line-height: 12.717px;
}

/* ---------- Sidebar ---------- */
.sidebar {
  position: absolute;
  z-index: 20;
  top: 58.02px;
  left: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 15.896px;
  width: 63.583px;
  height: 822.609px;
  padding: 15.896px 9.537px 12.717px;
  background: var(--wf-surface);
  box-shadow: inset -0.795px 0 0 var(--wf-line);
}
.navGroup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6.358px;
  width: 100%;
}
.navGroup + .navGroup {
  gap: 4.769px;
}
/* the design's 12.717px line sits in a 13px text box (divider is 19.358px tall); 13px keeps
   its baseline in Blink, which floors half-leading. */
.divider {
  width: 100%;
  padding: 3.179px 0;
  color: var(--wf-muted);
  font-size: 9.537px;
  font-weight: 500;
  line-height: 13px;
  letter-spacing: 0.3815px;
  text-align: center;
  text-transform: uppercase;
  font-feature-settings:
    'cv09' 1,
    'ss11' 1,
    'calt' 0,
    'liga' 0;
}
.navList {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3.179px;
}
.navItem {
  position: relative;
  display: flex;
  padding: 6.358px;
  border-radius: 6.416px;
  color: var(--wf-ink-2);
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}
.navItem:hover,
.navItem[data-hover] {
  color: var(--wf-ink);
  background: var(--wf-surface-2);
}
.navItem[aria-current='page'] {
  color: var(--wf-accent);
  background: var(--wf-surface-2);
}
.navItem:focus-visible {
  outline: 1.59px solid var(--wf-accent);
  outline-offset: 1.59px;
}
.navIcon {
  display: block;
  flex: none;
  width: 15.896px;
  height: 15.896px;
}
/* Labels show only in the narrow layout's drawer. */
.navLabel {
  display: none;
}
.indicator {
  position: absolute;
  top: 6.36px;
  left: -17.49px;
  width: 3.179px;
  height: 15.896px;
  border-radius: 0 3.179px 3.179px 0;
  background: var(--wf-accent);
  transform-origin: left center;
}
.indicator[data-animate] {
  animation: indicatorIn 0.28s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.tooltip {
  position: absolute;
  z-index: 1;
  top: 50%;
  left: calc(100% + 9.537px);
  padding: 3.179px 7.948px;
  border-radius: 6.358px;
  color: #fff;
  background: var(--wf-topbar);
  font-size: 11.127px;
  font-weight: 500;
  line-height: 15.896px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transform: translate(-3px, -50%);
  transition:
    opacity 0.15s ease,
    transform 0.15s ease;
}
.tooltip::before {
  position: absolute;
  top: 50%;
  left: -2.5px;
  width: 6px;
  height: 6px;
  border-radius: 1px;
  background: inherit;
  content: '';
  transform: translateY(-50%) rotate(45deg);
}
.navItem:hover .tooltip,
.navItem[data-hover] .tooltip,
.navItem:focus-visible .tooltip {
  opacity: 1;
  transform: translate(0, -50%);
}

/* ---------- Filter bar ---------- */
/* The bar's box with a hairline edge; title and chips sit inside its border. */
.filters {
  position: absolute;
  top: 76.763px;
  left: 88.685px;
  width: 1412.21px;
  height: 48.351px;
  border: 0.663px solid var(--wf-line);
  border-radius: 2.45px;
  background: var(--wf-surface);
}
.filtersTitle {
  position: absolute;
  top: 15.565px;
  left: 12.404px;
  color: var(--wf-ink-3);
  font-size: 13.262px;
  font-weight: 500;
  line-height: normal;
  white-space: nowrap;
}
.chips {
  position: absolute;
  top: 10.796px;
  left: 65.636px;
  display: flex;
  align-items: center;
  gap: 9.537px;
  height: 25.538px;
}
.chip,
.reset {
  display: flex;
  align-items: center;
  height: 25.538px;
  border-radius: 793.997px;
  background: var(--wf-surface-2);
  box-shadow: inset 0 0 0 0.795px var(--wf-line);
  color: var(--wf-ink-3);
  font-size: 11.127px;
  font-weight: 500;
  line-height: 16px;
  letter-spacing: -0.0668px;
  white-space: nowrap;
  font-feature-settings:
    'cv09' 1,
    'ss11' 1,
    'calt' 0,
    'liga' 0;
}
.chip {
  gap: 4.769px;
  padding: 4.769px 6.358px 4.769px 9.537px;
}
.chip[data-restored] {
  animation: chipIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
.chipRemove {
  display: grid;
  place-items: center;
  padding: 1.59px;
  border-radius: 3px;
  transition: background-color 0.15s ease;
}
.chipRemoveIcon {
  width: 9.538px;
  height: 9.538px;
  color: var(--wf-muted);
}
.chipRemove:hover,
.chipRemove[data-hover] {
  background: var(--wf-line);
}
.reset {
  padding: 4.769px 9.537px;
  color: var(--wf-accent);
  animation: chipIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  transition: background-color 0.15s ease;
}
.reset:hover,
.reset[data-hover] {
  background: var(--wf-accent-wash);
}
.chipRemove:focus-visible,
.reset:focus-visible {
  outline: 1.59px solid var(--wf-accent);
  outline-offset: 1.59px;
}

/* ---------- Decorative layer ---------- */
.decor {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.placeholder {
  position: absolute;
  top: 787.639px;
  height: 147.831px;
  background: var(--wf-placeholder);
}

@keyframes iconIn {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
}
@keyframes popoverIn {
  from {
    opacity: 0;
    transform: translateY(-4px) scale(0.97);
  }
}
@keyframes indicatorIn {
  from {
    opacity: 0;
    transform: scaleY(0);
  }
}
@keyframes chipIn {
  from {
    opacity: 0;
    transform: translateY(3px) scale(0.94);
  }
}

@media (prefers-reduced-motion: reduce) {
  .search,
  .tracker,
  .time,
  .toggle,
  .iconButton > *,
  .notice,
  .noticeDot,
  .navItem,
  .tooltip,
  .chipRemove,
  .reset {
    transition: none;
  }
  .stopIcon,
  .playIcon,
  .popover,
  .indicator[data-animate],
  .chip[data-restored],
  .reset {
    animation: none;
  }
}

/* ---------- Narrow layout ---------- */
/* Two rows of top bar (menu, logo, notifications, account; then search and the tracker), the
   sidebar as a drawer under it, and the filter chips wrapping in their box. The placeholders of
   the next row are dropped. */
@container workforce (width < 880px) {
  .decor {
    display: none;
  }
  .topBar {
    position: relative;
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    width: auto;
    height: 92px;
    margin: 0 calc(-1 * var(--gap, 12px));
    padding: 10px 12px;
  }
  .topBar > .logo,
  .topBar > .search,
  .topBar > .tracker,
  .topBar > .bell,
  .topBar > .avatar {
    position: relative;
    top: auto;
    left: auto;
  }
  .menuButton {
    display: grid;
    flex: none;
    place-items: center;
    width: 31.792px;
    height: 31.792px;
    border-radius: 50%;
    color: #fff;
    background: var(--wf-topbar-field);
    transition: filter 0.15s ease;
  }
  .menuButton:hover,
  .menuButton[aria-expanded='true'] {
    filter: brightness(1.25);
  }
  .menuIcon {
    width: 16px;
    height: 16px;
  }
  .logo {
    flex: none;
    margin-right: auto;
  }
  .searchButton {
    display: none;
  }
  .bell {
    position: static;
    flex: none;
  }
  .avatar {
    flex: none;
  }
  .search {
    order: 1;
    /* Too wide to share the first row, so it opens the second one, beside the tracker. */
    flex: 1 1 calc(100% - 140px);
    min-width: 0;
    width: auto;
  }
  .searchField {
    flex: 1 1 auto;
    width: auto;
  }
  .tracker {
    order: 2;
    flex: none;
    width: auto;
    gap: 8px;
  }
  .task,
  .calendar,
  .today {
    display: none;
  }
  .clock {
    width: auto;
  }
  .popover {
    top: 50px;
    right: 8px;
  }
  .popover::before {
    right: 61px;
  }

  .sidebar {
    top: 92px;
    left: 0;
    z-index: 30;
    align-items: stretch;
    width: 196px;
    height: auto;
    padding: 12px 10px 14px;
    border-radius: 0 12px 12px 0;
    box-shadow:
      inset -0.795px 0 0 var(--wf-line),
      0 18px 36px -12px rgb(14 18 27 / 0.28);
    transform: translateX(-104%);
    visibility: hidden;
    transition:
      transform 0.26s cubic-bezier(0.2, 0.8, 0.2, 1),
      visibility 0s linear 0.26s;
  }
  .sidebar[data-open] {
    transform: none;
    visibility: visible;
    transition: transform 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  .navGroup,
  .navList {
    align-items: stretch;
  }
  .divider {
    padding-left: 8px;
    text-align: left;
  }
  .navItem {
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px;
  }
  .navLabel {
    display: block;
    font-size: 12.717px;
    font-weight: 500;
    line-height: 16px;
  }
  .tooltip {
    display: none;
  }
  .indicator {
    top: 10.4px;
    left: -10px;
  }

  .filters {
    position: relative;
    top: auto;
    left: auto;
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    width: auto;
    height: auto;
    min-height: 48.351px;
    padding: 10px 12px;
  }
  .filtersTitle,
  .chips {
    position: static;
  }
  .chips {
    flex-wrap: wrap;
    height: auto;
    gap: 6px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .sidebar,
  .sidebar[data-open] {
    transition: none;
  }
}
```

### workforce-dashboard/activity-cards.tsx

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

'use client';

import {
  useEffect,
  useId,
  useLayoutEffect,
  useRef,
  useState,
  type CSSProperties,
  type FocusEvent,
  type KeyboardEvent,
  type ReactNode,
  type RefObject,
} from 'react';
import { CornerArrow, CornerGrip, DotsIcon, TooltipTail } from './icons';
import styles from './activity-cards.module.css';

/* ------------------------------------------------------------------------------------------------
 * Shared helpers
 * ---------------------------------------------------------------------------------------------- */

type Phase = 'static' | 'armed' | 'played';

/**
 * Entrance state for a card. The server (and reduced motion) renders the final state; on the client
 * the card is armed before paint and plays once it scrolls into view.
 */
function useEntrance<T extends Element>() {
  const ref = useRef<T>(null);
  const [phase, setPhase] = useState<Phase>('static');
  useLayoutEffect(() => {
    const node = ref.current;
    if (!node || typeof IntersectionObserver === 'undefined') return;
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    setPhase('armed');
    const observer = new IntersectionObserver(
      (entries) => {
        if (!entries.some((entry) => entry.isIntersecting)) return;
        setPhase('played');
        observer.disconnect();
      },
      { threshold: 0.35 },
    );
    observer.observe(node);
    return () => observer.disconnect();
  }, []);
  return [ref, phase] as const;
}

const SHOW_EVENTS = [
  'pointerover',
  'pointerenter',
  'pointermove',
  'mouseover',
  'mouseenter',
  'mousemove',
] as const;
const HIDE_EVENTS = ['pointerleave', 'pointerout', 'mouseleave', 'mouseout'] as const;

/**
 * Hover wiring that also answers synthetic events (the autoplay director dispatches them at a
 * target's client coordinates) and non-bubbling enter/leave events, by listening natively in the
 * capture phase. Leaving only counts when the pointer does not move to somewhere else inside.
 */
function useHover<T extends Element>(
  ref: RefObject<T | null>,
  onShow: (event: MouseEvent) => void,
  onHide: () => void,
) {
  const latest = useRef({ onShow, onHide });
  useLayoutEffect(() => {
    latest.current = { onShow, onHide };
  });
  useEffect(() => {
    const node = ref.current;
    if (!node) return;
    const show = (event: Event) => latest.current.onShow(event as MouseEvent);
    const hide = (event: Event) => {
      const next = (event as MouseEvent).relatedTarget;
      if (next instanceof Node && node.contains(next)) return;
      latest.current.onHide();
    };
    for (const type of SHOW_EVENTS) node.addEventListener(type, show, true);
    for (const type of HIDE_EVENTS) node.addEventListener(type, hide, true);
    return () => {
      for (const type of SHOW_EVENTS) node.removeEventListener(type, show, true);
      for (const type of HIDE_EVENTS) node.removeEventListener(type, hide, true);
    };
  }, [ref]);
}

/** Position of a client point in an element's own (unscaled) units, whatever CSS scale is applied. */
function localPoint(element: Element, event: MouseEvent, width: number, height: number) {
  const rect = element.getBoundingClientRect();
  if (!rect.width || !rect.height) return null;
  const inside =
    event.clientX >= rect.left &&
    event.clientX <= rect.right &&
    event.clientY >= rect.top &&
    event.clientY <= rect.bottom;
  if (!inside) return null;
  return {
    x: ((event.clientX - rect.left) / rect.width) * width,
    y: ((event.clientY - rect.top) / rect.height) * height,
  };
}

type MenuOption = {
  id: string;
  label: string;
  kind?: 'action' | 'check' | 'radio';
  checked?: boolean;
  onSelect: () => void;
};

/** A button that opens a small menu (WAI-ARIA menu button pattern). */
function MenuButton({
  className,
  triggerClassName,
  ariaLabel,
  menuLabel,
  options,
  children,
  demo,
}: {
  className: string;
  triggerClassName: string;
  ariaLabel?: string;
  menuLabel: string;
  options: MenuOption[];
  children: ReactNode;
  /** data-demo name of the trigger; items get `${demo}-${option.id}`. */
  demo?: string;
}) {
  const [open, setOpen] = useState(false);
  const wrapperRef = useRef<HTMLDivElement>(null);
  const triggerRef = useRef<HTMLButtonElement>(null);
  const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);
  const focusIndex = useRef(0);
  const menuId = useId();

  useEffect(() => {
    if (!open) return;
    itemRefs.current[focusIndex.current]?.focus();
    // Close on any press or click elsewhere (synthetic clicks included), except a scripted click
    // while the reader's focus is in the menu: unmounting it then would drop that focus.
    const onOutside = (event: Event) => {
      if (wrapperRef.current?.contains(event.target as Node)) return;
      const focused = document.activeElement as HTMLButtonElement | null;
      if (!event.isTrusted && focused && itemRefs.current.includes(focused)) return;
      setOpen(false);
    };
    document.addEventListener('pointerdown', onOutside);
    document.addEventListener('click', onOutside);
    return () => {
      document.removeEventListener('pointerdown', onOutside);
      document.removeEventListener('click', onOutside);
    };
  }, [open]);

  const openAt = (index: number) => {
    const checked = options.findIndex((option) => option.kind === 'radio' && option.checked);
    focusIndex.current =
      index < 0 ? options.length - 1 : checked >= 0 && index === 0 ? checked : index;
    setOpen(true);
  };
  const close = (returnFocus: boolean) => {
    setOpen(false);
    if (returnFocus) triggerRef.current?.focus();
  };
  const move = (index: number) => {
    const count = options.length;
    itemRefs.current[(index + count) % count]?.focus();
  };

  const onTriggerKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
    if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
    event.preventDefault();
    openAt(event.key === 'ArrowDown' ? 0 : -1);
  };
  const onMenuKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
    const current = itemRefs.current.indexOf(document.activeElement as HTMLButtonElement);
    if (event.key === 'ArrowDown') move(current + 1);
    else if (event.key === 'ArrowUp') move(current - 1);
    else if (event.key === 'Home') move(0);
    else if (event.key === 'End') move(options.length - 1);
    else if (event.key === 'Escape') close(true);
    else if (event.key === 'Tab') close(false);
    else return;
    if (event.key !== 'Tab') event.preventDefault();
  };

  return (
    <div ref={wrapperRef} className={className} data-open={open || undefined}>
      <button
        ref={triggerRef}
        type="button"
        className={triggerClassName}
        aria-label={ariaLabel}
        aria-haspopup="menu"
        aria-expanded={open}
        aria-controls={open ? menuId : undefined}
        data-demo={demo}
        onClick={() => (open ? close(false) : openAt(0))}
        onKeyDown={onTriggerKeyDown}
      >
        {children}
      </button>
      {open ? (
        <div
          id={menuId}
          role="menu"
          aria-label={menuLabel}
          className={styles.menu}
          onKeyDown={onMenuKeyDown}
        >
          {options.map((option, index) => (
            <button
              key={option.id}
              ref={(node) => {
                itemRefs.current[index] = node;
              }}
              type="button"
              role={
                option.kind === 'check'
                  ? 'menuitemcheckbox'
                  : option.kind === 'radio'
                    ? 'menuitemradio'
                    : 'menuitem'
              }
              aria-checked={option.kind && option.kind !== 'action' ? !!option.checked : undefined}
              tabIndex={-1}
              className={styles.menuItem}
              data-demo={demo ? `${demo}-${option.id}` : undefined}
              onClick={() => {
                option.onSelect();
                close(true);
              }}
            >
              <span className={styles.menuCheck} aria-hidden="true">
                {option.checked ? (
                  <svg viewBox="0 0 8 8" width="8" height="8">
                    <path d="M1.5 4.2 3.2 5.8 6.5 2.4" />
                  </svg>
                ) : null}
              </span>
              {option.label}
            </button>
          ))}
        </div>
      ) : null}
    </div>
  );
}

/** The card's 16-dot corner resize glyph (drawn inside the card root). */
function CornerGlyph({ className }: { className: string }) {
  return <CornerGrip className={`${styles.corner} ${className}`} />;
}

/* ------------------------------------------------------------------------------------------------
 * Active employees: area chart
 * ---------------------------------------------------------------------------------------------- */

const MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
const DAYS_2022 = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

type Grouping = 'department' | 'unit';

/**
 * The cards' words. `MONTHS` above stays
 * the English set: it also names the autoplay's targets (data-demo).
 */
const COPY: {
  months: string[];
  /** A reading's date, e.g. "APR 15, 2022" or "15 de abr. de 2022". */
  date: (month: number, day: number) => string;
  employees: (count: number) => string;
  period: [period: string, last: string, frequency: string, view: string];
  active: string;
  activeOptions: string;
  showPoints: string;
  replay: string;
  activeChart: string;
  groups: string;
  groupBy: string;
  groupedBy: string;
  groupsOptions: string;
  showTotal: string;
  showAll: string;
  groupings: Record<Grouping, string>;
  labels: Record<string, string>;
  byGroup: (group: string, total: number) => string;
  toggleGroups: (group: string) => string;
  total: string;
} = {
  months: MONTHS,
  date: (month, day) => `${MONTHS[month]} ${day}, 2022`,
  employees: (count) => `${count} employees`,
  period: ['Period:', 'Last 6 months', 'Frequency:', 'Monthly view'],
  active: 'Active employees',
  activeOptions: 'Active employees options',
  showPoints: 'Show data points',
  replay: 'Replay animation',
  activeChart: 'Active employees in 2022',
  groups: 'Employees by Group',
  groupBy: 'Group employees by',
  groupedBy: 'Grouped by:',
  groupsOptions: 'Employees by Group options',
  showTotal: 'Show total in centre',
  showAll: 'Show all groups',
  groupings: { department: 'Department', unit: 'Unit' },
  labels: {},
  byGroup: (group, total) => `Employees by ${group}, ${total} in total`,
  toggleGroups: (group) => `Show or hide groups by ${group}`,
  total: 'Employees',
};

/** Readings as [month index, day, active employees], traced from the drawn curve. */
const activeEmployees: [number, number, number][] = [
  [0, 8, 0],
  [0, 21, 200],
  [0, 28, 215],
  [1, 3, 262],
  [1, 13, 251],
  [1, 21, 267],
  [1, 27, 348],
  [2, 6, 310],
  [2, 13, 361],
  [2, 19, 347],
  [2, 27, 357],
  [2, 31, 355],
  [3, 4, 364],
  [3, 8, 390],
  [3, 10, 408],
  [3, 15, 469],
  [3, 19, 485],
  [3, 27, 434],
  [4, 6, 420],
  [4, 20, 427],
  [4, 27, 468],
  [5, 6, 468],
  [5, 12, 427],
  [5, 18, 361],
  [5, 26, 375],
  [6, 2, 349],
  [6, 4, 374],
  [6, 6, 392],
  [6, 9, 407],
  [6, 12, 420],
  [6, 19, 427],
  [6, 24, 393],
  [7, 1, 386],
  [7, 8, 353],
  [7, 15, 377],
  [7, 22, 377],
  [7, 31, 361],
  [8, 9, 411],
  [9, 23, 428],
  [10, 19, 493],
  [11, 5, 411],
  [11, 20, 531],
];

/** Plot geometry in card units: month centres, the value scale of the axis labels and the floor. */
const plot = {
  left: 33.119,
  top: 61.667,
  width: 304.046,
  height: 115.774,
  firstMonth: 7.624,
  monthStep: 26.523,
  zero: 113.03,
  unit: 0.2122,
};
const ticks = [500, 400, 300, 200, 100, 0];
const tickHeight = 10.632;

const points = activeEmployees.map(([month, day, value]) => ({
  month,
  day,
  value,
  x: plot.firstMonth + (month + (day - 0.5) / DAYS_2022[month] - 0.5) * plot.monthStep,
  y: plot.zero - value * plot.unit,
}));
const lastPoint = points.length - 1;
const round = (value: number) => Math.round(value * 100) / 100;
const linePath = `M${points.map((point) => `${round(point.x)} ${round(point.y)}`).join('L')}`;
const areaPath = `${linePath}L${round(points[lastPoint].x)} ${plot.height}L${round(points[0].x)} ${plot.height}Z`;
const strips = points.map((point, index) => {
  const start = index === 0 ? 0 : (points[index - 1].x + point.x) / 2;
  const end = index === lastPoint ? plot.width : (point.x + points[index + 1].x) / 2;
  return { x: round(start), width: round(end - start) };
});
const describePoint = (index: number, t: typeof COPY) => {
  const point = points[index];
  return `${t.date(point.month, point.day)}: ${t.employees(point.value)}`;
};
/** Index of the reading whose hover strip contains x (plot units). */
const stripAt = (x: number) => {
  for (let index = lastPoint; index > 0; index--) if (x >= strips[index].x) return index;
  return 0;
};
/** The highest reading of each month carries data-demo="area-<month>" for the autoplay director. */
const monthPeaks = new Map(
  MONTHS.map((_, month) => {
    let best = -1;
    points.forEach((point, index) => {
      if (point.month === month && (best < 0 || point.value > points[best].value)) best = index;
    });
    return [best, `area-${MONTHS[month].toLowerCase()}`] as const;
  }),
);

function ActiveEmployeesCard() {
  const t = COPY;
  const titleId = useId();
  const [cardRef, phase] = useEntrance<HTMLElement>();
  const [active, setActive] = useState<number | null>(null);
  const [keyboard, setKeyboard] = useState(false);
  const [showPoints, setShowPoints] = useState(false);
  const [replays, setReplays] = useState(0);
  const plotRef = useRef<SVGSVGElement>(null);
  const point = active === null ? null : points[active];

  useHover(
    plotRef,
    (event) => {
      const svg = plotRef.current;
      if (!svg) return;
      const local = localPoint(svg, event, plot.width, plot.height);
      const strip = (event.target as Element).closest?.('[data-index]');
      const index = local
        ? stripAt(local.x)
        : strip
          ? Number(strip.getAttribute('data-index'))
          : -1;
      if (index < 0) return;
      setKeyboard(false);
      setActive(index);
    },
    () => {
      if (!keyboard) setActive(null);
    },
  );

  const onKeyDown = (event: KeyboardEvent<SVGSVGElement>) => {
    let next: number;
    if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
      next = active === null ? 0 : Math.min(lastPoint, active + 1);
    } else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
      next = active === null ? lastPoint : Math.max(0, active - 1);
    } else if (event.key === 'Home') next = 0;
    else if (event.key === 'End') next = lastPoint;
    else if (event.key === 'Escape' && active !== null) {
      setActive(null);
      return;
    } else return;
    event.preventDefault();
    setKeyboard(true);
    setActive(next);
  };
  const onFocus = (event: FocusEvent<SVGSVGElement>) => {
    if (!event.currentTarget.matches(':focus-visible')) return;
    setKeyboard(true);
    setActive((current) => current ?? lastPoint);
  };

  const edge = point ? (point.x < 30 ? 14 : point.x > plot.width - 30 ? 86 : 50) : 50;
  const tooltipStyle = point
    ? ({
        left: plot.left + point.x,
        top: plot.top + point.y,
        '--anchor': `${edge}%`,
      } as CSSProperties)
    : undefined;

  return (
    <section
      role="group"
      ref={cardRef}
      className={`${styles.card} ${styles.activeCard}`}
      aria-labelledby={titleId}
      data-card="active"
      data-phase={phase}
    >
      <span className={styles.cardBox} data-part="card" aria-hidden="true" />
      <p id={titleId} className={styles.title} data-card-title="">
        {t.active}
      </p>
      <p className={styles.subtitle}>
        {t.period[0]} <strong>{t.period[1]}</strong> | {t.period[2]} <strong>{t.period[3]}</strong>
      </p>
      <MenuButton
        className={styles.dotsActive}
        triggerClassName={styles.dotsButton}
        ariaLabel={t.activeOptions}
        menuLabel={t.activeOptions}
        demo="menu-active"
        options={[
          {
            id: 'points',
            label: t.showPoints,
            kind: 'check',
            checked: showPoints,
            onSelect: () => setShowPoints((value) => !value),
          },
          ...(phase === 'static'
            ? []
            : [
                {
                  id: 'replay',
                  label: t.replay,
                  onSelect: () => setReplays((value) => value + 1),
                },
              ]),
        ]}
      >
        <DotsIcon className={styles.dotsIcon} />
      </MenuButton>

      <div className={styles.axis} aria-hidden="true">
        {ticks.map((tick) => (
          <span
            key={tick}
            className={styles.tick}
            style={{ top: plot.top + plot.zero - tick * plot.unit - tickHeight / 2 }}
          >
            {tick}
          </span>
        ))}
        {MONTHS.map((month, index) => (
          <span
            key={month}
            className={styles.month}
            data-active={point?.month === index || undefined}
            style={{ left: plot.left + plot.firstMonth + index * plot.monthStep }}
          >
            {t.months[index]}
          </span>
        ))}
      </div>

      <svg
        ref={plotRef}
        className={styles.plot}
        viewBox={`0 0 ${plot.width} ${plot.height}`}
        width={plot.width}
        height={plot.height}
        role="slider"
        tabIndex={0}
        aria-label={t.activeChart}
        aria-valuemin={0}
        aria-valuemax={lastPoint}
        aria-valuenow={active ?? lastPoint}
        aria-valuetext={describePoint(active ?? lastPoint, t)}

        data-demo="area-chart"
        onKeyDown={onKeyDown}
        onFocus={onFocus}
        onBlur={() => {
          setKeyboard(false);
          setActive(null);
        }}
      >
        <path key={`area-${replays}`} className={styles.area} d={areaPath} />
        <path key={`line-${replays}`} className={styles.line} d={linePath} pathLength={1} />
        {showPoints ? (
          <g className={styles.markers}>
            {points.map((item, index) => (
              <circle key={index} cx={round(item.x)} cy={round(item.y)} r={1.2} />
            ))}
          </g>
        ) : null}
        {point ? (
          <g className={styles.crosshair}>
            <line x1={point.x} x2={point.x} y1={0} y2={plot.height} />
            <line x1={0} x2={plot.width} y1={point.y} y2={point.y} />
            <circle className={styles.halo} cx={point.x} cy={point.y} r={5.3} />
            <circle className={styles.point} cx={point.x} cy={point.y} r={2.3} />
          </g>
        ) : null}
        <g className={styles.hits}>
          {strips.map((strip, index) => (
            <rect
              key={index}
              x={strip.x}
              y={0}
              width={strip.width}
              height={plot.height}
              data-index={index}
              data-demo={monthPeaks.get(index)}
            />
          ))}
        </g>
      </svg>

      {point ? (
        <div className={styles.tooltip} style={tooltipStyle} aria-hidden="true">
          <span className={styles.tooltipDate}>{t.date(point.month, point.day)}</span>
          <span className={styles.tooltipValue}>{point.value}</span>
          <span className={styles.tooltipTail}>
            <TooltipTail className={styles.tailIcon} />
          </span>
        </div>
      ) : null}
      <CornerGlyph className={styles.cornerActive} />
    </section>
  );
}

/* ------------------------------------------------------------------------------------------------
 * Employees by Group: donut
 * ---------------------------------------------------------------------------------------------- */

type Group = { id: string; label: string; value: number; color: string };

const groupings: Record<Grouping, { label: string; items: Group[] }> = {
  department: {
    label: 'Department',
    items: [
      {
        id: 'tecnologia',
        label: 'Technology',
        value: 90,
        color: 'color(display-p3 0.459 0.867 0.847)',
      },
      { id: 'comercial', label: 'Commercial', value: 10, color: 'color(display-p3 1 0.855 0.49)' },
      { id: 'sales', label: 'Sales', value: 20, color: 'color(display-p3 0.725 0.639 0.937)' },
    ],
  },
  unit: {
    label: 'Unit',
    items: [
      {
        id: 'sao-paulo',
        label: 'São Paulo',
        value: 58,
        color: 'color(display-p3 0.459 0.867 0.847)',
      },
      { id: 'rio', label: 'Rio de Janeiro', value: 38, color: 'color(display-p3 1 0.855 0.49)' },
      {
        id: 'curitiba',
        label: 'Curitiba',
        value: 24,
        color: 'color(display-p3 0.725 0.639 0.937)',
      },
    ],
  },
};

/** Ring geometry from the design (147.643 square group). */
const donut = { size: 147.643, centre: 73.822, outer: 54.041, inner: 37.835, lift: 3.3 };
const ringRadius = (donut.outer + donut.inner) / 2;
const ringWidth = donut.outer - donut.inner;
const circumference = 2 * Math.PI * ringRadius;

/** Angles are degrees clockwise from 12 o'clock; the first group runs anticlockwise from the top. */
function layoutSegments(items: Group[], hidden: ReadonlySet<string>) {
  const total = items.reduce((sum, item) => sum + (hidden.has(item.id) ? 0 : item.value), 0);
  let covered = 0;
  return items.map((item) => {
    const value = hidden.has(item.id) ? 0 : item.value;
    const share = total ? value / total : 0;
    const end = 360 - covered * 360;
    covered += share;
    const start = 360 - covered * 360;
    return { ...item, value, share, start, end };
  });
}

function polar(radius: number, degrees: number) {
  const radians = (degrees * Math.PI) / 180;
  return `${round(donut.centre + radius * Math.sin(radians))} ${round(donut.centre - radius * Math.cos(radians))}`;
}

function arcPath(start: number, end: number): string {
  if (end - start >= 359.99) return `${arcPath(start, start + 180)}${arcPath(start + 180, end)}`;
  const large = end - start > 180 ? 1 : 0;
  return [
    `M${polar(donut.outer, start)}`,
    `A${donut.outer} ${donut.outer} 0 ${large} 1 ${polar(donut.outer, end)}`,
    `L${polar(donut.inner, end)}`,
    `A${donut.inner} ${donut.inner} 0 ${large} 0 ${polar(donut.inner, start)}Z`,
  ].join('');
}

const percent = (share: number) => `${Math.round(share * 100)}%`;

function EmployeesByGroupCard() {
  const t = COPY;
  const titleId = useId();
  const [cardRef, phase] = useEntrance<HTMLElement>();
  const [grouping, setGrouping] = useState<Grouping>('department');
  const [hidden, setHidden] = useState<ReadonlySet<string>>(() => new Set());
  const [active, setActive] = useState<string | null>(null);
  const [showTotal, setShowTotal] = useState(false);
  const donutRef = useRef<SVGSVGElement>(null);

  const { items } = groupings[grouping];
  const groupLabel = t.groupings[grouping];
  const labelOf = (item: Group) => t.labels[item.id] ?? item.label;
  const segments = layoutSegments(items, hidden);
  const total = segments.reduce((sum, segment) => sum + segment.value, 0);
  const current = segments.find((segment) => segment.id === active && segment.value > 0) ?? null;

  /** The ring segment under a client point (covers the gap a lifted slice leaves behind). */
  const segmentAt = (event: MouseEvent) => {
    const local = donutRef.current && localPoint(donutRef.current, event, donut.size, donut.size);
    if (!local) return null;
    const dx = local.x - donut.centre;
    const dy = local.y - donut.centre;
    const radius = Math.hypot(dx, dy);
    if (radius < donut.inner || radius > donut.outer + donut.lift) return null;
    const angle = ((Math.atan2(dx, -dy) * 180) / Math.PI + 360) % 360;
    return (
      segments.find((segment) => segment.value > 0 && angle >= segment.start && angle < segment.end)
        ?.id ?? null
    );
  };
  // Slices and legend items name their group; anything else in the card resolves by position.
  useHover(
    cardRef,
    (event) => {
      const hit = (event.target as Element).closest?.('[data-group-id]');
      setActive(hit ? hit.getAttribute('data-group-id') : segmentAt(event));
    },
    () => setActive(null),
  );

  const toggle = (id: string) =>
    setHidden((set) => {
      const next = new Set(set);
      if (!next.delete(id)) next.add(id);
      return next;
    });

  return (
    <section
      role="group"
      ref={cardRef}
      className={`${styles.card} ${styles.groupCard}`}
      aria-labelledby={titleId}
      data-card="groups"
      data-phase={phase}
    >
      <span className={styles.cardBox} data-part="card" aria-hidden="true" />
      <p id={titleId} className={styles.title} data-card-title="">
        {t.groups}
      </p>
      <p className={styles.subtitle}>
        {t.period[0]} <strong>{t.period[1]}</strong> <span className={styles.divider}>|</span>{' '}
        {t.period[2]} <strong>{t.period[3]}</strong>
      </p>
      <MenuButton
        className={styles.groupBy}
        triggerClassName={styles.groupByButton}
        menuLabel={t.groupBy}
        demo="group-by"
        options={(Object.keys(groupings) as Grouping[]).map((key) => ({
          id: key,
          label: t.groupings[key],
          kind: 'radio',
          checked: key === grouping,
          onSelect: () => {
            if (key === grouping) return;
            setGrouping(key);
            setHidden(new Set());
            setActive(null);
          },
        }))}
      >
        <span className={styles.groupByLabel}>
          {t.groupedBy} <strong>{groupLabel}</strong>
        </span>
        <span className={styles.chevron} aria-hidden="true">
          <CornerArrow className={styles.chevronIcon} />
        </span>
      </MenuButton>
      <MenuButton
        className={styles.dotsGroup}
        triggerClassName={styles.dotsButton}
        ariaLabel={t.groupsOptions}
        menuLabel={t.groupsOptions}
        demo="menu-groups"
        options={[
          {
            id: 'total',
            label: t.showTotal,
            kind: 'check',
            checked: showTotal,
            onSelect: () => setShowTotal((value) => !value),
          },
          {
            id: 'reset',
            label: t.showAll,
            onSelect: () => setHidden(new Set()),
          },
        ]}
      >
        <DotsIcon className={styles.dotsIcon} />
      </MenuButton>

      <svg
        ref={donutRef}
        className={styles.donut}
        viewBox={`0 0 ${donut.size} ${donut.size}`}
        width={donut.size}
        height={donut.size}

        data-demo="donut"
      >
        <g className={styles.donutSpin}>
          <circle
            className={styles.track}
            cx={donut.centre}
            cy={donut.centre}
            r={ringRadius}
            strokeWidth={ringWidth}
          />
          <g role="list" aria-label={t.byGroup(groupLabel.toLowerCase(), total)}>
            {segments.map((segment, index) => {
              const middle = ((segment.start + segment.end) / 2) * (Math.PI / 180);
              const lifted = current?.id === segment.id;
              return (
                <g
                  key={index}
                  role="none"
                  className={styles.slice}
                  data-dim={(current && !lifted) || undefined}
                  style={{
                    transform: lifted
                      ? `translate(${round(Math.sin(middle) * donut.lift)}px, ${round(-Math.cos(middle) * donut.lift)}px)`
                      : undefined,
                  }}
                >
                  <circle
                    className={styles.segment}
                    cx={donut.centre}
                    cy={donut.centre}
                    r={ringRadius}
                    strokeWidth={ringWidth}
                    style={{
                      stroke: segment.color,
                      strokeDasharray: `${round(segment.share * circumference)} ${round(circumference)}`,
                      transform: `rotate(${round(segment.start - 90)}deg)`,
                    }}
                  />
                  {segment.value > 0 ? (
                    <path
                      className={styles.segmentHit}
                      d={arcPath(segment.start, segment.end)}
                      role="listitem"
                      tabIndex={0}
                      aria-label={`${labelOf(segment)}: ${t.employees(segment.value)}, ${percent(segment.share)}`}
                      data-group-id={segment.id}
                      data-demo={`slice-${segment.id}`}
                      onFocus={() => setActive(segment.id)}
                      onBlur={() => setActive(null)}
                    />
                  ) : null}
                </g>
              );
            })}
          </g>
        </g>
      </svg>

      <div className={styles.donutCentre} aria-hidden="true">
        {current ? (
          <span key={current.id} className={styles.centreContent}>
            <span className={styles.centreValue}>{percent(current.share)}</span>
            <span className={styles.centreLabel}>{labelOf(current)}</span>
            <span className={styles.centreMeta}>{t.employees(current.value)}</span>
          </span>
        ) : showTotal ? (
          <span key="total" className={styles.centreContent}>
            <span className={styles.centreValue}>{total}</span>
            <span className={styles.centreLabel}>{t.total}</span>
          </span>
        ) : null}
      </div>

      <ul className={styles.legend} aria-label={t.toggleGroups(groupLabel.toLowerCase())}>
        {segments.map((segment, index) => (
          <li key={segment.id}>
            <button
              type="button"
              className={styles.legendItem}
              aria-pressed={!hidden.has(segment.id)}
              style={{ '--dot': segment.color } as CSSProperties}
              data-group-id={segment.id}
              data-demo={`legend-${segment.id}`}
              onClick={() => toggle(segment.id)}
              onFocus={() => setActive(segment.id)}
              onBlur={() => setActive(null)}
            >
              <span className={styles.legendDot} aria-hidden="true" />
              {labelOf(segment)}
            </button>
          </li>
        ))}
      </ul>
      <CornerGlyph className={styles.cornerGroup} />
    </section>
  );
}

/** Active employees and Employees by Group. */
export function ActivityCards() {
  return (
    <>
      <ActiveEmployeesCard />
      <EmployeesByGroupCard />
    </>
  );
}
```

### workforce-dashboard/activity-cards.module.css

```css
/* Active employees and Employees by Group; units are dashboard px.
   Card roots carry no transform (the autoplay director animates `translate` on them).
   Text set in the design font renders in Inter here; Inter's glyphs sit higher in the
   same line box, so those runs are nudged down (measured against the design render). */
.card {
  position: absolute;
  top: 136.704px;
  width: 360.714px;
  height: 209.533px;
}
.card:focus-within,
.card:has([data-open]) {
  z-index: 3;
}
.activeCard {
  left: 89.017px;
}
.groupCard {
  left: 457.688px;
}
.cardBox {
  position: absolute;
  top: -0.332px;
  left: -0.332px;
  width: 361.377px;
  height: 210.196px;
  box-sizing: border-box;
  border: 0.663px solid var(--wf-line);
  border-radius: 2.45px;
  background: var(--wf-surface);
  pointer-events: none;
  user-select: none;
}

/* Header */
.title {
  position: absolute;
  color: var(--wf-ink-3);
  font-size: 13.262px;
  font-weight: 500;
  line-height: normal;
  white-space: nowrap;
}
.activeCard .title {
  top: 15.915px;
  left: 15.914px;
}
.groupCard .title {
  top: 10.333px;
  left: 12.829px;
}
.subtitle {
  position: absolute;
  translate: 0 0.5px;
  font-size: 7.957px;
  font-weight: 400;
  line-height: 10.646px;
  white-space: nowrap;
}
.subtitle strong {
  color: var(--wf-muted-2);
  font-weight: 500;
}
.activeCard .subtitle {
  top: 35.144px;
  left: 15.914px;
  color: var(--wf-muted-2);
}
.groupCard .subtitle {
  top: 29.562px;
  left: 12.829px;
  color: var(--wf-muted-3);
}
.divider {
  color: var(--wf-divider);
}

/* Menu buttons */
.dotsActive,
.dotsGroup,
.groupBy {
  position: absolute;
  z-index: 4;
}
.dotsActive {
  top: 15.915px;
  left: 325.571px;
}
.dotsGroup {
  top: 10.333px;
  left: 342.667px;
}
.dotsButton {
  display: block;
  border-radius: 2.652px;
  transition: background-color 0.15s ease;
}
.dotsButton:hover,
.dotsButton[aria-expanded='true'] {
  background-color: var(--wf-grid);
}
.dotsIcon {
  display: block;
  width: 13.2616px;
  height: 13.2616px;
  color: var(--wf-dots);
  pointer-events: none;
}
.dotsButton:focus-visible,
.groupByButton:focus-visible,
.legendItem:focus-visible,
.menuItem:focus-visible,
.plot:focus-visible {
  outline: 1.2px solid var(--wf-sky);
  outline-offset: 1.2px;
}

.groupBy {
  top: 27.562px;
  right: 4.91px;
}
.groupByButton {
  display: flex;
  align-items: flex-start;
  padding: 2px;
  border-radius: 2.652px;
  color: var(--wf-muted-3);
  font-size: 7.957px;
  line-height: 10.646px;
  white-space: nowrap;
}
.groupByLabel {
  translate: 0 0.6px;
}
.groupByButton strong {
  color: var(--wf-sky);
  font-weight: 500;
}
.groupByButton:hover strong {
  text-decoration: underline;
  text-underline-offset: 1.5px;
}
.chevron {
  display: flex;
  width: 4.2426px;
  height: 4.2426px;
  margin-top: 3.54px;
  margin-left: 3.2px;
  align-items: center;
  justify-content: center;
}
.chevronIcon {
  display: block;
  width: 2.99987px;
  color: var(--wf-sky);
  height: 3.00018px;
  transform: rotate(45deg) scaleX(-1);
  transition: transform 0.2s ease;
}
.groupByButton[aria-expanded='true'] .chevronIcon {
  transform: translateY(0.9px) rotate(225deg) scaleX(-1);
}

.menu {
  position: absolute;
  top: calc(100% + 3px);
  right: 0;
  display: flex;
  min-width: 86px;
  padding: 2.652px;
  flex-direction: column;
  border: 0.663px solid var(--wf-line);
  border-radius: 3.978px;
  background: var(--wf-surface);
  box-shadow:
    0 7.957px 15.914px color(display-p3 0.055 0.071 0.106 / 0.08),
    0 0.663px 1.326px color(display-p3 0.055 0.071 0.106 / 0.04);
  animation: menu-in 0.14s ease-out;
}
.menuItem {
  display: flex;
  padding: 3.315px 6.631px 3.315px 3.978px;
  align-items: center;
  gap: 3.315px;
  border-radius: 2.652px;
  color: var(--wf-ink-3);
  font-size: 7.957px;
  line-height: 10.646px;
  text-align: left;
  white-space: nowrap;
}
.menuItem:hover,
.menuItem:focus-visible {
  background: var(--wf-surface-2);
}
.menuItem[aria-checked='true'] {
  color: var(--wf-sky);
}
.menuCheck {
  display: flex;
  width: 8px;
  height: 8px;
  flex: none;
}
.menuCheck svg {
  width: 8px;
  height: 8px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Area chart */
.axis {
  position: absolute;
  inset: 0;
  color: var(--wf-ink-3);
  font-size: 6.631px;
  font-weight: 400;
  pointer-events: none;
}
.tick {
  position: absolute;
  left: 15.914px;
  translate: 0 0.55px;
  height: 10.632px;
  line-height: 10.632px;
  white-space: nowrap;
}
.month {
  position: absolute;
  /* the design line box top is 183.01px; +1.68px lines Inter up with the design font labels. */
  top: 184.69px;
  width: 24px;
  margin-left: -12px;
  line-height: 10.771px;
  text-align: center;
  transition: color 0.15s ease;
}
.month[data-active] {
  color: var(--wf-sky);
  font-weight: 600;
}
.plot {
  position: absolute;
  top: 61.667px;
  left: 33.119px;
  width: 304.046px;
  height: 115.774px;
  overflow: visible;
  border-radius: 1px;
  touch-action: pan-y;
}
.area {
  fill: color(display-p3 0.494 0.714 1);
  fill-opacity: 0.15;
}
.line {
  fill: none;
  stroke: var(--wf-sky);
  stroke-width: 1.419px;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.markers circle {
  fill: var(--wf-surface);
  stroke: var(--wf-sky);
  stroke-width: 0.8px;
  animation: fade-in 0.2s ease-out;
}
.crosshair {
  pointer-events: none;
}
.crosshair line {
  stroke: var(--wf-sky);
  stroke-dasharray: 1.989 1.989;
  stroke-opacity: 0.45;
  stroke-width: 0.663px;
}
.halo {
  fill: var(--wf-sky);
  fill-opacity: 0.16;
}
.point {
  fill: var(--wf-surface);
  stroke: var(--wf-sky);
  stroke-width: 1.326px;
}
.hits rect {
  fill: transparent;
}
/* Tooltip [1.0] at the dashboard's 0.795 instance scale, shared with the other cards. */
.tooltip {
  position: absolute;
  z-index: 2;
  display: flex;
  padding: 3.179px 7.948px;
  align-items: baseline;
  border-radius: 4.769px;
  background: var(--wf-tooltip);
  box-shadow:
    0 9.537px 19.075px color(display-p3 0.055 0.071 0.106 / 0.06),
    0 0.795px 1.59px color(display-p3 0.055 0.071 0.106 / 0.03);
  color: #fff;
  pointer-events: none;
  transform: translate(calc(var(--anchor) * -1), calc(-100% - 10.3px));
  white-space: nowrap;
}
.tooltipDate {
  margin-right: 4.769px;
  color: var(--wf-muted);
  font-size: 7.957px;
  line-height: 10.646px;
}
.tooltipValue {
  font-size: 11.13px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.0668px;
  line-height: 15.896px;
}
.tooltipTail {
  position: absolute;
  top: 100%;
  left: var(--anchor);
  width: 9.537px;
  height: 4.769px;
  margin-left: -4.769px;
}
.tailIcon {
  position: absolute;
  display: block;
  color: var(--wf-tooltip);
  top: -1.59px;
  left: -2.384px;
  width: 14.3062px;
  height: 6.35833px;
}

/* Donut */
.donut {
  position: absolute;
  top: 50.617px;
  left: 103px;
  width: 147.643px;
  height: 147.643px;
  overflow: visible;
}
.donutSpin {
  transform-origin: 73.822px 73.822px;
}
.track {
  fill: none;
  stroke: var(--wf-grid);
}
.slice {
  transition:
    transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.2s ease;
}
.slice[data-dim] {
  opacity: 0.45;
}
.segment {
  fill: none;
  transform-origin: 73.822px 73.822px;
  transition:
    stroke-dasharray 0.6s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.segmentHit {
  fill: transparent;
  stroke: none;
  cursor: pointer;
}
.segmentHit:focus {
  outline: none;
}
.segmentHit:focus-visible {
  stroke: var(--wf-sky);
  stroke-width: 1.2px;
}
.donutCentre {
  position: absolute;
  top: 50.617px;
  left: 103px;
  display: flex;
  width: 147.643px;
  height: 147.643px;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  text-align: center;
}
.centreContent {
  display: flex;
  max-width: 70px;
  flex-direction: column;
  align-items: center;
  animation: centre-in 0.22s ease-out;
}
.centreValue {
  color: var(--wf-ink-3);
  font-size: 17.24px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 20px;
}
.centreLabel {
  overflow: hidden;
  max-width: 100%;
  color: var(--wf-muted-2);
  font-size: 6.631px;
  font-weight: 700;
  line-height: 8.62px;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}
.centreMeta {
  color: var(--wf-muted-3);
  font-size: 5.968px;
  line-height: 7.957px;
}

.legend {
  position: absolute;
  top: 190.304px;
  left: 0;
  display: flex;
  width: 342.93px;
  justify-content: center;
  gap: 21px;
}
.legendItem {
  display: flex;
  margin: -2.652px -3.315px;
  padding: 2.652px 3.315px;
  align-items: center;
  gap: 5.304px;
  border-radius: 2.652px;
  color: var(--wf-muted-2);
  font-size: 6.631px;
  font-weight: 700;
  line-height: 8px;
  text-transform: uppercase;
  transition: opacity 0.2s ease;
  white-space: nowrap;
}
.legendDot {
  width: 6.631px;
  height: 6.631px;
  flex: none;
  border: 1.326px solid var(--dot);
  border-radius: 50%;
  background: var(--dot);
  transition: background-color 0.2s ease;
}
.legendItem[aria-pressed='false'] {
  opacity: 0.5;
}
.legendItem[aria-pressed='false'] .legendDot {
  background: transparent;
}

/* Corner resize glyphs, part of each card. */
.corner {
  position: absolute;
  top: 197.598px;
  width: 9.28282px;
  height: 9.28263px;
  color: var(--wf-muted-3);
  pointer-events: none;
  user-select: none;
}
.cornerActive {
  left: 346.529px;
}
.cornerGroup {
  left: 348.779px;
}

/* Entrance: armed before the card is seen, played once in view. */
.card[data-phase='armed'] .line {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
.card[data-phase='armed'] .area,
.card[data-phase='armed'] .donutSpin {
  opacity: 0;
}
@media print {
  .card[data-phase='armed'] .line {
    stroke-dashoffset: 0;
  }
  .card[data-phase='armed'] .area,
  .card[data-phase='armed'] .donutSpin {
    opacity: 1;
  }
}
.card[data-phase='played'] .line {
  stroke-dasharray: 1;
  animation: draw-line 1.5s cubic-bezier(0.45, 0, 0.2, 1) backwards;
}
.card[data-phase='played'] .area {
  animation: fade-in 0.9s ease-out 0.55s backwards;
}
.card[data-phase='played'] .donutSpin {
  animation: donut-in 1s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}
.card[data-phase='played'] .segment {
  animation: sweep 1s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

@keyframes draw-line {
  from {
    stroke-dashoffset: 1;
  }
  to {
    stroke-dashoffset: 0;
  }
}
@keyframes fade-in {
  from {
    opacity: 0;
  }
}
@keyframes donut-in {
  from {
    opacity: 0;
    transform: rotate(-90deg);
  }
}
@keyframes sweep {
  from {
    stroke-dasharray: 0 288.64;
  }
}
@keyframes centre-in {
  from {
    opacity: 0;
    transform: scale(0.92);
  }
}
@keyframes menu-in {
  from {
    opacity: 0;
    transform: translateY(-2px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .card *,
  .card *::after {
    animation: none !important;
    transition: none !important;
  }
}
```

### workforce-dashboard/team-cards.tsx

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

'use client';

import {
  useEffect,
  useId,
  useLayoutEffect,
  useRef,
  useState,
  type CSSProperties,
  type KeyboardEvent as ReactKeyboardEvent,
  type RefObject,
} from 'react';
import { useHydratedReducedMotion } from './use-reduced-motion';
import type { Person, PersonProps } from './types';
import { CheckBadge, CornerGrip, DotsIcon, Glyph, PagerArrow } from './icons';
import { HeartPattern, HeartShadow, HeartSurface, HeartWave } from './heart';
import styles from './team-cards.module.css';

type Time = [hours: number, minutes: number, seconds: number];
type MetricId = 'acumulado' | 'extras' | 'negativas';
type Reading = {
  value: Time;
  delta: Time;
  percent: number;
  direction: 'up' | 'down';
  tone: 'bad' | 'good';
};

/** `text` is the design's (whole-pixel) label box, so the chips keep the design's exact widths. */
const people: {
  id: Person;
  label: string;
  text: number;
  /** The initial on the chip's avatar, and its tint. */
  avatar?: string;
  tint?: 'blue' | 'grey' | 'pink';
}[] = [
  { id: 'all', label: 'All employees', text: 74 },
  { id: 'mateo', label: 'Mateo', text: 31, avatar: 'M', tint: 'blue' },
  { id: 'ava', label: 'Ava', text: 20, avatar: 'A', tint: 'grey' },
  { id: 'rosana', label: 'Rosana', text: 35, avatar: 'R', tint: 'pink' },
];

const metrics: { id: MetricId }[] = [{ id: 'acumulado' }, { id: 'extras' }, { id: 'negativas' }];

/** The cards' words. */
const COPY: {
  all: string;
  metrics: Record<MetricId, { label: string; header: string }>;
  up: string;
  down: string;
  title: string;
  employees: string;
  previous: string;
  next: string;
  metricMenu: string;
  health: string;
  excellent: [before: string, word: string, after: string];
  scoreText: string;
  quarter: string;
  saved: string;
  action: string;
} = {
  all: 'All employees',
  metrics: {
    acumulado: { label: 'Accumulated time bank', header: 'Selected time bank' },
    extras: { label: 'Overtime', header: 'Selected overtime' },
    negativas: { label: 'Negative hours', header: 'Selected negative hours' },
  },
  up: 'up',
  down: 'down',
  title: 'Total time bank',
  employees: 'Employees',
  previous: 'Previous employee',
  next: 'Next employee',
  metricMenu: 'Time bank metric',
  health: 'Company Health score',
  excellent: ['This score is considered to be ', 'Excellent', '.'],
  scoreText: 'Excellent',
  quarter: 'Your score for Q1 2022',
  saved: 'You saved 224:43h in extra hours',
  action: 'New Action',
};

/** Individual balances add up to the team totals shown for "All employees". */
const readings: Record<Person, Record<MetricId, Reading>> = {
  all: {
    acumulado: {
      value: [556, 34, 0],
      delta: [56, 10, 78],
      percent: 14,
      direction: 'up',
      tone: 'bad',
    },
    extras: { value: [224, 43, 0], delta: [31, 12, 40], percent: 16, direction: 'up', tone: 'bad' },
    negativas: {
      value: [48, 20, 0],
      delta: [6, 45, 12],
      percent: 12,
      direction: 'down',
      tone: 'good',
    },
  },
  mateo: {
    acumulado: {
      value: [182, 12, 0],
      delta: [18, 40, 22],
      percent: 11,
      direction: 'up',
      tone: 'bad',
    },
    extras: { value: [96, 30, 0], delta: [12, 5, 10], percent: 14, direction: 'up', tone: 'bad' },
    negativas: {
      value: [12, 10, 0],
      delta: [2, 15, 30],
      percent: 16,
      direction: 'down',
      tone: 'good',
    },
  },
  ava: {
    acumulado: { value: [146, 5, 0], delta: [9, 20, 45], percent: 7, direction: 'up', tone: 'bad' },
    extras: { value: [58, 15, 0], delta: [6, 40, 0], percent: 13, direction: 'up', tone: 'bad' },
    negativas: {
      value: [20, 45, 0],
      delta: [3, 10, 20],
      percent: 18,
      direction: 'up',
      tone: 'bad',
    },
  },
  rosana: {
    acumulado: {
      value: [228, 17, 0],
      delta: [28, 9, 11],
      percent: 14,
      direction: 'up',
      tone: 'bad',
    },
    extras: { value: [69, 58, 0], delta: [12, 27, 30], percent: 22, direction: 'up', tone: 'bad' },
    negativas: {
      value: [15, 25, 0],
      delta: [1, 20, 5],
      percent: 8,
      direction: 'down',
      tone: 'good',
    },
  },
};

const healthScore = 75;
const zero: Time = [0, 0, 0];
const noScroll = { preventScroll: true };

const pad = (value: number) => String(value).padStart(2, '0');
const formatTime = ([hours, minutes, seconds]: Time) =>
  `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;

/** Registered integer properties: CSS interpolates them and counters print the running value. */
const timeVars = (prefix: 'v' | 'd', [hours, minutes, seconds]: Time) =>
  ({
    [`--wf-team-${prefix}h`]: hours,
    [`--wf-team-${prefix}m`]: minutes,
    [`--wf-team-${prefix}s`]: seconds,
  }) as CSSProperties;

/** Counters need registered (@property) integers to interpolate; without them they just jump. */
const canCount = () =>
  typeof IntersectionObserver !== 'undefined' &&
  typeof CSS !== 'undefined' &&
  typeof CSS.registerProperty === 'function';

/**
 * Plays a count-up the first time `target` scrolls into view. The rendered values are always the
 * final ones (server HTML, before hydration, reduced motion, no @property support, already on
 * screen at load); only for the instant of the reveal are they reset to zero with transitions
 * off, so the browser then transitions them back up to the final values.
 */
function useCountUp(target: RefObject<HTMLElement | null>) {
  const reduceMotion = useHydratedReducedMotion();
  const [resetting, setResetting] = useState(false);
  useEffect(() => {
    const node = target.current;
    if (!node || reduceMotion || !canCount()) return;
    let initial = true;
    const observer = new IntersectionObserver(([entry]) => {
      const atLoad = initial;
      initial = false;
      if (!entry?.isIntersecting) return;
      observer.disconnect();
      if (atLoad) return;
      setResetting(true);
    });
    observer.observe(node);
    return () => observer.disconnect();
  }, [target, reduceMotion]);
  // The zero state is committed: flush it to style, then restore the values before paint, so the
  // change transitions.
  useLayoutEffect(() => {
    if (!resetting) return;
    target.current?.getBoundingClientRect();
    setResetting(false);
  }, [resetting, target]);
  return resetting;
}

const SHOW_EVENTS = [
  'pointerover',
  'pointerenter',
  'pointermove',
  'mouseover',
  'mouseenter',
  'mousemove',
] as const;
const HIDE_EVENTS = ['pointerout', 'pointerleave', 'mouseout', 'mouseleave'] as const;

/**
 * Mirrors hover onto the `[data-hoverable]` element under the pointer as a `data-hover`
 * attribute (not managed by React). Capture-phase listeners on the card root also catch the
 * non-bubbling enter/leave events that the case-study autoplay dispatches programmatically.
 * Compatibility mouse events that follow a touch are ignored so taps leave no sticky hover.
 */
function useHoverMirror(root: RefObject<HTMLElement | null>) {
  useEffect(() => {
    const node = root.current;
    if (!node) return;
    let current: HTMLElement | null = null;
    let touchedAt = -Infinity;
    const hoverableOf = (target: EventTarget | null) => {
      const item =
        target instanceof Element ? target.closest<HTMLElement>('[data-hoverable]') : null;
      return item && node.contains(item) ? item : null;
    };
    const set = (item: HTMLElement | null) => {
      if (item === current) return;
      current?.removeAttribute('data-hover');
      item?.setAttribute('data-hover', '');
      current = item;
    };
    const show = (event: Event) => {
      if (typeof PointerEvent !== 'undefined' && event instanceof PointerEvent) {
        if (event.pointerType === 'touch') {
          touchedAt = event.timeStamp;
          return;
        }
      } else if (event.timeStamp - touchedAt < 1000) {
        return;
      }
      set(hoverableOf(event.target));
    };
    // Leaving for a node inside the same hoverable keeps it; anything else (or nothing) hides it.
    const hide = (event: Event) => set(hoverableOf((event as MouseEvent).relatedTarget));
    for (const type of SHOW_EVENTS) node.addEventListener(type, show, true);
    for (const type of HIDE_EVENTS) node.addEventListener(type, hide, true);
    return () => {
      for (const type of SHOW_EVENTS) node.removeEventListener(type, show, true);
      for (const type of HIDE_EVENTS) node.removeEventListener(type, hide, true);
      set(null);
    };
  }, [root]);
}

/**
 * Whether the latest click or key press on the page was the reader's own. The
 * autoplay drives the cards with element.click(), whose events are untrusted. Window capture
 * runs before any React handler, so the answer is current when the resulting update renders.
 */
function useReaderInput() {
  const trusted = useRef(true);
  useEffect(() => {
    const note = (event: Event) => {
      trusted.current = event.isTrusted;
    };
    const options = { capture: true, passive: true };
    window.addEventListener('click', note, options);
    window.addEventListener('keydown', note, options);
    return () => {
      window.removeEventListener('click', note, options);
      window.removeEventListener('keydown', note, options);
    };
  }, []);
  return trusted;
}

/**
 * Text for a polite live region that speaks only the reader's changes. Autoplay changes are
 * written with aria-live="off" (the text stays current for reading), and the attribute is set
 * in the same commit, before the text, so screen readers never announce them.
 */
function useReaderAnnouncement(text: string) {
  const readerInput = useReaderInput();
  const [live, setLive] = useState({ text, polite: true });
  useLayoutEffect(() => {
    setLive((current) => (current.text === text ? current : { text, polite: readerInput.current }));
  }, [text, readerInput]);
  // Key order is prop order: React writes aria-live before the text.
  return { 'aria-live': live.polite ? 'polite' : 'off', children: live.text } as const;
}

/** A card's box: its surface and hairline edge. */
function Box({ className }: { className: string }) {
  return <span className={className} data-part="card" aria-hidden="true" />;
}

/** The card's 16-dot corner resize glyph; it lives inside the card root so it travels with it. */
function ResizeGlyph({ className }: { className: string }) {
  return <CornerGrip className={`${styles.resize} ${className}`} />;
}

/** Total time bank and Company Health score. */
export function TeamCards({ person: controlledPerson, onPersonChange }: PersonProps) {
  return (
    <div className={styles.region}>
      <HoursCard person={controlledPerson} onPersonChange={onPersonChange} />
      <HealthCard />
    </div>
  );
}

function HoursCard({ person: controlledPerson, onPersonChange }: PersonProps) {
  const [ownPerson, setOwnPerson] = useState<Person>('all');
  const person = controlledPerson ?? ownPerson;
  const [metric, setMetric] = useState<MetricId>('acumulado');
  const [menuOpen, setMenuOpen] = useState(false);
  const reduceMotion = useHydratedReducedMotion();
  const card = useRef<HTMLElement>(null);
  const amount = useRef<HTMLDivElement>(null);
  const chipRow = useRef<HTMLDivElement>(null);
  const menuButton = useRef<HTMLButtonElement>(null);
  const menu = useRef<HTMLDivElement>(null);
  const prevButton = useRef<HTMLButtonElement>(null);
  const nextButton = useRef<HTMLButtonElement>(null);
  const resetting = useCountUp(amount);
  const ids = useId();
  useHoverMirror(card);

  const t = COPY;
  const index = people.findIndex((entry) => entry.id === person);
  const reading = readings[person][metric];
  const activeMetric = t.metrics[metric];
  const shown = resetting ? { ...reading, value: zero, delta: zero, percent: 0 } : reading;
  const sign = reading.direction === 'up' ? '+' : '-';
  const announcement = useReaderAnnouncement(
    `${activeMetric.label}: ${formatTime(reading.value)}, ${
      reading.direction === 'up' ? t.up : t.down
    } ${formatTime(reading.delta)} (${sign}${reading.percent}%)`,
  );

  const selectPerson = (next: Person) => {
    if (controlledPerson === undefined) setOwnPerson(next);
    onPersonChange?.(next);
  };

  const step = (offset: number) => {
    const nextIndex = index + offset;
    const next = people[nextIndex];
    if (!next) return;
    selectPerson(next.id);
    // A focused pager that becomes disabled would drop keyboard focus; hand it to the other one.
    const focused = document.activeElement;
    if (nextIndex === 0 && focused === prevButton.current) nextButton.current?.focus(noScroll);
    if (nextIndex === people.length - 1 && focused === nextButton.current) {
      prevButton.current?.focus(noScroll);
    }
    // Keep the newly selected chip visible without moving the page itself.
    const row = chipRow.current;
    const chip = row?.querySelector<HTMLElement>(`[data-person="${next.id}"]`);
    if (!row || !chip) return;
    const left = chip.offsetLeft - row.clientWidth / 2 + chip.offsetWidth / 2;
    row.scrollTo({ left, behavior: reduceMotion ? 'auto' : 'smooth' });
  };

  const menuItems = () =>
    Array.from(menu.current?.querySelectorAll<HTMLElement>('[role="menuitemradio"]') ?? []);

  useEffect(() => {
    if (!menuOpen) return;
    const items = menuItems();
    (items.find((item) => item.getAttribute('aria-checked') === 'true') ?? items[0])?.focus(
      noScroll,
    );
    // `click` too, so a programmatic element.click() elsewhere also dismisses the menu, unless
    // the reader's focus is in it: hiding it then would drop that focus on the page.
    const close = (event: Event) => {
      const target = event.target as Node;
      if (menu.current?.contains(target) || menuButton.current?.contains(target)) return;
      if (!event.isTrusted && menu.current?.contains(document.activeElement)) return;
      setMenuOpen(false);
    };
    document.addEventListener('pointerdown', close);
    document.addEventListener('click', close);
    return () => {
      document.removeEventListener('pointerdown', close);
      document.removeEventListener('click', close);
    };
  }, [menuOpen]);

  const closeMenu = () => {
    setMenuOpen(false);
    menuButton.current?.focus(noScroll);
  };

  const onMenuKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {
    const items = menuItems();
    const current = items.indexOf(document.activeElement as HTMLElement);
    const focus = (next: number) => items[(next + items.length) % items.length]?.focus(noScroll);
    if (event.key === 'Escape') {
      event.preventDefault();
      closeMenu();
    } else if (event.key === 'ArrowDown') {
      event.preventDefault();
      focus(current + 1);
    } else if (event.key === 'ArrowUp') {
      event.preventDefault();
      focus(current - 1);
    } else if (event.key === 'Home') {
      event.preventDefault();
      focus(0);
    } else if (event.key === 'End') {
      event.preventDefault();
      focus(items.length - 1);
    } else if (event.key === 'Tab') {
      setMenuOpen(false);
    }
  };

  const onMenuButtonKeyDown = (event: ReactKeyboardEvent<HTMLButtonElement>) => {
    if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
      event.preventDefault();
      setMenuOpen(true);
    }
  };

  return (
    <section
      role="group"
      ref={card}
      className={`${styles.card} ${styles.hours}`}
      aria-labelledby={`${ids}-hours-title`}
      data-card="bank-total"
      data-count={resetting ? 'reset' : undefined}
    >
      <Box className={styles.boxHours} />
      <p
        id={`${ids}-hours-title`}
        className={`${styles.cardTitle} ${styles.hoursTitle}`}
        data-card-title=""
      >
        {t.title}
      </p>
      <DotsIcon className={`${styles.dots} ${styles.hoursDots}`} />

      <div className={styles.peopleHeader}>
        <p id={`${ids}-people`} className={styles.peopleLabel}>
          {t.employees}
        </p>
        <button
          ref={prevButton}
          type="button"
          className={styles.pager}
          onClick={() => step(-1)}
          disabled={index <= 0}
          aria-label={t.previous}
          data-hoverable=""
          data-demo="pager-prev"
        >
          <PagerArrow direction="left" className={styles.pagerIcon} />
        </button>
        <button
          ref={nextButton}
          type="button"
          className={styles.pager}
          onClick={() => step(1)}
          disabled={index >= people.length - 1}
          aria-label={t.next}
          data-hoverable=""
          data-demo="pager-next"
        >
          <PagerArrow direction="right" className={styles.pagerIcon} />
        </button>
      </div>

      <div ref={chipRow} className={styles.chipRow} role="group" aria-labelledby={`${ids}-people`}>
        {people.map((entry) => {
          const selected = entry.id === person;
          return (
            <button
              key={entry.id}
              type="button"
              data-person={entry.id}
              data-hoverable=""
              data-demo={`chip-${entry.id}`}
              className={`${styles.chip} ${entry.avatar ? styles.chipWithAvatar : ''}`}
              aria-pressed={selected}
              onClick={() => selectPerson(entry.id)}
            >
              {entry.avatar ? (
                <span className={styles.avatar} data-tint={entry.tint}>
                  {entry.avatar}
                </span>
              ) : null}
              <span className={styles.chipLabel} style={{ minWidth: entry.text }}>
                {entry.id === 'all' ? t.all : entry.label}
              </span>
              <span className={styles.checkSlot} aria-hidden="true">
                <CheckBadge className={styles.checkIcon} />
              </span>
            </button>
          );
        })}
      </div>

      <div className={styles.amountCard}>
        <button
          ref={menuButton}
          type="button"
          className={styles.select}
          aria-haspopup="menu"
          aria-expanded={menuOpen}
          aria-controls={`${ids}-metric-menu`}
          onClick={() => setMenuOpen((open) => !open)}
          onKeyDown={onMenuButtonKeyDown}
          data-hoverable=""
          data-demo="metric-select"
        >
          <span className={styles.selectLabel}>{activeMetric.header}</span>
          <Glyph name="chevronDown" className={styles.selectArrow} />
        </button>

        <div className={styles.amountBody}>
          <div ref={amount} className={styles.amount} data-demo="amount">
            <p key={metric} className={styles.caption}>
              {activeMetric.label}
            </p>
            <p
              className={`${styles.value} ${styles.counter}`}
              style={timeVars('v', shown.value)}
              aria-hidden="true"
            />
          </div>
          <div
            className={styles.delta}
            data-tone={reading.tone}
            data-direction={reading.direction}
            aria-hidden="true"
          >
            <span className={styles.deltaArrow} />
            <span
              className={`${styles.deltaText} ${styles.counter}`}
              data-sign={sign}
              style={
                {
                  ...timeVars('d', shown.delta),
                  '--wf-team-pct': shown.percent,
                } as CSSProperties
              }
            />
          </div>
          <p className={styles.srOnly} {...announcement} />
        </div>

        <div
          ref={menu}
          id={`${ids}-metric-menu`}
          className={styles.menu}
          role="menu"
          aria-label={t.metricMenu}
          hidden={!menuOpen}
          onKeyDown={onMenuKeyDown}
        >
          {metrics.map((entry) => (
            <button
              key={entry.id}
              type="button"
              role="menuitemradio"
              aria-checked={entry.id === metric}
              tabIndex={-1}
              className={styles.menuItem}
              data-hoverable=""
              data-demo={`metric-${entry.id}`}
              onClick={() => {
                setMetric(entry.id);
                closeMenu();
              }}
            >
              <span>{t.metrics[entry.id].label}</span>
              <CheckBadge className={styles.checkIcon} />
            </button>
          ))}
        </div>
      </div>

      <ResizeGlyph className={styles.hoursResize} />
    </section>
  );
}

function HealthCard() {
  const t = COPY;
  const card = useRef<HTMLElement>(null);
  const heart = useRef<HTMLDivElement>(null);
  const resetting = useCountUp(heart);
  const ids = useId();
  const [presses, setPresses] = useState(0);
  const [offscreen, setOffscreen] = useState(false);
  useHoverMirror(card);

  // The heartbeat loops forever; pause it while the heart is scrolled out of view.
  useEffect(() => {
    const node = heart.current;
    if (!node || typeof IntersectionObserver === 'undefined') return;
    const observer = new IntersectionObserver(([entry]) => setOffscreen(!entry?.isIntersecting));
    observer.observe(node);
    return () => observer.disconnect();
  }, []);

  return (
    <section
      role="group"
      ref={card}
      className={`${styles.card} ${styles.health}`}
      aria-labelledby={`${ids}-health-title`}
      data-card="health"
      data-count={resetting ? 'reset' : undefined}
    >
      <Box className={styles.boxHealth} />
      <div className={styles.healthHeading}>
        <p id={`${ids}-health-title`} className={styles.cardTitle} data-card-title="">
          {t.health}
        </p>
        <p className={styles.subtitle}>
          {t.excellent[0]}
          <strong>{t.excellent[1]}</strong>
          {t.excellent[2]}
        </p>
      </div>
      <DotsIcon className={`${styles.dots} ${styles.healthDots}`} />

      <div
        ref={heart}
        className={styles.heart}
        role="meter"
        tabIndex={0}
        aria-valuemin={0}
        aria-valuemax={100}
        aria-valuenow={healthScore}
        aria-valuetext={`${healthScore}%, ${t.scoreText}`}
        aria-labelledby={`${ids}-health-title`}
        data-hoverable=""
        data-demo="heart"
        data-paused={offscreen || undefined}
      >
        <HeartPattern className={styles.heartPattern} />
        <div className={styles.heartBeat} aria-hidden="true">
          <span className={styles.heartGlow} />
          <HeartShadow className={styles.heartShadow} />
          <HeartSurface className={styles.heartSurface} />
          <div className={styles.heartLiquid}>
            <HeartWave
              className={styles.wave03}
              opacity={0.8}
              glow
              from={[18.0853, 62.3863]}
              to={[210.126, 42.2669]}
            />
            <HeartWave
              className={styles.wave02}
              opacity={0.68}
              glow
              from={[53.0273, 52.2588]}
              to={[156.533, 48.9011]}
            />
            <HeartWave
              className={styles.wave01}
              opacity={0.24}
              glow={false}
              from={[-11.1434, 32.6618]}
              to={[94.7249, 46.3101]}
            />
            <p
              className={`${styles.score} ${styles.counter}`}
              style={{ '--wf-team-score': resetting ? 0 : healthScore } as CSSProperties}
            />
          </div>
        </div>
        <span className={styles.heartRing} aria-hidden="true" />
      </div>

      <div className={styles.scoreText}>
        <p className={styles.scoreTitle}>{t.quarter}</p>
        <p className={styles.scoreNote}>{t.saved}</p>
      </div>

      <button
        type="button"
        className={styles.action}
        onClick={() => setPresses((n) => n + 1)}
        data-hoverable=""
        data-demo="new-action"
      >
        {presses > 0 ? (
          <span key={presses} className={styles.actionPulse} aria-hidden="true" />
        ) : null}
        <Glyph
          key={`icon-${presses}`}
          name="add"
          className={`${styles.actionIcon} ${presses > 0 ? styles.actionIconSpin : ''}`}
        />
        <span className={styles.actionLabel}>{t.action}</span>
      </button>

      <ResizeGlyph className={styles.healthResize} />
    </section>
  );
}
```

### workforce-dashboard/team-cards.module.css

```css
/* the design (Total time bank) and (Company Health score).
   Every length is in the 1526×881 design space of frame . */

@property --wf-team-vh {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
@property --wf-team-vm {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
@property --wf-team-vs {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
@property --wf-team-dh {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
@property --wf-team-dm {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
@property --wf-team-ds {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
@property --wf-team-pct {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}
@property --wf-team-score {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}

.region {
  display: contents;
  --team-focus: var(--wf-accent);
  --team-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
}

.region .card {
  position: absolute;
  top: 136.704px;
  height: 309.969px;
  font-feature-settings:
    'cv09' 1,
    'ss11' 1,
    'calt' 0,
    'liga' 0;
}

.region .card :focus-visible {
  outline: 1.5px solid var(--team-focus);
  outline-offset: 1.5px;
}

/* Corner resize glyphs and (9.283 × 9.283), part of each card. */
.region .resize {
  position: absolute;
  z-index: 1;
  width: 9.28282px;
  height: 9.28263px;
  color: var(--wf-muted-3);
  top: 297.252px;
  pointer-events: none;
}
.region .hoursResize {
  left: 401.37px;
}
.region .healthResize {
  left: 233.669px;
}

.region .hours {
  left: 828.968px;
  width: 413.292px;
}

.region .health {
  left: 1255.771px;
  width: 244.796px;
}

.region .boxHours,
.region .boxHealth {
  position: absolute;
  top: -0.332px;
  left: -0.332px;
  box-sizing: border-box;
  border: 0.663px solid var(--wf-line);
  border-radius: 2.45px;
  background: var(--wf-surface);
  height: 310.632px;
}
.region .boxHours {
  width: 413.955px;
}
.region .boxHealth {
  width: 245.459px;
}

.region .cardTitle {
  color: var(--wf-ink-3);
  font-size: 13.262px;
  font-weight: 600;
  line-height: normal;
  white-space: nowrap;
}

.region .hoursTitle {
  position: absolute;
  top: 15.915px;
  left: 12.716px;
}

.region .dots {
  position: absolute;
  color: var(--wf-dots);
  width: 13.286px;
  height: 13.262px;
}
.region .hoursDots {
  top: 15.101px;
  left: 387.063px;
}
.region .healthDots {
  top: 15.896px;
  left: 221.178px;
}

/* ---------- Employees ---------- */

.region .peopleHeader {
  position: absolute;
  top: 53.251px;
  left: 12.716px;
  display: flex;
  align-items: center;
  gap: 6.358px;
  width: 386.269px;
  height: 15.896px;
}

.region .peopleLabel {
  flex: 1 0 0;
  min-width: 1px;
  height: 13px;
  color: var(--wf-muted);
  font-size: 9.537px;
  font-weight: 500;
  line-height: 12.717px;
  letter-spacing: 0.3815px;
  text-transform: uppercase;
}

.region .pager {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 15.896px;
  height: 15.896px;
  padding: 0.795px;
  border-radius: 4.812px;
  transition:
    background-color 160ms ease,
    transform 160ms var(--team-ease);
}
.region .pagerIcon {
  display: block;
  width: 14.306px;
  height: 14.306px;
  color: var(--wf-ink-2);
}
.region .pager:disabled .pagerIcon {
  color: var(--wf-muted-4);
}
.region .pager:disabled {
  cursor: default;
}
.region .pager:not(:disabled):hover,
.region .pager:not(:disabled)[data-hover] {
  background: var(--wf-surface-2);
}
.region .pager:not(:disabled):active {
  transform: scale(0.88);
}

.region .flip {
  transform: scaleX(-1);
}

/* Scroll container: 3px of breathing room keeps chip shadows and focus rings unclipped. */
.region .chipRow {
  position: absolute;
  top: 75.685px;
  left: 12.101px;
  display: flex;
  gap: 6.358px;
  width: 392.269px;
  height: 31.537px;
  padding: 3px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.region .chipRow::-webkit-scrollbar {
  display: none;
}

.region .chip {
  position: relative;
  display: flex;
  flex-shrink: 0;
  align-items: center;
  height: 25.537px;
  padding: 4.769px 9.537px;
  border-radius: 801.228px;
  color: var(--wf-ink-2);
  background: var(--wf-surface);
  box-shadow:
    inset 0 0 0 0.795px var(--wf-line),
    0 0.795px 1.59px rgba(10, 13, 20, 0.03);
  font-size: 11.13px;
  font-weight: 400;
  line-height: 15.896px;
  letter-spacing: -0.0668px;
  white-space: nowrap;
  transition:
    background-color 200ms ease,
    color 200ms ease,
    padding 260ms var(--team-ease),
    transform 160ms var(--team-ease);
}
.region .chipWithAvatar {
  padding-left: 4.769px;
}
.region .chip[aria-pressed='true'] {
  padding-right: 6.358px;
  color: var(--wf-ink-3);
  background: var(--wf-surface-2);
  box-shadow: inset 0 0 0 0.795px var(--wf-line);
  font-weight: 500;
}
.region .chip:hover,
.region .chip[data-hover] {
  background: var(--wf-surface-2);
}
.region .chip:active {
  transform: scale(0.96);
}

.region .avatar {
  position: relative;
  flex-shrink: 0;
  width: 15.896px;
  height: 15.896px;
  margin-right: 4.769px;
  overflow: hidden;
  border-radius: 999px;
}
.region .avatar {
  display: grid;
  place-items: center;
  font-size: 8.5px;
  font-weight: 600;
  line-height: 1;
}
.region .avatar[data-tint='blue'] {
  color: var(--wf-accent);
  background: var(--wf-accent-soft);
}
.region .avatar[data-tint='grey'] {
  color: var(--wf-ink-2);
  background: var(--wf-line);
}
.region .avatar[data-tint='pink'] {
  color: var(--wf-pink-ink);
  background: var(--wf-pink);
}

.region .chipLabel {
  display: block;
}

/* The check slides in by growing its slot (gap 4.769 + icon 12.717). */
.region .checkSlot {
  display: flex;
  flex-shrink: 0;
  justify-content: flex-end;
  width: 0;
  height: 12.717px;
  overflow: hidden;
  transition: width 260ms var(--team-ease);
}
.region .checkIcon {
  display: block;
  flex-shrink: 0;
  width: 12.717px;
  height: 12.717px;
  opacity: 0;
  transform: scale(0.3) rotate(-40deg);
  transition:
    opacity 160ms ease,
    transform 320ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.region .chip[aria-pressed='true'] .checkSlot {
  width: 17.486px;
}
.region .chip[aria-pressed='true'] .checkIcon {
  opacity: 1;
  transform: none;
  transition-delay: 60ms;
}

/* ---------- Card & Amount ---------- */

.region .amountCard {
  position: absolute;
  top: 116.835px;
  left: 12.716px;
  display: flex;
  flex-direction: column;
  width: 388.653px;
  height: 182.007px;
  overflow: clip;
  border-radius: 7.948px;
  background: var(--wf-surface);
  box-shadow: 0 0.795px 1.59px rgba(10, 13, 20, 0.03);
}
.region .amountCard::after {
  position: absolute;
  inset: 0;
  border: 0.795px solid var(--wf-line);
  border-radius: inherit;
  content: '';
  pointer-events: none;
}

.region .select {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 6.358px;
  width: 100%;
  height: 25.537px;
  padding: 4.769px 7.948px 4.769px 9.537px;
  color: var(--wf-ink-3);
  background: var(--wf-surface-2);
  box-shadow: inset 0 -0.795px 0 var(--wf-line);
  font-size: 11.13px;
  line-height: 15.896px;
  letter-spacing: -0.0668px;
  text-align: left;
  transition: background-color 160ms ease;
}
.region .select:hover,
.region .select[data-hover],
.region .select[aria-expanded='true'] {
  background: var(--wf-grid-2);
}
.region .select:focus-visible {
  outline-offset: -2px;
}
.region .selectLabel {
  flex: 1 0 0;
  min-width: 1px;
}
.region .selectArrow {
  display: block;
  color: var(--wf-ink-2);
  width: 15.896px;
  height: 15.896px;
  transition: transform 260ms var(--team-ease);
}
.region .select[aria-expanded='true'] .selectArrow {
  transform: rotate(180deg);
}

.region .amountBody {
  display: flex;
  flex: 1 0 0;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12.717px;
  min-height: 1px;
  padding: 6.358px;
  box-shadow: inset 0 -0.795px 0 var(--wf-line);
}

.region .amount {
  display: flex;
  flex: 1 0 0;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3.179px;
  width: 100%;
  min-height: 1px;
  padding-top: 6.358px;
  font-weight: 500;
  text-align: center;
}

/* Heights are the design's rounded text boxes (10 and 32), which set the stack's centring. */
.region .caption {
  width: 100%;
  height: 10px;
  color: var(--wf-muted);
  font-size: 8.743px;
  line-height: 9.537px;
  letter-spacing: 0.1749px;
  text-transform: uppercase;
  animation: teamCaptionIn 360ms var(--team-ease) both;
}

.region .value {
  width: 100%;
  height: 32px;
  color: var(--wf-ink);
  font-size: 25.433px;
  line-height: 31.792px;
  letter-spacing: -0.1272px;
}

/* Counters print the interpolated integers; the accessible copy lives in .srOnly. */
.region .counter {
  transition:
    --wf-team-vh 900ms var(--team-ease),
    --wf-team-vm 900ms var(--team-ease),
    --wf-team-vs 900ms var(--team-ease),
    --wf-team-dh 900ms var(--team-ease),
    --wf-team-dm 900ms var(--team-ease),
    --wf-team-ds 900ms var(--team-ease),
    --wf-team-pct 900ms var(--team-ease),
    --wf-team-score 1600ms cubic-bezier(0.16, 1, 0.3, 1) 200ms;
}
/* The instant before a count-up: values drop to zero without animating, then transition back. */
.region .card[data-count='reset'] .counter,
.region .card[data-count='reset'] .wave03,
.region .card[data-count='reset'] .wave02,
.region .card[data-count='reset'] .wave01 {
  transition: none;
}
.region .value.counter {
  counter-reset: vh var(--wf-team-vh) vm var(--wf-team-vm) vs var(--wf-team-vs);
}
.region .value.counter::after {
  content: counter(vh, decimal-leading-zero) ':' counter(vm, decimal-leading-zero) ':'
    counter(vs, decimal-leading-zero);
}

.region .delta {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  gap: 6.358px;
  width: 100%;
  height: 22.537px;
  padding: 4.769px 12.717px;
  border-radius: 4.769px;
  color: color(display-p3 0.984 0.369 0.369);
  background: var(--wf-surface-2);
  transition: color 300ms ease;
}
.region .delta[data-tone='good'] {
  color: color(display-p3 0.122 0.757 0.42);
}

.region .deltaArrow {
  width: 7.587px;
  height: 4.636px;
  background: currentColor;
  mask: url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 7.58708 4.63642%22 preserveAspectRatio=%22none%22%3E%3Cpath d=%22M3.79354 2.95066L6.7442 0L7.58708 0.842876L3.79354 4.63642L0 0.842876L0.842877 0L3.79354 2.95066Z%22/%3E%3C/svg%3E')
    center / 100% 100% no-repeat;
  transition: transform 360ms var(--team-ease);
}
.region .delta[data-direction='up'] .deltaArrow {
  transform: rotate(180deg);
}

/* the design's 76 × 13 text box, centred text; longer readings simply grow it. */
.region .deltaText {
  min-width: 76px;
  height: 13px;
  font-size: 9.537px;
  font-weight: 600;
  line-height: 12.717px;
  text-align: center;
  white-space: nowrap;
  counter-reset: dh var(--wf-team-dh) dm var(--wf-team-dm) ds var(--wf-team-ds) pct
    var(--wf-team-pct);
}
.region .deltaText::after {
  content: counter(dh, decimal-leading-zero) ':' counter(dm, decimal-leading-zero) ':'
    counter(ds, decimal-leading-zero) ' (' attr(data-sign) counter(pct) '%)';
}

/* ---------- Metric menu ---------- */

.region .menu {
  position: absolute;
  z-index: 2;
  top: 29.5px;
  right: 6.358px;
  left: 6.358px;
  display: flex;
  flex-direction: column;
  gap: 1.59px;
  padding: 3.179px;
  border-radius: 6.358px;
  background: var(--wf-surface);
  box-shadow:
    0 0 0 0.795px var(--wf-line),
    0 12px 24px -8px color(display-p3 0.055 0.071 0.106 / 0.16),
    0 2px 4px color(display-p3 0.055 0.071 0.106 / 0.04);
  transform-origin: 50% 0;
  animation: teamMenuIn 220ms var(--team-ease) both;
}
.region .menu[hidden] {
  display: none;
}

.region .menuItem {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6.358px;
  width: 100%;
  height: 25.434px;
  padding: 4.769px 7.948px 4.769px 6.358px;
  border-radius: 4.769px;
  color: var(--wf-ink-3);
  font-size: 11.13px;
  line-height: 15.896px;
  letter-spacing: -0.0668px;
  text-align: left;
  transition: background-color 140ms ease;
}
.region .menuItem:hover,
.region .menuItem[data-hover],
.region .menuItem:focus-visible {
  background: var(--wf-surface-2);
}
.region .menuItem:focus-visible {
  outline-offset: -1.5px;
}
.region .menuItem[aria-checked='true'] {
  font-weight: 500;
}
.region .menuItem .checkIcon {
  width: 12.717px;
  height: 12.717px;
  opacity: 0;
  transform: scale(0.4);
  transition:
    opacity 160ms ease,
    transform 260ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.region .menuItem[aria-checked='true'] .checkIcon {
  opacity: 1;
  transform: none;
}

/* ---------- Company Health score ---------- */

.region .healthHeading {
  position: absolute;
  top: 15.896px;
  left: 12.717px;
  display: flex;
  flex-direction: column;
  gap: 3.179px;
  width: 195.744px;
}

.region .subtitle {
  color: var(--wf-muted-2);
  font-size: 7.957px;
  line-height: 10.646px;
}
.region .subtitle strong {
  font-weight: 700;
}

/* Heart frame (245.924 × 122.962); the heart itself sits at 61.481, 7.685. */
.region .heart {
  position: absolute;
  top: 78.685px;
  left: -5.102px;
  width: 245.924px;
  height: 122.962px;
  cursor: default;
}
.region .heart:focus-visible {
  outline: none;
}

.region .heartPattern {
  position: absolute;
  display: block;
  top: -0.384px;
  left: -0.384px;
  width: 246.692px;
  height: 123.73px;
  transition: opacity 400ms ease;
}

/* Always beating: a lub-dub scale on the whole heart, with a soft red glow on each beat. */
.region .heartBeat {
  position: absolute;
  inset: 0;
  transform-origin: 122.962px 61.5px;
  animation: teamHeartBeat 1100ms infinite;
}

.region .heartGlow {
  position: absolute;
  top: 7.685px;
  left: 61.481px;
  width: 122.962px;
  height: 107.592px;
  opacity: 0;
  filter: blur(7px);
  animation: teamHeartGlow 1100ms infinite;
}
.region .heartGlow::before {
  position: absolute;
  inset: 0;
  background: color(display-p3 0.984 0.216 0.282);
  content: '';
  mask: url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 122.962 107.592%22 preserveAspectRatio=%22none%22%3E%3Cpath d=%22M89.1474 0C107.825 0 122.962 14.5394 122.962 34.8946C122.962 75.605 76.8512 98.8681 61.481 107.592C46.1107 98.8681 0 75.605 0 34.8946C0 14.5394 15.3702 0 33.8145 0C45.25 0 55.3329 5.81577 61.481 11.6315C67.6291 5.81577 77.712 0 89.1474 0Z%22/%3E%3C/svg%3E')
    0 0 / 100% 100% no-repeat;
  opacity: 0.55;
  transition: opacity 300ms ease;
}
.region .heart[data-paused] .heartBeat,
.region .heart[data-paused] .heartGlow {
  animation-play-state: paused;
}

.region .heartShadow {
  position: absolute;
  display: block;
  top: -8.454px;
  left: 33.051px;
  width: 179.832px;
  height: 165.037px;
}

.region .heartSurface,
.region .heartLiquid {
  position: absolute;
  display: block;
  top: 7.685px;
  left: 61.481px;
  width: 122.962px;
  height: 107.592px;
}

.region .heartLiquid {
  overflow: hidden;
  mask: url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 122.962 107.592%22 preserveAspectRatio=%22none%22%3E%3Cpath d=%22M89.1474 0C107.825 0 122.962 14.5394 122.962 34.8946C122.962 75.605 76.8512 98.8681 61.481 107.592C46.1107 98.8681 0 75.605 0 34.8946C0 14.5394 15.3702 0 33.8145 0C45.25 0 55.3329 5.81577 61.481 11.6315C67.6291 5.81577 77.712 0 89.1474 0Z%22/%3E%3C/svg%3E')
    0 0 / 100% 100% no-repeat;
}

.region .wave03,
.region .wave02,
.region .wave01 {
  position: absolute;
  display: block;
  width: 190.591px;
  height: 211.341px;
  transform: translateY(var(--rise, 0px)) scaleX(var(--flip, 1));
  transition: transform 1500ms cubic-bezier(0.16, 1, 0.3, 1);
}
.region .wave03 {
  top: 0.769px;
  left: -18.444px;
}
.region .wave02 {
  top: 2.306px;
  left: -49.185px;
  --flip: -1;
  transition-delay: 90ms;
}
.region .wave01 {
  top: 12.296px;
  left: -49.185px;
  --flip: -1;
  transition-delay: 180ms;
}
.region .health[data-count='reset'] .wave03,
.region .health[data-count='reset'] .wave02,
.region .health[data-count='reset'] .wave01 {
  --rise: 96px;
}

.region .score {
  position: absolute;
  top: 49.185px;
  left: 9.223px;
  width: 104.518px;
  font-size: 24.592px;
  font-weight: 500;
  line-height: 30.74px;
  /* the design sets this in Inter Display (-0.123px); tighter tracking approximates its narrower cut. */
  letter-spacing: -0.9px;
  text-align: center;
  counter-reset: score var(--wf-team-score);
}
.region .score::after {
  display: block;
  color: transparent;
  background: linear-gradient(to bottom, #fff 52.649%, rgb(255 255 255 / 0) 115%);
  background-clip: text;
  -webkit-background-clip: text;
  content: counter(score) '%';
}

.region .heartRing {
  position: absolute;
  top: 3px;
  left: 56px;
  width: 134px;
  height: 118px;
  border-radius: 14px;
  box-shadow: 0 0 0 1.5px var(--team-focus);
  opacity: 0;
  pointer-events: none;
  transition: opacity 160ms ease;
}
.region .heart:focus-visible .heartRing {
  opacity: 1;
}

/* Hover or focus: the liquid sloshes, the glow deepens and the grid recedes. */
.region .heart:hover .wave02,
.region .heart[data-hover] .wave02,
.region .heart:focus-visible .wave02 {
  animation: teamSlosh 2600ms ease-in-out infinite;
}
.region .heart:hover .wave01,
.region .heart[data-hover] .wave01,
.region .heart:focus-visible .wave01 {
  animation: teamSlosh 2600ms ease-in-out -1300ms infinite;
}
.region .heart:hover .heartGlow::before,
.region .heart[data-hover] .heartGlow::before,
.region .heart:focus-visible .heartGlow::before {
  opacity: 0.9;
}
.region .heart:hover .heartPattern,
.region .heart[data-hover] .heartPattern,
.region .heart:focus-visible .heartPattern {
  opacity: 0.7;
}

.region .scoreText {
  position: absolute;
  top: 218.568px;
  left: 23.844px;
  display: flex;
  flex-direction: column;
  gap: 2.41px;
  width: 192.801px;
  text-align: center;
}
/* the design's rounded text boxes (13 and 10) give the 15.41 offset of the second line. */
.region .scoreTitle {
  height: 13px;
  color: var(--wf-ink-3);
  font-size: 8.435px;
  font-weight: 700;
  line-height: 12.05px;
  letter-spacing: -0.0506px;
}
.region .scoreNote {
  height: 10px;
  color: var(--wf-muted-2);
  font-size: 7.23px;
  line-height: 9.64px;
}

.region .action {
  position: absolute;
  top: 268.64px;
  left: 10.332px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3.179px;
  width: 223.336px;
  height: 28.717px;
  padding: 6.358px;
  overflow: hidden;
  border-radius: 6.358px;
  color: var(--wf-ink-3);
  background: var(--wf-surface-2);
  transition:
    background-color 160ms ease,
    transform 180ms var(--team-ease),
    box-shadow 180ms ease;
}
.region .action:hover,
.region .action[data-hover] {
  background: var(--wf-grid-2);
}
.region .action:active {
  background: var(--wf-track);
  box-shadow: inset 0 1px 2px color(display-p3 0.055 0.071 0.106 / 0.08);
  transform: scale(0.97);
}

.region .actionIcon {
  position: relative;
  display: block;
  color: var(--wf-ink-3);
  width: 15.896px;
  height: 15.896px;
}
.region .actionIconSpin {
  animation: teamSpin 460ms var(--team-ease);
}

/* the design's text box is 61 wide, which centres the icon + label pair exactly. */
.region .actionLabel {
  position: relative;
  min-width: 67.358px;
  padding: 0 3.179px;
  font-size: 11.127px;
  font-weight: 500;
  line-height: 15.896px;
  letter-spacing: -0.0668px;
  white-space: nowrap;
}

.region .actionPulse {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 240px;
  height: 240px;
  margin: -120px 0 0 -120px;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--wf-accent) 16%, transparent) 0%,
    color-mix(in srgb, var(--wf-accent) 0%, transparent) 70%
  );
  pointer-events: none;
  animation: teamPulse 620ms ease-out both;
}

.region .srOnly {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@keyframes teamCaptionIn {
  from {
    opacity: 0;
    transform: translateY(3px);
  }
}

@keyframes teamMenuIn {
  from {
    opacity: 0;
    transform: translateY(-4px) scale(0.97);
  }
}

/* ~55 bpm: a strong "lub", a short recoil, a softer "dub", then rest. */
@keyframes teamHeartBeat {
  0% {
    transform: scale(1);
    animation-timing-function: cubic-bezier(0.33, 0, 0.2, 1);
  }
  10% {
    transform: scale(1.045);
    animation-timing-function: cubic-bezier(0.5, 0, 0.6, 1);
  }
  19% {
    transform: scale(0.995);
    animation-timing-function: cubic-bezier(0.33, 0, 0.2, 1);
  }
  28% {
    transform: scale(1.028);
    animation-timing-function: cubic-bezier(0.45, 0, 0.3, 1);
  }
  52%,
  100% {
    transform: scale(1);
  }
}

@keyframes teamHeartGlow {
  0% {
    opacity: 0;
    animation-timing-function: cubic-bezier(0.33, 0, 0.2, 1);
  }
  10% {
    opacity: 0.8;
    animation-timing-function: ease-in-out;
  }
  19% {
    opacity: 0.35;
    animation-timing-function: cubic-bezier(0.33, 0, 0.2, 1);
  }
  28% {
    opacity: 0.7;
    animation-timing-function: ease-in;
  }
  64%,
  100% {
    opacity: 0;
  }
}

@keyframes teamSlosh {
  0%,
  100% {
    translate: 0 0;
  }
  50% {
    translate: 7px -2px;
  }
}

@keyframes teamSpin {
  from {
    transform: rotate(-90deg) scale(0.6);
  }
}

@keyframes teamPulse {
  from {
    opacity: 1;
    transform: scale(0.1);
  }
  to {
    opacity: 0;
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .region *,
  .region *::before,
  .region *::after {
    animation: none !important;
    transition: none !important;
  }
}
```

### workforce-dashboard/hours-cards.tsx

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

'use client';

import {
  useEffect,
  useId,
  useRef,
  useState,
  type CSSProperties,
  type FocusEvent,
  type KeyboardEvent,
} from 'react';
import { CornerArrow, CornerGrip, DotsIcon, Glyph, StarBadge, StopwatchIcon } from './icons';
import styles from './hours-cards.module.css';

const SHOW_EVENTS = [
  'pointerover',
  'pointerenter',
  'pointermove',
  'mouseover',
  'mouseenter',
  'mousemove',
] as const;
const HIDE_EVENTS = ['pointerout', 'pointerleave', 'mouseout', 'mouseleave'] as const;

/**
 * Hover wired to native pointer and mouse events, so events dispatched programmatically at a
 * target (the case-study autoplay) behave like a real pointer. Moves between the target's own
 * children are ignored; an "out" with no related target always hides.
 */
function bindHover(node: HTMLElement, show: () => void, hide: () => void) {
  const onShow = () => show();
  const onHide = (event: Event) => {
    const next = (event as MouseEvent).relatedTarget;
    if (next instanceof Node && node.contains(next)) return;
    hide();
  };
  SHOW_EVENTS.forEach((type) => node.addEventListener(type, onShow));
  HIDE_EVENTS.forEach((type) => node.addEventListener(type, onHide));
  return () => {
    SHOW_EVENTS.forEach((type) => node.removeEventListener(type, onShow));
    HIDE_EVENTS.forEach((type) => node.removeEventListener(type, onHide));
  };
}

/** Purely visual hover state as a `data-hover` attribute (React does not manage it). */
function bindHoverAttribute(node: HTMLElement) {
  return bindHover(
    node,
    () => node.setAttribute('data-hover', ''),
    () => node.removeAttribute('data-hover'),
  );
}

/* ------------------------------------------------------------------ */
/* Time bank                      */
/* ------------------------------------------------------------------ */

/** Chart geometry in the card's own space (the card sits at 89.02, 359.25 in the frame). */
const CARD_WIDTH = 729.619;
const BASELINE = 185.505;
const FIRST_BAR = 37.797;
const BAR_STEP = 63.6554;
const BAR_WIDTH = 35.806;
const COLUMN_WIDTH = BAR_STEP;
const COLUMN_TOP = 56;
const COLUMN_HEIGHT = 150;
const SLOTS = 4;
/** Space between a bar and its side tooltip, room for the tooltip's tail. */
const TIP_GAP = 7.5;

type Month = { short: string; name: string; height: number };

/** Month columns as drawn in the design; `height` is the bar height in design pixels. */
const months: Month[] = [
  { short: 'JAN', name: 'January', height: 70.2863 },
  { short: 'FEB', name: 'February', height: 39.7847 },
  { short: 'MAR', name: 'March', height: 98.1355 },
  { short: 'APR', name: 'April', height: 24.5339 },
  { short: 'JUN', name: 'June', height: 39.7847 },
  { short: 'JUL', name: 'July', height: 80.2324 },
  { short: 'AUG', name: 'August', height: 98.1355 },
  { short: 'SEP', name: 'September', height: 39.7847 },
  { short: 'OCT', name: 'October', height: 20.5554 },
  { short: 'NOV', name: 'November', height: 88.1894 },
  { short: 'DEC', name: 'December', height: 35.8062 },
];

const BLUE = 'color(display-p3 0.396 0.71 0.969)';
const TEAL = 'color(display-p3 0.459 0.867 0.847)';
const SKY = 'color(display-p3 0.647 0.835 0.988)';
const LILAC = 'color(display-p3 0.839 0.647 0.988)';

type GroupingId = 'department' | 'shift' | 'contract';

type Grouping = {
  id: GroupingId;
  label: string;
  /** Stacked bottom to top. */
  categories: { name: string; color: string }[];
  /** Hours per month, one value per category. */
  values: number[][];
  /** Exact the design segment heights; other groupings split the bar proportionally. */
  heights?: number[][];
};

const groupings: Grouping[] = [
  {
    id: 'department',
    label: 'Department',
    categories: [
      { name: 'Operations', color: BLUE },
      { name: 'Technology', color: TEAL },
      { name: 'Commercial', color: SKY },
      { name: 'Finance', color: LILAC },
    ],
    values: [
      [73, 73, 0, 221],
      [36, 165, 41, 0],
      [239, 0, 0, 193],
      [0, 70, 53, 0],
      [0, 139, 59, 0],
      [0, 390, 0, 0],
      [142, 61, 0, 297],
      [140, 0, 60, 0],
      [0, 110, 0, 0],
      [130, 282, 0, 0],
      [0, 0, 0, 312],
    ],
    heights: [
      [13.9246, 13.9246, 0, 42.4371],
      [5.9677, 27.1862, 6.6308, 0],
      [54.3724, 0, 0, 43.7631],
      [0, 13.9246, 10.6093, 0],
      [0, 27.8493, 11.9354, 0],
      [0, 80.2324, 0, 0],
      [27.8493, 11.9354, 0, 58.3508],
      [27.8493, 0, 11.9354, 0],
      [0, 20.5554, 0, 0],
      [27.8493, 60.3401, 0, 0],
      [0, 0, 0, 35.8062],
    ],
  },
  {
    id: 'shift',
    label: 'Shift',
    categories: [
      { name: 'Morning', color: BLUE },
      { name: 'Afternoon', color: TEAL },
      { name: 'Night', color: LILAC },
    ],
    values: [
      [148, 164, 55],
      [96, 112, 34],
      [171, 188, 73],
      [52, 49, 22],
      [84, 80, 34],
      [150, 176, 64],
      [196, 214, 90],
      [88, 82, 30],
      [46, 44, 20],
      [162, 180, 70],
      [118, 136, 58],
    ],
  },
  {
    id: 'contract',
    label: 'Contract',
    categories: [
      { name: 'Full-time', color: TEAL },
      { name: 'Contractor', color: SKY },
      { name: 'Intern', color: LILAC },
    ],
    values: [
      [241, 94, 32],
      [168, 52, 22],
      [296, 101, 35],
      [81, 30, 12],
      [132, 44, 22],
      [262, 96, 32],
      [338, 118, 44],
      [141, 41, 18],
      [72, 28, 10],
      [279, 99, 34],
      [205, 80, 27],
    ],
  },
];

type Segment = { name: string; color: string; value: number; height: number; bottom: number };

/**
 * The cards' words. The month and category
 * data above stay in English: their short names also name the autoplay's targets (data-demo).
 */
const COPY: {
  months: { short: string; name: string }[];
  groupings: Record<GroupingId, string>;
  categories: Record<string, string>;
  bank: string;
  period: [period: string, last: string, frequency: string, view: string];
  groupedBy: string;
  groupBy: string;
  byMonth: (grouping: string) => string;
  hours: (count: number) => string;
  monthly: string;
  excellent: [before: string, word: string, after: string];
  person: string;
  stats: Record<string, { label: string; detail: string; value?: string }>;
  none: string;
} = {
  months: months.map(({ short, name }) => ({ short, name })),
  groupings: { department: 'Department', shift: 'Shift', contract: 'Contract' },
  categories: {},
  bank: 'Time bank',
  period: ['Period:', 'Last 6 months', 'Frequency:', 'Monthly view'],
  groupedBy: 'Grouped by:',
  groupBy: 'Group time bank by',
  byMonth: (grouping) => `Time bank by month, grouped by ${grouping}`,
  hours: (count) => `${count} hours`,
  monthly: 'Monthly employee',
  excellent: ['This score is considered to be ', 'Excellent', '.'],
  person: '48 major tasks completed in the last month',
  stats: {
    'stat-horas-extra': { label: 'Overtime', detail: 'No overtime this month' },
    'stat-pontualidade': { label: 'Punctuality', detail: 'Team average: 4.3 of 5' },
  },
  none: 'none',
};

function segmentsFor(grouping: Grouping, index: number): Segment[] {
  const values = grouping.values[index];
  const total = values.reduce((sum, value) => sum + value, 0);
  const heights =
    grouping.heights?.[index] ??
    values.map((value) => (total ? (months[index].height * value) / total : 0));
  let bottom = 0;
  const segments = Array.from({ length: SLOTS }, (_, slot) => {
    const category = grouping.categories[slot];
    const height = heights[slot] ?? 0;
    const segment = {
      name: category?.name ?? '',
      color: category?.color ?? 'transparent',
      value: values[slot] ?? 0,
      height,
      bottom,
    };
    bottom += height;
    return segment;
  });
  return segments;
}

const totalFor = (grouping: Grouping, index: number) =>
  grouping.values[index].reduce((sum, value) => sum + value, 0);

const barLeft = (index: number) => FIRST_BAR + index * BAR_STEP;

/**
 * Bar entrance. The server renders the final chart; on the client the card is only armed
 * (bars collapsed) while it is still off screen, then plays the first time it scrolls into
 * view. A card already on screen when scripts run, or reduced motion, keeps the final state.
 * The phase lives in a DOM attribute React does not render, so hydration never collapses it.
 */
function useEntrance<T extends HTMLElement>() {
  const ref = useRef<T>(null);
  useEffect(() => {
    const node = ref.current;
    if (!node || typeof IntersectionObserver === 'undefined') return;
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const rect = node.getBoundingClientRect();
    const onScreen = rect.bottom > 0 && rect.top < window.innerHeight && rect.height > 0;
    if (onScreen) return;
    node.dataset.phase = 'armed';
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (!entry.isIntersecting) return;
        observer.disconnect();
        node.dataset.phase = 'play';
      },
      { threshold: 0.3 },
    );
    observer.observe(node);
    return () => {
      observer.disconnect();
      if (node.dataset.phase === 'armed') delete node.dataset.phase;
    };
  }, []);
  return ref;
}

function GroupingMenu({
  value,
  onChange,
}: {
  value: GroupingId;
  onChange: (id: GroupingId) => void;
}) {
  const [open, setOpen] = useState(false);
  const menuId = useId();
  const root = useRef<HTMLDivElement>(null);
  const trigger = useRef<HTMLButtonElement>(null);
  const items = useRef<(HTMLButtonElement | null)[]>([]);
  const t = COPY;
  const current = groupings.find((grouping) => grouping.id === value) ?? groupings[0];

  useEffect(() => {
    const nodes = [trigger.current, ...items.current].filter(
      (node): node is HTMLButtonElement => node !== null,
    );
    const cleanups = nodes.map(bindHoverAttribute);
    return () => cleanups.forEach((cleanup) => cleanup());
  }, []);

  useEffect(() => {
    if (!open) {
      // A hidden item gets no pointer "out" event, so drop any hover it was left with.
      items.current.forEach((item) => item?.removeAttribute('data-hover'));
      return;
    }
    // Move focus into the menu only when the person opened it from the trigger; a scripted
    // click (the autoplay demo) must not steal focus or scroll the page.
    if (document.activeElement === trigger.current) {
      items.current[groupings.findIndex((grouping) => grouping.id === value)]?.focus({
        preventScroll: true,
      });
    }
    const close = (event: PointerEvent) => {
      if (!root.current?.contains(event.target as Node)) setOpen(false);
    };
    document.addEventListener('pointerdown', close);
    return () => document.removeEventListener('pointerdown', close);
  }, [open, value]);

  const choose = (id: GroupingId) => {
    const hadFocus = root.current?.contains(document.activeElement) ?? false;
    onChange(id);
    setOpen(false);
    if (hadFocus) trigger.current?.focus({ preventScroll: true });
  };

  const onMenuKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
    const index = items.current.findIndex((item) => item === document.activeElement);
    const last = groupings.length - 1;
    const move = (next: number) => {
      event.preventDefault();
      items.current[next]?.focus();
    };
    if (event.key === 'ArrowDown') move(index >= last ? 0 : index + 1);
    else if (event.key === 'ArrowUp') move(index <= 0 ? last : index - 1);
    else if (event.key === 'Home') move(0);
    else if (event.key === 'End') move(last);
    else if (event.key === 'Escape') {
      event.preventDefault();
      setOpen(false);
      trigger.current?.focus({ preventScroll: true });
    }
  };

  const onBlur = (event: FocusEvent<HTMLDivElement>) => {
    if (!root.current?.contains(event.relatedTarget as Node | null)) setOpen(false);
  };

  return (
    <div ref={root} className={styles.grouping} onBlur={onBlur}>
      <button
        ref={trigger}
        type="button"
        className={styles.groupingTrigger}
        aria-haspopup="menu"
        aria-expanded={open}
        aria-controls={menuId}
        data-demo="grouping"
        onClick={() => setOpen((isOpen) => !isOpen)}
        onKeyDown={(event) => {
          if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
            event.preventDefault();
            setOpen(true);
          }
        }}
      >
        <span>
          {t.groupedBy} <span className={styles.groupingValue}>{t.groupings[current.id]}</span>
        </span>
        <span className={styles.arrowBox} aria-hidden="true">
          <CornerArrow className={styles.arrow} />
        </span>
      </button>
      <div
        id={menuId}
        role="menu"
        aria-label={t.groupBy}
        className={styles.menu}
        hidden={!open}
        onKeyDown={onMenuKeyDown}
      >
        {groupings.map((grouping, index) => (
          <button
            key={grouping.id}
            ref={(node) => {
              items.current[index] = node;
            }}
            type="button"
            role="menuitemradio"
            aria-checked={grouping.id === value}
            tabIndex={-1}
            className={styles.menuItem}
            data-demo={`grouping-${grouping.id}`}
            onClick={() => choose(grouping.id)}
          >
            {t.groupings[grouping.id]}
          </button>
        ))}
      </div>
    </div>
  );
}

function TimeBankCard() {
  const t = COPY;
  const name = (category: string) => t.categories[category] ?? category;
  const titleId = useId();
  const tipId = useId();
  const ref = useEntrance<HTMLElement>();
  const plot = useRef<HTMLDivElement>(null);
  const [groupingId, setGroupingId] = useState<GroupingId>('department');
  const [hovered, setHovered] = useState<number | null>(null);
  const [focused, setFocused] = useState<number | null>(null);
  const [pinned, setPinned] = useState<number | null>(null);
  const [tabStop, setTabStop] = useState(0);
  const columns = useRef<(HTMLButtonElement | null)[]>([]);
  const grouping = groupings.find((item) => item.id === groupingId) ?? groupings[0];
  const active = hovered ?? focused ?? pinned;

  useEffect(() => {
    const cleanups = columns.current.map((node, index) =>
      node
        ? bindHover(
            node,
            () => setHovered(index),
            () => setHovered((current) => (current === index ? null : current)),
          )
        : undefined,
    );
    return () => cleanups.forEach((cleanup) => cleanup?.());
  }, []);

  useEffect(() => {
    if (pinned === null) return;
    const release = (event: PointerEvent) => {
      if (!plot.current?.contains(event.target as Node)) setPinned(null);
    };
    document.addEventListener('pointerdown', release);
    return () => document.removeEventListener('pointerdown', release);
  }, [pinned]);

  const onColumnKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {
    const last = months.length - 1;
    let next: number | null = null;
    if (event.key === 'ArrowRight' || event.key === 'ArrowDown')
      next = index >= last ? 0 : index + 1;
    else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp')
      next = index <= 0 ? last : index - 1;
    else if (event.key === 'Home') next = 0;
    else if (event.key === 'End') next = last;
    else if (event.key === 'Escape') {
      setFocused(null);
      setHovered(null);
      setPinned(null);
      return;
    }
    if (next === null) return;
    event.preventDefault();
    setTabStop(next);
    columns.current[next]?.focus();
  };

  let tooltip = null;
  if (active !== null) {
    const rows = segmentsFor(grouping, active).filter((segment) => segment.value > 0);
    // Padding (5.563 + 6.358), head line and gap, then one 10.646 line per row, in design px.
    const height = 11.921 + 10.646 + 2.384 + rows.length * 10.646;
    const barTop = BASELINE - months[active].height;
    const top = Math.max(22, Math.min(barTop, BASELINE - height));
    const flip = active >= months.length - 2;
    const place: CSSProperties = flip
      ? { top, right: CARD_WIDTH - barLeft(active) + TIP_GAP }
      : { top, left: barLeft(active) + BAR_WIDTH + TIP_GAP };
    tooltip = (
      <div
        id={tipId}
        role="tooltip"
        className={styles.tooltip}
        style={place}
        data-side={flip ? 'left' : 'right'}
      >
        <p className={styles.tooltipHead}>
          <span>{t.months[active].name}</span>
          <strong>{totalFor(grouping, active)} h</strong>
        </p>
        <ul>
          {rows.map((row) => (
            <li key={row.name} className={styles.tooltipRow}>
              <span
                className={styles.swatch}
                style={{ backgroundColor: row.color }}
                aria-hidden="true"
              />
              <span className={styles.tooltipName}>{name(row.name)}</span>
              <span className={styles.tooltipValue}>{row.value} h</span>
            </li>
          ))}
        </ul>
      </div>
    );
  }

  return (
    <section
      role="group"
      ref={ref}
      className={styles.banco}
      aria-labelledby={titleId}
      data-card="bank"
    >
      <span className={styles.bancoBox} data-part="card" aria-hidden="true" />
      <p id={titleId} className={styles.bancoTitle} data-card-title="">
        {t.bank}
      </p>
      <p className={styles.bancoMeta}>
        {t.period[0]} <span className={styles.metaStrong}>{t.period[1]}</span>{' '}
        <span className={styles.metaDivider}>|</span> {t.period[2]}{' '}
        <span className={styles.metaStrong}>{t.period[3]}</span>
      </p>
      <GroupingMenu value={groupingId} onChange={setGroupingId} />
      <DotsIcon className={styles.bancoDots} />
      <ul className={styles.yAxis} aria-hidden="true">
        {[500, 400, 300, 200, 100, 0].map((tick, index) => (
          <li key={tick} style={{ top: 68.803 + index * 2 * 11.6109 }}>
            {tick}
          </li>
        ))}
      </ul>
      <div
        ref={plot}
        role="group"
        aria-label={t.byMonth(t.groupings[grouping.id].toLowerCase())}
        className={styles.plot}
      >
        {months.map((month, index) => {
          const isActive = active === index;
          const segments = segmentsFor(grouping, index);
          return (
            <button
              key={month.short}
              ref={(node) => {
                columns.current[index] = node;
              }}
              type="button"
              className={styles.column}
              tabIndex={index === tabStop ? 0 : -1}
              aria-label={`${t.months[index].name}: ${t.hours(totalFor(grouping, index))}`}
              aria-describedby={isActive ? tipId : undefined}
              aria-pressed={pinned === index}
              data-demo={`bar-${month.short.toLowerCase()}`}
              data-active={isActive || undefined}
              data-dim={(active !== null && !isActive) || undefined}
              style={
                {
                  left: barLeft(index) - (COLUMN_WIDTH - BAR_WIDTH) / 2,
                  '--i': index,
                } as CSSProperties
              }
              onClick={() => setPinned((current) => (current === index ? null : index))}
              onFocus={(event) => {
                setTabStop(index);
                if (event.currentTarget.matches(':focus-visible')) setFocused(index);
              }}
              onBlur={(event) => {
                setFocused(null);
                if (!plot.current?.contains(event.relatedTarget as Node | null)) setPinned(null);
              }}
              onKeyDown={(event) => onColumnKeyDown(event, index)}
            >
              <span
                className={styles.value}
                style={{ bottom: COLUMN_HEIGHT - (BASELINE - COLUMN_TOP) + month.height + 4.61 }}
              >
                {totalFor(grouping, index)}
              </span>
              <span className={styles.bar} style={{ height: month.height }}>
                {segments.map((segment, slot) => (
                  <span
                    key={slot}
                    className={styles.segment}
                    style={{
                      bottom: segment.bottom,
                      height: segment.height,
                      backgroundColor: segment.color,
                    }}
                  />
                ))}
              </span>
              <span className={styles.month}>{t.months[index].short}</span>
            </button>
          );
        })}
      </div>
      {tooltip}
      <CornerGrip className={styles.cornerResize} style={{ left: 718.491, top: 198.698 }} />
    </section>
  );
}

/* ------------------------------------------------------------------ */
/* Monthly employee */
/* ------------------------------------------------------------------ */

type Stat = {
  demo: string;
  value: string;
  label: string;
  detail: string;
  icon: 'money' | 'logo';
};

const stats: Stat[] = [
  {
    demo: 'stat-horas-extra',
    value: '-',
    label: 'Overtime',
    detail: 'No overtime this month',
    icon: 'money',
  },
  {
    demo: 'stat-pontualidade',
    value: '4.9',
    label: 'Punctuality',
    detail: 'Team average: 4.3 of 5',
    icon: 'logo',
  },
];

function MonthlyEmployee() {
  const t = COPY;
  const titleId = useId();
  const tipBase = useId();
  const [hovered, setHovered] = useState<number | null>(null);
  const [focused, setFocused] = useState<number | null>(null);
  const statRefs = useRef<(HTMLDivElement | null)[]>([]);
  const shown = hovered ?? focused;

  useEffect(() => {
    const cleanups = statRefs.current.map((node, index) =>
      node
        ? bindHover(
            node,
            () => setHovered(index),
            () => setHovered((current) => (current === index ? null : current)),
          )
        : undefined,
    );
    return () => cleanups.forEach((cleanup) => cleanup?.());
  }, []);

  return (
    <section role="group" className={styles.monthly} aria-labelledby={titleId} data-card="employee">
      <span className={styles.monthlyBox} data-part="card" aria-hidden="true" />
      <div className={styles.monthlyHeader}>
        <div className={styles.monthlyHeading}>
          <p id={titleId} className={styles.monthlyTitle} data-card-title="">
            {t.monthly}
          </p>
          <p className={styles.monthlyNote}>
            {t.excellent[0]}
            <strong>{t.excellent[1]}</strong>
            {t.excellent[2]}
          </p>
        </div>
        <DotsIcon className={styles.monthlyDots} />
      </div>
      <div className={styles.profile}>
        <div className={styles.person}>
          <div className={styles.avatar}>
            <span className={styles.photo}>
              <span className={styles.initials}>MS</span>
            </span>
            <span className={styles.badge} aria-hidden="true">
              <StarBadge className={styles.badgeIcon} />
            </span>
          </div>
          <div className={styles.personText}>
            <p className={styles.personName}>Mateo Serrano</p>
            <p className={styles.personNote}>{t.person}</p>
          </div>
        </div>
        <ul className={styles.stats}>
          {stats.map((stat, index) => {
            const tipId = `${tipBase}-${index}`;
            const words = t.stats[stat.demo];
            const value = words.value ?? stat.value;
            return (
              <li key={stat.label} className={styles.statSlot}>
                <div
                  ref={(node) => {
                    statRefs.current[index] = node;
                  }}
                  className={styles.stat}
                  tabIndex={0}
                  role="group"
                  aria-label={`${words.label}: ${value === '-' ? t.none : value}`}
                  aria-describedby={tipId}
                  data-demo={stat.demo}
                  data-shown={shown === index || undefined}
                  onFocus={() => setFocused(index)}
                  onBlur={() => setFocused((current) => (current === index ? null : current))}
                  onKeyDown={(event) => {
                    if (event.key === 'Escape') {
                      setFocused(null);
                      setHovered(null);
                    }
                  }}
                >
                  {stat.icon === 'money' ? (
                    <span className={styles.statIcon} aria-hidden="true">
                      <Glyph name="money" className={styles.statGlyph} />
                    </span>
                  ) : (
                    <span
                      className={`${styles.statIcon} ${styles.statIconLogo}`}
                      aria-hidden="true"
                    >
                      <StopwatchIcon className={styles.statGlyph} />
                    </span>
                  )}
                  <span className={styles.statText} aria-hidden="true">
                    <span className={styles.statValue}>{value}</span>
                    <span className={styles.statLabel}>{words.label}</span>
                  </span>
                </div>
                <span id={tipId} role="tooltip" className={styles.statTip} hidden={shown !== index}>
                  {words.detail}
                </span>
              </li>
            );
          })}
        </ul>
      </div>
      <CornerGrip className={styles.cornerResize} style={{ left: 232.079, top: 304.405 }} />
    </section>
  );
}

/** Time bank and Monthly employee. */
export function HoursCards() {
  return (
    <>
      <TimeBankCard />
      <MonthlyEmployee />
    </>
  );
}
```

### workforce-dashboard/hours-cards.module.css

```css
/* Time bank and Monthly employee, positioned in the 1526×881 dashboard frame. */
.banco,
.monthly {
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ink-soft: var(--wf-ink-3);
  --ink-muted: var(--wf-muted-3);
  --accent: var(--wf-sky);
  position: absolute;
}

/* ---------------------------------------------------------------- */
/* Time bank                     */
/* ---------------------------------------------------------------- */
.banco {
  left: 89.017px;
  top: 359.246px;
  width: 729.619px;
  height: 209.825px;
}
.bancoBox,
.monthlyBox {
  position: absolute;
  left: -0.332px;
  top: -0.332px;
  box-sizing: border-box;
  border: 0.663px solid var(--wf-line);
  border-radius: 2.45px;
  background: var(--wf-surface);
  pointer-events: none;
}
.bancoBox {
  width: 730.282px;
  height: 210.488px;
}
.bancoTitle {
  position: absolute;
  left: 15.914px;
  top: 20.399px;
  color: var(--ink-soft);
  font-size: 13.262px;
  font-weight: 500;
  line-height: 16px;
  white-space: nowrap;
}
.bancoMeta {
  position: absolute;
  left: 15.914px;
  top: 39.628px;
  color: var(--ink-muted);
  font-size: 7.957px;
  line-height: 10.646px;
  white-space: nowrap;
}
.metaStrong {
  color: var(--wf-muted-2);
  font-weight: 500;
}
.metaDivider {
  color: var(--wf-divider);
}
.bancoDots {
  position: absolute;
  left: 700.211px;
  top: 20.399px;
  width: 13.2616px;
  height: 13.2616px;
  color: var(--wf-dots);
}
/* 16-dot corner glyph; left and top are set inline per card. */
.cornerResize {
  position: absolute;
  z-index: 2;
  width: 9.28282px;
  height: 9.28263px;
  color: var(--wf-muted-3);
  pointer-events: none;
}

/* Grouped by: Department */
.grouping {
  position: absolute;
  z-index: 3;
  right: 16.414px;
  top: 36.313px;
}
.groupingTrigger {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2.6px;
  height: 10.772px;
  border-radius: 2px;
  color: var(--ink-muted);
  font-size: 7.957px;
  line-height: 10.646px;
  white-space: nowrap;
}
.groupingTrigger::before {
  content: '';
  position: absolute;
  inset: -4px -5px;
}
.groupingTrigger:focus-visible {
  outline: 1.5px solid var(--accent);
  outline-offset: 2px;
}
.groupingValue {
  color: var(--accent);
  font-weight: 500;
  transition: color 160ms ease;
}
.groupingTrigger:hover .groupingValue,
.groupingTrigger[data-hover] .groupingValue {
  color: var(--wf-sky-strong);
}
.arrowBox {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 4.262px;
  height: 4.23px;
  transition: transform 220ms var(--ease-out);
}
.groupingTrigger[aria-expanded='true'] .arrowBox {
  transform: rotate(180deg);
}
.arrow {
  display: block;
  width: 3px;
  height: 3px;
  color: var(--wf-sky);
  transform: rotate(45deg) scaleX(-1);
}
.menu {
  position: absolute;
  top: calc(100% + 5px);
  right: -4px;
  display: grid;
  gap: 1px;
  min-width: 80px;
  padding: 3px;
  border-radius: 5px;
  background: var(--wf-surface);
  box-shadow:
    0 0 0 0.663px var(--wf-line),
    0 6px 16px color(display-p3 0.055 0.071 0.106 / 0.1);
  transform-origin: 100% 0;
  animation: pop 150ms var(--ease-out);
}
.menu[hidden] {
  display: none;
}
.menuItem {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 3.5px 6px;
  border-radius: 3px;
  color: var(--ink-soft);
  font-size: 7.957px;
  line-height: 10.646px;
  text-align: left;
  white-space: nowrap;
  transition: background-color 120ms ease;
}
.menuItem:hover,
.menuItem[data-hover] {
  background: var(--wf-surface-blue);
}
.menuItem:focus-visible {
  outline: none;
  background: var(--wf-surface-blue);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.menuItem[aria-checked='true'] {
  color: var(--accent);
  font-weight: 500;
}
.menuItem[aria-checked='true']::after {
  content: '';
  width: 4.5px;
  height: 2.4px;
  border-bottom: 1px solid currentColor;
  border-left: 1px solid currentColor;
  transform: translateY(-1px) rotate(-45deg);
}

/* Y axis: one label on every other 11.61px line, 500 → 0 */
.yAxis {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.yAxis li {
  position: absolute;
  left: 15.914px;
  /* Inter sits ~1.5px higher than the design font (the design font) in the same line box. */
  translate: 0 1.5px;
  height: 11.611px;
  color: var(--ink-soft);
  font-size: 6.631px;
  line-height: 11.611px;
  white-space: nowrap;
}

/* Month columns: bars sit on y = 185.5 in card space */
.plot {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.column {
  position: absolute;
  z-index: 1;
  top: 56px;
  width: 63.6554px;
  height: 150px;
  border-radius: 4px;
  pointer-events: auto;
  transition:
    opacity 180ms ease,
    background-color 180ms ease;
}
.column[data-active] {
  background-color: color-mix(in srgb, var(--wf-sky) 6%, transparent);
}
.column[data-dim] {
  opacity: 0.35;
}
.column:focus-visible {
  outline: 1.5px solid var(--accent);
  outline-offset: -1.5px;
}
.bar {
  position: absolute;
  left: 13.925px;
  bottom: 20.495px;
  width: 35.806px;
  overflow: hidden;
  border-radius: 1.3262px;
  transform-origin: 50% 100%;
}
.segment {
  position: absolute;
  left: 0;
  width: 100%;
  transition:
    bottom 620ms var(--ease-out),
    height 620ms var(--ease-out),
    background-color 620ms ease;
  transition-delay: calc(var(--i) * 28ms);
}
.value {
  position: absolute;
  left: 0;
  width: 100%;
  color: var(--wf-ink-4);
  font-size: 7.957px;
  font-weight: 500;
  line-height: 7.985px;
  text-align: center;
  white-space: nowrap;
}
.month {
  position: absolute;
  left: 0;
  top: 137.462px;
  /* the design font → Inter baseline correction for the 10.772px box, measured against the design. */
  translate: 0 0.75px;
  width: 100%;
  color: var(--ink-soft);
  font-size: 6.631px;
  line-height: 10.772px;
  text-align: center;
  white-space: nowrap;
}

/* Entrance: the server renders the final chart; the client arms it (collapsed) only while the
   card is off screen, and bars grow from the baseline once it scrolls into view. */
.banco[data-phase='armed'] .bar {
  transform: scaleY(0);
}
.banco[data-phase='armed'] .value {
  opacity: 0;
}
@media print {
  .banco[data-phase='armed'] .bar {
    transform: none;
  }
  .banco[data-phase='armed'] .value {
    opacity: 1;
  }
}
.banco[data-phase='play'] .bar {
  animation: grow 760ms var(--ease-out) calc(var(--i) * 55ms) both;
}
.banco[data-phase='play'] .value {
  animation: rise 360ms ease-out calc(var(--i) * 55ms + 440ms) both;
}

.column[aria-pressed='true'] {
  background-color: color-mix(in srgb, var(--wf-sky) 10%, transparent);
}

/* Breakdown tooltip, styled after Tooltip [1.0] at the dashboard's 0.795 scale. */
.tooltip {
  position: absolute;
  z-index: 2;
  width: 106px;
  padding: 5.563px 7.948px 6.358px;
  border-radius: 4.769px;
  color: #fff;
  background: var(--wf-tooltip);
  box-shadow:
    0 9.537px 19.075px color(display-p3 0.055 0.071 0.106 / 0.06),
    0 0.795px 1.59px color(display-p3 0.055 0.071 0.106 / 0.03);
  font-size: 7.957px;
  line-height: 10.646px;
  pointer-events: none;
  animation: pop 140ms var(--ease-out);
}
/* Tail turned to point at the bar. */
.tooltip::before {
  content: '';
  position: absolute;
  top: 6.05px;
  width: 4.769px;
  height: 9.537px;
  background: inherit;
}
.tooltip[data-side='right']::before {
  right: 100%;
  clip-path: polygon(100% 0, 0 50%, 100% 100%);
}
.tooltip[data-side='left']::before {
  left: 100%;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.tooltipHead {
  display: flex;
  justify-content: space-between;
  gap: 7.948px;
  margin-bottom: 2.384px;
  font-weight: 600;
  white-space: nowrap;
}
.tooltipHead strong {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.tooltipRow {
  display: flex;
  align-items: center;
  gap: 3.974px;
  white-space: nowrap;
}
.swatch {
  flex: none;
  width: 5.563px;
  height: 5.563px;
  border-radius: 50%;
}
.tooltipName {
  flex: 1;
  color: var(--wf-tooltip-muted);
}
.tooltipValue {
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------- */
/* Monthly employee                 */
/* ---------------------------------------------------------------- */
.monthly {
  left: 1255.771px;
  top: 460.979px;
  width: 244.796px;
  height: 316.327px;
  font-feature-settings:
    'cv09' 1,
    'ss11' 1,
    'calt' 0,
    'liga' 0;
}
.monthlyBox {
  width: 245.459px;
  height: 316.99px;
}
.monthlyHeader {
  position: absolute;
  left: 12.717px;
  top: 12.717px;
  display: flex;
  align-items: flex-start;
  gap: 12.717px;
  width: 221.747px;
}
.monthlyHeading {
  display: flex;
  flex: 1 0 0;
  flex-direction: column;
  gap: 3.179px;
  min-width: 0;
}
.monthlyTitle {
  color: var(--ink-soft);
  font-size: 13.262px;
  font-weight: 600;
  line-height: 16px;
}
.monthlyNote {
  color: var(--wf-muted-2);
  font-size: 7.957px;
  line-height: 10.646px;
}
.monthlyNote strong {
  font-weight: 700;
}
.monthlyDots {
  flex: none;
  color: var(--wf-dots);
  width: 13.2859px;
  height: 13.2616px;
}
.profile {
  position: absolute;
  left: 12.717px;
  top: 72.326px;
  display: flex;
  flex-direction: column;
  gap: 22.254px;
  width: 220.157px;
}
.person {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9.537px;
}
.avatar {
  position: relative;
  flex: none;
  width: 44.508px;
  height: 44.508px;
  border-radius: 999px;
  background: var(--wf-accent-soft);
}
.photo {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 999px;
}
.initials {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--wf-accent-soft);
  color: var(--wf-accent);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.02em;
}
.badge {
  position: absolute;
  left: 30.203px;
  top: 0;
  width: 19.072px;
  height: 19.076px;
}
.badgeIcon {
  display: block;
  position: absolute;
  left: -1.987px;
  top: -0.397px;
  width: 23.049px;
  height: 23.049px;
}
.personText {
  display: flex;
  flex-direction: column;
  gap: 3.179px;
  width: 100%;
  color: var(--ink-soft);
  text-align: center;
}
.personName {
  min-height: 20px;
  font-size: 14.306px;
  font-weight: 600;
  line-height: 19.075px;
  letter-spacing: -0.2146px;
}
.personNote {
  min-height: 16px;
  font-size: 9.537px;
  line-height: 15.896px;
  letter-spacing: -0.0572px;
}
.stats {
  display: flex;
  gap: 12.717px;
  width: 100%;
}
.statSlot {
  position: relative;
  flex: 1 0 0;
  min-width: 0;
}
.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9.537px;
  height: 92.583px;
  padding: 11.127px 9.537px;
  border-radius: 7.948px;
  background: var(--wf-surface-2);
  box-shadow: inset 0 0 0 0.795px var(--wf-line);
  transition:
    background-color 180ms ease,
    box-shadow 180ms ease,
    transform 220ms var(--ease-out);
}
.stat:hover,
.stat[data-shown] {
  background: var(--wf-surface);
  box-shadow:
    inset 0 0 0 0.795px var(--wf-accent-soft),
    0 3px 8px color(display-p3 0.055 0.071 0.106 / 0.06);
  transform: translateY(-1px);
}
.stat:focus-visible {
  outline: 1.5px solid var(--accent);
  outline-offset: 1.5px;
}
.statIcon {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 31.792px;
  height: 31.792px;
  border-radius: 999px;
  background: var(--wf-surface);
  box-shadow:
    inset 0 0 0 0.795px var(--wf-line),
    0 0.795px 1.59px rgba(10, 13, 20, 0.03);
  transition:
    transform 220ms var(--ease-out),
    box-shadow 180ms ease;
}
.statIconLogo {
  width: 32.322px;
  box-shadow:
    inset 0 0 0 1.06px var(--wf-line),
    0 1.06px 2.119px rgba(10, 13, 20, 0.03);
}
.stat:hover .statIcon,
.stat[data-shown] .statIcon {
  box-shadow:
    inset 0 0 0 0.795px var(--wf-accent-soft),
    0 1.5px 3px rgba(10, 13, 20, 0.06);
  transform: scale(1.06);
}
.statGlyph {
  display: block;
  width: 15.8958px;
  height: 15.8958px;
  color: var(--wf-ink-2);
}
.statIconLogo .statGlyph {
  width: 11.1272px;
  height: 14.8364px;
  color: var(--wf-accent);
}
.statText {
  display: flex;
  flex-direction: column;
  width: 100%;
  text-align: center;
}
.statValue {
  height: 16px;
  color: var(--wf-ink);
  font-size: 11.127px;
  font-weight: 500;
  line-height: 15.896px;
  letter-spacing: -0.0668px;
}
.statLabel {
  height: 13px;
  color: var(--wf-ink-2);
  font-size: 9.537px;
  line-height: 12.717px;
}
.statTip {
  position: absolute;
  z-index: 2;
  top: calc(100% + 6.358px);
  left: 50%;
  translate: -50% 0;
  padding: 2.384px 6.358px 3.179px;
  border-radius: 4.769px;
  color: #fff;
  background: var(--wf-tooltip);
  box-shadow:
    0 9.537px 19.075px color(display-p3 0.055 0.071 0.106 / 0.06),
    0 0.795px 1.59px color(display-p3 0.055 0.071 0.106 / 0.03);
  font-size: 7.957px;
  line-height: 10.646px;
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
  animation: pop 140ms var(--ease-out);
}
.statTip::before {
  content: '';
  position: absolute;
  bottom: 100%;
  left: 50%;
  translate: -50% 0;
  width: 9.537px;
  height: 4.769px;
  background: inherit;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.statTip[hidden] {
  display: none;
}

@keyframes grow {
  from {
    transform: scaleY(0);
  }
  to {
    transform: scaleY(1);
  }
}
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(3px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes pop {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .banco .bar,
  .banco .value {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .segment,
  .column,
  .arrowBox,
  .menuItem,
  .stat,
  .statIcon {
    transition: none;
  }
  .stat:hover,
  .stat[data-shown],
  .stat:hover .statIcon,
  .stat[data-shown] .statIcon {
    transform: none;
  }
  .menu,
  .tooltip,
  .statTip {
    animation: none;
  }
}
```

### workforce-dashboard/trend-cards.tsx

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

'use client';

import {
  useEffect,
  useId,
  useRef,
  useState,
  type CSSProperties,
  type KeyboardEvent,
  type ReactNode,
} from 'react';
import { useHydratedReducedMotion } from './use-reduced-motion';
import type { Person, PersonProps } from './types';
import { CornerGrip, DotsIcon, LegendDot, TooltipTail } from './icons';
import styles from './trend-cards.module.css';

/** Hours with a thousands separator: 5,000. */
const HOURS = new Intl.NumberFormat('en-US');

/* ------------------------------------------------------------------ data */

type DepartmentId = 'comercial' | 'tecnologia' | 'financeiro';

/** Card ids (data-card), which the autoplay finds them by. */
const cardIds = {
  departments: { id: 'departments' },
  monthly: { id: 'monthly-bank' },
  people: { id: 'people' },
} as const;

/**
 * `width` is the Chart Legends instance box: dot and gap (15.896) plus the
 * label's whole-pixel text box, as the design sizes it, so the legends keep the design's spacing.
 */
/** The legend's dots, a shade apart from the bars they name. */
const LEGEND: Record<DepartmentId, string> = {
  comercial: '#335cff',
  tecnologia: '#47c2ff',
  financeiro: '#7d52f4',
};

const departments: { id: DepartmentId; label: string; color: string; width: number }[] = [
  {
    id: 'comercial',
    label: 'Commercial',
    color: 'color(display-p3 0.259 0.557 0.937)',
    width: 62.896,
  },
  {
    id: 'tecnologia',
    label: 'Technology',
    color: 'color(display-p3 0.396 0.71 0.965)',
    width: 62.896,
  },
  {
    id: 'financeiro',
    label: 'Finance',
    color: 'color(display-p3 0.49 0.322 0.957)',
    width: 45.896,
  },
];

/** Stacked column geometry: one 12.717px step stands for 1.000 h. */
const PX_PER_HOUR = 12.7167 / 1000;
const TRACK_HEIGHT = 145.958;
const TRACK_TOP = 115.245;
const SEGMENT_GAP = 1.5896;

const months: {
  slug: string;
  short: string;
  name: string;
  left: number;
  width: number;
  hours: Record<DepartmentId, number>;
}[] = [
  {
    short: 'J',
    slug: 'jan',
    name: 'January',
    left: 50.867,
    width: 33.381,
    hours: { comercial: 5000, tecnologia: 2000, financeiro: 1000 },
  },
  {
    short: 'F',
    slug: 'feb',
    name: 'February',
    left: 103.323,
    width: 33.381,
    hours: { comercial: 0, tecnologia: 2000, financeiro: 1000 },
  },
  {
    short: 'M',
    slug: 'mar',
    name: 'March',
    left: 155.779,
    width: 32.586,
    hours: { comercial: 5000, tecnologia: 0, financeiro: 1000 },
  },
  {
    short: 'A',
    slug: 'apr',
    name: 'April',
    left: 208.235,
    width: 32.586,
    hours: { comercial: 1875, tecnologia: 2000, financeiro: 0 },
  },
  {
    short: 'M',
    slug: 'may',
    name: 'May',
    left: 259.897,
    width: 33.381,
    hours: { comercial: 3125, tecnologia: 2000, financeiro: 1000 },
  },
  {
    short: 'J',
    slug: 'jun',
    name: 'June',
    left: 312.353,
    width: 33.381,
    hours: { comercial: 5000, tecnologia: 2000, financeiro: 0 },
  },
  {
    short: 'J',
    slug: 'jul',
    name: 'July',
    left: 364.809,
    width: 33.381,
    hours: { comercial: 4750, tecnologia: 2000, financeiro: 1000 },
  },
];

/** Daily hours bank for December, in minutes. Fractional days keep the design's curve. */
const monthlyBank = [
  { day: 1, minutes: 0 },
  { day: 1.8, minutes: 0 },
  { day: 4.17, minutes: 2880 },
  { day: 6.36, minutes: 2880 },
  { day: 8.17, minutes: 1424 },
  { day: 9.85, minutes: 1424 },
  { day: 11.72, minutes: 381 },
  { day: 13.03, minutes: 381 },
  { day: 14.92, minutes: 1424 },
  { day: 17.92, minutes: 1424 },
  { day: 19.77, minutes: 2880 },
  { day: 22.09, minutes: 2880 },
  { day: 24.33, minutes: 1424 },
  { day: 25.61, minutes: 1424 },
  { day: 29.27, minutes: 2880 },
  { day: 31, minutes: 2880 },
];
const DAYS = 31;
const PLOT_WIDTH = 323.48;
const GRID_HEIGHT = 107.629;
const LINE_BOTTOM = 71.532;
const LINE_RANGE = 34.971;
const MAX_MINUTES = 2880;
const CORNER_RADIUS = 6.358;
const monthlyTicks = [
  { label: '0', left: 0 },
  { label: '2k', left: 61.517 },
  { label: '3k', left: 123.034 },
  { label: '4k', left: 184.551 },
  { label: '5k', left: 246.067 },
  { label: '100', left: 307.584 },
];

type Contributor = {
  name: string;
  person?: Exclude<Person, 'all'>;
  top: number;
  seconds: number;
  color: string;
  marker: string;
};

/** Positive hours bank per person; 104:54:32 is the value designed for Mateo. */
const contributors: Contributor[] = [
  {
    name: 'Kiran',
    top: 0,
    seconds: 506462,
    color: 'color(display-p3 0.839 0.647 0.988)',
    marker: 'color(display-p3 0.71 0.455 0.961)',
  },
  {
    name: 'Mateo',
    person: 'mateo',
    top: 19.87,
    seconds: 377672,
    color: 'color(display-p3 0.396 0.71 0.965)',
    marker: 'color(display-p3 0.278 0.761 1)',
  },
  {
    name: 'Ava',
    person: 'ava',
    top: 39.74,
    seconds: 232967,
    color: 'color(display-p3 0.49 0.322 0.957)',
    marker: 'color(display-p3 0.612 0.49 1)',
  },
  {
    name: 'Rosana',
    person: 'rosana',
    top: 61.199,
    seconds: 164968,
    color: 'color(display-p3 0.259 0.557 0.937)',
    marker: 'color(display-p3 0.2 0.361 1)',
  },
];
const BAR_LEFT = 49.277;
const BARS_WIDTH = 336.197;
/** Half of the 68.9px Tooltip [1.0] body; keeps the body inside the chart. */
const CLOCK_TIP_HALF = 34.448;
const PX_PER_SECOND = 207.4406 / 377672;
const barTicks = [
  { label: '0', left: 57.225 },
  { label: '2k', left: 111.112 },
  { label: '4k', left: 164.999 },
  { label: '6k', left: 218.885 },
  { label: '8k', left: 272.773 },
  { label: '10k', left: 326.659 },
];

/** The cards' words. */
const COPY: {
  departments: Record<DepartmentId, string>;
  /** Legend widths (dot, gap and label) for labels wider than the English ones. */
  legendWidths: Partial<Record<DepartmentId, number>>;
  months: { short: string; name: string }[];
  excellent: [before: string, word: string, after: string];
  byDepartment: string;
  shown: string;
  byMonth: string;
  noneSelected: string;
  noDepartments: string;
  total: string;
  monthly: string;
  day: (day: number) => string;
  dayValue: (day: number, hours: number, minutes: number) => string;
  byDay: string;
  people: string;
  filter: string;
  positive: string;
} = {
  departments: { comercial: 'Commercial', tecnologia: 'Technology', financeiro: 'Finance' },
  legendWidths: {},
  months: months.map(({ short, name }) => ({ short, name })),
  excellent: ['This score is considered to be ', 'Excellent', '.'],
  byDepartment: 'Time bank by department',
  shown: 'Departments shown',
  byMonth: 'Time bank by month and department',
  noneSelected: 'no department selected',
  noDepartments: 'No departments',
  total: 'Total',
  monthly: 'Monthly time bank',
  day: (day) => `Dec ${day}`,
  dayValue: (day, h, m) => `December ${day}: ${h} h ${m} min`,
  byDay: 'Time bank by day in December',
  people: 'Positive time bank by employee',
  filter: 'Filter dashboard by employee',
  positive: 'of positive time bank',
};

/* --------------------------------------------------------------- helpers */

const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
const round2 = (value: number) => Math.round(value * 100) / 100;

function formatClock(seconds: number) {
  const h = Math.floor(seconds / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  const s = seconds % 60;
  return [h, m, s].map((part) => String(part).padStart(2, '0')).join(':');
}

function formatMinutes(minutes: number) {
  return `${Math.floor(minutes / 60)}:${String(minutes % 60).padStart(2, '0')}`;
}

function minutesOn(day: number) {
  for (let i = 1; i < monthlyBank.length; i += 1) {
    const a = monthlyBank[i - 1];
    const b = monthlyBank[i];
    if (day <= b.day)
      return Math.round(a.minutes + ((day - a.day) / (b.day - a.day)) * (b.minutes - a.minutes));
  }
  return monthlyBank[monthlyBank.length - 1].minutes;
}

const xOfDay = (day: number) => ((day - 1) / (DAYS - 1)) * PLOT_WIDTH;
const yOfMinutes = (minutes: number) => LINE_BOTTOM - (minutes / MAX_MINUTES) * LINE_RANGE;

/** Polyline with arc-rounded joins, like the design's 8pt corner radius (scaled). */
function roundedPath(points: [number, number][], radius: number) {
  let d = `M${round2(points[0][0])} ${round2(points[0][1])}`;
  for (let i = 1; i < points.length - 1; i += 1) {
    const [ax, ay] = points[i - 1];
    const [px, py] = points[i];
    const [bx, by] = points[i + 1];
    const v1x = ax - px;
    const v1y = ay - py;
    const v2x = bx - px;
    const v2y = by - py;
    const l1 = Math.hypot(v1x, v1y);
    const l2 = Math.hypot(v2x, v2y);
    const turn = Math.PI - Math.acos(clamp((v1x * v2x + v1y * v2y) / (l1 * l2), -1, 1));
    if (turn < 0.001) {
      d += ` L${round2(px)} ${round2(py)}`;
      continue;
    }
    const tangent = Math.min(radius * Math.tan(turn / 2), l1 / 2, l2 / 2);
    const r = tangent / Math.tan(turn / 2);
    const sweep = (px - ax) * (by - py) - (py - ay) * (bx - px) > 0 ? 1 : 0;
    d += ` L${round2(px + (v1x / l1) * tangent)} ${round2(py + (v1y / l1) * tangent)}`;
    d += ` A${round2(r)} ${round2(r)} 0 0 ${sweep} ${round2(px + (v2x / l2) * tangent)} ${round2(py + (v2y / l2) * tangent)}`;
  }
  const [lx, ly] = points[points.length - 1];
  return `${d} L${round2(lx)} ${round2(ly)}`;
}

const linePoints = monthlyBank.map(
  ({ day, minutes }) => [xOfDay(day), yOfMinutes(minutes)] as [number, number],
);
const linePath = roundedPath(linePoints, CORNER_RADIUS);
const areaPath = `${linePath} L${PLOT_WIDTH} ${LINE_BOTTOM} L0 ${LINE_BOTTOM} Z`;

const SHOW_EVENTS = [
  'pointerover',
  'pointerenter',
  'pointermove',
  'mouseover',
  'mouseenter',
  'mousemove',
] as const;
const HIDE_EVENTS = ['pointerleave', 'pointerout', 'mouseleave', 'mouseout'] as const;

/**
 * Hover for every `[data-hover]` item inside the returned ref. Listens in the capture phase so
 * non-bubbling enter/leave events dispatched programmatically (the autoplay director) work too,
 * not only the pointerover/pointerout pair React derives its enter/leave handlers from.
 */
function useHoverItems<T extends HTMLElement>(
  onShow: (item: HTMLElement, event: MouseEvent) => void,
  onHide: (item: HTMLElement) => void,
) {
  const ref = useRef<T>(null);
  const handlers = useRef({ onShow, onHide });
  useEffect(() => {
    handlers.current = { onShow, onHide };
  });
  useEffect(() => {
    const root = ref.current;
    if (!root) return;
    const itemOf = (event: Event) => {
      const item = event.target instanceof Element ? event.target.closest('[data-hover]') : null;
      return item instanceof HTMLElement && root.contains(item) ? item : null;
    };
    const show = (event: Event) => {
      const item = itemOf(event);
      if (item) handlers.current.onShow(item, event as MouseEvent);
    };
    const hide = (event: Event) => {
      const item = itemOf(event);
      const related = (event as MouseEvent).relatedTarget;
      if (!item || (related instanceof Node && item.contains(related))) return;
      handlers.current.onHide(item);
    };
    for (const type of SHOW_EVENTS) root.addEventListener(type, show, true);
    for (const type of HIDE_EVENTS) root.addEventListener(type, hide, true);
    return () => {
      for (const type of SHOW_EVENTS) root.removeEventListener(type, show, true);
      for (const type of HIDE_EVENTS) root.removeEventListener(type, hide, true);
    };
  }, []);
  return ref;
}

/** Starts the entrance once the card scrolls into view; reduced motion shows the final state. */
function useReveal<T extends Element>() {
  const ref = useRef<T>(null);
  const [inView, setInView] = useState(false);
  const reducedMotion = useHydratedReducedMotion();
  useEffect(() => {
    const node = ref.current;
    if (!node || inView) return;
    const observer = new IntersectionObserver(
      (entries) => {
        if (!entries.some((entry) => entry.isIntersecting)) return;
        setInView(true);
        observer.disconnect();
      },
      { threshold: 0.25 },
    );
    observer.observe(node);
    // Print the finished chart even if it was never scrolled into view.
    const onBeforePrint = () => setInView(true);
    window.addEventListener('beforeprint', onBeforePrint);
    return () => {
      observer.disconnect();
      window.removeEventListener('beforeprint', onBeforePrint);
    };
  }, [inView]);
  return [ref, inView || reducedMotion] as const;
}

/* ------------------------------------------------------------ primitives */

/** The 16-dot corner resize glyph; it belongs to the card so it travels with it. */
function ResizeGlyph() {
  return <CornerGrip className={styles.resize} />;
}

function CardHeader({ id, title, children }: { id: string; title: string; children?: ReactNode }) {
  const t = COPY;
  const subtitle = (
    <p className={styles.subtitle}>
      {t.excellent[0]}
      <b>{t.excellent[1]}</b>
      {t.excellent[2]}
    </p>
  );
  return (
    <div className={styles.header}>
      <div className={styles.headerText}>
        {children ? (
          <div className={styles.titleGroup}>
            <p id={id} className={styles.title} data-card-title="">
              {title}
            </p>
            {subtitle}
          </div>
        ) : (
          <>
            <p id={id} className={styles.title} data-card-title="">
              {title}
            </p>
            {subtitle}
          </>
        )}
        {children}
      </div>
      <DotsIcon className={styles.more} />
    </div>
  );
}

function Tooltip({
  left,
  top,
  shift = 0,
  className,
  children,
}: {
  left: number;
  top: number;
  shift?: number;
  className?: string;
  children: ReactNode;
}) {
  return (
    <div
      className={`${styles.tip} ${className ?? ''}`}
      style={{ left, top, '--shift': `${shift}px` } as CSSProperties}
      aria-hidden="true"
    >
      <div className={styles.tipBody}>{children}</div>
      <div className={styles.tipTail}>
        <TooltipTail className={styles.tailIcon} />
      </div>
    </div>
  );
}

/* ----------------------------------------------- Time bank by department */

function DepartmentsCard() {
  const t = COPY;
  const hours = HOURS;
  const titleId = useId();
  const chartId = useId();
  const [ref, revealed] = useReveal<HTMLElement>();
  const [visible, setVisible] = useState<Record<DepartmentId, boolean>>({
    comercial: true,
    tecnologia: true,
    financeiro: true,
  });
  const [hovered, setHovered] = useState<number | null>(null);
  const [focused, setFocused] = useState<number | null>(null);
  const active = hovered ?? focused;
  const hoverRef = useHoverItems<HTMLDivElement>(
    (item) => setHovered(Number(item.dataset.hover)),
    (item) => {
      const index = Number(item.dataset.hover);
      setHovered((current) => (current === index ? null : current));
    },
  );

  const shown = departments.filter((department) => visible[department.id]);
  const stackHeight = (index: number) =>
    shown.reduce((sum, { id }) => {
      const value = months[index].hours[id];
      return value > 0 ? sum + value * PX_PER_HOUR + SEGMENT_GAP : sum;
    }, 0);
  const columnCenter = (index: number) => months[index].left + months[index].width / 2;
  const describe = (index: number) => {
    const month = months[index];
    const parts = shown.map(({ id }) => `${t.departments[id]} ${hours.format(month.hours[id])} h`);
    return `${t.months[index].name}: ${parts.length ? parts.join(', ') : t.noneSelected}`;
  };

  return (
    <section
      role="group"
      ref={ref}
      className={`${styles.card} ${styles.departments}`}
      aria-labelledby={titleId}
      data-card={cardIds.departments.id}
      data-revealed={revealed}
    >
      <CardHeader id={titleId} title={t.byDepartment}>
        <div className={styles.legends} role="group" aria-label={t.shown}>
          {departments.map((department) => (
            <button
              key={department.id}
              type="button"
              className={`${styles.legend} ${styles.focusable}`}
              style={{ width: t.legendWidths[department.id] ?? department.width }}
              data-demo={`legend-${department.id}`}
              aria-pressed={visible[department.id]}
              aria-controls={chartId}
              onClick={() =>
                setVisible((current) => ({ ...current, [department.id]: !current[department.id] }))
              }
            >
              <LegendDot className={styles.legendDot} color={LEGEND[department.id]} />
              <span className={styles.legendLabel}>{t.departments[department.id]}</span>
            </button>
          ))}
        </div>
      </CardHeader>

      <div className={styles.yAxis} aria-hidden="true">
        <span>20k</span>
        <span>15k</span>
        <span>10k</span>
        <span>0</span>
      </div>

      <div ref={hoverRef} id={chartId} role="group" aria-label={t.byMonth}>
        {months.map((month, index) => (
          <button
            key={month.name}
            type="button"
            className={`${styles.column} ${styles.focusable}`}
            style={{ left: month.left, width: month.width }}
            data-hover={index}
            data-demo={`dept-${month.slug}`}
            data-active={active === index}
            aria-label={describe(index)}
            onFocus={() => setFocused(index)}
            onBlur={() => setFocused((current) => (current === index ? null : current))}
          >
            <span className={styles.track} aria-hidden="true">
              <span className={styles.empty} />
              {departments.map(({ id, color }) => {
                const on = revealed && visible[id] && month.hours[id] > 0;
                return (
                  <span
                    key={id}
                    className={styles.segment}
                    style={{
                      height: on ? month.hours[id] * PX_PER_HOUR : 0,
                      marginTop: on ? SEGMENT_GAP : 0,
                      background: color,
                      transitionDelay: `${index * 45}ms`,
                    }}
                  />
                );
              })}
            </span>
            <span className={styles.columnLabel} aria-hidden="true">
              {t.months[index].short}
            </span>
          </button>
        ))}
      </div>

      {active !== null && (
        <Tooltip
          className={styles.columnTip}
          left={columnCenter(active)}
          top={TRACK_TOP + TRACK_HEIGHT - (revealed ? stackHeight(active) : 0) - 3.179}
          // Keep the 106px body inside the card; the tail stays on the column.
          shift={clamp(0, 57 - columnCenter(active), 356 - columnCenter(active))}
        >
          <p className={styles.tipTitle}>{t.months[active].name}</p>
          {shown.length ? (
            shown.map(({ id, color }) => (
              <p key={id} className={styles.tipRow}>
                <span className={styles.tipDot} style={{ background: color }} />
                <span>{t.departments[id]}</span>
                <span>{hours.format(months[active].hours[id])} h</span>
              </p>
            ))
          ) : (
            <p className={styles.tipRow}>
              <span>{t.noDepartments}</span>
            </p>
          )}
          {shown.length > 1 && (
            <p className={`${styles.tipRow} ${styles.tipTotal}`}>
              <span className={styles.tipDot} />
              <span>{t.total}</span>
              <span>
                {hours.format(shown.reduce((sum, { id }) => sum + months[active].hours[id], 0))} h
              </span>
            </p>
          )}
        </Tooltip>
      )}
      <ResizeGlyph />
    </section>
  );
}

/* ----------------------------------------------------- Monthly time bank */

function MonthlyCard() {
  const t = COPY;
  const titleId = useId();
  const gradientId = useId();
  const [ref, revealed] = useReveal<HTMLElement>();
  const [day, setDay] = useState<number | null>(null);
  const [focused, setFocused] = useState(false);

  const minutes = day === null ? 0 : minutesOn(day);
  const x = day === null ? 0 : xOfDay(day);
  const y = yOfMinutes(minutes);
  const valueText =
    day === null ? undefined : t.dayValue(day, Math.floor(minutes / 60), minutes % 60);

  /** Ratio of the rendered rect, so it holds while the dashboard is CSS-scaled. */
  const dayAt = (probe: Element, clientX: number) => {
    const rect = probe.getBoundingClientRect();
    const ratio = rect.width ? clamp((clientX - rect.left) / rect.width, 0, 1) : 0;
    setDay(Math.round(1 + ratio * (DAYS - 1)));
  };
  const hoverRef = useHoverItems<HTMLDivElement>(
    (probe, event) => dayAt(probe, event.clientX),
    () => {
      if (!focused) setDay(null);
    },
  );
  const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
    const current = day ?? 1;
    const next: Record<string, number> = {
      ArrowRight: current + 1,
      ArrowUp: current + 1,
      ArrowLeft: current - 1,
      ArrowDown: current - 1,
      PageUp: current + 7,
      PageDown: current - 7,
      Home: 1,
      End: DAYS,
    };
    if (!(event.key in next)) return;
    event.preventDefault();
    setDay(clamp(next[event.key], 1, DAYS));
  };

  return (
    <section
      role="group"
      ref={ref}
      className={`${styles.card} ${styles.monthly}`}
      aria-labelledby={titleId}
      data-card={cardIds.monthly.id}
      data-revealed={revealed}
    >
      <CardHeader id={titleId} title={t.monthly} />

      <div ref={hoverRef} className={styles.plot}>
        <svg
          className={styles.plotSvg}
          width={PLOT_WIDTH}
          height={123.988}
          viewBox={`0 0 ${PLOT_WIDTH} 123.988`}
          aria-hidden="true"
        >
          <defs>
            <linearGradient
              id={gradientId}
              x1="161.74"
              x2="161.74"
              y1="49.768"
              y2={LINE_BOTTOM}
              gradientUnits="userSpaceOnUse"
            >
              <stop style={{ stopColor: 'var(--wf-grid-line)' }} />
              <stop offset="1" style={{ stopColor: 'var(--wf-grid-line-2)', stopOpacity: 0 }} />
            </linearGradient>
          </defs>
          {monthlyTicks.map((tick) => (
            <line
              key={tick.label}
              x1={tick.left + 7.948}
              x2={tick.left + 7.948}
              y1="0"
              y2={GRID_HEIGHT}
              style={{ stroke: 'var(--wf-line)' }}
              strokeWidth="0.795"
              strokeDasharray="1.59 1.59"
            />
          ))}
          <path
            className={styles.area}
            d={areaPath}
            fill={`url(#${gradientId})`}
            fillOpacity="0.06"
          />
          <path
            className={styles.line}
            d={linePath}
            pathLength={1}
            fill="none"
            style={{ stroke: 'var(--wf-accent)' }}
            strokeWidth="1.59"
          />
          {day !== null && (
            <g className={styles.crosshair}>
              <line
                x1={x}
                x2={x}
                y1="0"
                y2={GRID_HEIGHT}
                style={{ stroke: 'var(--wf-accent)' }}
                strokeOpacity="0.45"
                strokeWidth="0.795"
              />
            </g>
          )}
        </svg>
        {monthlyTicks.map((tick) => (
          <span
            key={tick.label}
            className={styles.tick}
            style={{ left: tick.left + 7.948 }}
            aria-hidden="true"
          >
            {tick.label}
          </span>
        ))}
        {day !== null && (
          <>
            <span
              className={styles.marker}
              style={{ left: x, top: y, background: 'var(--wf-accent)' }}
            />
            <Tooltip left={x} top={y - 5.563} shift={clamp(0, 25 - x, 298.48 - x)}>
              <span className={styles.tipMuted}>{t.day(day)}</span>
              {formatMinutes(minutes)} h
            </Tooltip>
          </>
        )}
        <div
          className={`${styles.probe} ${styles.focusable}`}
          data-hover=""
          data-demo="monthly-line"
          role="slider"
          tabIndex={0}
          aria-label={t.byDay}
          aria-valuemin={1}
          aria-valuemax={DAYS}
          aria-valuenow={day ?? 1}
          aria-valuetext={valueText}
          onPointerDown={(event) => dayAt(event.currentTarget, event.clientX)}
          onFocus={() => {
            setFocused(true);
            setDay((current) => current ?? 1);
          }}
          onBlur={() => {
            setFocused(false);
            setDay(null);
          }}
          onKeyDown={onKeyDown}
        />
      </div>
      <ResizeGlyph />
    </section>
  );
}

/* ---------------------------------------- Positive time bank by employee */

function PeopleCard({ person, onSelect }: { person: Person; onSelect: (next: Person) => void }) {
  const t = COPY;
  const titleId = useId();
  const [ref, revealed] = useReveal<HTMLElement>();
  const [hovered, setHovered] = useState<number | null>(null);
  const [focused, setFocused] = useState<number | null>(null);
  const selected = contributors.findIndex((row) => row.person === person);
  const active = hovered ?? focused ?? (selected >= 0 ? selected : null);
  const activeRow = active === null ? null : contributors[active];
  const activeEnd = activeRow ? BAR_LEFT + activeRow.seconds * PX_PER_SECOND : 0;
  const hoverRef = useHoverItems<HTMLDivElement>(
    (item) => setHovered(Number(item.dataset.hover)),
    (item) => {
      const index = Number(item.dataset.hover);
      setHovered((current) => (current === index ? null : current));
    },
  );

  return (
    <section
      role="group"
      ref={ref}
      className={`${styles.card} ${styles.people}`}
      aria-labelledby={titleId}
      data-card={cardIds.people.id}
      data-revealed={revealed}
    >
      <CardHeader id={titleId} title={t.people} />

      <div className={styles.bars} data-filtered={selected >= 0}>
        <svg
          className={styles.plotSvg}
          width="336.197"
          height="100.939"
          viewBox="0 0 336.197 100.939"
          aria-hidden="true"
        >
          {barTicks.map((tick) => (
            <line
              key={tick.label}
              x1={tick.left}
              x2={tick.left}
              y1="0"
              y2="100.476"
              style={{ stroke: 'var(--wf-line)' }}
              strokeWidth="0.795"
              strokeDasharray="1.59 1.59"
            />
          ))}
        </svg>
        {barTicks.map((tick) => (
          <span
            key={tick.label}
            className={styles.tick}
            style={{ left: tick.left }}
            aria-hidden="true"
          >
            {tick.label}
          </span>
        ))}

        <div ref={hoverRef} role="group" aria-label={t.filter}>
          {contributors.map((row, index) => {
            const content = (
              <>
                <span className={styles.rowLabel}>{row.name}</span>
                <span
                  className={styles.bar}
                  style={
                    {
                      width: row.seconds * PX_PER_SECOND,
                      background: row.color,
                      '--delay': `${120 + index * 90}ms`,
                    } as CSSProperties
                  }
                />
                <span className={styles.srOnly}>
                  , {formatClock(row.seconds)} {t.positive}
                </span>
              </>
            );
            const shared = {
              style: { top: row.top },
              'data-hover': index,
              'data-demo': `people-${row.name.toLowerCase()}`,
              'data-active': active === index,
            };
            if (!row.person) {
              return (
                <div key={row.name} className={styles.row} {...shared}>
                  {content}
                </div>
              );
            }
            const target = row.person;
            return (
              <button
                key={row.name}
                type="button"
                className={`${styles.row} ${styles.focusable}`}
                aria-pressed={person === target}
                onClick={() => onSelect(person === target ? 'all' : target)}
                onFocus={() => setFocused(index)}
                onBlur={() => setFocused((current) => (current === index ? null : current))}
                {...shared}
              >
                {content}
              </button>
            );
          })}
        </div>

        {activeRow && revealed && (
          <>
            <span
              className={styles.marker}
              style={{
                left: activeEnd - 7.153,
                top: activeRow.top + 7.153,
                background: activeRow.marker,
              }}
            />
            <Tooltip
              left={activeEnd - 7.153 - 0.763}
              top={activeRow.top - 0.778}
              // Kiran's bar ends near the edge: slide the body in, the tail stays on the marker.
              shift={Math.min(0, BARS_WIDTH - CLOCK_TIP_HALF - (activeEnd - 7.153 - 0.763))}
            >
              {formatClock(activeRow.seconds)}
            </Tooltip>
          </>
        )}
      </div>
      <ResizeGlyph />
    </section>
  );
}

/* ------------------------------------------------------------------ region */

/** Time bank by department, Monthly time bank and Positive time bank by employee. */
export function TrendCards({ person, onPersonChange }: PersonProps) {
  const [internalPerson, setInternalPerson] = useState<Person>('all');
  const current = person ?? internalPerson;
  const select = (next: Person) => {
    setInternalPerson(next);
    onPersonChange?.(next);
  };
  return (
    <>
      <DepartmentsCard />
      <MonthlyCard />
      <PeopleCard person={current} onSelect={select} />
    </>
  );
}
```

### workforce-dashboard/trend-cards.module.css

```css
/* Baselines: Chrome rounds font ascent/descent to whole px and floors the half-leading, so a
   fixed line-height box puts small type up to ~1px above the design's baseline. The small labels
   below use line-height: normal inside a box of the design's line height, with the exact
   half-leading as padding (Inter: ascent 0.96875em, descent 0.2421875em).

   the design nodes (Time bank by department), (Monthly time bank) and (Positive time bank). Every length is in the dashboard's 1526×881 design space. */
.card {
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --focus: var(--wf-accent);
  position: absolute;
  border-radius: 2.122px;
  background: var(--wf-surface);
  /* The Box vector's 0.663px var(--wf-line) stroke is centred on the edge (half outside, half inside). */
  box-shadow:
    0 0 0 0.3315px var(--wf-line),
    inset 0 0 0 0.3315px var(--wf-line);
  font-feature-settings:
    'cv09' 1,
    'ss11' 1,
    'calt' 0,
    'liga' 0;
}
.departments {
  left: 828.968px;
  top: 459.39px;
  width: 413.292px;
  height: 317.917px;
}
.monthly {
  left: 89.017px;
  top: 580.993px;
  width: 360.835px;
  height: 196.314px;
}
.people {
  left: 457.8px;
  top: 580.993px;
  width: 360.835px;
  height: 196.314px;
}

/* Corner resize glyph: the export keeps its 9.283px size. */
.resize {
  position: absolute;
  width: 9.28282px;
  height: 9.28263px;
  color: var(--wf-muted-3);
  pointer-events: none;
}
.departments .resize {
  left: 401.37px;
  top: 305.995px;
}
.monthly .resize,
.people .resize {
  left: 347.324px;
  top: 182.802px;
}

/* ---------- Card header (Content & Emoji) ---------- */
.header {
  position: absolute;
  display: flex;
  align-items: flex-start;
  gap: 12.717px;
}
.departments .header {
  left: 12.717px;
  top: 13.512px;
  width: 385.474px;
}
.monthly .header,
.people .header {
  left: 12.717px;
  top: 11.127px;
  width: 335.402px;
}
.headerText {
  display: flex;
  flex: 1 0 0;
  flex-direction: column;
  gap: 3.179px;
  min-width: 1px;
}
.departments .headerText {
  gap: 7.153px;
}
.titleGroup {
  display: flex;
  flex-direction: column;
  gap: 3.974px;
}
.title {
  color: var(--wf-ink-3);
  font-size: 13.262px;
  font-weight: 600;
  line-height: normal;
}
.subtitle {
  color: var(--wf-muted-2);
  font-size: 7.957px;
  font-weight: 400;
  line-height: 10.646px;
}
.subtitle b {
  font-weight: 700;
}
.more {
  flex: none;
  color: var(--wf-dots);
  width: 13.286px;
  height: 13.262px;
}

/* ---------- Legend toggles ---------- */
.legends {
  display: flex;
  align-items: flex-start;
  gap: 12.717px;
}
.legend {
  display: flex;
  flex: none;
  align-items: center;
  gap: 3.179px;
  height: 12.717px;
  border-radius: 2px;
}
.legendDot {
  display: block;
  flex: none;
  width: 12.717px;
  height: 12.717px;
  transition:
    opacity 240ms ease,
    filter 240ms ease;
}
.legendLabel {
  color: var(--wf-muted-2);
  /* The Chart Legends instance is scaled; the design lays the label out at its 6.631px source size. */
  font-size: 6.631px;
  font-weight: 700;
  line-height: normal;
  text-transform: uppercase;
  white-space: nowrap;
  text-decoration: line-through transparent;
  transition:
    color 240ms ease,
    text-decoration-color 240ms ease;
}
.legend:hover .legendLabel {
  color: var(--wf-ink-3);
}
.legend[aria-pressed='false'] .legendDot {
  opacity: 0.35;
  filter: grayscale(1);
}
.legend[aria-pressed='false'] .legendLabel {
  color: var(--wf-muted-5);
  text-decoration-color: currentColor;
}

/* ---------- Stacked columns ---------- */
.yAxis {
  position: absolute;
  left: 12.717px;
  top: 115.245px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 19.075px;
  height: 128.756px;
  color: var(--wf-ink-2);
  font-size: 9.537px;
  line-height: normal;
}
/* 9.537px on a 12.717px line: half-leading (12.717 - 11.549) / 2. */
.yAxis span,
.columnLabel {
  flex: none;
  height: 12.717px;
  padding-top: 0.584px;
}
.column {
  position: absolute;
  top: 115.245px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9.537px;
  height: 168.496px;
  border-radius: 1px;
}
.track {
  display: flex;
  flex: 0 0 145.958px;
  flex-direction: column;
  width: 100%;
}
.empty {
  flex: 1 1 auto;
  min-height: 0;
  background: var(--wf-surface-2);
  transition: background-color 200ms ease;
}
.column[data-active='true'] .empty {
  background: var(--wf-grid-2);
}
.segment {
  flex: none;
  width: 100%;
  transition-property: height, margin-top;
  transition-duration: 640ms;
  transition-timing-function: var(--ease-out);
}
.columnLabel {
  width: 100%;
  color: var(--wf-ink-2);
  font-size: 9.537px;
  line-height: normal;
  text-align: center;
}

/* ---------- Line chart ---------- */
.plot {
  position: absolute;
  left: 15.896px;
  top: 57.225px;
  width: 323.48px;
  height: 123.988px;
}
.plotSvg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.line {
  stroke-dasharray: 1 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 1500ms cubic-bezier(0.65, 0, 0.35, 1) 150ms;
}
.card[data-revealed='true'] .line {
  stroke-dashoffset: 0;
}
.area {
  opacity: 0;
  transition: opacity 900ms ease 900ms;
}
.card[data-revealed='true'] .area {
  opacity: 1;
}
.crosshair {
  pointer-events: none;
}
.probe {
  position: absolute;
  left: 0;
  top: 0;
  width: 323.48px;
  height: 107.629px;
  border-radius: 2px;
  cursor: crosshair;
  touch-action: pan-y;
}
.tick {
  position: absolute;
  /* 8.743px on a 9.537px line: the half-leading is negative, (9.537 - 10.587) / 2. */
  margin-top: -0.525px;
  color: var(--wf-muted);
  font-size: 8.743px;
  font-weight: 500;
  line-height: normal;
  letter-spacing: 0.1749px;
  text-transform: uppercase;
  white-space: nowrap;
  transform: translateX(-50%);
}
.plot .tick {
  top: 114.451px;
}

/* ---------- Horizontal bars ---------- */
.bars {
  position: absolute;
  left: 13.511px;
  top: 65.968px;
  width: 336.197px;
  height: 100.939px;
}
.bars .tick {
  top: 107.297px;
}
.row {
  position: absolute;
  left: 0;
  display: block;
  width: 336.197px;
  height: 12.717px;
  border-radius: 2px;
  text-align: left;
}
.rowLabel {
  position: absolute;
  left: 0;
  top: 0;
  width: 38.15px;
  color: var(--wf-muted-2);
  font-size: 7.957px;
  font-weight: 600;
  line-height: 10.646px;
  transition:
    color 200ms ease,
    opacity 200ms ease;
}
.bar {
  position: absolute;
  left: 49.277px;
  top: 0;
  height: 12.717px;
  border-radius: 1.59px;
  transform: scaleX(0);
  transform-origin: left center;
  transition:
    transform 850ms var(--ease-out) var(--delay, 0ms),
    opacity 200ms ease,
    filter 200ms ease;
}
.card[data-revealed='true'] .bar {
  transform: scaleX(1);
}
button.row:hover .bar {
  filter: brightness(0.96);
}
button.row:hover .rowLabel,
.row[aria-pressed='true'] .rowLabel {
  color: var(--wf-ink);
}
.row[aria-pressed='true'] .rowLabel {
  font-weight: 700;
}
.bars[data-filtered='true'] .row:not([aria-pressed='true']) .bar {
  opacity: 0.35;
}
.bars[data-filtered='true'] .row:not([aria-pressed='true']) .rowLabel {
  opacity: 0.6;
}
.bars[data-filtered='true'] .row[data-active='true'] .bar,
.bars[data-filtered='true'] .row[data-active='true'] .rowLabel {
  opacity: 1;
}
.marker {
  position: absolute;
  z-index: 1;
  width: 6.358px;
  height: 6.358px;
  border: 1.192px solid var(--wf-surface);
  border-radius: 50%;
  box-shadow: 0 1.192px 2.384px rgba(27, 28, 29, 0.04);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* ---------- Tooltip [1.0] ---------- */
.tip {
  position: absolute;
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  transform: translate(-50%, -100%);
  pointer-events: none;
  animation: tip-in 180ms ease-out both;
}
.tipBody {
  /* 3.179px padding + a 15.896px line for 11.13px type, half-leading (15.896 - 13.477) / 2. */
  height: 22.254px;
  padding: 4.389px 7.948px 0;
  border-radius: 4.769px;
  color: #fff;
  background: var(--wf-tooltip);
  box-shadow:
    0 9.537px 19.075px color(display-p3 0.055 0.071 0.106 / 0.06),
    0 0.795px 1.59px color(display-p3 0.055 0.071 0.106 / 0.03);
  font-size: 11.13px;
  font-weight: 400;
  line-height: normal;
  letter-spacing: -0.0668px;
  white-space: nowrap;
  transform: translateX(var(--shift, 0px));
}
.tipTail {
  position: relative;
  flex: none;
  width: 9.537px;
  height: 4.769px;
}
.tailIcon {
  position: absolute;
  display: block;
  color: var(--wf-tooltip);
  left: -2.384px;
  top: -1.59px;
  width: 14.306px;
  height: 6.358px;
}
.tipMuted {
  margin-right: 4.769px;
  color: var(--wf-muted);
  font-size: 7.957px;
  letter-spacing: 0;
}
.columnTip {
  transition: top 640ms var(--ease-out);
}
.columnTip .tipBody {
  width: 106px;
  height: auto;
  padding: 5.563px 7.948px 6.358px;
  font-size: 7.957px;
  line-height: 10.646px;
  letter-spacing: 0;
}
.tipTitle {
  margin-bottom: 2.384px;
  font-weight: 600;
}
.tipRow {
  display: flex;
  align-items: center;
  gap: 3.974px;
}
.tipRow span:nth-child(2) {
  flex: 1;
  color: var(--wf-tooltip-muted);
}
.tipRow span:last-child {
  font-variant-numeric: tabular-nums;
}
.tipDot {
  flex: none;
  width: 5.563px;
  height: 5.563px;
  border-radius: 50%;
}
.tipTotal {
  margin-top: 3.179px;
  padding-top: 3.179px;
  border-top: 0.663px solid rgba(255, 255, 255, 0.14);
  font-weight: 600;
}
.tipTotal span:nth-child(2) {
  color: #fff;
}
@keyframes tip-in {
  from {
    opacity: 0;
    transform: translate(-50%, calc(-100% + 3px));
  }
}

/* ---------- Shared ---------- */
.focusable:focus {
  outline: none;
}
.focusable:focus-visible {
  outline: 1.2px solid var(--focus);
  outline-offset: 2px;
}
.srOnly {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .legendDot,
  .legendLabel,
  .empty,
  .segment,
  .line,
  .area,
  .bar,
  .rowLabel,
  .columnTip {
    transition: none;
  }
  .tip {
    animation: none;
  }
}
```

### workforce-dashboard/showcase.tsx

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

'use client';

import { useEffect, useLayoutEffect, useRef } from 'react';
import { useHydratedReducedMotion } from './use-reduced-motion';
import styles from './showcase.module.css';

/*
 * Autoplay showcase. A virtual cursor drives the real cards the way a reader would (pointer and
 * mouse events at client coordinates, element.click() on buttons) and then personalises the
 * layout by dragging cards into each other's slots. It plays only while the dashboard is on
 * screen, the tab is visible and nobody is using the dashboard (no pointer over it, no focus in
 * it); reduced motion turns it off. It never moves focus itself, and when the dashboard scrolls
 * sideways (phones) it scrolls that box, never the page, to keep each scene in view.
 */

type Point = { x: number; y: number };
type Slot = { left: number; top: number };
type SwapCard = 'active' | 'groups' | 'health' | 'employee' | 'monthly-bank' | 'people';

/** Home slots of the cards that move, in frame px (from the region files). Sizes never change. */
const HOME: Record<SwapCard, Slot & { width: number; height: number }> = {
  active: { left: 89.017, top: 136.704, width: 360.714, height: 209.533 },
  groups: { left: 457.688, top: 136.704, width: 360.714, height: 209.533 },
  health: { left: 1255.771, top: 136.704, width: 244.796, height: 309.969 },
  employee: { left: 1255.771, top: 460.979, width: 244.796, height: 316.327 },
  'monthly-bank': { left: 89.017, top: 580.993, width: 360.835, height: 196.314 },
  people: { left: 457.8, top: 580.993, width: 360.835, height: 196.314 },
};
const CARDS = Object.keys(HOME) as SwapCard[];

/**
 * Pairs that can trade places without breaking the grid: equal sizes side by side, or the right
 * column's two cards, which restack so the column keeps its top, bottom and gap. Each cycle
 * performs two swaps; over three cycles every pair is swapped twice, so the layout comes home.
 */
const SWAPS: [drag: SwapCard, target: SwapCard][] = [
  ['active', 'groups'],
  ['employee', 'health'],
  ['people', 'monthly-bank'],
];

const FRAME_WIDTH = 1526;
/** The frame's centre: the default perspective-origin used while a card is lifted. */
const ORIGIN: Point = { x: 763, y: 440.5 };
const PERSPECTIVE = 1500;
/** translateZ of a picked-up card; under the frame's perspective it reads ≈3% larger. */
const LIFT = 44;
const LIFT_SHADOW = '0 22px 44px -10px rgb(16 24 40 / 0.26), 0 6px 14px -4px rgb(16 24 40 / 0.12)';
const START_DELAY = 1600;
const RESUME_LEAD = 350;
const RESUME_DELAY = 5000;
/** Where the cursor first appears (bottom right, clear of the cards' content). */
const ENTRY: Point = { x: 1470, y: 845 };

const SYNTHETIC_TYPES = [
  'pointerover',
  'pointerout',
  'pointermove',
  'mouseover',
  'mouseout',
  'mousemove',
] as const;

const round = (value: number) => Math.round(value * 1000) / 1000;
const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));
const translateOf = (p: Point) => `${round(p.x)}px ${round(p.y)}px`;
const lerp = (a: number, b: number, t: number) => a + (b - a) * t;

const pausedError = () => new DOMException('Showcase paused', 'AbortError');
const isAbort = (error: unknown) => error instanceof DOMException && error.name === 'AbortError';

function sleep(ms: number, signal: AbortSignal) {
  return new Promise<void>((resolve, reject) => {
    if (signal.aborted) return reject(pausedError());
    const onAbort = () => {
      window.clearTimeout(timer);
      reject(pausedError());
    };
    const timer = window.setTimeout(() => {
      signal.removeEventListener('abort', onAbort);
      resolve();
    }, ms);
    signal.addEventListener('abort', onAbort, { once: true });
  });
}

/** Resolves when the animation finishes; rejects when the run is aborted or it is cancelled. */
function finished(animation: Animation, signal: AbortSignal) {
  return new Promise<void>((resolve, reject) => {
    if (signal.aborted) return reject(pausedError());
    const onAbort = () => reject(pausedError());
    signal.addEventListener('abort', onAbort, { once: true });
    animation.finished.then(
      () => {
        signal.removeEventListener('abort', onAbort);
        resolve();
      },
      () => {
        signal.removeEventListener('abort', onAbort);
        reject(pausedError());
      },
    );
  });
}

/** Resolves once `box` has scrolled to `left` (or after a second, if it never gets there). */
function scrolledTo(box: HTMLElement, left: number, signal: AbortSignal) {
  return new Promise<void>((resolve, reject) => {
    if (signal.aborted) return reject(pausedError());
    const started = performance.now();
    let id = 0;
    const onAbort = () => {
      cancelAnimationFrame(id);
      reject(pausedError());
    };
    const check = () => {
      if (Math.abs(box.scrollLeft - left) < 1.5 || performance.now() - started > 1000) {
        signal.removeEventListener('abort', onAbort);
        return resolve();
      }
      id = requestAnimationFrame(check);
    };
    signal.addEventListener('abort', onAbort, { once: true });
    id = requestAnimationFrame(check);
  });
}

/** Quadratic Bézier samples from a to b, bowed sideways by `bend` px. */
function curve(a: Point, b: Point, bend: number, count = 16) {
  const dx = b.x - a.x;
  const dy = b.y - a.y;
  const length = Math.hypot(dx, dy) || 1;
  const control = {
    x: (a.x + b.x) / 2 + (-dy / length) * bend,
    y: (a.y + b.y) / 2 + (dx / length) * bend,
  };
  return Array.from({ length: count + 1 }, (_, index) => {
    const t = index / count;
    const u = 1 - t;
    return {
      x: u * u * a.x + 2 * u * t * control.x + t * t * b.x,
      y: u * u * a.y + 2 * u * t * control.y + t * t * b.y,
    };
  });
}

/** Apparent scale of a card lifted by z under the frame's perspective. */
const depthScale = (z: number) => PERSPECTIVE / (PERSPECTIVE - z);

type Env = {
  frame: HTMLElement;
  stage: HTMLElement;
  cursor: HTMLElement;
  zone: HTMLElement;
  restoreFilters: () => void;
};

type Scene = {
  name: string;
  /** What the scene works on; kept in view when the dashboard is scrolled sideways. */
  area: () => (Element | null)[];
  play: (signal: AbortSignal) => Promise<void>;
};

type Drag = {
  card: HTMLElement;
  target: HTMLElement;
  animations: Animation[];
};

function createDirector({ frame, stage, cursor, zone, restoreFilters }: Env) {
  /* ------------------------------------------------------------- geometry */

  const scaleOf = (rect: DOMRect) => rect.width / (frame.offsetWidth || FRAME_WIDTH);
  const toClient = (p: Point) => {
    const rect = frame.getBoundingClientRect();
    const scale = scaleOf(rect);
    return { x: rect.left + p.x * scale, y: rect.top + p.y * scale };
  };
  const toFrame = (x: number, y: number) => {
    const rect = frame.getBoundingClientRect();
    const scale = scaleOf(rect) || 1;
    return { x: (x - rect.left) / scale, y: (y - rect.top) / scale };
  };
  /** A point on an element, as ratios of its rendered box, in frame px. */
  const pointOn = (element: Element, rx = 0.5, ry = 0.5) => {
    const rect = element.getBoundingClientRect();
    return toFrame(rect.left + rect.width * rx, rect.top + rect.height * ry);
  };
  /**
   * The narrow layout: the frame is no longer the 1526-px design (it flows as a column, unscaled),
   * so cards are not swapped there and only scenes on screen play.
   */
  const stacked = () => Math.abs(frame.offsetWidth - FRAME_WIDTH) > 1;
  /** Whether most of a scene's area is in the viewport (narrow layout). */
  const onScreen = (elements: (Element | null)[]) => {
    const nodes = elements.filter((node): node is Element => node !== null);
    if (nodes.length === 0) return false;
    const top = Math.min(...nodes.map((node) => node.getBoundingClientRect().top));
    const bottom = Math.max(...nodes.map((node) => node.getBoundingClientRect().bottom));
    const height = bottom - top;
    const seen = Math.min(bottom, window.innerHeight) - Math.max(top, 0);
    return height > 0 && seen >= Math.min(height, window.innerHeight) * 0.7;
  };
  const cardEl = (id: string) => frame.querySelector<HTMLElement>(`[data-card="${id}"]`);
  const target = (card: string, name: string) =>
    frame.querySelector<HTMLElement>(`[data-card="${card}"] [data-demo="${name}"]`);

  /* --------------------------------------------------------------- scroll */

  /** The box that scrolls the dashboard sideways on narrow screens. The page itself never moves. */
  const scroller = (() => {
    for (
      let node = frame.parentElement;
      node && node !== document.body;
      node = node.parentElement
    ) {
      const { overflowX } = getComputedStyle(node);
      if (overflowX === 'auto' || overflowX === 'scroll') return node;
    }
    return null;
  })();

  /** When the dashboard is clipped sideways, smoothly scrolls its box so `elements` are in view. */
  const reveal = async (elements: (Element | null)[], signal: AbortSignal) => {
    const box = scroller;
    const nodes = elements.filter((node): node is Element => node !== null);
    if (!box || nodes.length === 0) return;
    const room = box.scrollWidth - box.clientWidth;
    if (room < 1) return;
    const rects = nodes.map((node) => node.getBoundingClientRect());
    const left = Math.min(...rects.map((rect) => rect.left));
    const right = Math.max(...rects.map((rect) => rect.right));
    const viewLeft = box.getBoundingClientRect().left + box.clientLeft;
    const viewRight = viewLeft + box.clientWidth;
    const margin = Math.min(16, box.clientWidth * 0.04);
    if (left >= viewLeft + margin && right <= viewRight - margin) return;
    // Centre the area, or line up its left edge when it is wider than the view.
    const shift =
      right - left + margin * 2 > box.clientWidth
        ? left - margin - viewLeft
        : (left + right - viewLeft - viewRight) / 2;
    const goal = clamp(Math.round(box.scrollLeft + shift), 0, room);
    if (Math.abs(goal - box.scrollLeft) < 1) return;
    box.scrollTo({ left: goal, behavior: 'smooth' });
    await scrolledTo(box, goal, signal);
  };

  /* --------------------------------------------------------------- layout */

  const layout = new Map<SwapCard, Slot>(CARDS.map((id) => [id, { ...HOME[id] }]));

  /** Writes each moved card's slot as inline left/top; a card at home keeps its own CSS. */
  const applyLayout = () => {
    for (const id of CARDS) {
      const node = cardEl(id);
      const slot = layout.get(id);
      if (!node || !slot) continue;
      const home = HOME[id];
      if (Math.abs(slot.left - home.left) < 0.01 && Math.abs(slot.top - home.top) < 0.01) {
        node.style.removeProperty('left');
        node.style.removeProperty('top');
      } else {
        node.style.left = `${round(slot.left)}px`;
        node.style.top = `${round(slot.top)}px`;
      }
    }
  };

  /** New slots after `a` and `b` trade places along the axis they are laid out on. */
  const swappedSlots = (a: SwapCard, b: SwapCard) => {
    const boxes = [a, b].map((id) => ({ id, ...HOME[id], ...layout.get(id)! }));
    const row = Math.abs(boxes[0].top - boxes[1].top) < 1;
    const start = row ? 'left' : 'top';
    const size = row ? 'width' : 'height';
    const [first, second] = boxes[0][start] <= boxes[1][start] ? boxes : [boxes[1], boxes[0]];
    const next = new Map<SwapCard, Slot>();
    const snap = (id: SwapCard, slot: Slot) => {
      const home = HOME[id];
      const at = (value: number, want: number) => (Math.abs(value - want) < 0.01 ? want : value);
      next.set(id, { left: at(round(slot.left), home.left), top: at(round(slot.top), home.top) });
    };
    // The later card moves to the front; the earlier one ends where the later one ended.
    snap(second.id, { left: first.left, top: first.top, [start]: first[start] });
    snap(first.id, {
      left: first.left,
      top: first.top,
      [start]: second[start] + second[size] - first[size],
    });
    return next;
  };

  /* ------------------------------------------------------ pointer events */

  let hovered: Element | null = null;
  let hoverBounds: Element | null = null;

  const fire = (node: Element, type: string, client: Point, related: Element | null) => {
    const bubbles = !type.endsWith('enter') && !type.endsWith('leave');
    const init: MouseEventInit = {
      bubbles,
      cancelable: bubbles,
      composed: true,
      clientX: client.x,
      clientY: client.y,
      screenX: client.x,
      screenY: client.y,
      view: window,
      relatedTarget: related,
      buttons: 0,
    };
    const event = type.startsWith('pointer')
      ? new PointerEvent(type, {
          ...init,
          button: -1,
          pointerId: 1,
          pointerType: 'mouse',
          isPrimary: true,
          width: 1,
          height: 1,
        })
      : new MouseEvent(type, { ...init, button: 0 });
    node.dispatchEvent(event);
  };

  /** The element and its ancestors inside the frame, innermost first. */
  const chain = (node: Element | null) => {
    const list: Element[] = [];
    for (let at = node; at && at !== frame; at = at.parentElement) list.push(at);
    return list;
  };

  /** Boundary events in the browser's order when the pointer moves from `hovered` to `next`. */
  const moveHover = (next: Element | null, client: Point) => {
    const previous = hovered;
    if (previous === next) return;
    hovered = next;
    const left = chain(previous).filter((node) => !(next && node.contains(next)));
    const entered = chain(next)
      .filter((node) => !(previous && node.contains(previous)))
      .reverse();
    for (const kind of ['pointer', 'mouse']) {
      if (previous) {
        fire(previous, `${kind}out`, client, next);
        for (const node of left) fire(node, `${kind}leave`, client, next);
      }
      if (next) {
        fire(next, `${kind}over`, client, previous);
        for (const node of entered) fire(node, `${kind}enter`, client, previous);
      }
    }
  };

  /** Hover `bounds` at frame point p: the element under p inside it gets the events. */
  const hoverAt = (p: Point, bounds: Element) => {
    const client = toClient(p);
    let hit = document.elementFromPoint(client.x, client.y);
    // Off screen or covered: the intended element still receives the pointer.
    if (!hit || !bounds.contains(hit)) hit = bounds;
    hoverBounds = bounds;
    moveHover(hit, client);
    fire(hit, 'pointermove', client, null);
    fire(hit, 'mousemove', client, null);
  };

  const unhover = () => {
    if (hovered) moveHover(null, toClient(position));
    hoverBounds = null;
  };

  /* ---------------------------------------------------------------- cursor */

  let position: Point = { ...ENTRY };
  let cursorMotion: Animation | null = null;

  const placeCursor = (p: Point) => {
    position = p;
    cursor.style.translate = translateOf(p);
  };

  /** Where the cursor is drawn right now, mid-animation included. */
  const livePosition = (): Point => {
    const value = getComputedStyle(cursor).translate;
    if (!value || value === 'none') return { x: 0, y: 0 };
    const [x, y = '0'] = value.split(' ');
    return { x: parseFloat(x), y: parseFloat(y) };
  };

  const setMode = (mode: 'arrow' | 'grab' | 'grabbing') => {
    cursor.dataset.mode = mode;
  };

  /** Calls onFrame with the cursor's live position on every frame while `animation` runs. */
  const track = (animation: Animation, onFrame: (p: Point) => void) => {
    let id = 0;
    const tick = () => {
      if (animation.playState !== 'running') return;
      onFrame(livePosition());
      id = requestAnimationFrame(tick);
    };
    id = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(id);
  };

  /** Ends the hover once the cursor has left the hovered element's box. */
  const releaseHoverIfOutside = (p: Point) => {
    if (!hoverBounds) return;
    if (!hoverBounds.isConnected) return unhover();
    const client = toClient(p);
    const rect = hoverBounds.getBoundingClientRect();
    const inside =
      client.x >= rect.left &&
      client.x <= rect.right &&
      client.y >= rect.top &&
      client.y <= rect.bottom;
    if (!inside) unhover();
  };

  const glide = async (
    to: Point,
    signal: AbortSignal,
    options: {
      duration?: number;
      bend?: number;
      easing?: string;
      onFrame?: (p: Point) => void;
    } = {},
  ) => {
    const from = position;
    const distance = Math.hypot(to.x - from.x, to.y - from.y);
    if (distance < 0.5) return;
    // Letting go of a card header: the open hand turns back into the arrow as it moves away.
    setMode('arrow');
    const duration = options.duration ?? clamp(280 + distance * 0.5, 320, 950);
    // A hand-driven path bows slightly; which way depends on the direction of travel.
    const bend = options.bend ?? Math.min(36, distance * 0.1) * (to.x >= from.x ? 1 : -1);
    const keyframes = curve(from, to, bend).map((p) => ({ translate: translateOf(p) }));
    placeCursor(to);
    const animation = cursor.animate(keyframes, {
      duration,
      easing: options.easing ?? 'cubic-bezier(0.42, 0, 0.18, 1)',
    });
    cursorMotion = animation;
    const stop = track(animation, (p) => {
      if (options.onFrame) options.onFrame(p);
      else releaseHoverIfOutside(p);
    });
    try {
      await finished(animation, signal);
    } finally {
      stop();
      if (cursorMotion === animation) cursorMotion = null;
    }
    if (options.onFrame) options.onFrame(to);
    else releaseHoverIfOutside(to);
  };

  const ripple = (p: Point) => {
    const ring = document.createElement('span');
    ring.className = styles.ripple;
    ring.style.translate = translateOf(p);
    stage.append(ring);
    const animation = ring.animate(
      [
        { scale: 0.25, opacity: 1 },
        { scale: 1.55, opacity: 0 },
      ],
      { duration: 560, easing: 'cubic-bezier(0.2, 0.7, 0.3, 1)' },
    );
    animation.finished.then(
      () => ring.remove(),
      () => ring.remove(),
    );
  };

  const press = () => {
    cursor.firstElementChild?.animate([{ scale: 1 }, { scale: 0.84, offset: 0.35 }, { scale: 1 }], {
      duration: 260,
      easing: 'ease-out',
    });
  };

  /**
   * Scripted clicks still settling. React commits a click's updates (effects included) in a
   * microtask, so a task later they are done; focus moved before then is not the reader's.
   */
  let scripting = 0;
  const act = (action: () => void) => {
    scripting += 1;
    try {
      action();
    } finally {
      window.setTimeout(() => {
        scripting -= 1;
      }, 0);
    }
  };

  /** Presses at the cursor's position, then clicks the real button. */
  const click = async (button: HTMLElement, signal: AbortSignal) => {
    press();
    ripple(position);
    await sleep(90, signal);
    yieldToFocus();
    act(() => button.click());
  };

  /** Glides onto an element (a point given as ratios of its box) and hovers it there. */
  const hoverOn = async (
    element: Element,
    signal: AbortSignal,
    {
      rx = 0.5,
      ry = 0.5,
      geometry = element,
    }: { rx?: number; ry?: number; geometry?: Element } = {},
  ) => {
    await glide(pointOn(geometry, rx, ry), signal);
    hoverAt(position, element);
  };

  /* ----------------------------------------------------------------- drag */

  let drag: Drag | null = null;
  const returning = new Set<Animation>();

  const settleReturns = () => {
    for (const animation of returning) animation.finish();
    returning.clear();
  };

  const showZone = (slot: Slot, size: { width: number; height: number }) => {
    zone.style.left = `${round(slot.left)}px`;
    zone.style.top = `${round(slot.top)}px`;
    zone.style.width = `${round(size.width)}px`;
    zone.style.height = `${round(size.height)}px`;
    zone.toggleAttribute('data-visible', true);
  };
  const hideZone = () => zone.toggleAttribute('data-visible', false);

  const clearLiftStyles = (card: HTMLElement) => {
    card.style.removeProperty('z-index');
    if (!drag) frame.style.removeProperty('perspective');
  };

  /** Puts an interrupted drag back where it started, from wherever it was drawn. */
  const cancelDrag = () => {
    if (!drag) return;
    const { card, target: other, animations } = drag;
    drag = null;
    hideZone();
    // Read what is drawn now, then drop the drag animations and ease back from there.
    const from = [card, other].map((node) => {
      const style = getComputedStyle(node);
      return { translate: style.translate, boxShadow: style.boxShadow };
    });
    animations.forEach((animation) => animation.cancel());
    const resting = getComputedStyle(card).boxShadow;
    [card, other].forEach((node, index) => {
      const start = from[index].translate === 'none' ? '0px 0px' : from[index].translate;
      const frames: Keyframe[] =
        node === card
          ? [
              { translate: start, boxShadow: from[index].boxShadow },
              { translate: '0px 0px 0px', boxShadow: resting },
            ]
          : [{ translate: start }, { translate: '0px 0px' }];
      const back = node.animate(frames, {
        duration: 260,
        easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)',
      });
      returning.add(back);
      const done = () => {
        returning.delete(back);
        if (node === card) clearLiftStyles(card);
      };
      back.finished.then(done, done);
    });
  };

  /**
   * Picks `dragId` up by its title, carries it onto `targetId`'s slot while that card slides into
   * the vacated one, drops it, then commits both slots as left/top with no transform left over.
   */
  const swap = async (dragId: SwapCard, targetId: SwapCard, signal: AbortSignal) => {
    const card = cardEl(dragId);
    const other = cardEl(targetId);
    if (!card || !other) return;
    settleReturns();
    applyLayout();
    unhover();

    const size = HOME[dragId];
    const from = layout.get(dragId)!;
    const otherFrom = layout.get(targetId)!;
    const next = swappedSlots(dragId, targetId);
    const to = next.get(dragId)!;
    const otherTo = next.get(targetId)!;

    // Grab the header (a dashboard card's drag handle) in the free space right of the title.
    const title = card.querySelector('[data-card-title]');
    const titleEnd = title ? pointOn(title, 1, 0.5) : pointOn(card, 0.5, 0.08);
    const grab = {
      x: clamp(
        Math.max(titleEnd.x + 28, from.left + size.width * 0.55),
        from.left,
        from.left + size.width - 44,
      ),
      y: titleEnd.y,
    };
    await glide(grab, signal);
    setMode('grab');
    await sleep(320, signal);

    const centre = { x: from.left + size.width / 2, y: from.top + size.height / 2 };
    const end = { x: to.left + size.width / 2, y: to.top + size.height / 2 };
    const offset = { x: grab.x - centre.x, y: grab.y - centre.y };
    /** Translate that draws the card's centre at c, lifted by z (undoing the perspective). */
    const cardAt = (c: Point, z: number) => {
      const s = depthScale(z);
      const x = (c.x - ORIGIN.x) / s + ORIGIN.x - centre.x;
      const y = (c.y - ORIGIN.y) / s + ORIGIN.y - centre.y;
      return `${round(x)}px ${round(y)}px ${round(z)}px`;
    };
    /** The grab point follows the card, scaled with it while it is lifted. */
    const handAt = (c: Point, z: number) => {
      const s = depthScale(z);
      return { x: c.x + offset.x * s, y: c.y + offset.y * s };
    };
    type Motion = { card: Keyframe[]; hand: Keyframe[]; handEnd: Point };
    /** Card and cursor keyframes for one path, sampled together so they stay locked. */
    const phase = (path: (t: number) => { c: Point; z: number }, count: number): Motion => {
      const samples = Array.from({ length: count + 1 }, (_, index) => path(index / count));
      const hands = samples.map(({ c, z }) => handAt(c, z));
      return {
        card: samples.map(({ c, z }) => ({ translate: cardAt(c, z) })),
        hand: hands.map((p) => ({ translate: translateOf(p) })),
        handEnd: hands[hands.length - 1],
      };
    };
    const animations: Animation[] = [];
    const animateCard = (
      node: HTMLElement,
      keyframes: Keyframe[],
      timing: KeyframeAnimationOptions,
    ) => {
      const animation = node.animate(keyframes, { fill: 'forwards', ...timing });
      animations.push(animation);
      return animation;
    };
    const moveHand = (motion: Motion, timing: KeyframeAnimationOptions) => {
      placeCursor(motion.handEnd);
      const animation = cursor.animate(motion.hand, timing);
      cursorMotion = animation;
      return animation;
    };

    // Pick up: the card rises towards the viewer and casts a shadow.
    const restShadow = getComputedStyle(card).boxShadow;
    const liftedShadow = restShadow === 'none' ? LIFT_SHADOW : `${restShadow}, ${LIFT_SHADOW}`;
    frame.style.perspective = `${PERSPECTIVE}px`;
    card.style.zIndex = '25';
    drag = { card, target: other, animations };
    setMode('grabbing');
    press();
    const lift = phase((t) => ({ c: centre, z: LIFT * t }), 8);
    const liftTiming = { duration: 240, easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)' };
    animateCard(card, lift.card, liftTiming);
    animateCard(card, [{ boxShadow: restShadow }, { boxShadow: liftedShadow }], liftTiming);
    await finished(moveHand(lift, liftTiming), signal);

    // Carry: a slightly bowed path; the other card slides into the vacated slot on the way.
    const dx = end.x - centre.x;
    const dy = end.y - centre.y;
    const length = Math.hypot(dx, dy) || 1;
    let normal = { x: -dy / length, y: dx / length };
    if (normal.y > 0 || (normal.y === 0 && normal.x > 0)) normal = { x: -normal.x, y: -normal.y };
    const carry = phase(
      (t) => ({
        c: {
          x: lerp(centre.x, end.x, t) + normal.x * Math.sin(Math.PI * t) * 12,
          y: lerp(centre.y, end.y, t) + normal.y * Math.sin(Math.PI * t) * 12,
        },
        z: LIFT,
      }),
      24,
    );
    const carryDuration = clamp(700 + length * 1.2, 900, 1250);
    const carryTiming = { duration: carryDuration, easing: 'cubic-bezier(0.5, 0, 0.2, 1)' };
    const carrying = animateCard(card, carry.card, carryTiming);
    const hand = moveHand(carry, carryTiming);
    await sleep(carryDuration * 0.36, signal);
    const shift = { x: otherTo.left - otherFrom.left, y: otherTo.top - otherFrom.top };
    const slide = animateCard(
      other,
      [{ translate: '0px 0px' }, { translate: translateOf(shift) }],
      { duration: 560, easing: 'cubic-bezier(0.22, 1, 0.36, 1)' },
    );
    await sleep(carryDuration * 0.24, signal);
    showZone(to, size);
    await finished(hand, signal);
    await finished(carrying, signal);

    // Drop: the card settles into its new slot and the shadow fades.
    const drop = phase((t) => ({ c: end, z: LIFT * (1 - t) }), 8);
    const dropTiming = { duration: 260, easing: 'cubic-bezier(0.3, 0.7, 0.4, 1)' };
    animateCard(card, drop.card, dropTiming);
    animateCard(card, [{ boxShadow: liftedShadow }, { boxShadow: restShadow }], dropTiming);
    hideZone();
    setMode('grab');
    await finished(moveHand(drop, dropTiming), signal);
    await finished(slide, signal);

    // Commit (FLIP's "last"): real left/top for both cards, then drop every animation at once.
    // Nothing awaits after this, so an interruption can never replay a committed swap.
    layout.set(dragId, to);
    layout.set(targetId, otherTo);
    applyLayout();
    animations.forEach((animation) => animation.cancel());
    drag = null;
    clearLiftStyles(card);
  };

  /* --------------------------------------------------------------- scenes */

  const selectedPerson = () =>
    frame.querySelector<HTMLElement>('[data-card="bank-total"] [data-person][aria-pressed="true"]')
      ?.dataset.person ?? 'all';

  /** Undo steps for demo states a scene leaves half-done if it is interrupted. */
  const pending = new Set<() => void>();

  /**
   * The colleague selected before the colleagues scene changed it. That filter stays on across
   * scenes until the people scene clears it, so an interruption in between restores this one.
   */
  let personBefore: string | null = null;
  const restorePerson = () => {
    const before = personBefore;
    personBefore = null;
    if (before === null || selectedPerson() === before) return;
    target('bank-total', `chip-${before}`)?.click();
  };

  const colleagues = async (signal: AbortSignal, order: string[]) => {
    for (const person of order) {
      if (selectedPerson() === person) continue;
      const chip = target('bank-total', `chip-${person}`);
      if (!chip) continue;
      await hoverOn(chip, signal, { ry: 0.55 });
      await sleep(240, signal);
      personBefore ??= selectedPerson();
      await click(chip, signal);
      await sleep(1250, signal);
    }
  };

  const sweepArea = async (signal: AbortSignal) => {
    const chart = target('active', 'area-chart');
    if (!chart) return;
    await glide(pointOn(chart, 0.05, 0.7), signal);
    hoverAt(position, chart);
    await glide(pointOn(chart, 0.95, 0.28), signal, {
      duration: 2600,
      bend: -10,
      easing: 'cubic-bezier(0.37, 0, 0.63, 1)',
      onFrame: (p) => hoverAt(p, chart),
    });
    await sleep(450, signal);
  };

  const toggleLegend = async (signal: AbortSignal, [card, name]: [string, string]) => {
    const button = target(card, name);
    if (!button) return;
    await hoverOn(button, signal);
    await sleep(700, signal);
    if (button.getAttribute('aria-pressed') !== 'true') {
      await click(button, signal);
      await sleep(900, signal);
      return;
    }
    await click(button, signal);
    const undo = () => {
      if (button.isConnected && button.getAttribute('aria-pressed') === 'false') button.click();
    };
    pending.add(undo);
    await sleep(1150, signal);
    await click(button, signal);
    pending.delete(undo);
    await sleep(900, signal);
  };

  const hoverBars = async (signal: AbortSignal, months: string[]) => {
    for (const month of months) {
      const bar = target('bank', `bar-${month}`);
      if (!bar) continue;
      await hoverOn(bar, signal, { ry: 0.74 });
      await sleep(1050, signal);
    }
  };

  const peopleRows = async (signal: AbortSignal, first: string) => {
    const rowAt = (name: string) => target('people', `people-${name}`);
    const onBar = (row: HTMLElement) => ({
      rx: 0.7,
      geometry: row.children[1] ?? row,
    });
    const row = rowAt(first);
    if (row) {
      await hoverOn(row, signal, onBar(row));
      await sleep(1000, signal);
    }
    // Clicking the selected colleague again clears the filter across the dashboard.
    const person = selectedPerson();
    const selected = person === 'all' ? rowAt('kiran') : rowAt(person);
    if (!selected) return;
    await hoverOn(selected, signal, onBar(selected));
    await sleep(person === 'all' ? 900 : 450, signal);
    if (person !== 'all') await click(selected, signal);
    // The dashboard is back to every colleague: nothing left to restore.
    personBefore = null;
    if (person !== 'all') await sleep(1200, signal);
  };

  const filtersScene = async (signal: AbortSignal) => {
    const chipCount = () => frame.querySelectorAll('[data-demo^="filter-remove-"]').length;
    const undo = () => {
      if (chipCount() >= 3) return;
      // If the reader's focus landed on Reset, press it: the filter bar then moves focus to the
      // first chip instead of dropping it when the button unmounts.
      const reset = frame.querySelector<HTMLElement>('[data-demo="filter-reset"]');
      if (reset && reset === document.activeElement) reset.click();
      else restoreFilters();
    };
    for (const id of ['period', 'subsidiary', 'country']) {
      const remove = frame.querySelector<HTMLElement>(`[data-demo="filter-remove-${id}"]`);
      if (!remove) continue;
      await hoverOn(remove, signal);
      await sleep(200, signal);
      pending.add(undo);
      await click(remove, signal);
      await sleep(430, signal);
    }
    const reset = frame.querySelector<HTMLElement>('[data-demo="filter-reset"]');
    if (!reset) return;
    await sleep(250, signal);
    await hoverOn(reset, signal);
    await sleep(380, signal);
    await click(reset, signal);
    pending.delete(undo);
    await sleep(1000, signal);
  };

  const scenesFor = (cycle: number): Scene[] => {
    const odd = cycle % 2 === 1;
    const first = SWAPS[(cycle * 2) % SWAPS.length];
    const second = SWAPS[(cycle * 2 + 1) % SWAPS.length];
    const legend: [string, string] = odd
      ? ['departments', 'legend-comercial']
      : ['groups', 'legend-sales'];
    const filterControls = () =>
      Array.from(
        frame.querySelectorAll('[data-demo^="filter-remove-"], [data-demo="filter-reset"]'),
      );
    return [
      {
        name: 'colleagues',
        area: () => [cardEl('bank-total')],
        play: (signal) => colleagues(signal, odd ? ['rosana', 'mateo'] : ['mateo', 'ava']),
      },
      { name: 'area-chart', area: () => [cardEl('active')], play: sweepArea },
      {
        name: 'legend',
        area: () => [cardEl(legend[0])],
        play: (signal) => toggleLegend(signal, legend),
      },
      {
        name: 'bars',
        area: () => [cardEl('bank')],
        play: (signal) => hoverBars(signal, odd ? ['jul', 'nov'] : ['mar', 'aug']),
      },
      {
        name: 'people',
        area: () => [cardEl('people')],
        play: (signal) => peopleRows(signal, odd ? 'ava' : 'rosana'),
      },
      { name: 'filters', area: filterControls, play: filtersScene },
      {
        name: `swap-${first[0]}`,
        area: () => [cardEl(first[0]), cardEl(first[1])],
        play: (signal) => swap(first[0], first[1], signal),
      },
      {
        name: `swap-${second[0]}`,
        area: () => [cardEl(second[0]), cardEl(second[1])],
        play: (signal) => swap(second[0], second[1], signal),
      },
    ];
  };

  /* -------------------------------------------------------------- playback */

  let cycle = 0;
  let sceneIndex = 0;
  let controller: AbortController | null = null;
  let started = false;

  const run = async (signal: AbortSignal) => {
    await sleep(started ? RESUME_LEAD : START_DELAY, signal);
    if (!started && stacked()) placeCursor({ x: frame.offsetWidth - 28, y: 40 });
    started = true;
    setMode('arrow');
    cursor.toggleAttribute('data-visible', true);
    await sleep(220, signal);
    for (;;) {
      const scenes = scenesFor(cycle);
      stage.dataset.cycle = String(cycle);
      let played = 0;
      while (sceneIndex < scenes.length) {
        const scene = scenes[sceneIndex];
        yieldToFocus();
        // Narrow layout: no swaps, and a scene whose card is off screen waits for another turn.
        if (stacked() && (scene.name.startsWith('swap-') || !onScreen(scene.area()))) {
          sceneIndex += 1;
          continue;
        }
        played += 1;
        applyLayout();
        stage.dataset.scene = scene.name;
        await reveal(scene.area(), signal);
        await scene.play(signal);
        pending.clear();
        sceneIndex += 1;
      }
      // Nothing was on screen: hide the pointer and look again shortly.
      if (played === 0) {
        unhover();
        await sleep(700, signal);
      } else await sleep(500, signal);
      sceneIndex = 0;
      cycle += 1;
    }
  };

  const start = () => {
    if (controller) return;
    controller = new AbortController();
    stage.dataset.state = 'playing';
    run(controller.signal).catch((error) => {
      if (!isAbort(error)) console.error(error);
    });
  };

  /** Stops at once and leaves the dashboard tidy: no hover, no half-done drag, cursor hidden. */
  const stop = (state: string) => {
    stage.dataset.state = state;
    if (!controller) return;
    controller.abort();
    controller = null;
    if (cursorMotion) {
      const now = livePosition();
      cursorMotion.cancel();
      cursorMotion = null;
      placeCursor(now);
    }
    unhover();
    cancelDrag();
    act(() => {
      for (const undo of pending) undo();
      restorePerson();
    });
    pending.clear();
    cursor.toggleAttribute('data-visible', false);
    setMode('arrow');
  };

  /* ---------------------------------------------------------------- gating */

  let destroyed = false;
  let inView = false;
  let pageVisible = !document.hidden;
  let userActive = false;
  let pointerInside = false;
  let resumeTimer = 0;
  let startTimer = 0;

  const update = () => {
    const blocked = !inView || !pageVisible ? 'off' : userActive ? 'paused' : null;
    if (destroyed || blocked) {
      window.clearTimeout(startTimer);
      startTimer = 0;
      stop(blocked ?? 'off');
      return;
    }
    if (controller || startTimer) return;
    // Debounced, so hovering on the edge of the viewport does not stutter.
    startTimer = window.setTimeout(() => {
      startTimer = 0;
      if (destroyed || !inView || !pageVisible || userActive) return;
      if (focusInside()) handOver();
      else start();
    }, 250);
  };

  /**
   * Keyboard focus inside the dashboard is the reader's: the autoplay itself never moves it. A
   * control merely clicked with the mouse keeps an invisible focus, which must not hold the demo
   * paused for the rest of the visit, and scrolling the page with the keyboard does not count.
   */
  let keyboardFocus = false;
  let lastKeyAt = -Infinity;
  const passiveKeys = new Set([
    'PageUp',
    'PageDown',
    'Home',
    'End',
    'ArrowUp',
    'ArrowDown',
    'ArrowLeft',
    'ArrowRight',
    'Shift',
    'Control',
    'Alt',
    'Meta',
    'CapsLock',
  ]);
  const isTextEntry = (node: Element) =>
    (node instanceof HTMLInputElement && !['button', 'checkbox', 'radio'].includes(node.type)) ||
    node instanceof HTMLTextAreaElement ||
    (node instanceof HTMLElement && node.isContentEditable);
  const focusInside = () => {
    const active = document.activeElement;
    if (!active || active === document.body || !frame.contains(active)) return false;
    return keyboardFocus || isTextEntry(active);
  };

  const armResume = () => {
    window.clearTimeout(resumeTimer);
    resumeTimer = window.setTimeout(tryResume, RESUME_DELAY);
  };

  const tryResume = () => {
    resumeTimer = 0;
    if (destroyed || !userActive) return;
    // Still pointing at it or focused inside: check again later. Leaving (pointerleave or a
    // focusout) re-arms the full delay; a focused node that is removed ends it at the next check.
    if (pointerInside || focusInside()) return armResume();
    // Close any menu the reader left open, then release a mouse-click focus, before the demo
    // drives the controls again.
    frame
      .querySelectorAll<HTMLElement>('[aria-expanded="true"]')
      .forEach((trigger) => act(() => trigger.click()));
    const active = document.activeElement;
    if (active instanceof HTMLElement && frame.contains(active)) active.blur();
    userActive = false;
    update();
  };

  /** Hands the dashboard to the reader and keeps a resume check armed, so no pause is final. */
  const handOver = () => {
    userActive = true;
    update();
    if (!resumeTimer) armResume();
  };

  /** Stops the run (via handOver) when the reader has focus inside the dashboard. */
  const yieldToFocus = () => {
    if (!focusInside()) return;
    handOver();
    throw pausedError();
  };

  /** Real input inside the dashboard hands control to the reader straight away. */
  const onInput = (event: Event) => {
    if (!event.isTrusted) return;
    // Focus moved by the autoplay's own clicks (a React effect focusing a control) is not input.
    if (event.type === 'focusin' && scripting > 0) return;
    if (event.type === 'pointerdown') keyboardFocus = false;
    if (event.type === 'focusin') keyboardFocus = event.timeStamp - lastKeyAt < 600;
    if (event instanceof KeyboardEvent && !passiveKeys.has(event.key)) keyboardFocus = true;
    const touch = event instanceof PointerEvent && event.pointerType === 'touch';
    if (event instanceof PointerEvent && !touch) pointerInside = true;
    handOver();
    // Touch has no hover to leave, so count down from the last touch.
    if (touch) armResume();
  };
  const onEnter = (event: PointerEvent) => {
    if (event.isTrusted && event.pointerType !== 'touch') pointerInside = true;
  };
  const onLeave = (event: PointerEvent) => {
    if (!event.isTrusted) return;
    pointerInside = false;
    if (userActive) armResume();
  };
  const onFocusOut = (event: FocusEvent) => {
    if (!event.isTrusted || !userActive) return;
    if (event.relatedTarget instanceof Node && frame.contains(event.relatedTarget)) return;
    armResume();
  };
  /** Fallback for a pointer that left without a pointerleave (e.g. while the page scrolled). */
  const onDocumentMove = (event: PointerEvent) => {
    if (!pointerInside || !event.isTrusted) return;
    if (event.target instanceof Node && frame.contains(event.target)) return;
    pointerInside = false;
    if (userActive) armResume();
  };
  /** Keeps the scripted hover events inside the dashboard, away from page-level listeners. */
  const contain = (event: Event) => {
    if (!event.isTrusted) event.stopPropagation();
  };
  const onVisibility = () => {
    pageVisible = !document.hidden;
    update();
  };
  /** Any key, page-wide, so focus arriving by Tab from outside is recognised as keyboard focus. */
  const onDocumentKey = (event: KeyboardEvent) => {
    if (event.isTrusted) lastKeyAt = event.timeStamp;
  };

  const inputs = ['pointermove', 'pointerdown', 'keydown', 'focusin'] as const;
  inputs.forEach((type) => frame.addEventListener(type, onInput, true));
  frame.addEventListener('pointerenter', onEnter);
  frame.addEventListener('pointerleave', onLeave);
  frame.addEventListener('focusout', onFocusOut, true);
  SYNTHETIC_TYPES.forEach((type) => frame.addEventListener(type, contain));
  document.addEventListener('pointermove', onDocumentMove, { passive: true });
  document.addEventListener('visibilitychange', onVisibility);
  document.addEventListener('keydown', onDocumentKey, true);
  // On screen = at least a third of its height visible. Height, not area: on phones the page
  // scrolls the dashboard sideways, so less than half of its width is ever in view.
  const observer = new IntersectionObserver(
    ([entry]) => {
      const height = entry?.boundingClientRect.height ?? 0;
      const enough = Math.min(height / 3, window.innerHeight / 3);
      inView = !!entry && entry.isIntersecting && entry.intersectionRect.height >= enough;
      update();
    },
    { threshold: Array.from({ length: 21 }, (_, index) => index / 20) },
  );
  observer.observe(frame);

  setMode('arrow');
  placeCursor(position);
  stage.dataset.state = 'off';

  return {
    destroy() {
      destroyed = true;
      window.clearTimeout(resumeTimer);
      window.clearTimeout(startTimer);
      observer.disconnect();
      inputs.forEach((type) => frame.removeEventListener(type, onInput, true));
      frame.removeEventListener('pointerenter', onEnter);
      frame.removeEventListener('pointerleave', onLeave);
      frame.removeEventListener('focusout', onFocusOut, true);
      SYNTHETIC_TYPES.forEach((type) => frame.removeEventListener(type, contain));
      document.removeEventListener('pointermove', onDocumentMove);
      document.removeEventListener('visibilitychange', onVisibility);
      document.removeEventListener('keydown', onDocumentKey, true);
      stop('off');
      settleReturns();
      // Back to the designed layout.
      for (const id of CARDS) layout.set(id, { ...HOME[id] });
      applyLayout();
      frame.style.removeProperty('perspective');
      hideZone();
      stage.querySelectorAll(`.${styles.ripple}`).forEach((node) => node.remove());
    },
  };
}

/** The note that tells screen reader users about the autoplay. */
const NOTE =
  'This dashboard plays an automatic demo: a virtual cursor tries its controls and rearranges its cards. It pauses while you point at, touch or focus the dashboard, and resumes a few seconds after you leave.';

/**
 * Autoplay layer for the full dashboard. Render it inside DashboardFrame: it draws the virtual
 * cursor and drop zone in the frame's design space and directs the cards next to it.
 */
export function DashboardShowcase({
  enabled,
  onRestoreFilters,
}: {
  /** The autoplay prop: false renders nothing and never plays. */
  enabled: boolean;
  onRestoreFilters: () => void;
}) {
  const reduceMotion = useHydratedReducedMotion();
  const stageRef = useRef<HTMLDivElement>(null);
  const cursorRef = useRef<HTMLDivElement>(null);
  const zoneRef = useRef<HTMLDivElement>(null);
  const restore = useRef(onRestoreFilters);
  useLayoutEffect(() => {
    restore.current = onRestoreFilters;
  });

  useEffect(() => {
    const stage = stageRef.current;
    const cursor = cursorRef.current;
    const zone = zoneRef.current;
    const frame = stage?.parentElement;
    if (!enabled || reduceMotion || !stage || !cursor || !zone || !frame) return;
    if (typeof IntersectionObserver === 'undefined' || typeof frame.animate !== 'function') return;
    const director = createDirector({
      frame,
      stage,
      cursor,
      zone,
      restoreFilters: () => restore.current(),
    });
    return director.destroy;
  }, [enabled, reduceMotion]);

  if (!enabled || reduceMotion) return null;
  return (
    <>
      <p className={styles.note}>{NOTE}</p>
      <div ref={zoneRef} className={styles.zone} aria-hidden="true" />
      <div ref={stageRef} className={styles.stage} aria-hidden="true">
        <div ref={cursorRef} className={styles.cursor}>
          <div className={styles.icons}>
            <svg className={`${styles.icon} ${styles.arrow}`} viewBox="0 0 20 28">
              <path
                d="M2 2v20.2l5.1-4.7 3.3 7.7a1 1 0 0 0 1.3.5l2.6-1.1a1 1 0 0 0 .5-1.3l-3.2-7.4h6.8L2 2Z"
                fill="#101820"
                stroke="#fff"
                strokeWidth="1.6"
                strokeLinejoin="round"
              />
            </svg>
            <Hand className={`${styles.icon} ${styles.hand} ${styles.open}`} fingers={OPEN} />
            <Hand className={`${styles.icon} ${styles.hand} ${styles.closed}`} fingers={CLOSED} />
          </div>
        </div>
      </div>
    </>
  );
}

/** Fingers and thumb as round-capped strokes over one palm (24×24 box, centred hotspot). */
const OPEN =
  'M8.5 11.4L7.9 5.1M11.2 10.8L11 3.7M13.9 11L14.2 4.4M16.8 12.2L17.6 7.2M7.4 16.2L4.6 12.6';
const CLOSED = 'M8.5 11.2V8.8M11.2 10.8V8M13.9 11V8.5M16.8 12V10M7.5 15.6L6.1 13.8';
const PALM =
  'M7.1 11.6C7.1 10.7 7.8 10 8.7 10h7.9c.9 0 1.6.7 1.6 1.6v3.9c0 3.4-2.1 5.9-5.1 5.9h-1.8c-2 0-3.5-1.1-4.3-2.9L7.1 11.6Z';

/** A white hand with a dark outline: the outline pass is drawn wider, then the fill on top. */
function Hand({ className, fingers }: { className: string; fingers: string }) {
  return (
    <svg className={className} viewBox="0 0 24 24">
      <g stroke="#101820" strokeLinecap="round" strokeLinejoin="round">
        <path d={fingers} fill="none" strokeWidth="4.1" />
        <path d={PALM} fill="#101820" strokeWidth="1.8" />
      </g>
      <g stroke="#fff" strokeLinecap="round">
        <path d={fingers} fill="none" strokeWidth="2.3" />
        <path d={PALM} fill="#fff" stroke="none" />
      </g>
    </svg>
  );
}
```

### workforce-dashboard/showcase.module.css

```css
/*
 * Autoplay showcase layers, drawn in the dashboard's 1526×881 design space so they scale with it.
 * The stage (virtual cursor, click ripples) sits above every layer, the drop zone sits between
 * the resting cards and a card that is being dragged (z-index 25, set by the director).
 */

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

.stage {
  position: absolute;
  inset: 0;
  z-index: 60;
  overflow: hidden;
  pointer-events: none;
}

/* Zero-size anchor whose `translate` is the pointer's hotspot. */
.cursor {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.cursor[data-visible] {
  opacity: 1;
}

/* Pressed with `scale` around the hotspot on every click. */
.icons {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
}

.icon {
  position: absolute;
  display: block;
  overflow: visible;
  opacity: 0;
  scale: 0.9;
  filter: drop-shadow(0 1.2px 1.6px rgb(16 24 40 / 0.34));
  transition:
    opacity 0.12s ease,
    scale 0.16s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* The arrow's tip (2, 2 in its 20×28 box, drawn at 0.9) is the hotspot. */
.arrow {
  top: -1.8px;
  left: -1.8px;
  width: 18px;
  height: 25.2px;
  transform-origin: 1.8px 1.8px;
}

/* Hands are centred on the hotspot, as the system grab cursors are. */
.hand {
  top: -13px;
  left: -13px;
  width: 26px;
  height: 26px;
}

.cursor[data-mode='arrow'] .arrow,
.cursor[data-mode='grab'] .open,
.cursor[data-mode='grabbing'] .closed {
  opacity: 1;
  scale: 1;
}

.ripple {
  position: absolute;
  top: -13px;
  left: -13px;
  width: 26px;
  height: 26px;
  border: 1.6px solid color-mix(in srgb, var(--wf-accent) 60%, transparent);
  border-radius: 50%;
  background: color-mix(in srgb, var(--wf-accent) 14%, transparent);
  opacity: 0;
}

/* Where the dragged card will land, revealed once the card in that slot moves away. */
.zone {
  position: absolute;
  z-index: 15;
  border: 1.4px dashed color-mix(in srgb, var(--wf-accent) 55%, transparent);
  border-radius: 4px;
  background: color-mix(in srgb, var(--wf-accent) 5%, transparent);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.zone[data-visible] {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .stage,
  .zone {
    display: none;
  }
}
```

### workforce-dashboard/heart.tsx

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

'use client';

import { useId } from 'react';

/*
 * The health score's heart, drawn inline: a grid fading out from its middle, the raised heart
 * shape with its soft shadow, a blush surface, and three red waves that fill it. Each drawing is
 * sized by its class in the CSS (team-cards.module.css). Gradients and filters take ids from
 * useId, so two dashboards on a page never share them.
 */

const HEART =
  'M89.1474 0C107.825 0 122.962 14.5394 122.962 34.8946C122.962 75.605 76.8512 98.8681 61.481 107.592C46.1107 98.8681 0 75.605 0 34.8946C0 14.5394 15.3702 0 33.8145 0C45.25 0 55.3329 5.81577 61.481 11.6315C67.6291 5.81577 77.712 0 89.1474 0Z';

const WAVE =
  'M62.2134 20.59C26.9109 8.85154 6.02843 43.5632 0 62.3863L103.809 211.341C144.923 126.266 217.747 -33.834 180.129 6.36151C133.108 56.6059 106.341 35.2632 62.2134 20.59Z';

/** The grid behind the heart: 12 × 6 squares of 20.49 units, faded by a radial gradient. */
export function HeartPattern({ className }: { className: string }) {
  const id = useId();
  const step = 20.4937;
  const lines: string[] = [];
  for (let column = 0; column <= 12; column++) {
    const x = 0.384 + column * step;
    lines.push(`M${x.toFixed(3)} 0.384V123.346`);
  }
  for (let row = 0; row <= 6; row++) {
    const y = 0.384 + row * step;
    lines.push(`M0.384 ${y.toFixed(3)}H246.308`);
  }
  return (
    <svg className={className} viewBox="0 0 246.692 123.73" fill="none" aria-hidden="true">
      <path d={lines.join('')} stroke={`url(#${id})`} strokeOpacity="0.32" strokeWidth="0.768512" />
      <defs>
        <radialGradient
          id={id}
          cx="0"
          cy="0"
          r="1"
          gradientUnits="userSpaceOnUse"
          gradientTransform="translate(123.346 61.8652) rotate(90) scale(61.481 122.962)"
        >
          <stop style={{ stopColor: 'var(--wf-heart-grid)' }} />
          <stop offset="1" style={{ stopColor: 'var(--wf-heart-grid)', stopOpacity: 0 }} />
        </radialGradient>
      </defs>
    </svg>
  );
}

/** The raised heart: the card's surface colour, ringed, with a soft drop shadow. */
export function HeartShadow({ className }: { className: string }) {
  const id = useId();
  return (
    <svg className={className} viewBox="0 0 179.832 165.037" fill="none" aria-hidden="true">
      <g filter={`url(#${id})`} style={{ fill: 'var(--wf-surface)', stroke: 'var(--wf-surface)' }}>
        <path
          stroke="none"
          d="M117.582 16.1388C136.26 16.1388 151.397 30.6782 151.397 51.0334C151.397 91.7437 105.286 115.007 89.9159 123.73C74.5457 115.007 28.435 91.7437 28.435 51.0334C28.435 30.6782 43.8052 16.1388 62.2495 16.1388C73.685 16.1388 83.7678 21.9545 89.9159 27.7703C96.064 21.9545 106.147 16.1388 117.582 16.1388Z"
        />
        <path
          fill="none"
          strokeWidth="3.84256"
          d="M117.582 14.2179C137.339 14.2179 153.318 29.6349 153.318 51.0333C153.318 72.1739 141.337 88.6468 127.699 100.811C114.062 112.973 98.4793 121.08 90.8646 125.401L89.9164 125.94L88.9672 125.401C81.3525 121.08 65.7699 112.973 52.1332 100.811C38.495 88.6468 26.5141 72.1739 26.5141 51.0333C26.5141 29.6262 42.7349 14.2179 62.2494 14.2179C73.5176 14.2179 83.4478 19.5501 89.9154 25.1778C96.383 19.5499 106.314 14.2179 117.582 14.2179Z"
        />
      </g>
      <defs>
        <filter
          id={id}
          x="0"
          y="0"
          width="179.832"
          height="165.037"
          filterUnits="userSpaceOnUse"
          colorInterpolationFilters="sRGB"
        >
          <feFlood floodOpacity="0" result="clear" />
          <feColorMatrix
            in="SourceAlpha"
            values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
            result="alpha"
          />
          <feOffset dy="12.2962" />
          <feGaussianBlur stdDeviation="12.2962" />
          <feColorMatrix values="0 0 0 0 0.0533 0 0 0 0 0.0693 0 0 0 0 0.1067 0 0 0 0.06 0" />
          <feBlend in2="clear" result="shadow" />
          <feBlend in="SourceGraphic" in2="shadow" result="shape" />
        </filter>
      </defs>
    </svg>
  );
}

/** The heart's blush, under the liquid. */
export function HeartSurface({ className }: { className: string }) {
  return (
    <svg className={className} viewBox="0 0 122.962 107.592" aria-hidden="true">
      <path d={HEART} style={{ fill: 'var(--wf-heart-blush)' }} />
    </svg>
  );
}

/**
 * One of the three waves of red liquid. `glow` adds the white inner light along its top edge;
 * the gradient runs from `from` to `to` (user units of the 190.6 × 211.3 drawing).
 */
export function HeartWave({
  className,
  opacity,
  glow,
  from,
  to,
}: {
  className: string;
  opacity: number;
  glow: boolean;
  from: [number, number];
  to: [number, number];
}) {
  const id = useId();
  return (
    <svg className={className} viewBox="0 0 190.591 211.341" fill="none" aria-hidden="true">
      <g filter={glow ? `url(#${id}-glow)` : undefined}>
        <path d={WAVE} fill={`url(#${id}-fill)`} fillOpacity={opacity} />
      </g>
      <defs>
        {glow ? (
          <filter
            id={`${id}-glow`}
            x="0"
            y="0"
            width="190.591"
            height="220.563"
            filterUnits="userSpaceOnUse"
            colorInterpolationFilters="sRGB"
          >
            <feFlood floodOpacity="0" result="clear" />
            <feBlend in="SourceGraphic" in2="clear" result="shape" />
            <feColorMatrix
              in="SourceAlpha"
              values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
              result="alpha"
            />
            <feOffset dy="9.22215" />
            <feGaussianBlur stdDeviation="12.2962" />
            <feComposite in2="alpha" operator="arithmetic" k2="-1" k3="1" />
            <feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.72 0" />
            <feBlend in2="shape" />
          </filter>
        ) : null}
        <linearGradient
          id={`${id}-fill`}
          x1={from[0]}
          y1={from[1]}
          x2={to[0]}
          y2={to[1]}
          gradientUnits="userSpaceOnUse"
        >
          <stop stopColor="#e93535" />
          <stop offset="1" stopColor="#e93535" stopOpacity="0" />
        </linearGradient>
      </defs>
    </svg>
  );
}
```

### workforce-dashboard/icons.tsx

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

/*
 * The dashboard's icons, drawn inline so they take the colour tokens (currentColor or a CSS
 * variable) in light and dark. Every one is decorative (aria-hidden) and sized by its class in
 * the CSS, never by width/height attributes.
 */

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

type IconProps = { className?: string };

const glyph = (className: string | undefined, viewBox: string, children: ReactNode) => (
  <svg className={className} viewBox={viewBox} fill="none" aria-hidden="true" focusable="false">
    {children}
  </svg>
);

/** The invented product's mark: a clock face cut into a rounded tile, white on the top bar. */
export function LogoMark({ className }: IconProps) {
  return glyph(
    className,
    '0 0 23.27 31.027',
    <>
      <rect x="0.6" y="4.4" width="22.07" height="22.07" rx="6.6" fill="currentColor" />
      <circle cx="11.635" cy="15.435" r="6.4" fill="var(--wf-topbar)" />
      <path
        d="M11.635 11.4v4.3l2.9 1.8"
        stroke="currentColor"
        strokeWidth="1.7"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </>,
  );
}

export function SearchIcon({ className }: IconProps) {
  return glyph(
    className,
    '0 0 31.7917 31.7917',
    <>
      <rect width="31.7917" height="31.7917" rx="15.8958" fill="var(--wf-topbar-field)" />
      <path
        d="M15.2997 9.9349C18.2611 9.9349 20.6646 12.3383 20.6646 15.2997C20.6646 18.2611 18.2611 20.6646 15.2997 20.6646C12.3383 20.6646 9.9349 18.2611 9.9349 15.2997C9.9349 12.3383 12.3383 9.9349 15.2997 9.9349ZM15.2997 19.4724C17.6048 19.4724 19.4724 17.6048 19.4724 15.2997C19.4724 12.994 17.6048 11.1271 15.2997 11.1271C12.994 11.1271 11.1271 12.994 11.1271 15.2997C11.1271 17.6048 12.994 19.4724 15.2997 19.4724ZM20.3576 19.5147L22.0439 21.2005L21.2005 22.0439L19.5147 20.3576L20.3576 19.5147Z"
        fill="#fff"
      />
    </>,
  );
}

export function BellIcon({ className }: IconProps) {
  return glyph(
    className,
    '0 0 31.7917 31.7917',
    <>
      <rect width="31.7917" height="31.7917" rx="15.8958" fill="var(--wf-topbar-field)" />
      <path
        d="M20.6646 18.8763H21.8568V20.0685H9.9349V18.8763H11.1271V14.7036C11.1271 13.4389 11.6295 12.2259 12.5238 11.3316C13.4181 10.4373 14.6311 9.9349 15.8958 9.9349C17.1606 9.9349 18.3735 10.4373 19.2678 11.3316C20.1622 12.2259 20.6646 13.4389 20.6646 14.7036V18.8763ZM19.4724 18.8763V14.7036C19.4724 13.7551 19.0956 12.8454 18.4248 12.1746C17.7541 11.5039 16.8444 11.1271 15.8958 11.1271C14.9473 11.1271 14.0376 11.5039 13.3668 12.1746C12.6961 12.8454 12.3193 13.7551 12.3193 14.7036V18.8763H19.4724ZM14.1076 21.2607H17.6841V22.4529H14.1076V21.2607Z"
        fill="#fff"
      />
      <circle cx="20.8729" cy="11.1271" r="1.58958" fill="#fb3748" />
      <circle
        cx="20.8729"
        cy="11.1271"
        r="2.38437"
        stroke="var(--wf-topbar-field)"
        strokeWidth="1.58958"
      />
    </>,
  );
}

/** Two arrows, left and right: the task switcher in the tracker, and a sidebar item. */
export function SwitchIcon({ className }: IconProps) {
  return glyph(
    className,
    '0 0 15.8958 15.8958',
    <path
      d="M10.3621 7.97772L13.3128 10.9284L10.3621 13.8791L9.51922 13.0362L11.0309 11.5239L3.17917 11.5245V10.3323H11.0309L9.51922 8.8206L10.3621 7.97772ZM5.53374 2.01678L6.37661 2.85966L4.86492 4.37135H12.7167V5.56354H4.86492L6.37661 7.07524L5.53374 7.91811L2.58307 4.96745L5.53374 2.01678Z"
      fill="currentColor"
    />,
  );
}

export function CalendarIcon({ className }: IconProps) {
  return glyph(
    className,
    '0 0 14.3062 14.3062',
    <path
      d="M10.7297 1.43062H13.5909C13.7806 1.43062 13.9626 1.50599 14.0967 1.64014C14.2309 1.77428 14.3062 1.95622 14.3062 2.14594V13.5909C14.3062 13.7806 14.2309 13.9626 14.0967 14.0967C13.9626 14.2309 13.7806 14.3062 13.5909 14.3062H0.715312C0.5256 14.3062 0.343657 14.2309 0.20951 14.0967C0.075363 13.9626 0 13.7806 0 13.5909V2.14594C0 1.95622 0.075363 1.77428 0.20951 1.64014C0.343657 1.50599 0.5256 1.43062 0.715312 1.43062H3.57656V0H5.00719V1.43062H9.29906V0H10.7297V1.43062ZM9.29906 2.86125H5.00719V4.29187H3.57656V2.86125H1.43062V5.7225H12.8756V2.86125H10.7297V4.29187H9.29906V2.86125ZM12.8756 7.15312H1.43062V12.8756H12.8756V7.15312Z"
      fill="currentColor"
    />,
  );
}

export function CloseIcon({ className }: IconProps) {
  return glyph(
    className,
    '0 0 9.5375 9.5375',
    <path
      d="M7.15312 2.38437L2.38437 7.15312M2.38437 2.38437L7.15312 7.15312"
      stroke="currentColor"
      strokeWidth="1.19219"
      strokeLinecap="round"
      strokeLinejoin="round"
    />,
  );
}

export function MenuIcon({ className }: IconProps) {
  return glyph(
    className,
    '0 0 16 16',
    <path
      d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"
      stroke="currentColor"
      strokeWidth="1.3"
      strokeLinecap="round"
    />,
  );
}

/** Sidebar glyphs, 15.9 units square, in currentColor. */
const NAV_PATHS: Record<string, string> = {
  grid: 'M13.3128 2.58307C13.4709 2.58307 13.6225 2.64588 13.7343 2.75766C13.8461 2.86945 13.9089 3.02107 13.9089 3.17917V12.7167C13.9089 12.8748 13.8461 13.0264 13.7343 13.1382C13.6225 13.25 13.4709 13.3128 13.3128 13.3128H2.58307C2.42498 13.3128 2.27336 13.25 2.16157 13.1382C2.04978 13.0264 1.98698 12.8748 1.98698 12.7167V3.17917C1.98698 3.02107 2.04978 2.86945 2.16157 2.75766C2.27336 2.64588 2.42498 2.58307 2.58307 2.58307H13.3128ZM7.35182 8.54401H3.17917V12.1206H7.35182V8.54401ZM12.7167 8.54401H8.54401V12.1206H12.7167V8.54401ZM7.35182 3.77526H3.17917V7.35182H7.35182V3.77526ZM12.7167 3.77526H8.54401V7.35182H12.7167V3.77526Z',
  card: 'M2.58307 2.58307H13.3128C13.4709 2.58307 13.6225 2.64588 13.7343 2.75766C13.8461 2.86945 13.9089 3.02107 13.9089 3.17917V12.7167C13.9089 12.8748 13.8461 13.0264 13.7343 13.1382C13.6225 13.25 13.4709 13.3128 13.3128 13.3128H2.58307C2.42498 13.3128 2.27336 13.25 2.16157 13.1382C2.04978 13.0264 1.98698 12.8748 1.98698 12.7167V3.17917C1.98698 3.02107 2.04978 2.86945 2.16157 2.75766C2.27336 2.64588 2.42498 2.58307 2.58307 2.58307ZM12.7167 7.35182H3.17917V12.1206H12.7167V7.35182ZM12.7167 6.15964V3.77526H3.17917V6.15964H12.7167ZM9.1401 9.7362H11.5245V10.9284H9.1401V9.7362Z',
  arrows:
    'M10.3621 7.97772L13.3128 10.9284L10.3621 13.8791L9.51922 13.0362L11.0309 11.5239L3.17917 11.5245V10.3323H11.0309L9.51922 8.8206L10.3621 7.97772ZM5.53374 2.01678L6.37661 2.85966L4.86492 4.37135H12.7167V5.56354H4.86492L6.37661 7.07524L5.53374 7.91811L2.58307 4.96745L5.53374 2.01678Z',
  history:
    'M7.94792 1.98698C11.2401 1.98698 13.9089 4.65569 13.9089 7.94792C13.9089 11.2401 11.2401 13.9089 7.94792 13.9089C4.65569 13.9089 1.98698 11.2401 1.98698 7.94792H3.17917C3.17917 10.5815 5.31437 12.7167 7.94792 12.7167C10.5815 12.7167 12.7167 10.5815 12.7167 7.94792C12.7167 5.31437 10.5815 3.17917 7.94792 3.17917C6.30866 3.17917 4.86254 4.00595 4.00476 5.26549H5.56354V6.45768H1.98698V2.88112H3.17917V4.37135C4.26644 2.92285 5.99809 1.98698 7.94792 1.98698ZM8.54401 4.96745V7.70054L10.4771 9.63367L9.63367 10.4771L7.35182 8.1941V4.96745H8.54401Z',
  bill: 'M12.7167 13.9089H3.17917C3.02107 13.9089 2.86945 13.8461 2.75766 13.7343C2.64588 13.6225 2.58307 13.4709 2.58307 13.3128V2.58307C2.58307 2.42498 2.64588 2.27336 2.75766 2.16157C2.86945 2.04978 3.02107 1.98698 3.17917 1.98698H12.7167C12.8748 1.98698 13.0264 2.04978 13.1382 2.16157C13.25 2.27336 13.3128 2.42498 13.3128 2.58307V13.3128C13.3128 13.4709 13.25 13.6225 13.1382 13.7343C13.0264 13.8461 12.8748 13.9089 12.7167 13.9089ZM12.1206 12.7167V3.17917H3.77526V12.7167H12.1206ZM5.56354 6.15964H10.3323V7.35182H5.56354V6.15964ZM5.56354 8.54401H10.3323V9.7362H5.56354V8.54401Z',
  exchange:
    'M7.94792 13.9089C4.65569 13.9089 1.98698 11.2401 1.98698 7.94792C1.98698 4.65569 4.65569 1.98698 7.94792 1.98698C11.2401 1.98698 13.9089 4.65569 13.9089 7.94792C13.9089 11.2401 11.2401 13.9089 7.94792 13.9089ZM7.94792 12.7167C9.21267 12.7167 10.4256 12.2142 11.3199 11.3199C12.2142 10.4256 12.7167 9.21267 12.7167 7.94792C12.7167 6.68317 12.2142 5.47022 11.3199 4.5759C10.4256 3.68159 9.21267 3.17917 7.94792 3.17917C6.68317 3.17917 5.47022 3.68159 4.5759 4.5759C3.68159 5.47022 3.17917 6.68317 3.17917 7.94792C3.17917 9.21267 3.68159 10.4256 4.5759 11.3199C5.47022 12.2142 6.68317 12.7167 7.94792 12.7167ZM4.96745 8.54401H10.3323V9.7362H7.94792V11.5245L4.96745 8.54401ZM7.94792 6.15964V4.37135L10.9284 7.35182H5.56354V6.15964H7.94792Z',
  settings:
    'M5.97246 3.17917L7.52648 1.62515C7.63826 1.5134 7.78985 1.45062 7.94792 1.45062C8.10598 1.45062 8.25757 1.5134 8.36935 1.62515L9.92337 3.17917H12.1206C12.2787 3.17917 12.4303 3.24197 12.5421 3.35376C12.6539 3.46555 12.7167 3.61717 12.7167 3.77526V5.97246L14.2707 7.52648C14.3824 7.63826 14.4452 7.78985 14.4452 7.94792C14.4452 8.10598 14.3824 8.25757 14.2707 8.36935L12.7167 9.92337V12.1206C12.7167 12.2787 12.6539 12.4303 12.5421 12.5421C12.4303 12.6539 12.2787 12.7167 12.1206 12.7167H9.92337L8.36935 14.2707C8.25757 14.3824 8.10598 14.4452 7.94792 14.4452C7.78985 14.4452 7.63826 14.3824 7.52648 14.2707L5.97246 12.7167H3.77526C3.61717 12.7167 3.46555 12.6539 3.35376 12.5421C3.24197 12.4303 3.17917 12.2787 3.17917 12.1206V9.92337L1.62515 8.36935C1.5134 8.25757 1.45062 8.10598 1.45062 7.94792C1.45062 7.78985 1.5134 7.63826 1.62515 7.52648L3.17917 5.97246V3.77526C3.17917 3.61717 3.24197 3.46555 3.35376 3.35376C3.46555 3.24197 3.61717 3.17917 3.77526 3.17917H5.97246ZM4.37135 4.37135V6.46662L2.89006 7.94792L4.37135 9.42921V11.5245H6.46662L7.94792 13.0058L9.42921 11.5245H11.5245V9.42921L13.0058 7.94792L11.5245 6.46662V4.37135H9.42921L7.94792 2.89006L6.46662 4.37135H4.37135ZM7.94792 10.3323C7.31554 10.3323 6.70907 10.0811 6.26191 9.63392C5.81475 9.18677 5.56354 8.58029 5.56354 7.94792C5.56354 7.31554 5.81475 6.70907 6.26191 6.26191C6.70907 5.81475 7.31554 5.56354 7.94792 5.56354C8.58029 5.56354 9.18677 5.81475 9.63392 6.26191C10.0811 6.70907 10.3323 7.31554 10.3323 7.94792C10.3323 8.58029 10.0811 9.18677 9.63392 9.63392C9.18677 10.0811 8.58029 10.3323 7.94792 10.3323ZM7.94792 9.1401C8.2641 9.1401 8.56734 9.0145 8.79092 8.79092C9.0145 8.56734 9.1401 8.2641 9.1401 7.94792C9.1401 7.63173 9.0145 7.32849 8.79092 7.10491C8.56734 6.88133 8.2641 6.75573 7.94792 6.75573C7.63173 6.75573 7.32849 6.88133 7.10491 7.10491C6.88133 7.32849 6.75573 7.63173 6.75573 7.94792C6.75573 8.2641 6.88133 8.56734 7.10491 8.79092C7.32849 9.0145 7.63173 9.1401 7.94792 9.1401Z',
  headphone:
    'M7.94792 3.17917C6.68317 3.17917 5.47022 3.68159 4.5759 4.5759C3.68159 5.47022 3.17917 6.68317 3.17917 7.94792H4.96745C5.28364 7.94792 5.58687 8.07352 5.81045 8.2971C6.03403 8.52068 6.15964 8.82392 6.15964 9.1401V12.1206C6.15964 12.4368 6.03403 12.74 5.81045 12.9636C5.58687 13.1872 5.28364 13.3128 4.96745 13.3128H3.17917C2.86298 13.3128 2.55974 13.1872 2.33616 12.9636C2.11258 12.74 1.98698 12.4368 1.98698 12.1206V7.94792C1.98698 4.65569 4.65569 1.98698 7.94792 1.98698C11.2401 1.98698 13.9089 4.65569 13.9089 7.94792V12.1206C13.9089 12.4368 13.7832 12.74 13.5597 12.9636C13.3361 13.1872 13.0329 13.3128 12.7167 13.3128H10.9284C10.6122 13.3128 10.309 13.1872 10.0854 12.9636C9.8618 12.74 9.7362 12.4368 9.7362 12.1206V9.1401C9.7362 8.82392 9.8618 8.52068 10.0854 8.2971C10.309 8.07352 10.6122 7.94792 10.9284 7.94792H12.7167C12.7167 6.68317 12.2142 5.47022 11.3199 4.5759C10.4256 3.68159 9.21267 3.17917 7.94792 3.17917ZM3.17917 9.1401V12.1206H4.96745V9.1401H3.17917ZM10.9284 9.1401V12.1206H12.7167V9.1401H10.9284Z',
  money:
    'M2.58307 2.58307H13.3128C13.4709 2.58307 13.6225 2.64588 13.7343 2.75766C13.8461 2.86945 13.9089 3.02107 13.9089 3.17917V12.7167C13.9089 12.8748 13.8461 13.0264 13.7343 13.1382C13.6225 13.25 13.4709 13.3128 13.3128 13.3128H2.58307C2.42498 13.3128 2.27336 13.25 2.16157 13.1382C2.04978 13.0264 1.98698 12.8748 1.98698 12.7167V3.17917C1.98698 3.02107 2.04978 2.86945 2.16157 2.75766C2.27336 2.64588 2.42498 2.58307 2.58307 2.58307ZM3.17917 3.77526V12.1206H12.7167V3.77526H3.17917ZM5.86159 9.1401H9.1401C9.21915 9.1401 9.29496 9.1087 9.35086 9.05281C9.40675 8.99691 9.43815 8.9211 9.43815 8.84206C9.43815 8.76301 9.40675 8.6872 9.35086 8.63131C9.29496 8.57541 9.21915 8.54401 9.1401 8.54401H6.75573C6.36049 8.54401 5.98145 8.387 5.70197 8.10753C5.4225 7.82806 5.26549 7.44901 5.26549 7.05378C5.26549 6.65854 5.4225 6.27949 5.70197 6.00002C5.98145 5.72055 6.36049 5.56354 6.75573 5.56354H7.35182V4.37135H8.54401V5.56354H10.0342V6.75573H6.75573C6.67668 6.75573 6.60087 6.78713 6.54498 6.84303C6.48908 6.89892 6.45768 6.97473 6.45768 7.05378C6.45768 7.13282 6.48908 7.20863 6.54498 7.26453C6.60087 7.32042 6.67668 7.35182 6.75573 7.35182H9.1401C9.53534 7.35182 9.91439 7.50883 10.1939 7.7883C10.4733 8.06778 10.6303 8.44682 10.6303 8.84206C10.6303 9.23729 10.4733 9.61634 10.1939 9.89581C9.91439 10.1753 9.53534 10.3323 9.1401 10.3323H8.54401V11.5245H7.35182V10.3323H5.86159V9.1401Z',
  add: 'M7.35182 7.35182V3.77526H8.54401V7.35182H12.1206V8.54401H8.54401V12.1206H7.35182V8.54401H3.77526V7.35182H7.35182Z',
  chevronDown:
    'M7.94792 8.64654L10.8986 5.69587L11.7415 6.53875L7.94792 10.3323L4.15438 6.53875L4.99725 5.69587L7.94792 8.64654Z',
};

export type GlyphName = keyof typeof NAV_PATHS;

/** A 15.9-unit line glyph in currentColor. */
export function Glyph({ name, className }: IconProps & { name: GlyphName }) {
  return glyph(className, '0 0 15.8958 15.8958', <path d={NAV_PATHS[name]} fill="currentColor" />);
}

/** Small chevrons of the colleague pager, 14.3 units square, in currentColor. */
export function PagerArrow({ direction, className }: IconProps & { direction: 'left' | 'right' }) {
  return glyph(
    className,
    '0 0 14.3062 14.3062',
    <path
      d={
        direction === 'left'
          ? 'M6.58397 7.15258L9.23957 9.80818L8.48098 10.5668L5.0668 7.15258L8.48098 3.73839L9.23957 4.49698L6.58397 7.15258Z'
          : 'M7.72239 7.15258L5.0668 4.49698L5.82539 3.73839L9.23957 7.15258L5.82539 10.5668L5.0668 9.80818L7.72239 7.15258Z'
      }
      fill="currentColor"
    />,
  );
}

/** The card menu's three dots. */
export function DotsIcon({ className }: IconProps) {
  return glyph(
    className,
    '0 0 13.2859 13.2616',
    <g fill="currentColor">
      <ellipse cx="2.9229" cy="6.63078" rx="1.32859" ry="1.32616" />
      <ellipse cx="6.64295" cy="6.63078" rx="1.32859" ry="1.32616" />
      <ellipse cx="10.363" cy="6.63078" rx="1.32859" ry="1.32616" />
    </g>,
  );
}

/** The 16-dot resize grip in a card's bottom-right corner. */
const GRIP = [
  [8.61975, 0.663078],
  [7.29406, 1.98896],
  [8.61975, 3.31542],
  [5.96837, 3.31542],
  [7.29406, 4.6415],
  [8.61975, 5.96719],
  [4.64191, 4.6415],
  [5.96837, 5.96719],
  [7.29406, 7.29365],
  [8.61975, 8.61953],
  [3.31544, 5.96719],
  [4.64191, 7.29365],
  [5.96837, 8.61953],
  [1.98975, 7.29365],
  [3.31544, 8.61953],
  [0.66329, 8.61953],
];
export function CornerGrip({ className, style }: IconProps & { style?: CSSProperties }) {
  return (
    <svg
      className={className}
      style={style}
      viewBox="0 0 9.28282 9.28263"
      aria-hidden="true"
      focusable="false"
    >
      <g fill="currentColor">
        {GRIP.map(([cx, cy]) => (
          <circle key={`${cx}-${cy}`} cx={cx} cy={cy} r="0.663078" />
        ))}
      </g>
    </svg>
  );
}

/** The small corner arrow of the "Grouped by" pickers. */
export function CornerArrow({ className }: IconProps) {
  return glyph(
    className,
    '0 0 2.99987 3.00018',
    <path
      fillRule="evenodd"
      clipRule="evenodd"
      d="M0.333297 0C0.149222 0 0 0.149222 0 0.333297V2.66637C0 2.71033 0.00850977 2.7523 0.0239718 2.79073C0.0731305 2.91348 0.19319 3.00018 0.333499 3.00018H2.66658C2.85065 3.00018 2.99987 2.85096 2.99987 2.66688C2.99987 2.48281 2.85065 2.33358 2.66658 2.33358H0.666594V0.333297C0.666594 0.149222 0.517372 0 0.333297 0Z"
      fill="currentColor"
    />,
  );
}

/** A tooltip's tail and the hairline it sits on, in the tooltip's colour. */
export function TooltipTail({ className }: IconProps) {
  return glyph(
    className,
    '0 0 14.3062 6.35833',
    <g fill="currentColor">
      <path d="M8.22738 5.74658C7.7618 6.21216 7.00695 6.21216 6.54137 5.74658L2.61562 1.82083L12.1531 1.82083L8.22738 5.74658Z" />
      <rect width="14.3062" height="1.58958" rx="0.794792" transform="matrix(1 0 0 -1 0 1.58958)" />
    </g>,
  );
}

/** The green check badge next to "All employees". */
export function CheckBadge({ className }: IconProps) {
  return glyph(
    className,
    '0 0 12.7167 12.7167',
    <path
      d="M6.35833 11.1271C3.72455 11.1271 1.58958 8.99211 1.58958 6.35833C1.58958 3.72455 3.72455 1.58958 6.35833 1.58958C8.99211 1.58958 11.1271 3.72455 11.1271 6.35833C11.1271 8.99211 8.99211 11.1271 6.35833 11.1271ZM5.88289 8.26583L9.25439 4.89385L8.58009 4.21955L5.88289 6.91723L4.53381 5.56815L3.85951 6.24245L5.88289 8.26583Z"
      fill="#1fc16b"
    />,
  );
}

/** The star badge on the monthly employee's avatar. */
export function StarBadge({ className }: IconProps) {
  return glyph(
    className,
    '0 0 23.049 23.049',
    <>
      <circle cx="11.5245" cy="9.9349" r="8.34531" fill="var(--wf-surface)" />
      <circle cx="11.5245" cy="9.9349" r="7.15312" fill="#1fc16b" />
      <path
        d="M11.5242 12.3331L8.57688 14.1076L9.37815 10.7959L6.75573 8.58157L10.198 8.30972L11.5242 5.16615L12.8505 8.30972L16.2932 8.58157L13.6703 10.7959L14.4716 14.1076L11.5242 12.3331Z"
        fill="#fff"
      />
    </>,
  );
}

/** A stopwatch, for the punctuality score. */
export function StopwatchIcon({ className }: IconProps) {
  return glyph(
    className,
    '0 0 11.1272 14.8364',
    <g stroke="currentColor" strokeWidth="1.45" strokeLinecap="round" strokeLinejoin="round">
      <circle cx="5.5636" cy="8.6" r="4.9" />
      <path d="M5.5636 6.1v2.6l1.7 1.1M4 1.1h3.1M9.4 3.9l.9-.9" />
    </g>,
  );
}

/** A dot in a legend: the series colour ringed in the card's surface. */
export function LegendDot({ className, color }: IconProps & { color: string }) {
  return glyph(
    className,
    '0 0 12.7167 12.7167',
    <circle
      cx="6.35833"
      cy="6.35833"
      r="3.97396"
      fill={color}
      stroke="var(--wf-surface)"
      strokeWidth="1.58958"
    />,
  );
}
```

### workforce-dashboard/types.ts

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

/** Shared state of the workforce dashboard. Every name in it is invented. */

/** The invented product the dashboard belongs to (the logo's accessible name). */
export const PRODUCT = 'Hourline';

export type Person = 'all' | 'mateo' | 'ava' | 'rosana';

export type Filter = { id: string; label: string };

export const initialFilters: Filter[] = [
  { id: 'country', label: 'Brazil' },
  { id: 'subsidiary', label: 'All subsidiary' },
  { id: 'period', label: 'Dec 01 - Dec 31 - 2022' },
];

/** Regions work uncontrolled on their own and controlled inside the full dashboard. */
export type PersonProps = {
  person?: Person;
  onPersonChange?: (person: Person) => void;
};

export type FilterProps = {
  filters?: Filter[];
  onRemoveFilter?: (id: string) => void;
  onResetFilters?: () => void;
};
```

### workforce-dashboard/use-reduced-motion.ts

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

'use client';

import { useSyncExternalStore } from 'react';

const query = '(prefers-reduced-motion: reduce)';

const subscribe = (onStoreChange: () => void) => {
  const media = window.matchMedia(query);
  media.addEventListener('change', onStoreChange);
  return () => media.removeEventListener('change', onStoreChange);
};

const getClientSnapshot = () => window.matchMedia(query).matches;
const getServerSnapshot = () => false;

export function useHydratedReducedMotion() {
  return useSyncExternalStore(subscribe, getClientSnapshot, getServerSnapshot);
}
```