/* ---------------------------------------------------------------------------
   BrillMark design system (from the "BrillMark macOS Desktop App" Claude
   Design project). Every category req.md's Design System section calls for —
   Typography, Colors, Spacing, Radius, Shadows, Borders, Icons, Status
   indicators, Buttons, Cards, Inputs, Navigation, Tables/lists, Activity
   items — is a named token below. Nothing past this block should hardcode a
   raw px/rgba() value that already has a token; add a token here first if a
   new one is genuinely needed, so the system stays the single source of
   truth rather than drifting back into one-off values.

   Every token below resolves to the exact pixel/color value the app already
   had — this is a naming/formalization pass, not a visual redesign — with
   two disclosed, sub-perceptual exceptions: the type scale consolidates
   ~16 near-duplicate hand-tuned sizes (differing by ≤0.5px) into 9 real
   steps, and one border opacity (.09 -> .1, a 1% difference) reuses an
   existing token rather than getting a single-use one of its own.

   Specialist colors specifically live in public/js/design-system.js (not
   here) since they're keyed by specialist id, not by CSS selector — but they
   are the same five hues as --tone-info/-success/-warn/-error plus one
   purple, per req.md's own blue/purple/green/orange/red specialist palette.
   --------------------------------------------------------------------------- */
:root {
  /* ---- Color ---- */
  --color-brand-primary: #0960A8;
  --color-brand-secondary: #13367D;
  --color-brand-secondary-hover: #0F2A64;
  --color-text: #112446;
  --color-body: #313F58;
  --color-text-muted: #4F5C74;
  --color-label: #5B6880;
  --color-meta: #55627A;
  --color-surface: #FFFFFF;
  --color-surface-alt: #F4F7FA;
  --color-tint: #ECF5FA;
  --app-bg: #DFE7EE;
  --sidebar-grad-top: #E9EFF5;
  --sidebar-grad-bottom: #E3EBF3;

  --tone-success-bg: #E8F5EE; --tone-success-fg: #1A7F4B;
  --tone-info-bg: #ECF5FA;    --tone-info-fg: #0960A8;
  --tone-warn-bg: #FFF4E6;    --tone-warn-fg: #A65B0B;
  --tone-error-bg: #FBECEC;   --tone-error-fg: #A33A3A;
  --tone-neutral-bg: #F4F7FA; --tone-neutral-fg: #5B6A82;

  /* ---- Borders — every hairline in the app is one of these five. ---- */
  --border-faint: rgba(0, 0, 0, .07);
  --border-subtle: rgba(0, 0, 0, .06);
  --border-default: rgba(0, 0, 0, .1);
  --border-medium: rgba(0, 0, 0, .15);
  --border-strong: rgba(17, 36, 70, .22);
  --border-on-brand: rgba(9, 96, 168, .45);
  --color-border: var(--border-default); /* kept as an alias — this was the original token name */

  /* ---- Shadows — a 4-step elevation scale; every box-shadow in the app is
     one of these, from resting surfaces up to modals. ---- */
  --shadow-xs: 0 1px 2px rgba(17, 36, 70, .08);
  --shadow-sm: 0 1px 3px rgba(17, 36, 70, .12);
  --shadow-md: 0 4px 12px rgba(9, 96, 168, .1);
  --shadow-lg: 0 20px 60px rgba(17, 36, 70, .3);
  --overlay-scrim: rgba(17, 36, 70, .35);
  --surface-veil-hover: rgba(255, 255, 255, .6);
  --surface-veil: rgba(255, 255, 255, .75);

  /* ---- Typography — one named scale (see the disclosed-consolidation note
     above). ---- */
  --font-body: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* The one monospace stack. Added for the Review screen, where a diff,
     a file path and a line count all have to line up column-for-column —
     the body face is proportional, so a diff set in it misreads. System
     faces only: this app has no webfont loading step. */
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
  --text-2xs: 10.5px;
  --text-xs: 11.5px;
  --text-sm: 12.5px;
  --text-base: 13.5px;
  --text-md: 14px;
  --text-lg: 15.5px;
  --text-xl: 16.5px;
  --text-2xl: 21px;
  --text-3xl: 36px;
  --text-4xl: 40px;

  /* ---- Spacing — a real scale. Used wherever an existing value matches it
     exactly; kept literal wherever it doesn't (most gaps/padding here were
     hand-tuned per component, not built from a scale to begin with, so
     forcing all of them onto one would itself be a visual change). ---- */
  --space-3xs: 3px; --space-2xs: 5px; --space-xs: 10px; --space-sm: 15px;
  --space-md: 20px; --space-lg: 30px; --space-xl: 40px;

  /* ---- Radius — a full scale covering every distinct radius the app
     actually uses, including the avatar-specific sizes, rather than forcing
     close-but-different values (8/9/10/11/12/14px) into fewer buckets. ---- */
  --radius-sm: 4px; --radius-md: 6px; --radius-control: 8px; --radius-lg: 10px; --radius-xl: 12px;
  --radius-avatar-sm: 9px; --radius-avatar-md: 11px; --radius-avatar-lg: 14px;
  --radius-pill: 999px;

  /* ---- Icons — the sizes every inline <svg> in the app actually uses,
     named so new icon usage has a real choice to make rather than picking
     an arbitrary number. (SVG width/height attributes can't read CSS custom
     properties, so this is documentation of the scale the JS templates
     follow — see public/js/design-system.js's specialistIconSvg/
     statusIconSvg — rather than an enforced CSS rule.) ---- */
  --icon-sm: 13px; --icon-md: 15px; --icon-lg: 20px;

  /* ---- Tokens the JS templates reference by name ----
     These used to be hex literals inside inline `style="…"` attributes, which
     is why a dark theme was impossible: a token can be redefined per theme, a
     literal baked into a style attribute cannot. See public/js/util.js's TONE
     and AVATARS, features/runs.js's runCardTint, and projects.js's banner
     gradients — every one of them now names one of these. ---- */
  --surface-running: #F8FBFD;
  --border-warn: rgba(166, 91, 11, .3);
  --border-error: rgba(163, 58, 58, .3);
  --placeholder: #626F86;

  --avatar-1-bg: #ECF0F5; --avatar-1-fg: #313F58;
  --avatar-2-bg: #DAEEF8; --avatar-2-fg: #0960A8;
  --avatar-3-bg: #D8E8F5; --avatar-3-fg: #0960A8;
  --avatar-4-bg: #E8F5EE; --avatar-4-fg: #1A7F4B;
  --avatar-5-bg: #F5E4E4; --avatar-5-fg: #8B3A3A;

  --banner-1a: #E9F1FA; --banner-1b: #DCEBF7;
  --banner-2a: #EDE9FE; --banner-2b: #E3DBFB;
  --banner-3a: #E8F5EE; --banner-3b: #DCF0E4;
  --banner-4a: #FFF4E6; --banner-4b: #FCE9D0;

  /* The five specialist hues (see js/design-system.js), which are keyed by
     agent id rather than by selector and so also arrive as inline styles. */
  --spec-blue-bg: #ECF5FA; --spec-blue-fg: #0960A8;
  --spec-purple-bg: #EDE9FE; --spec-purple-fg: #5B3FBF;
  --spec-green-bg: #E8F5EE; --spec-green-fg: #1A7F4B;
  --spec-orange-bg: #FFF4E6; --spec-orange-fg: #A65B0B;
  --spec-red-bg: #FBECEC; --spec-red-fg: #A33A3A;
}

/* ---------------------------------------------------------------------------
   The dark palette.

   Modelled on Conductor, which this app's whole layout now follows: a
   near-black ground rather than a dark grey, surfaces that separate by a small
   lift in lightness instead of by a border, and borders that are a faint light
   wash rather than a dark line. The brand blue is lifted well past its light
   value — #0960A8 on a near-black ground fails contrast badly — and every
   status tone becomes a deep, desaturated ground with a bright ink on it, so a
   pill still reads as a pill.

   Two blocks, deliberately identical in content: the media query covers the
   default "follow the system" state, and the attribute selector covers an
   explicit choice made in this app. The media query is guarded so that
   choosing light here beats a dark OS.
   --------------------------------------------------------------------------- */
:root[data-theme="dark"] {
  color-scheme: dark;
  /* Ground and surfaces. Conductor separates panes by lightness, not by a
     line, so the ground is the darkest thing and each surface lifts slightly. */
  --app-bg: #07090D;
  --color-surface: #131820;
  --color-surface-alt: #1A212B;
  --color-tint: #14212E;
  --sidebar-grad-top: #0B0E13;
  --sidebar-grad-bottom: #0B0E13;

  /* Text. Not pure white — a slightly cool off-white reads as ink rather than
     as a light source at this contrast. */
  --color-text: #E7EDF5;
  --color-body: #BFCAD8;
  --color-text-muted: #A2AFC1;
  --color-label: #93A1B4;
  --color-meta: #93A1B4;

  /* The brand blue, lifted. #0960A8 on a near-black ground is unreadable; this
     is the same hue at a lightness that works on it. */
  --color-brand-primary: #4EA8F0;
  --color-brand-secondary: #2E7FD1;
  --color-brand-secondary-hover: #4593DE;

  /* Status tones: a deep desaturated ground, a bright ink. */
  --tone-success-bg: #102A1C; --tone-success-fg: #5FCB8B;
  --tone-info-bg: #10243A;    --tone-info-fg: #62B2F0;
  --tone-warn-bg: #2E2110;    --tone-warn-fg: #E0A254;
  --tone-error-bg: #2E1618;   --tone-error-fg: #EE8A8A;
  --tone-neutral-bg: #171D25; --tone-neutral-fg: #93A1B3;

  /* Borders are a light wash at low alpha, never a darker line — a dark line on
     a dark ground disappears. */
  --border-faint: rgba(196, 214, 240, .07);
  --border-subtle: rgba(196, 214, 240, .06);
  --border-default: rgba(196, 214, 240, .11);
  --border-medium: rgba(196, 214, 240, .16);
  --border-strong: rgba(196, 214, 240, .24);
  --border-on-brand: rgba(78, 168, 240, .45);
  --border-warn: rgba(224, 162, 84, .35);
  --border-error: rgba(238, 138, 138, .35);

  /* Shadows do almost nothing on a dark ground; they are kept but pulled back
     so they read as depth rather than as smudge. */
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .5);
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, .55);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, .5);
  --shadow-lg: 0 20px 60px rgba(0, 0, 0, .65);
  --overlay-scrim: rgba(0, 0, 0, .6);
  --surface-veil-hover: rgba(255, 255, 255, .04);
  --surface-veil: rgba(255, 255, 255, .06);

  --surface-running: #0E1B29;
  --placeholder: #8A98AC;

  --avatar-1-bg: #1B2230; --avatar-1-fg: #BFCAD8;
  --avatar-2-bg: #10243A; --avatar-2-fg: #62B2F0;
  --avatar-3-bg: #13283E; --avatar-3-fg: #62B2F0;
  --avatar-4-bg: #102A1C; --avatar-4-fg: #5FCB8B;
  --avatar-5-bg: #2E1618; --avatar-5-fg: #EE8A8A;

  --banner-1a: #12243A; --banner-1b: #0E1B2C;
  --banner-2a: #1E1938; --banner-2b: #17132C;
  --banner-3a: #102A1C; --banner-3b: #0C2016;
  --banner-4a: #2E2110; --banner-4b: #23190C;

  --spec-blue-bg: #10243A;   --spec-blue-fg: #62B2F0;
  --spec-purple-bg: #211A3D; --spec-purple-fg: #A78BFA;
  --spec-green-bg: #102A1C;  --spec-green-fg: #5FCB8B;
  --spec-orange-bg: #2E2110; --spec-orange-fg: #E0A254;
  --spec-red-bg: #2E1618;    --spec-red-fg: #EE8A8A;

  --add-line-bg: #102A1C;
  --del-line-bg: #2E1618;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
  /* Ground and surfaces. Conductor separates panes by lightness, not by a
     line, so the ground is the darkest thing and each surface lifts slightly. */
  --app-bg: #07090D;
  --color-surface: #131820;
  --color-surface-alt: #1A212B;
  --color-tint: #14212E;
  --sidebar-grad-top: #0B0E13;
  --sidebar-grad-bottom: #0B0E13;

  /* Text. Not pure white — a slightly cool off-white reads as ink rather than
     as a light source at this contrast. */
  --color-text: #E7EDF5;
  --color-body: #BFCAD8;
  --color-text-muted: #A2AFC1;
  --color-label: #93A1B4;
  --color-meta: #93A1B4;

  /* The brand blue, lifted. #0960A8 on a near-black ground is unreadable; this
     is the same hue at a lightness that works on it. */
  --color-brand-primary: #4EA8F0;
  --color-brand-secondary: #2E7FD1;
  --color-brand-secondary-hover: #4593DE;

  /* Status tones: a deep desaturated ground, a bright ink. */
  --tone-success-bg: #102A1C; --tone-success-fg: #5FCB8B;
  --tone-info-bg: #10243A;    --tone-info-fg: #62B2F0;
  --tone-warn-bg: #2E2110;    --tone-warn-fg: #E0A254;
  --tone-error-bg: #2E1618;   --tone-error-fg: #EE8A8A;
  --tone-neutral-bg: #171D25; --tone-neutral-fg: #93A1B3;

  /* Borders are a light wash at low alpha, never a darker line — a dark line on
     a dark ground disappears. */
  --border-faint: rgba(196, 214, 240, .07);
  --border-subtle: rgba(196, 214, 240, .06);
  --border-default: rgba(196, 214, 240, .11);
  --border-medium: rgba(196, 214, 240, .16);
  --border-strong: rgba(196, 214, 240, .24);
  --border-on-brand: rgba(78, 168, 240, .45);
  --border-warn: rgba(224, 162, 84, .35);
  --border-error: rgba(238, 138, 138, .35);

  /* Shadows do almost nothing on a dark ground; they are kept but pulled back
     so they read as depth rather than as smudge. */
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .5);
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, .55);
  --shadow-md: 0 4px 12px rgba(0, 0, 0, .5);
  --shadow-lg: 0 20px 60px rgba(0, 0, 0, .65);
  --overlay-scrim: rgba(0, 0, 0, .6);
  --surface-veil-hover: rgba(255, 255, 255, .04);
  --surface-veil: rgba(255, 255, 255, .06);

  --surface-running: #0E1B29;
  --placeholder: #8A98AC;

  --avatar-1-bg: #1B2230; --avatar-1-fg: #BFCAD8;
  --avatar-2-bg: #10243A; --avatar-2-fg: #62B2F0;
  --avatar-3-bg: #13283E; --avatar-3-fg: #62B2F0;
  --avatar-4-bg: #102A1C; --avatar-4-fg: #5FCB8B;
  --avatar-5-bg: #2E1618; --avatar-5-fg: #EE8A8A;

  --banner-1a: #12243A; --banner-1b: #0E1B2C;
  --banner-2a: #1E1938; --banner-2b: #17132C;
  --banner-3a: #102A1C; --banner-3b: #0C2016;
  --banner-4a: #2E2110; --banner-4b: #23190C;

  --spec-blue-bg: #10243A;   --spec-blue-fg: #62B2F0;
  --spec-purple-bg: #211A3D; --spec-purple-fg: #A78BFA;
  --spec-green-bg: #102A1C;  --spec-green-fg: #5FCB8B;
  --spec-orange-bg: #2E2110; --spec-orange-fg: #E0A254;
  --spec-red-bg: #2E1618;    --spec-red-fg: #EE8A8A;

  --add-line-bg: #102A1C;
  --del-line-bg: #2E1618;
  }
}

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--app-bg); }
body {
  font-family: var(--font-body);
  color: var(--color-text);
  -webkit-font-smoothing: antialiased;
}
button, input, textarea, select { font-family: inherit; color: inherit; }
::placeholder { color: var(--placeholder); }

@keyframes ws-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes ws-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes ws-pop { from { opacity: 0; transform: translateY(-8px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes ws-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

/* A real "the agent is still working" signal inside the live transcript
   itself (see thinkingIndicatorHtml in runs.js) — the header's own
   pulse-dot status pill is easy to miss since it sits above the tab, not
   in the conversation; this sits right where someone is actually looking,
   including in the real gap between a tool result landing and the next
   turn starting, which otherwise showed nothing at all. */
@keyframes ws-typing { 0%, 80%, 100% { transform: scale(.6); opacity: .4; } 40% { transform: scale(1); opacity: 1; } }
.thinking-indicator { display: inline-flex; align-items: center; gap: 5px; padding: 10px 2px 2px; }
.thinking-indicator span {
  width: 6px; height: 6px; border-radius: var(--radius-pill); flex: none;
  background: var(--color-brand-primary); animation: ws-typing 1.2s ease-in-out infinite;
}
.thinking-indicator span:nth-child(2) { animation-delay: .15s; }
.thinking-indicator span:nth-child(3) { animation-delay: .3s; }
@media (prefers-reduced-motion: reduce) {
  .thinking-indicator span { animation: none; opacity: .7; }
}
.ask-working-banner {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin-top: 20px; padding: 12px 16px;
  background: var(--surface-veil); border: 1px solid var(--border-subtle); border-radius: var(--radius-control);
}
.ask-working-banner .thinking-indicator { padding: 0; }

.session-usage-bar {
  display: flex; gap: 28px; flex-wrap: wrap;
  padding: 12px 16px; margin-bottom: 14px;
  background: var(--surface-veil); border: 1px solid var(--border-subtle); border-radius: var(--radius-control);
}
#app { height: 100vh; min-height: 600px; display: flex; overflow: hidden; }

/* ---------------------------------------------------------------------------
   The version bar (features/dashboard-version.js)

   A slim strip above everything, present only on the machine the app is being
   built on. It takes real height rather than floating over the dashboard: an
   overlay would sit on top of the sidebar's own top row, and a bar you have to
   scroll under is a bar people stop seeing.
   --------------------------------------------------------------------------- */
:root { --view-mode-bar-h: 32px; }
body:has(#viewModeBar:not([hidden])) #app { height: calc(100vh - var(--view-mode-bar-h)); }

.view-mode-bar {
  height: var(--view-mode-bar-h);
  display: flex; align-items: center; gap: var(--space-sm);
  padding: 0 var(--space-md);
  background: var(--color-surface-alt);
  border-bottom: 1px solid var(--border-default);
  font-size: var(--text-xs); color: var(--color-meta);
  user-select: none;
}
/* Pretending to be the other dashboard is coloured, and deliberately: a
   dashboard behaving as something other than what it is, with nothing on screen
   admitting it, is an afternoon lost to a bug that is not one. */
.view-mode-bar.pretending { background: var(--tone-warn-bg); border-bottom-color: var(--tone-warn-fg); color: var(--tone-warn-fg); }

.view-mode-label { font-weight: 500; letter-spacing: .01em; }

.view-mode-switch {
  display: inline-flex; padding: 2px; gap: 2px;
  background: var(--color-surface); border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
}
.view-mode-option {
  border: 0; background: transparent; cursor: pointer;
  padding: 1px 10px; border-radius: var(--radius-pill);
  font-size: var(--text-xs); color: var(--color-text-muted);
}
.view-mode-option:hover { color: var(--color-text); }
.view-mode-option.active { background: var(--color-brand-primary); color: #FFFFFF; }

/* The sentence saying what the choice actually means. First to go on a narrow
   window — the two buttons carry the meaning on their own, and the note is the
   part that explains rather than the part that works. */
.view-mode-note { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 700px) { .view-mode-note { display: none; } }

/* ---------------------------------------------------------------------------
   Audit leads (features/leads.js) — a list somebody scans, then one report they read.
   --------------------------------------------------------------------------- */
/* Says that a row is showing a shortened description, so a truncated trigger list
   does not read as the whole thing. */
.reg-desc .reg-more { color: var(--color-meta); font-style: italic; }

.lead-stats { margin: -4px 0 20px; }
.lead-list { display: flex; flex-direction: column; gap: 10px; }

/* A card per lead rather than a registry row. A row can render fifty near-identical
   items; a lead is a person, a site, a sentence they wrote, a result and a price,
   and a row can only show those as one grey line. */
.lead-card {
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  background: var(--color-surface); overflow: hidden;
  transition: border-color .14s ease, box-shadow .14s ease;
}
.lead-card:hover { border-color: var(--border-medium); box-shadow: var(--shadow-sm); }
/* A report waiting to be sent is the one state worth marking on the card itself. */
.lead-card.is-ready { border-left: 3px solid var(--tone-success-fg); }
.lead-card.is-busy { border-left: 3px solid var(--tone-warn-fg); }

.lead-card-main { display: flex; align-items: flex-start; gap: 14px; padding: 15px 16px 13px; }
.lead-open {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px;
  border: 0; background: transparent; padding: 0; text-align: left; cursor: pointer; font-family: inherit;
}
.lead-site {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  font-size: var(--text-lg); color: var(--color-text); line-height: 1.25;
}
.lead-who { font-size: var(--text-sm); color: var(--color-text-muted); }
.lead-who strong { color: var(--color-body); font-weight: 500; }
/* The address on its own line only when a name is shown too — otherwise the name
   line already is the address. */
.lead-email { display: block; font-size: var(--text-xs); color: var(--color-meta); margin-top: 1px; }
.lead-said {
  font-size: var(--text-sm); color: var(--color-body); line-height: 1.5;
  border-left: 2px solid var(--border-medium); padding-left: 10px; margin-top: 3px; max-width: 78ch;
}
.lead-card-side { flex: none; display: flex; align-items: center; gap: 7px; }
.lead-del {
  border: 0; background: transparent; color: var(--color-meta); cursor: pointer;
  padding: 5px; border-radius: var(--radius-sm); display: flex; opacity: 0; transition: opacity .12s ease;
}
.lead-card:hover .lead-del, .lead-card:focus-within .lead-del { opacity: 1; }
/* The table row's own version of the same reveal — `.lead-del` is shared
   between the (now unused) card and the list's table rows, and a `<tr>`
   never matches `.lead-card:hover`, which without this left the delete
   button permanently at opacity 0 inside the table. */
.lead-table tbody tr:hover .lead-del, .lead-table tbody tr:focus-within .lead-del { opacity: 1; }
.lead-del:hover { color: var(--tone-error-fg); background: var(--tone-error-bg); }
.lead-row-actions { display: flex; align-items: center; gap: 7px; flex-wrap: nowrap; }
/* Without this, a flex child narrower than its neighbours (a fixed-width
   button whose own content is short, like "Resend" next to "Send email")
   is exactly the one the default flex-shrink shrinks first once the row
   runs short on space — silently undoing a fixed width set to stop that. */
.lead-row-actions > * { flex-shrink: 0; }

/* Fixed column widths (set via <colgroup>, leads.js) so a row with a long
   name or two action buttons never pushes every column after it out of line
   with the row above and below it — the failure mode of the browser's
   default auto layout, which resizes each column to its widest cell. Top
   alignment for the same reason: rows are genuinely different heights (one
   line of site name vs. site name + fit pill, one line of contact vs. name +
   company + email), and middle-aligning that mismatch is what made pills and
   buttons drift up and down against their neighbours. min-width plus the
   existing `.data-table-wrap` scroll — not a squeezed, illegible table — is
   this app's answer to a narrow window, same as the board's card table. */
.lead-table { table-layout: fixed; width: auto; min-width: 1094px; border-collapse: separate; border-spacing: 0; }
.lead-table th, .lead-table td { vertical-align: top; }
/* flex:1 + min-width:0 rather than width:100% now that the company avatar
   sits beside this button in the same flex row — min-width:0 is what lets
   the ellipsis on the site-name div below actually engage inside a flex
   child, which a bare width:100% does not guarantee. */
.lead-table .lead-open { flex: 1; min-width: 0; }
.lead-table .lead-open > div:first-child,
.lead-table td > div > strong,
.lead-table td.mono-cell {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lead-table td:nth-child(3) > div { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lead-table .lead-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A pill's default `.pill` rule wraps like ordinary text — fine at the widths
   it is used at everywhere else, but "Usage limit reached" wrapping to two
   lines inside a ~100px status cell was the most visible defect in this
   table. One line, always — and, since "Profile generated 23h ago" turned
   out to be the actual longest label the column holds (wider than the
   column itself, at the width this rule alone left it), clipped with an
   ellipsis rather than left to run past the cell's own edge into Tier's
   text right next to it, which is what a bare `nowrap` with nothing to stop
   it did. */
.lead-table .pill { white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
/* The last-attempt warning next to the Status pill — small and quiet until
   actually hovered, the same "explain, don't clutter" reasoning the pill
   itself already follows for its own tooltip. */
.lead-generation-warning { display: inline-block; margin-left: 5px; color: var(--tone-warn-fg); cursor: help; font-size: var(--text-xs); }
/* A sortable column header — same look as a plain `<th>` label until
   hovered or active, so the three that are clickable (Status/BANT/Time)
   don't read as different from Site/Contact/Tier, which aren't. */
.lead-sort-header { all: unset; cursor: pointer; font: inherit; color: inherit; text-transform: inherit; letter-spacing: inherit; }
.lead-sort-header:hover, .lead-sort-header[aria-pressed="true"] { color: var(--color-text); }
/* "Resend" and "Send email" are the same button in two states, but at
   different widths — left as-is, "Read" next to it lands at a different x
   on every other row depending on which label a given lead happens to be
   in. Fixed to the wider label's width so "Read" is the one thing in this
   column that never moves. */
.lead-send-btn { width: 100px; }

/* Where the audit is accounted for: how it arrived, and what it cost. Separated by
   a rule because it is evidence about the lead, not part of it. */
.lead-card-foot {
  display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  padding: 9px 16px; border-top: 1px solid var(--border-faint);
  background: var(--color-surface-alt);
  font-size: var(--text-xs); color: var(--color-meta);
}
.lead-facts { font-variant-numeric: tabular-nums; }
.lead-quote {
  margin: 0 0 16px; padding: 11px 15px; border-left: 3px solid var(--color-brand-primary);
  background: var(--color-surface-alt); font-size: var(--text-base); color: var(--color-body);
  max-width: 74ch;
}
/* The report in an iframe, deliberately. It is a finished branded document with its
   own palette and type scale; letting it inherit the dashboard's stylesheet would
   show something different from the PDF the prospect receives. */
.lead-report {
  width: 100%; height: 74vh; min-height: 520px; border: 1px solid var(--color-border);
  border-radius: var(--radius-lg); background: #fff;
}

/* What ran (features/leads.js leadLogHtml) — two phases, because an audit really is
   two: a browser this app drives, then an agent reading what it found. The phases are
   panels rather than one flat list so the free half and the paid half are visibly
   different things, which is what makes the cost figure make sense. */
/* A sentence under a figure in a .stat tile. Not .stat-label, which is an uppercase
   mono caption meant for one word — a breakdown set in it comes out shouting. */
.stat-note { font-size: var(--text-xs); color: var(--color-text-muted); line-height: 1.45; }

.audit-log { display: flex; flex-direction: column; gap: 14px; }
/* The log's own bar: what this panel shows, and the file that shows more. Wraps, so the
   sentence never squeezes the button off the row (a lesson from the modal whose Delete
   ended up 64px outside it). */
.audit-log-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
}
.audit-log-bar .meta-text { margin: 0; max-width: 78ch; flex: 1 1 340px; }
.audit-log-phase {
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  background: var(--color-surface); padding: 14px 16px;
}
.audit-log-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; margin-bottom: 10px;
}
.audit-log-line { display: flex; gap: 9px; align-items: baseline; padding: 5px 0; font-size: var(--text-sm); }
.audit-log-mark { flex: none; font-family: var(--font-mono); font-size: var(--text-xs); }
.audit-log-detail { display: block; color: var(--color-text-muted); font-size: var(--text-xs); margin-top: 2px; }
/* Scrolls on its own. A real transcript is hundreds of lines, and letting it push the
   page height around while an agent writes means the thing you are reading moves. */
.audit-transcript { max-height: 60vh; overflow-y: auto; }

/* The feedback thread (leadFeedbackHtml). Sided like a conversation, but quietly —
   what is being read here is a document review, not a chat app. */
.lead-thread { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.lead-turn {
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  padding: 11px 14px; background: var(--color-surface); max-width: 74ch;
}
.lead-turn.is-mine { background: var(--color-surface-alt); border-color: transparent; align-self: flex-end; }
.lead-turn-head { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; font-size: var(--text-sm); }
.lead-turn p { margin: 0; font-size: var(--text-sm); line-height: 1.6; white-space: pre-wrap; }

/* ---------------------------------------------------------------------------
   The out-of-step banner (features/out-of-step.js) — above the whole app, because
   the screen underneath is not what is wrong.
   --------------------------------------------------------------------------- */
.stale-banner {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 14px 9px 16px;
  background: var(--tone-warn-bg);
  border-bottom: 1px solid var(--tone-warn-fg);
  font-size: var(--text-sm); line-height: 1.45;
}
.stale-banner > svg { flex: none; color: var(--tone-warn-fg); }
.stale-text { flex: 1; min-width: 0; color: var(--color-body); }
.stale-text strong { color: var(--color-text); margin-right: 4px; }
.stale-text code {
  font-family: var(--font-mono); font-size: var(--text-xs);
  padding: 1px 5px; border-radius: var(--radius-sm); background: var(--color-surface);
}
.stale-dismiss {
  flex: none; border: 0; background: transparent; color: var(--color-text-muted);
  cursor: pointer; padding: 5px; border-radius: var(--radius-sm); display: flex;
}
.stale-dismiss:hover { background: var(--color-surface); color: var(--color-text); }
/* The banner is prepended to <body>, so the app below it gives up the height
   rather than being pushed off the bottom of the window. Scoped to the state
   where a banner is actually rendered — #app keeps its own 100vh otherwise. */
body:has(#outOfStepBanner .stale-banner) { display: flex; flex-direction: column; height: 100vh; }
body:has(#outOfStepBanner .stale-banner) #app { height: auto; flex: 1; min-height: 0; }

/* ---- Sidebar ---- */
#sidebar {
  width: 248px;
  flex: none;
  display: flex;
  flex-direction: column;
  padding: 38px 12px var(--space-xs);
  background: linear-gradient(180deg, var(--sidebar-grad-top) 0%, var(--sidebar-grad-bottom) 100%);
  -webkit-app-region: drag;
}
.sidebar-search {
  flex: none; display: flex; align-items: center; gap: var(--space-2xs);
  height: 34px; padding: 0 var(--space-xs); margin-bottom: 16px;
  background: var(--surface-veil); border: 1px solid var(--border-subtle); border-radius: var(--radius-control);
  -webkit-app-region: no-drag;
}
.sidebar-search:focus-within { background: var(--color-surface); }
.sidebar-search svg { flex: none; color: #6B7A90; }
.sidebar-search input {
  flex: 1; min-width: 0; border: 0; background: transparent; font-size: var(--text-sm); color: var(--color-text);
}
.sidebar-search input:focus { outline: none; }
.sidebar-search kbd {
  flex: none; font: inherit; font-size: var(--text-xs); color: var(--color-meta); background: none;
}
.nav-label {
  padding: 0 var(--space-xs); margin: 2px 0 6px; font-size: var(--text-2xs); letter-spacing: .08em;
  text-transform: uppercase; color: var(--color-meta); flex: none;
}
.nav-group { display: flex; flex-direction: column; gap: 1px; flex: 1; min-height: 0; }
.nav-item {
  display: flex; align-items: center; gap: var(--space-xs);
  height: 32px; padding: 0 var(--space-xs); border: 0; border-radius: var(--radius-control);
  cursor: pointer; font-size: var(--text-base); text-align: left;
  color: var(--color-text-muted); background: transparent;
  -webkit-app-region: no-drag;
}
.nav-item:hover { background: var(--surface-veil-hover); }
.nav-item.active { background: var(--color-surface); color: var(--color-brand-secondary); font-weight: 500; box-shadow: var(--shadow-xs); }
.nav-item svg { flex: none; opacity: .85; }
.nav-item .nav-badge {
  flex: none; min-width: 19px; height: 18px; padding: 0 6px; border-radius: var(--radius-pill);
  background: var(--tone-info-bg); color: var(--tone-info-fg); font-size: var(--text-2xs);
  display: flex; align-items: center; justify-content: center;
}
/* One button, full width — see the note in index.html. */
.sidebar-footer {
  flex: none; display: flex; align-items: center; gap: var(--space-xs); width: 100%;
  margin-top: 6px; padding: var(--space-xs); border: 0; border-top: 1px solid var(--border-faint);
  background: transparent; font-family: inherit; text-align: left; cursor: pointer;
  border-radius: var(--radius-md);
  -webkit-app-region: no-drag;
}
.sidebar-footer:hover { background: var(--surface-veil-hover); }
.sidebar-footer:focus-visible { outline: 2px solid var(--color-brand-primary); outline-offset: -2px; }
.sidebar-footer .footer-chev { flex: none; color: var(--color-brand-primary); }
.sidebar-footer .footer-mark {
  width: 28px; height: 28px; flex: none; border-radius: var(--radius-pill);
  background: var(--color-brand-secondary); color: #fff; font-size: var(--text-xs);
  display: flex; align-items: center; justify-content: center;
}
.sidebar-footer .footer-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* Both lines used to be short, fixed app text ("Automation Agents", a
   version number) — now a real person's name and a real workspace's name,
   either of which can run long enough to need the same truncation every
   other identity label in this app already gets. */
.sidebar-footer .footer-text .name {
  font-size: var(--text-sm); color: var(--color-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-footer .footer-text .sub {
  font-size: var(--text-2xs); color: var(--color-meta);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- Main panel ---- */
#main {
  flex: 1; min-width: 0; margin: 8px 8px 8px 0;
  background: var(--color-surface); border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  overflow: auto; position: relative;
}
/* ---- Page width ----
   Two widths, chosen by what the content actually is — this replaced seven
   different inline values (640, 720, 820, 900, 1060 and this rule's own 1120),
   which is why every screen looked like it had been measured separately.

   The default is full width. Nearly every screen in this app is *records* —
   registry rows, a card table, settings rows, a run list — and a row is scanned
   across, not read as prose, so capping it leaves empty space beside a table that
   wanted the room. The page's own 52px gutters do the containing.

   `.view--form` is the exception: a column of fields filled in order reads and
   fills better narrow, and a form stretched to 1600px is genuinely worse. Prose
   inside any view is held to its own measure by `p { max-width }` rather than by
   the page, so a paragraph never runs the full width even here. */
.view { padding: var(--space-xl) 52px 56px; animation: ws-in .25s ease; }
.view--form { max-width: 720px; }
/* Body copy keeps a readable measure regardless of how wide the page is. */
.view .subtitle, .view > p, .view--form p { max-width: 74ch; }
.view h1 { margin: 0 0 8px; font-weight: 300; font-size: var(--text-4xl); line-height: 1.15; letter-spacing: -.5px; }
.view .subtitle { margin: 0; font-size: var(--text-lg); color: var(--color-text-muted); }
.view-header-row { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-md); flex-wrap: wrap; margin-bottom: var(--space-lg); }
/* A section's own name — "Custom Agents", "Dev", "Available Themes" — is
   load-bearing hierarchy, not a hint, so it gets real contrast and weight
   rather than the same faint gray a field's helper text uses. */
.eyebrow { font-size: var(--text-md); font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--color-text-muted); margin-bottom: 9px; }

/* ---- Buttons ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2xs);
  height: 42px; padding: 0 18px; border: 1px solid transparent; border-radius: var(--radius-md);
  font-size: var(--text-md); cursor: pointer; white-space: nowrap;
  transition: background-color .15s ease, border-color .15s ease;
  /* Some buttons here are genuinely links — a PDF download, a report opened in a new
     tab — and an anchor carries the browser's underline. A button that looks like a
     button must not be underlined just because of the element it happens to be, so
     the two are stated here rather than at each call site. */
  text-decoration: none; font-family: inherit;
}
.btn:hover { text-decoration: none; }
.btn-primary { background: var(--color-brand-secondary); color: #fff; }
.btn-primary:hover { background: var(--color-brand-secondary-hover); }
.btn-secondary { background: var(--color-surface); border-color: var(--border-strong); color: var(--color-text); }
.btn-secondary:hover { background: var(--color-surface-alt); }
.btn-sm { height: 36px; padding: 0 14px; font-size: var(--text-base); }
.btn:disabled { opacity: .55; cursor: not-allowed; }

/* ---- Cards — one base every card-like surface (agent/specialist cards,
   modals, run cards) extends conceptually, so "a card" means one consistent
   radius + border + hover-elevation contract, not independent definitions
   that happen to agree. ---- */
.card-surface {
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg);
}

.agent-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 20px; }
.agent-card {
  text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  padding: var(--space-md); background: var(--color-surface); cursor: pointer;
  display: flex; flex-direction: column; gap: 14px;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.agent-card:hover { border-color: var(--border-on-brand); box-shadow: var(--shadow-md); }
.section-card:hover { border-color: var(--border-on-brand); box-shadow: var(--shadow-md); }

/* A project card's decorative top banner — purely chrome (no live
   screenshot exists to show), seeded per-project so cards are visually
   distinct in a grid, with a plain caption rather than implying a real
   preview. */
.project-card-banner {
  height: 132px; border-radius: var(--radius-lg) var(--radius-lg) 0 0; position: relative; flex: none;
}
.project-card-banner-label {
  position: absolute; left: 10px; bottom: 10px; font-size: var(--text-2xs); padding: 3px 10px;
  border-radius: var(--radius-pill); background: var(--surface-veil); color: var(--color-text-muted);
}
.agent-avatar {
  width: 44px; height: 44px; border-radius: var(--radius-avatar-md); flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--text-lg); letter-spacing: .02em;
}
.agent-avatar.lg { width: 60px; height: 60px; border-radius: var(--radius-avatar-lg); font-size: 20px; }
.agent-avatar.sm { width: 22px; height: 22px; border-radius: var(--radius-avatar-sm); font-size: var(--text-xs); font-weight: 600; }
.agent-card .name { font-size: 18.5px; font-weight: 500; line-height: 1.3; }
.agent-card .blurb { font-size: var(--text-md); color: var(--color-text-muted); line-height: 1.55; flex: 1; }
.agent-card .slug { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--text-sm); color: #7E8CA0; }
.card-footer {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs);
  padding-top: 12px; border-top: 1px solid var(--border-faint);
}
/* The agent/skill/tool card's own footer — bumped a step past the base
   status-inline/meta-text sizes (still used at their original, deliberately
   quieter size everywhere else, like a project card's own footer) since a
   card this size reads as cramped at the smallest text in the whole system. */
.agent-card .card-footer .status-inline,
.agent-card .card-footer .meta-text { font-size: var(--text-base); }
.status-inline { display: flex; align-items: center; gap: 7px; font-size: var(--text-sm); }
.status-dot { width: 7px; height: 7px; border-radius: var(--radius-pill); flex: none; }
.meta-text { font-size: var(--text-xs); color: var(--color-meta); }

/* ---- The Agents screen's own tile (features/bots.js) — a big media slot
   (a real uploaded image, or the agent's identity icon tinted with its own
   color) beside its name, access pill, description and real capability
   checklist. A separate shape from .agent-card (still used by Skills/Tools'
   own cards) rather than a variant of it, since this one has a media slot
   the others never will. Every color here comes from an existing token, so
   it reads correctly in light and dark without its own overrides. ---- */
.specialist-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 20px; }
.specialist-tile {
  display: flex; gap: 16px; align-items: flex-start; text-align: left; font-family: inherit;
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  padding: 20px; background: var(--color-surface); cursor: pointer;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.specialist-tile:hover { border-color: var(--border-on-brand); box-shadow: var(--shadow-md); }
.specialist-tile-media {
  flex: none; width: 56px; height: 56px; border-radius: var(--radius-lg);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.specialist-tile-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The one visual break between the icon and everything it's describing —
   a full-height rule (align-self: stretch lets it match whichever of the
   two flex siblings ends up taller) rather than one scoped to the icon's
   own small square. */
.specialist-tile-divider { flex: none; align-self: stretch; width: 1px; background: var(--border-faint); }
.specialist-tile-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.specialist-tile-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.specialist-tile-name {
  font-size: 15px; font-weight: 600; line-height: 1.3; color: var(--color-text);
  text-transform: uppercase; letter-spacing: .03em;
}
.specialist-tile-desc {
  margin: 0; font-size: var(--text-sm); color: var(--color-text-muted); line-height: 1.5;
  /* Same real length cap for every card (see truncateText's own 90-char
     limit) plus a hard 2-line clamp, so a short description and a long
     one still leave every card in a row the same shape. */
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.specialist-tile-cap-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.specialist-tile-cap-list li { display: flex; align-items: center; gap: 8px; font-size: var(--text-sm); color: var(--color-text-muted); }
.specialist-tile-cap-list svg { flex: none; color: var(--tone-success-fg); }
.specialist-tile-foot {
  display: flex; align-items: center; gap: 12px; margin-top: 4px; padding-top: 10px;
  border-top: 1px solid var(--border-faint);
}

/* The account's plan usage window — see claude-status.js's currentSessionUsageHtml. */
.usage-progress { width: 100%; height: 6px; border-radius: var(--radius-pill); background: var(--surface-veil); overflow: hidden; }
.usage-progress-fill { height: 100%; border-radius: var(--radius-pill); background: var(--color-brand-primary); }

/* ---- Status indicators (pills/tags) — label + icon + color together;
   see public/js/design-system.js for the status-vocabulary/specialist-
   identity data these render, so a status or specialist never appears as
   color alone anywhere in the app. ---- */
.pill { display: inline-flex; align-items: center; gap: var(--space-2xs); font-size: var(--text-base); padding: 5px 13px; border-radius: var(--radius-pill); }
.pill-sm { font-size: var(--text-xs); padding: 3px 11px; }
.tag { height: 32px; display: inline-flex; align-items: center; padding: 0 14px; border-radius: var(--radius-pill); background: var(--color-tint); color: #0B4F86; font-size: var(--text-base); }
/* Flat metadata chips (a project card's "Gutenberg · n Pages" row) — a
   deliberately different shape from .pill/.tag's fully-rounded look, so a
   card's own real metadata reads as plain fact tags, not a status pill. */
.tag-chip { display: inline-flex; align-items: center; font-size: var(--text-sm); padding: 4px 9px; border-radius: 4px; background: var(--color-surface-alt); color: var(--color-text-muted); }

/* Where a design's picture actually is (features/design.js). The same shape as
   .tag-chip beside it, because it sits in the same row and a second shape there
   would read as a second kind of thing — only the colour differs, and only far
   enough to make 'Failed' findable while scrolling. Every colour is an existing
   tone token, so it reads correctly in light and dark with no override. */
.storage-chip {
  display: inline-flex; align-items: center; font-size: var(--text-sm);
  padding: 4px 9px; border-radius: 4px; font-weight: 500;
  background: var(--tone-neutral-bg); color: var(--tone-neutral-fg);
}
.storage-chip-success { background: var(--tone-success-bg); color: var(--tone-success-fg); }
.storage-chip-info    { background: var(--tone-info-bg);    color: var(--tone-info-fg); }
.storage-chip-warn    { background: var(--tone-warn-bg);    color: var(--tone-warn-fg); }
.storage-chip-error   { background: var(--tone-error-bg);   color: var(--tone-error-fg); }
.storage-chip-neutral { background: var(--tone-neutral-bg); color: var(--tone-neutral-fg); }
/* The storage word is also a button — it answers "is this actually safe?" by
   opening the design's own storage panel — so it needs the affordance without
   becoming a second kind of thing beside the plain chips it sits with. */
button.storage-chip { border: 0; cursor: pointer; font-family: inherit; }
button.storage-chip:hover { filter: brightness(1.06); text-decoration: underline; }

/* ---------------------------------------------------------------------------
   The Design library (features/design.js and its four companions)

   One rule holds this whole screen together: the screenshot is the content and
   everything else is a label on it. That is why the card is a picture with a
   caption rather than a metadata block with a thumbnail, and why the controls
   are pinned rather than scrolling away from a list two thousand designs long.
   --------------------------------------------------------------------------- */

/* Search · Filters · Sort · Grid/List, and the tabs above them, stay put while
   the library scrolls underneath. `#main` is what actually scrolls in this app
   (see its own rule above), so `top: 0` sticks to the top of the reading area.
   The negative side margins with matching padding let the background reach the
   page's own 52px gutters — without them the sticky block would sit in a
   channel with designs visible sliding past on either side. */
/* ---- What sits on top of what ----
   Four layers, and the order matters because three of them overlap by design:

     26  the sticky toolbar        pinned, and must stay readable over the cards
     25  the bulk-action bar      pinned, same
     20  a card's ••• menu        over its neighbours, under both pinned bars
      2  a card's checkbox        over its own picture, and nothing else

   The checkbox was the bug: at a higher number than the bulk bar it painted
   *through* it, so a row of ticks floated over the words "60 selected". A
   checkbox never needs to be above anything but the picture it sits on. */
.design-sticky {
  position: sticky; top: 0; z-index: 26;
  margin: 0 -52px var(--space-md); padding: 10px 52px 2px;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}
.design-sticky .filter-tabs { margin-bottom: 10px; }
.design-tabs { overflow-x: auto; scrollbar-width: thin; }
.design-summary { margin: -4px 0 14px; }

.design-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding-bottom: 10px; }
.design-toolbar-spacer { flex: 1 1 auto; }
.design-search { width: 260px; max-width: 100%; }
.design-sort { width: auto; }
/* The Filters button while the panel is open — pressed, not merely hovered. */
.design-toolbar .btn.on { background: var(--color-surface-alt); border-color: var(--border-strong); }
.design-toolbar .btn .count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; margin-left: 6px; padding: 0 5px;
  border-radius: var(--radius-pill); font-size: var(--text-sm);
  background: var(--color-brand-secondary); color: #fff;
}

/* What is narrowing the screen, in words, each with its own way off. A filtered
   library looks exactly like a small library, so this is not decoration — it is
   the only thing on screen that says why there are twelve designs. */
.design-active-filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-bottom: 10px; }
.design-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--text-sm); font-family: inherit; padding: 4px 10px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  background: var(--color-surface-alt); color: var(--color-text); cursor: pointer;
}
.design-chip:hover { border-color: var(--tone-error-fg); color: var(--tone-error-fg); }

/* Capped and scrolled, not left to grow. The panel lives inside the sticky
   block, so a library with forty tags would otherwise make the pinned area
   taller than the window and leave the designs themselves unreachable — the
   filter would have eaten the thing it filters. */
.design-filter-panel {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 18px 24px; padding: 16px 0 14px; border-top: 1px solid var(--color-border);
  max-height: min(42vh, 380px); overflow-y: auto;
}
.design-filter-group { min-width: 0; }
.design-filter-option {
  display: flex; align-items: center; gap: 8px; padding: 3px 0;
  font-size: var(--text-base); color: var(--color-text-muted); cursor: pointer;
}
.design-filter-option:hover { color: var(--color-text); }
.design-filter-option.on { color: var(--color-text); }
.design-filter-option-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.design-filter-more { display: block; margin-top: 6px; }

/* ---- The card ----
   `minmax(0, 1fr)` rather than a fixed minimum on the track so a narrow window
   still gets whole cards instead of one clipped column. */
.design-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(clamp(240px, 24vw, 320px), 1fr)); gap: 18px; }
.design-card {
  position: relative; display: flex; flex-direction: column;
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  background: var(--color-surface); overflow: visible;
}
.design-card.selected { border-color: var(--color-brand-secondary); box-shadow: 0 0 0 1px var(--color-brand-secondary); }

/* The picture takes the whole top of the card and is the thing you click. 16/10
   because that is roughly the shape a section screenshot actually is; `cover` so
   a mixed set of sizes still lines up as a grid rather than as a ragged wall. */
.design-card-image {
  position: relative; aspect-ratio: 16 / 10; overflow: hidden; cursor: pointer;
  background: var(--color-surface-alt);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
/* `color: transparent` so the alt text never flashes as raw words across the
   card in the moment between a picture failing to load and `onerror` swapping in
   the proper "No picture downloaded" line. The alt text itself stays, for the
   screen readers that are the reason it is written. */
.design-card-image img { width: 100%; height: 100%; object-fit: cover; display: block; color: transparent; }
.design-card-image:focus-visible { outline: 2px solid var(--color-brand-primary); outline-offset: -2px; }
.design-card-nopicture { display: none; position: absolute; inset: 0; align-items: center; justify-content: center; text-align: center; padding: 0 14px; }
.design-card-image.missing img { display: none; }
.design-card-image.missing .design-card-nopicture { display: flex; }

/* The heart and the storage word sit *on* the picture, bottom corners, over a
   gradient rather than a solid bar — a solid bar would crop the design, which is
   the one thing on the card that must not be covered. */
.design-card-overlay {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 20px 10px 8px;
  background: linear-gradient(to top, rgba(0, 0, 0, .55), rgba(0, 0, 0, 0));
}
.design-heart {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex: none; padding: 0;
  border: 0; border-radius: var(--radius-pill); cursor: pointer;
  background: rgba(255, 255, 255, .9); color: #6B7280;
  font-size: 15px; line-height: 1;
}
.design-heart:hover { background: #fff; color: var(--tone-error-fg); }
.design-heart.on { color: var(--tone-error-fg); }
.design-heart:disabled { opacity: .6; cursor: default; }
/* In a list row there is no picture behind it, so it needs no plate of its own. */
.design-row .design-heart { background: transparent; }
.design-row .design-heart:hover { background: var(--color-surface-alt); }

/* Straight out to the design's own live page. Built to the heart's shape
   deliberately — they are the two one-press acts on a card, and a pair that
   matched in weight but not in size would read as one button and one
   afterthought. */
.design-visit {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex: none; padding: 0;
  border: 0; border-radius: var(--radius-pill); cursor: pointer;
  background: rgba(255, 255, 255, .9); color: #6B7280;
  font-size: 14px; line-height: 1; font-family: inherit;
}
.design-visit:hover { background: #fff; color: var(--color-brand-primary); }
.design-row .design-visit { background: transparent; }
.design-row .design-visit:hover { background: var(--color-surface-alt); color: var(--color-brand-primary); }

.design-card-body { position: relative; padding: 12px 14px 10px; display: flex; flex-direction: column; gap: 6px; }
.design-card-title {
  font-size: var(--text-base); color: var(--color-text); line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.design-card-domain { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The domain under the title, when there is a real site behind it. Styled as
   the quiet line it already was rather than as a blue link — it is a label
   first, and the arrow is what says it can be pressed. */
.design-visit-link {
  display: block; width: 100%; text-align: left; border: 0; background: none;
  padding: 0; cursor: pointer; font-family: inherit; font-size: inherit;
}
.design-visit-link:hover { color: var(--color-brand-primary); text-decoration: underline; }
.design-card-tags { margin: 2px 0 0; }
.design-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 24px; }
.design-card-more {
  flex: none; border: 0; background: none; cursor: pointer; padding: 2px 6px;
  color: var(--color-text-muted); font-size: var(--text-md); letter-spacing: 1px; line-height: 1;
  border-radius: var(--radius-sm);
}
.design-card-more:hover { background: var(--color-surface-alt); color: var(--color-text); }

/* The ••• menu. Anchored to the card and above its neighbours — a menu that
   opened *inside* the card's overflow would be clipped by the card it belongs
   to, which is why .design-card is `overflow: visible`. */
.design-card-menu {
  position: absolute; right: 10px; bottom: 34px; z-index: 20; min-width: 210px;
  display: flex; flex-direction: column; padding: 5px;
  background: var(--color-surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-control); box-shadow: var(--shadow-md);
}
.design-card-menu button {
  text-align: left; border: 0; background: none; cursor: pointer;
  padding: 7px 9px; border-radius: var(--radius-sm);
  font-size: var(--text-base); font-family: inherit; color: var(--color-text);
}
.design-card-menu button:hover { background: var(--color-surface-alt); }
.design-card-menu button.danger { color: var(--tone-error-fg); }
.design-list .design-card-menu { right: var(--space-md); bottom: auto; top: 44px; }

/* The checkbox appears on hover, and stays once anything is selected — nobody
   should have to find a mode switch to start selecting, or to stop. It is always
   there for the keyboard, only hidden to the eye. */
.design-card-select { position: absolute; top: 10px; left: 10px; z-index: 2; opacity: 0; transition: opacity .12s ease; }
.design-card:hover .design-card-select,
.design-card.selecting .design-card-select,
.design-card-select:focus-within { opacity: 1; }
.design-card-select input { width: 17px; height: 17px; cursor: pointer; accent-color: var(--color-brand-secondary); }

/* ---- The same designs as a list ---- */
.design-list .list-row { position: relative; }
.design-row.selected { background: var(--color-surface-alt); }
.design-row-open {
  flex: 1; display: flex; align-items: center; gap: 12px; min-width: 0;
  border: 0; background: none; cursor: pointer; padding: 0; font-family: inherit; text-align: left;
}
.design-row-thumb {
  width: 62px; height: 40px; flex: none; border-radius: var(--radius-sm);
  overflow: hidden; background: var(--color-surface-alt);
}
.design-row-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.design-row-title { display: block; font-size: var(--text-base); color: var(--color-text); }
.design-row-tags { flex: 1.4; margin: 0; min-width: 0; overflow: hidden; }
.design-row-tags .design-card-tags { margin: 0; }

/* ---- The same designs, grouped under the site they came from ----
   The heading is the site's name and the way to it, so it is a real button
   rather than a label with a link beside it. Underlined only on hover, because
   a page of these underlined at rest reads as a wall of links rather than as
   the headings they are. */
/* How many websites are under the headings, and how many of them this page got
   to. Sits above the first heading rather than in the toolbar, because it is a
   fact about the list below it and moves out of the way with it. */
.design-groups-count { margin: 0 0 14px; }
.design-groups { display: flex; flex-direction: column; gap: 26px; }
.design-group { display: flex; flex-direction: column; gap: 10px; }
.design-group-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-bottom: 7px; border-bottom: 1px solid var(--color-border);
}
.design-group-site {
  border: 0; background: none; padding: 0; cursor: pointer; font-family: inherit;
  font-size: var(--text-md); font-weight: 600; color: var(--color-text);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.design-group-site:hover { color: var(--color-brand-primary); text-decoration: underline; }
.design-group-site.none { cursor: default; color: var(--color-text-muted); font-weight: 500; }

/* A tag somebody here typed, marked apart from the gallery's own — one of the
   two lists is rewritten by the next import and the other is not. */
.tag-chip-mine { background: var(--tone-info-bg); color: var(--tone-info-fg); gap: 4px; }
.tag-chip-more { border: 0; font-family: inherit; cursor: pointer; }
.tag-chip-more:hover { background: var(--border-strong); color: var(--color-text); }
.tag-chip-x { border: 0; background: none; cursor: pointer; color: inherit; padding: 0 0 0 2px; font-size: var(--text-md); line-height: 1; opacity: .7; }
.tag-chip-x:hover { opacity: 1; }

/* A row in the "Add to collection" picker: the name is the button that adds,
   and Rename/Delete sit beside it rather than inside it — a nested button is
   invalid markup and, worse, a Delete that also adds. */
.design-collection-pick {
  flex: 1; display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-width: 0; border: 0; background: none; cursor: pointer; padding: 0; font-family: inherit; text-align: left;
}
.design-collection-pick:hover { text-decoration: none; }

/* ---- Paging ---- */
.design-more { display: flex; justify-content: center; padding: 24px 0 8px; }

/* ---- What the last action did ---- */
.design-notice {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  margin: 0 0 var(--space-md); padding: 10px 14px;
  border: 1px solid var(--color-border); border-radius: var(--radius-control);
  background: var(--tone-info-bg); color: var(--tone-info-fg); font-size: var(--text-base);
}
.design-notice.error { background: var(--tone-error-bg); color: var(--tone-error-fg); }

/* ---- The bulk bar ----
   Pinned to the bottom of the reading area, because a bar at the top of a list
   of four hundred is a bar nobody can reach at the moment they want it. */
.design-bulk-bar {
  position: sticky; bottom: 0; z-index: 25;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: var(--space-md) -52px 0; padding: 12px 52px;
  background: var(--color-surface); border-top: 1px solid var(--border-strong);
  box-shadow: 0 -6px 18px rgba(17, 36, 70, .06);
}
.design-bulk-count { font-size: var(--text-base); color: var(--color-text); font-weight: 500; flex: none; }
.design-bulk-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.design-bulk-tail { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-left: auto; }


/* ---- The upload dashboard ----
   How much of the library is in Firebase, how much is not, and the button that
   closes the gap. Above the tabs rather than inside the sticky strip: it is
   read once on the way in and watched during a run, not consulted per scroll,
   and pinning it would cost a line of the gallery on every screen for ever. */
.design-upload-dashboard {
  margin: 0 0 var(--space-md); padding: 14px 18px 10px;
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  background: var(--color-surface-alt);
}
.design-upload-head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.design-upload-title { font-size: var(--text-base); font-weight: 500; color: var(--color-text); }
.design-upload-tiles { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-left: auto; }
/* Tabular figures so a count ticking up during an upload does not shuffle
   everything beside it left and right on every batch. */
.design-upload-tile {
  font-size: var(--text-md); color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}
.design-upload-tile strong { font-size: var(--text-base); font-weight: 600; color: var(--color-text); }
.design-upload-tile.synced strong { color: var(--tone-success-fg); }
.design-upload-tile.remaining strong { color: var(--tone-info-fg); }
.design-upload-tile.failed strong { color: var(--tone-error-fg); }
.design-upload-tile.none strong { color: var(--color-text-muted); }
.design-upload-bar {
  position: relative; height: 8px; margin: 12px 0 8px;
  border-radius: 999px; background: var(--border-subtle); overflow: hidden;
}
.design-upload-fill {
  display: block; height: 100%; border-radius: 999px;
  background: var(--tone-success-fg);
  /* Eased, because the bar moves a batch at a time — twenty pictures at once —
     and an unanimated jump of several percent reads as a glitch rather than as
     progress. */
  transition: width .3s ease;
}
.design-upload-line { margin: 0; font-variant-numeric: tabular-nums; }

@media (max-width: 900px) {
  .design-upload-tiles { margin-left: 0; width: 100%; }
}
/* ---- The Sources panel ----
   A table of galleries: how many designs, how many stored, when last touched. */
.design-sources {
  margin: 0 0 var(--space-lg); padding: 16px 18px 6px;
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  background: var(--color-surface-alt);
}
.design-sources-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 14px; }
.design-sources-head h2 { margin: 0 0 4px; font-size: var(--text-xl); font-weight: 500; }
.design-source-row {
  display: grid; align-items: center; gap: 14px;
  grid-template-columns: minmax(0, 2.4fr) 90px 110px 110px 110px minmax(0, auto);
  padding: 11px 0; border-bottom: 1px solid var(--color-border);
}
.design-source-head-row { padding: 0 0 8px; font-size: var(--text-md); color: var(--color-text-muted); text-transform: uppercase; letter-spacing: .05em; }
.design-source-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.design-source-title { font-size: var(--text-base); color: var(--color-text); }
.design-source-num { text-align: right; font-size: var(--text-base); color: var(--color-text); font-variant-numeric: tabular-nums; }
.design-source-head-row .design-source-num { font-size: var(--text-md); color: var(--color-text-muted); }
.design-source-actions { display: flex; align-items: center; gap: 8px; justify-content: flex-end; }
.design-source-ok { color: var(--tone-success-fg); }
.design-source-part { color: var(--tone-warn-fg); }
.design-source-error { font-size: var(--text-sm); color: var(--tone-error-fg); }
.design-source-row-inactive { opacity: .7; }
.design-sources-note { padding: 12px 0 6px; }

/* ---- The full-screen viewer ----
   Its own overlay rather than .modal-overlay: a modal card is sized for a form,
   and this is a full-resolution screenshot with a column of facts beside it. */
.design-viewer {
  position: fixed; inset: 0; z-index: 200;
  display: flex; flex-direction: column;
  background: var(--app-bg); padding: 12px;
}
.design-viewer-bar { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 0 4px 12px; flex: none; }
.design-viewer-hint { opacity: .75; }
.design-viewer-body { flex: 1; min-height: 0; display: flex; gap: 18px; }
.design-viewer-image {
  position: relative; flex: 1; min-width: 0; display: flex; align-items: center; justify-content: center;
  background: var(--color-surface-alt); border-radius: var(--radius-lg); overflow: auto;
}
/* Fits the frame both ways rather than only shrinking to it. `max-width` alone
   leaves a small screenshot floating at its natural size in the middle of a
   1400px panel, which is the opposite of looking at it properly. */
.design-viewer-image img { width: 100%; height: 100%; object-fit: contain; display: block; }
.design-viewer-missing { display: none; }
.design-viewer-image.missing img { display: none; }
.design-viewer-image.missing .design-viewer-missing { display: block; padding: 0 24px; text-align: center; }
.design-viewer-side {
  flex: none; width: min(380px, 40vw); overflow-y: auto;
  padding: 0 4px 24px; display: flex; flex-direction: column; gap: 18px;
}
.design-viewer-side h2 { margin: 0 0 4px; font-size: var(--text-2xl); font-weight: 400; line-height: 1.2; }
.design-viewer-block { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.design-viewer-block .eyebrow { margin: 0; }
.design-viewer-block .list-panel { width: 100%; }
.design-viewer-block .chip-row { margin: 0; }
.design-viewer-note { margin: 0; }
.design-viewer-error { color: var(--tone-error-fg); }
.design-viewer-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-top: 4px; }
.design-tag-editor { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; width: 100%; }
.design-tag-editor .input { flex: 1; min-width: 150px; }
.design-path { font-size: var(--text-sm); word-break: break-all; color: var(--color-text-muted); }

/* On a narrow window the facts go under the picture rather than squeezing it. */
@media (max-width: 900px) {
  .design-viewer-body { flex-direction: column; }
  .design-viewer-side { width: auto; max-height: 46vh; }
  .design-sticky, .design-bulk-bar { margin-left: -20px; margin-right: -20px; padding-left: 20px; padding-right: 20px; }
  .design-source-row { grid-template-columns: minmax(0, 1.6fr) 70px 90px; }
  .design-source-row > :nth-child(4), .design-source-row > :nth-child(5) { display: none; }
}

/* Present for a screen reader, invisible to everyone else — the word behind an
   × that only shows a symbol. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.specialist-badge { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-base); font-weight: 500; padding: 4px 11px; border-radius: var(--radius-pill); }
.specialist-badge svg { flex: none; }

/* Project detail's own top tab bar — the reference design's own
   navigation shape for a project (a horizontal toolbar of tabs, not a
   vertical sidebar list). Sticky so it stays pinned while #main's own
   content scrolls beneath it, matching a native toolbar. Reuses .filter-tab
   for each tab's own look (active underline + real badge counts), the same
   pattern Activity's own filter row already uses. */
/* A plain tab strip, used inside a view rather than as a pane header — the
   Skills/Tools library's own two tabs (see features/library.js). Kept separate
   from .pane-head below, which is a sticky two-row header and would be wrong
   sitting in the middle of a page. */
.project-tab-bar {
  display: flex; align-items: center; gap: 22px; flex-wrap: wrap;
  padding-bottom: 2px; border-bottom: 1px solid var(--border-default);
}

/* ---- The run transcript (features/runs.js) ----
   The agent's prose is the document; its tool calls fold away into one line
   each stretch. Before this, every call printed a heading and two code fences,
   so a real build was a screen of fences with the sentences lost among them. */
.turn-text { font-size: var(--text-base); line-height: 1.65; color: var(--color-body); }
.turn-text > :first-child { margin-top: 0; }
.turn-text > :last-child { margin-bottom: 0; }

.tool-group { border: 1px solid var(--border-faint); border-radius: var(--radius-md); background: var(--color-surface-alt); margin: 8px 0; }
.tool-group > summary {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 7px 12px; cursor: pointer; font-size: var(--text-sm); color: var(--color-text-muted);
  list-style: none;
}
.tool-group > summary::-webkit-details-marker { display: none; }
/* A real disclosure triangle, drawn rather than relying on the platform's own —
   the native marker cannot be positioned inside a flex summary consistently. */
.tool-group > summary::before {
  content: '▸'; color: var(--color-meta); font-size: 10px; flex: none;
  transition: transform .12s ease;
}
.tool-group[open] > summary::before { transform: rotate(90deg); }
.tool-group > summary:hover { color: var(--color-text); }
.tool-count { color: var(--color-text); }
.tool-names { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-meta); }
.tool-group.thinking { background: transparent; }

.tool-pre {
  margin: 5px 0 0; padding: 7px 9px; background: var(--color-surface); border-radius: var(--radius-sm);
  font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.6; color: var(--color-body);
  white-space: pre-wrap; word-break: break-word; max-height: 240px; overflow: auto;
}
.tool-pre-result { color: var(--color-text-muted); }
/* When a log line actually happened. Pushed to the end of its row rather than set
   before the label: the reading order in a transcript is what-happened first, and a
   column of timestamps down the left edge is what makes a log look like a server
   error file instead of something a person reads. */
.log-clock {
  margin-left: auto; flex: none; font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--color-meta); font-weight: 400;
}
/* Except in the CLI-event lines, where it sits inside a line of text rather than in a
   flex row — `margin-left: auto` computes to nothing on an inline box, so the reading
   would otherwise butt straight up against the label. */
.audit-log-line .log-clock { margin-left: 8px; }
.run-end { font-size: var(--text-sm); margin: 10px 0 0; display: flex; align-items: baseline; gap: 8px; }
/* The closing facts under "Completed" — turns, wall time, the SDK's own cost and token
   counts. Quiet on purpose: the line above it is the answer, this is the accounting. */
.run-end-facts {
  margin: 2px 0 0; font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--color-text-muted); line-height: 1.6;
}
/* A run's own task text. Scrolls rather than growing without limit, since an
   orchestrated step's prompt can run to hundreds of words — see taskDetailHtml,
   which folds the long ones away entirely. */
.task-text {
  max-height: 220px; overflow: auto; white-space: pre-wrap; word-break: break-word;
  border: 1px solid var(--border-faint); border-radius: var(--radius-md);
  padding: 9px 11px; background: var(--color-surface-alt);
  font-size: var(--text-sm); line-height: 1.6; color: var(--color-body);
}

/* ---- A task row in the rail, with its own delete ----
   The row is a grid so the delete sits beside the open-button rather than inside
   it — nesting one button in another is invalid and the inner click would not be
   separable. Hidden until hover or focus: a row clicked many times a day should
   not carry a destructive control in the way, but it must still be reachable by
   keyboard, which is what :focus-within is for. */
.task-row { display: flex; align-items: center; position: relative; }
/* Room kept for the delete button at all times, not only while it is showing:
   the label ellipsises at the same place whether or not the button is visible, so
   nothing reflows on hover and the trash icon never sits on top of the title. */
.task-row .task-open { flex: 1; min-width: 0; padding-right: 26px; }
.task-delete {
  position: absolute; right: 4px; flex: none; border: 0; background: transparent;
  color: var(--color-meta); cursor: pointer; padding: 4px; border-radius: var(--radius-sm);
  opacity: 0; transition: opacity .12s ease;
}
/* The row you are actually on keeps its delete visible. Hover-only was the whole
   reason it could not be found: on the open task — the one you are most likely to
   want rid of — there was nothing on screen to suggest deleting was possible at
   all. The header's own "···" menu carries it too, for anyone looking at the task
   rather than at the rail. */
.task-row:hover .task-delete, .task-row:focus-within .task-delete,
.task-row.active .task-delete { opacity: 1; }
.task-row.active .task-delete { color: var(--color-text-muted); }
.task-delete:hover { color: var(--tone-error-fg); background: var(--tone-error-bg); }

/* ---------------------------------------------------------------------------
   The inspector — the panel beside a task's conversation (features/inspector.js).

   Modelled on Conductor's right-hand pane. The point is that it is *beside* the
   conversation rather than a tab you leave for: watching what an agent is doing
   to the files while it works was previously two views of one website that could
   not be open at once.
   --------------------------------------------------------------------------- */
.pane-split { display: block; }
.pane-split.with-inspector {
  display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 20px; align-items: start;
}
.pane-main { min-width: 0; }

.inspector {
  position: sticky; top: 96px; display: flex; flex-direction: column;
  max-height: calc(100vh - 130px); min-height: 260px;
  border: 1px solid var(--border-faint); border-radius: var(--radius-lg);
  background: var(--color-surface); overflow: hidden;
}
.inspector-head {
  display: flex; align-items: center; gap: 2px; flex: none;
  padding: 6px 6px 6px 8px; background: var(--color-surface-alt);
  border-bottom: 1px solid var(--border-faint);
}
.inspector-tab {
  border: 0; background: transparent; font-family: inherit; font-size: var(--text-sm);
  color: var(--color-text-muted); padding: 4px 8px; border-radius: var(--radius-sm); cursor: pointer;
}
.inspector-tab:hover { color: var(--color-text); }
.inspector-tab.active { background: var(--color-surface); color: var(--color-text); box-shadow: var(--shadow-xs); }
.inspector-close { border: 0; background: transparent; color: var(--color-meta); cursor: pointer; padding: 4px; display: flex; }
.inspector-close:hover { color: var(--color-text); }
.inspector-body { flex: 1; min-height: 0; overflow: auto; }
.inspector-scroll { padding: 10px 12px; }
.inspector-foot {
  flex: none; padding: 6px 10px; border-top: 1px solid var(--border-faint);
  font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--color-label);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The file tree. Mono throughout, because these are filenames — the same reason
   the registry sets identifiers in mono. */
.tree { padding: 6px 0; font-family: var(--font-mono); font-size: var(--text-xs); }
.tree-row {
  display: flex; align-items: center; gap: 6px; width: 100%; text-align: left;
  border: 0; background: transparent; font-family: inherit; font-size: inherit;
  color: var(--color-body); padding: 3px 10px 3px 8px; cursor: pointer;
}
.tree-row.file { cursor: default; }
.tree-row:hover { background: var(--color-surface-alt); }
.tree-caret { flex: none; width: 9px; color: var(--color-meta); font-size: 9px; transition: transform .12s ease; }
.tree-caret.open { transform: rotate(90deg); }
.tree-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree-name.changed { color: var(--color-text); }
/* One dot, and it is the whole reason the tree sits next to the conversation:
   which files this run has already touched. */
.tree-dot { flex: none; width: 5px; height: 5px; border-radius: 50%; background: var(--color-brand-primary); }
.tree-size { flex: none; margin-left: auto; color: var(--color-meta); font-size: var(--text-2xs); }
.tree-empty { padding: 14px 12px; font-size: var(--text-sm); color: var(--color-text-muted); text-align: center; }

.check-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 7px 12px; border-bottom: 1px solid var(--border-faint);
  font-size: var(--text-sm);
}
.check-name { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-body); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.check-value { flex: none; font-size: var(--text-xs); }

/* ---- Three columns, on a screen with room for two ----
   A task section with the Inspector open is the section column, the
   conversation and a 340px panel. That is fine on a monitor and not fine on a
   laptop, so below 1280px the section column narrows to its icons and gives the
   ~150px back to the conversation. It is a width change, not a disappearance:
   `.split-page.is-icons` keeps the active state and moves each label into the
   tooltip and the accessible name, both of which `sectionNavHtml` already
   writes. A media query rather than a container query because what is short of
   room here is the window, not the panel.

   Applied only while the Inspector is actually open — the class is put on by
   renderProjectDetailView — so closing it gives the labels straight back. */
@media (max-width: 1280px) {
  .split-page.collapses-for-inspector { grid-template-columns: 48px minmax(0, 1fr); }
  .split-page.collapses-for-inspector::before { left: 48px; }
  .split-page.collapses-for-inspector .split-nav { padding-right: 10px; align-items: center; }
  .split-page.collapses-for-inspector .split-nav-item { justify-content: center; padding: 0; gap: 0; }
  .split-page.collapses-for-inspector .split-nav-label,
  .split-page.collapses-for-inspector .split-nav-count,
  .split-page.collapses-for-inspector .split-nav-group-label,
  .split-page.collapses-for-inspector .split-identity-name,
  .split-page.collapses-for-inspector .split-identity-meta { display: none; }
  .split-page.collapses-for-inspector .split-panel { padding-left: 14px; }
}

@media (max-width: 1100px) {
  /* Below this the two columns would each be too narrow to read; the inspector
     goes under the conversation rather than beside it. */
  .pane-split.with-inspector { grid-template-columns: minmax(0, 1fr); }
  .inspector { position: static; max-height: 420px; }
  /* With the inspector stacked under the conversation there is no third column
     left to make room for, so the labels come back. */
  .split-page.collapses-for-inspector { grid-template-columns: 200px minmax(0, 1fr); }
  .split-page.collapses-for-inspector::before { left: 200px; }
  .split-page.collapses-for-inspector .split-nav { align-items: stretch; }
  .split-page.collapses-for-inspector .split-nav-item { justify-content: flex-start; padding: 0 9px; gap: 9px; }
  .split-page.collapses-for-inspector .split-nav-label,
  .split-page.collapses-for-inspector .split-nav-count,
  .split-page.collapses-for-inspector .split-nav-group-label,
  .split-page.collapses-for-inspector .split-identity-name,
  .split-page.collapses-for-inspector .split-identity-meta { display: revert; }
  .split-page.collapses-for-inspector .split-panel { padding-left: var(--space-md); }
}

/* ---- Analytics (features/analytics.js) ---- */
/* The four headline figures. The label sits above the number, so a column reads
   downward as a sentence instead of asking you to look back up to find out what
   the big number was. */
/* Named `.stat`, not `.count` — `.count` was already the small badge inside a
   `.filter-tab` (see below), so calling these tiles `.count` made every tab badge
   in the app inherit a tile's padding and turn into a large box. A generic class
   name for a specific component is how that happens. */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.stat {
  background: var(--color-surface); border: 1px solid var(--border-faint);
  border-radius: var(--radius-lg); padding: 15px 18px;
  display: flex; flex-direction: column; gap: 3px;
}
.stat-label {
  font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: .1em;
  text-transform: uppercase; color: var(--color-label);
}
.stat b {
  font-family: var(--font-mono); font-size: 26px; font-weight: 600; line-height: 1.1;
  color: var(--color-text); font-variant-numeric: tabular-nums;
}
.stat span:last-child { font-size: var(--text-sm); color: var(--color-text-muted); }

/* A tile's optional icon, sitting beside its label.
   The only colour on the tile, and it comes from the app's five specialist hues
   rather than from new ones invented here. It marks which tile you are looking
   at; the figure below it carries the meaning. */
.stat-mark {
  flex: none; width: 28px; height: 28px; border-radius: var(--radius-control);
  display: flex; align-items: center; justify-content: center;
}
.stat-mark-blue { background: var(--spec-blue-bg); color: var(--spec-blue-fg); }
.stat-mark-orange { background: var(--spec-orange-bg); color: var(--spec-orange-fg); }
.stat-mark-green { background: var(--spec-green-bg); color: var(--spec-green-fg); }
.stat-mark-purple { background: var(--spec-purple-bg); color: var(--spec-purple-fg); }

/* A share-of-spend bar under a breakdown row. Length is all it encodes — the
   figure is already printed beside it, so this is a shape to compare down a
   column, not a second copy of the number. */
.bar { display: block; height: 3px; margin-top: 8px; background: var(--color-surface-alt); border-radius: var(--radius-pill); overflow: hidden; }
.bar-fill { display: block; height: 100%; background: var(--color-brand-primary); border-radius: var(--radius-pill); }

/* Figures line up in columns, so they are tabular and right-aligned; an
   identifier column stays mono for the same reason it does in the registry. */
.data-table th.num, .data-table td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.data-table td.mono-cell { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-body); }

/* ---- Settings rows (features/settings.js) ----
   A registry row that holds a control rather than navigating. Values that are
   identifiers — a path, a port, a version — are set in mono, which is the whole
   reason a long data directory is now readable a segment at a time. */
.set-row {
  display: flex; align-items: center; gap: 16px; padding: 13px 18px;
  border-bottom: 1px solid var(--border-faint);
}
.set-row:last-child { border-bottom: 0; }
.set-label { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.set-name { font-size: var(--text-md); color: var(--color-text); }
.set-value { font-size: var(--text-sm); color: var(--color-text-muted); word-break: break-word; }
.set-value.mono { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--color-body); }
/* The Claude status block reuses .set-row for its padding but carries its own
   two-line markup, so its inner list-row must not add padding of its own. */
#claudeStatusBox .list-row { padding: 0; border: 0; }

/* ---------------------------------------------------------------------------
   The toolbar — one row above a table or list.

   Replaces a block of seven permanently-visible label-and-dropdown pairs (see
   features/board.js). Search and the one field worth a single click stay out;
   the rest go behind a Filters button and come back as removable chips when they
   are actually set, so a hidden filter is never a forgotten one.
   --------------------------------------------------------------------------- */
.toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px;
}
.toolbar-search {
  flex: 0 1 260px; min-width: 160px;
  padding: 7px 11px; border: 1px solid var(--border-medium); border-radius: var(--radius-md);
  background: var(--color-surface); color: var(--color-text); font-size: var(--text-base);
}
.toolbar .btn.on { border-color: var(--border-on-brand); color: var(--color-brand-primary); }
/* A select in a toolbar is painted by the platform unless it is told otherwise,
   which on a dark palette hands back a light grey box inside a dark row. Same
   two properties the .field controls need, plus a drawn arrow in place of the
   platform's own. */
.toolbar select {
  appearance: none; padding: 6px 28px 6px 10px;
  border: 1px solid var(--border-medium); border-radius: var(--radius-md);
  background-color: var(--color-surface); color: var(--color-text);
  font-family: inherit; font-size: var(--text-sm); cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--color-text-muted) 50%),
    linear-gradient(135deg, var(--color-text-muted) 50%, transparent 50%);
  background-position: calc(100% - 15px) center, calc(100% - 10px) center;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
}
.toolbar select:hover { border-color: var(--border-on-brand); }

/* The popover the hidden filters live in. Positioned rather than fixed so it
   travels with its button when the toolbar wraps onto two rows. */
.popover {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 20; min-width: 240px;
  background: var(--color-surface); border: 1px solid var(--border-default);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md); padding: 6px;
  display: flex; flex-direction: column; gap: 2px;
}
.popover-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 6px 8px; font-size: var(--text-sm); color: var(--color-body);
}
.popover-row select {
  flex: 0 1 130px; padding: 4px 8px; border: 1px solid var(--border-medium);
  border-radius: var(--radius-sm); background: var(--color-surface); color: var(--color-text);
  font-size: var(--text-sm);
}

/* The pane header's overflow menu. Its wrapper is the positioning context — the
   header itself is a flex row that can wrap, and a menu anchored to the row
   rather than to its own button lands in the wrong place the moment it does. */
.pane-menu-wrap { position: relative; flex: none; }
.pane-menu { min-width: 214px; }
.popover-item {
  display: block; width: 100%; text-align: left; border: 0; background: transparent;
  padding: 7px 9px; border-radius: var(--radius-sm); cursor: pointer;
  font-family: inherit; font-size: var(--text-sm); color: var(--color-body);
}
.popover-item:hover { background: var(--color-surface-alt); color: var(--color-text); }
.popover-item.danger { color: var(--tone-error-fg); }
.popover-item.danger:hover { background: var(--tone-error-bg); }
.popover-sep { display: block; height: 1px; margin: 5px 4px; background: var(--border-faint); }
/* Why an action is missing, rather than a control that does nothing when clicked. */
/* A destructive action that must be present without being prominent — quiet
   until you are actually on it. Used for Delete in the card modal, which sits in
   the same row as the primary action. */
.btn-danger-quiet {
  background: transparent; border: 1px solid transparent; color: var(--color-meta);
}
.btn-danger-quiet:hover { background: var(--tone-error-bg); border-color: var(--tone-error-fg); color: var(--tone-error-fg); }

.popover-note {
  display: block; padding: 6px 9px; font-size: var(--text-xs); line-height: 1.45;
  color: var(--color-meta); max-width: 30ch;
}

.chip-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: -4px 0 12px; }
.filter-chip {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--border-default); border-radius: var(--radius-pill);
  background: var(--color-tint); color: var(--color-brand-primary);
  font-family: inherit; font-size: var(--text-xs); padding: 3px 9px; cursor: pointer;
}
.filter-chip:hover { border-color: var(--border-on-brand); }

/* ---------------------------------------------------------------------------
   The registry — how Agents, Skills and Tools are listed.

   These were card grids: a pastel icon chip, a title, a mono path repeating the
   title, a whole paragraph, and a row of pills. Two problems. The pills were the
   same on every card (`Filesystem`, `Bash`) so nothing on screen said what
   actually differed between two agents, and at 26 skills a paragraph each you
   could see three of them.

   So: a registry, not a gallery. The reasoning is that this app's own nouns are
   *identifiers* — `development-agent`, `install-design`, `fidelity.php`, a
   theme path — and an identifier belongs in mono, where its exact characters are
   the point. Structure comes from hairlines rather than from floating cards,
   colour is spent only on state, and the row carries the thing that genuinely
   distinguishes one entry from the next: for an agent, the skills it owns; for a
   skill, who owns it. Dense enough to scan the whole library at once, which is
   what a library is for.
   --------------------------------------------------------------------------- */
.reg {
  border: 1px solid var(--border-faint); border-radius: var(--radius-lg);
  background: var(--color-surface); overflow: hidden;
}
.reg-group-label {
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--color-label);
  padding: 11px 18px 7px; background: var(--color-surface-alt);
  border-bottom: 1px solid var(--border-faint);
}
.reg-group-label:not(:first-child) { border-top: 1px solid var(--border-faint); }

.reg-row {
  display: block; width: 100%; text-align: left; border: 0; background: transparent;
  font-family: inherit; cursor: pointer; padding: 13px 18px;
  border-bottom: 1px solid var(--border-faint);
}
.reg-row:last-child { border-bottom: 0; }
.reg-row:hover { background: var(--color-surface-alt); }
.reg-row:focus-visible { outline: 2px solid var(--color-brand-primary); outline-offset: -2px; }

/* The identifier line: the name in mono, then whatever this row's one real
   right-hand fact is (an agent's write capability, a skill's owner). */
.reg-head { display: flex; align-items: baseline; gap: 12px; }
.reg-id {
  font-family: var(--font-mono); font-size: var(--text-md); color: var(--color-text);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.reg-meta {
  flex: none; margin-left: auto; font-size: var(--text-xs); color: var(--color-meta);
  display: flex; align-items: center; gap: 10px;
}
/* One line, clamped. The full text is on the entry's own page; a paragraph here
   is what made 26 skills unscannable. */
.reg-desc {
  display: block; margin-top: 3px; font-size: var(--text-sm); color: var(--color-text-muted);
  line-height: 1.5; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The differentiator row — an agent's own skills, as real named things. */
.reg-owns { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 9px; }
.reg-owns-label {
  font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: .1em;
  text-transform: uppercase; color: var(--color-label); margin-right: 2px;
}
.reg-chip {
  font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--color-body);
  background: var(--color-surface-alt); border: 1px solid var(--border-faint);
  border-radius: var(--radius-sm); padding: 2px 6px; cursor: pointer;
}
.reg-chip:hover { color: var(--color-brand-primary); border-color: var(--border-on-brand); }
.reg-chip.plain { cursor: default; }
.reg-chip.plain:hover { color: var(--color-body); border-color: var(--border-faint); }
.reg-state { display: inline-flex; align-items: center; gap: 5px; font-size: var(--text-xs); }

/* ---- A segmented three-way control (features/appearance.js) ---- */
.seg { display: inline-flex; padding: 2px; gap: 2px; background: var(--color-surface-alt); border: 1px solid var(--border-faint); border-radius: var(--radius-control); }
.seg-btn {
  border: 0; background: transparent; padding: 5px 12px; border-radius: var(--radius-md);
  font-family: inherit; font-size: var(--text-sm); color: var(--color-text-muted); cursor: pointer;
}
.seg-btn:hover { color: var(--color-text); }
.seg-btn.on { background: var(--color-surface); color: var(--color-text); box-shadow: var(--shadow-xs); }

/* ---- The three-step setup (features/setup.js) ---- */
/* A button that reads as a link, for a sentence that offers a different route
   mid-paragraph — a real <button> because it acts, not navigates to a URL. */
.link-btn {
  border: 0; background: transparent; padding: 0; font-family: inherit; font-size: inherit;
  color: var(--color-brand-primary); cursor: pointer; text-decoration: underline;
}
.stepper { display: flex; align-items: center; gap: 10px; margin-bottom: 28px; }
.step {
  display: flex; align-items: center; gap: 8px; border: 0; background: transparent;
  padding: 0; font-family: inherit; font-size: var(--text-base); color: var(--color-meta);
  cursor: default;
}
/* A completed step is the only clickable one — going back is allowed, jumping
   forward is not, because step 2 needs a connected site and step 3 needs its
   themes read (see setSetupStep). */
.step.done { color: var(--color-brand-primary); cursor: pointer; }
.step.here { color: var(--color-text); }
.step-n {
  width: 22px; height: 22px; border-radius: 999px; display: grid; place-items: center;
  font-size: var(--text-xs); background: var(--color-surface-alt); color: var(--color-text-muted);
}
.step.here .step-n { background: var(--color-brand-primary); color: #fff; }
.step.done .step-n { background: var(--tone-success-bg); color: var(--tone-success-fg); }
.step-line { flex: 1; height: 1px; background: var(--border-default); }

.setup-h { font-size: var(--text-2xl); font-weight: 400; color: var(--color-text); margin: 0 0 6px; }
.setup-p { font-size: var(--text-base); color: var(--color-text-muted); margin: 0 0 20px; max-width: 60ch; }
.pick-group { display: flex; flex-direction: column; gap: 8px; margin-bottom: 22px; }
.pick-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  padding: 11px 14px; background: var(--color-surface); cursor: pointer; font-family: inherit;
}
.pick-row:hover:not([disabled]) { border-color: var(--border-on-brand); }
.pick-row[disabled] { opacity: .55; cursor: default; }
.notice {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  border: 1px solid var(--border-faint); border-left: 3px solid var(--color-brand-primary);
  border-radius: var(--radius-md); padding: 14px 16px; background: var(--color-surface);
}

/* A dashed border, never a colour, marks something this app's own agent
   wrote — a recommendation or a draft — as distinct from a confirmed fact
   pulled from a cited source: colour is spoken for already (status pills,
   tone-coded verdicts), so basis gets its own, unrelated visual channel and
   never reads as the same kind of signal. See leads.js's own note on this
   (bantScorecardHtml, stageResultHtml). */
.draft-card { border: 1px dashed var(--color-border); border-radius: var(--radius-md); padding: 14px; margin-top: 14px; }
.draft-tag { font-family: var(--font-mono); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--color-meta); flex: none; }

/* The job cards — step 3. A blocked job is still shown and still selectable:
   clicking it is how you find out *why* it cannot run, which is the whole point
   of naming requirements here instead of letting an agent discover them. */
.job-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.job-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
  text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);
  padding: 14px 16px; background: var(--color-surface); cursor: pointer; font-family: inherit;
}
.job-card:hover { border-color: var(--border-on-brand); }
.job-card.selected { border: 2px solid var(--color-brand-primary); padding: 13px 15px; }
.job-title { font-size: var(--text-base); color: var(--color-text); }
.job-blurb { font-size: var(--text-sm); color: var(--color-text-muted); line-height: 1.45; }
.job-need { font-size: var(--text-xs); color: var(--tone-warn-fg); }
.job-card.blocked .job-title { color: var(--color-text-muted); }
/* The skills a job runs, on the card — so picking a job also says which of the
   library's procedures it will follow, rather than that being invisible until an
   agent decides. */
.job-runs { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }

/* A registry row used as a checkbox — the panel/page picker. */
.reg-row.pick { padding: 9px 14px; }
.tick {
  width: 15px; height: 15px; border-radius: var(--radius-sm); flex: none;
  border: 1px solid var(--border-medium); background: var(--color-surface);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; line-height: 1; color: transparent;
}
.tick.on { background: var(--color-brand-primary); border-color: var(--color-brand-primary); color: #fff; }
:root[data-theme="dark"] .tick.on { color: #07090D; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .tick.on { color: #07090D; } }

/* ---- The pane header ----
   Two rows, sticky together: the breadcrumb saying which website and which task
   is open, and that pane's own tab row directly under it. They are one unit
   because the tabs mean different things depending on what the breadcrumb says
   — a task's tabs are Agent/Diff/Checks/Report, a website's are Board/Theme/
   Pages — so scrolling one away from the other would leave a row of tabs with
   nothing saying what they belong to. */
.pane-head {
  position: sticky; top: 0; z-index: 5; background: var(--color-surface);
  border-bottom: 1px solid var(--border-default); padding: 14px 52px 0;
}
.crumbs { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-height: 30px; }
.crumb {
  border: 0; background: transparent; padding: 0; font-family: inherit; cursor: pointer;
  font-size: var(--text-lg); color: var(--color-text-muted); max-width: 34ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.crumb:hover { color: var(--color-brand-primary); }
/* The last crumb is where you are, so it is not a link and does not pretend to
   be one. */
.crumb-current { color: var(--color-text); cursor: default; }
.crumb-current:hover { color: var(--color-text); }
.crumb-sep { color: var(--color-meta); font-size: var(--text-md); }
.pane-tabs { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; padding-top: 8px; }

/* ---- Detail views ---- */
.back-link {
  display: flex; align-items: center; gap: 7px; border: 0; background: transparent;
  color: var(--color-brand-primary); font-size: var(--text-base); cursor: pointer; padding: 0; margin-bottom: 22px;
}
.detail-header { display: flex; align-items: flex-start; gap: 18px; margin-bottom: 28px; flex-wrap: wrap; }
.detail-header h1 { margin: 0 0 9px; font-weight: 300; font-size: var(--text-3xl); line-height: 1.15; }
.role-text { margin: 0; font-size: var(--text-xl); line-height: 1.6; color: var(--color-text-muted); max-width: 640px; white-space: pre-wrap; }
.tag-row { display: flex; flex-wrap: wrap; gap: 9px; }

/* ---- Lists/tables — every list of rows in the app (agent runs, workflow
   steps, memory items, settings rows) is one .list-panel of .list-row. ---- */
.list-panel { border: 1px solid var(--color-border); border-radius: var(--radius-lg); overflow: hidden; }
.list-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 13px var(--space-md); border-bottom: 1px solid var(--color-border); background: var(--color-surface); }
.list-row:last-child { border-bottom: 0; }
/* The "tick everything" row that sits above a list of choices (the design
   import picker). Shaded and a shade bolder so it reads as being about the
   list rather than as the first thing in it. */
.pick-all-row { background: var(--color-surface-alt); font-weight: 600; }

/* ---------------------------------------------------------------------------
   The Admin area (features/admin.js) — a management dashboard first and an
   audit log second.

   Everything here is a layout on top of the shapes the app already has:
   `.stats`/`.stat` for the six figures, `.list-panel`/`.list-row` for both
   lists, `.pill` for every status. What these rules add is the *order* a row
   is read in, which a flex row with three children cannot express — a person
   row is who, then what state they are in, then when they were last here, then
   what can be done about it, and it has to stay in that order as it narrows.

   ## Why container queries rather than a width breakpoint

   The thing that gets narrow here is the content column, not the window: the
   app shell keeps its full sidebar at every size, so a `@media` rule would
   fire at the wrong moment on a laptop with a panel open and never fire at all
   for the column being 400px inside a 1400px window. `container-type` on the
   panel asks the only question that matters — how wide is *this list* — and
   answers it the same way on a phone and in a squeezed column.
   --------------------------------------------------------------------------- */

/* A figure that is only worth a colour when it is not zero. Nothing else in
   the summary is toned: a zero shown in red teaches people to ignore red, and
   six coloured numbers say nothing about which one to look at. */
.stat-warn b { color: var(--tone-warn-fg); }
.stat-alert b { color: var(--tone-error-fg); }

.admin-list { container-type: inline-size; }
/* Six figures on one line on a laptop rather than five and a stray. The
   `min()` is what lets a track shrink below its own minimum in a column too
   narrow for even one — without it the grid is the widest thing on the page
   and the panel scrolls sideways. */
.admin-summary { grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); }
/* Room for a two-line label, so all six figures sit on one baseline. Three of
   these labels wrap and three do not, and six numbers at four different
   heights is a row you compare by hunting rather than by looking across. */
.admin-summary .stat-label { min-height: 2.4em; }
/* The section heading, its one sentence of explanation, and the one control
   that belongs to it (search, or a count) on the same line. */
/* The tab bar needs air above it — it belongs to the sections below, not to
   the figures above, and butted against the tiles it reads as their footer. */
.admin-tabs { margin-top: var(--space-lg); }
/* A section's own line of explanation and its one control. No top margin: the
   tab bar directly above already provides the separation, and the two together
   left a gap wide enough to read as a missing heading. */
.admin-section-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 0 0 12px;
}
.admin-section-head .meta-text { margin: 0; flex: 1 1 auto; }
/* Shrinks with the column rather than holding it open. `min-width: 0` is the
   part that matters: an input's default intrinsic minimum is what makes a
   search box the widest thing on a narrow screen. */
.admin-section-search { flex: 1 1 240px; min-width: 0; max-width: 260px; width: auto; }
/* The three history filters, for the same reason. */
.admin-filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.admin-filters .input { flex: 0 1 auto; width: auto; min-width: 0; max-width: 100%; }

/* ---- One person, one row ---- */
.admin-person {
  display: grid;
  /* Fixed widths on the last three, so every pill, every "last seen" and every
     button starts at the same place down the whole list. With `auto` they line
     up with nothing, and a column that does not line up has to be read rather
     than scanned — which is the entire difference this row is trying to make. */
  grid-template-columns: auto minmax(0, 1fr) 12.5rem 8.5rem 19.5rem;
  align-items: center;
  column-gap: var(--space-sm); row-gap: 9px;
}
/* Switched off, dimmed — but not the pill that says so, which is the one thing
   on a dimmed row somebody is looking for. */
.admin-person.is-off .admin-person-who,
.admin-person.is-off .admin-person-seen { opacity: .55; }
.admin-person-avatar {
  width: 34px; height: 34px; flex: none; border-radius: var(--radius-avatar-sm);
  background: var(--color-tint); color: var(--color-brand-primary);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--text-sm); font-weight: 600; overflow: hidden;
}
.admin-person-avatar img { width: 100%; height: 100%; object-fit: cover; }
.admin-person-who { min-width: 0; }
/* The name never truncates; the email does. A name shortened to "Manohar Sin…"
   is a row you cannot identify, which is the whole job of this column. */
.admin-person-name {
  font-size: var(--text-md); color: var(--color-text); font-weight: 500;
  display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
}
.admin-person-email {
  display: block; font-size: var(--text-xs); color: var(--color-meta);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.admin-person-status { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.admin-person-seen {
  font-size: var(--text-xs); color: var(--color-meta); text-align: right;
  line-height: 1.5; white-space: nowrap;
}
.admin-person-seen b { display: block; font-weight: 500; color: var(--color-text-muted); }
.admin-person-actions { display: flex; align-items: center; gap: 6px; justify-content: flex-end; }

/* ---- One event, one row ---- */
.admin-event {
  display: grid;
  /* A fixed first column, because each row is its own grid — `auto` would let
     every row put its pill at a different width and the list would have no
     left edge to run the eye down. Wide enough for most labels; the two or
     three long ones ("Changed somebody's permission level") wrap inside it
     rather than pushing into the person's name. */
  grid-template-columns: 12.5rem minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: var(--space-sm); row-gap: 5px;
}
.admin-event-kind { justify-self: start; min-width: 0; }
.admin-event-kind .pill { white-space: normal; line-height: 1.35; text-align: center; }
.admin-event-who { min-width: 0; font-size: var(--text-md); color: var(--color-text); }
.admin-event-what {
  display: block; font-size: var(--text-xs); color: var(--color-meta);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.admin-event-when {
  font-size: var(--text-xs); color: var(--color-meta); text-align: right;
  line-height: 1.5; white-space: nowrap;
}
.admin-event-when b { display: block; font-weight: 500; color: var(--color-text-muted); }

/* ---- A row's own actions, kept findable without being the loudest thing on
   it. Always drawn, never hidden until hover: a control that appears only when
   the pointer is already on it cannot be found by somebody looking for it. It
   is quieter than .btn-secondary rather than absent, which is what makes a
   person's name and status the first thing the eye lands on. ---- */
.btn-quiet {
  background: transparent; border-color: var(--border-faint);
  color: var(--color-text-muted); font-weight: 400;
}
.btn-quiet:hover:not(:disabled) {
  background: var(--color-surface-alt); border-color: var(--border-strong); color: var(--color-text);
}
.btn-quiet:disabled { opacity: .5; cursor: default; }

/* ---- The two tools that are not why anybody opened this screen ----
   Lead intake keys and Roles are looked at a few times a year, so they sit
   under the history behind a disclosure rather than between People and
   Activity where they used to interrupt the one path through the page. A real
   <details>, so it needs no state of its own — the same choice runs.js made. */
.admin-drawer { border-top: 1px solid var(--color-border); margin-top: var(--space-md); }
.admin-drawer > summary {
  display: flex; align-items: center; gap: 9px; cursor: pointer; list-style: none;
  padding: 14px 2px; font-size: 14px; font-weight: 500; color: var(--color-text);
}
.admin-drawer > summary::-webkit-details-marker { display: none; }
.admin-drawer > summary::before {
  content: '›'; display: inline-block; color: var(--color-meta);
  transition: transform .15s ease; font-size: var(--text-lg); line-height: 1;
}
.admin-drawer[open] > summary::before { transform: rotate(90deg); }
.admin-drawer > summary .meta-text { font-weight: 400; }
.admin-drawer-body { padding-bottom: var(--space-md); }

@container (max-width: 680px) {
  /* Actions go under the row rather than squeezing the name, and the two
     metadata columns move below the person they describe. Nothing scrolls
     sideways, and the reading order is unchanged. */
  /* Three columns rather than two, so the status and the last-seen share one
     line instead of taking a line each — four stacked lines is a row you
     scroll past rather than scan. */
  .admin-person { grid-template-columns: auto minmax(0, 1fr) auto; }
  .admin-person-who { grid-column: 2 / -1; }
  .admin-person-status { grid-column: 2; flex-wrap: wrap; }
  .admin-person-seen { grid-column: 3; text-align: right; white-space: normal; }
  .admin-person-seen b { display: inline; }
  .admin-person-actions { grid-column: 1 / -1; justify-content: flex-start; flex-wrap: wrap; }
  /* `.btn` never wraps, which is right on a toolbar and wrong in a column
     narrower than the words. Below this width the label wraps and the button
     grows downwards, rather than the panel growing sideways. */
  .admin-person-actions .btn {
    white-space: normal; height: auto; min-height: 36px; padding: 6px 12px; line-height: 1.3;
  }

  .admin-event { grid-template-columns: minmax(0, 1fr); }
  .admin-event-when { text-align: left; white-space: normal; }
  .admin-event-when b { display: inline; }
}


/* Steps the orchestrator started close together in time — a visual hint
   that they ran together, not a guaranteed concurrency proof. */
.parallel-group { background: var(--color-tint); border-bottom: 1px solid var(--color-border); }
.parallel-group .list-row { background: transparent; border-bottom: 1px dashed var(--color-border); }
.parallel-group .list-row:last-child { border-bottom: 0; }
.list-row .task { font-size: var(--text-base); }
.section-block { margin-bottom: 28px; margin-top: 28px; }

/* ---- The board's card table.
   The first genuinely tabular view in the app: eight columns a person scans
   across, which .list-panel's flex rows cannot line up. Everything visual here
   — border, radius, surface, row divider — is the same tokens .list-panel uses,
   and the numbers match .md-table's cells so a table reads identically whether
   it came from a card row or from rendered markdown.
   The wrapper scrolls, not the page, for the same reason .md-table-wrap does:
   a long card title must not widen the whole screen sideways. ---- */
.data-table-wrap { overflow-x: auto; }
.data-table { border-collapse: collapse; width: 100%; font-size: var(--text-sm); }
.data-table th, .data-table td { padding: 11px 14px; text-align: left; border-bottom: 1px solid var(--color-border); vertical-align: middle; }
.data-table th {
  font-size: var(--text-2xs); letter-spacing: .05em; text-transform: uppercase;
  color: var(--color-meta); font-weight: 500; white-space: nowrap;
}
.data-table tbody tr { cursor: pointer; }
.data-table tbody tr:hover { background: var(--color-surface-alt); }
.data-table tbody tr:last-child td { border-bottom: 0; }

/* A date-grouped list's own section header — a table row spanning every
   column (leads.js's groupByDate), or a plain heading above a grid of cards
   (the Templates tab). Not clickable, not hoverable like the rows/cards
   around it — it is not a row, just where one date's worth of them starts. */
.data-table tbody tr.date-group-row { cursor: default; }
.data-table tbody tr.date-group-row:hover { background: none; }
.data-table tbody tr.date-group-row td {
  padding: 16px 14px 6px; border-bottom: 0; font-size: var(--text-2xs); font-weight: 600;
  letter-spacing: .05em; text-transform: uppercase; color: var(--color-text-muted);
}
.data-table tbody tr.date-group-row:first-child td { padding-top: 4px; }
.date-group-heading {
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--color-text-muted); margin: 22px 0 10px;
}
.date-group-heading:first-child { margin-top: 0; }

/* Overview's two-up rows (Build Progress + Currently Running; Pages +
   Recent Activity) — stacks to one column on a narrow window rather than
   squeezing both halves unreadably thin. minmax(0, 1fr), not plain 1fr: a
   bare 1fr track's minimum is its content's min-content, so one long
   unbreakable line (a run's full task/goal text) silently widens the whole
   column past the row's own ellipsis truncation instead of being clipped —
   minmax(0, ...) is what actually lets a track shrink below that. */
.overview-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; align-items: start; margin-bottom: 20px; }
@media (max-width: 760px) { .overview-grid { grid-template-columns: 1fr; } }

/* Memory tab (features/memory.js): the tree on the left, a selected item's
   real report on the right. The right column's own report content (a QA/
   audit report's monospace table) can be genuinely wide — minmax(0, ...),
   not a bare 1fr, is what lets that column actually shrink to the space
   available instead of the grid track stretching to fit its content and
   pushing the whole page into horizontal scroll. Below 760px the two
   columns no longer both fit at a readable width, so the detail stacks
   under the tree instead of beside it — same breakpoint as .overview-grid. */
.memory-grid { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 16px; align-items: start; }
@media (max-width: 760px) { .memory-grid { grid-template-columns: minmax(0, 1fr); } }

/* ---- Activity items ---- */
.filter-tabs { display: flex; align-items: center; gap: 18px; border-bottom: 1px solid var(--border-default); margin-bottom: var(--space-md); }
.filter-tab {
  border: 0; background: transparent; padding: 0 0 11px; margin-bottom: -1px;
  cursor: pointer; font-size: var(--text-base); color: var(--color-text-muted);
  border-bottom: 2px solid transparent; display: flex; align-items: center; gap: 7px;
}
.filter-tab.active { color: var(--color-text); border-bottom-color: var(--color-brand-secondary); }
/* The small number beside a tab label. Tight on purpose — it is a count, not a
   tile: no padding beyond a hair, and tabular digits so 1 and 11 do not shift the
   label beside them. */
.filter-tab .count {
  font-size: var(--text-xs); color: var(--color-meta);
  font-variant-numeric: tabular-nums; padding: 0; margin-left: 1px; background: none; border: 0;
}

/* A run's own Result/Transcript switch — two views of one already-expanded
   card, not page-level navigation, so it deliberately does not reuse
   .filter-tab's underline treatment: that reads clearly as a tab bar at the
   top of a whole screen, but the same thin underline gets lost sitting
   inside a small, already-bordered card. A compact segmented control —
   a bounded track with the active side filled in — reads as "these are two
   tabs" at a glance even at this size, which a bare underline did not. */
.run-detail-tabs {
  display: inline-flex; gap: 2px; padding: 3px; margin-bottom: 12px;
  background: var(--color-surface-alt); border-radius: var(--radius-control);
}
.run-detail-tab {
  border: 0; background: transparent; cursor: pointer; font-family: inherit;
  font-size: var(--text-sm); font-weight: 500; color: var(--color-text-muted);
  padding: 6px 14px; border-radius: var(--radius-sm); transition: background .12s ease, color .12s ease;
}
.run-detail-tab:hover:not(.active) { color: var(--color-text); }
.run-detail-tab.active {
  background: var(--color-surface); color: var(--color-text);
  box-shadow: 0 1px 2px rgba(17, 36, 70, .12);
}

/* Category filter chips (Sections/Templates) — a filled, pill-shaped chip
   button, distinct from .filter-tab's underline-tab treatment; both are
   real patterns already in this app, just for different screens. */
.category-chip {
  height: 32px; padding: 0 14px; border-radius: var(--radius-pill); font-size: var(--text-sm);
  border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-text-muted);
  cursor: pointer; font-family: inherit; transition: border-color .15s ease, background .15s ease;
}
.category-chip:hover { border-color: var(--border-on-brand); }
.category-chip.active { background: var(--color-tint); border-color: var(--color-brand-primary); color: var(--color-brand-primary); }

/* Theme stat cells — a true grid look (every cell bordered on top and on
   the right, except the last column), not just one outer divider. */
.theme-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); }
.theme-stat-grid > div { border-top: 1px solid var(--color-border); }
.theme-stat-grid > div:not(:last-child) { border-right: 1px solid var(--color-border); }

.run-card { border: 1px solid var(--color-border); border-radius: var(--radius-lg); overflow: hidden; background: var(--color-surface); margin-bottom: 12px; }
.run-toggle {
  width: 100%; display: flex; align-items: center; gap: 14px; padding: 15px 18px;
  border: 0; background: transparent; cursor: pointer; text-align: left; color: var(--color-text);
}
.run-avatar { width: 34px; height: 34px; flex: none; border-radius: var(--radius-avatar-sm); font-size: var(--text-sm); display: flex; align-items: center; justify-content: center; }
.run-toggle .chev { flex: none; transition: transform .15s ease; }
.run-toggle.open .chev { transform: rotate(0deg); }
.run-toggle:not(.open) .chev { transform: rotate(-90deg); }
.run-body-expanded { padding: 0 18px 18px 66px; }

/* A live run's own transcript, while it's still growing — bounded and
   scrollable in place, like a terminal pane, rather than an unbounded box
   that just pushes the rest of the page down as it grows. Kept scrolled to
   the latest output (see patchLiveSteps/render() in the JS) — the entire
   point is seeing what the agent is doing *right now* without having to go
   looking for it. */
.run-steps-live { max-height: 340px; overflow-y: auto; padding-right: 4px; }
.empty-state { padding: var(--space-xl) 0; font-size: var(--text-md); color: var(--color-meta); text-align: center; }

/* ---- Modal (also a card-like surface, at the top of the elevation scale) ---- */
.modal-overlay {
  position: fixed; inset: 0; background: var(--overlay-scrim);
  display: flex; align-items: center; justify-content: center; z-index: 50;
  animation: ws-fade .15s ease;
}
.modal-card {
  width: 480px; max-width: calc(100vw - 40px); max-height: calc(100vh - 60px); overflow: auto;
  background: var(--color-surface); border-radius: var(--radius-xl); box-shadow: var(--shadow-lg);
  padding: 26px; /* doesn't fit the --space-* scale exactly; kept literal rather than forced into it */
  animation: ws-pop .18s ease;
}
.modal-card h2 { margin: 0 0 6px; font-size: var(--text-2xl); font-weight: 400; }

/* The two lists in a delete confirmation (see modal.js openDangerModal). Kept
   visually distinct because they answer opposite questions, and the second one —
   what survives — is the one people are actually looking for. */
.danger-list { margin: 0 0 14px; }
.danger-list .eyebrow { display: block; margin-bottom: 6px; }
.danger-list ul { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 5px; }
.danger-list li {
  position: relative; padding-left: 18px;
  font-size: var(--text-base); line-height: 1.5; color: var(--color-body);
}
.danger-list li::before {
  position: absolute; left: 0; font-weight: 500;
}
.danger-list.gone li::before { content: '−'; color: var(--tone-error-fg); }
.danger-list.safe li::before { content: '✓'; color: var(--tone-success-fg); font-size: 11px; top: 1px; }

/* A destructive primary. The card modal's quiet Delete opens the confirmation;
   the button that actually does it is unmistakable. */
.btn-danger { background: var(--tone-error-fg); border-color: var(--tone-error-fg); color: #fff; }
.btn-danger:hover { filter: brightness(1.08); }
.modal-sub { margin: 0 0 var(--space-md); font-size: var(--text-base); color: var(--color-text-muted); }

/* A side drawer — same overlay/open/close lifecycle as .modal-overlay/
   .modal-card (openDrawer below reuses closeModal() itself), pinned to
   the right edge and full-height instead of centered, for a cluster of
   related actions that belong together but aren't one single task the
   way a modal's own form is. */
.drawer-overlay {
  position: fixed; inset: 0; background: var(--overlay-scrim);
  display: flex; justify-content: flex-end; z-index: 50;
  animation: ws-fade .15s ease;
}
.drawer-panel {
  width: 420px; max-width: calc(100vw - 40px); height: 100%; overflow: auto;
  background: var(--color-surface); box-shadow: var(--shadow-lg);
  padding: 26px; animation: drawer-slide-in .2s ease;
}
@keyframes drawer-slide-in { from { transform: translateX(100%); } to { transform: translateX(0); } }
.drawer-section { padding: 18px 0; border-bottom: 1px solid var(--border-faint); }
.drawer-section:first-of-type { padding-top: 0; }
.drawer-section:last-of-type { border-bottom: none; padding-bottom: 0; }
.drawer-section h3 { margin: 0 0 4px; font-size: var(--text-base); font-weight: 600; }
.drawer-section p { margin: 0 0 12px; font-size: var(--text-sm); color: var(--color-text-muted); line-height: 1.5; }

/* ---- Inputs ---- */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.field label { font-size: var(--text-xs); color: var(--color-label); text-transform: uppercase; letter-spacing: .05em; }
.field input, .field textarea, .field select {
  padding: var(--space-xs) 12px; border: 1px solid var(--border-medium); border-radius: var(--radius-md);
  font-size: var(--text-md); resize: vertical;
  /* Explicit, not inherited: a control with no background of its own is painted
     by the platform, which on a dark palette hands back a light grey box inside
     a dark form. `color-scheme: dark` on the root fixes the scrollbars and the
     dropdown list; the closed control itself still needs these two. */
  background: var(--color-surface); color: var(--color-text);
}
/* The native arrow is drawn by the platform in the platform's colours, so the
   select gets its own drawn one and hides that. */
.field select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--color-text-muted) 50%),
    linear-gradient(135deg, var(--color-text-muted) 50%, transparent 50%);
  background-position: calc(100% - 17px) center, calc(100% - 12px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 32px;
}
.field input:focus, .field textarea:focus, .field select:focus { outline: 2px solid var(--color-brand-primary); outline-offset: 1px; }
.field textarea { min-height: 90px; }
.field-hint { font-size: var(--text-xs); color: var(--color-meta); margin: 2px 0 0; }
/* A bare input/select outside a labeled .field — the invite form and the
   "new workspace" form (see workspaces.js) ask for one thing with a visible
   placeholder, so a <label> above it would just repeat the placeholder. */
.input {
  padding: var(--space-xs) 12px; border: 1px solid var(--border-medium); border-radius: var(--radius-md);
  font-size: var(--text-md); background: var(--color-surface); color: var(--color-text);
}
.input:focus { outline: 2px solid var(--color-brand-primary); outline-offset: 1px; }
/* An input sitting in a row of .btn-sm buttons — the search box and the sort
   menu on the Design screen. Matched to the small button's own height rather
   than given a size of its own, so the row lines up instead of nearly doing. */
.input-sm { height: 36px; padding: 0 10px; font-size: var(--text-base); }
/* Wraps rather than overflows. Without flex-wrap, a row of buttons wider than the
   modal spills *leftward* past the card edge under justify-content:flex-end —
   which is exactly what happened when the card modal gained a fifth button. */
.modal-actions { display: flex; flex-wrap: wrap; gap: var(--space-xs); justify-content: flex-end; margin-top: 4px; }
/* An action that belongs at the opposite end of the row from the rest. */
.modal-actions .push-left { margin-right: auto; }
.modal-error { font-size: var(--text-base); color: var(--tone-error-fg); margin: -8px 0 14px; }

/* Overview's prominent "start a workflow" entry point — a deliberately
   bigger, harder-to-miss treatment than a form squeezed into a half-width
   column, since that's exactly what made it easy to overlook before. */
.goal-hero { border: 1px solid var(--color-border); border-radius: var(--radius-xl); background: var(--color-surface); padding: 40px 32px 28px; margin-bottom: 20px; text-align: center; }
.goal-hero-heading { font-size: var(--text-3xl); font-weight: 500; color: var(--color-text); margin-bottom: 10px; }
.goal-hero-chips { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }

/* The slash-command autocomplete list — shown in normal document flow
   (not absolutely positioned) right under the goal box while typing
   "/partial-id", so it never needs a positioned-ancestor dance against the
   hero's own layout. */
.slash-suggestions { border: 1px solid var(--color-border); border-radius: var(--radius-md); margin: 8px 0; overflow: hidden; text-align: left; }
/* The positioning context for a composer's own suggestion menu. Must wrap the
   input, not the whole screen — a menu anchored to a distant ancestor lands in the
   wrong place the moment anything above it changes height.

   It carries the same 720px cap as .ask-input-bar, and that is what makes the menu
   the width of the composer: an absolutely positioned child stretches to its
   *positioning parent*, so an uncapped wrapper produced a menu spanning the whole
   pane above a 720px input. */
.composer-wrap { position: relative; max-width: 720px; margin-left: auto; margin-right: auto; }
/* Floating, so opening it moves nothing. It used to sit in the flow and take real
   height as soon as it opened, pushing the footer line and everything below it down
   by however many rows matched — so typing `/dev-` shifted the page under the
   cursor, then shifted it back on the next keystroke.

   Anchored to the *bottom* of the composer and growing upward: this box sits at the
   end of a conversation, so a downward menu would cover the text being typed, and
   near the window bottom would have nowhere to go. */
.slash-suggestions.floating {
  position: absolute; left: 0; right: 0; bottom: calc(100% + 6px); z-index: 30;
  margin: 0; max-height: 274px; overflow-y: auto;
  background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.slash-suggestion-row {
  display: flex; align-items: baseline; gap: 8px; width: 100%; text-align: left;
  padding: 8px 12px; border: 0; border-bottom: 1px solid var(--color-border);
  background: var(--color-surface); cursor: pointer; font-family: inherit; font-size: var(--text-sm); color: var(--color-text);
}
.slash-suggestion-row:last-child { border-bottom: 0; }
.slash-suggestion-row:hover { background: var(--color-surface-alt); }

/* Ask an agent's own chat transcript — real workflow turns (see
   askWorkflowTurnHtml in projects.js): "You" and the workflow's own final
   summary are plain .list-row turns, and every specialist step with a real
   run renders as the exact same .run-card Activity itself uses (see
   runCardHtml in runs.js) — not a bespoke conversation widget. */
.ask-transcript { display: flex; flex-direction: column; gap: 14px; margin-bottom: 20px; max-width: 720px; margin-left: auto; margin-right: auto; }
.ask-transcript .list-row { border: 1px solid var(--color-border); border-radius: var(--radius-lg); }
/* Composer — one row at rest (a pill), the textarea grows upward while the
   circular send button stays bottom-aligned. Shared by the "New ask" hero
   and an existing thread's reply composer, so both look and behave the
   same everywhere a message is actually typed to an agent.
   The radius is capped rather than left at --radius-pill: a 999px corner
   reads as a pill only while the bar is one line tall. The moment the
   textarea wraps to several lines, all four 999px corners get forced into
   visible arcs on a much taller box, which is what turned a long prompt
   into a stretched oval instead of a rounded rectangle. */
.ask-input-bar {
  max-width: 720px; margin: 0 auto;
  display: flex; align-items: flex-end; gap: 8px;
  border: 1px solid var(--border-medium); border-radius: min(var(--radius-pill), 22px); background: var(--color-surface);
  box-shadow: var(--shadow-sm); padding: 10px 10px 10px 20px;
}
.ask-input-bar textarea {
  display: block; flex: 1; min-width: 0; border: none; background: transparent; resize: none;
  font-family: inherit; font-size: 15px; line-height: 1.5; color: var(--color-text);
  padding: 8px 0; max-height: 200px; min-height: 22px; outline: none;
}
.ask-input-bar textarea::placeholder { color: var(--color-meta); }
.ask-input-footer { max-width: 720px; margin: 8px auto 0; text-align: left; }
.ask-send-btn {
  flex: none; width: 36px; height: 36px; padding: 0; border: 0; border-radius: var(--radius-pill);
  background: var(--color-brand-primary); color: #fff; cursor: pointer; font-family: inherit;
  display: flex; align-items: center; justify-content: center; transition: background .15s ease;
}
.ask-send-btn:hover:not(:disabled) { background: var(--color-brand-secondary); }
.ask-send-btn:disabled { background: var(--border-medium); cursor: not-allowed; }

.ask-attach-btn {
  flex: none; width: 30px; height: 30px; padding: 0; margin-bottom: 3px; border: 0; border-radius: var(--radius-pill);
  background: transparent; color: var(--color-text-muted); cursor: pointer; font-family: inherit;
  display: flex; align-items: center; justify-content: center; transition: background .15s ease, color .15s ease;
}
.ask-attach-btn:hover { background: var(--color-surface-alt); color: var(--color-text); }

/* The composer while an agent is working: the same bar in the same place, with
   the status where the text would be and Pause/Stop where Send would be. Held to
   .ask-input-bar's own 720px so the box does not change width when a task starts
   or finishes — that jump was most of what made the task page feel unsettled. */
.composer-working { margin-top: 24px; }
.ask-input-bar.is-working {
  padding: 9px 10px 9px 18px; align-items: center; gap: 10px;
  background: var(--color-surface-alt); box-shadow: none;
}
.composer-status { flex: 1; min-width: 0; display: flex; align-items: center; gap: 9px; }
.composer-status-text {
  font-size: var(--text-sm); color: var(--color-text-muted); line-height: 1.45;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Three dots, offset in phase — the plainest "a reply is coming" there is, and
   it reads as activity at a glance without a spinner's suggestion of progress
   toward a known end. */
.composer-dots { flex: none; display: inline-flex; align-items: center; gap: 3px; }
.composer-dots i {
  width: 4px; height: 4px; border-radius: 50%; background: var(--color-brand-primary);
  animation: composer-dot 1.1s ease-in-out infinite;
}
.composer-dots i:nth-child(2) { animation-delay: .18s; }
.composer-dots i:nth-child(3) { animation-delay: .36s; }
@keyframes composer-dot { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }
/* Paused is deliberately still: a moving indicator on a paused task would say
   the opposite of what is true. */
.composer-paused-dot {
  flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: var(--tone-warn-fg); box-shadow: 0 0 0 3px var(--tone-warn-bg);
}
@media (prefers-reduced-motion: reduce) {
  .composer-dots i { animation: none; opacity: .7; }
}

.composer-wedged {
  max-width: 720px; margin: 24px auto 0; padding: 16px 18px;
  border: 1px solid var(--tone-warn-fg); border-radius: var(--radius-md);
  background: var(--tone-warn-bg);
  display: flex; flex-direction: column; align-items: flex-start; gap: 11px;
}

/* A staged file, not yet sent — sits above the input bar it belongs to,
   same 720px column so it lines up with the bar exactly. */
.attachment-chip-row { display: flex; flex-wrap: wrap; gap: 8px; max-width: 720px; margin: 0 auto 10px; }
.attachment-chip {
  display: inline-flex; align-items: center; gap: 7px; font-size: var(--text-sm); max-width: 100%;
  background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius-pill);
  padding: 5px 6px 5px 12px; color: var(--color-text); text-decoration: none;
}
/* An already-sent chip is a real link (see attachmentChipHtml) — same look
   as a staged one, just clickable, so an attachment never reads as a
   different kind of thing depending on which side of "sent" it happens to
   be on. */
a.attachment-chip:hover { border-color: var(--border-on-brand); }
.attachment-chip-name { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attachment-chip-remove {
  flex: none; width: 20px; height: 20px; border: 0; border-radius: 50%; background: var(--color-surface);
  color: var(--color-text-muted); cursor: pointer; font-size: 14px; line-height: 1; display: flex; align-items: center; justify-content: center;
}
.attachment-chip-remove:hover { background: var(--tone-error-bg); color: var(--tone-error-fg); }

/* A whole folder or a zip's own worth of attachments — one real, clickable
   summary that expands to the same real chips a plain attachment gets,
   rather than a wall of them shown all at once. */
.attachment-batch { display: flex; flex-direction: column; gap: 0; max-width: 100%; }
.attachment-batch-summary {
  display: inline-flex; align-items: center; gap: 7px; font-size: var(--text-sm); max-width: 100%;
  background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius-pill);
  padding: 5px 10px 5px 12px; color: var(--color-text); cursor: pointer; font-family: inherit;
}
.attachment-batch-summary:hover { border-color: var(--border-on-brand); }
.attachment-batch-summary .chev { flex: none; color: var(--color-text-muted); }

/* ---------------------------------------------------------------------------
   Agent answers (see public/js/markdown.js). Everything an agent writes comes
   back as Markdown, so every screen that shows one of those answers — a
   Workflow Summary turn, a run result, a recorded memory, a pending finding —
   wraps it in .md-body and gets the same reading experience: a table reads as
   a table, a heading as a heading, and a bullet list as a list, instead of the
   raw `**`/`|---|` text these used to render.

   Sized one step down from body prose on purpose: these blocks sit INSIDE
   cards that already have their own heading and label, so an agent's own `#`
   heading has to read as subordinate to the card holding it, never as a peer.
   --------------------------------------------------------------------------- */
/* break-word, not anywhere: a long token (a URL, a theme slug) still breaks
   rather than pushing the card sideways, but ordinary text keeps breaking at
   real word boundaries. */
.md-body { font-size: var(--text-base); line-height: 1.65; color: var(--color-body); overflow-wrap: break-word; }
.md-body > *:first-child { margin-top: 0; }
.md-body > *:last-child { margin-bottom: 0; }
.md-body p { margin: 0 0 10px; }

.md-body .md-h { font-weight: 600; color: var(--color-text); line-height: 1.35; margin: 18px 0 8px; }
.md-body .md-h1 { font-size: var(--text-lg); }
.md-body .md-h2 { font-size: var(--text-md); }
.md-body .md-h3,
.md-body .md-h4,
.md-body .md-h5,
.md-body .md-h6 { font-size: var(--text-base); }
/* An answer that opens on a heading shouldn't push itself off its own label. */
.md-body > .md-h:first-child { margin-top: 0; }

.md-body strong { font-weight: 600; color: var(--color-text); }
.md-body em { font-style: italic; }
.md-body del { color: var(--color-meta); }
.md-body a { color: var(--color-brand-primary); text-decoration: underline; text-underline-offset: 2px; }
.md-body a:hover { color: var(--color-brand-secondary); }

.md-body .md-list { margin: 0 0 10px; padding-left: 20px; }
.md-body .md-list li { margin: 3px 0; }
/* A nested list belongs to the item above it — no extra gap of its own. */
.md-body .md-list .md-list { margin: 3px 0 0; }
.md-body ul.md-list { list-style: disc; }
.md-body ol.md-list { list-style: decimal; }

.md-body .md-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em;
  background: var(--color-surface-alt); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm); padding: 1px 5px; color: var(--color-text);
}
.md-body .md-pre {
  margin: 0 0 12px; padding: 12px 14px; overflow-x: auto;
  background: var(--color-surface-alt); border: 1px solid var(--color-border); border-radius: var(--radius-lg);
}
.md-body .md-pre code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--text-sm);
  line-height: 1.6; color: var(--color-text); white-space: pre;
}

.md-body .md-quote {
  margin: 0 0 12px; padding: 2px 0 2px 14px;
  border-left: 3px solid var(--border-medium); color: var(--color-text-muted);
}

.md-body .md-hr { border: 0; border-top: 1px solid var(--color-border); margin: 16px 0; }

/* The wrapper scrolls, not the page: a wide agent table stays inside its own
   card instead of stretching the whole column sideways. */
.md-body .md-table-wrap {
  margin: 0 0 12px; overflow-x: auto;
  border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: var(--color-surface);
}
.md-body .md-table { border-collapse: collapse; width: 100%; font-size: var(--text-sm); }
.md-body .md-table th,
.md-body .md-table td { padding: 9px 13px; text-align: left; border-bottom: 1px solid var(--color-border); vertical-align: top; }
.md-body .md-table th {
  background: var(--color-surface-alt); color: var(--color-label); font-weight: 500;
  font-size: var(--text-xs); letter-spacing: .04em; text-transform: uppercase; white-space: nowrap;
}
.md-body .md-table tbody tr:last-child td { border-bottom: 0; }
/* A slug in a table cell must not be hyphenated across two lines
   ("twentytwentyfi / ve") just because a 100%-width table squeezed its
   column — the cell holds its width and the wrapper above scrolls instead. */
.md-body .md-table .md-code { white-space: nowrap; }

/* ---------------------------------------------------------------------------
   Writing Markdown (see public/js/markdown-editor.js)

   The other half of the block above: one box that a card's issue is written in,
   with the same renderer behind its Preview as the screens that show it
   afterwards. It reads as a single control — the toolbar, the writing surface
   and the preview share one border and one focus ring, so the buttons belong to
   the box rather than floating above it.
   --------------------------------------------------------------------------- */
.md-editor-box {
  border: 1px solid var(--border-medium); border-radius: var(--radius-md);
  background: var(--color-surface); overflow: hidden;
}
.md-editor-box:focus-within { outline: 2px solid var(--color-brand-primary); outline-offset: 1px; }

.md-editor-bar {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs);
  flex-wrap: wrap; padding: 6px 8px;
  background: var(--color-surface-alt); border-bottom: 1px solid var(--color-border);
}
.md-editor-tools { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }

.md-editor-tool {
  min-width: 30px; height: 28px; padding: 0 7px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius-sm); background: transparent;
  color: var(--color-text-muted); font-family: inherit; font-size: var(--text-sm);
  line-height: 1; cursor: pointer;
}
.md-editor-tool:hover { background: var(--color-surface); color: var(--color-text); }

.md-editor-tabs { display: flex; gap: 2px; }
.md-editor-tab {
  height: 28px; padding: 0 11px; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-meta);
  font-family: inherit; font-size: var(--text-xs); cursor: pointer;
}
.md-editor-tab:hover { color: var(--color-text); }
.md-editor-tab.is-active { background: var(--color-surface); color: var(--color-text); box-shadow: var(--shadow-xs); }

/* The box already draws the border and the focus ring, so the textarea inside
   it draws neither — otherwise there are two of each, one inside the other. */
.field.md-editor .md-editor-box textarea {
  display: block; width: 100%; border: 0; border-radius: 0; background: transparent;
  padding: 12px 14px; font-family: inherit; font-size: var(--text-md); line-height: 1.6;
}
.field.md-editor .md-editor-box textarea:focus { outline: 0; }
/* A placeholder several lines long is an example, not an instruction — it has
   to stay legible as prose but never be mistaken for text already typed. */
.field.md-editor .md-editor-box textarea::placeholder { color: var(--color-meta); opacity: 1; }

/* Scrolls at the height the writing surface would have had, so switching to
   Preview and back does not move everything below it up and down the modal. */
.md-editor-preview { padding: 12px 14px; min-height: 120px; max-height: 340px; overflow-y: auto; }
.md-editor { margin-bottom: 16px; }

/* ---- Sign-in — the whole app while nobody is signed in. Deliberately the
   only screen with no sidebar and no navigation: there is exactly one thing
   to do here, and every other endpoint answers 401 until it is done. ---- */
#app.signed-out #sidebar { display: none; }
#app.signed-out #main { margin: 8px; }

.signin-screen {
  min-height: 100%; display: grid; place-items: center;
  padding: var(--space-xl) var(--space-md); animation: ws-in .25s ease;
}
.signin-card { width: min(25rem, 100%); text-align: center; }
.signin-mark {
  width: 46px; height: 46px; margin: 0 auto var(--space-md);
  display: grid; place-items: center; border-radius: var(--radius-avatar-lg);
  background: var(--color-brand-secondary); color: #fff;
  font-size: var(--text-lg); font-weight: 600; letter-spacing: .5px;
}
.signin-card h1 { margin: 0 0 6px; font-weight: 500; font-size: var(--text-2xl); letter-spacing: -.2px; }
.signin-card > p { margin: 0 0 var(--space-lg); font-size: var(--text-lg); color: var(--color-text-muted); }
.signin-btn { width: 100%; }
.signin-note {
  margin: var(--space-sm) 0 0; font-size: var(--text-base);
  line-height: 1.5; color: var(--color-text-muted);
}
.signin-error {
  margin: var(--space-sm) 0 0; padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-md); font-size: var(--text-base);
  background: var(--tone-error-bg); color: var(--tone-error-fg); text-align: left;
}

/* ---- Workspace bar + sync bar ----------------------------------------------
   The workspace on screen, your level in it, and an honest sync state — both
   pinned in the sidebar rather than hidden in a menu. Once somebody belongs to
   two workspaces every other screen is ambiguous without the first, and a sync
   state nobody can see is a sync state nobody trusts. ------------------------ */
.workspace-bar {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 7px 8px; margin-bottom: 8px;
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  background: var(--color-surface); cursor: pointer; font: inherit; text-align: left;
  -webkit-app-region: no-drag;
}
.workspace-bar:hover { border-color: var(--color-brand-primary); }
.workspace-bar-mark {
  flex: none; width: 22px; height: 22px; border-radius: 6px;
  background: var(--color-brand-primary); color: #fff;
  display: grid; place-items: center; font-size: 11px; font-weight: 600;
}
.workspace-bar-text { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.workspace-bar-text .name {
  font-size: 12.5px; color: var(--color-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.workspace-bar-text .level { font-size: 10.5px; color: var(--color-text-muted); }
.workspace-bar-badge {
  flex: none; min-width: 16px; height: 16px; padding: 0 5px; border-radius: 999px;
  background: var(--tone-warn-bg); color: var(--tone-warn-fg);
  font-size: 10px; display: grid; place-items: center;
}
.workspace-notice {
  margin: 0 0 8px; padding: 6px 8px; border-radius: var(--radius-md);
  background: var(--tone-warn-bg); color: var(--tone-warn-fg); font-size: 11px; line-height: 1.4;
}

/* A notice that belongs to the screen rather than the sidebar: a workspace
   that is read-only at this level, or a project whose setup did not finish.
   It stays put and stays readable — the point of both is that somebody can
   still read it a minute later, when they wonder why nothing is happening. */
.screen-notice {
  margin: 0 0 16px; padding: 10px 14px; border-radius: var(--radius-md);
  background: var(--tone-warn-bg); color: var(--tone-warn-fg);
  font-size: 13px; line-height: 1.5;
}
.screen-notice strong { font-weight: 600; }
.screen-notice .screen-notice-next { display: block; margin-top: 4px; opacity: .85; }
/* A notice with a real dismiss control rather than only clearing itself
   once something else happens — a one-time confirmation (success tone) or a
   standing "you'll likely hit this again" signal (default warn tone, see
   usageLimitBannerHtml) both have nothing further to wait on. Layout only;
   the tone comes from .screen-notice's own default or a color modifier
   like -success below, so either can use this without duplicating colors. */
.screen-notice-dismissible {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.screen-notice-dismissible button {
  background: transparent; border: 0; color: inherit; opacity: .7; cursor: pointer;
  font-size: 16px; line-height: 1; padding: 0 2px; flex: none;
}
.screen-notice-dismissible button:hover { opacity: 1; }
/* A real thing that just succeeded, not a standing warning — same box,
   success tone instead of the default warn. */
.screen-notice-success { background: var(--tone-success-bg); color: var(--tone-success-fg); }
/* A hard stop nothing will get past right now (a plan usage limit, a rate
   limit) — same box, error tone and a visible border instead of the default
   warn, so this reads as more urgent than an ordinary standing caution. */
.screen-notice-error {
  background: var(--tone-error-bg); color: var(--tone-error-fg);
  border: 1px solid var(--tone-error-fg); font-weight: 500;
}

/* The people/invite screen (see renderMembersView in workspaces.js) — the
   same header contract every other screen gets from .view/.view h1/
   .view .subtitle, under its own name since this one also needs the
   notice/section pieces above it. */
.screen { padding: var(--space-xl) 52px 56px; max-width: 1120px; animation: ws-in .25s ease; }
.screen-header { margin: 0 0 var(--space-lg); }
.screen-header h1 { margin: 0 0 8px; font-weight: 300; font-size: var(--text-4xl); line-height: 1.15; letter-spacing: -.5px; }
.screen-sub { margin: 0; font-size: var(--text-lg); color: var(--color-text-muted); }

.sync-bar {
  display: flex; align-items: center; gap: 7px; width: 100%;
  padding: 6px 8px; margin-top: 8px;
  border: 0; border-top: 1px solid var(--border-faint); border-radius: 0;
  background: transparent; cursor: pointer; font: inherit; text-align: left;
  -webkit-app-region: no-drag;
}
.sync-bar:hover .sync-bar-text { color: var(--color-text); }
.sync-bar-text { flex: 1; min-width: 0; font-size: 11px; color: var(--color-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.workspace-row { width: 100%; border: 0; background: transparent; cursor: pointer; font: inherit; text-align: left; }
.workspace-row:hover { background: var(--color-surface-alt, rgba(0,0,0,.03)); }
.workspace-row.current .task { font-weight: 500; color: var(--color-brand-primary); }

.modal-section { margin: 0 0 6px; font-size: 12px; font-weight: 500; color: var(--color-text-muted); }
.screen-section { margin: 0 0 10px; font-size: 14px; font-weight: 500; }

/* ---------------------------------------------------------------------------
   Previewing the app as a role

   The one place in this app where a banner is not a notice but a **mode**.
   While it is on screen the app is deliberately lying to you: it is drawing
   what somebody else would see, and pressing anything is refused. That has to
   be impossible to lose track of, so the bar is sticky, full width, carries the
   role's name in bold, and has its own way out on it.

   Deliberately not the same tone as any other banner in the app. The read-only
   notice, the sync bar and the usage-limit notice are all information; this one
   changes what every other pixel on the screen means, and reading it as "some
   information" is exactly the mistake it must not invite. Hence the striped
   edge, which nothing else here uses.
   --------------------------------------------------------------------------- */

.role-preview-bar {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 18px;
  padding: 11px 14px;
  border: 1px solid var(--tone-warn-fg, #a15c00);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: var(--tone-warn-bg, #fff6e5);
  color: var(--tone-warn-fg, #a15c00);
  font-size: var(--text-sm, 13px);
  line-height: 1.45;
  /* The stripe. Not decoration: it is what stops this reading as one more
     coloured notice among the app's several. */
  background-image: repeating-linear-gradient(
    135deg,
    transparent 0 9px,
    rgba(161, 92, 0, 0.06) 9px 18px
  );
}

.role-preview-eye {
  display: flex;
  flex: none;
  align-items: center;
}

.role-preview-text {
  flex: 1;
  min-width: 0;
}

.role-preview-text strong {
  display: block;
  font-weight: 600;
}

.role-preview-exit {
  flex: none;
}

@media (max-width: 640px) {
  /* A narrow window puts the way out under the sentence rather than squeezing
     both onto one line, because the way out is the part that must stay
     obvious. */
  .role-preview-bar {
    flex-wrap: wrap;
  }

  .role-preview-exit {
    width: 100%;
  }
}

/* ---------------------------------------------------------------------------
   The role maker's grid

   Twenty rows against five columns — a hundred boxes, which is exactly as many
   as it takes to become unreadable. Three things keep it readable, and each is
   doing real work rather than decoration:

     · the header row sticks, or the bottom half of the grid is five unlabelled
       columns;
     · the name column sticks sideways, so a narrow window scrolls the boxes
       and never the row you are reading;
     · a row with nothing ticked dims and says "hidden completely" on the row
       itself. That is the most consequential state on the screen and the
       easiest to set by accident, so it is stated where it happens rather than
       in a tooltip.

   See docs/role-maker-design.md for the layout this implements and why.
   --------------------------------------------------------------------------- */

.role-grid {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm, 13px);
}

.role-grid th,
.role-grid td {
  padding: 6px 10px;
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}

.role-grid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--color-surface);
  font-weight: 600;
  color: var(--color-text-muted);
  border-bottom: 1px solid var(--color-border-medium, var(--color-border));
}

.role-grid tbody th {
  /* Pinned sideways, and it needs its own background: without one the boxes
     scroll *underneath* the name and the column becomes unreadable. */
  z-index: 1;
  min-width: 11rem;
}

.role-grid input[type="checkbox"] {
  width: 15px;
  height: 15px;
  cursor: pointer;
}

/* The live summary. A panel rather than a line of small print, because it is
   the part of this screen that matters most: somebody building a role for a
   client should be able to answer "what will this person see?" from it without
   reading the grid at all. */
.role-summary {
  padding: 12px 14px;
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-brand-primary);
  border-radius: var(--radius-sm);
  background: var(--color-tint, var(--color-surface));
  line-height: 1.5;
}

/* "Built in" on a role row. A badge and not a disabled state: the four are
   shown in full and read exactly like the custom ones, because seeing what
   Viewer allows is often how somebody works out they need no custom role. */
.badge {
  flex: none;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--tone-info-bg, #eef2ff);
  color: var(--tone-info-fg, #3f4a9b);
  font-size: 11.5px;
  font-weight: 500;
}

/* A name that opens something, styled as the text it already is rather than as
   a button — the row is a list row, and a button in the middle of one reads as
   an action rather than as a way in. */
.link-button {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--color-border-medium, var(--color-border));
  text-underline-offset: 2px;
}

.link-button:hover {
  text-decoration-color: currentColor;
}

/* ---------------------------------------------------------------------------
   The Workspace page

   Three visual levels: the app's own rail, this page's own narrow navigation,
   and the section on the right. The rail chooses Workspace, this navigation
   chooses the section, the panel does the work.

   The name and the role you hold stay in the left panel at all times, which is
   what lets every section drop its own big header. Repeating "Deepak's
   workspace" at the top of five panels is five chances to read the same fact.

   ## Why container queries rather than width breakpoints

   The same reason the Admin list gives: the thing that gets narrow here is this
   page, not the window. The app shell keeps its full sidebar at every size, so a
   `@media` rule fires at the wrong moment on a laptop with the inspector open
   and never fires at all for a panel that is 400px wide inside a 1400px window.
   `container-type` asks the only question that matters — how wide is *this* —
   and answers it the same way on a phone and in a squeezed column.
   --------------------------------------------------------------------------- */

.split-screen {
  container-type: inline-size;
  /* A column, so `.split-page-above` can take the height it needs and the grid
     below it takes what is left. Without this the grid's own
     `min-height: calc(100vh - 112px)` is measured from the top of the page
     rather than from the top of the grid, so any strip above it pushes the
     bottom of the column rule past the fold and the page grows a scrollbar
     it has nothing to scroll to. */
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - 112px);
  /* Wider than the default screen, and by an amount that is worked out rather
     than picked: the 52px gutters, the 200px rail and its own gutter come to
     324px, so this leaves the content column its 1100–1200px of readable width
     on a monitor big enough to give it. */
  max-width: 1460px;
}

.split-page {
  position: relative;
  display: grid;
  /* 200px, holding 180px of items and a 20px gutter before the rule. A rail,
     not a second sidebar: five words, an icon and a count fit, and every pixel
     not spent here goes to the one column doing work. */
  grid-template-columns: 200px minmax(0, 1fr);
  /* No gap — each column carries its own gutter, so the rule between them lands
     in the middle of a known 40px rather than wherever a gap happened to fall. */
  gap: 0;
  align-items: start;
  /* `align-items` handles one row's cross-axis; this handles the rows
     themselves. It matters because `.split-screen` is a flex column that hands
     this grid whatever height is left over, and a grid's default
     `align-content` stretches auto-sized rows to absorb it. Harmless in the
     two-column layout, where there is only one row — but the narrow layout has
     two, so the strip of sections and the panel below it each grew by half the
     leftover height and opened a screen-tall gap between them. */
  align-content: start;
  /* The rule reaches the bottom of the panel even when a short section does not:
     #main's 8px margins and this screen's own 40/56 padding come to 112px. A
     divider that stops two-thirds of the way down reads as an unfinished box
     rather than as the edge of a column. */
  min-height: calc(100vh - 112px);
}
/* Drawn here rather than as a border on either column because both of them are
   `start`-aligned — the navigation is sticky and must stay short enough to
   stick, and a border on a short element is a short border. */
.split-page::before {
  content: ''; position: absolute; top: 0; bottom: 0; left: 200px;
  width: 1px; background: var(--color-border);
}

.split-nav {
  position: sticky;
  /* Stays put while a long section scrolls: which workspace you are in and what
     you are allowed to do in it must not scroll off the top of the one page
     that is about it. */
  top: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  padding-right: var(--space-md);
}

/* The name, as a button, because it is also the way to another workspace — the
   switcher was the only way in to any of this until this page existed. */
.split-identity {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: 0; border: 0; background: none; cursor: pointer; text-align: left;
  color: var(--color-text); font-family: inherit;
}
.split-identity-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-lg); font-weight: 500; letter-spacing: -.2px;
}
/* Settings and the Library have no second one to switch to, so their title is
   text rather than a button. Same size and weight either way — what changes is
   the chevron and the pointer, which are the two things that promise a click. */
.split-identity.is-static { cursor: default; }
.split-identity-name.is-mono { font-family: var(--font-mono); font-size: var(--text-md); }
.split-identity svg { flex: none; opacity: .5; }
.split-identity:hover svg { opacity: 1; }
.split-identity:hover .split-identity-name { color: var(--color-brand-primary); }
.split-identity-meta {
  margin: 3px 0 0; font-size: var(--text-xs); color: var(--color-meta);
}
.split-nav-rule {
  display: block; height: 1px; margin: var(--space-xs) 0 6px;
  background: var(--color-border);
}

.split-nav-items { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

/* Deliberately not `.nav-item`: that is the app's rail, and a second copy of it
   nested inside the page would read as a rail that had grown a branch. Two
   pixels shorter than that rail and no icon — five words do not need five
   pictures, and this list is a rail inside a page rather than the page's own. */
.split-nav-item {
  display: flex; align-items: center; gap: 9px;
  width: 100%; height: 32px; padding: 0 9px;
  border: 0; border-radius: var(--radius-control); cursor: pointer;
  font-family: inherit; font-size: var(--text-base); text-align: left;
  color: var(--color-text-muted); background: transparent;
}
/* `--surface-veil-hover` is the rail's own hover and it is a white wash — right
   on the sidebar's grey gradient, invisible here on a white panel. */
.split-nav-item:hover { background: var(--color-surface-alt); color: var(--color-text); }
/* A tint, the brand blue, and a 3px bar down the left edge. "Which section am I
   in" is the one question this navigation has to answer, and the answer used to
   be white-on-white: `--color-surface` is a raised pill on the sidebar's grey
   and nothing at all on this panel, which left a 2px hairline doing all the
   work. */
.split-nav-item.active {
  background: var(--color-tint); color: var(--color-brand-primary); font-weight: 600;
  box-shadow: inset 3px 0 0 var(--color-brand-primary);
}
.split-nav-item svg { flex: none; opacity: .8; }
.split-nav-item.active svg { opacity: 1; }
.split-nav-label {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.split-nav-count {
  flex: none; min-width: 19px; height: 18px; padding: 0 6px; border-radius: var(--radius-pill);
  background: var(--tone-info-bg); color: var(--tone-info-fg); font-size: var(--text-2xs);
  display: flex; align-items: center; justify-content: center;
  font-variant-numeric: tabular-nums;
}

.split-screen > .split-page { flex: 1 1 auto; min-height: 0; }

.split-panel { container-type: inline-size; min-width: 0; padding-left: var(--space-md); }

/* ---- The strip that spans both columns ----
   For the few things that belong to the *screen* rather than to a section of
   it: Admin's six figures, which answer "is the app being used" and should not
   be somewhere you have to navigate to, and an audit lead's own header. Content
   that belongs to one section goes in that section — see req.md, Part 5. */
.split-page-above { flex: none; margin-bottom: var(--space-lg); }

/* ---- A group's heading, for a column carrying two of them ----
   The website screen's column lists a website's sections and the selected
   task's, and they are different scales — that is the distinction the one tab
   row it replaces could not make, because it silently swapped its own contents
   instead of showing both. Same eyebrow the app's own rail uses for WEBSITES
   and LIBRARY, so a group heading in a page's column reads as the same object
   as a group heading in the sidebar. */
.split-nav-group-label {
  padding: 0 9px; margin: var(--space-sm) 0 5px;
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--color-meta);
}
.split-nav-group-label:first-child { margin-top: 0; }

/* ---- Collapsed to icons ----
   One case only: the website screen with the Inspector open below 1280px, where
   the section column, the content and a 320px panel do not all fit. The column
   narrows to its icons rather than disappearing — navigation that vanishes is
   how somebody gets lost, and the active state is still legible at 48px. The
   label survives in the tooltip and in the accessible name, both of which
   `sectionNavHtml` already writes. */
.split-page.is-icons { grid-template-columns: 48px minmax(0, 1fr); }
.split-page.is-icons::before { left: 48px; }
.split-page.is-icons .split-nav { padding-right: 10px; align-items: center; }
.split-page.is-icons .split-nav-item { justify-content: center; padding: 0; gap: 0; }
.split-page.is-icons .split-nav-label,
.split-page.is-icons .split-nav-count,
.split-page.is-icons .split-nav-group-label,
.split-page.is-icons .split-identity-name,
.split-page.is-icons .split-identity-meta { display: none; }
.split-page.is-icons .split-panel { padding-left: 14px; }

/* Every section reads the same way: what it is, one sentence of why, and the one
   action that belongs to it on the right. */
.split-section-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-sm); flex-wrap: wrap; margin: 0 0 var(--space-md);
}
/* 26px, not the 36px of a page title. The rail already says which workspace this
   is, so this line names a section inside one — and at 36 it pushed the four
   figures it introduces a third of the way down the panel. */
/* Both levels, drawn the same. Which one a section uses is about the document
   outline, not about the size — see `sectionHeadHtml`. */
.split-section-head h1,
.split-section-head h2 {
  margin: 0 0 4px; font-weight: 400; font-size: 26px; line-height: 1.2;
  letter-spacing: -.3px;
}
/* Held to the app's own reading measure, the same 74ch `.view .subtitle` uses.
   Without it a long explanation ran the full width of the panel, which took the
   whole first row of the flex head — so the section's one action wrapped
   underneath the text and left-aligned, instead of staying in the upper right
   where every other primary action in this app is. */
.split-section-sub { margin: 0; font-size: var(--text-md); color: var(--color-text-muted); max-width: 74ch; }
.workspace-block-head { margin-top: var(--space-md); }
.workspace-note { margin: var(--space-xs) 0 0; }

/* Four figures, one row, every tile the same height.
   The label is held to one line rather than given room for two. Reserving the
   second line is what made these tiles half again as tall as the four lines of
   text inside them, and one line puts every figure on the same baseline anyway —
   which was the only reason for the reservation. */
.workspace-stats { grid-template-columns: repeat(auto-fit, minmax(min(184px, 100%), 1fr)); }
/* A grid rather than the base tile's flex column, so the icon can sit beside the
   label on one line without a wrapper coming between the label and its figure. */
.workspace-stats .stat {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: center; column-gap: 8px; row-gap: 4px; padding: 18px;
}
.workspace-stats .stat-mark { grid-row: 1; grid-column: 1; }
.workspace-stats .stat-label {
  grid-row: 1; grid-column: 2;
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.workspace-stats .stat b, .workspace-stats .stat > span:last-child { grid-column: 1 / -1; }
.workspace-stats .stat b { overflow-wrap: anywhere; }
.workspace-stats .stat b.is-word {
  font-family: inherit; font-size: 22px; font-weight: 500; letter-spacing: -.3px;
}

/* One card, two rows, a hairline between them — the app's own list panel, at a
   row height that suits two facts. Two separate cards for two fields is how a
   page ends up with everything on it looking like a card. */
.workspace-about > .list-row { padding: 11px var(--space-md); }
.workspace-about > .list-row > .meta-text { text-align: right; }

/* Between the roles tally and what the workspace itself is: two different
   subjects, and the only rule on the page that separates them. */
.split-section-rule {
  display: block; height: 1px; margin: var(--space-md) 0 0;
  background: var(--color-border);
}

.workspace-role-chips { margin: 0; }
.workspace-chip-count { font-weight: 600; font-variant-numeric: tabular-nums; }
.filter-chip.on { background: var(--color-brand-secondary); color: #fff; border-color: var(--color-brand-secondary); }

/* The search box and the count, on one line. `min-width: 0` is the part that
   matters — an input's default intrinsic minimum is what makes a search box the
   widest thing in a narrow column. */
.workspace-toolbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 12px;
}
.workspace-search { flex: 1 1 240px; min-width: 0; max-width: 280px; width: auto; }

/* ---- One person, one row ---- */
.workspace-person {
  display: grid;
  /* Fixed width on the role and the menu, so every role and every `···` starts
     at the same place down the whole list. With `auto` they line up with
     nothing, and a column that does not line up has to be read rather than
     scanned — which is the whole point of showing the role as text. */
  grid-template-columns: auto minmax(0, 1fr) 8.5rem 2.75rem;
  align-items: center;
  column-gap: var(--space-sm); row-gap: 8px;
}
.workspace-person-avatar {
  width: 34px; height: 34px; flex: none; border-radius: var(--radius-avatar-sm);
  background: var(--color-tint); color: var(--color-brand-primary);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--text-sm); font-weight: 600;
}
.workspace-person-who { min-width: 0; }
/* The name never truncates; the email does. A name shortened to "Manohar Sin…"
   is a row you cannot identify, which is this column's whole job. */
.workspace-person-name {
  font-size: var(--text-md); color: var(--color-text); font-weight: 500;
  display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
}
.workspace-person-email,
.workspace-person-joined {
  display: block; font-size: var(--text-xs); color: var(--color-meta);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The answer to "what can this person do here", drawn as an answer. It used to
   be a dropdown whenever you were allowed to change it, so reading the list
   meant reading five controls. */
.workspace-person-role {
  font-size: var(--text-sm); color: var(--color-text-muted); font-weight: 500;
}
.workspace-person-actions { display: flex; align-items: center; justify-content: flex-end; }

/* The `···` menu. Its wrapper is the positioning context, not the row: a menu
   anchored to a row that can wrap lands in the wrong place the moment it does —
   the same reason `.pane-menu-wrap` exists. */
.row-menu-wrap { position: relative; flex: none; }
.row-menu {
  /* Never wider than the column it opens in, so a role selector or an action
     menu cannot end up outside the screen on a phone. */
  min-width: 232px; max-width: min(20rem, calc(100cqw - 24px));
}
.row-menu .popover-row { gap: 8px; }
/* A select's intrinsic width is set by its longest option, so without this the
   role picker pushes out through the side of the menu it lives in. */
.row-menu .popover-row select { flex: 1 1 auto; min-width: 0; max-width: 100%; }

/* A list panel clips — `overflow: hidden` is what gives it its rounded corners —
   and a `···` menu opening out of the last row is exactly what gets clipped: the
   Remove item was cut off below the panel's own bottom edge. So the two lists
   that contain a menu do their corners by rounding the first and last rows
   instead, and clip nothing. */
.list-panel.workspace-list { overflow: visible; }
.workspace-list > .list-row:first-child {
  border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg);
}
.workspace-list > .list-row:last-child {
  border-bottom-left-radius: var(--radius-lg); border-bottom-right-radius: var(--radius-lg);
}
/* The row holding an open menu sits above the rows below it, or their own
   background paints over the menu. */
.workspace-list > .list-row:has(.popover) { position: relative; z-index: 5; }

/* A role, read as a row: the name, what it allows, and how many people here
   hold it. Clickable, because "Admin — 2 people" is a question whose answer is
   the People list narrowed to them. */
.workspace-role-row {
  width: 100%; border: 0; background: transparent; cursor: pointer;
  font: inherit; text-align: left;
}
.workspace-role-row:hover { background: var(--color-surface-alt); }
/* What a role allows is the useful half of this row, so it wraps onto a second
   line rather than being cut off with an ellipsis — the opposite of the People
   list, where the email is a label and the name is the thing being read. */
.workspace-role-row .workspace-person-email { white-space: normal; overflow: visible; }
/* "Nobody holds it here" never breaks across lines: four rows each wrapping at a
   different word is a column you cannot compare down. */
.workspace-role-row > .meta-text { flex: none; white-space: nowrap; }

/* ---- Settings ---- */
.workspace-form { display: flex; flex-direction: column; gap: var(--space-md); max-width: 34rem; }
.workspace-field { display: flex; flex-direction: column; gap: 5px; }
.workspace-field-label { font-size: var(--text-sm); font-weight: 500; color: var(--color-text); }
.workspace-field-note { font-size: var(--text-xs); color: var(--color-meta); }
.workspace-saved { margin: 0 0 var(--space-md); font-size: var(--text-sm); color: var(--tone-success-fg); }

/* At the bottom, and clearly separated. The one genuinely destructive thing on
   this page, so it is fenced rather than sitting in the same rhythm as Save. */
.workspace-danger {
  /* Held to the same column as the form above it. Full width, it read as a
     footer belonging to the page rather than as the last thing in one form. */
  max-width: 48rem;
  margin-top: var(--space-lg); padding: var(--space-md);
  border: 1px solid var(--tone-error-fg); border-radius: var(--radius-lg);
  background: var(--tone-error-bg);
}
.workspace-danger-head {
  margin: 0 0 10px; font-size: 14px; font-weight: 600; color: var(--tone-error-fg);
}
.workspace-danger-row {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-md); flex-wrap: wrap;
}
.workspace-danger-row strong { font-size: var(--text-md); color: var(--color-text); }
.workspace-danger-row .meta-text { margin: 4px 0 0; max-width: 42ch; }

.workspace-invite-form { display: flex; flex-direction: column; gap: var(--space-md); }

/* ---- Narrow: never three columns, and never a sideways scroll ---- */
@container (max-width: 760px) {
  /* The navigation goes above the content as a tab bar rather than squeezing
     the only column that is doing work. The reading order is unchanged: which
     workspace, then which section, then the section. */
  .split-screen { min-height: 0; }
  .split-page { grid-template-columns: minmax(0, 1fr); gap: var(--space-md); min-height: 0; }
  /* An icon-only rail is a wide-screen compromise. On a phone the column is
     already a strip above the content, so the labels come back. */
  .split-page.is-icons { grid-template-columns: minmax(0, 1fr); }
  .split-page.is-icons::before { display: none; }
  .split-page.is-icons .split-nav-label { display: inline; }
  .split-nav-group-label { margin: 0 0 4px; }
  /* One column: the rule between two of them has nothing left to separate. */
  .split-page::before { display: none; }
  .split-nav {
    position: static; padding: 0 0 var(--space-xs);
    border-bottom: 1px solid var(--color-border);
  }
  .split-panel { padding-left: 0; }
  .split-nav-rule { margin: var(--space-xs) 0; }
  /* Scrolls sideways *itself* rather than making the page do it — five sections
     at any font size still fit on two thumb-swipes, and nothing else on the page
     moves. */
  .split-nav-items {
    flex-direction: row; gap: 4px; overflow-x: auto; scrollbar-width: thin;
    padding-bottom: 2px;
  }
  .split-nav-item { width: auto; flex: none; padding: 0 12px; }
  .split-nav-item.active { box-shadow: inset 0 -2px 0 var(--color-brand-primary); }
}

@container (max-width: 620px) {
  /* Rows stack: the role goes under the name it belongs to and the menu keeps
     its own corner, so no row grows wider than the card. */
  .workspace-person { grid-template-columns: auto minmax(0, 1fr) auto; }
  .workspace-person-who { grid-column: 2 / 3; }
  .workspace-person-role { grid-column: 2 / 3; }
  .workspace-person-actions { grid-column: 3; grid-row: 1 / span 2; align-self: start; }
  .workspace-search { max-width: none; }
  .workspace-danger-row .btn { width: 100%; }
}

@container (max-width: 420px) {
  /* The last stop. The initials go — they are decoration, and the name is the
     identity — and the email and joined date wrap instead of being truncated to
     nothing, which is what a 90px column does to them. */
  .workspace-person { grid-template-columns: minmax(0, 1fr) auto; }
  .workspace-person-avatar { display: none; }
  .workspace-person-who,
  .workspace-person-role { grid-column: 1; }
  .workspace-person-actions { grid-column: 2; grid-row: 1 / span 2; }
  .workspace-person-email,
  .workspace-person-joined { white-space: normal; overflow-wrap: anywhere; }
  .workspace-role-row { flex-wrap: wrap; }
}

/* The one rule here that asks about the *window* rather than about this page,
   and it is the right question to ask: 52px of padding down each side is right
   in a window with room for it and is a third of the available width in one
   without. Nothing else about the layout changes here — the container queries
   above do that work, and they keep answering correctly for a squeezed column
   inside a wide window, which this rule cannot. */
@media (max-width: 700px) {
  .split-screen { padding-left: var(--space-md); padding-right: var(--space-md); }
}
