# Today cards

> An assistant’s new-session screen that opens on your day: a greeting over the composer, rotating suggestions, and four cards for meetings, tasks, pipelines and numbers.

The home screen of an invented sales assistant. The greeting changes with the suggestion picked, word by word, as the row of suggestions slides to centre it. Under the composer, the date and four cards: upcoming meetings that count down, tasks to tick (with a toast to undo and the next task sliding in), the busiest pipelines and the last 30 days’ numbers. Each card has its actions on hover: a Browse palette with search and groups, a menu whose Ask item opens questions in a submenu. A side card announces the cards and opens a release note.

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

Category: Dashboards.

## Anatomy

1. **Sidebar**: Workspace, navigation, pinned pipelines and sessions, 215 px wide. Under 900 px it hides behind a menu button as a drawer.
2. **Greeting**: One line around the reader’s name, in grey. It changes with the suggestion: the old words blur out, the new ones come in 55 ms apart.
3. **Suggestions**: A looping row of chips with arrows; the picked one slides to the middle. On its own it moves on every 7 s while on screen.
4. **Composer**: The message field, its placeholder set by the suggestion, the Files, Integrations and Routines tools, and a tray to connect apps.
5. **Card**: A title and three rows on a soft grey tile. In Tasks due each row has a box to tick and its due label, late ones in red.
6. **Card actions**: Add, Browse and More, shown on hover or focus, each with a tooltip that waits 350 ms.
7. **Recent performance**: Three figures over the last 30 days, each with a ring and its change. Its one action asks from a list of six questions.
8. **Side card**: Announces the cards over a drifting picture of them, with Learn more (the release note) and Try it. Dismissible; hidden under 900 px.

## When to use

- The empty state of an assistant or chat product: a new session that opens on the reader’s day instead of a blank box.
- When a few things from other tools (meetings, tasks, pipelines, figures) should be one click from a question about them.
- Rotating suggestions that teach what the assistant can do, with the greeting and placeholder changing to match.
- Cards that stay calm until pointed at: actions, tooltips and “what a click does” only on hover or focus.
- A screen that must work in a wide window, a narrow panel and a phone: it lays out by its own width, not the window’s.

## When not to use

- A full dashboard to read figures over time. Four cards of three rows each are a glance; use charts and tables instead.
- Long lists of meetings or tasks. Each card shows three; the Browse palette is for jumping, not for managing a backlog.
- A product with no assistant or composer: without a question to ask, the cards are just widgets. Use a workspace home.
- A screen read mostly by screen reader or keyboard in a hurry: the hover affordances add steps. Keep plain links there.
- When the suggestions’ motion would distract from work under way. Turn `autoplay` off, or don’t open on this screen.

## Installation

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

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

Or copy all the source files below into one folder of your project. Either way it needs React 19.2 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 { TodayCards } from './today-cards';

export function NewSession() {
  return <TodayCards name="Marta" defaultSuggestion="recap" />;
}
```

## States

- **Opening**: The greeting for the first suggestion, the four cards at rest and the side card.
- **Another suggestion**: Recap a meeting picked: the name leads the greeting, and the placeholder follows. `defaultSuggestion="recap"`.
- **Card under the pointer**: Its actions show, and Browse’s tooltip after its 350 ms wait.
- **Row under the pointer**: A row trades its figures for what a click does, here Join meeting, over a fade.
- **Task ticked**: Crossed out and held, with the Undo toast. Live, it leaves after 1.6 s.
- **Next tasks in**: Two tasks ticked away: the rest moved up and the next ones due came in.
- **Nothing due**: Every task done: the card says so in one quiet line.
- **Browse meetings**: The palette over a dimmed screen, grouped by day; the footer button says what Enter does.
- **More, with questions**: Top pipelines’ menu: its Ask item opens three questions in a submenu.
- **Ask about performance**: Recent performance asks from its own list of six questions.
- **Release note**: Learn more opens a tall sheet over the screen; it scrolls on its own in the demo.
- **Draft, side card gone**: A question in the composer turns the mic into Send; the suggestions stop rotating.
- **Phone**: Under 640 px the cards stack, the greeting shrinks and the sidebar sits behind a menu button.
- **Phone, drawer open**: The sidebar slides over the screen with a scrim; the scrim closes it.

## How it works

- The suggestions are a loop drawn three times over: the row is translated so the picked chip’s centre meets the view’s, on a 0.48 s ease-out. When the pick leaves the middle copy, the row jumps back to the same chip in it with its transition off for one frame, so the loop never ends. On its own it moves on every 7 s while on screen, and stops while a palette, the note or a draft is open.
- The greeting is a list of words, the name one of them in grey. A new pick keeps the old line for 180 ms as its words blur out, while the new words come in one after another (55 ms apart) from a blur and 4 px lower. The composer’s placeholder changes with it.
- A ticked task is crossed out at once and a toast offers Undo. After 1.6 s its row blurs away; the rows under it slide up into its place (FLIP, by the Web Animations API, measured in a layout effect) and the next task due fades in at the bottom. Undo cancels the timers and puts it back where it was.
- Each card shows add, Browse and More on hover or focus, with a tooltip that waits 350 ms. Browse opens a palette over a dimmed screen: a search, groups, the arrow keys and a footer button that says what Enter will do (Join meeting, Open, Open pipeline). More opens a menu whose Ask item opens three questions in a submenu; Recent performance asks from its own list. A question goes into the composer.
- Rows say what a click does (Join meeting, Open task, Open pipeline, Ask Sevenfold AI) over a fade on hover, in place of their figures.
- Menus, the palette, the note and the toast are drawn at the screen’s root and placed from their button’s box in the screen’s own px, so they are right when the screen is drawn scaled. Each enters by opacity, a 3 px blur and a 3% scale, and leaves the same way in 140 ms.
- The rainbow across the top is one SVG: seven strokes along the same curve, blurred once by an SVG filter and masked at both ends, over a soft grey haze. The side card’s picture is the meetings card, drawn again with a slow drift of three radial gradients behind it.
- Layout is by container queries on the root: the sidebar becomes a drawer under 900 px, and under 640 px the cards stack, the greeting shrinks and a submenu opens under its menu.
- While on screen and untouched, a demo pointer opens the release note and scrolls it, browses meetings, ticks two tasks, asks Recent performance, browses pipelines and opens two menus with their questions, then puts the tasks back. Any real input hands it over; it comes back after 8 s of quiet.

## API reference

- **name** (`string`, default `'Marta'`): The reader’s first name, in grey in the greeting.
- **defaultSuggestion** (`'apps' | 'csv' | 'deal' | 'follow' | 'recap' | 'changed' | 'routine'`, default `'deal'`): The suggestion picked first, which sets the first greeting and placeholder.
- **autoplay** (`boolean`, default `true`): While on screen and untouched, the suggestions rotate and a demo pointer shows the note, the palettes, the tasks and the menus. Real input hands control to the reader.
- **colorScheme** (`'light' | 'dark' | 'system'`): Light or dark colours, or the reader’s system setting; without it, the page’s color-scheme.
- **defaultState** (`TodayCardsState`): Opens held in a state, with the demo and the rotation off: { palette?: 'meetings' | 'tasks' | 'pipelines'; menu?: card id; pick?, subPick?: the item drawn active; article?: boolean; promo?: boolean; done?, gone?: task ids ticked or ticked away; drawer?: boolean; text?: a draft; hover?: a card, row or button drawn as hovered }.
- **onTaskToggle** (`(id: string, done: boolean) => void`): Called when a task is ticked, or put back by Undo or a second click.
- **onSuggestionChange** (`(id: string) => void`): Called when another suggestion is picked, by the reader or the rotation.
- **onSend** (`(text: string) => void`): Called with the message when the composer is sent.
- **className** (`string`): Extra class for the root.

## Accessibility

- Landmarks and headings: the sidebar is an aside with a nav, the screen a main whose h1 is the greeting (its full sentence, for screen readers, while the animated words are hidden from them); the day and each card are sections named by their headings.
- The suggestions are a group of toggle buttons (aria-pressed) with previous and next buttons; only the real copy of the loop is in the tab order.
- Card actions are named buttons that also show on focus; More and Ask have aria-haspopup and aria-expanded. Menus are role="menu" with the arrow keys, ArrowRight or Enter into a submenu, ArrowLeft or Escape back, and the focus returns to the button.
- The Browse palette is a modal dialog whose search is a combobox over a listbox, with aria-activedescendant following the arrow keys; Enter runs the active item and Escape closes it.
- Each task’s box is a role="checkbox" named for the task; the toast is in a polite live region, with Undo as a real button.
- The release note is a modal dialog labelled by its title; it takes the focus on its close button and gives it back to Learn more.
- The demo pointer is aria-hidden, and under reduced motion nothing moves on its own: no rotation, no demo, no blurs or slides.

## Source

### today-cards/today-cards.tsx

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

'use client';

import {
  useEffect,
  useEffectEvent,
  useLayoutEffect,
  useRef,
  useState,
  useSyncExternalStore,
  type CSSProperties,
  type ReactNode,
} from 'react';
import {
  ASK,
  DATE_LABEL,
  MEETINGS,
  METRICS,
  PERFORMANCE_QUESTIONS,
  PIPELINES,
  PRODUCT,
  ROUTINES,
  SESSIONS,
  SUGGESTIONS,
  TASKS,
  TOP_PIPELINES,
  WORKSPACE,
  money,
  type Meeting,
} from './data';
import { DemoPointer, runDemo, type Point } from './demo-pointer';
import {
  AppMarks,
  DayTile,
  Icon,
  Mark,
  PipelineTile,
  Ring,
  Rings,
  RoutineTile,
  type IconName,
} from './icons';
import {
  Article,
  Menu,
  Palette,
  ToastView,
  type MenuItem,
  type PaletteKind,
  type Toast,
} from './overlays';
import { useAutoplay } from './use-autoplay';
import styles from './today-cards.module.css';

/*
 * Today cards: an assistant's new-session screen that opens on the reader's day. A sidebar, a
 * greeting over the composer, suggestions that change the greeting, and under them the date and
 * four cards: upcoming meetings, tasks due, top pipelines and recent performance. A card in the
 * sidebar announces the cards and links to a release note.
 *
 * - The suggestions are a loop: the row slides to centre the one picked (by click, the arrows or
 *   on its own every 7 s), and the greeting's words blur out and back in one after another.
 * - Each card shows its actions on hover (add, browse, more) with a tooltip. Browse opens a
 *   palette over a dimmed screen: search, groups, the arrow keys, and a footer button that says
 *   what Enter does. More opens a menu whose "Ask Sevenfold" item has questions in a submenu;
 *   Recent performance asks its own questions from a list.
 * - Ticking a task crosses it out and shows a toast with Undo; a moment later the row blurs
 *   away, the rows under it slide up (FLIP, by the Web Animations API) and the next task fades in.
 * - Rows show what a click does ("Join meeting", "Open task"…) on hover, over a fade.
 * - Layout is by container queries on the root: the sidebar is a drawer under 900 px, the cards
 *   stack under 640 px.
 * - While on screen and untouched, a demo pointer opens the release note, browses meetings,
 *   ticks tasks, opens the menus and browses pipelines, then puts the tasks back. Any real input
 *   hands control to the reader.
 */

type CardId = 'meetings' | 'tasks' | 'pipelines' | 'performance';
type Scheme = 'light' | 'dark' | 'system';
type MenuState = {
  card: CardId;
  kind: 'more' | 'ask';
  place: { top: number; left: number };
  width?: number;
  subSide: 'right' | 'left' | 'below';
  opener: HTMLElement | null;
  focus: boolean;
};

/**
 * A state to open in, held still: giving it turns the demo and the suggestions' rotation off.
 * - `palette`: a card's Browse palette open; `pick`, the item in it drawn active.
 * - `menu`: a card's menu open (More; Ask on Recent performance), without taking the focus;
 *   `pick`, an item drawn active (`'ask'` shows its questions) and `subPick` one of those.
 * - `article`: the release note open. `promo: false`: the side card already dismissed.
 * - `done`: tasks (by id) ticked, crossed out and held there, with the Undo toast.
 *   `gone`: tasks already ticked away, so the next ones have moved up.
 * - `drawer`: under 900 px of width, the sidebar open as a drawer.
 * - `text`: a draft in the composer.
 * - `hover`: a card, a row (by its id) or a card's button (`'meetings-browse'`…) drawn as under
 *   the pointer, its card's actions and its tooltip shown.
 */
export type TodayCardsState = {
  palette?: PaletteKind;
  menu?: CardId;
  pick?: string;
  subPick?: string;
  article?: boolean;
  promo?: boolean;
  done?: string[];
  gone?: string[];
  drawer?: boolean;
  text?: string;
  hover?: string;
};

/** How long a ticked task stays, crossed out, before it leaves; and its leaving. */
const LINGER = 1600;
const LEAVE = 200;
/** How long between two suggestions, on its own. */
const ROTATE = 7000;
/** How long an overlay takes to leave. */
const CLOSE = 140;
const N = SUGGESTIONS.length;

/** Whether the reader asks for less motion. */
function useReducedMotion() {
  return useSyncExternalStore(
    (change) => {
      const query = window.matchMedia('(prefers-reduced-motion: reduce)');
      query.addEventListener('change', change);
      return () => query.removeEventListener('change', change);
    },
    () => window.matchMedia('(prefers-reduced-motion: reduce)').matches,
    () => false,
  );
}

const minutes = (meeting: Meeting): ReactNode =>
  meeting.inMinutes === 0 ? (
    'Now'
  ) : (
    <>
      in <b>{meeting.inMinutes}</b>
      <small>m</small>
    </>
  );

/** The greeting, as words; the name is its own word, in grey. */
function greetingWords(index: number, name: string) {
  const [before, after] = SUGGESTIONS[index].greeting;
  const words: { text: string; name?: boolean; tail?: string }[] = before
    ? before.split(' ').map((text) => ({ text }))
    : [];
  const rest = after.startsWith(',') ? after.slice(1) : after;
  words.push({ text: name, name: true, tail: after.startsWith(',') ? ',' : undefined });
  if (rest.trim())
    words.push(
      ...rest
        .trim()
        .split(' ')
        .map((text) => ({ text })),
    );
  return words;
}

function Greeting({ index, name, leaving }: { index: number; name: string; leaving?: boolean }) {
  return (
    <span className={styles.greetingLine} data-leaving={leaving || undefined} aria-hidden="true">
      {greetingWords(index, name).map((word, i) => (
        <span
          key={i}
          className={styles.word}
          data-name={word.name || undefined}
          style={{ '--i': i } as CSSProperties}
        >
          {word.text}
          {word.tail && <span className={styles.wordTail}>{word.tail}</span>}
        </span>
      ))}
    </span>
  );
}

/** The cards, close up, for the side card and the release note. */
function Preview({ large }: { large?: boolean }) {
  return (
    <div className={styles.preview} data-large={large || undefined}>
      <div className={styles.previewGlow} />
      <div className={styles.previewCard}>
        <span className={styles.previewTitle}>Upcoming meetings</span>
        {[
          { title: 'Brightwater review', when: 'Now', live: true },
          { title: 'Kestrel check-in', when: '12' },
          { title: 'Oakmere pricing prep', when: '48' },
        ].map((row) => (
          <span key={row.title} className={styles.previewRow}>
            <DayTile day={14} live={row.live} className={styles.day} />
            <span className={styles.previewLabel}>{row.title}</span>
            <span className={styles.previewWhen} data-live={row.live || undefined}>
              {row.live ? (
                'Now'
              ) : (
                <>
                  in <b>{row.when}</b>m
                </>
              )}
            </span>
          </span>
        ))}
      </div>
      <div className={styles.previewCard} data-peek>
        <span className={styles.previewTitle}>Tasks due</span>
        {[0, 1, 2].map((i) => (
          <span key={i} className={styles.previewRow}>
            <span className={styles.box} />
          </span>
        ))}
      </div>
    </div>
  );
}

/** An icon button with its tooltip above it. */
function IconButton({
  icon,
  tip,
  part,
  expanded,
  controls,
  hover,
  onClick,
  children,
}: {
  icon?: IconName;
  tip: string;
  part: string;
  /** Drawn as under the pointer (a held state). */
  hover?: boolean;
  expanded?: boolean;
  controls?: string;
  onClick: (button: HTMLButtonElement) => void;
  children?: ReactNode;
}) {
  return (
    <button
      type="button"
      className={styles.iconButton}
      data-part={part}
      data-hover={hover || undefined}
      aria-label={tip}
      aria-haspopup={expanded !== undefined ? 'menu' : undefined}
      aria-expanded={expanded}
      aria-controls={expanded ? controls : undefined}
      onClick={(event) => onClick(event.currentTarget)}
    >
      {children ?? (icon && <Icon name={icon} className={styles.icon14} />)}
      <span className={styles.tip} aria-hidden="true">
        {tip}
      </span>
    </button>
  );
}

export function TodayCards({
  name = 'Marta',
  autoplay = true,
  colorScheme,
  defaultSuggestion = 'deal',
  defaultState,
  onTaskToggle,
  onSuggestionChange,
  onSend,
  className = '',
}: {
  /** The reader's first name, in the greeting. */
  name?: string;
  /** While on screen and untouched, the suggestions rotate and a demo pointer shows the cards. */
  autoplay?: boolean;
  /** Light or dark colours, or the reader's ('system'); without it, the page's color-scheme. */
  colorScheme?: Scheme;
  /** The suggestion picked first, by id ('apps', 'csv', 'deal', 'follow', 'recap', 'changed', 'routine'). */
  defaultSuggestion?: string;
  /** Opens in this state (a palette, a menu, ticked tasks…), held still: the demo is off. */
  defaultState?: TodayCardsState;
  /** Called when a task is ticked or put back. */
  onTaskToggle?: (id: string, done: boolean) => void;
  /** Called when another suggestion is picked, by the reader or the rotation. */
  onSuggestionChange?: (id: string) => void;
  /** Called with the message when the composer is sent. */
  onSend?: (text: string) => void;
  className?: string;
}) {
  const { ref: root, playing, take } = useAutoplay<HTMLDivElement>(8000);
  /** Held in defaultState: no demo, no rotation, and its toast stays. */
  const [held] = useState(defaultState !== undefined);
  const hovered = defaultState?.hover;
  const reduced = useReducedMotion();
  const app = useRef<HTMLDivElement>(null);
  const scroller = useRef<HTMLDivElement>(null);
  const pointer = useRef<HTMLSpanElement>(null);
  const sheet = useRef<HTMLDivElement>(null);

  // ——— Suggestions ———
  const first = Math.max(
    0,
    SUGGESTIONS.findIndex((item) => item.id === defaultSuggestion),
  );
  /** Where the row stands, in a track of three copies: the middle copy is the real one. */
  const [pos, setPos] = useState(N + first);
  const [snap, setSnap] = useState(false);
  const active = ((pos % N) + N) % N;
  const [greeting, setGreeting] = useState({ index: active, key: 0 });
  const [leavingGreeting, setLeavingGreeting] = useState<{ index: number; key: number } | null>(
    null,
  );
  const chipView = useRef<HTMLDivElement>(null);
  const chips = useRef<(HTMLButtonElement | null)[]>([]);
  const [shift, setShift] = useState(0);

  // ——— Cards ———
  const [done, setDone] = useState<string[]>(() => defaultState?.done ?? []);
  const [leaving, setLeaving] = useState<string[]>([]);
  const [gone, setGone] = useState<string[]>(() => defaultState?.gone ?? []);
  const timers = useRef(new Map<string, number[]>());
  const [palette, setPalette] = useState<{
    kind: PaletteKind;
    opener: HTMLElement | null;
    focus: boolean;
  } | null>(() =>
    defaultState?.palette ? { kind: defaultState.palette, opener: null, focus: false } : null,
  );
  const [paletteClosing, setPaletteClosing] = useState(false);
  const [menu, setMenu] = useState<MenuState | null>(null);
  const [menuClosing, setMenuClosing] = useState(false);
  const [article, setArticle] = useState<{ focus: boolean } | null>(() =>
    defaultState?.article ? { focus: false } : null,
  );
  const [articleClosing, setArticleClosing] = useState(false);
  const [promo, setPromo] = useState(defaultState?.promo ?? true);
  const [promoClosing, setPromoClosing] = useState(false);
  const [toast, setToast] = useState<Toast | null>(() =>
    defaultState?.done?.length
      ? {
          key: 0,
          message: 'Task done',
          undo: () => {
            defaultState.done?.forEach((id) => undo(id));
            setOldToast(null);
            setToast(null);
          },
        }
      : null,
  );
  const [oldToast, setOldToast] = useState<Toast | null>(null);
  const [drawer, setDrawer] = useState(!!defaultState?.drawer);
  const [open, setOpen] = useState({ pipelines: true, sessions: true });
  const [text, setText] = useState(defaultState?.text ?? '');
  /** What the demo (or a held state) points at inside an overlay. */
  const [demoPick, setDemoPick] = useState<{ item?: string | null; sub?: string | null }>(() => ({
    item: defaultState?.pick,
    sub: defaultState?.subPick,
  }));
  const composer = useRef<HTMLTextAreaElement>(null);
  const toastKey = useRef(0);
  /** The demo is driving: what it opens leaves the focus where it is. */
  const demoOn = useRef(false);

  const [scheme, setScheme] = useState<Scheme | undefined>(colorScheme);
  const [schemeSeen, setSchemeSeen] = useState(colorScheme);
  if (schemeSeen !== colorScheme) {
    setSchemeSeen(colorScheme);
    setScheme(colorScheme);
  }

  // The greeting follows the picked suggestion: the old words blur out as the new ones come in.
  if (greeting.index !== active) {
    setLeavingGreeting(greeting);
    setGreeting({ index: active, key: greeting.key + 1 });
  }
  const reportSuggestion = useEffectEvent((id: string) => onSuggestionChange?.(id));
  const firstActive = useRef(active);
  useEffect(() => {
    if (active === firstActive.current) return;
    firstActive.current = -1;
    reportSuggestion(SUGGESTIONS[active].id);
  }, [active]);
  useEffect(() => {
    if (!leavingGreeting) return;
    const timer = window.setTimeout(() => setLeavingGreeting(null), 220);
    return () => window.clearTimeout(timer);
  }, [leavingGreeting]);

  // The row slides so the picked chip sits in the middle of the view.
  useLayoutEffect(() => {
    const measure = () => {
      const view = chipView.current;
      const chip = chips.current[pos];
      if (!view || !chip) return;
      setShift(view.clientWidth / 2 - (chip.offsetLeft + chip.offsetWidth / 2));
    };
    measure();
    const observer = new ResizeObserver(measure);
    if (chipView.current) observer.observe(chipView.current);
    return () => observer.disconnect();
  }, [pos]);
  // Out of the middle copy, it jumps back to the same chip there, without a slide.
  const settle = () => {
    if (pos >= N && pos < 2 * N) return;
    setSnap(true);
    setPos(N + active);
  };
  useEffect(() => {
    if (!snap) return;
    const frame = requestAnimationFrame(() => requestAnimationFrame(() => setSnap(false)));
    return () => cancelAnimationFrame(frame);
  }, [snap]);
  useEffect(() => {
    if (reduced) settle();
  });

  const rotateOn = autoplay && !held && playing && !reduced && !palette && !article && !text;
  useEffect(() => {
    if (!rotateOn) return;
    const timer = window.setInterval(() => setPos((value) => value + 1), ROTATE);
    return () => window.clearInterval(timer);
  }, [rotateOn]);

  // ——— Toasts ———
  const showToast = (next: Omit<Toast, 'key'>) => {
    setOldToast(toast);
    toastKey.current += 1;
    setToast({ ...next, key: toastKey.current });
  };
  const dismissToast = () => {
    setOldToast(toast);
    setToast(null);
  };
  useEffect(() => {
    if (!oldToast) return;
    const timer = window.setTimeout(() => setOldToast(null), 220);
    return () => window.clearTimeout(timer);
  }, [oldToast]);
  useEffect(() => {
    if (!toast || (held && toast.key === 0)) return;
    const timer = window.setTimeout(() => {
      setOldToast(toast);
      setToast(null);
    }, 5000);
    return () => window.clearTimeout(timer);
  }, [toast]);

  // ——— Tasks ———
  const shownTasks = TASKS.filter((task) => !gone.includes(task.id)).slice(0, 3);
  const clearTimers = (id: string) => {
    timers.current.get(id)?.forEach((timer) => window.clearTimeout(timer));
    timers.current.delete(id);
  };
  const undo = (id: string) => {
    if (done.includes(id) || gone.includes(id)) onTaskToggle?.(id, false);
    clearTimers(id);
    setDone((list) => list.filter((item) => item !== id));
    setLeaving((list) => list.filter((item) => item !== id));
    setGone((list) => list.filter((item) => item !== id));
  };
  const tick = (id: string) => {
    if (done.includes(id)) {
      undo(id);
      return;
    }
    setDone((list) => [...list, id]);
    onTaskToggle?.(id, true);
    showToast({
      message: 'Task done',
      undo: () => {
        undo(id);
        setOldToast(null);
        setToast(null);
      },
    });
    const wait = reduced ? 900 : LINGER;
    const linger = window.setTimeout(() => setLeaving((list) => [...list, id]), wait);
    const second = window.setTimeout(
      () => {
        setLeaving((list) => list.filter((item) => item !== id));
        setGone((list) => [...list, id]);
        timers.current.delete(id);
      },
      wait + (reduced ? 0 : LEAVE),
    );
    timers.current.set(id, [linger, second]);
  };
  useEffect(() => {
    const all = timers.current;
    return () => all.forEach((list) => list.forEach((timer) => window.clearTimeout(timer)));
  }, []);

  // The rows under a task that left slide up into its place; a new last row fades in.
  const taskList = useRef<HTMLUListElement>(null);
  const tops = useRef(new Map<string, number>());
  const shownIds = shownTasks.map((task) => task.id).join();
  useLayoutEffect(() => {
    const list = taskList.current;
    if (!list) return;
    const next = new Map<string, number>();
    list.querySelectorAll<HTMLElement>('[data-task]').forEach((row) => {
      const id = row.dataset.task ?? '';
      next.set(id, row.offsetTop);
      if (reduced) return;
      const before = tops.current.get(id);
      if (before === undefined && tops.current.size) {
        row.animate(
          [
            { opacity: 0, filter: 'blur(3px)' },
            { opacity: 1, filter: 'blur(0)' },
          ],
          { duration: 260, delay: 120, easing: 'ease-out', fill: 'backwards' },
        );
      } else if (before !== undefined && before !== row.offsetTop) {
        row.animate(
          [{ transform: `translateY(${before - row.offsetTop}px)` }, { transform: 'none' }],
          {
            duration: 240,
            easing: 'cubic-bezier(0.25, 0.8, 0.25, 1)',
          },
        );
      }
    });
    tops.current = next;
  }, [shownIds, reduced]);

  // ——— Overlays ———
  /** A box's place in the app's own px (the app may be drawn scaled, as in the gallery). */
  const placeOf = (element: HTMLElement) => {
    const frame = app.current!;
    const outer = frame.getBoundingClientRect();
    const scale = outer.width / frame.offsetWidth || 1;
    const box = element.getBoundingClientRect();
    return {
      left: (box.left - outer.left) / scale,
      top: (box.top - outer.top) / scale,
      right: (box.right - outer.left) / scale,
      bottom: (box.bottom - outer.top) / scale,
      width: box.width / scale,
      frame: frame.offsetWidth,
    };
  };

  const closeMenu = (refocus: boolean) => {
    if (!menu) return;
    const opener = menu.opener;
    setMenuClosing(true);
    window.setTimeout(() => {
      setMenu(null);
      setMenuClosing(false);
    }, CLOSE);
    if (refocus) opener?.focus({ preventScroll: true });
  };
  const openMenu = (
    card: CardId,
    kind: 'more' | 'ask',
    button: HTMLElement,
    focus = !demoOn.current,
  ) => {
    if (menu && menu.opener === button) {
      closeMenu(false);
      return;
    }
    const box = placeOf(button);
    if (kind === 'ask') {
      const cardBox = placeOf(button.closest<HTMLElement>('[data-card]')!);
      setMenu({
        card,
        kind,
        place: { top: box.bottom + 6, left: cardBox.left + 8 },
        width: cardBox.width - 16,
        subSide: 'right',
        opener: button,
        focus,
      });
    } else {
      const width = 217;
      const left = Math.max(8, box.right - width);
      setMenu({
        card,
        kind,
        place: { top: box.bottom + 6, left },
        width,
        subSide: left + width + 232 <= box.frame ? 'right' : left >= 232 ? 'left' : 'below',
        opener: button,
        focus,
      });
    }
    setMenuClosing(false);
  };

  const closePalette = () => {
    if (!palette) return;
    const opener = palette.opener;
    setPaletteClosing(true);
    window.setTimeout(() => {
      setPalette(null);
      setPaletteClosing(false);
    }, CLOSE);
    if (palette.focus) opener?.focus({ preventScroll: true });
  };
  const openPalette = (kind: PaletteKind, opener: HTMLElement | null) => {
    const focus = !demoOn.current;
    setMenu(null);
    setPaletteClosing(false);
    setPalette({ kind, opener, focus });
  };

  const learnMore = useRef<HTMLButtonElement>(null);
  const closeArticle = () => {
    if (!article) return;
    setArticleClosing(true);
    window.setTimeout(() => {
      setArticle(null);
      setArticleClosing(false);
    }, CLOSE + 40);
    if (article.focus) learnMore.current?.focus({ preventScroll: true });
  };
  const closePromo = () => {
    setPromoClosing(true);
    window.setTimeout(() => setPromo(false), 180);
  };

  /** Puts a question in the composer, as picking a question starts a session asking it. */
  const ask = (question: string) => {
    setText(question);
    requestAnimationFrame(() => composer.current?.focus({ preventScroll: true }));
  };

  const menuItems = (card: CardId, kind: 'more' | 'ask'): MenuItem[] => {
    if (kind === 'ask')
      return PERFORMANCE_QUESTIONS.map((question) => ({
        id: question,
        label: question,
        icon: 'mark',
      }));
    const noun = card === 'meetings' ? 'meeting' : card === 'tasks' ? 'task' : 'pipeline';
    return [
      {
        id: 'ask',
        label: `Ask ${PRODUCT}`,
        icon: 'mark',
        sub: ASK[card as 'meetings' | 'tasks' | 'pipelines'],
      },
      { id: 'go', label: `Go to ${noun}s page`, icon: 'enter' },
      { id: 'settings', label: `${noun[0].toUpperCase()}${noun.slice(1)} settings`, icon: 'gear' },
    ];
  };

  // A menu held open by defaultState is placed from its button once the screen is laid out.
  const openHeldMenu = useEffectEvent(() => {
    const card = defaultState?.menu;
    if (!card) return;
    const part = card === 'performance' ? 'performance-ask' : `${card}-more`;
    const button = root.current?.querySelector<HTMLElement>(`[data-part="${part}"]`);
    if (button) openMenu(card, card === 'performance' ? 'ask' : 'more', button, false);
  });
  useLayoutEffect(() => openHeldMenu(), []);

  // ——— The demo ———
  const demo = autoplay && !held && playing && !reduced;
  const demoReset = useEffectEvent(() => {
    TASKS.forEach((task) => undo(task.id));
    setToast(null);
    setDemoPick({});
  });
  const demoAct = useEffectEvent((action: string, arg?: string) => {
    if (action === 'close-palette') closePalette();
    if (action === 'close-menu') closeMenu(false);
    if (action === 'close-article') closeArticle();
    if (action === 'pick') setDemoPick((value) => ({ ...value, item: arg ?? null }));
    if (action === 'sub') setDemoPick((value) => ({ ...value, sub: arg ?? null }));
    if (action === 'clear') setDemoPick({});
  });
  const demoState = useEffectEvent(() => ({
    promo,
    menu: !!menu,
    palette: !!palette,
    article: !!article,
  }));

  useEffect(() => {
    const stage = root.current;
    const arrow = pointer.current;
    const frame = app.current;
    if (!demo || !stage || !arrow || !frame) return;
    demoOn.current = true;
    const find = (selector: string) => stage.querySelector<HTMLElement>(selector);
    let lit: HTMLElement[] = [];
    /** Lights an element (and its card) as hovering would. */
    const hover = (element: HTMLElement | null) => {
      lit.forEach((item) => delete item.dataset.hover);
      lit = [];
      if (!element) return;
      const card = element.closest<HTMLElement>('[data-card]');
      for (const item of [element, card]) {
        if (item) {
          item.dataset.hover = '';
          lit.push(item);
        }
      }
    };
    const middle = (element: HTMLElement, dx = 0, dy = 0): Point => {
      const rect = stage.getBoundingClientRect();
      const scale = rect.width / stage.offsetWidth || 1;
      const box = element.getBoundingClientRect();
      return {
        x: (box.left + box.width * (0.5 + dx) - rect.left) / scale,
        y: (box.top + box.height * (0.5 + dy) - rect.top) / scale,
      };
    };
    /** Scrolls the screen (never the page) so an element is in view. */
    const reveal = (element: HTMLElement) => {
      const view = scroller.current;
      if (!view || !view.contains(element)) return false;
      const box = element.getBoundingClientRect();
      const port = view.getBoundingClientRect();
      if (box.top >= port.top + 8 && box.bottom <= port.bottom - 8) return false;
      view.scrollTo({
        top: view.scrollTop + box.top - port.top - port.height / 2,
        behavior: 'smooth',
      });
      return true;
    };

    const stop = runDemo(stage, arrow, async ({ wait, glide, glyph }) => {
      const to = async (selector: string, ms = 650, dx = 0, dy = 0) => {
        const element = find(selector);
        if (!element || !element.getClientRects().length) return null;
        if (reveal(element)) await wait(450);
        glyph('arrow');
        await glide(middle(element, dx, dy), ms);
        return element;
      };
      const point = async (selector: string, ms = 650, dx = 0, dy = 0) => {
        const element = await to(selector, ms, dx, dy);
        hover(element);
        return element;
      };
      const press = async (selector: string, ms = 650, dx = 0, dy = 0) => {
        const element = await point(selector, ms, dx, dy);
        if (!element) return false;
        glyph('hand');
        await wait(200);
        element.click();
        return true;
      };
      /** Scrolls the release note by `by` px over `ms`, with an ease-in-out. */
      const scrollSheet = async (by: number, ms: number) => {
        const target = sheet.current;
        if (!target) return;
        const from = target.scrollTop;
        const start = performance.now();
        await new Promise<void>((resolve) => {
          const tickFrame = (now: number) => {
            const t = Math.min(1, (now - start) / ms);
            target.scrollTop = from + by * (t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2);
            if (t < 1 && target.isConnected) requestAnimationFrame(tickFrame);
            else resolve();
          };
          requestAnimationFrame(tickFrame);
        });
        await wait(0);
      };

      for (;;) {
        await wait(900);
        // The side card's release note.
        if (demoState().promo && (await point('[data-part="learn-more"]', 900))) {
          await wait(450);
          glyph('hand');
          await wait(200);
          find('[data-part="learn-more"]')?.click();
          hover(null);
          glyph('arrow');
          await wait(900);
          await glide(middle(find('[data-part="article"]') ?? frame, -0.05, 0.1), 700);
          await scrollSheet(380, 1300);
          await wait(700);
          await scrollSheet(760, 1700);
          await wait(900);
          await glide(middle(frame, -0.36, -0.1), 800);
          glyph('hand');
          await wait(200);
          demoAct('close-article');
          glyph('arrow');
          await wait(1000);
        }
        // Upcoming meetings: a row, then Browse.
        await point('[data-part="meetings"] [data-row="m2"]', 900, 0.1);
        await wait(900);
        if (await point('[data-part="meetings-browse"]', 600)) {
          await wait(800);
          await press('[data-part="meetings-browse"]', 120);
          hover(null);
          await wait(600);
          for (const item of ['m2', 'm3', 'm4', 'go', 'configure']) {
            const row = await to(`[data-part="palette"] [data-item="${item}"]`, 380, -0.3);
            if (row) demoAct('pick', item);
            await wait(item === 'm3' ? 900 : 450);
          }
          await wait(500);
          demoAct('close-palette');
          demoAct('clear');
          await wait(900);
        }
        // Tasks due: tick two, the second one later.
        await point('[data-part="tasks"] [data-row]:nth-child(2)', 800, 0.1);
        await wait(700);
        if (await press('[data-part="tasks"] [data-row]:nth-child(1) [data-part="check"]', 500)) {
          await wait(2300);
          await press('[data-part="tasks"] [data-row]:nth-child(1) [data-part="check"]', 500);
          await wait(1000);
          await press('[data-part="tasks"] [data-row]:nth-child(3) [data-part="check"]', 500);
          await wait(2600);
        }
        // Recent performance: its questions.
        await point('[data-part="performance"] [data-row="win"]', 800, 0.1);
        await wait(800);
        if (await press('[data-part="performance-ask"]', 600)) {
          hover(null);
          await wait(500);
          for (const index of [2, 3, 4, 5]) {
            const row = await to(
              `[data-part="menu"] [role="menuitem"]:nth-child(${index + 1})`,
              380,
              -0.25,
            );
            if (row) demoAct('pick', PERFORMANCE_QUESTIONS[index]);
            await wait(index === 5 ? 900 : 500);
          }
          demoAct('close-menu');
          demoAct('clear');
          await wait(800);
        }
        // Top pipelines: Browse.
        if (await point('[data-part="pipelines-browse"]', 800)) {
          await wait(800);
          await press('[data-part="pipelines-browse"]', 120);
          hover(null);
          await wait(600);
          for (const item of ['sales', 'expansion', 'agency', 'configure']) {
            const row = await to(`[data-part="palette"] [data-item="${item}"]`, 420, -0.3);
            if (row) demoAct('pick', item);
            await wait(550);
          }
          demoAct('close-palette');
          demoAct('clear');
          await wait(900);
        }
        // Top pipelines' menu, and its questions.
        for (const card of ['pipelines', 'meetings'] as const) {
          if (!(await point(`[data-part="${card}-more"]`, 800))) continue;
          await wait(700);
          await press(`[data-part="${card}-more"]`, 120);
          hover(null);
          await wait(500);
          if (await to('[data-part="menu"] [data-item="ask"]', 450, 0.2)) demoAct('pick', 'ask');
          await wait(600);
          for (const question of ASK[card]) {
            const row = await to(`[data-part="submenu"] [data-item="${question}"]`, 420, -0.3);
            if (row) demoAct('sub', question);
            await wait(600);
          }
          demoAct('close-menu');
          demoAct('clear');
          await wait(1100);
        }
        hover(null);
        await glide(middle(frame, 0.25, 0.3), 900);
        demoReset();
        await wait(1800);
      }
    });
    return () => {
      stop();
      demoOn.current = false;
      hover(null);
      demoAct('clear');
    };
  }, [demo, root]);

  // ——— Rendering ———
  const suggestion = SUGGESTIONS[active];
  /** Whether a held hover is on this card: the card, one of its buttons or rows. */
  const cardLit = (id: CardId) =>
    !!hovered &&
    (hovered === id ||
      hovered.startsWith(`${id}-`) ||
      (id === 'meetings' && MEETINGS.some((item) => item.id === hovered)) ||
      (id === 'tasks' && TASKS.some((item) => item.id === hovered)) ||
      (id === 'pipelines' && TOP_PIPELINES.includes(hovered)) ||
      (id === 'performance' && METRICS.some((item) => item.id === hovered)));
  const card = (id: CardId, title: string, actions: ReactNode, body: ReactNode) => (
    <section
      className={styles.card}
      data-card
      data-part={id}
      data-hover={cardLit(id) || undefined}
      aria-labelledby={`tc-${id}`}
    >
      <header className={styles.cardHead}>
        <h3 className={styles.cardTitle} id={`tc-${id}`}>
          {title}
        </h3>
        <div className={styles.cardActions} data-part="card-actions">
          {actions}
        </div>
      </header>
      {body}
    </section>
  );
  const standardActions = (id: 'meetings' | 'tasks' | 'pipelines', noun: string) => (
    <>
      <IconButton
        icon="plus"
        tip={`New ${noun}`}
        part={`${id}-add`}
        hover={hovered === `${id}-add`}
        onClick={() => showToast({ message: `New ${noun}`, icon: 'mark' })}
      />
      <IconButton
        icon="browse"
        tip={`Browse ${id}`}
        part={`${id}-browse`}
        hover={hovered === `${id}-browse`}
        onClick={(button) => openPalette(id, button)}
      />
      <IconButton
        icon="more"
        tip="More"
        part={`${id}-more`}
        hover={hovered === `${id}-more`}
        expanded={menu?.card === id && !menuClosing}
        controls="tc-menu"
        onClick={(button) => openMenu(id, 'more', button)}
      />
    </>
  );

  const sidebar = (
    <aside
      className={styles.sidebar}
      data-part="sidebar"
      data-open={drawer || undefined}
      aria-label="Workspace"
    >
      <div className={styles.workspace}>
        <button type="button" className={styles.workspaceButton}>
          <span className={styles.workspaceLogo} aria-hidden="true">
            <Mark className={styles.icon12} />
          </span>
          <span className={styles.workspaceName}>{WORKSPACE}</span>
          <Icon name="chevron-down" className={styles.icon12} />
        </button>
        <button type="button" className={styles.sideIcon} aria-label="Search">
          <Icon name="search" className={styles.icon14} />
        </button>
        <button
          type="button"
          className={styles.sideIcon}
          aria-label="Close sidebar"
          onClick={() => setDrawer(false)}
        >
          <Icon name="panel" className={styles.icon14} />
        </button>
      </div>
      <nav className={styles.nav} aria-label="Main">
        <a
          className={styles.navItem}
          href="#new"
          aria-current="page"
          onClick={(event) => event.preventDefault()}
        >
          <Icon name="plus" className={styles.icon14} />
          New session
        </a>
        {(
          [
            ['Review', 'review'],
            ['Knowledge', 'book'],
            ['Settings', 'gear'],
          ] as [string, IconName][]
        ).map(([label, icon]) => (
          <a
            key={label}
            className={styles.navItem}
            href={`#${label.toLowerCase()}`}
            onClick={(event) => event.preventDefault()}
          >
            <Icon name={icon} className={styles.icon14} />
            {label}
          </a>
        ))}
      </nav>
      <div className={styles.sideGroup}>
        <button
          type="button"
          className={styles.sideHeading}
          aria-expanded={open.pipelines}
          onClick={() => setOpen((value) => ({ ...value, pipelines: !value.pipelines }))}
        >
          Pipelines
          <Icon name="caret-down" className={styles.caret} />
        </button>
        {open.pipelines && (
          <ul className={styles.sideList} data-kind="pipelines">
            {PIPELINES.slice(0, 5).map((pipeline, index) => (
              <li key={pipeline.id}>
                <a
                  className={styles.sideItem}
                  href={`#${pipeline.id}`}
                  onClick={(event) => event.preventDefault()}
                >
                  <PipelineTile pipeline={pipeline} className={styles.tile} />
                  <span className={styles.sideLabel}>{pipeline.name}</span>
                  <Icon
                    name="pin"
                    className={`${styles.pin} ${index === 0 ? styles.pinned : ''}`}
                  />
                </a>
              </li>
            ))}
            <li>
              <button
                type="button"
                className={styles.sideItem}
                onClick={() => openPalette('pipelines', null)}
              >
                <Icon name="more" className={styles.icon14} />
                <span className={styles.sideLabel}>More</span>
              </button>
            </li>
          </ul>
        )}
      </div>
      <div className={styles.sideGroup}>
        <button
          type="button"
          className={styles.sideHeading}
          aria-expanded={open.sessions}
          onClick={() => setOpen((value) => ({ ...value, sessions: !value.sessions }))}
        >
          Sessions
          <Icon name="caret-down" className={styles.caret} />
        </button>
        {open.sessions && (
          <ul className={styles.sideList} data-kind="sessions">
            {SESSIONS.map((session) => (
              <li key={session}>
                <a
                  className={styles.sideItem}
                  href="#session"
                  onClick={(event) => event.preventDefault()}
                >
                  <span className={styles.sessionDot} aria-hidden="true" />
                  <span className={styles.sideLabel}>{session}</span>
                  <Icon name="pin" className={`${styles.pin} ${styles.pinned}`} />
                </a>
              </li>
            ))}
          </ul>
        )}
      </div>
    </aside>
  );

  return (
    <div
      ref={root}
      className={`${styles.root} ${className}`}
      data-part="root"
      data-color-scheme={scheme}
      onPointerDownCapture={take}
      onKeyDownCapture={take}
      onWheelCapture={take}
      onPointerMove={(event) => {
        if (event.pointerType !== 'touch') take();
      }}
    >
      <div ref={app} className={styles.app} data-drawer={drawer || undefined}>
        {sidebar}
        <div className={styles.drawerScrim} onClick={() => setDrawer(false)} aria-hidden="true" />
        <main className={styles.main} data-part="main">
          <div className={styles.topbar}>
            <button
              type="button"
              className={styles.menuButton}
              aria-label="Open sidebar"
              aria-expanded={drawer}
              onClick={() => setDrawer(true)}
            >
              <Icon name="menu" className={styles.icon16} />
            </button>
            <button type="button" className={styles.history}>
              <Icon name="clock" className={styles.icon14} />
              History
              <Icon name="chevron-down" className={styles.icon12} />
            </button>
          </div>
          <div ref={scroller} className={styles.scroll}>
            <div className={styles.sky} aria-hidden="true">
              <svg className={styles.aurora} viewBox="0 0 1513 260" preserveAspectRatio="none">
                <defs>
                  <filter id="tc-soft" x="-10%" y="-60%" width="120%" height="220%">
                    <feGaussianBlur stdDeviation="7" />
                  </filter>
                  <filter id="tc-haze" x="-10%" y="-100%" width="120%" height="300%">
                    <feGaussianBlur stdDeviation="22" />
                  </filter>
                  <linearGradient id="tc-fade" x1="0" x2="1">
                    <stop offset="0" stopColor="#fff" stopOpacity="0.1" />
                    <stop offset="0.25" stopColor="#fff" stopOpacity="1" />
                    <stop offset="0.85" stopColor="#fff" stopOpacity="1" />
                    <stop offset="1" stopColor="#fff" stopOpacity="0.3" />
                  </linearGradient>
                  <mask id="tc-mask">
                    <rect width="1513" height="260" fill="url(#tc-fade)" />
                  </mask>
                </defs>
                <g mask="url(#tc-mask)">
                  <path
                    className={styles.haze}
                    d={AURORA}
                    transform="translate(0 10)"
                    filter="url(#tc-haze)"
                  />
                  <g filter="url(#tc-soft)" fill="none" strokeWidth="8" opacity="0.95">
                    {[
                      '#b4dcff',
                      '#c8f4ff',
                      '#ffffff',
                      '#fff0b0',
                      '#ffc489',
                      '#ffb0b4',
                      '#d9c6ff',
                    ].map((color, i) => (
                      <path
                        key={color}
                        d={AURORA}
                        stroke={color}
                        transform={`translate(0 ${i * 6 - 28})`}
                      />
                    ))}
                  </g>
                </g>
              </svg>
              <Rings className={styles.rings} />
            </div>

            <section className={styles.hero} aria-labelledby="tc-greeting">
              <Mark className={styles.mark} />
              <h1 className={styles.greeting} id="tc-greeting" data-part="greeting">
                <span className={styles.srOnly}>
                  {suggestion.greeting[0]} {name}
                  {suggestion.greeting[1]}
                </span>
                {leavingGreeting && (
                  <Greeting
                    key={`l${leavingGreeting.key}`}
                    index={leavingGreeting.index}
                    name={name}
                    leaving
                  />
                )}
                <Greeting key={greeting.key} index={greeting.index} name={name} />
              </h1>

              <div
                className={styles.chipRow}
                role="group"
                aria-label="Suggestions"
                data-part="suggestions"
              >
                <button
                  type="button"
                  className={styles.chipArrow}
                  aria-label="Previous suggestion"
                  onClick={() => setPos((value) => value - 1)}
                >
                  <Icon name="chevron-left" className={styles.icon12} />
                </button>
                <div ref={chipView} className={styles.chipView}>
                  <div
                    className={styles.chipTrack}
                    data-snap={snap || reduced || undefined}
                    style={{ transform: `translateX(${shift}px)` }}
                    onTransitionEnd={(event) => {
                      if (event.target === event.currentTarget) settle();
                    }}
                  >
                    {[0, 1, 2].flatMap((copy) =>
                      SUGGESTIONS.map((item, index) => {
                        const at = copy * N + index;
                        const real = copy === 1;
                        return (
                          <button
                            key={at}
                            ref={(element) => {
                              chips.current[at] = element;
                            }}
                            type="button"
                            className={styles.chip}
                            data-active={at === pos || undefined}
                            aria-pressed={real ? index === active : undefined}
                            aria-hidden={real ? undefined : true}
                            tabIndex={real ? 0 : -1}
                            onClick={() => {
                              setPos(at);
                              take();
                            }}
                          >
                            <span className={styles.chipIcon}>
                              <Icon name={item.icon} className={styles.icon12} />
                            </span>
                            {item.label}
                          </button>
                        );
                      }),
                    )}
                  </div>
                </div>
                <button
                  type="button"
                  className={styles.chipArrow}
                  aria-label="Next suggestion"
                  onClick={() => setPos((value) => value + 1)}
                >
                  <Icon name="chevron-right" className={styles.icon12} />
                </button>
              </div>

              <form
                className={styles.composer}
                data-part="composer"
                onSubmit={(event) => {
                  event.preventDefault();
                  if (!text.trim()) return;
                  onSend?.(text.trim());
                  setText('');
                  showToast({ message: 'Session started', icon: 'mark' });
                }}
              >
                <div className={styles.field}>
                  <textarea
                    ref={composer}
                    className={styles.input}
                    value={text}
                    rows={2}
                    aria-label={`Message ${PRODUCT}`}
                    onChange={(event) => setText(event.target.value)}
                    onKeyDown={(event) => {
                      if (event.key === 'Enter' && !event.shiftKey) {
                        event.preventDefault();
                        event.currentTarget.form?.requestSubmit();
                      }
                    }}
                  />
                  {!text && (
                    <span key={suggestion.id} className={styles.placeholder} aria-hidden="true">
                      {suggestion.placeholder}
                    </span>
                  )}
                  <div className={styles.tools}>
                    <button type="button" className={styles.tool}>
                      <Icon name="clip" className={styles.icon14} />
                      Files
                    </button>
                    <button type="button" className={styles.tool}>
                      <Icon name="plug" className={styles.icon14} />
                      Integrations
                    </button>
                    <button type="button" className={styles.tool}>
                      <RoutineTile className={styles.icon14} />
                      Routines
                    </button>
                    <button
                      type={text.trim() ? 'submit' : 'button'}
                      className={styles.send}
                      aria-label={text.trim() ? 'Send' : 'Dictate'}
                    >
                      <Icon name={text.trim() ? 'arrow-up' : 'mic'} className={styles.icon14} />
                    </button>
                  </div>
                </div>
                <button type="button" className={styles.tray}>
                  <span>Connect your apps for better answers</span>
                  <AppMarks className={styles.apps} />
                </button>
              </form>
            </section>

            <section className={styles.today} aria-labelledby="tc-date">
              <h2 className={styles.date} id="tc-date" data-part="date">
                <span>{DATE_LABEL}</span>
              </h2>
              <div className={styles.grid} data-part="cards">
                {card(
                  'meetings',
                  'Upcoming meetings',
                  standardActions('meetings', 'meeting'),
                  <ul className={styles.rows}>
                    {MEETINGS.slice(0, 3).map((meeting) => (
                      <li key={meeting.id} className={styles.rowItem}>
                        <button
                          type="button"
                          className={styles.row}
                          data-row={meeting.id}
                          data-hover={hovered === meeting.id || undefined}
                          onClick={() =>
                            showToast({ message: `Joining ${meeting.title}`, icon: 'mark' })
                          }
                        >
                          <DayTile day={meeting.day} live className={styles.day} />
                          <span className={styles.rowLabel}>{meeting.title}</span>
                          <span className={styles.rowValue} data-tone="live">
                            {minutes(meeting)}
                          </span>
                          <span className={styles.rowHint} aria-hidden="true">
                            Join meeting <Icon name="arrow-right" className={styles.icon12} />
                          </span>
                        </button>
                      </li>
                    ))}
                  </ul>,
                )}
                {card(
                  'tasks',
                  'Tasks due',
                  standardActions('tasks', 'task'),
                  <ul ref={taskList} className={styles.rows}>
                    {shownTasks.map((task) => {
                      const ticked = done.includes(task.id);
                      return (
                        <li
                          key={task.id}
                          className={styles.rowItem}
                          data-task={task.id}
                          data-row={task.id}
                          data-hover={hovered === task.id || undefined}
                          data-done={ticked || undefined}
                          data-leaving={leaving.includes(task.id) || undefined}
                        >
                          <div className={styles.row} data-kind="task">
                            <button
                              type="button"
                              role="checkbox"
                              aria-checked={ticked}
                              aria-label={`Done: ${task.title}`}
                              className={styles.check}
                              data-part="check"
                              onClick={() => tick(task.id)}
                            >
                              <Icon name="check" className={styles.icon12} />
                            </button>
                            <button
                              type="button"
                              className={styles.taskOpen}
                              onClick={() =>
                                showToast({ message: `Opening “${task.title}”`, icon: 'mark' })
                              }
                            >
                              <span className={styles.rowLabel}>{task.title}</span>
                              <span
                                className={styles.rowValue}
                                data-tone={task.late && !ticked ? 'late' : undefined}
                              >
                                {task.due}
                              </span>
                              <span className={styles.rowHint} aria-hidden="true">
                                Open task <Icon name="arrow-right" className={styles.icon12} />
                              </span>
                            </button>
                          </div>
                        </li>
                      );
                    })}
                    {!shownTasks.length && (
                      <li className={styles.empty}>Nothing due. Enjoy the quiet.</li>
                    )}
                  </ul>,
                )}
                {card(
                  'pipelines',
                  'Top pipelines',
                  standardActions('pipelines', 'pipeline'),
                  <ul className={styles.rows}>
                    {TOP_PIPELINES.map((id) => {
                      const pipeline = PIPELINES.find((item) => item.id === id)!;
                      const [amount, unit] = money(pipeline.value ?? 0);
                      return (
                        <li key={id} className={styles.rowItem}>
                          <button
                            type="button"
                            className={styles.row}
                            data-row={id}
                            data-hover={hovered === id || undefined}
                            onClick={() =>
                              showToast({ message: `Opening ${pipeline.name}`, icon: 'mark' })
                            }
                          >
                            <PipelineTile pipeline={pipeline} className={styles.tile} />
                            <span className={styles.rowLabel}>{pipeline.name}</span>
                            <span className={styles.rowValue}>
                              <b>{pipeline.deals}</b> <small>deals</small>
                              <span className={styles.money}>
                                <small>$</small>
                                <b>{amount}</b>
                                <small>{unit}</small>
                              </span>
                            </span>
                            <span className={styles.rowHint} aria-hidden="true">
                              Open pipeline <Icon name="arrow-right" className={styles.icon12} />
                            </span>
                          </button>
                        </li>
                      );
                    })}
                  </ul>,
                )}
                {card(
                  'performance',
                  'Recent performance',
                  <IconButton
                    tip={`Ask ${PRODUCT}`}
                    part="performance-ask"
                    hover={hovered === 'performance-ask'}
                    expanded={menu?.card === 'performance' && !menuClosing}
                    controls="tc-menu"
                    onClick={(button) => openMenu('performance', 'ask', button)}
                  >
                    <Mark className={styles.icon14} />
                  </IconButton>,
                  <ul className={styles.rows}>
                    {METRICS.map((metric) => (
                      <li key={metric.id} className={styles.rowItem}>
                        <button
                          type="button"
                          className={styles.row}
                          data-row={metric.id}
                          data-hover={hovered === metric.id || undefined}
                          onClick={() =>
                            ask(
                              `Why did my ${metric.label.toLowerCase()} move over the last 30 days?`,
                            )
                          }
                        >
                          <span className={styles.ringTile}>
                            <Ring share={metric.ring} className={styles.ring} />
                          </span>
                          <span className={styles.rowLabel}>{metric.label}</span>
                          <span className={styles.rowValue}>
                            <span className={styles.change} data-good={metric.good || undefined}>
                              {metric.up ? '↑' : '↓'} {metric.change}
                            </span>
                            <b>{metric.value}</b>
                            <small>{metric.unit === '%' ? ' %' : ` ${metric.unit}`}</small>
                          </span>
                          <span className={styles.rowHint} aria-hidden="true">
                            Ask {PRODUCT} AI <Icon name="arrow-right" className={styles.icon12} />
                          </span>
                        </button>
                      </li>
                    ))}
                  </ul>,
                )}
              </div>
            </section>

            <section className={styles.routines} aria-labelledby="tc-routines" data-part="routines">
              <h2 className={styles.routinesTitle} id="tc-routines">
                Meet {PRODUCT} <b>Routines</b>
                <sup>™</sup>
              </h2>
              <p className={styles.routinesText}>
                Routines are saved tasks {PRODUCT} runs on demand, like a morning brief, meeting
                prep, or a pipeline review.
              </p>
              <ul className={styles.routineList}>
                {ROUTINES.map((routine) => (
                  <li key={routine.id} className={styles.routine}>
                    <RoutineTile className={styles.icon16} />
                    <span className={styles.routineText}>
                      <b>{routine.title}</b>
                      <span>{routine.detail}</span>
                    </span>
                    <button
                      type="button"
                      className={styles.routineRun}
                      onClick={() =>
                        showToast({
                          message: `Running ${routine.title.toLowerCase()}`,
                          icon: 'mark',
                        })
                      }
                    >
                      Run
                    </button>
                  </li>
                ))}
              </ul>
            </section>
          </div>
        </main>

        {promo && (
          <aside
            className={styles.promo}
            data-part="promo"
            data-closing={promoClosing || undefined}
            aria-label="New"
          >
            <div className={styles.promoArt} aria-hidden="true">
              <Preview />
            </div>
            <button
              type="button"
              className={styles.promoClose}
              aria-label="Dismiss"
              onClick={closePromo}
            >
              <Icon name="close" className={styles.icon12} />
            </button>
            <div className={styles.promoBody}>
              <span className={styles.promoKicker}>Now available</span>
              <h2 className={styles.promoTitle}>Your day in four cards</h2>
              <ul className={styles.promoList}>
                {(
                  [
                    [
                      'calendar',
                      'Walk into your next meeting',
                      'Your next meetings sit under the composer. A call that is on joins in a click.',
                    ],
                    [
                      'checklist',
                      'Clear what needs you today',
                      'Tasks due, or what waits for your review, sit where you start. Finish one in a click.',
                    ],
                    [
                      'chart',
                      'Ask about any number',
                      'Win rate, closed won and sales cycle cover the last 30 days. Click one to ask.',
                    ],
                  ] as [IconName, string, string][]
                ).map(([icon, title, body]) => (
                  <li key={title}>
                    <Icon name={icon} className={styles.icon14} />
                    <span>
                      <b>{title}</b>
                      {body}
                    </span>
                  </li>
                ))}
              </ul>
              <div className={styles.promoFoot}>
                <button
                  ref={learnMore}
                  type="button"
                  className={styles.learnMore}
                  data-part="learn-more"
                  onClick={() => {
                    setArticleClosing(false);
                    setArticle({ focus: !demoOn.current });
                  }}
                >
                  Learn more
                  <Icon name="external" className={styles.icon12} />
                </button>
                <button
                  type="button"
                  className={styles.tryIt}
                  onClick={() => {
                    closePromo();
                    scroller.current
                      ?.querySelector<HTMLElement>('[data-part="meetings"] [data-row]')
                      ?.focus({ preventScroll: false });
                  }}
                >
                  Try it
                </button>
              </div>
            </div>
          </aside>
        )}

        {palette && (
          <Palette
            key={palette.kind}
            kind={palette.kind}
            closing={paletteClosing}
            hover={demoPick.item}
            openTasks={TASKS.filter((task) => !gone.includes(task.id))}
            autoFocus={palette.focus}
            onClose={closePalette}
            onPick={(_kind, _id, label) => {
              closePalette();
              showToast({ message: `Opening ${label}`, icon: 'mark' });
            }}
          />
        )}
        {menu && (
          <Menu
            id="tc-menu"
            key={`${menu.card}-${menu.kind}`}
            items={menuItems(menu.card, menu.kind)}
            place={menu.place}
            width={menu.width}
            closing={menuClosing}
            hover={demoPick.item}
            subHover={demoPick.sub}
            autoFocus={menu.focus}
            label={menu.kind === 'ask' ? `Ask ${PRODUCT}` : 'More'}
            subSide={menu.subSide}
            onClose={closeMenu}
            onPick={(item, question) => {
              closeMenu(false);
              if (question) ask(question);
              else if (menu.kind === 'ask') ask(item);
              else
                showToast({
                  message: item === 'go' ? 'Opening the page' : 'Opening settings',
                  icon: 'mark',
                });
            }}
          />
        )}
        {menu && (
          <div
            className={styles.menuCatch}
            onPointerDown={() => closeMenu(false)}
            aria-hidden="true"
          />
        )}
        {article && (
          <Article
            closing={articleClosing}
            autoFocus={article.focus}
            sheetRef={sheet}
            onClose={closeArticle}
            preview={<Preview large />}
          />
        )}

        <div className={styles.toasts} role="status" aria-live="polite">
          {oldToast && (
            <ToastView
              key={oldToast.key}
              toast={oldToast}
              leaving
              onClose={() => setOldToast(null)}
            />
          )}
          {toast && (
            <ToastView key={toast.key} toast={toast} leaving={false} onClose={dismissToast} />
          )}
        </div>
      </div>
      <div className={styles.pointerLayer} aria-hidden="true">
        <DemoPointer
          ref={pointer}
          className={styles.pointer}
          arrowClass={styles.arrow}
          handClass={styles.hand}
        />
      </div>
    </div>
  );
}

/** The rainbow's path across the top of the screen, in the sky's 1513 × 260 box. */
const AURORA =
  'M0 120 C 180 108 360 64 560 62 C 700 61 760 98 850 96 C 980 92 1110 112 1290 160 C 1390 186 1460 196 1513 198';
```

### today-cards/overlays.tsx

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

'use client';

import {
  useEffect,
  useId,
  useRef,
  useState,
  type KeyboardEvent,
  type ReactNode,
  type Ref,
} from 'react';
import {
  ARTICLE,
  MEETINGS,
  PIPELINES,
  TASKS,
  type Meeting,
  type Pipeline,
  type Task,
} from './data';
import { DayTile, Icon, Mark, PipelineTile, type IconName } from './icons';
import styles from './today-cards.module.css';

/*
 * What opens over the screen: the Browse palette (meetings, tasks or pipelines, with its search,
 * groups and a footer button that names what Enter does), the cards' menus with their "Ask"
 * submenu, the release note the side card links to, and the toast.
 *
 * Each is drawn at the screen's root and placed by its parent, so no card clips it. Each enters
 * by opacity, a little blur and a little scale, and leaves the same way, faster: its parent keeps
 * it mounted with data-closing for that moment.
 */

export type PaletteKind = 'meetings' | 'tasks' | 'pipelines';

type PaletteItem = {
  id: string;
  label: string;
  group: string;
  /** What the footer button says while this item is active. */
  action: string;
  lead: ReactNode;
  /** The right side: a time, a due label. */
  meta?: ReactNode;
  tone?: 'live' | 'late';
};

const liveLabel = (meeting: Meeting) =>
  meeting.inMinutes === 0
    ? 'Now'
    : meeting.inMinutes !== undefined
      ? `in ${meeting.inMinutes}m`
      : meeting.when;

function paletteItems(kind: PaletteKind, openTasks: Task[]): PaletteItem[] {
  const config: PaletteItem[] =
    kind === 'meetings'
      ? [
          {
            id: 'go',
            label: 'Go to meetings page',
            group: 'More',
            action: 'Open',
            lead: <Icon name="enter" className={styles.paletteIcon} />,
          },
          {
            id: 'configure',
            label: 'Configure meetings',
            group: 'More',
            action: 'Open',
            lead: <Icon name="sliders" className={styles.paletteIcon} />,
          },
        ]
      : kind === 'tasks'
        ? [
            {
              id: 'go',
              label: 'Go to tasks page',
              group: 'More',
              action: 'Open',
              lead: <Icon name="enter" className={styles.paletteIcon} />,
            },
            {
              id: 'configure',
              label: 'Configure tasks',
              group: 'More',
              action: 'Open',
              lead: <Icon name="sliders" className={styles.paletteIcon} />,
            },
          ]
        : [
            {
              id: 'configure',
              label: 'Configure pipelines',
              group: 'Settings',
              action: 'Open',
              lead: <Icon name="sliders" className={styles.paletteIcon} />,
            },
          ];
  if (kind === 'meetings')
    return [
      ...MEETINGS.map((meeting) => ({
        id: meeting.id,
        label: meeting.title,
        group: meeting.group,
        action:
          meeting.inMinutes !== undefined && meeting.inMinutes < 60
            ? 'Join meeting'
            : 'Open meeting',
        lead: (
          <DayTile
            day={meeting.day}
            live={meeting.inMinutes !== undefined}
            className={styles.day}
          />
        ),
        meta: liveLabel(meeting),
        tone: meeting.inMinutes !== undefined ? ('live' as const) : undefined,
      })),
      ...config,
    ];
  if (kind === 'tasks')
    return [
      ...openTasks.map((task) => ({
        id: task.id,
        label: task.title,
        group: task.late ? 'Overdue' : 'This week',
        action: 'Open task',
        lead: <span className={styles.paletteBox} aria-hidden="true" />,
        meta: task.due,
        tone: task.late ? ('late' as const) : undefined,
      })),
      ...config,
    ];
  return [
    ...PIPELINES.map((pipeline: Pipeline) => ({
      id: pipeline.id,
      label: pipeline.name,
      group: 'Pipelines',
      action: 'Open pipeline',
      lead: <PipelineTile pipeline={pipeline} className={styles.tile} />,
    })),
    ...config,
  ];
}

const PALETTE_TITLE: Record<PaletteKind, string> = {
  meetings: 'Browse meetings',
  tasks: 'Browse tasks',
  pipelines: 'Browse pipelines',
};

/**
 * The Browse palette: a search field over grouped results. The arrow keys (or the pointer) pick
 * the active item, Enter runs it, the footer button names what that does, Escape or the back
 * arrow closes it.
 */
export function Palette({
  kind,
  closing,
  hover,
  openTasks = TASKS,
  autoFocus,
  onClose,
  onPick,
}: {
  kind: PaletteKind;
  closing: boolean;
  /** An item the demo points at. */
  hover?: string | null;
  openTasks?: Task[];
  autoFocus: boolean;
  onClose: () => void;
  onPick: (kind: PaletteKind, id: string, label: string) => void;
}) {
  const [query, setQuery] = useState('');
  const all = paletteItems(kind, openTasks);
  const words = query.trim().toLowerCase();
  const items = words ? all.filter((item) => item.label.toLowerCase().includes(words)) : all;
  const [active, setActive] = useState<string | null>(null);
  const shown = hover ?? active;
  const current = items.find((item) => item.id === shown) ?? null;
  const input = useRef<HTMLInputElement>(null);
  const listId = useId();

  useEffect(() => {
    if (autoFocus) input.current?.focus({ preventScroll: true });
  }, [autoFocus]);

  const move = (step: number) => {
    if (!items.length) return;
    const index = items.findIndex((item) => item.id === shown);
    const next =
      index < 0 ? (step > 0 ? 0 : items.length - 1) : (index + step + items.length) % items.length;
    setActive(items[next].id);
    document.getElementById(`${listId}-${items[next].id}`)?.scrollIntoView({ block: 'nearest' });
  };
  const onKeyDown = (event: KeyboardEvent) => {
    if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
      event.preventDefault();
      move(event.key === 'ArrowDown' ? 1 : -1);
    } else if (event.key === 'Enter') {
      event.preventDefault();
      const item = current ?? items[0];
      if (item) onPick(kind, item.id, item.label);
    } else if (event.key === 'Escape') {
      event.preventDefault();
      onClose();
    }
  };

  const groups = [...new Set(items.map((item) => item.group))];
  return (
    <>
      <div
        className={styles.scrim}
        data-closing={closing || undefined}
        onClick={onClose}
        aria-hidden="true"
      />
      <div
        className={styles.palette}
        role="dialog"
        aria-modal="true"
        aria-label={PALETTE_TITLE[kind]}
        data-part="palette"
        data-closing={closing || undefined}
        onKeyDown={onKeyDown}
      >
        <div className={styles.paletteHead}>
          <button type="button" className={styles.paletteBack} onClick={onClose} aria-label="Close">
            <Icon name="chevron-left" className={styles.icon14} />
          </button>
          <span className={styles.paletteScope}>
            <Icon name="browse" className={styles.icon14} />
            {PALETTE_TITLE[kind]}
          </span>
          <input
            ref={input}
            className={styles.paletteInput}
            value={query}
            onChange={(event) => {
              setQuery(event.target.value);
              setActive(null);
            }}
            placeholder={`Search ${kind}...`}
            role="combobox"
            aria-expanded="true"
            aria-controls={listId}
            aria-activedescendant={current ? `${listId}-${current.id}` : undefined}
            aria-label={`Search ${kind}`}
          />
        </div>
        <div
          className={styles.paletteList}
          id={listId}
          role="listbox"
          aria-label={PALETTE_TITLE[kind]}
        >
          {groups.map((group) => (
            <div
              key={group}
              role="group"
              aria-labelledby={`${listId}-g-${group.replace(/\s/g, '')}`}
            >
              <div className={styles.paletteGroup} id={`${listId}-g-${group.replace(/\s/g, '')}`}>
                {group}
              </div>
              {items
                .filter((item) => item.group === group)
                .map((item) => (
                  <div
                    key={item.id}
                    id={`${listId}-${item.id}`}
                    role="option"
                    aria-selected={item.id === current?.id}
                    className={styles.paletteItem}
                    data-item={item.id}
                    data-active={item.id === current?.id || undefined}
                    onPointerMove={() => setActive(item.id)}
                    onClick={() => onPick(kind, item.id, item.label)}
                  >
                    {item.lead}
                    <span className={styles.paletteLabel}>{item.label}</span>
                    {item.meta && (
                      <span className={styles.paletteMeta} data-tone={item.tone}>
                        {item.meta}
                      </span>
                    )}
                    <span className={styles.paletteHint} aria-hidden="true">
                      {item.action}
                      <Icon name="arrow-right" className={styles.icon12} />
                    </span>
                  </div>
                ))}
            </div>
          ))}
          {!items.length && (
            <div className={styles.paletteEmpty}>
              No {kind} match “{query}”.
            </div>
          )}
        </div>
        <div className={styles.paletteFoot}>
          <button
            type="button"
            className={styles.paletteAction}
            data-part="palette-action"
            disabled={!items.length}
            onClick={() => {
              const item = current ?? items[0];
              if (item) onPick(kind, item.id, item.label);
            }}
          >
            {(current ?? items[0])?.action ?? 'Open'}
          </button>
        </div>
      </div>
    </>
  );
}

export type MenuItem = {
  id: string;
  label: string;
  icon?: IconName | 'mark';
  /** Items of a submenu, opened to the side. */
  sub?: string[];
};

/**
 * A card's menu: role="menu" with arrow keys, Enter, and Escape back to its button. An item
 * with `sub` opens its submenu on hover, ArrowRight or Enter; a question in either list ends
 * with an "Ask Sevenfold AI" hint on hover.
 */
export function Menu({
  id,
  items,
  place,
  width,
  closing,
  hover,
  subHover,
  autoFocus,
  label,
  subSide = 'right',
  onPick,
  onClose,
  menuRef,
}: {
  id: string;
  items: MenuItem[];
  place: { top: number; left: number };
  width?: number;
  closing: boolean;
  /** The item and submenu item the demo points at. */
  hover?: string | null;
  subHover?: string | null;
  autoFocus: boolean;
  label: string;
  /** Where the submenu opens: beside the menu, or under its item when neither side has room. */
  subSide?: 'right' | 'left' | 'below';
  onPick: (item: string, question?: string) => void;
  onClose: (refocus: boolean) => void;
  menuRef?: Ref<HTMLDivElement>;
}) {
  const [active, setActive] = useState<string | null>(null);
  const [subOpen, setSubOpen] = useState<string | null>(null);
  const [subActive, setSubActive] = useState<string | null>(null);
  const list = useRef<HTMLDivElement>(null);
  const sub = useRef<HTMLDivElement>(null);
  const shownActive = hover ?? active;
  const shownSub = hover && items.find((item) => item.id === hover)?.sub ? hover : subOpen;
  const subItems = items.find((item) => item.id === shownSub)?.sub;
  const shownSubActive = subHover ?? subActive;

  useEffect(() => {
    if (!autoFocus) return;
    list.current?.querySelector<HTMLElement>('[role="menuitem"]')?.focus({ preventScroll: true });
  }, [autoFocus]);

  const focusIn = (container: HTMLDivElement | null, step: number) => {
    const all = [...(container?.querySelectorAll<HTMLElement>('[role="menuitem"]') ?? [])];
    const index = all.indexOf(document.activeElement as HTMLElement);
    all[(index + step + all.length) % all.length]?.focus();
  };

  const onKeyDown = (event: KeyboardEvent, inSub: boolean) => {
    const container = inSub ? sub.current : list.current;
    if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
      event.preventDefault();
      focusIn(container, event.key === 'ArrowDown' ? 1 : -1);
    } else if (event.key === 'Escape' || (inSub && event.key === 'ArrowLeft')) {
      event.preventDefault();
      event.stopPropagation();
      if (inSub) {
        const opener = subOpen;
        setSubOpen(null);
        list.current?.querySelector<HTMLElement>(`[data-item="${opener}"]`)?.focus();
      } else onClose(true);
    } else if (event.key === 'Tab') {
      onClose(false);
    }
  };

  const openSub = (item: MenuItem, focus: boolean) => {
    setSubOpen(item.id);
    setSubActive(null);
    if (focus)
      requestAnimationFrame(() =>
        sub.current
          ?.querySelector<HTMLElement>('[role="menuitem"]')
          ?.focus({ preventScroll: true }),
      );
  };

  const subTop = items.findIndex((item) => item.id === shownSub);
  return (
    <div
      ref={menuRef}
      className={styles.menuLayer}
      data-part="menu-layer"
      style={{ top: place.top, left: place.left }}
      data-closing={closing || undefined}
    >
      <div
        ref={list}
        id={id}
        className={styles.menu}
        style={width ? { width } : undefined}
        role="menu"
        aria-label={label}
        data-part="menu"
        onKeyDown={(event) => onKeyDown(event, false)}
      >
        {items.map((item) => (
          <button
            key={item.id}
            type="button"
            role="menuitem"
            tabIndex={-1}
            className={styles.menuItem}
            data-item={item.id}
            data-active={item.id === shownActive || item.id === shownSub || undefined}
            aria-haspopup={item.sub ? 'menu' : undefined}
            aria-expanded={item.sub ? item.id === shownSub : undefined}
            onPointerEnter={() => {
              setActive(item.id);
              if (item.sub) openSub(item, false);
              else setSubOpen(null);
            }}
            onPointerLeave={() => setActive(null)}
            onKeyDown={(event) => {
              if (
                item.sub &&
                (event.key === 'ArrowRight' || event.key === 'Enter' || event.key === ' ')
              ) {
                event.preventDefault();
                openSub(item, true);
              }
            }}
            onClick={() => (item.sub ? openSub(item, false) : onPick(item.id))}
          >
            {item.icon === 'mark' ? (
              <Mark className={styles.menuMark} />
            ) : item.icon ? (
              <Icon name={item.icon} className={styles.menuIcon} />
            ) : null}
            <span className={styles.menuLabel}>{item.label}</span>
            {item.sub ? (
              <Icon name="caret-right" className={styles.menuCaret} />
            ) : item.icon === 'mark' ? (
              <span className={styles.menuHint} aria-hidden="true">
                Ask Sevenfold AI <Icon name="arrow-right" className={styles.icon12} />
              </span>
            ) : null}
          </button>
        ))}
      </div>
      {subItems && (
        <div
          ref={sub}
          className={styles.submenu}
          style={{ top: subSide === 'below' ? subTop * 33 + 40 : subTop * 33 - 33 }}
          data-side={subSide}
          role="menu"
          aria-label={items[subTop]?.label}
          data-part="submenu"
          onKeyDown={(event) => onKeyDown(event, true)}
        >
          {subItems.map((question) => (
            <button
              key={question}
              type="button"
              role="menuitem"
              tabIndex={-1}
              className={styles.menuItem}
              data-item={question}
              data-active={question === shownSubActive || undefined}
              onPointerEnter={() => setSubActive(question)}
              onPointerLeave={() => setSubActive(null)}
              onClick={() => onPick(shownSub ?? '', question)}
            >
              <Mark className={styles.menuMark} />
              <span className={styles.menuLabel}>{question}</span>
              <span className={styles.menuHint} aria-hidden="true">
                Ask Sevenfold AI <Icon name="arrow-right" className={styles.icon12} />
              </span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

/** The release note: a tall sheet over a dimmed screen, scrolling on its own. */
export function Article({
  closing,
  autoFocus,
  sheetRef,
  onClose,
  preview,
}: {
  closing: boolean;
  autoFocus: boolean;
  sheetRef: Ref<HTMLDivElement>;
  onClose: () => void;
  /** The illustration: the cards, close up. */
  preview: ReactNode;
}) {
  const close = useRef<HTMLButtonElement>(null);
  const titleId = useId();
  useEffect(() => {
    if (autoFocus) close.current?.focus({ preventScroll: true });
  }, [autoFocus]);
  return (
    <>
      <div
        className={styles.scrim}
        data-closing={closing || undefined}
        onClick={onClose}
        aria-hidden="true"
      />
      <div
        ref={sheetRef}
        className={styles.article}
        role="dialog"
        aria-modal="true"
        aria-labelledby={titleId}
        data-part="article"
        data-closing={closing || undefined}
        onKeyDown={(event) => {
          if (event.key === 'Escape') {
            event.preventDefault();
            onClose();
          }
        }}
      >
        <button
          ref={close}
          type="button"
          className={styles.articleClose}
          onClick={onClose}
          aria-label="Close"
        >
          <Icon name="close" className={styles.icon14} />
        </button>
        <article className={styles.articleBody}>
          <span className={styles.articleKicker}>{ARTICLE.kicker}</span>
          <h2 className={styles.articleTitle} id={titleId}>
            {ARTICLE.title}
          </h2>
          <p className={styles.articleLede}>{ARTICLE.lede}</p>
          <div className={styles.articleByline}>
            <span className={styles.authors} aria-label="By the Sevenfold team">
              <span data-face="1" />
              <span data-face="2" />
            </span>
            <time dateTime="2026-10-13">{ARTICLE.date}</time>
          </div>
          <p className={styles.articleText}>{ARTICLE.intro}</p>
          <figure className={styles.articleFigure} aria-hidden="true">
            {preview}
          </figure>
          {ARTICLE.sections.map((section) => (
            <section key={section.title}>
              <h3 className={styles.articleHeading}>{section.title}</h3>
              <p className={styles.articleText}>{section.body}</p>
              {'points' in section && section.points && (
                <ul className={styles.articlePoints}>
                  {section.points.map((point) => (
                    <li key={point}>{point}</li>
                  ))}
                </ul>
              )}
            </section>
          ))}
        </article>
      </div>
    </>
  );
}

export type Toast = { key: number; message: string; undo?: () => void; icon?: 'check' | 'mark' };

/** The toast in the corner: a tick, its message, Undo when it can, and a close button. */
export function ToastView({
  toast,
  leaving,
  onClose,
}: {
  toast: Toast;
  leaving: boolean;
  onClose: () => void;
}) {
  return (
    <div className={styles.toast} data-leaving={leaving || undefined} data-part="toast">
      <span className={styles.toastTick} aria-hidden="true">
        {toast.icon === 'mark' ? (
          <Mark className={styles.icon14} />
        ) : (
          <Icon name="check" className={styles.icon14} />
        )}
      </span>
      <span className={styles.toastText}>{toast.message}</span>
      {toast.undo && (
        <button
          type="button"
          className={styles.toastUndo}
          onClick={toast.undo}
          tabIndex={leaving ? -1 : 0}
        >
          Undo
        </button>
      )}
      <button
        type="button"
        className={styles.toastClose}
        onClick={onClose}
        aria-label="Dismiss"
        tabIndex={leaving ? -1 : 0}
      >
        <Icon name="close" className={styles.icon12} />
      </button>
    </div>
  );
}
```

### today-cards/data.ts

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

/*
 * The invented workspace the screen shows: an assistant called Sevenfold, a sales team's day on
 * Tuesday 13 October, its meetings, tasks, pipelines and numbers, the composer's suggestions and
 * the release note the side card links to. Every company and person here is made up.
 */

export const PRODUCT = 'Sevenfold';
export const WORKSPACE = 'Hollowell Demo';
export const DATE_LABEL = 'Tue, Oct 13';

export type PipelineIcon = 'case' | 'branch' | 'swap' | 'people' | 'stack';

/** A pipeline's tile: a gradient square with a white glyph. */
export type Pipeline = {
  id: string;
  name: string;
  /** Two colours, top left to bottom right. */
  tint: [string, string];
  icon: PipelineIcon;
  deals?: number;
  /** In dollars. */
  value?: number;
};

export const PIPELINES: Pipeline[] = [
  {
    id: 'sales',
    name: 'Sales',
    tint: ['#f6c343', '#ee5a3b'],
    icon: 'case',
    deals: 31,
    value: 1.8e6,
  },
  {
    id: 'partnerships',
    name: 'Partnerships',
    tint: ['#f55bb7', '#b42fe0'],
    icon: 'branch',
    deals: 9,
    value: 385e3,
  },
  {
    id: 'renewals',
    name: 'Renewals',
    tint: ['#5ef08a', '#18a9a0'],
    icon: 'swap',
    deals: 31,
    value: 960e3,
  },
  { id: 'enterprise', name: 'Enterprise deals', tint: ['#56e3f0', '#2a8de8'], icon: 'case' },
  { id: 'expansion', name: 'Expansion revenue', tint: ['#c4f04a', '#2cb54a'], icon: 'case' },
  { id: 'mid-market', name: 'Mid-market', tint: ['#8fa8ff', '#6a4cf0'], icon: 'people' },
  { id: 'agency', name: 'Agency partners', tint: ['#c08bff', '#5b4de8'], icon: 'branch' },
  { id: 'recruiting', name: 'Recruiting', tint: ['#ff7ad9', '#5a5cf0'], icon: 'stack' },
];

/** The three the Top pipelines card shows: the busiest. */
export const TOP_PIPELINES = ['sales', 'renewals', 'partnerships'];

export type Meeting = {
  id: string;
  title: string;
  /** Day of the month on its calendar tile. */
  day: number;
  /** Minutes from now, for today's; a time label for later ones. */
  inMinutes?: number;
  when?: string;
  group: 'Today' | 'Tomorrow' | 'This week';
};

export const MEETINGS: Meeting[] = [
  { id: 'm1', title: 'Brightwater security review', day: 13, inMinutes: 0, group: 'Today' },
  { id: 'm2', title: 'Kestrel check-in', day: 13, inMinutes: 3, group: 'Today' },
  { id: 'm3', title: 'Oakmere pricing prep', day: 13, inMinutes: 24, group: 'Today' },
  { id: 'm4', title: 'Oakmere pricing follow-up', day: 14, when: '9:00 AM', group: 'Tomorrow' },
  { id: 'm5', title: 'Tidewell renewal sync', day: 19, when: 'Mon 9:00 AM', group: 'This week' },
];

export type Task = {
  id: string;
  title: string;
  /** What the due label says, and whether it is late. */
  due: string;
  late?: boolean;
};

/** Tasks due, soonest first; the card shows the first three still open. */
export const TASKS: Task[] = [
  { id: 't1', title: 'Send Brightwater the revised pricing', due: '3d late', late: true },
  {
    id: 't2',
    title: 'Follow up with Larchfield on the security review',
    due: '1d late',
    late: true,
  },
  { id: 't3', title: 'Prep the renewal deck for Quillon', due: 'Today' },
  { id: 't4', title: 'Draft the Pemberly renewal note', due: 'Tomorrow' },
  { id: 't5', title: 'Reset the Brightwater close date', due: 'Thu' },
];

export type Metric = {
  id: string;
  label: string;
  /** The change against the 30 days before, and whether it went the way you want. */
  change: string;
  good: boolean;
  up: boolean;
  value: string;
  unit: string;
  /** How full its ring is, 0 to 1. */
  ring: number;
};

export const METRICS: Metric[] = [
  {
    id: 'win',
    label: 'Win rate',
    change: '8 pts',
    good: true,
    up: true,
    value: '63.6',
    unit: '%',
    ring: 0.64,
  },
  {
    id: 'won',
    label: 'Deals won',
    change: '2',
    good: true,
    up: true,
    value: '7',
    unit: 'deals',
    ring: 0.7,
  },
  {
    id: 'cycle',
    label: 'Avg sales cycle',
    change: '2.8 days',
    good: true,
    up: false,
    value: '28',
    unit: 'days',
    ring: 0.35,
  },
];

/** The questions the Recent performance card offers. */
export const PERFORMANCE_QUESTIONS = [
  'Analyze my performance over the last 30 days',
  'Compare deals won and lost',
  'Break down closed won revenue by owner',
  'Show deals closed per week',
  'Compare the last 30 days to the previous 30',
  'Show average sales cycle by stage',
];

/** What each card's "Ask" submenu offers. */
export const ASK = {
  meetings: ['Prepare my next meeting', 'What meetings are today?', 'Recap my last meeting'],
  tasks: ['What should I do first?', 'Which tasks are overdue?', 'Plan my afternoon'],
  pipelines: ['Summarize my pipelines', 'Which deals are at risk?', 'What changed in my deals?'],
};

export type ChipIcon = 'apps' | 'csv' | 'deal' | 'follow' | 'recap' | 'changed' | 'routine';

/**
 * The composer's suggestions. Picking one changes the greeting (its words, with the name at the
 * start or the end) and the composer's placeholder.
 */
export type Suggestion = {
  id: string;
  label: string;
  icon: ChipIcon;
  /** The greeting around the name: [before, after]. */
  greeting: [string, string];
  placeholder: string;
};

export const SUGGESTIONS: Suggestion[] = [
  {
    id: 'apps',
    label: 'Connect your apps',
    icon: 'apps',
    greeting: ['Connect your apps,', ''],
    placeholder: 'Which apps can I connect for better answers?',
  },
  {
    id: 'csv',
    label: 'Import a CSV',
    icon: 'csv',
    greeting: ['Hand me a spreadsheet,', ''],
    placeholder: 'Import the deals in this CSV',
  },
  {
    id: 'deal',
    label: 'Ask about a deal',
    icon: 'deal',
    greeting: ['Ask me about a deal,', ''],
    placeholder: 'Catch me up on my most active deals',
  },
  {
    id: 'follow',
    label: 'Draft a follow-up',
    icon: 'follow',
    greeting: ['', ', I can draft follow-ups'],
    placeholder: 'Draft a follow-up for my last meeting',
  },
  {
    id: 'recap',
    label: 'Recap a meeting',
    icon: 'recap',
    greeting: ['', ', want a recap?'],
    placeholder: 'Recap my last meeting',
  },
  {
    id: 'changed',
    label: 'What changed',
    icon: 'changed',
    greeting: ['Ask me what changed,', ''],
    placeholder: 'What changed across my pipeline this week?',
  },
  {
    id: 'routine',
    label: 'Run a routine',
    icon: 'routine',
    greeting: ['I can run your routines,', ''],
    placeholder: 'Run my weekly pipeline review',
  },
];

export const SESSIONS = [
  'Review: reading surface',
  'Loading states',
  'CSV import, streaming',
  'Weekly pipeline review with Dana',
  'Brightwater renewal, pricing',
];

export const ROUTINES = [
  { id: 'brief', title: 'Morning brief', detail: 'Weekdays at 8:30 AM', runs: 'Ran today' },
  { id: 'prep', title: 'Meeting prep', detail: 'An hour before each call', runs: 'Next at 11:00' },
  { id: 'review', title: 'Pipeline review', detail: 'Mondays at 9:00 AM', runs: 'Ran Monday' },
];

/** The release note the side card's "Learn more" opens. */
export const ARTICLE = {
  kicker: 'Update',
  title: 'Your day in four cards',
  lede: 'A new session now opens on your day: four cards with your next meetings, what is waiting on you, your busiest pipelines and how your numbers moved.',
  date: 'October 13, 2026',
  intro:
    'Until now, a new session began with an empty box and a question you had to bring yourself. Your calendar, your tasks and your figures each lived on a page of their own. This release puts them where you already start: under the composer, one card per question you tend to ask first, and every line in them is a click away from the thing it names.',
  sections: [
    {
      title: 'Walk into your next meeting',
      body: 'The first card holds your next three meetings, soonest first. Inside the hour a meeting counts down in minutes; once it has started it says Now, with an orange dot. While a call is on, or about to be, its row joins it in a new tab, and otherwise the row opens the meeting with its notes and recording. The plus on the card books another without leaving the page.',
    },
    {
      title: 'Clear what needs you today',
      body: 'The second card is what waits on you: the three tasks due soonest, each with a box to tick. A ticked task lingers for a moment, crossed out, with an Undo in the corner, then makes room for the next one.',
      points: [
        'Late tasks say how late, in red.',
        'Click a task to open it, or the plus to add one.',
        'Browse lists every task due this week.',
      ],
    },
    {
      title: 'Ask about any number',
      body: 'The last card reads your win rate, the deals you won and your average sales cycle over the last 30 days, each beside how far it moved against the 30 days before: green when it went your way, red when it did not. Every figure is also a question. Pick one and Sevenfold starts a session that explains the move from your own deals.',
    },
    {
      title: 'Open the pipeline that matters',
      body: 'Your three busiest pipelines sit beside the numbers, with their open deals and what they are worth. A row opens that pipeline; Browse finds any other. The card’s menu carries a few questions of its own, such as which deals are at risk, and each one opens a session already asked.',
    },
    {
      title: 'What comes next',
      body: 'There is nothing to switch on. The cards are on every new session today, filled from the meetings, tasks and deals already in your workspace, on every plan.',
    },
  ],
};

/** "$1.8M", "$960K". */
export function money(value: number): [string, string] {
  if (value >= 1e6) return [(value / 1e6).toFixed(1).replace(/\.0$/, ''), 'M'];
  return [String(Math.round(value / 1e3)), 'K'];
}
```

### today-cards/icons.tsx

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

import type { CSSProperties } from 'react';
import type { ChipIcon, Pipeline, PipelineIcon } from './data';

/*
 * The screen's drawings: line icons on a 16-unit grid in currentColor (their size comes from the
 * stylesheet), the assistant's seven-sided mark, the pipelines' gradient tiles, the calendar
 * tile, the progress ring and the invented app marks of the composer's tray. All decorative:
 * whatever they stand for is said in text beside them.
 */

export type IconName =
  | 'plus'
  | 'search'
  | 'panel'
  | 'review'
  | 'book'
  | 'gear'
  | 'chevron-down'
  | 'chevron-left'
  | 'chevron-right'
  | 'caret-down'
  | 'caret-right'
  | 'pin'
  | 'more'
  | 'clock'
  | 'clip'
  | 'plug'
  | 'mic'
  | 'arrow-up'
  | 'arrow-right'
  | 'browse'
  | 'enter'
  | 'sliders'
  | 'check'
  | 'close'
  | 'external'
  | 'calendar'
  | 'checklist'
  | 'chart'
  | 'menu'
  | 'dot'
  | ChipIcon;

const PATHS: Record<IconName, string> = {
  plus: 'M8 3.2v9.6M3.2 8h9.6',
  search: 'M7.2 12a4.8 4.8 0 1 0 0-9.6 4.8 4.8 0 0 0 0 9.6ZM10.6 10.6l2.9 2.9',
  panel:
    'M3.4 3h9.2c.8 0 1.4.6 1.4 1.4v7.2c0 .8-.6 1.4-1.4 1.4H3.4c-.8 0-1.4-.6-1.4-1.4V4.4C2 3.6 2.6 3 3.4 3ZM6.2 3v10',
  review: 'M3 3.5v9M5.6 5h7.6M5.6 8.2h5M5.6 11.4h3.2M11 10l2 1.6-2 1.6',
  book: 'M4 2.8h7.4c.6 0 1 .4 1 1v8.6H5c-.6 0-1 .4-1 1M4 2.8v10.6c0 .6.4 1 1 1h7.4M6.4 6h3.6',
  gear: 'M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4ZM8 1.8v1.6M8 12.6v1.6M14.2 8h-1.6M3.4 8H1.8M12.4 3.6l-1.2 1.2M4.8 11.2l-1.2 1.2M12.4 12.4l-1.2-1.2M4.8 4.8 3.6 3.6',
  'chevron-down': 'm4.5 6.5 3.5 3.5 3.5-3.5',
  'chevron-left': 'M9.8 4.2 6 8l3.8 3.8',
  'chevron-right': 'M6.2 4.2 10 8l-3.8 3.8',
  'caret-down': 'M5 6.5h6L8 10Z',
  'caret-right': 'M6.5 5v6L10 8Z',
  pin: 'M9.6 2.6 13.4 6.4 11 7.6 9 9.6l.4 2.6-1 1-2.2-2.2L3 14.2M6.2 11 5 9.8l-1-1 1-1 2.6.4 2-2 1.2-2.4',
  more: 'M3.6 8h.01M8 8h.01M12.4 8h.01',
  clock: 'M8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12ZM8 4.8V8l2.2 1.4',
  clip: 'm12.8 7.6-4.9 4.9a3 3 0 0 1-4.3-4.3l5.2-5.2a2 2 0 0 1 2.9 2.9L6.6 11a1 1 0 0 1-1.5-1.4l4.6-4.6',
  plug: 'M5.6 2.4v2.8M10.4 2.4v2.8M3.8 5.2h8.4v1.6a4.2 4.2 0 0 1-8.4 0Zm4.2 5.8v2.6',
  mic: 'M8 2.2a1.8 1.8 0 0 0-1.8 1.8v3.6a1.8 1.8 0 0 0 3.6 0V4A1.8 1.8 0 0 0 8 2.2ZM4.4 7.4a3.6 3.6 0 0 0 7.2 0M8 11v2.6',
  'arrow-up': 'M8 13V3.4M4 7.2 8 3.2l4 4',
  'arrow-right': 'M3 8h9.6M8.8 4.2 12.6 8l-3.8 3.8',
  browse:
    'M2.6 4h10.8M2.6 7.4h4.2M2.6 10.8h3.2M10.6 12a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4ZM12.2 11.6l1.4 1.4',
  enter: 'M9.4 2.8h2.4c.6 0 1.2.6 1.2 1.2v8c0 .6-.6 1.2-1.2 1.2H9.4M2.8 8h7M7.2 5.4 9.8 8l-2.6 2.6',
  sliders:
    'M2.8 5h6M11.6 5h1.6M2.8 11h1.6M7.2 11h6M10.2 6.6a1.6 1.6 0 1 0 0-3.2 1.6 1.6 0 0 0 0 3.2ZM5.8 12.6a1.6 1.6 0 1 0 0-3.2 1.6 1.6 0 0 0 0 3.2Z',
  check: 'm3.4 8.4 3 3 6.2-6.6',
  close: 'm4.4 4.4 7.2 7.2M11.6 4.4l-7.2 7.2',
  external:
    'M9.4 2.8h3.8v3.8M13.2 2.8 7.6 8.4M11.8 9.2v3c0 .6-.4 1-1 1H3.8c-.6 0-1-.4-1-1V5.2c0-.6.4-1 1-1h3',
  calendar:
    'M3.6 3.6h8.8c.6 0 1 .4 1 1v8c0 .6-.4 1-1 1H3.6c-.6 0-1-.4-1-1v-8c0-.6.4-1 1-1ZM2.6 6.6h10.8M5.6 2.2v2.4M10.4 2.2v2.4M10 9.4v3.4l1.4-1 1.4 1',
  checklist: 'm2.6 4.4 1.2 1.2 2-2.2M2.6 10.4l1.2 1.2 2-2.2M8 4.6h5.4M8 10.6h5.4',
  chart: 'M2.6 2.6v10.8h10.8M4.8 10.4l2.6-3 2.2 1.8 3.2-4.2M10.6 5h2.2v2.2',
  menu: 'M2.8 4.4h10.4M2.8 8h10.4M2.8 11.6h10.4',
  dot: 'M8 9.2a1.2 1.2 0 1 0 0-2.4 1.2 1.2 0 0 0 0 2.4Z',
  apps: 'M4.6 6.4a1.8 1.8 0 1 0 0-3.6 1.8 1.8 0 0 0 0 3.6ZM11.4 6.4a1.8 1.8 0 1 0 0-3.6 1.8 1.8 0 0 0 0 3.6ZM4.6 13.2a1.8 1.8 0 1 0 0-3.6 1.8 1.8 0 0 0 0 3.6ZM9.6 11.4h3.6M11.4 9.6v3.6',
  csv: 'M9.6 2.4H4.8c-.6 0-1 .4-1 1v9.2c0 .6.4 1 1 1h6.4c.6 0 1-.4 1-1V5ZM9.6 2.4V5h2.6M6.4 9.6h3.2M8 8v3.2',
  deal: 'M3.6 5.2h8.8c.6 0 1 .4 1 1v6c0 .6-.4 1-1 1H3.6c-.6 0-1-.4-1-1v-6c0-.6.4-1 1-1ZM6 5.2V4c0-.6.4-1 1-1h2c.6 0 1 .4 1 1v1.2M2.8 8h10.4M8 9.6v.01',
  follow:
    'M12.6 7V4.4c0-.6-.4-1-1-1H4.4c-.6 0-1 .4-1 1v6.2c0 .6.4 1 1 1h2.8M3.6 4.4 8 7.6l4.4-3.2M9.4 13.2l.4-1.6 3-3a1 1 0 0 1 1.4 1.4l-3 3Z',
  recap:
    'M12.4 7V4.6c0-.6-.4-1-1-1H3.6c-.6 0-1 .4-1 1v7.8c0 .6.4 1 1 1h3.6M2.6 6.6h9.8M5.6 2.2v2.4M9.6 2.2v2.4M10.8 9.2v1.8l1.2.8M11.2 14a3 3 0 1 0 0-6',
  changed: 'M3.2 8a4.8 4.8 0 1 0 1.6-3.6M3.2 3v2.4h2.4M8 5.6V8l1.6 1.2',
  routine: 'M9.8 3.2 6.2 12.8',
};

const BOX: CSSProperties = {
  fill: 'none',
  stroke: 'currentColor',
  strokeWidth: 1.25,
  strokeLinecap: 'round',
  strokeLinejoin: 'round',
};

/** A line icon in currentColor; its size is its class's. */
export function Icon({ name, className }: { name: IconName; className?: string }) {
  const filled = name === 'caret-down' || name === 'caret-right';
  return (
    <svg viewBox="0 0 16 16" className={className} aria-hidden="true">
      <path
        d={PATHS[name]}
        style={
          filled
            ? { fill: 'currentColor', stroke: 'none' }
            : name === 'more'
              ? { ...BOX, strokeWidth: 2 }
              : BOX
        }
      />
    </svg>
  );
}

/** The heptagon's corners, for a ring of radius `r` around (cx, cy), first corner up. */
function heptagon(cx: number, cy: number, r: number, turn = 0) {
  return Array.from({ length: 7 }, (_, i) => {
    const angle = ((i / 7) * 360 - 90 + turn) * (Math.PI / 180);
    return `${(cx + r * Math.cos(angle)).toFixed(2)},${(cy + r * Math.sin(angle)).toFixed(2)}`;
  }).join(' ');
}

/** Sevenfold's mark: a thick seven-sided ring with softened corners. */
export function Mark({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 64 64" className={className} aria-hidden="true">
      <polygon
        points={heptagon(32, 32, 23.5, 8)}
        fill="none"
        stroke="currentColor"
        strokeWidth="9"
        strokeLinejoin="round"
      />
    </svg>
  );
}

/** The faint nested heptagons behind the greeting. */
export function Rings({ className }: { className?: string }) {
  return (
    <svg viewBox="-600 -400 1200 800" className={className} aria-hidden="true">
      {[110, 230, 380, 560].map((r, i) => (
        <polygon
          key={r}
          points={heptagon(0, 0, r, 8 + i * 4)}
          fill="none"
          stroke="currentColor"
          strokeWidth="1"
          strokeDasharray={i % 2 ? '2 4' : undefined}
          vectorEffect="non-scaling-stroke"
        />
      ))}
    </svg>
  );
}

const GLYPHS: Record<PipelineIcon, string> = {
  case: 'M5 6.2h6c.5 0 .8.3.8.8v3.4c0 .5-.3.8-.8.8H5c-.5 0-.8-.3-.8-.8V7c0-.5.3-.8.8-.8ZM6.6 6.2v-.8c0-.3.2-.5.5-.5h1.8c.3 0 .5.2.5.5v.8M6.6 8.6h2.8',
  branch:
    'M8 6.4a1.3 1.3 0 1 0 0-2.6 1.3 1.3 0 0 0 0 2.6ZM5.2 12.2a1.3 1.3 0 1 0 0-2.6 1.3 1.3 0 0 0 0 2.6ZM10.8 12.2a1.3 1.3 0 1 0 0-2.6 1.3 1.3 0 0 0 0 2.6ZM8 6.4v1.4M5.2 9.6l2.8-1.8 2.8 1.8',
  swap: 'M4.4 6.6h7M9.4 4.6l2 2-2 2M11.6 9.4h-7M6.6 7.4l-2 2 2 2',
  people: 'M8 7.6a1.6 1.6 0 1 0 0-3.2 1.6 1.6 0 0 0 0 3.2ZM5 11.6c.4-1.6 1.6-2.4 3-2.4s2.6.8 3 2.4',
  stack: 'M8 4.2 11.8 6 8 7.8 4.2 6ZM4.2 8.2 8 10l3.8-1.8M4.2 10.2 8 12l3.8-1.8',
};

/** A pipeline's tile: its two colours as a gradient, its glyph in white. */
export function PipelineTile({ pipeline, className }: { pipeline: Pipeline; className?: string }) {
  const id = `tc-tile-${pipeline.id}`;
  return (
    <svg viewBox="0 0 16 16" className={className} aria-hidden="true">
      <defs>
        <linearGradient id={id} x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stopColor={pipeline.tint[0]} />
          <stop offset="1" stopColor={pipeline.tint[1]} />
        </linearGradient>
      </defs>
      <rect width="16" height="16" rx="4.6" fill={`url(#${id})`} />
      <path
        d={GLYPHS[pipeline.icon]}
        fill="none"
        stroke="#fff"
        strokeWidth="1.1"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

/** A meeting's calendar tile: the day, with an orange dot when it is on or within the hour. */
export function DayTile({
  day,
  live,
  className,
}: {
  day: number;
  live?: boolean;
  className?: string;
}) {
  return (
    <span className={className} data-live={live || undefined} aria-hidden="true">
      {day}
    </span>
  );
}

/** A metric's ring, `share` full, in a small white tile. */
export function Ring({ share, className }: { share: number; className?: string }) {
  const r = 4.4;
  const length = 2 * Math.PI * r;
  return (
    <svg viewBox="0 0 16 16" className={className} aria-hidden="true">
      <circle cx="8" cy="8" r={r} fill="none" stroke="var(--ring-track)" strokeWidth="1.4" />
      <circle
        cx="8"
        cy="8"
        r={r}
        fill="none"
        stroke="var(--ring)"
        strokeWidth="1.4"
        strokeLinecap="round"
        strokeDasharray={`${(length * share).toFixed(2)} ${length.toFixed(2)}`}
        transform="rotate(-90 8 8)"
      />
    </svg>
  );
}

/** The Routines button's tile: a warm gradient with a slash. */
export function RoutineTile({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 16 16" className={className} aria-hidden="true">
      <defs>
        <linearGradient id="tc-routine" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stopColor="#ff9a3c" />
          <stop offset="0.55" stopColor="#f2386f" />
          <stop offset="1" stopColor="#a12cf0" />
        </linearGradient>
      </defs>
      <rect width="16" height="16" rx="4" fill="url(#tc-routine)" />
      <path d="M9.6 4.2 6.4 11.8" stroke="#fff" strokeWidth="1.5" strokeLinecap="round" />
    </svg>
  );
}

/** Eight invented app marks for the "Connect your apps" tray. */
export function AppMarks({ className }: { className?: string }) {
  const marks = [
    // A coral sunburst.
    <g key="a" stroke="#e8735a" strokeWidth="1.3" strokeLinecap="round">
      {Array.from({ length: 8 }, (_, i) => {
        const a = (i / 8) * Math.PI;
        return (
          <line
            key={i}
            x1={8 - 5.5 * Math.cos(a)}
            y1={8 - 5.5 * Math.sin(a)}
            x2={8 + 5.5 * Math.cos(a)}
            y2={8 + 5.5 * Math.sin(a)}
          />
        );
      })}
    </g>,
    // Olive rings.
    <g key="b">
      <circle cx="8" cy="8" r="6.4" fill="#c9d77a" />
      <circle cx="8" cy="8" r="3.6" fill="none" stroke="#3f6b2c" strokeWidth="1.4" />
      <circle cx="8" cy="8" r="1.2" fill="#3f6b2c" />
    </g>,
    // A framed square with two bars.
    <g key="c">
      <rect
        x="2"
        y="2"
        width="12"
        height="12"
        rx="2.4"
        fill="#fff"
        stroke="#2b2b2b"
        strokeWidth="1.3"
      />
      <path d="M5.6 5v6M10.4 5v6M5.6 5l4.8 6" stroke="#2b2b2b" strokeWidth="1.3" fill="none" />
    </g>,
    // A striped disc.
    <g key="d">
      <circle cx="8" cy="8" r="6.4" fill="#4a4a4f" />
      <path
        d="M3 6.6 9.4 13M2.6 9.8l3.6 3.6M5.4 2.8l7.8 7.8M8.8 1.8l5.4 5.4"
        stroke="#fff"
        strokeWidth="0.9"
      />
    </g>,
    // A blue envelope with a cloud.
    <g key="e">
      <rect x="1.6" y="4.4" width="9.6" height="8" rx="1.6" fill="#2f74d0" />
      <path d="m2.4 5.4 4.4 3.4 4.4-3.4" stroke="#fff" strokeWidth="1" fill="none" />
      <circle cx="11.6" cy="6" r="3" fill="#7ec3f5" />
      <circle cx="13.6" cy="8.2" r="2.2" fill="#a9d8fa" />
    </g>,
    // Four coloured petals.
    <g key="f">
      <rect x="2" y="6.6" width="5.4" height="2.8" rx="1.4" fill="#36c5a8" />
      <rect x="8.6" y="6.6" width="5.4" height="2.8" rx="1.4" fill="#e0457b" />
      <rect x="6.6" y="2" width="2.8" height="5.4" rx="1.4" fill="#ecb22e" />
      <rect x="6.6" y="8.6" width="2.8" height="5.4" rx="1.4" fill="#4b8ef0" />
    </g>,
    // A violet coin with a wave.
    <g key="g">
      <circle cx="8" cy="8" r="6.6" fill="#7a67ee" />
      <path
        d="M5 9.6c1.2-2.6 2.8 1.4 4 .2s.8-3.2 2-2.6"
        stroke="#fff"
        strokeWidth="1.4"
        fill="none"
        strokeLinecap="round"
      />
    </g>,
    // Stepped bars.
    <g key="h">
      <path d="M2 13 6 3" stroke="#f05a8c" strokeWidth="2.2" strokeLinecap="round" />
      <path d="M5.6 13 9.6 3" stroke="#f0a43a" strokeWidth="2.2" strokeLinecap="round" />
      <path d="M9.2 13h4.8l-2.4-5Z" fill="#5a63f0" />
    </g>,
  ];
  return (
    <span className={className} aria-hidden="true">
      {marks.map((mark, i) => (
        <svg key={i} viewBox="0 0 16 16">
          {mark}
        </svg>
      ))}
    </span>
  );
}
```

### today-cards/demo-pointer.tsx

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

'use client';

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

/*
 * The demo's pointer, and the steps that move it. A demo is a script of awaited steps: wait, or
 * glide the pointer somewhere. Stopping the demo (the reader takes over, or it scrolls away) ends
 * the step in progress at once: its promise rejects, which ends the script right there, and the
 * pointer fades out.
 *
 * Each glide is a curved flight played by the Web Animations API, so nothing runs frame by frame.
 * The points of the flight are handed to whoever wants to move along with it (nothing here
 * does, but the hook is kept). The arrow and the hand are HTML boxes
 * that swap by their visibility: a change of style inside an SVG would lay the SVG out again.
 */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

### today-cards/use-autoplay.ts

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

'use client';

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

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

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

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

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

### today-cards/today-cards.module.css

```css
/* Today cards (today-cards.tsx): an assistant's new-session screen. Near-white, with a pale warm
   grey sidebar, a soft rainbow across the top, white composer and pale grey cards; orange for
   meetings on now, crimson for late tasks, green for numbers that moved the right way. At night
   near-black, the cards a step lighter, the rainbow dimmed.

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

   Layout is by container queries on the root: a 215 px sidebar from 900 px, a drawer behind a
   menu button under that, the cards in one column under 640 px.

   Memory. Only transforms, opacity and a small blur animate (menus, the palette, the toast, the
   greeting's words, the suggestions' row); the rainbow is one static SVG, blurred once. */

.root {
  --bg: #fdfcfc;
  --side: #f8f7f7;
  --side-line: #efeded;
  --ink: #1d1d1d;
  --ink-2: #3a3a3a;
  --muted: #8b8b8b;
  --muted-2: #a3a3a3;
  --faint: #bdbdbd;
  --line: #ececec;
  --hover: #efeeee;
  --nav-on: #ecebeb;
  --card: #f7f6f6;
  --card-edge: rgb(0 0 0 / 0.025);
  --row-hover: #eeeded;
  --surface: #fff;
  --surface-edge: rgb(0 0 0 / 0.07);
  --chip-edge: rgb(0 0 0 / 0.022);
  --tray: #f3f2f2;
  --tile: #fff;
  --tile-edge: #e6e5e5;
  --chip-icon: #f1f0f0;
  --live: #d4652b;
  --live-soft: #e28b5a;
  --dot: #f0702c;
  --late: #c8284d;
  --good: #2e9a57;
  --bad: #c8284d;
  --ring: #2e9a57;
  --ring-track: #e3e3e3;
  --solid: #1c1c1c;
  --solid-ink: #fff;
  --tip: #1c1c1c;
  --tip-ink: #fff;
  --scrim: rgb(24 24 24 / 0.14);
  --shadow: rgb(20 20 20 / 0.06);
  --shadow-2: rgb(20 20 20 / 0.1);
  --rings: #e7e6e6;
  --haze: rgb(0 0 0 / 0.06);
  --aurora: 1;
  --veil: transparent;
  --wash: rgb(0 0 0 / 0.045);
  --check: #1c1c1c;
  --focus: #6d8cff;
  --glow-a: #f9d8f0;
  --glow-b: #e2f4ea;
  --glow-c: #e3e6fb;
  --pointer: #1c1c1e;
  --pointer-edge: #fff;
  --pointer-shadow: rgb(0 0 0 / 0.25);

  position: relative;
  container: today / inline-size;
  width: 100%;
  overflow: hidden;
  overflow: clip;
  border-radius: 16px;
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--line);
  color: var(--ink);
  font-family:
    'Library Inter',
    Inter,
    ui-sans-serif,
    system-ui,
    -apple-system,
    'Segoe UI',
    sans-serif;
  font-size: 12.5px;
  line-height: 18px;
  letter-spacing: -0.03em;
  font-feature-settings: 'cv11' 1;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  text-align: left;
}
@supports (color: light-dark(#fff, #000)) {
  .root {
    --bg: light-dark(#fdfcfc, #111112);
    --side: light-dark(#f8f7f7, #151516);
    --side-line: light-dark(#efeded, #222224);
    --ink: light-dark(#1d1d1d, #ececee);
    --ink-2: light-dark(#3a3a3a, #cfcfd3);
    --muted: light-dark(#8b8b8b, #8e8e94);
    --muted-2: light-dark(#a3a3a3, #6f6f75);
    --faint: light-dark(#bdbdbd, #56565b);
    --line: light-dark(#ececec, #232325);
    --hover: light-dark(#efeeee, #222224);
    --nav-on: light-dark(#ecebeb, #242426);
    --card: light-dark(#f7f6f6, #19191b);
    --card-edge: light-dark(rgb(0 0 0 / 0.025), rgb(255 255 255 / 0.04));
    --row-hover: light-dark(#eeeded, #252528);
    --surface: light-dark(#fff, #1d1d20);
    --surface-edge: light-dark(rgb(0 0 0 / 0.07), rgb(255 255 255 / 0.08));
    --chip-edge: light-dark(rgb(0 0 0 / 0.022), rgb(255 255 255 / 0.06));
    --tray: light-dark(#f3f2f2, #18181a);
    --tile: light-dark(#fff, #26262a);
    --tile-edge: light-dark(#e6e5e5, #333337);
    --chip-icon: light-dark(#f1f0f0, #2a2a2d);
    --live: light-dark(#d4652b, #ff9a5c);
    --live-soft: light-dark(#e28b5a, #d98557);
    --dot: light-dark(#f0702c, #ff8a45);
    --late: light-dark(#c8284d, #ff6f8e);
    --good: light-dark(#2e9a57, #5fd38b);
    --bad: light-dark(#c8284d, #ff6f8e);
    --ring: light-dark(#2e9a57, #5fd38b);
    --ring-track: light-dark(#e3e3e3, #38383c);
    --solid: light-dark(#1c1c1c, #f1f1f2);
    --solid-ink: light-dark(#fff, #151516);
    --tip: light-dark(#1c1c1c, #f1f1f2);
    --tip-ink: light-dark(#fff, #151516);
    --scrim: light-dark(rgb(24 24 24 / 0.14), rgb(0 0 0 / 0.5));
    --shadow: light-dark(rgb(20 20 20 / 0.06), rgb(0 0 0 / 0.4));
    --shadow-2: light-dark(rgb(20 20 20 / 0.1), rgb(0 0 0 / 0.55));
    --rings: light-dark(#e7e6e6, #222225);
    --veil: light-dark(transparent, rgb(17 17 18 / 0.62));
    --wash: light-dark(rgb(0 0 0 / 0.045), rgb(255 255 255 / 0.02));
    --haze: light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.25));
    --check: light-dark(#1c1c1c, #f1f1f2);
    --focus: light-dark(#6d8cff, #8ea6ff);
    --glow-a: light-dark(#f9d8f0, #3d1f37);
    --glow-b: light-dark(#e2f4ea, #15302a);
    --glow-c: light-dark(#e3e6fb, #1c2440);
  }
}
.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;
}
@media (prefers-color-scheme: dark) {
  .root[data-color-scheme='system'] {
    color-scheme: dark;
  }
}

:where(.root) *,
:where(.root) *::before,
:where(.root) *::after {
  box-sizing: border-box;
}
:where(.root) :is(h1, h2, h3, p, ul, figure) {
  margin: 0;
  padding: 0;
}
:where(.root) ul {
  list-style: none;
}
:where(.root) button {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-align: inherit;
  cursor: pointer;
}
:where(.root) a {
  color: inherit;
  text-decoration: none;
}
.root :is(button, a, input, textarea, [role='option']):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}
:where(.root) b {
  font-weight: inherit;
}
:where(.root) small {
  font-size: inherit;
}
.srOnly {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Icon sizes: the lab forces unsized SVGs to 16 px, so every one is sized here. */
.icon12 {
  flex: none;
  width: 12px;
  height: 12px;
}
.icon14 {
  flex: none;
  width: 14px;
  height: 14px;
}
.icon16 {
  flex: none;
  width: 16px;
  height: 16px;
}
.tile {
  flex: none;
  width: 16px;
  height: 16px;
}

/* ——— The frame ——— */

.app {
  position: relative;
  display: grid;
  grid-template-columns: 215px minmax(0, 1fr);
  height: 1080px;
}

/* ——— Sidebar ——— */

.sidebar {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 6px 7px;
  border-right: 1px solid var(--side-line);
  background: var(--side);
  color: var(--ink-2);
}
.workspace {
  display: flex;
  align-items: center;
  height: 28px;
  gap: 1px;
}
.workspaceButton {
  display: flex;
  flex: 1;
  align-items: center;
  min-width: 0;
  height: 28px;
  padding: 0 6px;
  gap: 9px;
  border-radius: 8px;
  color: #6b6b6b;
  color: var(--muted);
}
.workspaceButton:hover,
.sideIcon:hover {
  background: var(--hover);
}
.workspaceLogo {
  display: grid;
  flex: none;
  width: 16px;
  height: 16px;
  place-items: center;
  border-radius: 4px;
  background: var(--tile);
  box-shadow: inset 0 0 0 1px var(--tile-edge);
  color: var(--muted);
}
.workspaceLogo svg {
  width: 10px;
  height: 10px;
}
.workspaceName {
  min-width: 0;
  overflow: hidden;
  color: var(--ink-2);
  white-space: nowrap;
  mask-image: linear-gradient(to left, transparent, #000 14px);
}
.sideIcon {
  display: grid;
  flex: none;
  width: 24px;
  height: 24px;
  place-items: center;
  border-radius: 7px;
  color: var(--ink-2);
}
.nav {
  display: flex;
  flex-direction: column;
  margin-top: 8px;
  gap: 2.5px;
}
.navItem,
.sideItem {
  display: flex;
  align-items: center;
  width: 100%;
  height: 28px;
  padding: 0 7px;
  gap: 12px;
  border-radius: 8px;
  color: var(--ink-2);
  white-space: nowrap;
  transition: background-color 0.12s ease;
}
.navItem svg,
.sideItem > svg:first-child {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--ink-2);
}
.sideItem > .tile:first-child {
  width: 16px;
  height: 16px;
  margin: 0 -1px;
}
.navItem:hover,
.sideItem:hover {
  background: var(--hover);
}
.navItem[aria-current='page'] {
  background: var(--nav-on);
  color: var(--ink);
}
.sideGroup {
  display: flex;
  flex-direction: column;
  margin-top: 10.5px;
}
.sideHeading {
  display: flex;
  align-items: center;
  align-self: flex-start;
  height: 24px;
  padding: 0 7px;
  gap: 6px;
  border-radius: 6px;
  color: var(--muted);
  font-size: 12px;
}
.sideHeading:hover {
  color: var(--ink-2);
}
.caret {
  width: 10px;
  height: 10px;
  color: var(--faint);
  transition: rotate 0.15s ease;
}
.sideHeading[aria-expanded='false'] .caret {
  rotate: -90deg;
}
.sideList {
  display: flex;
  flex-direction: column;
  margin-top: 3px;
  gap: 1px;
}
.sideList[data-kind='sessions'] {
  gap: 2.5px;
}
.sideLabel {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  mask-image: linear-gradient(to left, transparent, #000 18px);
}
.sideList[data-kind='pipelines'] .sideLabel {
  mask-image: none;
}
.pin {
  flex: none;
  width: 13px;
  height: 13px;
  color: var(--muted-2);
  opacity: 0;
}
.pinned,
.sideItem:hover .pin {
  opacity: 1;
}
.sessionDot {
  display: grid;
  flex: none;
  width: 14px;
  place-items: center;
}
.sessionDot::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--tile-edge);
}

/* ——— Main ——— */

.main {
  position: relative;
  min-width: 0;
  min-height: 0;
}
.topbar {
  position: absolute;
  z-index: 3;
  top: 11px;
  left: 8px;
  display: flex;
  align-items: center;
  gap: 4px;
}
.history {
  display: flex;
  align-items: center;
  height: 28px;
  padding: 0 6px;
  gap: 8px;
  border-radius: 8px;
  color: var(--ink-2);
}
.history svg:last-child {
  margin-left: -2px;
  color: var(--muted-2);
}
.history:hover,
.menuButton:hover {
  background: var(--hover);
}
.menuButton {
  display: none;
  width: 30px;
  height: 30px;
  place-items: center;
  border-radius: 8px;
  color: var(--ink-2);
}
.scroll {
  position: relative;
  height: 100%;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
.scroll::-webkit-scrollbar {
  display: none;
}

/* The sky: the rainbow and the faint rings behind the greeting. */
.sky {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  height: 560px;
  overflow: hidden;
  background: linear-gradient(to bottom, var(--wash), transparent 240px);
  pointer-events: none;
}
.aurora {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 260px;
  opacity: var(--aurora);
}
.sky::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--veil);
}
.haze {
  fill: none;
  stroke: var(--haze);
  stroke-width: 90;
}
.rings {
  position: absolute;
  top: calc(201px - 400px);
  left: calc(50% - 600px);
  width: 1200px;
  height: 800px;
  color: var(--rings);
  mask-image: radial-gradient(closest-side, #000 40%, transparent);
}

.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: min(690px, 100% - 32px);
  margin: 0 auto;
  padding-top: 168px;
}
.mark {
  width: 66px;
  height: 66px;
  color: var(--ink);
}
.greeting {
  position: relative;
  display: grid;
  width: 100%;
  margin-top: 15px;
  color: var(--ink);
  font-size: 31px;
  font-weight: 330;
  line-height: 44px;
  letter-spacing: -0.05em;
  text-align: center;
}
.greetingLine {
  grid-area: 1 / 1;
}
.word {
  display: inline-block;
  margin-right: 0.24em;
  animation: tc-word-in 0.42s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  animation-delay: calc(var(--i) * 55ms + 60ms);
}
.word:last-child {
  margin-right: 0;
}
.word[data-name] {
  color: var(--muted);
}
.wordTail {
  color: var(--ink);
}
.greetingLine[data-leaving] .word {
  animation: tc-word-out 0.18s ease-in both;
}

.chipRow {
  position: relative;
  width: 100%;
  margin-top: 26px;
}
.chipView {
  overflow: hidden;
  padding: 4px 0;
  margin: -4px 0;
  mask-image: linear-gradient(
    to right,
    transparent,
    #000 70px,
    #000 calc(100% - 70px),
    transparent
  );
}
.chipTrack {
  display: flex;
  width: max-content;
  gap: 8.5px;
  transition: transform 0.48s cubic-bezier(0.3, 0.7, 0.15, 1);
  will-change: transform;
}
.chipTrack[data-snap] {
  transition: none;
}
.chip {
  display: flex;
  flex: none;
  align-items: center;
  height: 29px;
  padding: 0 12px 0 6px;
  gap: 9px;
  border-radius: 10px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--chip-edge),
    0 1px 4px var(--shadow);
  color: var(--muted);
  white-space: nowrap;
  transition:
    color 0.25s ease,
    box-shadow 0.25s ease;
}
.chip:hover {
  color: var(--ink-2);
}
.chip[data-active] {
  color: var(--ink);
  box-shadow:
    0 0 0 1px light-dark(#f3dbe8, #3a2a35),
    0 0 0 3px light-dark(rgb(255 214 236 / 0.35), rgb(255 140 200 / 0.08)),
    0 2px 8px light-dark(rgb(150 230 190 / 0.25), rgb(0 0 0 / 0.3));
}
.chipIcon {
  display: grid;
  width: 18px;
  height: 18px;
  place-items: center;
  border-radius: 5px;
  background: var(--chip-icon);
  color: var(--muted);
}
.chip[data-active] .chipIcon {
  color: var(--ink-2);
}
.chipArrow {
  position: absolute;
  top: 50%;
  display: grid;
  width: 22px;
  height: 22px;
  margin-top: -11px;
  place-items: center;
  border-radius: 6px;
  color: var(--muted-2);
}
.chipArrow:first-child {
  left: -42px;
}
.chipArrow:last-child {
  right: -43px;
}
.chipArrow:hover {
  background: var(--hover);
  color: var(--ink);
}

/* The composer, with its tray of apps under it. */
.composer {
  position: relative;
  width: 100%;
  margin-top: 33px;
}
.field {
  position: relative;
  z-index: 1;
  height: 113px;
  border-radius: 16px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--surface-edge),
    0 2px 6px var(--shadow),
    0 8px 24px var(--shadow);
}
.input {
  display: block;
  width: 100%;
  height: 74px;
  padding: 9px 13px 0;
  border: 0;
  border-radius: 16px 16px 0 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  line-height: 20px;
  letter-spacing: 0;
  resize: none;
  outline: none;
}
.input:focus-visible {
  outline: none;
}
.field:has(.input:focus-visible) {
  box-shadow:
    0 0 0 1px var(--focus),
    0 2px 6px var(--shadow),
    0 8px 24px var(--shadow);
}
.placeholder {
  position: absolute;
  top: 9px;
  left: 13px;
  color: var(--faint);
  font-size: 13.5px;
  line-height: 20px;
  letter-spacing: 0;
  pointer-events: none;
  animation: tc-blur-in 0.45s cubic-bezier(0.2, 0.7, 0.2, 1) 0.12s both;
}
.tools {
  position: absolute;
  right: 4px;
  bottom: 4px;
  left: 9px;
  display: flex;
  align-items: center;
  height: 32px;
  gap: 12px;
}
.tool {
  display: flex;
  align-items: center;
  height: 26px;
  margin: 0 -6px;
  padding: 0 6px;
  gap: 7px;
  border-radius: 7px;
  color: var(--ink-2);
}
.tool:hover {
  background: var(--hover);
}
.tool:nth-child(3) {
  margin-left: -2px;
}
.send {
  display: grid;
  width: 32px;
  height: 32px;
  margin-left: auto;
  place-items: center;
  border-radius: 50%;
  background: var(--solid);
  color: var(--solid-ink);
  transition: transform 0.15s ease;
}
.send:active {
  transform: scale(0.94);
}
.tray {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: calc(100% - 36px);
  height: 49px;
  margin: -12px auto 0;
  padding: 12px 13.5px 0;
  gap: 12px;
  border-radius: 0 0 13px 13px;
  background: var(--tray);
  color: var(--muted);
  font-size: 12.5px;
}
.tray:hover {
  color: var(--ink-2);
}
.tray > span:first-child {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.apps {
  display: flex;
  flex: none;
  gap: 10.5px;
}
.apps svg {
  width: 14px;
  height: 14px;
}

/* ——— The day ——— */

.today {
  position: relative;
  width: min(690px, 100% - 32px);
  margin: 0 auto;
  padding-top: 55px;
}
.date {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 25px;
  color: var(--ink);
  font-size: 16px;
  font-weight: 400;
  line-height: 22px;
  letter-spacing: 0;
}
.date::before,
.date::after {
  content: '';
  width: 195px;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--line));
}
.date::after {
  background: linear-gradient(to left, transparent, var(--line));
}
.grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: 34px;
  gap: 10px 10.5px;
}
.card {
  position: relative;
  height: 135px;
  padding: 0 6px 7px;
  border-radius: 16px;
  background: var(--card);
  box-shadow: inset 0 0 0 1px var(--card-edge);
}
.cardHead {
  display: flex;
  align-items: center;
  height: 41px;
  padding: 3px 3px 0 9px;
}
.cardTitle {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: var(--ink);
  font-size: 13px;
  font-weight: 500;
  line-height: 20px;
  letter-spacing: -0.02em;
  white-space: nowrap;
}
.cardActions {
  display: flex;
  gap: 6.5px;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.card:hover .cardActions,
.card[data-hover] .cardActions,
.card:focus-within .cardActions,
.cardActions:has([aria-expanded='true']) {
  opacity: 1;
}
.iconButton {
  position: relative;
  display: grid;
  width: 24px;
  height: 24px;
  place-items: center;
  border-radius: 7px;
  color: var(--muted);
  transition:
    background-color 0.12s ease,
    color 0.12s ease;
}
.iconButton:hover,
.iconButton[data-hover],
.iconButton[aria-expanded='true'] {
  background: var(--row-hover);
  color: var(--ink);
}
.card[data-part='performance'] .iconButton {
  color: var(--ink);
}
.tip {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  z-index: 4;
  padding: 3px 7px;
  border-radius: 6px;
  background: var(--tip);
  color: var(--tip-ink);
  font-size: 11.5px;
  font-weight: 500;
  line-height: 16px;
  white-space: nowrap;
  opacity: 0;
  translate: -50% 3px;
  pointer-events: none;
  transition:
    opacity 0.12s ease,
    translate 0.12s ease;
}
.iconButton:is(:hover, [data-hover], :focus-visible):not([aria-expanded='true']) .tip {
  opacity: 1;
  translate: -50% 0;
  transition-delay: 0.35s;
}
.rows {
  position: relative;
}
.rowItem {
  position: relative;
}
.row {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  height: 29px;
  padding: 0 7px 0 8px;
  gap: 10px;
  border-radius: 8px;
  color: var(--ink);
  transition: background-color 0.2s ease;
}
.row:hover,
.row[data-hover],
.rowItem[data-hover] .row,
.rowItem:has(.taskOpen:hover) .row {
  background: var(--row-hover);
  transition-duration: 0.08s;
}
.rowLabel {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  mask-image: linear-gradient(to left, transparent, #000 22px);
}
.rowValue {
  flex: none;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  transition: opacity 0.12s ease;
}
.rowValue b {
  color: var(--ink);
}
.rowValue small {
  color: var(--muted-2);
}
.rowValue[data-tone='live'],
.rowValue[data-tone='live'] :is(b, small) {
  color: var(--live);
}
.rowValue[data-tone='live'] small {
  opacity: 0.75;
}
.rowValue[data-tone='late'] {
  color: var(--late);
}
.money {
  margin-left: 6px;
}
.money b {
  margin: 0 1px;
}
.change {
  margin-right: 7px;
  color: var(--bad);
}
.change[data-good] {
  color: var(--good);
}
.rowHint {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  padding: 0 9px 0 34px;
  gap: 6px;
  border-radius: 0 8px 8px 0;
  background: linear-gradient(to right, transparent, var(--row-hover) 30px);
  color: var(--muted-2);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
}
:is(.row:hover, .row[data-hover], .rowItem[data-hover] .row, .rowItem:has(.taskOpen:hover) .row)
  .rowHint {
  opacity: 1;
}
:is(.row:hover, .row[data-hover], .rowItem[data-hover] .row, .rowItem:has(.taskOpen:hover) .row)
  .rowValue {
  opacity: 0;
}
.day {
  position: relative;
  display: grid;
  flex: none;
  width: 16px;
  height: 16px;
  place-items: center;
  border-radius: 4px;
  background: var(--tile);
  box-shadow: inset 0 0 0 1px var(--tile-edge);
  color: var(--ink-2);
  font-size: 7.5px;
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.day[data-live]::after {
  content: '';
  position: absolute;
  top: -2px;
  right: -2px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--dot);
}
.ringTile {
  display: grid;
  flex: none;
  width: 16px;
  height: 16px;
  place-items: center;
  border-radius: 4px;
  background: var(--tile);
  box-shadow: inset 0 0 0 1px var(--tile-edge);
}
.ring {
  width: 14px;
  height: 14px;
}

/* A task: its box, then its name, which opens it. */
.row[data-kind='task'] {
  padding: 0;
  gap: 0;
}
.check {
  display: grid;
  flex: none;
  width: 34px;
  height: 29px;
  padding-left: 8px;
  place-items: center start;
  border-radius: 8px 0 0 8px;
}
.check::before {
  content: '';
  grid-area: 1 / 1;
  width: 16px;
  height: 16px;
  border-radius: 5px;
  background: var(--tile);
  box-shadow: inset 0 0 0 1px var(--tile-edge);
  transition:
    background-color 0.15s ease,
    box-shadow 0.15s ease;
}
.check svg {
  grid-area: 1 / 1;
  width: 16px;
  height: 11px;
  color: var(--solid-ink);
  opacity: 0;
  scale: 0.6;
  transition:
    opacity 0.15s ease,
    scale 0.2s cubic-bezier(0.3, 1.6, 0.5, 1);
}
.check:hover::before {
  box-shadow: inset 0 0 0 1px var(--faint);
}
.check[aria-checked='true']::before {
  background: var(--check);
  box-shadow: none;
}
.check[aria-checked='true'] svg {
  opacity: 1;
  scale: 1;
}
.taskOpen {
  position: relative;
  display: flex;
  flex: 1;
  align-items: center;
  min-width: 0;
  height: 29px;
  padding-right: 7px;
  gap: 10px;
  border-radius: 0 8px 8px 0;
}
.rowItem[data-done] .rowLabel {
  color: var(--muted-2);
  text-decoration: line-through;
  text-decoration-color: var(--muted-2);
}
.rowItem[data-done] .rowValue {
  color: var(--muted-2);
}
.rowItem[data-leaving] {
  opacity: 0;
  filter: blur(3px);
  transition:
    opacity 0.2s ease,
    filter 0.2s ease;
}
.empty {
  padding: 6px 9px;
  color: var(--muted);
}

/* ——— Routines ——— */

.routines {
  width: min(690px, 100% - 32px);
  margin: 0 auto;
  padding: 53px 0 48px;
  text-align: center;
}
.routinesTitle {
  color: var(--ink);
  font-size: 18px;
  font-weight: 350;
  line-height: 24px;
  letter-spacing: -0.015em;
}
.routinesTitle b {
  font-weight: 560;
}
.routinesTitle sup {
  font-size: 8px;
  vertical-align: 9px;
  line-height: 0;
}
.routinesText {
  max-width: 380px;
  margin: 6px auto 0;
  color: var(--muted);
  font-size: 14px;
  line-height: 19px;
}
.routineList {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 26px;
  gap: 10.5px;
  text-align: left;
}
.routine {
  display: flex;
  align-items: center;
  padding: 12px;
  gap: 10px;
  border-radius: 14px;
  background: var(--card);
}
.routineText {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}
.routineText b {
  font-weight: 500;
}
.routineText span {
  overflow: hidden;
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.routineRun {
  height: 24px;
  padding: 0 10px;
  border-radius: 7px;
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--surface-edge);
}
.routineRun:hover {
  background: var(--hover);
}

/* ——— The side card ——— */

.promo {
  position: absolute;
  z-index: 5;
  bottom: 14px;
  left: 14px;
  width: 306px;
  height: 498px;
  overflow: hidden;
  border-radius: 14px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--surface-edge),
    0 4px 12px var(--shadow),
    0 16px 40px var(--shadow);
  animation: tc-pop 0.3s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.promo[data-closing] {
  opacity: 0;
  scale: 0.97;
  filter: blur(2px);
  transition:
    opacity 0.18s ease,
    scale 0.18s ease,
    filter 0.18s ease;
}
.promoArt {
  position: relative;
  height: 160px;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
}
.promoClose {
  position: absolute;
  top: 10px;
  right: 10px;
  display: grid;
  width: 20px;
  height: 20px;
  place-items: center;
  border-radius: 50%;
  background: color-mix(in srgb, var(--surface) 60%, transparent);
  color: var(--muted);
}
.promoClose:hover {
  background: var(--surface);
  color: var(--ink);
}
.promoBody {
  padding: 16px 16px 0;
}
.promoKicker {
  display: block;
  color: var(--muted-2);
  font-size: 12px;
  line-height: 18px;
}
.promoTitle {
  margin-top: 4px;
  color: var(--ink);
  font-size: 20px;
  font-weight: 330;
  line-height: 28px;
  letter-spacing: -0.04em;
}
.promoList {
  display: flex;
  flex-direction: column;
  margin-top: 14px;
  gap: 18px;
}
.promoList li {
  display: flex;
  gap: 15px;
}
.promoList li > svg {
  margin: 2px 0 0 -2px;
  color: var(--muted);
}
.promoList b {
  display: block;
  margin-bottom: 2px;
  color: var(--ink);
  font-size: 13px;
  font-weight: 400;
}
.promoList li > span {
  color: var(--muted);
  font-size: 12.5px;
  line-height: 17px;
}
.promoFoot {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 42px;
  padding: 0 9px 0 16px;
  border-top: 1px solid var(--line);
}
.learnMore {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 12.5px;
}
.learnMore svg {
  opacity: 0;
  translate: -3px 0;
  transition:
    opacity 0.15s ease,
    translate 0.15s ease;
}
.learnMore:is(:hover, [data-hover], :focus-visible) {
  color: var(--ink);
  text-decoration: underline dotted;
  text-decoration-color: var(--faint);
  text-underline-offset: 4px;
}
.learnMore:is(:hover, [data-hover], :focus-visible) svg {
  opacity: 1;
  translate: 0 0;
}
.tryIt {
  height: 23px;
  padding: 0 9px;
  border-radius: 7px;
  background: var(--solid);
  color: var(--solid-ink);
  font-size: 12.5px;
}

/* The cards close up: the side card's picture and the release note's. */
.preview {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: color-mix(in srgb, var(--surface) 80%, var(--glow-a));
}
.previewGlow {
  position: absolute;
  inset: -60%;
  background:
    radial-gradient(closest-side at 25% 30%, var(--glow-a), transparent),
    radial-gradient(closest-side at 70% 40%, var(--glow-b), transparent),
    radial-gradient(closest-side at 45% 75%, var(--glow-c), transparent);
  animation: tc-drift 18s ease-in-out infinite alternate;
}
.previewCard {
  position: absolute;
  top: 42px;
  left: 31px;
  display: flex;
  flex-direction: column;
  width: 227px;
  height: 160px;
  padding: 14px 12px 0 14px;
  border-radius: 14px;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--surface) 70%, transparent);
  mask-image: linear-gradient(to bottom, #000 90px, transparent 120px);
}
.previewCard[data-peek] {
  left: 276px;
}
.previewTitle {
  color: var(--ink);
  font-weight: 500;
  line-height: 18px;
  white-space: nowrap;
}
.previewRow {
  display: flex;
  align-items: center;
  height: 29px;
  gap: 10px;
}
.previewTitle + .previewRow {
  margin-top: 8px;
}
.previewLabel {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}
.previewWhen {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.previewWhen b {
  color: var(--ink);
}
.previewWhen[data-live] {
  color: var(--live);
}
.box {
  width: 16px;
  height: 16px;
  border-radius: 5px;
  background: var(--tile);
}
.preview[data-large] .previewCard {
  zoom: 1.74;
  top: 42px;
  left: 44px;
}
.preview[data-large] .previewCard[data-peek] {
  left: 285px;
}
.preview[data-large] .previewRow {
  animation: tc-blur-in 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  animation-delay: 0.25s;
}

/* ——— Overlays ——— */

.scrim {
  position: absolute;
  z-index: 10;
  inset: 0;
  background: var(--scrim);
  animation: tc-fade 0.18s ease both;
}
.scrim[data-closing] {
  opacity: 0;
  transition: opacity 0.14s ease;
}

.palette {
  position: absolute;
  z-index: 11;
  top: 90px;
  right: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  width: min(631px, 100% - 24px);
  max-height: calc(100% - 110px);
  margin: 0 auto;
  border-radius: 16px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--surface-edge),
    0 12px 40px var(--shadow-2);
  animation: tc-pop 0.2s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.palette[data-closing],
.menuLayer[data-closing],
.article[data-closing] {
  opacity: 0;
  scale: 0.985;
  filter: blur(2px);
  transition:
    opacity 0.14s ease,
    scale 0.14s ease,
    filter 0.14s ease;
}
.paletteHead {
  display: flex;
  flex: none;
  align-items: center;
  height: 40px;
  padding: 0 8px;
  gap: 6px;
}
.paletteBack {
  display: grid;
  width: 22px;
  height: 22px;
  place-items: center;
  border-radius: 6px;
  color: var(--muted);
}
.paletteBack:hover {
  background: var(--hover);
  color: var(--ink);
}
.paletteScope {
  display: flex;
  flex: none;
  align-items: center;
  height: 24px;
  padding: 0 8px;
  gap: 8px;
  border-radius: 7px;
  background: var(--card);
  color: var(--ink-2);
}
.paletteInput {
  flex: 1;
  min-width: 0;
  height: 30px;
  margin-left: 7px;
  padding: 0 6px;
  border: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  outline: none;
}
.paletteInput::placeholder {
  color: var(--faint);
}
.paletteInput:focus-visible {
  outline: none;
}
.paletteList {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 3px 5px 40px;
  scrollbar-width: thin;
}
.paletteGroup {
  display: flex;
  align-items: center;
  height: 26px;
  padding: 0 6px;
  color: var(--muted-2);
  font-size: 11.5px;
}
.paletteItem {
  position: relative;
  display: flex;
  align-items: center;
  height: 33px;
  padding: 0 8px 0 5px;
  gap: 10px;
  border-radius: 8px;
  color: var(--ink);
  cursor: pointer;
}
.paletteItem[data-active] {
  background: var(--card);
}
.paletteIcon {
  flex: none;
  width: 16px;
  height: 14px;
  color: var(--muted);
}
.paletteBox {
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: 5px;
  background: var(--tile);
  box-shadow: inset 0 0 0 1px var(--tile-edge);
}
.paletteLabel {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.paletteMeta {
  flex: none;
  color: var(--muted-2);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.paletteMeta[data-tone='live'] {
  color: var(--live);
}
.paletteMeta[data-tone='late'] {
  color: var(--late);
}
.paletteHint {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: none;
  align-items: center;
  padding: 0 9px 0 40px;
  gap: 7px;
  border-radius: 0 8px 8px 0;
  background: linear-gradient(to right, transparent, var(--card) 34px);
  color: var(--muted-2);
  font-size: 12px;
}
.paletteItem[data-active] .paletteHint {
  display: flex;
}
.paletteEmpty {
  padding: 16px 8px;
  color: var(--muted);
}
.paletteFoot {
  position: absolute;
  right: 7px;
  bottom: 7px;
}
.paletteAction {
  height: 24px;
  padding: 0 9px;
  border-radius: 6px;
  background: var(--solid);
  color: var(--solid-ink);
  font-size: 12.5px;
}

.menuCatch {
  position: absolute;
  z-index: 11;
  inset: 0;
}
.menuLayer {
  position: absolute;
  z-index: 12;
  animation: tc-pop 0.16s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  transform-origin: top right;
}
.menu,
.submenu {
  padding: 4px;
  border-radius: 12px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--surface-edge),
    0 6px 16px var(--shadow),
    0 18px 40px var(--shadow);
}
.menu {
  display: flex;
  flex-direction: column;
}
.submenu {
  position: absolute;
  left: calc(100% + 4.5px);
  display: flex;
  flex-direction: column;
  width: 223px;
  animation: tc-pop 0.16s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.submenu[data-side='left'] {
  right: calc(100% + 4.5px);
  left: auto;
}
.submenu[data-side='below'] {
  right: 12px;
  left: auto;
  max-width: calc(100cqw - 24px);
}
.menuItem {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  height: 33px;
  padding: 0 10px 0 6px;
  gap: 11px;
  border-radius: 8px;
  color: var(--ink);
  white-space: nowrap;
  transition: background-color 0.18s ease;
}
.menuItem[data-active],
.menuItem:focus-visible {
  background: var(--card);
  outline: none;
  transition-duration: 0.06s;
}
.menuIcon {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--muted);
}
.menuMark {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--muted-2);
  transition: color 0.12s ease;
}
.menuItem[data-active] .menuMark,
.menuItem:focus-visible .menuMark,
.menu .menuItem[aria-haspopup] .menuMark {
  color: var(--ink);
}
.menuLabel {
  flex: 1;
  min-width: 0;
  overflow: hidden;
}
.menuCaret {
  flex: none;
  width: 13px;
  height: 13px;
  color: var(--ink-2);
}
.menuHint {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  padding: 0 10px 0 44px;
  gap: 7px;
  border-radius: 0 8px 8px 0;
  background: linear-gradient(to right, transparent, var(--card) 40px);
  color: var(--muted-2);
  font-size: 12px;
  opacity: 0;
  transition: opacity 0.12s ease;
}
.menuItem:is([data-active], :focus-visible) .menuHint {
  opacity: 1;
}

.article {
  position: absolute;
  z-index: 11;
  top: 12px;
  right: 0;
  bottom: 12px;
  left: 0;
  width: min(720px, 100% - 16px);
  margin: 0 auto;
  overflow-y: auto;
  border-radius: 16px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--surface-edge),
    0 16px 50px var(--shadow-2);
  overscroll-behavior: contain;
  scrollbar-width: thin;
  animation: tc-pop 0.22s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.articleClose {
  position: sticky;
  z-index: 1;
  top: 12px;
  float: right;
  display: grid;
  width: 28px;
  height: 28px;
  margin: 12px 12px 0 0;
  place-items: center;
  border-radius: 50%;
  background: var(--card);
  color: var(--muted);
}
.articleClose:hover {
  color: var(--ink);
}
.articleBody {
  display: flex;
  flex-direction: column;
  max-width: 563px;
  margin: 0 auto;
  padding: 56px 0 72px;
  color: var(--ink-2);
  font-size: 14px;
  line-height: 21.6px;
}
.articleKicker {
  align-self: flex-start;
  padding: 2px 6px;
  border-radius: 5px;
  box-shadow: inset 0 0 0 1px var(--line);
  color: var(--muted);
  font-family: 'Library Mono', ui-monospace, monospace;
  font-size: 9.5px;
  line-height: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.articleTitle {
  margin-top: 14px;
  color: var(--ink);
  font-size: 32px;
  font-weight: 330;
  line-height: 40px;
  letter-spacing: -0.035em;
}
.articleLede {
  margin-top: 12px;
  color: var(--muted-2);
  font-size: 15px;
  line-height: 24px;
}
.articleByline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 20px;
  padding-bottom: 28px;
  border-bottom: 1px solid var(--line);
  color: var(--muted-2);
  font-family: 'Library Mono', ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.authors {
  display: flex;
}
.authors span {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--surface);
  background: radial-gradient(circle at 50% 38%, #e9c3a5 0 26%, transparent 27%), #6b5a4e;
}
.authors span + span {
  margin-left: -5px;
  background: radial-gradient(circle at 50% 38%, #f0cfb0 0 26%, transparent 27%), #a8573b;
}
.articleText {
  margin-top: 28px;
}
.articleFigure {
  position: relative;
  height: 318px;
  margin: 50px -28px 6px;
  overflow: hidden;
  border-radius: 18px;
}
.articleHeading {
  margin-top: 38px;
  color: var(--ink);
  font-size: 22px;
  font-weight: 330;
  line-height: 30px;
  letter-spacing: -0.025em;
}
.articleHeading + .articleText {
  margin-top: 16px;
}
.articlePoints {
  display: flex;
  flex-direction: column;
  margin-top: 14px;
  gap: 6px;
}
.articlePoints li {
  display: flex;
  gap: 14px;
}
.articlePoints li::before {
  content: '';
  flex: none;
  width: 14px;
  height: 1px;
  margin-top: 11px;
  background: var(--faint);
}

.toasts {
  position: absolute;
  z-index: 20;
  right: 32px;
  bottom: 32px;
  display: grid;
  width: 355px;
  pointer-events: none;
}
.toast {
  display: flex;
  grid-area: 1 / 1;
  align-items: center;
  align-self: end;
  height: 56px;
  padding: 0 15px 0 14px;
  gap: 11px;
  border-radius: 14px;
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--surface-edge),
    0 4px 16px var(--shadow);
  pointer-events: auto;
  animation: tc-toast-in 0.28s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
.toast[data-leaving] {
  pointer-events: none;
  animation: tc-toast-out 0.2s ease-in both;
}
.toastTick {
  display: grid;
  flex: none;
  width: 27px;
  height: 27px;
  place-items: center;
  border-radius: 8px;
  box-shadow: inset 0 0 0 1px var(--line);
  color: var(--ink);
}
.toastText {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: var(--ink);
  font-size: 14px;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.toastUndo {
  height: 23px;
  padding: 0 11px;
  border-radius: 12px;
  background: var(--hover);
  color: var(--ink);
}
.toastClose {
  display: grid;
  width: 21px;
  height: 21px;
  margin-left: 3px;
  place-items: center;
  border-radius: 50%;
  background: var(--hover);
  color: var(--muted);
}
.toastUndo:hover,
.toastClose:hover {
  background: var(--row-hover);
  color: var(--ink);
}

/* ——— The demo's pointer ——— */

.pointerLayer {
  position: absolute;
  z-index: 50;
  inset: 0;
  pointer-events: none;
}
.pointer {
  width: 22px;
  height: 22px;
}
.hand {
  visibility: hidden;
}
.pointer[data-glyph='hand'] .arrow {
  visibility: hidden;
}
.pointer[data-glyph='hand'] .hand {
  visibility: visible;
}

/* ——— Narrow ——— */

.drawerScrim {
  display: none;
}
@container today (max-width: 899px) {
  .app {
    grid-template-columns: minmax(0, 1fr);
    height: 860px;
  }
  .sidebar {
    position: absolute;
    z-index: 15;
    top: 0;
    bottom: 0;
    left: 0;
    width: 240px;
    box-shadow: 0 0 40px var(--shadow-2);
    translate: -105% 0;
    transition: translate 0.25s cubic-bezier(0.3, 0.7, 0.2, 1);
    visibility: hidden;
  }
  .sidebar[data-open] {
    translate: 0 0;
    visibility: visible;
  }
  .app[data-drawer] .drawerScrim {
    position: absolute;
    z-index: 14;
    inset: 0;
    display: block;
    background: var(--scrim);
    animation: tc-fade 0.2s ease both;
  }
  .menuButton {
    display: grid;
  }
  .promo {
    display: none;
  }
  .hero {
    padding-top: 112px;
  }
  .chipArrow {
    display: none;
  }
}
@container today (max-width: 639px) {
  .app {
    height: 780px;
  }
  .hero {
    padding-top: 84px;
  }
  .mark {
    width: 52px;
    height: 52px;
  }
  .greeting {
    margin-top: 12px;
    font-size: 26px;
    line-height: 34px;
  }
  .chipRow {
    width: calc(100% + 32px);
  }
  .chipView {
    mask-image: linear-gradient(
      to right,
      transparent,
      #000 32px,
      #000 calc(100% - 32px),
      transparent
    );
  }
  .tray > span:first-child {
    display: none;
  }
  .tray {
    justify-content: center;
  }
  .grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .date::before,
  .date::after {
    width: 60px;
  }
  .routineList {
    grid-template-columns: minmax(0, 1fr);
  }
  .toasts {
    right: 12px;
    bottom: 12px;
    width: calc(100% - 24px);
  }
  .tools .tool:nth-child(2) {
    display: none;
  }
}

@keyframes tc-word-in {
  from {
    opacity: 0;
    filter: blur(8px);
    translate: 0 4px;
  }
}
@keyframes tc-word-out {
  to {
    opacity: 0;
    filter: blur(8px);
  }
}
@keyframes tc-blur-in {
  from {
    opacity: 0;
    filter: blur(6px);
  }
}
@keyframes tc-fade {
  from {
    opacity: 0;
  }
}
@keyframes tc-pop {
  from {
    opacity: 0;
    scale: 0.97;
    filter: blur(3px);
  }
}
@keyframes tc-toast-in {
  from {
    opacity: 0;
    translate: 0 14px;
  }
}
@keyframes tc-toast-out {
  to {
    opacity: 0;
    translate: 0 -10px;
    scale: 0.97;
  }
}
@keyframes tc-drift {
  from {
    transform: translate(-6%, -4%) rotate(0deg);
  }
  to {
    transform: translate(6%, 5%) rotate(25deg);
  }
}

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