/* ===========================================================================
   LEGACY STYLESHEET -- pre-2026-10-09. DO NOT ADD TO THIS FILE.
   ===========================================================================

   This was `soil/web/static/soil.css` until 2026-10-09, when the approved design
   system (design/soil-design-system/) became the real soil.css. It is the OLD
   design, and it is kept for exactly one reason: roughly 45 screens still render
   against the class names below, and deleting the file would blank them.

   How it loads: legacy.css is linked BEFORE soil.css. Same origin, same
   specificity for the rules they share, so the later sheet wins -- soil.css (the
   design system) always beats this file. That is the whole mechanism of the
   migration: nothing here needs to be edited or deleted as screens move over,
   because the new sheet simply overrides it.

   How it goes away: each screen gets converted to the `sl-*` components in
   soil.css, one at a time. When no template references a non-`sl-` class any
   more, delete this file and its <link>. Nothing else should ever be added here
   -- new styling belongs in soil.css's App layer section.
   =========================================================================== */

/* Soil design system ("Strata") — shared tokens and components for every
   Jinja2-rendered app page (base.html + the app shell: home, ask, operators,
   dashboard, actions, settings, billing, login/signup, connect, configure). Only
   soil/web/templates.py's metrics_page() (the DORA dashboard) still carries its own
   copy of these tokens, since it isn't Jinja2/base.html-based — kept in sync by hand.

   Token values below are the single source of truth defined in CLAUDE.md's
   "## Design system" section; keep them in sync if that spec ever changes. */

:root {
  /* Page width. 1180px left ~700px of a 1900px screen empty and squeezed Today's
     attention column into a tall ribbon. Widened deliberately -- this departs from the
     1280px in CLAUDE.md's design system, with the user's approval. A token because the
     five page-width rules (banner, header, list, stats, hero) must stay in step: move
     one alone and the header stops lining up with the list under it. The 1180px
     @media breakpoint is NOT this -- it is a viewport query, not a page width. */
  --page-max: 1560px;
  /* Surfaces -- "Strata" design system (matches soil/web/static/site/site.css's
     ink/graphite/slate/canvas/signal palette exactly; kept under this file's own,
     pre-existing variable names so every template/class below needed no rename). */
  --page-bg: #ffffff;
  --card-bg: #ffffff;
  --sidebar-bg: #f6f7f9;
  --raised-bg: #eef0f3;

  /* Borders */
  --border-card: #e4e6eb;
  --border-divider: #eef0f3;
  --border-input: #d5d8df;

  /* Text */
  --text-primary: #0e1116;
  --text-secondary: #3b4150;
  --text-tertiary: #5e6472;

  /* Accent -- Strata's "Signal" blue: Soil's own intelligence (answers, citations,
     AI actions) only. Used sparingly, never as decoration. */
  --accent: #2b59ff;
  --accent-hover: #1e42c8;
  --accent-text: #2b59ff;
  --accent-tint: #eef2ff; /* Strata's "Signal tint" -- AI-surface backgrounds, badges */

  /* A second, muted category color for a left-edge stripe that must read as
     distinct from --accent/--warning/--danger (Review Watch rows) -- matches
     site.css's --tint-docs-text purple rather than inventing an off-palette hue. */
  --glow-violet: #43305f;

  /* Status -- always paired with an icon/label in the markup, never color alone */
  --warning: #8a4208;
  --warning-bg: #fff3df;
  --danger: #a8231b;
  --danger-bg: #fdeceb;
  --success: #0f6b4b;
  --success-bg: #e7f5ee;

  /* "Most urgent item" emphasis -- Strata has no warm/orange token; the reference
     design marks its one must-act item with solid ink instead (see its "Act" step
     and every primary CTA), so this repurposes accent-warm as an ink tint rather
     than inventing a color outside the palette. Used by Home's top attention row. */
  --accent-warm: #0e1116;
  --accent-warm-text: #0e1116;
  --accent-warm-bg: rgba(14, 17, 22, 0.05);

  /* Spacing scale -- 4/8/12/16/24/32/48 only. Every padding, margin and gap in this
     file resolves to one of these; off-scale values (10/14/18/20/22px) were snapped
     to the nearest step during the 2026-10 design pass. Use the token, not a number. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;

  /* Primary action -- "acts on it" is ink, never Signal blue. Strata has exactly two
     action colors: blue says "this is Soil's own intelligence" (answers, citations),
     black says "this does something". design/soil-app-reference.html uses ink for
     every primary CTA (Approve / Add Leo as reviewer / Nudge Meera for sign-off);
     before the 2026-10 pass .btn-primary was --accent, which made the accent read as
     page decoration -- the one thing the design system explicitly forbids. */
  --action: #0e1116;
  --action-hover: #000000;
  --action-text: #ffffff;

  /* Category / source tints (background + text), straight from CLAUDE.md's design
     system. Previously only the text halves existed, hardcoded further down this
     file; the Ask answer's source chips need both halves to match the reference
     design's layered source rail. */
  --tint-tickets-bg: #e6ecfb;
  --tint-tickets-text: #22346b;
  --tint-code-bg: #e8f0e8;
  --tint-code-text: #24452a;
  --tint-conversations-bg: #f3ece1;
  --tint-conversations-text: #5a4222;
  --tint-docs-bg: #eee8f5;
  --tint-docs-text: #43305f;
  --tint-people-bg: #e5f0f2;
  --tint-people-text: #1f4a52;

  /* Fonts */
  --font-heading: "Schibsted Grotesk", "Hanken Grotesk", -apple-system, sans-serif;
  --font-body: "Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Radius -- "square-ish corners, hairline borders, almost no shadow" per Strata's
     own component spec. --radius-pill no longer means a true pill; kept as a name
     so every chip/badge/button below needed no rename. */
  --radius-sm: 4px;   /* hairline chips/dots only -- never buttons, inputs or cards */
  --radius-lg: 12px;
  --radius-md: 8px;
  --radius-pill: 8px;

  /* Elevation -- a 3-level scale (CLAUDE.md's design system). This REVERSES the
     earlier "almost no shadow" rule, deliberately and with the user's instruction.
     Three levels means card / popover / modal; a hairline border is still the default
     separator, so this is not a licence to put a shadow on every box. No glow, no
     neon, no rainbow gradients. */
  --shadow-1: 0 1px 2px rgba(14, 17, 22, 0.06), 0 1px 1px rgba(14, 17, 22, 0.04);
  --shadow-2: 0 4px 12px rgba(14, 17, 22, 0.08), 0 1px 3px rgba(14, 17, 22, 0.06);
  --shadow-3: 0 16px 40px rgba(14, 17, 22, 0.16), 0 4px 12px rgba(14, 17, 22, 0.08);

  /* The one signature dark band: ink with a subtle top-to-bottom gradient. Used on
     Today, and reserved for Departments and Warning when those are built. */
  --band-dark: linear-gradient(180deg, #0e1116 0%, #151920 100%);

  /* Module colours -- one per business module CLAUDE.md names. PROVENANCE: the brief
     cited docs/SOIL_DESIGN_LANGUAGE.md "rule 7" as the source; that file does not
     exist in this repository. These are the five Strata source-tint TEXT hues
     re-pointed at the five modules, so the palette stays inside Strata rather than
     introducing new hues. Only Engineering is live today ("department" in the code is
     currently just engineering | company_wide, an agent filter). A left-edge stripe,
     a chip or a band label -- never a fill behind body text, never a button. */
  --module-engineering: #24452a;
  --module-sales: #22346b;
  --module-customers: #5a4222;
  --module-finance: #1f4a52;
  --module-people: #43305f;

  /* Shadows -- --shadow-card predates the scale above and is kept as an alias so the
     ~40 existing rules that read it need no rename; it resolves to level 1. */
  --shadow-card: var(--shadow-1);
  --shadow-btn-primary: 0 1px 2px rgba(14, 17, 22, 0.08);
  --page-glow: none;

  /* RENAMED 2026-10-09, from `--focus-ring`, and the rename IS the fix.
     soil.css links after this sheet and its design tokens declare `--focus-ring` on
     the same `:root` -- as a COLOUR (`#5C56FC`), because the new system spends it in
     `outline: 2px solid var(--focus-ring)`. Custom properties do not care which sheet
     a name came from: the later `:root` won, and all 36 rules in this file that spell
     the ring as `box-shadow: var(--focus-ring)` started resolving to
     `box-shadow: #5C56FC` -- not a valid <shadow>, so the declaration was INVALID AT
     COMPUTED-VALUE TIME and fell back to `none`. Every one of those rules also carries
     `outline: none`, which stayed perfectly valid, and each of them outscores the
     global `:focus-visible` on specificity (`.theme-switch-opt:focus-visible` is 0,2,0
     against 0,1,0), so soil.css's ring could not fill the gap either. Result: the theme
     switcher, every <summary>, the auth brand link and every <input>, <select> and
     <textarea> on an unconverted screen painted NO keyboard focus ring at all --
     446 findings in audit/UI_AUDIT.md, one root cause.
     The name now says what the value is, and no longer collides with a design token. */
  --focus-ring-shadow: 0 0 0 2px #2b59ff;

  /* --- Pairings that must invert WITH the fill they sit on -------------------
     Added 2026-10-05. Each of these existed only as a literal hex on a rule
     whose BACKGROUND was already a token, so the text stayed put while the fill
     flipped -- the .ask-bubble-mine defect, repeated. */

  /* Text on an --accent fill (.citation:hover, .answer a:hover, .source-n).
     --accent lightens to #6c8cff in dark mode, where white drops to 3.07:1. */
  --accent-contrast: #ffffff;

  /* Muted text on an --action fill (the "recommended" plan card). --action is
     ink in light and near-white in dark, so a fixed light grey is unreadable in
     one of them. */
  --action-text-muted: #c9cdd6;

  /* Hairline on an --accent-tint surface (.summary-card, .onb-reveal). */
  --accent-tint-border: #dce3ff;

  /* A surface that is dark in BOTH themes, by design: the login aside, the MCP
     card and the graph "what Soil read" panel. These were painted with
     var(--text-primary), which inverts to near-white in dark mode and turned
     every one of them into a white slab carrying white text (1.1:1). They are
     not theme-reactive surfaces -- they are dark chrome, like --band-dark -- so
     the token is deliberately the SAME value in all three blocks. Changing it
     per theme is what broke them; it is restated below so that is explicit. */
  --surface-ink: #0e1116;
}

/* ---------------------------------------------------------------------------
   Dark mode. Same token NAMES, different values -- nothing below this block
   knows which theme is active, which is the whole point: a literal hex outside
   a :root block cannot theme and is a bug.

   Two triggers, deliberately in this order:
     1. @media (prefers-color-scheme: dark) -- the OS preference, guarded so an
        explicit [data-theme="light"] wins over it.
     2. [data-theme="dark"] -- the Settings toggle, which must beat the OS.

   Surfaces lift rather than invert: Strata's depth comes from layering, so the
   dark card sits ABOVE the dark page rather than below it. Signal blue is
   lightened only enough to clear 4.5:1 on the dark page -- it stays recognisably
   the same blue, because it carries the brand.
   --------------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --page-bg: #0e1116;
    --card-bg: #151920;
    --sidebar-bg: #11151b;
    --raised-bg: #1c2129;

    --border-card: #262c36;
    --border-divider: #1e232b;
    --border-input: #333a46;

    --text-primary: #f2f4f7;
    --text-secondary: #c2c8d4;
    --text-tertiary: #949cab;

    --accent: #6c8cff;
    --accent-rgb: 108, 140, 255;
    --accent-hover: #8aa3ff;
    --accent-text: #8aa3ff;
    --accent-tint: #19203a;

    /* Ink is the page in dark mode, so the primary action inverts to stay the
       thing that "does something" rather than disappearing into the background. */
    --action: #f2f4f7;
    --action-hover: #ffffff;
    --action-text: #0e1116;
    --accent-warm: #f2f4f7;
    --accent-warm-text: #f2f4f7;
    --accent-warm-bg: rgba(242, 244, 247, 0.08);

    --warning: #f0b464;
    --warning-bg: #2b2114;
    --danger: #f08a82;
    --danger-bg: #2e1816;
    --success: #6fcfa4;
    --success-bg: #12271f;

    --tint-tickets-bg: #1a2135;
    --tint-tickets-text: #a8bdf0;
    --tint-code-bg: #16251a;
    --tint-code-text: #9fcaa6;
    --tint-conversations-bg: #272017;
    --tint-conversations-text: #d9bd92;
    --tint-docs-bg: #221a2e;
    --tint-docs-text: #c0a8dd;
    --tint-people-bg: #142628;
    --tint-people-text: #8fc4cc;

    --module-engineering: #9fcaa6;
    --module-sales: #a8bdf0;
    --module-customers: #d9bd92;
    --module-finance: #8fc4cc;
    --module-people: #c0a8dd;

    /* The signature band is already ink; on a dark page it lifts instead, so it
       still reads as a distinct stratum rather than merging with the page. */
    --band-dark: linear-gradient(180deg, #1c2129 0%, #151920 100%);

    /* Shadow on a dark surface reads as noise unless it is much deeper. */
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.5);
    --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.55), 0 1px 3px rgba(0, 0, 0, 0.4);
    --shadow-3: 0 16px 40px rgba(0, 0, 0, 0.6), 0 4px 12px rgba(0, 0, 0, 0.45);
    --shadow-btn-primary: 0 1px 2px rgba(0, 0, 0, 0.5);
    --focus-ring-shadow: 0 0 0 2px #6c8cff;

    /* Ink reads on the lightened Signal blue; white would be 3.07:1. */
    --accent-contrast: #0e1116;
    /* --action is near-white here, so muted text on it must be dark graphite. */
    --action-text-muted: #3b4150;
    --accent-tint-border: #2a3558;
    /* Deliberately UNCHANGED: see :root. This surface stays dark in both themes. */
    --surface-ink: #0e1116;
  }
}

:root[data-theme="dark"] {
  --page-bg: #0e1116;
  --card-bg: #151920;
  --sidebar-bg: #11151b;
  --raised-bg: #1c2129;

  --border-card: #262c36;
  --border-divider: #1e232b;
  --border-input: #333a46;

  --text-primary: #f2f4f7;
  --text-secondary: #c2c8d4;
  --text-tertiary: #949cab;

  --accent: #6c8cff;
  --accent-rgb: 108, 140, 255;
  --accent-hover: #8aa3ff;
  --accent-text: #8aa3ff;
  --accent-tint: #19203a;

  --action: #f2f4f7;
  --action-hover: #ffffff;
  --action-text: #0e1116;
  --accent-warm: #f2f4f7;
  --accent-warm-text: #f2f4f7;
  --accent-warm-bg: rgba(242, 244, 247, 0.08);

  --warning: #f0b464;
  --warning-bg: #2b2114;
  --danger: #f08a82;
  --danger-bg: #2e1816;
  --success: #6fcfa4;
  --success-bg: #12271f;

  --tint-tickets-bg: #1a2135;
  --tint-tickets-text: #a8bdf0;
  --tint-code-bg: #16251a;
  --tint-code-text: #9fcaa6;
  --tint-conversations-bg: #272017;
  --tint-conversations-text: #d9bd92;
  --tint-docs-bg: #221a2e;
  --tint-docs-text: #c0a8dd;
  --tint-people-bg: #142628;
  --tint-people-text: #8fc4cc;

  --module-engineering: #9fcaa6;
  --module-sales: #a8bdf0;
  --module-customers: #d9bd92;
  --module-finance: #8fc4cc;
  --module-people: #c0a8dd;

  --band-dark: linear-gradient(180deg, #1c2129 0%, #151920 100%);

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.5);
  --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.55), 0 1px 3px rgba(0, 0, 0, 0.4);
  --shadow-3: 0 16px 40px rgba(0, 0, 0, 0.6), 0 4px 12px rgba(0, 0, 0, 0.45);
  --shadow-btn-primary: 0 1px 2px rgba(0, 0, 0, 0.5);
  --focus-ring-shadow: 0 0 0 2px #6c8cff;

  --accent-contrast: #0e1116;
  --action-text-muted: #3b4150;
  --accent-tint-border: #2a3558;
  /* Deliberately UNCHANGED: see :root. This surface stays dark in both themes. */
  --surface-ink: #0e1116;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--page-bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-heading);
  margin: 0;
  letter-spacing: -0.01em;
  font-weight: 700;
}

/* Most templates set an explicit h1 size inline per page and that's left alone here,
   but h2/h3/h4 almost never get one -- they were falling through to the bare browser
   default (h2 ~1.5em), which happens to land close to a 2rem h1 and reads as no
   hierarchy at all between a page title and its card headings ("Current setting",
   "Update", etc.) wherever a template didn't think to size them by hand. An inline
   style on a specific heading still wins over these. */
h1 { font-size: 2rem; line-height: 1.25; }
h2 { font-size: 1.25rem; line-height: 1.3; }
h3 { font-size: 1.05rem; line-height: 1.35; }
h4 { font-size: 0.95rem; line-height: 1.4; }

p {
  color: var(--text-secondary);
  line-height: 1.55;
  margin: 0 0 var(--space-3);
}

a {
  color: var(--accent-text);
}

.mono {
  font-family: var(--font-mono);
}

/* Small uppercase mono label above a card/section (e.g. team_detail.html's "Who",
   "Needs attention", stat-card labels) -- was repeated as an identical inline style
   block in a dozen places; one class so it can't silently drift between them. */
.eyebrow-label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 0 var(--space-2);
}

.stat-card-value {
  font-size: 1.8rem;
  font-weight: 700;
  margin: 0;
  color: var(--text-primary);
}

.team-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

.team-table col.col-who { width: 17%; }
.team-table col.col-working { width: 43%; }
.team-table col.col-status { width: 20%; }
.team-table col.col-seen { width: 14%; }
.team-table col.col-remove { width: 6%; }

.team-table th {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-tertiary);
  text-transform: uppercase;
  text-align: left;
  padding: 0 var(--space-3) var(--space-3) 0;
}

.team-table thead tr {
  border-bottom: 1px solid var(--border-divider);
}

.team-table td {
  padding: var(--space-3) var(--space-3) var(--space-3) 0;
  border-bottom: 1px solid var(--border-divider);
  vertical-align: top;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.team-table td.col-working-cell {
  white-space: normal;
}

/* Accessible focus ring on every interactive element, keyboard or not-hovered */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   Page glow background — the radial accent glow used behind main content areas
   --------------------------------------------------------------------------- */
.page-glow {
  background: var(--page-bg);
}

/* No gradient text in Strata -- restraint carries the brand, not decoration. Class
   name kept as-is so ask.html needed no template edit. */
.gradient-headline {
  color: var(--text-primary);
}

/* ---------------------------------------------------------------------------
   Cards
   --------------------------------------------------------------------------- */
.card {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  /* Real default so plain content (a form, a paragraph, a search input) never
     sits flush against the card border -- previously .card had no padding at
     all, so any page that didn't add its own inline override (e.g.
     configure_picker.html's search input, llm_settings.html's cards) rendered
     with content touching the edge. Pages built as a self-contained "list"
     component that intentionally bleeds its rows to the card's true edge
     (home.html's .home-attention/.home-side-card/.home-tools, and any bare
     .card wrapping an .attention-list below) cancel this back out just below
     -- see those rules for why. */
  padding: var(--space-5);
}

/* These three home.html card variants lay out their own heading
   (.home-card-head) and row lists (.operator-row/.recent-row/.forecast-row/
   .home-tools-list) with padding already sized for a zero-padding parent, so
   dividers and the colored left-edge stripe on attention rows reach the
   card's true edge. Restore that zero base here rather than reworking every
   nested row's own padding. */
.home-attention,
.home-side-card,
.home-tools {
  padding: 0;
}

/* A plain .card (actions.html, releases.html, people.html,
   release_detail.html) gets the new default padding above for its heading/
   paragraph content, but an .attention-list inside it is a row-divider list
   whose own row padding (see .attention-row) is sized to reach the card's
   true left/right/bottom edge -- pull it back out to the edge rather than
   doubling the inset. (.home-attention is excluded: it already zeroes its
   own padding just above, so its .attention-list needs no correction.) */
.card:not(.home-attention) .attention-list {
  /* calc(), not -24px: these three values have to BE --space-5, because --space-5 is
     what .card's own padding is set to just above. Written as a literal they could
     drift apart silently, and the row would stop reaching the card edge. */
  margin: 0 calc(var(--space-5) * -1) calc(var(--space-5) * -1);
}

.card-sm {
  border-radius: var(--radius-md);
}

/* ---------------------------------------------------------------------------
   Buttons — real <button>/<a> elements styled as pills
   --------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.95rem;
  line-height: 1;
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  /* `white-space: nowrap` plus visible overflow is a page-breaker, and it was one:
     a `btn-block` carrying a 200-character label measured clientWidth 340 and
     scrollWidth 915 inside a 390px column, and `document.documentElement.scrollWidth`
     went to 940 on a 390px viewport -- horizontal scroll on the whole page, in both
     themes. The label is not ours: soil.css already records the real case beside
     `.btn-block .btn-label` ("Post summary to #<team-slug>" with a GitHub-derived
     slug), and that mitigation only works if the CALLER remembers to wrap the text in
     a span. This clips at the component instead, so a caller who forgets gets a
     clipped button rather than a scrolling page.

     `min-width: 0` because a .btn is almost always a flex child and `min-width: auto`
     refuses to shrink below content. `overflow: hidden` rather than `clip` because the
     box-shadow and the `outline` focus ring both paint OUTSIDE the box and neither is
     affected, and `hidden` is what `.source-chip` and now `.badge`/`.status` use -- one
     behaviour across the four chips, not three. */
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.btn:active {
  transform: scale(0.97);
}

.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
}

/* A DISABLED BUTTON MUST NOT RESPOND TO THE POINTER. Each variant's `:hover` rule
   below is (0,2,0) and `:disabled` alone is (0,2,0) too, so the hover won on order and
   a disabled primary still darkened, a disabled secondary still turned blue, and a
   disabled ghost still lifted -- a control that answers the mouse reads as one you can
   press, which is the opposite of what `disabled` means. Each selector here is (0,3,0),
   so it wins on specificity rather than on where it sits in the file.

   Written as explicit `:disabled:hover` pairs rather than `pointer-events: none`,
   because removing pointer events also removes the `title` tooltip -- and the tooltip
   is where _investor_update_card.html's disabled Send button says WHY it is disabled
   ("Add at least one recipient below first"). Suppressing the reason while suppressing
   the hover would be the worse bug.

   `transform` is reset too: `.btn:active { transform: scale(.97) }` is (0,2,0) and was
   also beating `:disabled`, so a disabled button still depressed when clicked. */
.btn-primary:disabled:hover      { background: var(--action); }
.btn-secondary:disabled:hover    { border-color: var(--border-input); color: var(--text-primary); }
.btn-ghost:disabled:hover        { background: transparent; color: var(--text-secondary); }
.btn-light:disabled:hover        { background: var(--raised-bg); }
.btn-danger-quiet:disabled:hover { background: transparent; }
.btn:disabled:active             { transform: none; }

.btn-primary {
  background: var(--action);
  color: var(--action-text);
  box-shadow: var(--shadow-btn-primary);
}

.btn-primary:hover {
  background: var(--action-hover);
}

.btn-secondary {
  background: transparent;
  color: var(--text-primary);
  border-color: var(--border-input);
}

.btn-secondary:hover {
  border-color: var(--accent-text);
  color: var(--accent-text);
}

/* Pressed state for a toggle rendered as a .btn-secondary (Ask page's +1/-1) --
   reuses the existing accent-tint token rather than inventing a new color. */
.btn-secondary.active {
  background: var(--accent-tint);
  border-color: var(--accent-text);
  color: var(--accent-text);
}

/* Ask page's "Suggested next actions" row, directly under .answer-footer -- same
   .answer-actions button layout, just its own labeled group. */
.answer-suggested-actions {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-divider);
}

.btn-light {
  background: var(--raised-bg);
  color: var(--text-primary);
  border: 1px solid var(--border-input);
}

.btn-light:hover {
  background: var(--border-card);
}

.btn-block {
  width: 100%;
}

/* .btn is display:inline-flex, so text-overflow:ellipsis on .btn itself has no
   effect (ellipsis only clips a block/inline-block's own text run, not a flex
   container's). A few .btn-block buttons carry dynamic content that can run long
   (e.g. team_detail.html's "Post summary to #<team-slug>" with a GitHub-repo-derived
   slug) -- wrap that text in <span class="btn-label"> so it clips gracefully instead
   of spilling past the card edge. */
.btn-block .btn-label {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Chips
   --------------------------------------------------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-input);
  color: var(--text-secondary);
  font-size: 0.86rem;
  font-weight: 500;
  background: transparent;
  text-decoration: none;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.chip:hover {
  border-color: var(--accent-text);
  color: var(--text-primary);
}

.chip.active,
.chip[aria-current="true"] {
  /* finding P2: ink-on-blue-tint measured 3.58:1, below AA. A selected filter is a
     state the person set, not Soil's own intelligence, so Strata's action colour is
     the right one -- and white-on-ink is 17:1. Matches the reference design's
     selected "Try a question" chip, which is solid black. */
  border-color: var(--action);
  color: var(--action-text);
  background: var(--action);
}

/* ---------------------------------------------------------------------------
   Badges — small status labels, mono + uppercase
   --------------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: 0.72rem;
  font-weight: 700;
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
  /* A badge label is a connected tool's word, not ours. Without these three a
     200-character one is 1,476px wide and takes the whole PAGE into horizontal
     scroll -- measured in the component gallery at 1440 and at 390, in both themes.
     Same three lines `.source-chip` has carried since it was written; `.badge` and
     `.status` never got them. `min-width: 0` because this is usually a flex child and
     a flex item's default `min-width: auto` refuses to shrink below its content. */
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
}

/* `text-overflow` lives on the inner span, not on the chip: it does not clip a flex
   container's own text run. Same reason, same shape, as `.btn-block .btn-label`. */
.badge-label,
.status-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.badge-warning {
  color: var(--warning);
  background: var(--warning-bg);
}

.badge-danger {
  color: var(--danger);
  background: var(--danger-bg);
}

.badge-success {
  color: var(--success);
  background: var(--success-bg);
}

/* ---------------------------------------------------------------------------
   Forms
   --------------------------------------------------------------------------- */
label {
  display: block;
  color: var(--text-secondary);
  font-size: 0.86rem;
  font-weight: 600;
  margin-bottom: var(--space-2);
}

input:not([type="radio"]):not([type="checkbox"]),
textarea,
select {
  width: 100%;
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--text-primary);
  background: var(--card-bg);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}

/* Radio/checkbox inputs must never pick up the text-input box styling above (full
   width, border, padding) -- that's the difference between a native control and an
   invisible full-width box with a tiny dot inside it. Give them a sane native size and
   the brand accent color instead. */
input[type="radio"],
input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  flex-shrink: 0;
  cursor: pointer;
}

input::placeholder,
textarea::placeholder {
  color: var(--text-tertiary);
}

input:not([type="radio"]):not([type="checkbox"]):focus,
textarea:focus,
select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--focus-ring-shadow);
}

/* DISABLED. Added 2026-10-06: before this, a disabled control rendered pixel for
   pixel like an editable one -- same white fill, same border, same ink text -- and the
   only way to find out a setting was locked was to type into it and submit. The dim is
   on the SURFACE and the CURSOR, not an `opacity` over the whole box: an opacity under
   0.8 is the exact defect scripts/ui_audit.py caught on `.attention-pick-more` and
   `.op-avatar.off`, where a legal token was multiplied down below 4.5:1. The text here
   stays --text-tertiary (~6:1) so the value is still READABLE, which matters because a
   locked setting is something a person has come to read.

   Deliberately NOT `opacity` and deliberately NOT colour alone: `cursor: not-allowed`
   and the recessed fill both say it, and the native control is genuinely inert, which
   is the thing assistive tech reads.

   `appearance: none` is not touched -- a disabled native select must keep its platform
   affordance, and repainting it is how a select stops looking like a select. */
input:not([type="radio"]):not([type="checkbox"]):disabled,
textarea:disabled,
select:disabled {
  background: var(--raised-bg);
  border-color: var(--border-divider);
  color: var(--text-tertiary);
  cursor: not-allowed;
}

/* READONLY is not DISABLED and must not look like it. A read-only value is a fact the
   person may select and copy -- the repo a row belongs to, the slug Soil derived -- so
   it keeps full-contrast text and a normal cursor, and only the fill says "not yours
   to change". A readonly input is still focusable and still in the tab order. */
input:not([type="radio"]):not([type="checkbox"])[readonly],
textarea[readonly] {
  background: var(--raised-bg);
  color: var(--text-secondary);
}

/* INVALID. Never colour alone -- `.field-error` below renders the reason in words and
   is tied to the input by aria-describedby, and aria-invalid is what assistive tech
   reads. The border is the third signal, not the only one.

   Specificity: (0,2,1) with the attribute selector, which beats the base
   `input:not(...):not(...)` rule at (0,2,1) on order alone -- it is declared after it,
   deliberately, in the same block. */
input:not([type="radio"]):not([type="checkbox"])[aria-invalid="true"],
textarea[aria-invalid="true"],
select[aria-invalid="true"] {
  border-color: var(--danger);
}

input:not([type="radio"]):not([type="checkbox"])[aria-invalid="true"]:focus,
textarea[aria-invalid="true"]:focus,
select[aria-invalid="true"]:focus {
  border-color: var(--danger);
  box-shadow: 0 0 0 2px var(--danger);
}

.field {
  margin-bottom: var(--space-4);
}

/* The last field before a submit button sits closer to it than to the field
   above -- was being done ad hoc with a per-instance inline style on
   login.html/signup.html, which is how it drifted (email->password used the
   full 16px while password->button used 8px, an inconsistent rhythm). */
.auth-card form .field:last-of-type {
  margin-bottom: var(--space-2);
}

.hint {
  color: var(--text-tertiary);
  font-size: 0.8rem;
  /* Was -10px, which is off the 4/8/12/16/24/32/48 scale entirely. --space-2 is the
     nearest step and leaves an 8px gap under the field instead of 6px. */
  margin: calc(var(--space-2) * -1) 0 0;
}

.error-banner {
  background: var(--danger-bg);
  color: var(--danger);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  font-size: 0.88rem;
  margin-bottom: var(--space-4);
}

/* ---------------------------------------------------------------------------
   Glowing search bar (Home / Ask entry point)
   --------------------------------------------------------------------------- */
.search-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-5);
  /* A 60px-blur glow tuned for the original dark-theme background reads as a faint,
     smudgy second outline on the current white --page-bg instead of a glow -- swapped
     for the same crisp depth shadow every other card uses, plus a tight low-opacity
     accent ring (matching --shadow-btn-primary's tighter, higher-opacity approach)
     so this search bar still reads as the page's one special input. */
  box-shadow: var(--shadow-card), 0 0 0 1px rgba(var(--accent-rgb), 0.1);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.search-bar input {
  flex: 1;
  border: none;
  background: transparent;
  padding: var(--space-3) 0;
  font-size: 1rem;
}

.search-bar input:focus {
  box-shadow: none;
}

.search-bar:focus-within {
  /* One ring, on-token. This stacked --shadow-card UNDER a 3px 22%-opacity blue glow,
     which read as two rings and matched neither the design system's 2px #2B59FF nor
     the --focus-ring-shadow every other focusable surface uses. */
  border-color: var(--accent);
  box-shadow: var(--focus-ring-shadow);
}

.search-bar .btn-primary {
  padding: var(--space-3) var(--space-4);
}

/* ---------------------------------------------------------------------------
   App shell — sidebar + main content, used by base.html
   --------------------------------------------------------------------------- */
.app-shell {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: 260px;
  flex-shrink: 0;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--border-divider);
  display: flex;
  flex-direction: column;
  padding: var(--space-5) var(--space-4);
  /* The sidebar used to grow with the page, so on a long screen like Today its
     footer -- the signed-in card and the LOG OUT control -- ended up ~1300px below
     the fold and was effectively unreachable. Pinning it to the viewport keeps the
     account controls on screen at every scroll position.

     The sidebar itself no longer scrolls: .sidebar-scroll inside it does. When the
     sidebar scrolled, the footer had to be position:sticky to stay visible, and a
     sticky footer paints OVER whatever follows it -- which is how agents.html's
     waiting-approvals box ended up half-hidden under the signed-in card. Two flex
     children (a scrolling nav region, then the footer) cannot overlap at all. */
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
}

/* The scrolling half of the sidebar: everything from the first nav item down to the
   last extra block. min-height: 0 is load-bearing -- without it a flex child refuses
   to shrink below its content height and the region never scrolls, it just pushes
   the footer off the bottom of the screen, which is the exact bug this replaced. */
.sidebar-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.sidebar-logo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-1) var(--space-2) var(--space-5);
  text-decoration: none;
  /* EXPLICIT, because the mark is painted with currentColor. This is an <a> with no
     colour of its own, so it inherited the link blue and the three strata rendered
     blue instead of the ink-and-greys of design/brand/soil-mark.svg. The wordmark
     beside it was already ink, which is why the two disagreed on screen. Every brand
     link needs this for the same reason. */
  color: var(--text-primary);
}

.sidebar-logo span {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--text-primary);
}

.side-nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-5);
  list-style: none;
  padding: 0;
}

.side-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  text-decoration: none;
  font-weight: 500;
  font-size: 0.96rem;
  border: 1px solid transparent;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.side-link svg {
  flex-shrink: 0;
}

.side-link:hover {
  background: rgba(14, 17, 22, 0.05);
  color: var(--text-primary);
}

.side-link.active {
  background: var(--raised-bg);
  color: var(--text-primary);
  border-color: var(--border-input);
  font-weight: 700;
}

.side-label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-tertiary);
  padding: 0 var(--space-3);
  margin: var(--space-3) 0 var(--space-2);
}

/* docs/SOIL_V1.md PART 3's fourth nav group is "Bottom: Connections · Settings" -- a
   position, not a heading. It sits outside .sidebar-scroll (see base.html) so it is
   always on screen; the hairline separates it from the three named groups without
   inventing a label for them. Same 1px divider token every other separator in the app
   uses; no new colour, no new spacing value. */
.side-nav-bottom {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-divider);
  flex-shrink: 0;
}

.sidebar-section {
  margin-bottom: var(--space-1);
}

.nav-count {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  padding: 1px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--danger-bg);
  color: var(--danger);
}

.side-link.active .nav-count {
  background: var(--card-bg);
  color: var(--text-primary);
}

.team-quicklist {
  display: flex;
  flex-direction: column;
  gap: 1px;
  list-style: none;
  padding: 0;
  margin: 0;
}

.teamlink {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.86rem;
}

.teamlink:hover {
  background: rgba(14, 17, 22, 0.05);
  color: var(--text-primary);
}

.team-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
}

.team-dot-ok { background: var(--success); }
.team-dot-risk { background: var(--warning); }
.team-dot-bad { background: var(--danger); }

.team-link-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.team-link-tag {
  font-size: 0.74rem;
  font-weight: 600;
  flex-shrink: 0;
}

.team-link-tag-ok { color: var(--success); }
.team-link-tag-risk { color: var(--warning); }
.team-link-tag-bad { color: var(--danger); }

.connected-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.connected-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font-size: 0.88rem;
  color: var(--text-secondary);
}

.connected-row .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--success);
  flex-shrink: 0;
}

.empty-note {
  font-size: 0.85rem;
  color: var(--text-tertiary);
  padding: 0 var(--space-3);
}

.sidebar-footer {
  margin-top: auto;
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-divider);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-left: var(--space-2);
}

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  /* The People source tint, NOT Signal blue. A person's initials are decoration, and
     CLAUDE.md reserves `--accent` for Soil's own intelligence -- answers, citations,
     the source rail -- "never decoration". This was the only solid Signal-blue
     background in the whole app shell (the sidebar footer avatar, on every screen),
     and design/soil-design-v3.pdf p1 draws it as a pale neutral circle with dark text.
     Same tokens .avatar-person already used, so the two can no longer disagree. */
  background: var(--tint-people-bg);
  color: var(--tint-people-text);
  font-weight: 700;
  font-size: 0.8rem;
  font-family: var(--font-heading);
  flex-shrink: 0;
}

.sidebar-user-name {
  color: var(--text-primary);
  font-weight: 600;
  font-size: 0.92rem;
  line-height: 1.3;
}

.sidebar-user-meta {
  color: var(--text-tertiary);
  font-size: 0.78rem;
  font-family: var(--font-mono);
  line-height: 1.3;
}

.main-content {
  flex: 1;
  min-width: 0;
  /* finding C1, the actual root cause of Today's 754px horizontal overflow at 390px
     (and 100px at 1440px): .app-main is `flex-direction: column`, so `flex: 1` here
     sizes HEIGHT, and width is a cross-axis concern. An `auto` margin on the cross
     axis suppresses `align-items: stretch`, so this element fell back to fit-content
     -- i.e. it sized itself to its widest descendant (a nowrap forecast string),
     clamped only by max-width. `width: 100%` makes the cross-axis size definite
     again; margin:auto still centers it once the viewport exceeds max-width.
     Every other page escaped this only because nothing in them was wide enough. */
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  background: var(--page-bg);
  padding: var(--space-7) var(--space-7) var(--space-7);
}

/* ---------------------------------------------------------------------------
   App topbar — persistent ask bar + sync status above main-content on every
   authenticated page (base.html).
   --------------------------------------------------------------------------- */
.app-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.app-topbar {
  position: sticky;
  top: 0;
  z-index: 5;
  height: 60px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0 var(--space-6);
  background: rgba(246, 247, 249, 0.92);
  backdrop-filter: saturate(1.2) blur(6px);
  -webkit-backdrop-filter: saturate(1.2) blur(6px);
  border-bottom: 1px solid var(--border-card);
}

.topbar-askbar {
  flex: 1;
  max-width: 640px;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: 40px;
  padding: 0 var(--space-2) 0 var(--space-4);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  color: var(--text-tertiary);
  text-decoration: none;
  transition: border-color 0.15s ease;
}

.topbar-askbar:hover {
  border-color: var(--text-primary);
}

.topbar-askbar span.ph-t {
  flex: 1;
  font-size: 0.9rem;
}

.topbar-kbd {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-sm);
  color: var(--text-tertiary);
}

.topbar-sync {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 0.8rem;
  color: var(--text-secondary);
  white-space: nowrap;
  margin-left: auto;
}

.topbar-sync .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--success);
  flex-shrink: 0;
}

@media (max-width: 900px) {
  .app-topbar { padding: 0 var(--space-4); }
  .topbar-kbd { display: none; }
  .topbar-sync .txt { display: none; }
}

/* ---------------------------------------------------------------------------
   Centered single-column shell for logged-out flows (auth, connect)
   --------------------------------------------------------------------------- */
.centered-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--page-bg);
  padding: var(--space-5);
}

.auth-card {
  width: 100%;
  max-width: 420px;
  padding: var(--space-7) var(--space-6);
}

.auth-logo {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
  justify-content: center;
  text-decoration: none;
}

.auth-logo span {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--text-primary);
}

.auth-card h1 {
  font-size: 1.5rem;
  text-align: center;
  margin-bottom: var(--space-2);
}

.auth-card p.sub {
  text-align: center;
  margin: 0 0 var(--space-5);
  font-size: 0.92rem;
}

.auth-card .foot-link {
  text-align: center;
  color: var(--text-secondary);
  font-size: 0.9rem;
  margin-top: var(--space-1);
}

/* Ink, not the default link blue. Every error and auth card renders this link, and
   Signal blue is reserved for Soil's own intelligence -- answers, citations, the
   source rail. A navigation link out of an error page is not that, and it was the
   most-seen blue in the app precisely because it appears on every failure. */
.auth-card .foot-link a {
  font-weight: 600;
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.auth-card .foot-link a:hover { color: var(--action); }

/* ---------------------------------------------------------------------------
   Utility layout helpers
   --------------------------------------------------------------------------- */
.stack {
  display: flex;
  flex-direction: column;
}

.row {
  display: flex;
  align-items: center;
}

.gap-8 {
  gap: var(--space-2);
}

.gap-12 {
  gap: var(--space-3);
}

.gap-16 {
  gap: var(--space-4);
}

.gap-20 {
  gap: var(--space-5);
}

.grid-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: var(--space-4);
}

/* ---------------------------------------------------------------------------
   Departments (sidebar) — org-wide module switcher; only Engineering is real
   today (see CLAUDE.md's vision doc), so the rest are plain non-interactive
   <li> rows, never a link or button.
   --------------------------------------------------------------------------- */
.dept-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  list-style: none;
  padding: 0;
  margin: 0;
}

.dept-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-3);
  font-size: 0.9rem;
}

.dept-row-active {
  color: var(--text-primary);
  font-weight: 700;
}

.dept-row-disabled {
  color: var(--text-tertiary);
}

.dept-tag {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
}

.dept-tag-live {
  color: var(--success);
}

/* ---------------------------------------------------------------------------
   Home screen (/org/{slug}) — hero search + Needs your attention / Your
   operators / Recently active cards
   --------------------------------------------------------------------------- */
/* Hero: an asymmetric layout on wide screens -- the greeting/headline/search sit
   left as the page's primary voice, and a compact live status readout (attention
   count, operators watching, tools connected) anchors the right, giving the fold
   a genuine focal point instead of centered-with-equal-margins. Collapses to a
   single stacked column (pulse panel below the search) under 960px. */
/* "Verify your email" banner (Home only, soil/web/templates/home.html) -- reuses the
   existing at-risk warning tokens rather than inventing a new color, paired with
   copy (not color alone) per CLAUDE.md's accessibility rule. */
.verify-email-banner {
  max-width: var(--page-max);
  margin: 0 auto var(--space-5);
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--warning);
  background: var(--warning-bg);
  border-radius: var(--radius-md);
  color: var(--warning);
  font-size: 0.875rem;
}

/* ---------------------------------------------------------------------------
   Today view (design/soil-app-reference.html) -- the ranked priority feed and
   stat row above the existing Ask hero. Reuses .card/.badge/.chip/.btn tokens
   throughout, no new colors introduced.
   --------------------------------------------------------------------------- */
.today-header {
  max-width: var(--page-max);
  margin: 0 auto var(--space-5);
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.today-headline {
  font-family: var(--font-heading);
  font-size: 1.9rem;
  font-weight: 700;
  color: var(--text-primary);
  margin: var(--space-2) 0 var(--space-2);
  line-height: 1.25;
}

.today-sub {
  color: var(--text-secondary);
  font-size: 0.92rem;
  margin: 0;
}

.today-list {
  max-width: var(--page-max);
  margin: 0 auto var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.today-card {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
}

.today-card-num {
  color: var(--text-tertiary);
  font-size: 0.86rem;
  flex-shrink: 0;
  padding-top: var(--space-1);
}

.today-card-body {
  flex: 1;
  min-width: 0;
}

.today-card-title {
  font-weight: 600;
  color: var(--text-primary);
  margin: var(--space-2) 0 var(--space-1);
  font-size: 0.98rem;
}

.today-card-detail {
  color: var(--text-secondary);
  font-size: 0.86rem;
  margin-bottom: var(--space-2);
}

.today-card-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.today-chip {
  padding: var(--space-1) var(--space-3);
  font-size: 0.74rem;
}

.today-card-impact {
  color: var(--accent-text);
  font-size: 0.86rem;
  font-weight: 500;
}

.today-card-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  flex-shrink: 0;
  align-items: stretch;
}

.today-card-actions .btn {
  white-space: nowrap;
}

@media (max-width: 700px) {
  .today-card {
    flex-direction: column;
  }
  .today-card-actions {
    flex-direction: row;
    width: 100%;
  }
  .today-card-actions .btn {
    flex: 1;
  }
}

.today-stats {
  max-width: var(--page-max);
  margin: 0 auto var(--space-6);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  gap: var(--space-4);
}

@media (max-width: 900px) {
  .today-stats {
    grid-template-columns: repeat(2, 1fr);
  }
}

.today-stat {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  padding: var(--space-4) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.today-stat-value {
  font-size: 1.4rem;
  font-weight: 700;
  color: var(--text-primary);
}

.today-stat-label {
  font-size: 0.76rem;
  color: var(--text-tertiary);
}

/* Company bets + Today's meetings, prepped -- Today view's two-column footer row,
   same .home-side-card/.home-card-head shell as Recently active/Release forecasts. */
.bets-list, .meetings-list {
  display: flex;
  flex-direction: column;
}

.bets-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--border-divider);
}

.bets-row-main {
  flex: 1;
  min-width: 0;
}

.bets-row-name {
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--text-primary);
}

.bets-row-meta {
  font-size: 0.76rem;
  color: var(--text-tertiary);
  margin-top: var(--space-1);
}

.meetings-row {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--border-divider);
}

.meetings-time {
  flex-shrink: 0;
  color: var(--text-tertiary);
  font-size: 0.78rem;
  padding-top: 1px;
}

.meetings-title {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--text-primary);
}

.meetings-note {
  font-size: 0.82rem;
  color: var(--text-secondary);
  margin-top: var(--space-1);
}

.team-load-list {
  display: flex;
  flex-direction: column;
}

.team-load-row {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr) 42px;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-2) var(--space-5);
}

.team-load-name {
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text-primary);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.team-load-bar {
  position: relative;
  height: 8px;
  border-radius: var(--radius-sm);
  background: var(--raised-bg);
  border: 1px solid var(--border-divider);
}

.team-load-bar i {
  position: absolute;
  left: 0;
  top: -1px;
  bottom: -1px;
  border-radius: var(--radius-sm);
  background: var(--text-primary);
}

.team-load-norm {
  position: absolute;
  top: -4px;
  bottom: -4px;
  left: 66.666%;
  width: 2px;
  background: var(--text-tertiary);
  /* The one raw border-radius left in the stylesheet. On a 2px-wide tick 4px clamps
     to the same rounded end 1px gave it, so this is the token, not a new look. */
  border-radius: var(--radius-sm);
}

.team-load-pct {
  font-size: 0.78rem;
  color: var(--text-secondary);
  text-align: left;
}

.team-load-pct-high {
  color: var(--danger);
  font-weight: 600;
}

.graph-card {
  padding: 0;
  overflow: hidden;
}

.graph-legend {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
  font-size: 0.82rem;
  color: var(--text-secondary);
}

.graph-legend span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.graph-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
}

.graph-dot-tickets { background: var(--tint-tickets-text); }
.graph-dot-code { background: var(--tint-code-text); }
.graph-dot-conversations { background: var(--tint-conversations-text); }
.graph-dot-docs { background: var(--tint-docs-text); }
.graph-dot-people { background: var(--tint-people-text); }

.graph-legend-count {
  margin-left: auto;
  color: var(--text-tertiary);
}

#soil-graph-svg {
  display: block;
  background: var(--page-bg);
}

.home-hero {
  max-width: var(--page-max);
  margin: 0 auto var(--space-6);
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: var(--space-7);
  align-items: center;
}

@media (max-width: 960px) {
  .home-hero {
    grid-template-columns: 1fr;
    gap: var(--space-5);
  }
}

.home-hero-main {
  text-align: left;
}

.home-kicker {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin: 0 0 var(--space-4);
}

/* 48-64px per CLAUDE.md's type scale for Today. clamp() so 390px gets the lower bound
   without a second breakpoint: at 2.5rem the headline was the same size as a section
   heading, which is why the screen read flat. 800 is Schibsted Grotesk's heaviest and
   is what the design file uses for this one line. */
.home-headline {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: clamp(2rem, 1.2rem + 2.6vw, 3.5rem);
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin: 0 0 var(--space-6);
  /* A headline computed from data can carry a repo or release name. */
  overflow-wrap: anywhere;
}

.home-search input {
  font-size: 1.05rem;
}

.home-scope-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin: var(--space-4) 0 var(--space-5);
}

.home-scope-label {
  color: var(--text-tertiary);
  font-size: 0.86rem;
}

.chip-disabled {
  opacity: 0.4;
  cursor: default;
}

.chip-disabled:hover {
  border-color: var(--border-input);
  color: var(--text-secondary);
}

.home-kbd-hint {
  color: var(--text-tertiary);
  font-size: 0.78rem;
  margin-left: var(--space-1);
}

.home-kbd-hint kbd {
  font-family: var(--font-mono);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  padding: 1px var(--space-2);
}

.home-suggestions {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.home-suggestions form {
  display: contents;
}

/* Status pulse -- the hero's right-hand anchor. Three real, already-computed
   numbers (never invented): how many things need attention, how many operators
   are actively watching, how many tools are connected. */
.home-pulse {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: var(--space-1) var(--space-5);
}

.home-pulse-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) 0;
}

.home-pulse-row + .home-pulse-row {
  border-top: 1px solid var(--border-divider);
}

.home-pulse-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--success);
}

.home-pulse-dot.dot-on {
  background: var(--success);
}

.home-pulse-dot.dot-off {
  background: var(--text-tertiary);
}

.home-pulse-alert .home-pulse-dot {
  background: var(--warning);
}

.home-pulse-clear .home-pulse-dot {
  background: var(--success);
}

.home-pulse-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.home-pulse-value {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1.1;
  color: var(--text-primary);
}

.home-pulse-label {
  font-size: 0.76rem;
  color: var(--text-tertiary);
  margin-top: var(--space-1);
}

.home-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--space-5);
  align-items: start;
  /* Without this, the gap below .home-grid was accidental -- it only looked right
     because "Needs your attention" happened to be taller than the sidebar stack next
     to it, leaving visual space by coincidence rather than by a real margin. Matches
     .home-tools's own margin-top: var(--space-5) below, so the rhythm holds regardless of which column is taller. */ margin-bottom: var(--space-5);
}

@media (max-width: 900px) {
  .home-grid {
    grid-template-columns: 1fr;
  }
}

.home-recent-forecasts-row {
  display: flex;
  gap: var(--space-5);
  align-items: flex-start;
}

.home-recent-forecasts-row > .card {
  flex: 1;
  min-width: 0;
}

@media (max-width: 900px) {
  /* Was an inline flex row with no class, so it never stacked at any width --
     .home-grid above it stacks at this same breakpoint, so match it here too. */
  .home-recent-forecasts-row {
    flex-direction: column;
    /* finding C1: the top-level rule sets `align-items: flex-start`, which is right
       for a row (cards align to their top) but wrong once this becomes a column --
       on the cross axis flex-start means fit-content, so each card sized itself to
       its widest nowrap descendant instead of the viewport. */
    align-items: stretch;
  }
}

.home-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-5) var(--space-2);
}

.home-card-head h2 {
  font-size: 1.2rem;
}

/* "Needs your attention" is the page's reason to exist -- it gets a heavier
   heading than the lighter-weight sidebar cards next to it (see .home-side-card
   below), independent of the .card it shares with them. */
.home-attention .home-card-head h2 {
  font-size: 1.4rem;
  font-weight: 800;
}

.home-card-sub {
  color: var(--text-tertiary);
  font-size: 0.8rem;
  margin: 0;
}

.attention-list {
  display: flex;
  flex-direction: column;
}

.attention-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5) var(--space-4) var(--space-5);
  border-top: 1px solid var(--border-divider);
  border-left: 3px solid transparent;
  /* The approve/reject pair cannot shrink below its buttons, so without this the row
     pushed it straight off the card -- measured 126px past the padding edge at 783px,
     and 100px of page overflow on /agents. It got worse today: the labels are derived
     from has_external_effect() now, so "Mark reviewed" became "Approve & send on
     Slack". Wrapping puts the buttons on their own line instead. */
  flex-wrap: wrap;
}

/* .attention-body carries only the .flex-1 utility, which is `flex: 1` -- basis 0, but
   min-width still auto, so it would not shrink below the intrinsic width of a quoted
   Slack message or a repo path. The same half-fix as .stat-cell: min-width on the
   flex ITEM is the part that is easy to miss. */
.attention-body {
  /* flex-basis 18rem, NOT 0. min-width: 0 alone fixed the overflow and introduced a
     worse bug, caught in a screenshot and not by the overflow check: with basis 0 the
     column is "satisfied" at any width, so a short button pair (Reject / Approve) kept
     its line and squeezed the text to a 45px ribbon. A real basis makes the ROW wrap
     before the text becomes unreadable -- the same mistake, and the same fix, as
     .page-header-text's `flex: 1 1 0`. */
  flex: 1 1 18rem;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* At 390px the approve/reject pair needs 326px and has 308px, so even on its own line
   it did not fit: "Approve & send on Slack" cannot sit beside "Reject" on a phone.
   Scoped to this row rather than the shared .row utility, which other screens rely on
   staying a single line. */
.attention-row > .row {
  flex-wrap: wrap;
}

/* Per-severity left-edge stripe: which operator flagged it (Release Watch /
   Review Watch), or how bad a broken Promise Tracker commitment is. Color, not
   copy -- the category label text is unchanged. */
.attention-row-release {
  border-left-color: var(--accent);
}

.attention-row-review {
  border-left-color: var(--glow-violet);
}

.attention-row-promise-atrisk {
  border-left-color: var(--warning);
}

.attention-row-promise-missed {
  border-left-color: var(--danger);
}

/* The single top item in an already worst-first list gets the same warm-accent
   treatment as landing.html's "fastest fix" callout -- a genuine "look here
   first" signal, not a decorative repaint of every row. */
.attention-row.attention-row-top {
  border-left-color: var(--accent-warm);
  background: linear-gradient(90deg, var(--accent-warm-bg), transparent 80%);
}

.attention-title {
  font-weight: 700;
  margin-bottom: var(--space-1);
}

.attention-detail {
  color: var(--text-secondary);
  font-size: 0.86rem;
}

.attention-meta {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-shrink: 0;
}

.risk-category {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  white-space: nowrap;
}

.attention-meta .btn {
  padding: var(--space-2) var(--space-4);
  font-size: 0.84rem;
}

.attention-meta form {
  margin: 0;
}

.attention-meta .btn[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* The honest "nothing flagged" empty state: calm and considered, not a plain
   gray sentence that reads the same as a broken feature. */
.attention-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-4);
  padding: var(--space-7) var(--space-5) var(--space-7);
}

.attention-empty-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--success-bg);
  color: var(--success);
}

.attention-empty-text {
  color: var(--text-secondary);
  font-size: 0.94rem;
  font-weight: 500;
  max-width: 360px;
  margin: 0;
}

/* "Your operators", "Release forecasts" and "Recently active" are sidebar-weight
   content, not primary content -- a lighter, more compact treatment (thin
   divider instead of a full card border+shadow, smaller heading, tighter row
   padding) so visual weight actually matches informational weight next to the
   heavier "Needs your attention" card they sit beside. */
.home-side-card {
  background: transparent;
  border: 1px solid var(--border-divider);
  border-radius: var(--radius-md);
  box-shadow: none;
}

.home-side-card .home-card-head {
  padding: var(--space-4) var(--space-5) var(--space-1);
}

.home-side-card .home-card-head h2 {
  font-size: 0.96rem;
}

.home-side-card .operator-row,
.home-side-card .recent-row,
.home-side-card .forecast-row {
  padding: var(--space-2) var(--space-5);
}

.home-side-card .home-week-note {
  padding: var(--space-2) var(--space-5) var(--space-4);
}

.operator-list,
.recent-list {
  display: flex;
  flex-direction: column;
  padding: var(--space-1) 0 var(--space-2);
}

.operator-row,
.recent-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;   /* finding C1: without this the nowrap .recent-label inside sets
                     this flex item's intrinsic width and pushes the page wider than
                     the viewport, defeating the label's own text-overflow:ellipsis */
  padding: var(--space-3) var(--space-5);
}

.recent-row {
  text-decoration: none;
  color: inherit;
}

.recent-row:hover {
  background: rgba(14, 17, 22, 0.04);
}

.operator-body {
  flex: 1;
  min-width: 0;
}

.operator-name {
  font-weight: 600;
  font-size: 0.92rem;
}

.operator-detail {
  color: var(--text-tertiary);
  font-size: 0.8rem;
}

.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.status-dot.dot-on {
  background: var(--success);
}

.status-dot.dot-off {
  background: var(--text-tertiary);
}

.status-dot.dot-warn {
  background: var(--warning);
}

.status-dot.dot-danger {
  background: var(--danger);
}

.recent-provider {
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
  width: 52px;
  flex-shrink: 0;
}

.recent-label {
  flex: 1 1 0;      /* see .forecast-body -- zero basis, not content basis (finding C1) */
  font-size: 0.9rem;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.home-week-note {
  color: var(--text-tertiary);
  font-size: 0.76rem;
  padding: var(--space-3) var(--space-5) var(--space-5);
  border-top: 1px solid var(--border-divider);
  margin: var(--space-2) 0 0;
}

/* Release forecasts -- a lighter-weight sidebar list (see .home-side-card),
   distinct from .attention-row since a forecast isn't a detected risk. */
.forecast-list {
  display: flex;
  flex-direction: column;
  padding: var(--space-1) 0 var(--space-2);
}

.forecast-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-width: 0;   /* finding C1 -- see .recent-row above */
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--border-divider);
}

.forecast-body {
  /* finding C1: flex-basis must be 0, not auto. With `flex: 0 1 auto` this item's
     base size is its own (nowrap, ~900px) text, so the whole Today page sized itself
     to the text instead of the viewport and text-overflow:ellipsis never engaged --
     min-width:0 alone only PERMITS shrinking, it doesn't cause it. */
  flex: 1 1 0;
  min-width: 0;
}

.forecast-title {
  font-weight: 600;
  font-size: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.forecast-reasoning {
  color: var(--text-tertiary);
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Ask page — sidebar additions (base.html's sidebar_top / sidebar_below_nav
   blocks) plus the question/answer/sources layout
   --------------------------------------------------------------------------- */
.recent-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.recent-q {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.86rem;
  line-height: 1.35;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease;
}

.recent-q:hover {
  background: rgba(14, 17, 22, 0.05);
  color: var(--text-primary);
}

.recent-q.active {
  background: var(--raised-bg);
  color: var(--text-primary);
  font-weight: 600;
}

.ask-shell {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-6);
}

.ask-main {
  flex: 1;
  min-width: 0;
  max-width: 800px;
  display: flex;
  flex-direction: column;
}

.ask-empty {
  padding: var(--space-7) 0;
  max-width: 520px;
}

.ask-empty h1 {
  font-size: 1.9rem;
  margin-bottom: var(--space-3);
}

.question-row {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-5);
}

.question-bubble {
  /* finding C3/C2: this was solid Signal blue, which made the PERSON's words read as
     Soil's own output -- exactly backwards. Blue on this page belongs to the answer
     and its citations. The question is quieter than the answer it produced. */
  background: var(--raised-bg);
  color: var(--text-primary);
  border: 1px solid var(--border-card);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  max-width: 70%;
  width: fit-content;
  font-weight: 600;
  font-size: 0.98rem;
  line-height: 1.4;
}

.answer-card {
  padding: var(--space-6) var(--space-6);
}

/* Optimistic "Thinking…" placeholder shown immediately on submit, before the real
   (possibly 10-30s, for a local Ollama model) answer comes back -- see ask.html's
   soilOptimisticAsk(). */
.ask-thinking-text {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}
.ask-thinking-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-tertiary);
  animation: soil-thinking-pulse 1.4s ease-in-out infinite;
}
.ask-thinking-dot:nth-child(2) { animation-delay: 0.18s; }
.ask-thinking-dot:nth-child(3) { animation-delay: 0.36s; }
@keyframes soil-thinking-pulse {
  0%, 100% { opacity: 0.25; }
  50% { opacity: 1; }
}
/* Reduced motion still needs to read as "working", so the dots stay visible and
   simply stop moving -- hiding them would leave an empty card. */
@media (prefers-reduced-motion: reduce) {
  .ask-thinking-dot { animation: none; opacity: 0.55; }
}

.answer-headline {
  font-size: 1.4rem;
  margin-bottom: var(--space-4);
}

.answer p {
  margin: 0 0 var(--space-4);
}

.answer p:last-child {
  margin-bottom: 0;
}

.answer a {
  color: var(--accent-text);
  font-weight: 600;
  text-decoration: underline;
}

.answer h4 {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
  font-family: var(--font-mono);
  margin: var(--space-4) 0 var(--space-2);
}

.answer h4:first-child {
  margin-top: 0;
}

.answer-list {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-4);
  counter-reset: answer-step;
}

.answer-list li {
  counter-increment: answer-step;
  position: relative;
  padding: var(--space-2) 0 var(--space-2) var(--space-6);
  line-height: 1.5;
}

.answer-list li::before {
  content: counter(answer-step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 8px;
  font-family: var(--font-mono);
  font-size: 0.76rem;
  font-weight: 700;
  color: var(--warning);
}

.answer-perm {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-divider);
  font-size: 0.82rem;
  color: var(--text-secondary);
}

.answer-trace {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-divider);
  font-size: 0.86rem;
  color: var(--text-secondary);
}

.answer-trace summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--text-primary);
  list-style: none;
}

.answer-trace summary::-webkit-details-marker {
  display: none;
}

.answer-trace summary::before {
  content: "▸ ";
  color: var(--text-tertiary);
}

.answer-trace[open] summary::before {
  content: "▾ ";
}

.answer-trace ul {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
}

.answer-trace li {
  padding: var(--space-2) 0;
  border-top: 1px solid var(--border-divider);
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
}

.answer-trace li:first-child {
  border-top: none;
}

.trace-tag {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--accent-text);
  background: rgba(var(--accent-rgb), 0.14);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-md);
  flex-shrink: 0;
}

.answer-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.answer-footer .meta {
  font-size: 0.82rem;
  color: var(--text-tertiary);
  margin: 0;
}

.answer-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.answer-actions .btn {
  padding: var(--space-2) var(--space-4);
  font-size: 0.84rem;
}

.example-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: var(--space-5) 0 0;
}

button.chip {
  font: inherit;
  font-family: var(--font-body);
}

.ask-inputbar {
  margin-top: var(--space-5);
}

.sources-panel {
  width: 280px;
  flex-shrink: 0;
  padding-top: var(--space-1);
}

.sources-panel h2 {
  font-size: 1.1rem;
  margin-bottom: var(--space-4);
}

.sources-panel h3 {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
  font-family: var(--font-mono);
  margin: var(--space-5) 0 var(--space-3);
}

.sources-panel h3:first-of-type {
  margin-top: 0;
}

.source-item {
  display: block;
  padding: var(--space-3) var(--space-3);
  margin-bottom: var(--space-2);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-card);
  text-decoration: none;
  transition: border-color 0.15s ease;
}

.source-item:hover {
  border-color: var(--accent-text);
}

.source-item .title {
  display: block;
  color: var(--text-primary);
  font-weight: 600;
  font-size: 0.86rem;
  line-height: 1.35;
}

.source-item .detail {
  display: block;
  color: var(--text-tertiary);
  font-size: 0.76rem;
  margin-top: var(--space-1);
}

/* ---------------------------------------------------------------------------
   Operators library (soil/web/templates/operators.html)
   --------------------------------------------------------------------------- */
.op-tabs {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  border-bottom: 1px solid var(--border-divider);
  margin-bottom: var(--space-5);
  flex-wrap: wrap;
}

.op-tab {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: 0 0 var(--space-3);
  color: var(--text-secondary);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.94rem;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.op-tab .count {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  color: var(--text-tertiary);
}

.op-tab.active {
  color: var(--text-primary);
  border-bottom-color: var(--accent);
}

.op-tab.soon {
  color: var(--text-tertiary);
  cursor: default;
}

.op-tab .soon-label {
  font-size: 0.72rem;
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-tertiary);
}

.op-card {
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
}

.op-avatar {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-lg);
  font-size: 0.86rem;
}

.op-avatar.off {
  background: var(--raised-bg);
  color: var(--text-tertiary);
}

.op-status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-tertiary);
}

.op-status .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-tertiary);
}

.op-status.on {
  color: var(--success);
}

.op-status.on .dot {
  background: var(--success);
}

.op-card h3 {
  font-size: 1.1rem;
  margin: var(--space-4) 0 var(--space-2);
}

.op-card .op-desc {
  font-size: 0.86rem;
  flex-grow: 1;
  margin-bottom: var(--space-4);
}

.op-providers {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.op-providers .chip {
  padding: var(--space-1) var(--space-3);
  font-size: 0.78rem;
}

.op-card-footer {
  border-top: 1px solid var(--border-divider);
  padding-top: var(--space-4);
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.op-card-footer .stat-line {
  font-size: 0.8rem;
  color: var(--text-tertiary);
  font-family: var(--font-mono);
}

.op-card-footer form {
  margin: 0;
}

.sidebar-stat-box {
  margin-top: var(--space-4);
  padding: var(--space-4) var(--space-4) var(--space-4);
}

.sidebar-stat-box .stat-label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-tertiary);
  margin-bottom: var(--space-2);
}

.sidebar-stat-box .stat-headline {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.5rem;
  color: var(--text-primary);
  margin-bottom: var(--space-2);
}

.sidebar-stat-box p {
  font-size: 0.82rem;
  margin: 0;
  line-height: 1.4;
}

.op-waitlist-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-5);
  margin-top: var(--space-5);
  flex-wrap: wrap;
}

.op-waitlist-banner h3 {
  font-size: 1.05rem;
  margin-bottom: var(--space-1);
}

.op-waitlist-banner p {
  margin: 0;
  max-width: 560px;
}

/* ---------------------------------------------------------------------------
   Home screen — compact "Your tools" strip (same connect/configure data as
   dashboard.html's onboarding grid, just condensed for a Home screen that
   already has real risk/activity data to lead with)
   --------------------------------------------------------------------------- */
.home-tools {
  margin-top: var(--space-5);
}

.home-tools-bar {
  height: 6px;
  border-radius: var(--radius-md);
  background: var(--raised-bg);
  overflow: hidden;
  margin: 0 var(--space-5) var(--space-4);
}

.home-tools-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-md);
}

.home-tools-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 400px), 1fr));
  gap: var(--space-1) var(--space-5);
  padding: 0 var(--space-5);
}

.home-tool-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: nowrap;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border-divider);
  font-size: 0.86rem;
  min-width: 0;
}

.home-tool-row .status-dot {
  flex-shrink: 0;
}

.home-tool-label {
  font-weight: 600;
  flex-shrink: 0;
  margin-right: auto;
}

.home-tool-stat {
  color: var(--text-tertiary);
  font-size: 0.76rem;
  margin-right: var(--space-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Connect/Configure/Upgrade read as real actions on a system-status readout, not
   plain inline text links. */
.home-tool-action {
  font-size: 0.78rem;
  font-weight: 600;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-input);
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
  flex-shrink: 0;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.home-tool-action:hover {
  border-color: var(--accent-text);
  color: var(--accent-text);
  background: rgba(var(--accent-rgb), 0.06);
}

/* ---------------------------------------------------------------------------
   Mobile — app shell (sidebar + main content). Below 768px the left sidebar
   (primary nav, team quick-list, departments, connected status) is hidden
   entirely and replaced by .mobile-tabbar, a fixed bottom bar with the same
   6 primary destinations — the pattern design/soil-app-reference.html itself
   specifies for phone width, rather than squeezing the whole desktop sidebar
   into a scrollable block above the content (which is what used to happen
   here, and read as a broken, un-app-like page on a real phone). A handful of
   rows that assume desktop-width space (the Ask sources rail, the home "Your
   tools" grid, attention-row's action column) wrap instead of forcing the
   page to scroll sideways. Same breakpoint pattern as landing.html's
   @media (max-width: 860px) nav-link collapse.
   --------------------------------------------------------------------------- */
/* Bottom tab bar: the mobile replacement for the sidebar's primary nav (see the
   768px block below). Hidden on desktop by default; `display:grid` turned back on
   inside that media query -- this base rule must come BEFORE the media query in
   source order, since CSS resolves equal-specificity rules by source position, not
   by which one is inside a media query; the reverse order silently always wins with
   `none` regardless of viewport width (a real bug this comment is here to prevent
   reintroducing). Fixed to the viewport bottom, not .app-shell, so it stays put
   under any page's content regardless of scroll position. */
.mobile-tabbar {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  /* SEVEN since docs/SOIL_CHIEF.md Step 2 added "Waiting on you", and the count lives
     here because below 768px the sidebar is hidden ENTIRELY and this bar is the whole
     of the app's navigation -- a destination missing from it is unreachable on a
     phone, not merely harder to reach.
     A bare `1fr` and not `minmax(0, 1fr)`: a 1fr track's floor is its own content, so
     a cell grows rather than clipping its label, and the neighbours give the width up.
     That is the behaviour the "Repositories" comment in base.html measured at six
     cells, and it was re-measured at seven -- 390, 360 and 320, light and dark, real
     Chromium -- rather than reasoned about. `scrollWidth - clientWidth` stays 0.
     An explicit 0 floor would guarantee no page overflow and buy it with a clipped
     "Repositorie…", which is a worse answer to a problem that does not exist. */
  grid-template-columns: repeat(7, 1fr);
  background: var(--card-bg);
  border-top: 1px solid var(--border-card);
  padding: var(--space-2) var(--space-1) calc(6px + env(safe-area-inset-bottom));
}

@media (max-width: 768px) {
  .sidebar {
    display: none;
  }

  .app-main {
    width: 100%;
  }

  .main-content {
    padding: var(--space-5) var(--space-4) calc(80px + env(safe-area-inset-bottom));
  }

  /* Ask: a fixed 280px sources rail next to the answer column -- wrap it
     below instead of squeezing the answer into nothing. */
  .ask-shell {
    flex-wrap: wrap;
  }

  .sources-panel {
    width: 100%;
  }

  /* Home "Your tools" strip assumes 400px-wide columns -- one per row instead. */
  .home-tools-list {
    grid-template-columns: 1fr;
  }

  /* Attention rows put "See why" / nudge buttons in a flex-shrink:0 column
     beside the title text; there isn't room for both side by side below 768px. */
  .attention-row {
    flex-wrap: wrap;
  }

  .attention-meta {
    flex-shrink: 1;
    flex-wrap: wrap;
    width: 100%;
  }

  .mobile-tabbar {
    display: grid;
  }
}

.mobile-tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-1);
  border-radius: var(--radius-md);
  color: var(--text-tertiary);
  text-decoration: none;
  font-size: 0.64rem;
  font-weight: 500;
  position: relative;
}

.mobile-tab svg {
  flex-shrink: 0;
}

.mobile-tab.active {
  color: var(--text-primary);
  font-weight: 700;
}

.mobile-tab .nav-count {
  position: absolute;
  top: -1px;
  right: 14px;
  padding: 0 var(--space-1);
}

@media (max-width: 380px) {
  /* operators.html sets its own grid columns inline (minmax(min(100%, 300px), 1fr));
     on the narrowest phones 300px can exceed the width left after
     main-content's padding, so force a single column. */
  #op-grid {
    grid-template-columns: 1fr !important;
  }
}

/* --------------------------------------------------------------------------
   Provider item picker (soil/web/templates/configure_picker.html) — the
   checkbox/radio list an admin uses to pick which repos/projects/channels
   Soil should sync for a connected tool.
   --------------------------------------------------------------------------- */
.picker-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  font-size: 0.95rem;
  cursor: pointer;
}
.picker-row:hover {
  border-color: var(--accent);
  background: var(--raised-bg);
}
.picker-row:has(input:checked) {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.08);
}
.picker-row input[type="checkbox"],
.picker-row input[type="radio"] {
  flex-shrink: 0;
}
.picker-row span {
  overflow-wrap: anywhere;
}

/* ===================================================================
   Primitives added in the 2026-10 design pass (Step 2 of the design
   pass in CLAUDE.md). Everything below is referenced from
   soil/web/templates/_components.html -- the single Jinja component
   set. Do not hand-roll a button, card, badge, chip, empty state or
   skeleton in a page template again; import the macro instead.
   =================================================================== */

/* --- Buttons: ghost (the third weight the component set needs) ----- */
.btn-ghost {
  background: transparent;
  color: var(--text-secondary);
  border-color: transparent;
}
.btn-ghost:hover { background: var(--raised-bg); color: var(--text-primary); }

/* A quiet, text-weight destructive action. Disconnect/Delete are real and
   must stay reachable, but they are never the loudest thing on a card --
   see the design pass's finding H2 on the Connections page. */
.btn-danger-quiet {
  background: transparent;
  border-color: transparent;
  color: var(--danger);
  font-weight: 500;
  padding: var(--space-2) var(--space-3);
}
.btn-danger-quiet:hover { background: var(--danger-bg); }

/* --- Source chip: one per connected tool category ------------------
   The layering that carries the brand (CLAUDE.md: "Citations and layering
   carry the brand, not color or motion"). Each category gets its Strata
   tint; the chip is the visible seam between a claim and where it came from. */
.source-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid transparent;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.source-chip-tickets       { background: var(--tint-tickets-bg);       color: var(--tint-tickets-text); }
.source-chip-code          { background: var(--tint-code-bg);          color: var(--tint-code-text); }
.source-chip-conversations { background: var(--tint-conversations-bg); color: var(--tint-conversations-text); }
.source-chip-docs          { background: var(--tint-docs-bg);          color: var(--tint-docs-text); }
.source-chip-people        { background: var(--tint-people-bg);        color: var(--tint-people-text); }

/* --- Inline citation marker ---------------------------------------
   A superscript link sitting directly in the answer prose, the way
   design/soil-website-reference.html's hero mock shows it. Signal blue
   here is correct and deliberate: this IS Soil's own intelligence. */
.citation {
  display: inline-block;
  min-width: 14px;
  padding: 0 var(--space-1);
  /* --space-1, not the 2px this was: CLAUDE.md closes spacing to --space-1..7 and a
     raw px cannot be re-scaled with the system. 4px before a superscript marker reads
     the same as 2px did -- it is the gap after a word, and the marker's own padding
     already separates it from the digit. */
  margin-left: var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--accent-tint);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.68em;
  font-weight: 600;
  line-height: 1.5;
  vertical-align: super;
  text-decoration: none;
}
.citation:hover { background: var(--accent); color: var(--accent-contrast); }
.citation:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

/* --- Empty state ---------------------------------------------------
   Never a blank page: a title that says what is missing, one line of
   plain help, and exactly one action that fixes it. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-3);
  padding: var(--space-7) var(--space-5);
  border: 1px dashed var(--border-input);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
}
.empty-state-title { font-family: var(--font-heading); font-size: 1.05rem; font-weight: 600; color: var(--text-primary); margin: 0; }
.empty-state-body  { font-size: 0.95rem; color: var(--text-tertiary); margin: 0; max-width: 46ch; line-height: 1.6; }

/* --- Loading skeleton ----------------------------------------------
   Shown only while a real request is in flight; never as a stand-in for
   data Soil does not have (CLAUDE.md's honesty rule -- an empty state
   says "nothing here", a skeleton says "still loading"). */
.skeleton {
  display: block;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--raised-bg) 25%, var(--border-divider) 37%, var(--raised-bg) 63%);
  background-size: 400% 100%;
  animation: skeleton-sheen 1.4s ease-in-out infinite;
}
.skeleton-text  { height: 12px; margin-bottom: var(--space-2); }
.skeleton-title { height: 20px; width: 40%; margin-bottom: var(--space-3); }
.skeleton-card  { height: 96px; border-radius: var(--radius-lg); }
/* The ragged line lengths skeleton_text() cycles through. These were an inline
   `style="width: N%"` inside the macro -- the one inline style left in the component
   set, which CLAUDE.md's closed utility set forbids outright. Same five values. */
.skeleton-text-w1 { width: 92%; }
.skeleton-text-w2 { width: 78%; }
.skeleton-text-w3 { width: 64%; }
.skeleton-text-w4 { width: 85%; }
.skeleton-text-w5 { width: 70%; }
@keyframes skeleton-sheen { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* --- Toast ---------------------------------------------------------- */
.toast-region {
  position: fixed;
  bottom: var(--space-5);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  z-index: 60;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-card);
  background: var(--card-bg);
  box-shadow: var(--shadow-card);
  font-size: 0.92rem;
  color: var(--text-primary);
  pointer-events: auto;
}
/* `display: flex` above beats the UA stylesheet's `[hidden] { display: none }` -- the
   same trap already commented on .modal-backdrop and .iu-editor below. Without this,
   the shell's #request-failed-toast was PAINTED on every page in the app: an empty
   34x26px box with a red error border, sitting at the bottom centre of every screen,
   and soil.js's "reveal the toast on a failed request" revealed something already
   visible. */
.toast[hidden] { display: none; }
.toast-success { border-color: var(--success); }
.toast-error   { border-color: var(--danger); }

/* On a phone the bottom of the screen belongs to .mobile-tabbar and, on Ask, to the
   composer right above it -- a bottom-anchored toast lands on top of both, covering
   the field the message is telling you to try again in. Same top placement and side
   gutters .connected-toast already uses at phone width. */
@media (max-width: 768px) {
  .toast-region {
    top: var(--space-4);
    bottom: auto;
    left: var(--space-4);
    right: var(--space-4);
    transform: none;
  }
}

/* --- Modal ---------------------------------------------------------- */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(14, 17, 22, 0.32);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  z-index: 70;
}
.modal {
  width: 100%;
  max-width: 520px;
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
}
.modal-title { font-family: var(--font-heading); font-size: 1.25rem; font-weight: 600; margin: 0 0 var(--space-2); }
.modal-actions { display: flex; gap: var(--space-2); justify-content: flex-end; margin-top: var(--space-5); }

/* --- Truncation helper ---------------------------------------------
   The fix for the design pass's finding C1: three `white-space: nowrap`
   leaves (.recent-label, .forecast-title, .forecast-reasoning) had no
   ellipsis and no min-width:0, so their intrinsic width forced the whole
   Today page 742px wider than a 390px viewport. Any nowrap text in a flex
   or grid child needs all three of these, not just the nowrap. */
.truncate {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- Tabular figures ------------------------------------------------
   Every number Soil shows is compared against another number, so digits
   must line up in a column (CLAUDE.md's type scale calls for numbers in
   Schibsted Grotesk; this makes them align). */
.tabular, .today-stat-value, .metric-value, .stat-value {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; }
}

/* --- Page header ----------------------------------------------------
   One h1, one optional sentence, one primary action. Rendered only via
   _components.html's page_header() macro so a screen cannot quietly grow
   a second competing headline the way Today had. */
.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
  margin-bottom: var(--space-5);
}
/* flex-basis is 22rem, not 0. With `flex: 1 1 0` this column was "satisfied" at any
   width, so the parent's flex-wrap never fired and the title/subtitle were crushed
   instead -- measured at 38px wide at a 783px viewport, one word per line, while the
   tabs beside it kept their intrinsic width. A real basis makes the row wrap onto two
   lines once there is not enough space, which is what flex-wrap was there for.
   min-width: 0 stays: it is what lets a long connected-tool name break. */
.page-header-text { min-width: 0; flex: 1 1 22rem; }
.page-header-text h1 {
  font-family: var(--font-heading);
  font-size: 1.75rem;
  line-height: 1.15;
  font-weight: 700;
  margin: 0;
  color: var(--text-primary);
}
.page-kicker {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin: 0 0 var(--space-2);
}
.page-subtitle {
  margin: var(--space-2) 0 0;
  color: var(--text-tertiary);
  font-size: 0.95rem;
  line-height: 1.6;
  max-width: 68ch;
}
.page-header-actions { display: flex; gap: var(--space-2); flex-shrink: 0; }

/* --- Status (word + dot, never colour alone) ------------------------ */
.status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 0.82rem;
  font-weight: 600;
  white-space: nowrap;
  /* See `.badge`: a stored status string took the page into horizontal scroll at
     1,262px wide on a 390px viewport. The dot stays (`flex-shrink: 0` below), the
     word clips. */
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
}
.status .status-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.status-on-track          { color: var(--success); }
.status-on-track .status-dot  { background: var(--success); }
.status-at-risk           { color: var(--warning); }
.status-at-risk .status-dot   { background: var(--warning); }
.status-blocked           { color: var(--danger); }
.status-blocked .status-dot   { background: var(--danger); }
.status-neutral           { color: var(--text-tertiary); }
.status-neutral .status-dot   { background: var(--text-tertiary); }

/* --- Field help text ------------------------------------------------ */
.field-help {
  margin: var(--space-1) 0 0;
  font-size: 0.82rem;
  color: var(--text-tertiary);
  line-height: 1.5;
}

/* --- Field error: the per-field twin of .error-banner ----------------
   The message, not the border, IS the error state. `field()` renders this with an id
   and points the input's aria-describedby at it, so the reason reaches a screen reader
   as well as an eye. --danger is ~5:1 on the card in light and ~6:1 in dark; it is a
   token in both, so this cannot be the "literal paired with an inverting token"
   defect. The weight is 500 rather than 600 because this is a sentence, not a label. */
.field-error {
  margin: var(--space-1) 0 0;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--danger);
  line-height: 1.5;
}

/* --- Skeleton group ------------------------------------------------- */
.skeleton-group { display: flex; flex-direction: column; gap: var(--space-2); }


/* --- Minimum touch targets (finding H1) -----------------------------
   The design system asks for 44px minimum touch targets on mobile. At 390px the
   audit found 50 controls under 44px on Today alone, 42 on Agents, 21 on
   Connections. Applied at the breakpoint only: on a pointer device a 32px row is
   fine and 44px everywhere would make dense tables feel bloated. */
@media (max-width: 900px) {
  .btn,
  .chip,
  .nav-item,
  .sidebar a,
  .mobile-tabbar a,
  .side-link,
  .recent-q,
  .source-item,
  .tool-row a,
  .connections-links a,
  .page-header-actions a,
  nav a,
  li > a,
  dd > a,
  summary,
  button:not(.citation):not(.skeleton),
  select,
  input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
  input[type="submit"] {
    min-height: 44px;
  }
  /* A link sitting inside a sentence keeps its natural box -- stretching it to 44px
     would break the line it lives in. Those are hit via the surrounding row instead. */
  p a, .lede a, .scope-note a, .answer a, .citation { min-height: 0; }
  /* Inline text links inside prose keep their natural size, but get enough padding
     to be hit reliably without changing the line box. */
  .recent-row,
  .forecast-row,
  .today-card-actions > *,
  .source-chip {
    min-height: 44px;
    display: flex;
    align-items: center;
  }
  .source-chip { display: inline-flex; }
}


/* ===================================================================
   Ask: citations and source layering (finding C2)
   The marketing site's hero mock has always shown tinted source chips and
   visible in-prose citations; the real page had neither -- plain grey
   boxes in a detached rail and ordinary underlined links. This is the
   brand element, so it gets real treatment.
   =================================================================== */

/* A source reference sitting inside the answer prose. soil/ask.py's system prompt
   already requires the model to emit [label](url) for every source, and
   templates.py's render_answer_html turns those into real anchors -- so these are
   genuine citations, not decoration invented here. Nothing is renumbered or
   re-ordered; only the styling changes. */
.answer a {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  padding: 1px var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--accent-tint);
  color: var(--accent);
  font-weight: 600;
  font-size: 0.94em;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.answer a:hover { background: var(--accent); color: var(--accent-contrast); }
.answer a:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

/* The source rail, as layers. Each provider group carries its Strata category tint
   so the eye reads "this claim came from tickets / from code / from conversations"
   without having to read the label. */
.sources-panel h3 {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--raised-bg);
  color: var(--text-secondary);
}
.sources-panel .src-tickets       { background: var(--tint-tickets-bg);       color: var(--tint-tickets-text); }
.sources-panel .src-code          { background: var(--tint-code-bg);          color: var(--tint-code-text); }
.sources-panel .src-conversations { background: var(--tint-conversations-bg); color: var(--tint-conversations-text); }
.sources-panel .src-docs          { background: var(--tint-docs-bg);          color: var(--tint-docs-text); }
.sources-panel .src-people        { background: var(--tint-people-bg);        color: var(--tint-people-text); }

/* The group's tint carries down its items as a left edge -- the "reading down
   through layers" idea the design language is named for. */
.source-group { margin-bottom: var(--space-4); }
.source-group .source-item {
  border-left: 2px solid var(--edge, var(--border-card));
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
.source-group-tickets       { --edge: var(--tint-tickets-text); }
.source-group-code          { --edge: var(--tint-code-text); }
.source-group-conversations { --edge: var(--tint-conversations-text); }
.source-group-docs          { --edge: var(--tint-docs-text); }
.source-group-people        { --edge: var(--tint-people-text); }

/* finding P9: +1 / -1 were full secondary buttons, visually equal to Share and
   Watch. Feedback is a quiet, optional afterthought, not a peer action. */
.answer-feedback .btn {
  padding: var(--space-2);
  min-width: 36px;
  color: var(--text-tertiary);
  border-color: transparent;
  background: transparent;
}
.answer-feedback .btn:hover { background: var(--raised-bg); color: var(--text-primary); }
.answer-feedback .btn.active { color: var(--action); border-color: var(--border-input); background: var(--raised-bg); }

/* finding P5: the ask row drew a focus ring AND the input drew its own border,
   giving a doubled blue outline. The row owns the ring; the input goes seamless.

   That fix only ever covered .search-bar below. .ask-inputbar still showed BOTH --
   the form's box-shadow ring and the input's own `outline: 2px solid var(--accent)`
   from the global input:focus-visible rule -- which is the double ring on the Ask
   screen. The form owns the ring; the input inside it draws none. */
.ask-inputbar:focus-within { box-shadow: var(--focus-ring-shadow); border-color: var(--accent); }
/* The :not() chain is copied from the base rule at the top of this file on purpose,
   NOT tidied away: `input:not([type=radio]):not([type=checkbox]):focus` scores 0,3,1,
   so the obvious `.ask-inputbar input:focus-visible` (0,2,1) loses to it and the input
   kept drawing its own ring inside the form's. Matching the chain scores 0,4,1. */
.ask-inputbar input:not([type="radio"]):not([type="checkbox"]):focus,
.ask-inputbar input:not([type="radio"]):not([type="checkbox"]):focus-visible {
  outline: none;
  box-shadow: none;
  border-color: transparent;
}
/* The composer is a <textarea> now, and textarea is the EASY case: the two base
   rules it has to beat are `textarea:focus-visible` and `textarea:focus`, both
   (0,1,1), with no :not() chain inflating them the way the input ones are. So
   (0,2,1) here wins on specificity alone and does NOT depend on source order --
   worth stating, because the input rule above had to be hand-tuned to 0,4,1 and a
   later reader could reasonably assume this one is just as fragile. It is not. */
.ask-inputbar textarea:focus,
.ask-inputbar textarea:focus-visible {
  outline: none;
  box-shadow: none;
  border-color: transparent;
}
.search-bar input:not([type="radio"]):not([type="checkbox"]):focus,
.search-bar input:not([type="radio"]):not([type="checkbox"]):focus-visible {
  outline: none;
  box-shadow: none;
  border-color: transparent;
}

.answer-feedback { display: inline-flex; align-items: center; gap: var(--space-1); margin-left: auto; }
.answer-feedback-label { font-size: 0.8rem; color: var(--text-tertiary); margin-right: var(--space-1); }
.answer-feedback form { display: inline-flex; }
/* The sidebar's new-question control is a navigation affordance, not the page's
   primary action -- it sits above the nav, so it must not out-shout it. */
.ask-new-question { margin-bottom: var(--space-4); }

/* Today's "Try asking" row: a quiet suggestion strip, not a second page headline
   (finding C6 -- the 40px h2 and its own search box were removed). */
.home-ask-heading {
  font-family: var(--font-heading);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text-tertiary);
  margin: 0 0 var(--space-3);
}

/* Company-bets entry, behind a disclosure (finding H5) */
.bets-add { border-top: 1px solid var(--border-divider); padding: var(--space-3) var(--space-5) var(--space-4); }
.bets-add > summary {
  cursor: pointer;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-secondary);
  padding: var(--space-2) 0;
  list-style: none;
}
.bets-add > summary::-webkit-details-marker { display: none; }
.bets-add > summary::before { content: "+ "; color: var(--text-tertiary); }
.bets-add[open] > summary::before { content: "− "; }
.bets-add > summary:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; border-radius: var(--radius-sm); }
.bets-add-form { display: grid; gap: var(--space-3); margin-top: var(--space-3); }
.bets-add-form .btn { justify-self: start; }

/* ===================================================================
   Connections (rewritten in the 2026-10 design pass; replaces 109
   inline style= attributes that used to live in connections.html)
   =================================================================== */
.scope-note {
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-top: var(--space-5);
  margin-bottom: var(--space-5);
  background: var(--card-bg);
}
.scope-note > summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--text-secondary);
  list-style: none;
}
.scope-note > summary::-webkit-details-marker { display: none; }
/* A chevron, not a literal "?" -- the question mark read as part of the label
   ("? Who can see what"). The default marker is suppressed above, so something
   has to signal that this row opens. */
.scope-note > summary::before {
  content: "";
  display: inline-block;
  width: 0.4em;
  height: 0.4em;
  margin-right: var(--space-2);
  vertical-align: 0.1em;
  border-right: 1.5px solid var(--text-tertiary);
  border-bottom: 1.5px solid var(--text-tertiary);
  transform: rotate(-45deg);
  transition: transform 150ms ease;
}
.scope-note[open] > summary::before { transform: rotate(45deg); }
.scope-note > summary:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; border-radius: var(--radius-sm); }
.scope-note p { margin: var(--space-3) 0 0; font-size: 0.9rem; color: var(--text-tertiary); line-height: 1.6; max-width: 72ch; }

.linkmap-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}
.linkmap-text { min-width: 0; flex: 1 1 220px; }
.linkmap-text strong { display: block; color: var(--text-primary); margin-bottom: var(--space-1); }
.linkmap-text p { margin: 0; color: var(--text-tertiary); font-size: 0.92rem; }
.linkmap-progress { flex-shrink: 0; text-align: right; }
.linkmap-progress .progress-track {
  background: var(--raised-bg);
  border-radius: var(--radius-md);
  height: 6px;
  width: 180px;
  overflow: hidden;
  margin-bottom: var(--space-2);
}
/* Ink, not Signal blue: a sync meter is progress, not an answer (finding C3). */
.linkmap-progress .progress-fill { background: var(--action); height: 100%; border-radius: var(--radius-md); }
.linkmap-count {
  margin: 0;
  font-size: 0.72rem;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.connection-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
.connection-card { display: flex; flex-direction: column; padding: var(--space-4); }
.connection-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-3); }

/* The tool mark was a solid Signal-blue square with initials on all eight cards --
   the single biggest use of the accent as decoration on any screen (finding C3).
   Neutral by default; the badge beside it carries the state. */
.tool-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  background: var(--raised-bg);
  border: 1px solid var(--border-card);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
}

.connection-name { font-family: var(--font-heading); font-size: 1.02rem; font-weight: 600; margin: 0 0 var(--space-3); }
.connection-facts {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-4);
  font-size: 0.86rem;
  flex-grow: 1;
}
.connection-facts dt { color: var(--text-tertiary); }
.connection-facts dd { margin: 0; color: var(--text-primary); }
.connection-foot { border-top: 1px solid var(--border-divider); padding-top: var(--space-3); margin-top: auto; }
.connection-stat, .connection-error { font-size: 0.76rem; margin: 0 0 var(--space-2); }
.connection-stat { color: var(--text-tertiary); }
.connection-error { color: var(--danger); }
.connections-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
.connections-foot .meta { margin: 0; font-size: 0.86rem; color: var(--text-tertiary); }

/* Agents: the actions toggle (was five inline declarations) */
.op-status-toggle {
  background: none;
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  font: inherit;
  color: var(--text-secondary);
}
.op-status-toggle:hover { border-color: var(--text-tertiary); }
.op-waitlist-banner { margin-bottom: var(--space-6); }
/* An agent whose switch is off reads as dimmed, not as a different colour -- but the
   dim belongs on the BOX, never on the letters inside it. `opacity: 0.45` on the whole
   tile put the initials at 1.84:1 in light mode and 2.22:1 in dark (axe, via
   scripts/ui_audit.py, on every Agents screen in both themes): not "quiet", unreadable.
   `.op-avatar.off` above already repaints it in the tertiary token on the raised
   surface, which is ~6:1, and the row says the word "Off" beside it, so the state is
   carried by a word and a surface rather than by colour alone -- the rule CLAUDE.md
   sets for every status in the product. */
.tool-mark.op-avatar.off { border-color: var(--border-divider); }
/* finding H8: agent cards had four different footer shapes, so the grid was ragged.
   A fixed-height footer row lines every card's baseline up. */
.op-card { display: flex; flex-direction: column; }
.op-card .op-card-foot, .op-card > :last-child { margin-top: auto; }

/* ===================================================================
   Utilities (2026-10 design pass)
   A deliberately small set, every value drawn from the spacing scale,
   added so templates stop carrying 581 inline style= attributes. These
   are the ONLY utilities -- if a screen needs something else, it gets a
   named component class, not a new utility.
   =================================================================== */
.lede { max-width: 68ch; margin: 0; color: var(--text-tertiary); line-height: 1.6; }
.page-title-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-5);
  margin-bottom: var(--space-5);
  flex-wrap: wrap;
}
.page-title { font-family: var(--font-heading); font-size: 1.75rem; font-weight: 700; line-height: 1.15; margin: 0 0 var(--space-2); }
.card-note { padding: var(--space-1) var(--space-5) var(--space-4); margin: 0; color: var(--text-tertiary); font-size: 0.9rem; }

.m-0  { margin: 0; }
.mt-0 { margin-top: 0; }
.mt-2 { margin-top: var(--space-2); }
.mt-4 { margin-top: var(--space-4); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-5 { margin-bottom: var(--space-5); }
.mb-6 { margin-bottom: var(--space-6); }
.meta-sm { font-size: 0.76rem; color: var(--text-tertiary); }
.center-block { padding: var(--space-7) var(--space-5); text-align: center; }
.input-sm { padding: var(--space-2); border: 1px solid var(--border-input); border-radius: var(--radius-md); }
.connections-links { display: flex; gap: var(--space-4); flex-wrap: wrap; }

.connection-desc { font-size: 0.88rem; color: var(--text-tertiary); margin: 0 0 var(--space-4); flex-grow: 1; line-height: 1.55; }
/* Pro-gated tools, kept visible but visually secondary (finding H6) */
.pro-tools { margin-top: var(--space-6); }
.pro-tools-head {
  font-family: var(--font-heading);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text-tertiary);
  margin: 0 0 var(--space-3);
}
.pro-tools .connection-card { background: var(--sidebar-bg); }
.op-search-input { padding: var(--space-2) 0; border: none; background: none; font: inherit; color: inherit; width: 100%; }
.op-search-input:focus { outline: none; }

/* Second utility round (2026-10 design pass): the repeated patterns left after the
   first sweep. Still token-valued, still a closed set. */
.pill-sm     { padding: var(--space-2) var(--space-3); }
.pill-xs     { padding: var(--space-1) var(--space-3); }
.prose-col   { max-width: 68ch; }
.section-head{ margin: var(--space-6) 0 var(--space-3); }
/* Deliberately NOT `min-width: 0` here, though that is the canonical flexbox fix.
   Adding it to a utility this widely used trades overflow for COLLAPSE: with basis 0
   and no floor, a column next to a non-shrinking sibling squeezes to an unreadable
   ribbon instead of making the row wrap. That was measured on .attention-body at
   390px -- a 45px-wide text column -- and it is invisible to an overflow check, which
   is why the component rules below set a real flex-basis instead. */
.flex-1      { flex: 1; }
.note        { font-size: 0.9rem; color: var(--text-tertiary); }
.tertiary    { color: var(--text-tertiary); }
.stat-big    { font-size: 1.4rem; font-weight: 700; }
.stack-2     { display: flex; flex-direction: column; gap: var(--space-2); }
.field-label { display: block; font-weight: 500; margin-bottom: var(--space-2); }
.inline-row  { display: flex; align-items: center; gap: var(--space-2); font-weight: 500; }
.mb-3b       { margin-bottom: var(--space-3); }

@media (max-width: 900px) {
  /* Remaining sub-44px controls found by the Step 4 audit, by group.
     Native checkboxes/radios stay their platform size -- the LABEL around them is the
     real tap target, so that is what gets the height. */
  label:has(> input[type="checkbox"]),
  label:has(> input[type="radio"]),
  .checkbox-row,
  .radio-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 44px;
  }
  .topbar-askbar { min-height: 44px; }
  .op-tab { min-height: 44px; display: inline-flex; align-items: center; }
  /* Small links inside dense card headers, rows and footers. An ::after hit area
     works but cannot be verified by measuring the element, so these get a real 44px
     box at mobile instead -- the extra height is fine on a phone and the layouts
     these sit in are single-column at this width anyway. */
  .home-card-head a,
  .op-card a,
  .op-card-footer a,
  .attention-title a,
  .connection-stat a,
  .team-load-name,
  .onboarding-checklist a,
  .foot-link a,
  .legal-nav a,
  .nav-links a,
  .empty-note a,
  .col-working-cell a,
  .auth-logo,
  .legal-logo,
  .skip-link,
  summary.eyebrow-label,
  .pill-sm input,
  .pill-xs input {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* Signed-in card + the logout control that was missing entirely before the
   2026-10 design pass (/logout existed; nothing linked to it). */
.sidebar-footer {
  /* A plain flex child at the end of the sidebar column, NOT sticky. .sidebar-scroll
     above takes all the leftover height and scrolls, so this stays on screen at every
     scroll position without being painted over anything. It was position:sticky with
     a z-index before, which is what hid the block rendered after it. */
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--sidebar-bg);
  border-top: 1px solid var(--border-divider);
  padding-top: var(--space-3);
}
.sidebar-user-text { min-width: 0; flex: 1 1 0; }
.sidebar-user-name, .sidebar-user-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-logout {
  /* A labelled control, not a bare icon. An unlabelled exit glyph tucked beside an
     avatar is not something a person finds when they are looking to sign out. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  flex-shrink: 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  background: var(--card-bg);
  font-size: 0.82rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.sidebar-logout:hover { background: var(--raised-bg); color: var(--text-primary); }
.sidebar-logout:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
@media (max-width: 900px) { .sidebar-logout { min-height: 44px; } }

/* Mobile-only logout in the top bar. Hidden on desktop, where the sidebar's own
   labelled Log out control is always visible. */
.topbar-logout { display: none; }
@media (max-width: 900px) {
  .topbar-logout {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    flex-shrink: 0;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    margin-left: auto;
    border: 1px solid var(--border-input);
    border-radius: var(--radius-md);
    background: var(--card-bg);
    color: var(--text-secondary);
    font-size: 0.82rem;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
  }
  .topbar-logout:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
}

/* ===================================================================
   Ask: "Soil can see" — real per-connector counts under the input, so
   a person knows what there is to ask about, and can tell "nothing has
   synced yet" apart from "Soil is broken". A connector that is
   connected but never synced is shown as such, not counted as working.
   =================================================================== */
.ask-visibility { margin-top: var(--space-5); }
.ask-visibility-head {
  font-family: var(--font-heading);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin: 0 0 var(--space-2);
}
.ask-visibility-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.ask-visibility-item {
  display: grid;
  grid-template-columns: 90px minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-2) var(--space-3);
  background: var(--card-bg);
  border: 1px solid var(--border-divider);
  border-radius: var(--radius-sm);
  font-size: 0.86rem;
}
.ask-visibility-tool { font-weight: 600; color: var(--text-primary); }
.ask-visibility-counts { color: var(--text-secondary); font-size: 0.78rem; }
.ask-visibility-synced { color: var(--text-tertiary); font-size: 0.74rem; white-space: nowrap; }
.ask-visibility-item.is-empty .ask-visibility-tool,
.ask-visibility-item.is-empty .ask-visibility-counts { color: var(--text-tertiary); }

@media (max-width: 900px) {
  .ask-visibility-item { grid-template-columns: 1fr auto; row-gap: var(--space-1); }
  .ask-visibility-counts { grid-column: 1 / -1; }
}

/* ===================================================================
   AGENTS PAGE (2026-10) — appended block, nothing above is modified.

   The Agents screen was rebuilt so each card can prove the agent works:
   a real on/off switch (not a full-width button), schedule, last run,
   what it found this week, its latest narrated result with the source
   it came from, and a disclosure that opens the agent's steps, rule and
   run history in place. Every value here resolves to a Strata token:
   the spacing scale (--space-1..7), --radius-sm/md/lg, and the existing
   colour tokens. Signal blue is NOT used as a button colour anywhere
   below — the switch's "on" state uses --success, and the one primary
   action stays ink (--action), per the design system.
   =================================================================== */

/* Header: the page's one primary action sits beside the org actions toggle. */
.agents-header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

/* Finding: an empty approvals queue rendered a full card with a floating "0".
   Nothing waiting on you is one quiet line, not a container. */
.approvals-clear {
  margin: 0 0 var(--space-5);
  padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--border-input);
  color: var(--text-tertiary);
  font-size: 0.92rem;
}

/* A proposed action's preview is the thing a person actually reads before
   approving, so it gets room and keeps the draft's own line breaks. */
.approval-preview {
  max-width: 68ch;
}
.approval-preview-draft {
  white-space: pre-wrap;
}

.agents-grid-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  margin: var(--space-6) 0 var(--space-3);
  flex-wrap: wrap;
}

/* One control, one border. The old search box nested an <input> inside a
   bordered .search-bar wrapper, which drew two outlines. It is also only
   rendered at all once an org has more than nine agents. */
.agent-search {
  width: min(280px, 100%);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  font: inherit;
  color: inherit;
}

.agent-list {
  /* var(--card-bg), never a literal hex: a literal cannot theme, and in dark mode this
     rendered a WHITE list behind light text -- measured 2.76:1, against CLAUDE.md's 4.5
     minimum. This is the defect the Today-only design pass named and left. */
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-6);
  overflow: hidden;
}
.agent-item {
  border-bottom: 1px solid var(--border-card);
}
.agent-item:last-child {
  border-bottom: 0;
}
.agent-item[hidden] {
  display: none;
}

.agent-row {
  display: grid;
  grid-template-columns: minmax(0, 2.2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.3fr) 104px;
  gap: var(--space-4);
  /* start, not center: the columns are a label over a value, so their first lines are
     what must line up across the row. (Until 2026-10-08 the reason given here was a
     wrapping job description, which no longer renders on the face of the row -- the
     alignment is still start, for the labels.) */
  align-items: start;
  padding: var(--space-4) var(--space-5);
}

.agent-row-id {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
}
.agent-row-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.agent-row-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.agent-col-label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-tertiary);
}
.agent-col-value {
  font-size: 0.84rem;
  color: var(--text-secondary);
  /* An agent name or a watched item can carry an identifier from a connected tool. */
  overflow-wrap: anywhere;
  min-width: 0;
}

.agent-row-switch {
  justify-self: end;
  min-width: 0;
}

/* Off reads as off: the whole row recedes, and the ON/OFF word beside the switch
   carries the state in text, never colour alone. */
.agent-item.is-off .agent-name,
.agent-item.is-off .agent-job,
.agent-item.is-off .agent-col-value {
  color: var(--text-tertiary);
}

/* Capped rather than full-bleed: as the last cell of a three-column grid this card was
   ~320px wide, and letting it span the whole list stretched its three example prompts
   into 1300px bars with a few words adrift in each. */
.agent-new-wrap {
  margin-bottom: var(--space-6);
  max-width: 44rem;
}
.agent-card-new {
  padding: var(--space-5);
}

/* Mobile: one column, every field on its own line, and the switch on its own row at
   the end rather than tucked into the top-right corner.

   Two earlier attempts are worth recording, because both LOOKED right in a screenshot
   and were not. Putting the identity in column 1 beside the switch squeezed the job
   description into a 148px ribbon six lines deep. Spanning the identity and reserving
   the switch's corner with padding on the name instead let the job run to the row's
   full width -- and a box check then found the job overlapping the switch in 7 of 10
   rows. It only read correctly because the text happened to wrap before reaching it;
   a longer first word would have collided. CSS cannot indent only a first line, and a
   grid item ignores float, so the robust answer is to stop sharing the line at all. */
@media (max-width: 1180px) {
  .agent-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
  }
  .agent-row-id,
  .agent-row-col,
  .agent-row-switch { grid-column: 1 / -1; }
  .agent-row-switch { justify-self: end; }
  .agent-row-col { flex-direction: row; gap: var(--space-2); }
  .agent-col-label { min-width: 5.5rem; }
}

.agent-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

/* The switch. A real <button role="switch"> inside a form, so it posts and is
   reachable by keyboard; the track/thumb are decoration on top of that. */
.agent-switch-form {
  margin: 0;
}
.agent-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  background: none;
  border: none;
  padding: var(--space-1);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-tertiary);
}
.agent-switch:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
.agent-switch-track {
  position: relative;
  width: 34px;
  height: 20px;
  border-radius: var(--radius-lg);
  background: var(--raised-bg);
  border: 1px solid var(--border-input);
  transition: background 0.15s ease, border-color 0.15s ease;
}
.agent-switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--text-tertiary);
  transition: transform 0.15s ease, background 0.15s ease;
}
.agent-switch.on {
  color: var(--success);
}
.agent-switch.on .agent-switch-track {
  background: var(--success-bg);
  border-color: var(--success);
}
.agent-switch.on .agent-switch-thumb {
  background: var(--success);
  transform: translateX(14px);
}

/* Name and job sit in the row's identity column now, not as an h3/p pair in a card,
   so they carry no margins of their own -- the flex column's gap spaces them. */
.agent-name {
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 700;
  color: var(--text-primary);
  margin: 0;
  overflow-wrap: anywhere;
  min-width: 0;
}
.agent-job {
  font-size: 0.84rem;
  line-height: 1.45;
  margin: 0;
  color: var(--text-secondary);
}
/* The job sentence moved off the face of the row and into the row's own disclosure
   (agents.html, 2026-10-08): nine agents x 22 words was a fifth of this screen's visible
   prose, and every fact in it is restated by the Trigger / Reads / Explains / Proposes
   steps directly below this paragraph. It is still the first thing inside the
   disclosure, still the `title` on the agent's name, and still in `data-search`. */
.agent-detail-job {
  margin: 0 0 var(--space-3);
}

/* Schedule / last run, as a definition list so it reads as data, not prose. */
.agent-meta {
  display: flex;
  gap: var(--space-5);
  margin: 0 0 var(--space-3);
  flex-wrap: wrap;
}
.agent-meta-item dt {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-tertiary);
  margin-bottom: var(--space-1);
}
.agent-meta-item dd {
  margin: 0;
  font-size: 0.84rem;
  color: var(--text-secondary);
}

.agent-impact {
  margin: 0 0 var(--space-3);
  font-size: 0.86rem;
  color: var(--text-secondary);
}

/* The latest real result. It used to sit on the face of the card with flex-grow
   absorbing the difference between a one-line and a three-line narration; it lives
   inside the disclosure now, so it simply flows. */
.agent-latest {
  padding: var(--space-3) 0 var(--space-4);
}
.agent-latest-label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-tertiary);
  margin: 0 0 var(--space-2);
}
.agent-latest-text {
  margin: 0;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--text-secondary);
}
.agent-latest-sources {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
}

/* The disclosure belongs to the row above it, so it carries the row's own horizontal
   padding -- without it the summary hung off the left edge of the list, reading as a
   stray line rather than part of that agent. Same reasoning as .conn-manage summary. */
.agent-detail {
  border-top: 0;
}
.agent-open {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-2);
  padding: 0 var(--space-5) var(--space-4);
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-tertiary);
  list-style: none;
}
.agent-open:hover {
  color: var(--text-primary);
}
.agent-open::-webkit-details-marker {
  display: none;
}
.agent-open-caret {
  color: var(--text-tertiary);
  transition: transform 0.15s ease;
}
.agent-detail[open] > .agent-open .agent-open-caret {
  transform: rotate(90deg);
}
/* Indented under the row it belongs to, and tinted so an open agent is obviously an
   expansion of the row above rather than a new record in the list. */
.agent-detail-body {
  padding: var(--space-4) var(--space-5) var(--space-5);
  background: var(--sidebar-bg);
  border-top: 1px solid var(--border-divider);
}
.agent-detail-head {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-tertiary);
  margin: var(--space-4) 0 var(--space-2);
}
.agent-detail-body > .agent-detail-head:first-child {
  margin-top: 0;
}

.agent-steps {
  margin: 0;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: 0.86rem;
  color: var(--text-secondary);
}
.agent-step-label {
  font-weight: 600;
  color: var(--text-primary);
}

.agent-gov-form {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.agent-gov-label {
  font-size: 0.84rem;
  color: var(--text-secondary);
}
/* The rule's heading and the select's label are ONE element now (agents.html,
   2026-10-08): an h4 reading "Rule for Nudge (Slack DM)" above a label reading "How
   this action is approved" is one fact said twice, nine times down the page. The label
   keeps .agent-detail-head's mono look, takes the whole first line of the form so the
   controls still sit under it exactly as they did under the h4, and drops that rule's
   vertical margin -- a margin on a flex item whose siblings have none shifts the row
   off centre. The form carries the top margin the h4 used to provide instead. */
.agent-detail-body .agent-gov-form {
  margin-top: var(--space-4);
}
.agent-gov-form > .agent-gov-label.agent-detail-head {
  margin: 0;
  flex-basis: 100%;
}

.agent-runs {
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.agent-run {
  border-left: 2px solid var(--border-divider);
  padding-left: var(--space-3);
}
.agent-run-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: 0.8rem;
}
.agent-run-when {
  color: var(--text-tertiary);
}
.agent-run-result {
  font-weight: 600;
}
.agent-run-text {
  margin: var(--space-2) 0 0;
  font-size: 0.84rem;
  line-height: 1.5;
  color: var(--text-secondary);
}

.agent-detail-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-4);
}
.agent-detail-actions form {
  margin: 0;
}

/* The builder entry point, now its own card below the list rather than a cell in it.
   Dashed so it reads as "add one", never as an agent that exists. */
.agent-card-new {
  border-style: dashed;
  background: var(--sidebar-bg);
}
/* The three seed prompts sit behind a disclosure (agents.html, 2026-10-08): 42 words of
   example text that only matters to somebody who has already decided to start one, and
   they are the single biggest block of prose left on the screen once the queue is read.
   A <details>, not script -- the page runs no JavaScript for this. */
.agent-examples {
  display: block;
  /* The sentence above it has margin: 0, and the label this replaced carried the only
     separation there was. */
  margin-top: var(--space-3);
}
.agent-examples form {
  margin: 0;
}
.agent-examples-summary {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-tertiary);
  cursor: pointer;
  padding: var(--space-1) 0;
}
.agent-examples-summary:hover {
  color: var(--text-primary);
}
.agent-examples-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.agent-example {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--card-bg);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--text-secondary);
  cursor: pointer;
}
.agent-example:hover {
  border-color: var(--text-tertiary);
  color: var(--text-primary);
}
.agent-new-foot {
  margin-top: var(--space-4);
}

/* Custom agents page: the three worked examples above the builder. */
.ca-examples {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.ca-examples form {
  margin: 0;
}

@media (max-width: 900px) {
  .agent-switch,
  .agent-open,
  .agent-examples-summary,
  .agent-example {
    min-height: 44px;
  }
  /* A summary stays display: list-item -- a block-level box, so min-height applies to
     it (the trap CLAUDE.md records is min-height on an INLINE box) and it keeps its
     native disclosure triangle, which display: flex would delete. The padding is what
     makes the words sit in the middle of the 44px, since a block cannot centre them. */
  .agent-examples-summary {
    padding: var(--space-3) 0;
  }
  .agent-open {
    align-items: center;
  }
  .agents-header-actions {
    width: 100%;
  }
}

/* Rows size to their own content, so opening one no longer stretches its siblings --
   the :has() rule that used to undo the card grid's equal heights is not needed. */

/* Step 0 (design/soil-app-design.html): shown to admins when this deployment is
   running the canned test provider instead of a real model. Deliberately loud --
   placeholder prose passing as a real answer is the worst failure this app has. */
.llm-fake-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-5);
  border: 1px solid var(--danger);
  border-radius: var(--radius-md);
  background: var(--danger-bg);
  color: var(--danger);
  font-size: 0.9rem;
  line-height: 1.5;
}
.llm-fake-banner code {
  font-family: var(--font-mono);
  font-size: 0.82em;
  background: var(--card-bg);
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
}
.llm-fake-banner a { color: var(--danger); font-weight: 600; }

/* ===========================================================================
   2026-10 REDESIGN -- shared shell, Today and Ask
   (design/soil-app-design.html; see that file's header comment for the spec)

   Appended, never edited in place: every rule above this line is still the
   source of truth for the screens other agents own, and the handful of rules
   below that intentionally override an earlier one say so in a comment.

   Strata rules this block obeys, same as the rest of the file:
     * spacing is only --space-1..7 (4/8/12/16/24/32/48); radii only
       --radius-sm/md/lg plus 50% for dots,
     * --action (ink) is the only primary-button colour; Signal blue appears
       only on Soil's own intelligence -- the answer surface, citations, the
       numbered source markers and the "why this is happening" label,
     * status colour never carries meaning alone: every vs-normal pill, tag and
       confidence badge also says the word.
   =========================================================================== */

/* --- Shell: content column ------------------------------------------------ */
/* Overrides .main-content's `max-width: 1280px; margin: 0 auto` above. The spec
   is explicit: 1200px, left-aligned with the sidebar, not centred in a large
   empty canvas. margin-right stays auto so nothing stretches past 1200px. */
.main-content {
  max-width: 1200px;
  margin-left: 0;
  margin-right: auto;
  padding: var(--space-5) var(--space-6) var(--space-7);
  background: var(--sidebar-bg);
}

.app-main { background: var(--sidebar-bg); }

/* Overrides .sidebar's 260px above -- 232px is the design's own column. */
.sidebar {
  width: 232px;
  background: var(--card-bg);
  border-right: 1px solid var(--border-card);
}

.app-topbar {
  height: 64px;
  background: var(--card-bg);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.topbar-title {
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 700;
  color: var(--text-primary);
  white-space: nowrap;
  flex-shrink: 0;
}

/* --- Shell: org card ------------------------------------------------------ */
.org-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  margin-bottom: var(--space-5);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
  text-decoration: none;
  color: inherit;
}

.org-card:hover { border-color: var(--border-input); }
.org-card:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

/* Each fact in the meta line is its own link now, so each needs its own affordance.
   Underline on hover rather than colour: Signal blue is reserved for Soil's own
   intelligence, and a sidebar stat is not that. */
.org-card-link {
  color: inherit;
  text-decoration: none;
}
.org-card-link:hover { text-decoration: underline; color: var(--text-primary); }
.org-card-link:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; border-radius: var(--radius-sm); }

/* The providers Soil cannot prove an identity for: real, documented, and collapsed,
   because six paragraphs of architecture were the first thing on a screen someone
   opened to link one account. */
.linked-blocked summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.88rem;
  color: var(--text-tertiary);
  padding: var(--space-2) 0;
}
.linked-blocked summary:hover { color: var(--text-primary); }
.linked-blocked[open] summary { margin-bottom: var(--space-3); }

/* Rows, not raw <ul> bullets: the name and the requirement are two fields of the
   same record, which is what every other list in this app looks like. */
.leak-needs { display: flex; flex-direction: column; }
.leak-need {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
}
.leak-need:last-child { border-bottom: 0; }
.leak-need-name { font-weight: 600; color: var(--text-primary); overflow-wrap: anywhere; min-width: 0; }
.leak-need-why { color: var(--text-secondary); overflow-wrap: anywhere; min-width: 0; }
@media (max-width: 1180px) {
  .leak-need { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
}

/* The work title links to the same place its chip does. Inherits colour rather than
   taking link blue -- Signal blue is reserved for Soil's own intelligence. */
.work-title-link {
  color: inherit;
  text-decoration: none;
}
.work-title-link:hover { text-decoration: underline; color: var(--text-primary); }
.work-title-link:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; border-radius: var(--radius-sm); }

.org-card-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: var(--action);
  color: var(--action-text);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.78rem;
}

.org-card-text { display: flex; flex-direction: column; min-width: 0; }
.org-card-name {
  font-weight: 600;
  font-size: 0.92rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.org-card-meta { font-size: 0.68rem; color: var(--text-tertiary); }

.topbar-sync .dot.dot-off { background: var(--text-tertiary); }

.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;
}

/* --- Shared surfaces used by Today and Ask -------------------------------- */
.panel {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-card);
}

.panel-head h2 {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 700;
}

.panel-head-meta { font-size: 0.8rem; color: var(--text-tertiary); }
.panel-head-link { font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); }
.panel-note { padding: var(--space-4) var(--space-5); margin: 0; line-height: 1.6; }

.panel-label {
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
  margin: 0;
}

/* --- Today: header -------------------------------------------------------- */
.setup-strip {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-5);
  margin-bottom: var(--space-5);
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  font-size: 0.9rem;
}

.setup-step { color: var(--text-tertiary); }
.setup-step.is-done { color: var(--text-secondary); }
.setup-step a { margin-left: var(--space-1); }

.today-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
  margin-bottom: var(--space-5);
}

.today-head-text { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }

.today-head h1 {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.9rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.15;
}

.today-kicker {
  margin: 0;
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
}

.today-sub { margin: 0; color: var(--text-secondary); font-size: 0.95rem; }

.today-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--space-5);
  align-items: start;
}

.today-col,
.today-rail { display: flex; flex-direction: column; gap: var(--space-5); min-width: 0; }

/* --- Today: "Needs you" -- ONE WIDE LIST, the why inside each row ---------
   SOIL_OS M1. This was `grid-template-columns: minmax(300px, 0.75fr) minmax(0, 1fr)`:
   a narrow picker of rows beside a separate panel that explained whichever one was
   selected. Two things were wrong with it and neither was cosmetic. The list that
   matters most on the whole product got about three-eighths of the content column,
   so every PR title plus repo name wrapped into a tall thin ribbon; and the panel
   beside it could only ever explain ONE row, so reading a second row's reasoning was
   a full page reload (`?focus=`) that re-rendered the entire screen.

   One column now, full width, and each row carries its own <details> with its own
   dated chain. No htmx and no JavaScript: see home.html for why that is the right
   tool here and not a shortcut. */
.attention-panel {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.attention-list {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* One row, and it carries no border of its own: the hairline between rows lives on
   `.attention-pick-row` further down, because the "Not useful" control is a SIBLING of
   this element and a border here would stop a button's width short of the card edge.
   Dividers in this list are meant to be full-bleed. (A hairline, not a card per row:
   a list of boxes inside a box reads as five competing surfaces, and Strata's default
   separator is the hairline.) */
.attn-row {
  flex: 1 1 auto;
  min-width: 0;
}

/* The <summary> IS the row, and the whole row is the click target. The native
   disclosure triangle is removed (both spellings -- `list-style` for the standards
   path, the pseudo-element for WebKit) because the word on the right says what
   opening the row gets you, which a triangle does not. */
.attn-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  min-width: 0;
  cursor: pointer;
  list-style: none;
}
.attn-head::-webkit-details-marker { display: none; }
.attn-head::marker { content: ""; }
.attn-head:hover { background: var(--sidebar-bg); }
.attn-head:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
.attn-row[open] > .attn-head { background: var(--sidebar-bg); }

.attn-head-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

/* The most urgent row -- the top-ranked one, or whichever `?focus=` names -- gets
   solid ink on its leading edge. CLAUDE.md: Strata has no colour for "urgent", only
   "the answer" (blue) and "acts on it" (black), and emphasis goes to one thing at a
   time. An inset shadow rather than a border, so the row does not shift by 3px when
   the selection moves. */
.attn-row.is-selected > .attn-head { box-shadow: inset 3px 0 0 var(--action); }
.attn-row.is-selected > .attn-head:focus-visible {
  box-shadow: inset 3px 0 0 var(--action), var(--focus-ring-shadow);
}

/* 44px at every width, not only on phones: this is the row's one control, and the
   reason CLAUDE.md gives for the floor on a phone (a word is not a target) is just as
   true of a trackpad. The <summary> around it is taller than 44px on its own. */
.attn-head-open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  min-height: 44px;
  min-width: 44px;
  padding: 0 var(--space-2);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-secondary);
}
.attn-row[open] > .attn-head .attn-head-open { color: var(--text-primary); }
/* "Why" closed, "Hide" open. A label that still reads "Why" on an opened row is
   telling the reader to do the thing they have already done. */
.attn-open-open { display: none; }
.attn-row[open] .attn-open-shut { display: none; }
.attn-row[open] .attn-open-open { display: inline; }

/* The body lines up under the summary's text: same horizontal inset, no top padding
   of its own (the summary's bottom padding is the gap). */
.attn-row .attention-why { padding: 0 var(--space-5) var(--space-5); }

/* The tag / area / signal-type strip above a row's title. `justify-content` was
   `space-between` when this lived in a 300px column and held two children; in a wide
   row that pushed the area name to the far edge, a hand's width from the tag it
   qualifies. They read as one strip now, and wrap together on a phone. */
.attention-pick-top {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  min-width: 0;
}

.pick-tag {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  font-size: 0.7rem;
  font-weight: 700;
}

.pick-tag-blocked { background: var(--danger-bg); color: var(--danger); }
.pick-tag-at-risk { background: var(--warning-bg); color: var(--warning); }
.pick-tag-on-track { background: var(--success-bg); color: var(--success); }

.pick-team {
  font-size: 0.64rem;
  color: var(--text-tertiary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A row's title, now that the row is the full width of the content column rather than
   three-eighths of it. 1.05rem is the design's body size: this IS the line a reader
   scans, and at 0.9rem it was smaller than the body text of every panel under it.
   `overflow-wrap: anywhere` because the string comes from a connected tool and can be
   one unbroken token. */
.attention-pick-title {
  font-weight: 700;
  font-size: 1.05rem;
  line-height: 1.35;
  overflow-wrap: anywhere;
  min-width: 0;
}
.attention-pick-sub {
  font-size: 0.85rem;
  color: var(--text-tertiary);
  line-height: 1.5;
  overflow-wrap: anywhere;
  min-width: 0;
}
/* "· +3 more reasons" sits on the same line as the top reason.
   NO `opacity` HERE, and that is the whole point of this rule now. It used to carry
   `opacity: 0.75`, and opacity on text multiplies down the contrast of a colour that
   is already the quietest token in the system: axe measured the result at 3.41:1 in
   light mode and 4.13:1 in dark, against CLAUDE.md's non-negotiable 4.5:1 floor
   (scripts/ui_audit.py found it at 1440px on Today, in both themes). Hierarchy on this
   line is carried by the "·" separator, the shorter phrase and `.attention-pick-sub`'s
   smaller size -- never by washing the words out. */
.attention-pick-more { white-space: nowrap; }

.attention-why {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
  min-width: 0;
}

.why-head { display: flex; flex-direction: column; gap: var(--space-1); }

/* The link through to the full Warning page. Signal blue is correct here and only
   here in this block: it is a link into Soil's own reasoning, not a button. */
.why-full-link {
  align-self: flex-start;
  margin-top: var(--space-1);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}
.why-full-link:hover { text-decoration: underline; }

/* Signal blue, used exactly as the design system allows: this label marks Soil's
   own reasoning, not a button and not decoration. */
.why-label { color: var(--accent); }

/* h2 on the Warning page, h3 inside a Today row -- the row sits under the section's
   own "Needs you" h2, so the chain's headline is a level below it. One rule, because
   the LEVEL is about document structure and the SIZE is the same either way. */
.why-head h2,
.why-head h3 {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.3;
}

.why-chain { list-style: none; margin: 0; padding: 0; }

.why-step {
  display: grid;
  grid-template-columns: 48px 12px minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-divider);
  font-size: 0.9rem;
}

.why-day { font-size: 0.68rem; color: var(--text-tertiary); padding-top: 3px; }

.why-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-top: 6px;
  background: var(--border-input);
}

.why-step.is-conclusion .why-dot { background: var(--action); }
.why-step.is-conclusion .why-text { font-weight: 600; }
/* Soil's conclusion is now one row per reason rather than every reason joined into
   one paragraph, so only the FIRST of that run carries the ink emphasis -- three bold
   lines in a row is a shout, and Strata gives emphasis to one thing at a time. */
.why-step.is-conclusion + .why-step.is-conclusion .why-text { font-weight: 400; }
.why-text { line-height: 1.45; min-width: 0; }
.why-plain { margin: 0; font-size: 0.95rem; line-height: 1.6; color: var(--text-secondary); }

.why-unblock {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
}

/* The row already wraps and the text column already has min-width: 0, so what was left
   over at 1180px was the BUTTON itself: 141px of "Add reviewer" in a 138px column, 3px
   past the edge even on its own line. Same treatment .conn-action .btn already gets --
   let the label wrap rather than force the container wider. */
.why-unblock > form { min-width: 0; }
.why-unblock .btn { white-space: normal; }

.why-unblock-text { display: flex; flex-direction: column; min-width: 0; }
.why-unblock-label { font-size: 0.78rem; color: var(--text-tertiary); }
.why-unblock-value { font-weight: 600; }

/* --- Today: quiet day ----------------------------------------------------- */
.quiet-banner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
}

.quiet-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--success-bg);
  color: var(--success);
  font-weight: 700;
}

.quiet-text { display: flex; flex-direction: column; }
.quiet-text span { color: var(--text-tertiary); font-size: 0.9rem; }

/* --- Today: in progress table --------------------------------------------- */
.work-table { display: flex; flex-direction: column; }

.work-row {
  display: grid;
  /* WHO is 170, not 150: it carries a hand-off ("Rahul → Ana", design p1), which is
     two names and an arrow, and the column ellipsises. */
  grid-template-columns: minmax(0, 1fr) 170px 110px 120px;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
  font-size: 0.9rem;
}

.work-row:last-child { border-bottom: 0; }

.work-head {
  font-family: var(--font-mono);
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}

.work-what { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }

.work-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.work-who {
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.work-wait { font-size: 0.82rem; }

.vs {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
}

.vs-bad  { background: var(--danger-bg);  color: var(--danger); }
.vs-warn { background: var(--warning-bg); color: var(--warning); }
.vs-ok   { background: var(--success-bg); color: var(--success); }
.vs-none { background: var(--raised-bg);  color: var(--text-tertiary); }

/* --- Today: since-yesterday feed ------------------------------------------ */
.feed { list-style: none; margin: 0; padding: 0; }

.feed-row {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
  font-size: 0.9rem;
}

.feed-row:last-child { border-bottom: 0; }
.feed-time { font-size: 0.68rem; color: var(--text-tertiary); }
.feed-text { min-width: 0; line-height: 1.45; }
.feed-text a { text-decoration: none; }
.feed-text a:hover { text-decoration: underline; }

/* --- Today rail: track record (the one dark surface in the app) ----------- */
/* THE signature dark band (CLAUDE.md's design system): ink with a subtle
   top-to-bottom gradient to #151920. One per screen, on Today / Departments /
   Warning only -- its whole job is to be the single element that stops the page
   reading as a uniform sheet of white cards.

   background is --band-dark, NOT --action: --action inverts to near-white in dark
   mode (ink is the page there), so a band painted with it would have turned into a
   white slab on a dark page. The band keeps its own token for exactly that reason.
   The text colour is literal white because this surface is dark in BOTH themes. */
.rail-dark {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--band-dark);
  color: #ffffff;
  box-shadow: var(--shadow-2);
}

.rail-dark-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.rail-dark-head h2 { margin: 0; font-family: var(--font-heading); font-size: 1rem; font-weight: 700; color: #ffffff; }
.rail-dark-head a { color: #9db2ff; font-size: 0.78rem; text-decoration: none; }
.rail-dark-head a:hover { text-decoration: underline; }

.rail-dark-figure {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.9rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.rail-dark-figure span {
  display: block;
  font-size: 0.88rem;
  font-weight: 600;
  color: #c9cdd6;
  letter-spacing: 0;
  line-height: 1.45;
}

.rail-dark-figure-small { font-size: 1rem; font-weight: 600; line-height: 1.5; }
.rail-dark-note { margin: 0; font-size: 0.78rem; color: #9aa0ad; line-height: 1.5; }

.track-strip { display: flex; gap: var(--space-1); }

.track-cell {
  flex: 1 1 0;
  height: 18px;
  border-radius: var(--radius-sm);
  background: #3a404c;
}

.track-cell-resolved { background: #3dd68c; }
.track-cell-escalated { background: #9db2ff; }

/* --- Today rail: your company's normal --------------------------------------- */
.normal-list { display: flex; flex-direction: column; }

.normal-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
}

.normal-row:last-child { border-bottom: 0; }
.normal-label { color: var(--text-secondary); font-size: 0.9rem; min-width: 0; }

.normal-value {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.normal-note { grid-column: 1 / -1; font-size: 0.78rem; color: var(--text-tertiary); }

/* --- Today rail: agents + ask --------------------------------------------- */
.agents-week { margin: 0; padding: var(--space-4) var(--space-5) var(--space-2); font-size: 0.9rem; color: var(--text-secondary); }

.agent-lines { display: flex; flex-direction: column; gap: var(--space-2); padding: 0 var(--space-5) var(--space-4); }

.agent-line { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); font-size: 0.86rem; }
.agent-line-name { font-weight: 600; }
.agent-line-detail { color: var(--text-tertiary); text-align: right; }

.ask-suggestions { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4) var(--space-5) var(--space-2); }

.ask-suggestion {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.88rem;
  cursor: pointer;
}

.ask-suggestion:hover { border-color: var(--border-input); background: var(--sidebar-bg); }
.ask-suggestion:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

/* --- Ask: history rail + one conversation column --------------------------- */
/* Two columns, not three. The third was a SECOND listing of sources the answer prose
   already links to inline, and a permanent side panel is most of what made this read
   as a document rather than a conversation. That list now lives inside each answer's
   own disclosure (.ask-proof), so nothing was dropped -- it moved to the answer it
   belongs to. */
.ask-layout {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
  /* Ask's own reading width. NOT --page-max (1560px): that token is shared by five
     page-width rules and is deliberately left alone. Ask is a reading-and-typing
     screen, and a question box stretched across 1560px is harder to scan than one
     at 1200 -- but the grid still FILLS that 1200, so the start screen has no empty
     right half, which is what it had when the centre column was the only thing in
     a three-column grid. */
  max-width: 1200px;
}

.ask-rail { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.ask-rail-group { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.ask-rail-group .panel-label { padding: 0 var(--space-2) var(--space-2); }

.ask-rail-q {
  padding: var(--space-2) var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.88rem;
  line-height: 1.35;
  /* A question can quote a ticket key or URL from a connected tool with no spaces
     in it, which would otherwise push the rail wider than its column. */
  overflow-wrap: anywhere;
  min-width: 0;
}

/* Two lines, then "…" at a WORD boundary. The clamp is on this span rather than on
   the anchor because the anchor is a flex item inside .ask-rail-row, and a flex
   item's `display` is blockified -- `-webkit-box` there computed to `flow-root`, the
   clamp never engaged, and the question was simply cut off at three lines with no
   ellipsis at all. A plain `text-overflow: ellipsis` cannot do this either: it does
   nothing on a wrapping box. The full text stays on the anchor's title attribute, so
   hover still gives all of it. */
.ask-rail-q-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}

.ask-rail-q:hover { background: var(--card-bg); color: var(--text-primary); }
.ask-rail-q:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

.ask-rail-q.is-active {
  background: var(--card-bg);
  border-color: var(--border-card);
  color: var(--text-primary);
  font-weight: 600;
}

.ask-rail-watch {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2) var(--space-3);
  font-size: 0.88rem;
  color: var(--text-secondary);
}

.ask-rail .note { padding: 0 var(--space-3); line-height: 1.5; }

/* The conversation column: a scrolling stream with the composer pinned under it. */
.ask-convo {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

.ask-stream {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}

/* The composer sits at the END of the conversation, in normal flow -- NOT sticky.
   Sticky was tried and the screenshot killed it: the stream is part of the page's own
   scroll, so a bottom-stuck bar floats OVER the conversation, and an opened proof
   disclosure had its source list running underneath it. A chat app can pin its
   composer because the message list is its own scroll container; replicating that here
   would mean nesting a scroller inside the page scroll, which fights the sidebar on
   desktop and the address bar on mobile. Bottom-of-the-conversation reads as a chat
   without hiding the thing you are reading. */
.ask-inputbar { position: static; }

.confidence {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
}

.confidence-on-track { background: var(--success-bg); color: var(--success); }
.confidence-at-risk  { background: var(--warning-bg); color: var(--warning); }
.confidence-blocked  { background: var(--danger-bg);  color: var(--danger); }

/* ONE disclosure under each answer, carrying everything that proves it: the
   confidence and what it counts, how the answer was built, every retrieval step and
   the numbered sources. Quiet by default and one click from open -- the proof is the
   product, so it is never removed, but it does not have to shout over the answer. */
.ask-proof {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-divider);
}

.ask-proof > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--text-tertiary);
  list-style: none;
}
.ask-proof > summary::-webkit-details-marker { display: none; }
.ask-proof > summary:hover { color: var(--text-primary); }
.ask-proof > summary:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; border-radius: var(--radius-sm); }
.ask-proof-summary-text { overflow-wrap: anywhere; min-width: 0; }

.ask-proof-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-top: var(--space-3);
}

.ask-proof-trace {
  margin: 0;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: 0.84rem;
  color: var(--text-secondary);
}
/* A trace step quotes a repo path or ticket key from a connected tool. */
.ask-proof-trace li { overflow-wrap: anywhere; min-width: 0; }

.ask-proof-sources-head { margin: 0; }

/* The numbered sources were designed for the 320px side column that used to hold
   them. `justify-content: space-between` there kept the chip at the right edge of a
   narrow card; inside a full-width answer bubble it threw the number and the title to
   opposite ends of the screen with a void between. Packed left instead, which is how
   a list reads at this width. */
.ask-proof .source-row {
  justify-content: flex-start;
  align-items: flex-start;
  padding: var(--space-3) 0;
}
.ask-proof .source-list { padding: 0; }
.ask-proof .source-body { flex: 1 1 auto; }

.ask-thinking-text { margin: 0; color: var(--text-tertiary); font-size: 1rem; }

/* Actions sit OUTSIDE the disclosure: they are what you do about the answer, and
   nothing here runs without approval. */
.ask-actions { display: flex; flex-direction: column; gap: var(--space-2); }
.ask-actions-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.ask-feedback { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.btn.is-chosen { border-color: var(--action); font-weight: 700; }

.ask-starters { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.ask-starters .ask-suggestion { width: auto; }

/* --- Ask: the start screen (no question asked yet) ------------------------- */
.ask-start-head { display: flex; flex-direction: column; gap: var(--space-2); }

.ask-start-title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.95rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.15;
  /* The org's own name comes from a connected tool and can be anything. */
  overflow-wrap: anywhere;
  min-width: 0;
}

.ask-start-sub { margin: 0; color: var(--text-secondary); font-size: 0.97rem; }

/* The question box leads the page on the start screen, so it is taller than the
   follow-up bar and nothing above it competes for the first glance. */
/* min-height, not height: the start-screen box still LEADS at 64px, but it is a
   composer now and has to be able to grow past that like the follow-up bar does. */
.ask-inputbar-lg { min-height: 64px; }
.ask-inputbar-lg input,
.ask-inputbar-lg textarea { font-size: 1.08rem; }

/* Suggested questions, in three groups. auto-fit rather than a fixed 3 columns:
   a group with nothing real in it is dropped server-side, and the remaining ones
   should fill the row instead of leaving a hole where it was. */
.ask-suggest-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-4);
  align-items: start;
}

.ask-suggest-group { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.ask-suggest-group .panel-label { margin: 0; }
.ask-suggest-cards { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }

.ask-suggest-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  width: 100%;
  min-width: 0;
  padding: var(--space-3) var(--space-4);
  text-align: left;
  font: inherit;
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  cursor: pointer;
}

.ask-suggest-card:hover { border-color: var(--border-input); }
.ask-suggest-card:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

.ask-suggest-name {
  font-weight: 600;
  color: var(--text-primary);
  /* A PR title or ticket summary comes from a connected tool: it can be long, and
     it can be one unbroken token. */
  overflow-wrap: anywhere;
  min-width: 0;
}

.ask-suggest-reason {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-tertiary);
}

.ask-suggest-q { font-size: 0.86rem; color: var(--text-secondary); overflow-wrap: anywhere; }

/* "Soil can answer from": one row per Strata layer. */
.ask-layers { display: flex; flex-direction: column; gap: var(--space-3); }

.ask-layers-head {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.05rem;
  font-weight: 700;
}

.ask-layers-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  overflow: hidden;
}

.ask-layer {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-divider);
}

.ask-layer:last-child { border-bottom: 0; }
.ask-layer-chip { justify-self: start; }
.ask-layer-sees { color: var(--text-secondary); overflow-wrap: anywhere; min-width: 0; }
.ask-layer-synced { color: var(--text-tertiary); font-size: 0.74rem; white-space: nowrap; }
.ask-layer.is-empty .ask-layer-sees { color: var(--text-tertiary); }
.ask-layer-action:empty { display: none; }

@media (max-width: 900px) {
  /* The chip keeps its own row; the words wrap under it rather than being squeezed
     into a column narrower than one word. */
  .ask-layer { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--space-2); }
  .ask-layer-chip { grid-column: 1; }
  .ask-layer-synced { grid-column: 2; }
  .ask-layer-sees { grid-column: 1 / -1; }
  .ask-layer-action { grid-column: 1 / -1; justify-self: start; }
}

/* Overrides .ask-inputbar above so the follow-up box matches the design's single
   ink-bordered bar with the Ask button inside it. */
/* A message composer: the bar grows downward as the question gets longer, the way a
   chat input does, instead of scrolling a one-line box sideways. `height` became
   `min-height` so the bar can grow; `align-items: flex-end` keeps the Ask button
   pinned to the bottom edge as it does, rather than floating in the vertical middle
   of a four-line box. Padding moved off the bar and onto the textarea so the text
   scrolls UNDER the padding once the max height is reached. */
.ask-inputbar {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  min-height: 54px;
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border: 1px solid var(--action);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
}

.ask-inputbar input,
.ask-inputbar textarea {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  font: inherit;
  font-size: 1rem;
  background: transparent;
  color: var(--text-primary);
}

/* resize: none because ask.js owns the height. The max-height is what turns "grows
   forever" into "grows, then scrolls internally" -- without it a pasted essay would
   push the Ask button off the bottom of the screen. overflow-y stays auto so no
   scrollbar gutter appears until it is actually needed. */
.ask-inputbar textarea {
  resize: none;
  overflow-y: auto;
  max-height: 9.5rem;
  padding: var(--space-2) 0;
  line-height: 1.45;
  /* A question can carry a ticket key or URL from a connected tool with no spaces. */
  overflow-wrap: anywhere;
}

/* The send button must not shrink as the textarea grows, and it keeps a 44px touch
   target on mobile (design system: 44px minimum). */
.ask-inputbar .ask-send {
  flex-shrink: 0;
  min-height: 38px;
}

@media (max-width: 1180px) {
  .ask-inputbar .ask-send { min-height: 44px; }
  /* The same question wraps to far more lines in a 390px column than in a 1440px
     one, so the desktop cap starts scrolling almost immediately on a phone -- the
     screenshot showed an ordinary two-sentence question already clipped at the top.
     A taller cap on small screens keeps a normal question fully visible; it is still
     a cap, so a pasted essay cannot push the Ask button off the screen. */
  .ask-inputbar textarea { max-height: 12.5rem; }
}

/* --- Ask: numbered source rail -------------------------------------------- */
.ask-sources {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.sources-count { font-size: 0.68rem; color: var(--accent); text-transform: uppercase; }
.source-list { list-style: none; margin: 0; padding: 0; }

.source-row {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
}

.source-row:last-child { border-bottom: 0; }

/* The numbered marker is the citation: Signal blue, same as .citation above. */
.source-n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 0.64rem;
}

.source-body { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1); min-width: 0; }
.source-title { font-weight: 600; font-size: 0.9rem; color: var(--text-primary); text-decoration: none; word-break: break-word; }
.source-title:hover { text-decoration: underline; }
.source-quote { font-size: 0.8rem; color: var(--text-secondary); line-height: 1.45; }

/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 1180px) {
  .ask-layout { grid-template-columns: 200px minmax(0, 1fr); }
  .ask-sources { grid-column: 1 / -1; }
}

@media (max-width: 1040px) {
  .today-grid { grid-template-columns: minmax(0, 1fr); }
  /* `.attention-panel` and `.attention-picker` used to be unstacked here. They are
     not: the panel is one column at every width now (SOIL_OS M1), so there is
     nothing left to collapse and the picker no longer exists. */
}

@media (max-width: 900px) {
  .main-content { padding: var(--space-4) var(--space-4) var(--space-7); }
  .topbar-title { display: none; }
  .ask-layout { grid-template-columns: minmax(0, 1fr); }
  .today-head h1 { font-size: 1.45rem; }

  /* The four-column work table and the three-column feed both stop fitting well
     before 390px. Stacking is the honest fix; `minmax(0, ...)` everywhere keeps
     a long title from forcing the page wider than the viewport, which is the
     exact overflow bug CLAUDE.md's design-pass notes warn about. */
  .work-row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: var(--space-2);
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }

  .work-head { display: none; }
  .work-what { grid-column: 1 / -1; }
  .work-who { font-size: 0.82rem; }

  .feed-row {
    grid-template-columns: 48px minmax(0, 1fr);
    row-gap: var(--space-1);
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }

  .feed-row .source-chip { grid-column: 2; justify-self: start; }

  .why-step { grid-template-columns: 44px 12px minmax(0, 1fr); row-gap: var(--space-1); }
  .why-step .source-chip { grid-column: 3; justify-self: start; }

  .panel-head,
  .panel-note,
  .normal-row,
  .source-row,
  .agents-week,
  .ask-suggestions,
  .agent-lines { padding-left: var(--space-4); padding-right: var(--space-4); }

  /* The row's summary and its body keep the same horizontal inset as each other, so
     both drop to --space-4 together; anything else steps the body in or out from the
     title above it. */
  .attn-head { padding: var(--space-4); }
  .attn-row .attention-why { padding: 0 var(--space-4) var(--space-4); }
  .org-card { display: none; }
}

/* --- Redesign follow-ups found in the 1440/390 screenshot pass -------------- */

/* The generic `p { color: ... }` rule sets colour directly, so it beats the dark
   card's inherited white. Stating it here is what keeps the headline figure
   readable on ink (it was rendering graphite-on-black, an AA failure). */
.rail-dark-figure { color: #ffffff; }

/* At 232px the account row could not fit avatar + name + "Log out" on one line and
   truncated the person's name to four characters. Logging out is a full-width row
   of its own now, which also gives it a 44px touch target. `position: sticky` and
   the labelled control itself are kept exactly as they are above -- this only wraps
   them; the background is restated because the redesign makes the sidebar white, and
   a sticky footer with the old canvas background would show a seam over it. */
.sidebar-footer {
  flex-wrap: wrap;
  background: var(--card-bg);
  padding-bottom: var(--space-3);
}

.sidebar-logout { width: 100%; justify-content: center; margin-top: var(--space-2); }

/* The work key is the row's identifier -- truncating it ("MOBILE-APP!3C") loses the
   one thing that makes the row findable, so the title gives way instead. */
.work-what .source-chip {
  flex-shrink: 0;
  max-width: 45%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.trace-chip-more { color: var(--text-tertiary); }

/* The global `a` colour is Signal blue app-wide (pre-existing, left alone for every
   other screen). In the Since-yesterday feed each line's subject is the work item
   itself, so a blue link there reads as a citation -- which in Strata it is not.
   Ink, underlined on hover, keeps blue meaning only "Soil's own intelligence". */
.feed-text a { color: var(--text-primary); }

/* A long repo name ("nousonworktechnologies/KredMe") squeezed the severity tag onto
   two lines. The tag is the one thing that must stay readable at a glance, so it
   keeps its width and the identifier truncates instead. */
.pick-tag { flex-shrink: 0; white-space: nowrap; }
.pick-team { min-width: 0; }

/* Empty Ask state: nothing has been asked, so there is no source rail to show and
   the third column is not reserved for one. Scoped to the wide breakpoint on purpose:
   this block sits after the max-width:900px rules above, so an unscoped version would
   win on a phone too and put a 220px column back next to the content (a real 116px
   overflow at 390px, caught in the screenshot pass). */
@media (min-width: 901px) {
  .ask-layout-no-sources { grid-template-columns: 220px minmax(0, 1fr); }
}
/* =====================================================================
   2026-10 redesign — Settings, Updates and Track record
   (design/soil-app-design.html screens "Settings", "Updates",
   "Review and send" and "Track record").

   APPENDED BLOCK: nothing above this line was changed. Every value below
   resolves to the spacing/radius/colour tokens declared in :root at the top
   of this file — no raw px, no colour outside the Strata palette, and no
   Signal blue on a button (ink `--action` is the only primary-action colour;
   blue only ever marks Soil's own intelligence).

   The toggle/chip/day/provider controls below are all real <input
   type="radio"/"checkbox"> elements with a visible <span> label, styled via
   :checked. They look like the design's buttons but stay keyboard-operable,
   screen-reader-labelled and functional with JavaScript off, which a <button>
   + JS state would not be in a server-rendered app.
   ===================================================================== */

/* --- Segmented tab bar (Settings tabs, Updates "Which update", Track record
       filter). Rendered as real links or submit buttons, never JS tabs. --- */
.seg-tabs {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding: var(--space-1);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
}
.seg-tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-tertiary);
  font: inherit;
  font-size: 0.88rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
}
.seg-tab:hover { color: var(--text-primary); }
.seg-tab:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
.seg-tab[aria-current="page"],
.seg-tab[aria-pressed="true"] {
  background: var(--card-bg);
  color: var(--text-primary);
  font-weight: 700;
  box-shadow: var(--shadow-card);
}
.seg-tabs-sm .seg-tab { min-height: 26px; padding: 0 var(--space-2); font-size: 0.8rem; }

/* --- Two-up layouts: main column + right rail, and an even two-column grid. --- */
.cols-rail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: var(--space-5);
  align-items: start;
}
.cols-half {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-5);
  align-items: start;
}
/* Review and send: the controls are the fixed-width column and the email preview
   takes the rest — the mirror of .cols-rail. */
.cols-review {
  display: grid;
  grid-template-columns: 420px minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}
@media (max-width: 1100px) {
  .cols-review { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 1100px) {
  .cols-rail { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 900px) {
  .cols-half { grid-template-columns: minmax(0, 1fr); }
}

.rail { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.rail-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: 0.88rem;
  min-width: 0;
}
.rail-row > :last-child { color: var(--text-secondary); text-align: right; }

.stack-3 { display: flex; flex-direction: column; gap: var(--space-3); }
.stack-4 { display: flex; flex-direction: column; gap: var(--space-4); }
.stack-5 { display: flex; flex-direction: column; gap: var(--space-5); }

/* A row of buttons that wraps — named, so no screen needs an inline style for it. */
.btn-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
/* page_header's action slot is `flex-shrink: 0`, so three buttons inside it keep
   their combined intrinsic width and push a 390px viewport 32px wide. Scoped with
   :has() to rows that opt in, so no existing page-header anywhere else changes. */
.page-header-actions:has(> .btn-row) { flex-shrink: 1; min-width: 0; }
.input-block { width: 100%; }
.provider-name { font-family: var(--font-heading); font-size: 1.35rem; font-weight: 800; }

/* --- Card with its own header strip and flush (unpadded) body. --- */
.card-flush { padding: 0; overflow: hidden; }
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-card);
}
.card-head h2,
.card-head h3 { font-family: var(--font-heading); font-size: 1rem; font-weight: 700; margin: 0; }
/* .card-head is a flex row and its children are flex items, so min-width defaults to
   auto and the text block refuses to shrink. On Settings -> AI that block holds the
   provider and MODEL id -- "claude-haiku-4-5-20251001" already, longer when an org
   configures its own -- which pushed 72px past the card at 390px. Shared here rather
   than per-screen, because every card-head has the same shape. */
.card-head > * { min-width: 0; }
.provider-name, .card-head .mono { overflow-wrap: anywhere; }
.card-body { padding: var(--space-4) var(--space-5); }
.card-foot {
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--border-divider);
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

/* --- Inline note boxes. Status colour is always paired with words. --- */
.notebox {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--warning-bg);
  color: var(--warning);
  font-size: 0.9rem;
  line-height: 1.5;
}
.notebox-danger { background: var(--danger-bg); color: var(--danger); }
.notebox-good   { background: var(--success-bg); color: var(--success); }
.notebox-quiet  { background: var(--raised-bg); color: var(--text-secondary); }

/* --- Radio/checkbox rendered as a button (brief channel, send-when, days). --- */
.toggle-group { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.toggle-opt { position: relative; display: inline-flex; flex: 1 1 140px; }
.toggle-opt input,
.chip-opt input,
.day-opt input,
.pick-card input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.toggle-opt > span {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 var(--space-4);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  color: var(--text-secondary);
  font-weight: 600;
  text-align: center;
}
.toggle-opt input:checked + span {
  background: var(--action);
  border-color: var(--action);
  color: var(--action-text);
}
.toggle-opt input:focus-visible + span,
.chip-opt input:focus-visible + span,
.day-opt input:focus-visible + span,
.pick-card input:focus-visible + span { box-shadow: var(--focus-ring-shadow); outline: none; }
.toggle-opt input:disabled + span { opacity: 0.55; }

/* Topic chips: checked = Soil may warn you about this. */
.chip-group { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip-opt { position: relative; display: inline-flex; }
.chip-opt > span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 36px;
  padding: 0 var(--space-3);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  color: var(--text-tertiary);
  font-size: 0.9rem;
}
.chip-opt input:checked + span {
  border-color: var(--accent);
  background: var(--accent-tint);
  color: var(--tint-tickets-text);
  font-weight: 600;
}

/* Day buttons: UNCHECKED is the "on" look, because the underlying field is
   `quiet_days` (checked = skip this day). The legend in the template says so
   in words; the strike-through is the second, non-colour cue. */
.day-group { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.day-opt { position: relative; display: inline-flex; }
.day-opt > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 52px;
  min-height: 44px;
  padding: 0 var(--space-2);
  border: 1px solid var(--action);
  border-radius: var(--radius-md);
  background: var(--action);
  color: var(--action-text);
  font-weight: 600;
}
.day-opt input:checked + span {
  background: var(--card-bg);
  border-color: var(--border-input);
  color: var(--text-tertiary);
  text-decoration: line-through;
}

/* Provider choice cards (Settings -> AI model). */
.pick-card { position: relative; display: block; }
.pick-card > span {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--card-bg);
}
.pick-card input:checked + span { border-color: var(--action); box-shadow: inset 0 0 0 1px var(--action); }
.pick-text { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.pick-title { font-weight: 700; }
.pick-desc { font-size: 0.86rem; color: var(--text-secondary); }
.pick-tag { font-family: var(--font-mono); font-size: 0.68rem; color: var(--text-tertiary); flex-shrink: 0; }

/* --- A labelled settings row (teams, action rules, data controls). --- */
.srow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border-divider);
}
.srow:first-of-type { border-top: 0; }
.srow-text { min-width: 0; flex: 1 1 200px; }
.srow-text strong { display: block; }
/* "What Soil has learned about you" (docs/SOIL_GLEAN_LESSONS.md Part 2c): the effect
   on its own line, then one line per sentence. Named classes rather than inline
   styles, and block rather than a second flex column, because `.srow-text` is already
   the flex child that must stay shrinkable (min-width: 0). */
.learned-effect { display: block; margin-top: var(--space-1); }
.learned-line { display: block; margin-top: var(--space-1); }
.srow-form { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.srow-form input,
.srow-form select {
  min-height: 36px;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  font: inherit;
  color: var(--text-primary);
  max-width: 100%;
}

/* --- Brief live preview --- */
.preview-head {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-card);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.preview-body { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.preview-greeting { font-family: var(--font-heading); font-size: 1.05rem; font-weight: 700; }
.preview-item { display: flex; gap: var(--space-3); min-width: 0; }
.preview-dot {
  width: 8px;
  height: 8px;
  margin-top: var(--space-1);
  border-radius: 50%;
  background: var(--danger);
  flex-shrink: 0;
}
.preview-item-text { min-width: 0; line-height: 1.45; }

/* --- Four-up stat strip (Track record). --- */
.stat-strip { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); padding: 0; }
.stat-cell { padding: var(--space-4) var(--space-5); border-right: 1px solid var(--border-divider); min-width: 0; }
.stat-cell:last-child { border-right: 0; }
.stat-cell-label { color: var(--text-secondary); font-size: 0.9rem; }
.stat-cell-value {
  font-family: var(--font-heading);
  font-size: 1.9rem;
  font-weight: 800;
  line-height: 1.1;
  margin: var(--space-1) 0;
}
.stat-cell-value-quiet { color: var(--text-tertiary); font-size: 1.1rem; font-weight: 700; }
.stat-cell-note { font-size: 0.78rem; color: var(--text-tertiary); }
.stat-cell-note-good { color: var(--success); font-weight: 600; }
@media (max-width: 900px) {
  .stat-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat-cell { border-bottom: 1px solid var(--border-divider); }
  .stat-cell:nth-child(2n) { border-right: 0; }
}
@media (max-width: 520px) {
  .stat-strip { grid-template-columns: minmax(0, 1fr); }
  .stat-cell { border-right: 0; }
}

/* --- Grid data table (Track record predictions, Updates recipients). The
       column labels become inline cell labels below 900px instead of a
       horizontally scrolling table. --- */
.dtable-head,
.dtable-row {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  align-items: start;
  min-width: 0;
}
.dtable-head {
  font-family: var(--font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  border-bottom: 1px solid var(--border-divider);
}
.dtable-row { border-bottom: 1px solid var(--border-divider); font-size: 0.92rem; }
.dtable-row:last-child { border-bottom: 0; }
.dtable-predictions .dtable-head,
.dtable-predictions .dtable-row { grid-template-columns: minmax(0, 1.4fr) 110px 120px minmax(0, 150px) 110px; }
.dtable-cell { min-width: 0; }
.dtable-what { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.cell-label {
  display: none;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-right: var(--space-2);
}
@media (max-width: 900px) {
  .dtable-predictions .dtable-head { display: none; }
  .dtable-predictions .dtable-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .cell-label { display: inline; }
}

/* --- Result badge (Right / Wrong / Open). Always a word plus a mark. --- */
.result-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 24px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-md);
  font-size: 0.8rem;
  font-weight: 600;
  background: var(--raised-bg);
  color: var(--text-secondary);
  white-space: nowrap;
}
.result-badge-right { background: var(--success-bg); color: var(--success); }
.result-badge-wrong { background: var(--danger-bg); color: var(--danger); }

/* --- Updates: document-style draft --- */
.doc { padding: var(--space-6) var(--space-7); display: flex; flex-direction: column; gap: var(--space-5); }
.doc-intro { font-size: 1.05rem; color: var(--text-secondary); margin: 0; }
.doc-section h2 { font-family: var(--font-heading); font-size: 1.12rem; font-weight: 700; margin: 0 0 var(--space-2); }
.doc-section ul { margin: 0; padding-left: var(--space-5); display: flex; flex-direction: column; gap: var(--space-2); }
@media (max-width: 640px) {
  .doc { padding: var(--space-5) var(--space-4); }
}

.kpi-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
}
.kpi-cell { padding: var(--space-3) var(--space-4); border-right: 1px solid var(--border-divider); min-width: 0; }
.kpi-cell:last-child { border-right: 0; }
.kpi-label { font-size: 0.8rem; color: var(--text-secondary); }
.kpi-value { font-family: var(--font-heading); font-size: 1.3rem; font-weight: 800; margin: var(--space-1) 0 0; }
.kpi-note { font-size: 0.78rem; color: var(--text-tertiary); }
@media (max-width: 760px) {
  .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kpi-cell { border-bottom: 1px solid var(--border-divider); }
  .kpi-cell:nth-child(2n) { border-right: 0; }
}

/* A number Soil genuinely cannot see yet. Yellow, named, never a guess. */
.ph-mark {
  background: var(--warning-bg);
  color: var(--warning);
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
  font-weight: 600;
}

/* --- Updates: review and send --- */
.rcpt-row {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
}
.rcpt-row input[type="checkbox"] { width: 18px; height: 18px; margin: 0; accent-color: var(--action); }
.rcpt-text { min-width: 0; line-height: 1.3; }
.rcpt-name { font-weight: 600; }
.rcpt-group { font-size: 0.78rem; color: var(--text-tertiary); flex-shrink: 0; }
.mailhead {
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-card);
  background: var(--sidebar-bg);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: 0.88rem;
}
.mailbody {
  padding: var(--space-5) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  line-height: 1.6;
  overflow-wrap: anywhere;
}
@media (max-width: 640px) {
  .mailbody { padding: var(--space-4); }
}

/* The delete-workspace modal is the one place a dialog is opened client-side;
   `[hidden]` needs this rule because .modal-backdrop sets display:flex. */
.modal-backdrop[hidden] { display: none; }
/* ===========================================================================
   2026-10 redesign block: Billing, Warning detail, Agent builder.
   Appended, not interleaved: every rule below is a NEW named component class for
   the three screens built against design/soil-app-design.html (screens 12, 13,
   14). Nothing above this comment was edited. Spacing uses --space-*, radii use
   --radius-*, actions use --action; Signal blue stays on Soil's own intelligence
   (the why-chain rail, the dry-run panel) and never on a button.
   =========================================================================== */

.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;
}

/* --- segmented control (USD/INR, and any other two-or-three-way page switch) -- */
.seg {
  display: inline-flex;
  gap: var(--space-1);
  padding: var(--space-1);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
}
.seg-opt {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  font-size: 0.86rem;
  font-weight: 500;
  color: var(--text-tertiary);
  text-decoration: none;
}
.seg-opt:hover { color: var(--text-primary); }
.seg-opt-active {
  background: var(--card-bg);
  color: var(--text-primary);
  font-weight: 700;
  box-shadow: var(--shadow-card);
}
.seg-opt:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

/* --- plan cards ----------------------------------------------------------- */
.plan-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  align-items: stretch;
}
.plan-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
  min-width: 0;
}
.plan-card-feature {
  background: var(--action);
  border-color: var(--action);
  color: var(--action-text);
}
.plan-card-head {
}
.plan-name { font-size: 1.1rem; margin: 0; }
.plan-card-feature .plan-name { color: var(--action-text); }
.plan-tag {
  flex-shrink: 0;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--raised-bg);
  color: var(--text-secondary);
  font-size: 0.64rem;
  letter-spacing: 0.06em;
}
.plan-card-feature .plan-tag {
  background: rgba(255, 255, 255, 0.16);
  color: var(--action-text);
}
.plan-amount {
  font-family: var(--font-heading);
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.1;
}
.plan-cadence { color: var(--text-tertiary); font-size: 0.9rem; }
.plan-card-feature .plan-cadence { color: var(--action-text-muted); }

/* The price on the dark "recommended" card rendered graphite-on-ink -- barely
   legible, and the single most important number on the page. .plan-amount sets no
   colour of its own, so it inherited from its <p class="plan-price"> parent, and the
   global `p { color: var(--text-secondary) }` beat the white inherited from
   .plan-card-feature. Set it explicitly on both the paragraph and the amount. */
.plan-card-feature .plan-price,
.plan-card-feature .plan-amount {
  color: var(--action-text);
}
.plan-who { margin: 0; color: var(--text-tertiary); font-size: 0.9rem; }
.plan-card-feature .plan-who { color: var(--action-text-muted); }
.plan-feats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  flex: 1 1 auto;
  font-size: 0.9rem;
  color: var(--text-secondary);
}
.plan-feats li { position: relative; padding-left: var(--space-4); line-height: 1.45; }
.plan-feats li::before {
  content: "\2713";
  position: absolute;
  left: 0;
  /* Strata reserves Signal blue for Soil's own intelligence; a feature tick is
     ordinary list furniture, so it stays in the tertiary ink. */
  color: var(--text-tertiary);
}
.plan-card-feature .plan-feats { color: var(--action-text); }
.plan-card-feature .plan-feats li::before { color: var(--action-text); }
.plan-card-foot { margin-top: var(--space-2); }
.plan-card-feature .note { color: var(--action-text-muted); }
/* --- invoices + usage row ------------------------------------------------- */
.billing-lower {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: var(--space-5);
  align-items: start;
}
.billing-invoices,
.billing-usage { padding: 0; }
.invoice-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.invoice-table th {
  text-align: left;
  padding: var(--space-2) var(--space-5);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  border-bottom: 1px solid var(--border-divider);
}
.invoice-table td {
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
  color: var(--text-secondary);
}
.invoice-table tr:last-child td { border-bottom: 0; }
.invoice-link { text-align: right; }
.usage-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5) var(--space-5);
}
.usage-meter { display: flex; flex-direction: column; gap: var(--space-2); }
.usage-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  color: var(--text-secondary);
}
.meter-track {
  display: block;
  height: 6px;
  border-radius: var(--radius-sm);
  background: var(--raised-bg);
  overflow: hidden;
}
.meter-track-warn { background: var(--warning-bg); }
.meter-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-sm);
  background: var(--action);
}
.meter-fill-warn { background: var(--warning); }
.usage-warn { font-size: 0.86rem; color: var(--warning); line-height: 1.5; }

@media (max-width: 1100px) {
  .plan-grid { grid-template-columns: 1fr; }
  .billing-lower { grid-template-columns: minmax(0, 1fr); }
}

/* The two billing cards below the plan row lay out their own heading, so they
   cancel .card's default padding (same pattern home.html's .home-side-card uses)
   and draw a hairline under the heading like the reference screen. */
.billing-invoices .home-card-head,
.billing-usage .home-card-head {
  align-items: center;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-divider);
}
.billing-invoices .home-card-head h2,
.billing-usage .home-card-head h2 { font-size: 1.05rem; }

/* The design-partner card's "price" is a sentence ("3 months free"), not a figure --
   it gets a smaller size so it sits on one line beside its cadence like the two real
   prices do. */
.plan-amount-text { font-size: 1.4rem; }

/* --- Warning detail (/org/{slug}/warnings/{id}) ---------------------------- */

/* Soil's own computed step in a why-chain. This is the one place Signal blue is
   correct on this screen: it marks Soil's own intelligence, exactly like a citation,
   and never appears on a button. */
.source-chip-soil { background: var(--accent-tint); color: var(--accent-text); }

.back-link {
  display: inline-block;
  margin-bottom: var(--space-3);
  font-size: 0.86rem;
  color: var(--text-secondary);
  text-decoration: none;
}
.back-link:hover { color: var(--text-primary); }

.warning-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: var(--space-5);
  align-items: start;
}
.warning-main { display: flex; flex-direction: column; gap: var(--space-5); min-width: 0; }
.warning-rail { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }

.stat-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}
.stat-cell {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  border-right: 1px solid var(--border-divider);
  min-width: 0;
}
.stat-cell:last-child { border-right: 0; }

/* min-width: 0 on the CELL is only half the fix, which is the trap CLAUDE.md names
   explicitly. These spans are flex items, and a flex item defaults to min-width: auto
   -- so "nousonworktechnologies/KredMe usually 0.1 days" refused to shrink below its
   intrinsic width, escaped its tile and collided with the next cell's caption. A
   warning's note renders a repo path from a connected tool, which is one long
   unbreakable token by nature. */
.stat-cell > * {
  min-width: 0;
  overflow-wrap: anywhere;
}
.stat-cell-label { color: var(--text-secondary); font-size: 0.86rem; }
.stat-cell-value {
  font-family: var(--font-heading);
  font-size: 1.5rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.stat-cell-note { font-size: 0.78rem; color: var(--text-tertiary); line-height: 1.45; }
.stat-cell-note-bad { color: var(--danger); font-weight: 600; }
.stat-cell-note-warn { color: var(--warning); font-weight: 600; }

.why-card, .tried-card, .fix-card, .sources-card { padding: 0; }
.why-card .home-card-head,
.tried-card .home-card-head,
.fix-card .home-card-head,
.sources-card .home-card-head {
  align-items: center;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-divider);
}
.why-card .home-card-head h2,
.tried-card .home-card-head h2,
.fix-card .home-card-head h2,
.sources-card .home-card-head h2 { font-size: 1.05rem; }

/* Scoped to .why-card: these are the WARNING PAGE's chain, where the column is
   full width. Unscoped they also hit Today's chain, whose column is 300px at a
   1280 viewport -- there the 4-column grid collapsed .why-text to 16px, one
   character per line. */
.why-card .why-chain { list-style: none; margin: 0; padding: var(--space-4) var(--space-5) var(--space-5); }
.why-card .why-step {
  display: grid;
  grid-template-columns: 62px 16px minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: start;
}
.why-card .why-when { font-size: 0.74rem; color: var(--text-tertiary); padding-top: 2px; }
.why-card .why-rail { display: flex; flex-direction: column; align-items: center; align-self: stretch; }
.why-card .why-dot { width: 9px; height: 9px; margin-top: 5px; border-radius: 50%; background: var(--text-tertiary); }
/* design/soil-design-v3.pdf p5 escalates the chain grey -> amber -> red down to the
   step that actually broke, and warning_routes._build_chain already computes exactly
   that severity per step. It never showed: `.why-card .why-dot` is (0,2,0) and these
   two were (0,1,0), so the base grey won every time and every chain rendered flat.
   Scoped to the card so they outrank it. The colour is never alone -- each step
   carries its date, its sentence and its source chip. */
.why-card .why-dot-warn { background: var(--warning); }
.why-card .why-dot-bad { background: var(--danger); }
.why-line { flex: 1; width: 2px; background: var(--border-card); }
.why-step:last-child .why-line { background: transparent; }
.why-text { padding-bottom: var(--space-4); line-height: 1.5; color: var(--text-secondary); min-width: 0; }
.why-text a { color: var(--text-secondary); }

.tried-list { list-style: none; margin: 0; padding: var(--space-3) var(--space-5) var(--space-4); }
.tried-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border-divider);
}
.tried-row:last-child { border-bottom: 0; }
.tried-what { font-weight: 600; }
.tried-msg { font-size: 0.88rem; color: var(--text-tertiary); line-height: 1.5; }

.fix-body { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4) var(--space-5) var(--space-5); }
.fix-body form { display: contents; }
.fix-option {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  width: 100%;
  text-align: left;
  padding: var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.fix-option:hover { border-color: var(--border-input); }
.fix-option:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
.fix-option-active { border-color: var(--action); }
.fix-option-off { background: var(--sidebar-bg); cursor: default; }
.fix-option-off .fix-option-title { color: var(--text-tertiary); }
.fix-option-title { font-weight: 700; }
.fix-option-detail { font-size: 0.86rem; color: var(--text-tertiary); line-height: 1.45; }

.fix-preview {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
  font-size: 0.9rem;
  color: var(--text-secondary);
}
.fix-preview-label { font-size: 0.66rem; letter-spacing: 0.06em; color: var(--text-tertiary); }
.fix-done {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--success-bg);
  color: var(--success);
  font-size: 0.9rem;
}
.fix-done a { color: var(--success); }

.source-list { list-style: none; margin: 0; padding: var(--space-2) var(--space-5) var(--space-4); }
.source-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-divider);
  min-width: 0;
}
.source-row:last-child { border-bottom: 0; }
.source-row > a, .source-row > span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 1100px) {
  .warning-layout { grid-template-columns: minmax(0, 1fr); }
  .stat-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat-cell:nth-child(2) { border-right: 0; }
  .stat-cell:nth-child(1), .stat-cell:nth-child(2) { border-bottom: 1px solid var(--border-divider); }
}
@media (max-width: 560px) {
  .stat-strip { grid-template-columns: minmax(0, 1fr); }
  .stat-cell { border-right: 0; border-bottom: 1px solid var(--border-divider); }
  .stat-cell:last-child { border-bottom: 0; }
  .why-step { grid-template-columns: 54px 16px minmax(0, 1fr); }
  .why-step .source-chip { grid-column: 3; justify-self: start; margin-bottom: var(--space-3); }
}

/* --- Agent builder (/org/{slug}/custom-agents) ----------------------------- */

.builder-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 400px;
  gap: var(--space-5);
  align-items: start;
  margin-bottom: var(--space-6);
}
.builder-main { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.builder-rail { min-width: 0; }

.builder-form { display: flex; flex-direction: column; gap: var(--space-3); }
.builder-form .btn { align-self: flex-start; }
.builder-textarea {
  width: 100%;
  padding: var(--space-3);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  font: inherit;
  font-size: 1rem;
  line-height: 1.5;
  resize: vertical;
}
.builder-textarea:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; border-color: var(--accent); }

.idea-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-divider);
}
.idea-label { font-size: 0.64rem; letter-spacing: 0.06em; color: var(--text-tertiary); }
.idea-chip {
  max-width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  font: inherit;
  font-size: 0.84rem;
  color: var(--text-secondary);
  text-align: left;
  cursor: pointer;
}
.idea-chip:hover { border-color: var(--border-input); color: var(--text-primary); }
.idea-chip:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

.steps-card, .testrun-card { padding: 0; }
.steps-card .home-card-head,
.testrun-card .home-card-head {
  align-items: center;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-divider);
}
.steps-card .home-card-head h2, .testrun-card .home-card-head h2 { font-size: 1.05rem; }
.step-warn { color: var(--warning); }

.step-list { list-style: none; margin: 0; padding: 0; }
.step-row {
  display: grid;
  grid-template-columns: 76px minmax(0, 1fr) auto;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
}
.step-row:last-child { border-bottom: 0; }
.step-kind { font-size: 0.64rem; letter-spacing: 0.05em; color: var(--text-tertiary); }
.step-body { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.step-title { font-weight: 600; }
.step-detail { font-size: 0.84rem; color: var(--text-tertiary); line-height: 1.45; }
.step-side { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }
.step-gov { display: flex; align-items: center; gap: var(--space-2); }

.step-edit { border-top: 1px solid var(--border-divider); padding: var(--space-4) var(--space-5); }
.step-edit > summary { cursor: pointer; font-weight: 600; }
.step-edit > summary::-webkit-details-marker { display: none; }
.step-edit > summary::before { content: "+ "; color: var(--text-tertiary); }
.step-edit[open] > summary::before { content: "- "; }
.step-edit > summary:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; border-radius: var(--radius-sm); }
.step-edit-form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-end;
  margin-top: var(--space-4);
}
.step-edit-form .field { min-width: 0; }
.step-edit-foot { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; flex-basis: 100%; }

.builder-actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

.testrun-body { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4) var(--space-5) var(--space-5); }
.testrun-hit {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  min-width: 0;
}
.testrun-hit-title { font-weight: 600; overflow-wrap: anywhere; }
.testrun-hit-detail { font-size: 0.84rem; color: var(--text-tertiary); line-height: 1.45; }
.testrun-msg {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
  font-size: 0.9rem;
  color: var(--text-secondary);
}

.builder-prebuilt { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); margin-bottom: var(--space-6); }
.prebuilt-card { display: flex; flex-direction: column; gap: var(--space-2); }

.rules-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.rules-table th {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  border-bottom: 1px solid var(--border-divider);
}
.rules-table td {
  padding: var(--space-3);
  border-bottom: 1px solid var(--border-divider);
  color: var(--text-secondary);
}
.rules-table tr:last-child td { border-bottom: 0; }

@media (max-width: 1100px) {
  .builder-layout { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 560px) {
  .step-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
  .step-side { justify-content: flex-start; }
  .idea-chip { width: 100%; }
}

/* A wide data table on a phone scrolls inside its card rather than pushing the whole
   page sideways (the design pass's "zero horizontal overflow at 390px" rule). */
.table-scroll { overflow-x: auto; }

/* ===================================================================
   2026-10 redesign — Teams, Connections and Work graph
   (design/soil-app-design.html screens 3, 7 and 8).

   Appended as one block; nothing above this line was edited. Every value
   resolves to an existing token: the spacing scale (--space-1..7), the
   three radii, the Strata colours and the status/tint pairs. Signal blue
   appears only where the design system allows it — Soil's own written
   paragraph and the citations inside it — never on a button.
   =================================================================== */

/* ---- Shared surfaces used by all three screens --------------------- */

/* A card whose rows run to its true edge and manage their own padding
   (the .card default padding would double-inset every divider). */
.panel {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-card);
}
.panel-head h2,
.panel-title {
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 700;
  margin: 0 0 var(--space-3);
}
.panel-head h2 { margin: 0; }

/* ---- Teams --------------------------------------------------------- */

.team-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--space-5);
  align-items: start;
}
.team-main,
.team-rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}

/* Soil's own written paragraph: the one place on this screen where Signal
   blue is correct, because it marks Soil's intelligence, not an action. */
.summary-card {
  background: var(--accent-tint);
  border: 1px solid var(--accent-tint-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
}
.summary-card-quiet {
  background: var(--sidebar-bg);
  border-color: var(--border-card);
}
.summary-label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin: 0 0 var(--space-2);
}
.summary-card-quiet .summary-label { color: var(--text-tertiary); }
.summary-body {
  margin: 0;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--text-primary);
}
.summary-body p { margin: 0; display: inline; }

.statbar {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.statbar-cell {
  padding: var(--space-3) var(--space-4);
  border-left: 1px solid var(--border-divider);
  min-width: 0;
}
.statbar-cell:first-child { border-left: 0; }
.statbar-label { margin: 0; color: var(--text-secondary); font-size: 0.86rem; }
.statbar-value {
  margin: var(--space-1) 0 0;
  font-family: var(--font-heading);
  font-size: 1.6rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.statbar-note { margin: var(--space-1) 0 0; font-size: 0.78rem; font-weight: 600; color: var(--text-tertiary); }
/* Named for the VERDICT, not the direction: a trend that is better is green, one that
   is worse is amber, and in both cases the note itself already says "better" or
   "worse" in words -- colour is never the only signal (CLAUDE.md design system). */
.statbar-note-better { color: var(--success); }
.statbar-note-worse { color: var(--warning); }

.worklist-row {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr) 160px;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
}
.worklist-row-empty { display: block; }
.worklist-person { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.worklist-name { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; overflow-wrap: anywhere; }
.worklist-name .note { font-size: 0.78rem; }
.worklist-work { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.worklist-work-title { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; font-weight: 600; }
.worklist-status { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); }
.worklist-foot {
  margin: 0;
  padding: var(--space-3) var(--space-5);
  font-size: 0.82rem;
  color: var(--text-tertiary);
}

/* The people tint, not the accent: an avatar is a person, not an action. */
.avatar-person {
  width: 32px;
  height: 32px;
  font-size: 0.74rem;
}

.attention-card-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.attention-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
}
.attention-item-title { font-weight: 700; color: var(--text-primary); }
.attention-normal { font-size: 0.78rem; font-family: var(--font-mono); color: var(--text-tertiary); }

.feed { list-style: none; margin: 0; padding: 0; }
.feed-row {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  gap: var(--space-2);
  padding: var(--space-2) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
  font-size: 0.9rem;
}
.feed-time { font-size: 0.72rem; color: var(--text-tertiary); }
.feed-empty { margin: 0; padding: var(--space-4) var(--space-5); }
.feed-foot { padding: var(--space-3) var(--space-5) var(--space-4); }
.feed-foot .note { margin: var(--space-2) 0 0; }
/* A Slack channel derived from a repo path ("#nousonworktechnologies-kredme") is a
   real, long name -- the button wraps rather than clipping it. */
.feed-foot .btn { white-space: normal; height: auto; min-height: 38px; padding: var(--space-2) var(--space-3); }
.feed-foot .btn .btn-label { overflow-wrap: anywhere; }

.team-links { display: flex; flex-direction: column; gap: var(--space-2); font-size: 0.9rem; font-weight: 600; }
.team-admin summary { cursor: pointer; font-weight: 600; }
.team-admin-form { display: flex; align-items: flex-end; gap: var(--space-3); flex-wrap: wrap; margin-top: var(--space-3); }
.team-admin-form .field { margin: 0; min-width: 220px; }

/* ---- Connections --------------------------------------------------- */

.conn-table { width: 100%; }
.conn-head,
/* align-items: start, not center. Rows are not all one height -- Google Drive's
   carries two explanatory sentences -- and centring made every other column float to
   the middle of whichever row happened to be tallest, so the tool names no longer
   lined up with each other down the column. Top alignment is what makes a list of
   rows read as a table. */
.conn-row {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) 170px minmax(0, 0.9fr) 120px 160px;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-4) var(--space-5);
}
/* One tool = its row plus its own Manage disclosure, grouped so the disclosure
   visually belongs to the row ABOVE it and not the next tool down. */
.conn-item { border-bottom: 1px solid var(--border-card); }
.conn-item:last-child { border-bottom: 0; }
.conn-item .conn-row { border-bottom: 0; }
.conn-head {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  border-bottom: 1px solid var(--border-card);
}
.conn-row { border-bottom: 1px solid var(--border-divider); }
.conn-row:last-of-type { border-bottom: 0; }
.conn-tool { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.conn-name { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.conn-name strong { font-weight: 700; }
.conn-health { display: flex; flex-direction: column; gap: var(--space-1); }
.conn-sees,
.conn-access { color: var(--text-secondary); font-size: 0.9rem; min-width: 0; }
.conn-action { justify-self: end; display: flex; gap: var(--space-2); min-width: 0; }
.conn-action .btn { white-space: normal; text-align: center; }
.conn-manage summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.86rem;
  color: var(--text-tertiary);
  padding: 0 var(--space-5) var(--space-3) calc(var(--space-5) + var(--space-6));
}
.conn-manage[open] summary { color: var(--text-primary); }
.conn-manage-body {
  padding: 0 var(--space-5) var(--space-4) calc(var(--space-5) + var(--space-6));
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.conn-manage-body .note { margin: 0; }
.conn-manage-body > div > p { margin: 0 0 var(--space-1); }
.conn-manage-title { font-weight: 700; }
.conn-manage-actions { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; }
/* The grant note states what the OAuth scope itself allows, which is wider than the
   picker. Quiet surface, not an alarm: it is a fact about the provider, not a fault. */
.conn-grant {
  margin: 0;
  padding: var(--space-3);
  border-left: 2px solid var(--border-input);
  background: var(--sidebar-bg);
  border-radius: var(--radius-md);
  font-size: 0.88rem;
  color: var(--text-secondary);
  max-width: 68ch;
}
.conn-stale {
  margin: 0;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--warning);
}

/* A provider's own error, contained.
   The live screen rendered Google's whole 403 JSON document as one unbroken string:
   it blew out its grid cell and was cut mid-word, with the console URL that fixes it
   always inside the cut part. Three rules keep that impossible regardless of how long
   the stored message is -- the summary is clamped to two lines (already cut at a word
   boundary by templates.sync_error_display), the URL is a real link outside the
   clamp, and the untouched original sits behind a disclosure. */
.conn-error {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  font-size: 0.88rem;
  color: var(--text-secondary);
}
.conn-error-summary {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* An identifier from a connected tool -- a project id, a path -- has no spaces to
     break at, so it must be allowed to break anywhere (CLAUDE.md's layout traps). */
  overflow-wrap: anywhere;
  min-width: 0;
  line-height: 1.45;
}
.conn-error-link { font-weight: 600; overflow-wrap: anywhere; min-width: 0; }
.conn-error-more > summary {
  cursor: pointer;
  color: var(--text-tertiary);
  font-size: 0.82rem;
}
.conn-error-full {
  margin: var(--space-2) 0 0;
  padding: var(--space-3);
  background: var(--sidebar-bg);
  border: 1px solid var(--border-divider);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  line-height: 1.5;
  color: var(--text-secondary);
  /* The provider's raw body is one long line; it must wrap rather than scroll the
     page sideways. max-height keeps a 40-line stack trace from owning the screen. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 14rem;
  overflow-y: auto;
}

/* A tool mark showing the vendor's real logo instead of two initials.
   The box, border and radius stay exactly as .tool-mark sets them, so a logo and a
   fallback pair of initials line up in the same column -- an org with a provider Soil
   ships no asset for must not get a ragged list. The white plate is deliberate: these
   are multi-colour brand marks, and several (Slack, Google Drive) lose contrast
   against the raised grey .tool-mark uses for initials. */
.tool-mark-logo {
  background: #ffffff;
  padding: var(--space-1);
  /* Stated here rather than inherited: onboarding's .onb-tool-mark carries no border
     of its own (its Strata tint supplied the background), so a white plate would
     otherwise float on a white card with no edge. */
  border: 1px solid var(--border-card);
}
.tool-mark-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* The one thing to check when a real selection has produced no rows. Not a warning --
   nothing is broken -- so it reads as the quiet instruction it is. */
.conn-hint {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--text-tertiary);
  overflow-wrap: anywhere;
  min-width: 0;
}

/* Selection accounting: why the picked sources with no data have none. Only the
   .is-bad variant is a problem -- the rest is Soil correctly finding nothing, so it
   stays in ordinary meta text rather than borrowing a warning colour it has not
   earned. Colour never carries the meaning on its own here: every line says what it
   is in words (store._accounting_sentences). */
.conn-account {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--text-tertiary);
  overflow-wrap: anywhere;
  min-width: 0;
}

.conn-account.is-bad { color: var(--danger); }

.conn-failed-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.conn-failed-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
}

/* A repository name is an identifier from a connected tool, so it wraps anywhere
   rather than forcing the row wide -- CLAUDE.md's rule for exactly this case. */
.conn-failed-name {
  font-size: 0.78rem;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
  min-width: 0;
}

.conn-failed-why {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  overflow-wrap: anywhere;
  min-width: 0;
  flex: 1;
}

.linkmap-note { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

.mcp-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-5); }
.mcp-card { display: flex; flex-direction: column; gap: var(--space-3); }
.mcp-card h2 { font-family: var(--font-heading); font-size: 1rem; font-weight: 700; margin: 0; }
.mcp-card p { margin: 0; color: var(--text-secondary); }
.mcp-card-dark {
  /* --surface-ink, NOT --text-primary -- see .graph-read. Measured 1.1:1 in dark
     mode before this; .mcp-setup-* and .mcp-url below all sit on this surface. */
  background: var(--surface-ink);
  border-color: var(--surface-ink);
  color: #ffffff;
}
.mcp-card-dark p { color: #c9cdd6; }
.mcp-url {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: #151920;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: #9db2ff;
  overflow-wrap: anywhere;
}
.mcp-url span { flex: 1; min-width: 0; }
/* The plate is literal white because the card is dark in both themes, so the label
   must be literal ink too: var(--text-primary) inverts to near-white and left this
   button reading white-on-white (1.1:1) in dark mode. */
.mcp-card-dark .btn { background: #ffffff; border-color: #ffffff; color: var(--surface-ink); }
.mcp-card-dark .btn:hover { background: #e4e6eb; border-color: #e4e6eb; }
.mcp-chips { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.mcp-chip {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  font-size: 0.88rem;
  color: var(--text-secondary);
}

/* ---- Work graph ---------------------------------------------------- */

/* The team/focus switcher sits in page_header's actions slot, which is a max-content
   flex item -- a long list of tabs would push the whole page sideways at 390px even
   though .op-tabs already wraps, because its parent never shrinks. This ADDS a
   constraint for the two headers that contain a switcher; it changes nothing for any
   other page's header actions. (:has() is already used elsewhere in this file.) */
.page-header-actions:has(> .op-tabs) { min-width: 0; flex: 0 1 auto; }
.op-tabs.graph-focus-tabs,
.op-tabs.team-switch-tabs { max-width: 100%; min-width: 0; }
@media (min-width: 901px) {
  .op-tabs.graph-focus-tabs,
  .op-tabs.team-switch-tabs { justify-content: flex-end; }
}

/* A focus tab is labelled with an entity's real title, so it can be a whole sentence
   ("A2 follow-up: stop ruling on a wallet the app never managed to read"). It was
   being cut mid-word -- "...on a walle" -- with no ellipsis and no gap, so two tabs
   ran together and neither was readable. Each tab is now bounded and truncates at a
   character with an ellipsis; the full text is already on `title` for hover.
   Separators do the work the old 0-width gap could not. */
.graph-focus-tab {
  max-width: 24ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding-left: var(--space-3);
  padding-right: var(--space-3);
  min-height: 44px;              /* touch target */
  align-items: center;
}
.graph-focus-tab + .graph-focus-tab {
  border-left: 1px solid var(--border-divider);
}
/* An inactive tab gave no sign it was clickable. */
.graph-focus-tab:not(.active):hover {
  color: var(--text-primary);
  background: var(--sidebar-bg);
}
@media (max-width: 1180px) {
  .graph-focus-tab { max-width: 16ch; }
}

.graph-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--space-5);
  align-items: start;
}
.graph-rail { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.graph-board { display: flex; flex-direction: column; gap: var(--space-4); }
.graph-colhead,
.graph-columns {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
}
.graph-colhead span {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.graph-columns { align-items: start; }
.graph-col { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.graph-node {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: flex-start;
  text-align: left;
  width: 100%;
  padding: var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.graph-node { text-decoration: none; color: var(--text-primary); }
.graph-node:hover { background: var(--sidebar-bg); }
.graph-node:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
.graph-node.is-focus { border-color: var(--text-primary); border-width: 2px; }
.graph-node-title { font-weight: 700; line-height: 1.3; }

/* A GitHub org name ("nousonworktechnologies") and a 67-character PR title are one
   unbroken token as far as wrapping is concerned. .graph-node is a column flex with
   align-items: flex-start, which makes every child fit-content on the cross axis --
   the exact trap CLAUDE.md's design notes warn about -- so such a token sized to its
   own width and overflowed the card rather than wrapping inside it. stretch puts the
   children back on the card's width; overflow-wrap lets a long token break. Both are
   needed: stretch alone still overflows, because the token itself cannot wrap. */
.graph-node { align-items: stretch; }
.graph-node-title,
.graph-node-note { overflow-wrap: anywhere; min-width: 0; }
.graph-node-note { font-size: 0.82rem; color: var(--text-secondary); }
.graph-col-empty { font-size: 0.82rem; color: var(--text-tertiary); padding: var(--space-2) 0; }
.graph-foot {
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
  font-size: 0.86rem;
  color: var(--text-secondary);
  margin: 0;
}
.graph-why { margin: 0; padding-left: var(--space-4); display: flex; flex-direction: column; gap: var(--space-1); font-size: 0.86rem; }
.graph-read {
  /* --surface-ink, NOT --text-primary: --text-primary inverts to near-white in
     dark mode, which turned this panel into a white slab carrying white text
     (measured 1.1:1). This surface is dark in both themes by design. */
  background: var(--surface-ink);
  border-color: var(--surface-ink);
  color: #ffffff;
}
.graph-read .summary-label { color: #9db2ff; }
.graph-read p { margin: 0; line-height: 1.5; color: #ffffff; }
.graph-read a { color: #9db2ff; }

/* ---- Phone (390px) ------------------------------------------------- */

@media (max-width: 900px) {
  .team-layout,
  .graph-layout { grid-template-columns: minmax(0, 1fr); }
  .statbar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .statbar-cell:nth-child(odd) { border-left: 0; }
  .statbar-cell:nth-child(n + 3) { border-top: 1px solid var(--border-divider); }
  .worklist-row { grid-template-columns: minmax(0, 1fr); }
  .mcp-cards { grid-template-columns: minmax(0, 1fr); }
  .graph-colhead { display: none; }
  .graph-columns { grid-template-columns: minmax(0, 1fr); }
  .graph-col::before {
    content: attr(data-label);
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
  }
  .graph-col:empty { display: none; }
  .conn-head { display: none; }
  .conn-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2);
  }
  .conn-action { justify-self: start; }
  .feed-row { grid-template-columns: minmax(0, 1fr); }
  .feed-foot .btn { min-height: 44px; }
}

/* ===========================================================================
   APPENDED BLOCK — logged-out (auth) screens + guided onboarding
   Added for design/soil-app-design.html's "Sign up / log in" and "Onboarding"
   screens. Nothing above this line was changed.

   Rules this block follows, same as the rest of the file:
     * every padding/margin/gap is a --space-* token, every radius a --radius-*;
     * --action (ink) is the only primary-action colour. Signal blue appears only
       as the step-progress / "Soil's own intelligence" accent, never on a button;
     * no new utility classes — every selector below is a named component;
     * 390px layouts avoid `margin:auto` on column-flex children (the bug that made
       Today 742px wide at phone size) by using grid rows + align-content instead.
   =========================================================================== */

/* ------------------------------------------------------- the two-panel shell -- */
.auth-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 100vh;
  background: var(--sidebar-bg);
}

.auth-split-main {
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: var(--space-6);
  background: var(--card-bg);
  padding: var(--space-6) var(--space-7);
  min-width: 0;
}

.auth-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  align-self: start;
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--text-primary);
  text-decoration: none;
}
.auth-brand:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; border-radius: var(--radius-sm); }

/* The centred column. `align-content` rather than an auto margin: an auto margin on
   a column-flex child suppresses stretch and makes it size to its widest descendant
   (see CLAUDE.md's design-pass notes). */
.auth-body {
  align-self: center;
  justify-self: start;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  width: 100%;
  max-width: 420px;
  min-width: 0;
}

.auth-heading h1 {
  font-family: var(--font-heading);
  font-size: 2.05rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.1;
  margin: 0 0 var(--space-2);
}
.auth-heading p {
  margin: 0;
  color: var(--text-secondary);
  font-size: 1rem;
}

.auth-legal {
  align-self: end;
  margin: 0;
  font-size: 0.78rem;
  color: var(--text-tertiary);
}

/* ------------------------------------------------- provider (SSO) buttons -- */
/* Rendered disabled on purpose: no Google/Microsoft OAuth app exists for this
   deployment. .btn's own :disabled styling already dims them; this only handles the
   provider mark and the full-width layout. */
.auth-sso {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.sso-btn {
  width: 100%;
  justify-content: center;
  gap: var(--space-3);
  min-height: 46px;
}
.auth-sso-reason {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

.sso-mark {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  display: block;
}
/* Google's real mark, not an approximation of it. This was a conic-gradient pie --
   four coloured quarters in a circle -- which reads as "a Google-ish logo" at a glance
   and is not Google's logo at all. A vendor's mark is the one thing on a screen that
   must be exactly theirs; Microsoft's four squares below were already correct. The
   file is self-hosted like every other brand asset, because the CSP forbids a CDN. */
.sso-mark-google { display: block; }
.sso-mark-google img { width: 100%; height: 100%; display: block; }
.sso-mark-microsoft {
  display: grid;
  grid-template-columns: 8px 8px;
  grid-template-rows: 8px 8px;
  gap: 2px;
  width: auto;
  height: auto;
}
.sso-mark-microsoft span:nth-child(1) { background: #f25022; }
.sso-mark-microsoft span:nth-child(2) { background: #7fba00; }
.sso-mark-microsoft span:nth-child(3) { background: #00a4ef; }
.sso-mark-microsoft span:nth-child(4) { background: #ffb900; }

/* ------------------------------------------------------------- the email form -- */
.auth-divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-tertiary);
}
.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-card);
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.auth-form .field { margin-bottom: var(--space-3); }
.auth-form-note {
  margin: var(--space-3) 0 0;
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

.auth-notice-host { display: block; }
.auth-notice-slot:empty { display: none; }

.auth-notice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  font-size: 0.86rem;
  line-height: 1.45;
}
.auth-notice-text { min-width: 0; }
.auth-notice-join {
  background: var(--success-bg);
  color: var(--success);
}
.auth-notice-new {
  background: var(--sidebar-bg);
  color: var(--text-secondary);
}
.auth-notice-mark {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  background: var(--action);
  color: var(--action-text);
  font-family: var(--font-heading);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.auth-foot {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-divider);
  font-size: 0.88rem;
  color: var(--text-secondary);
}

/* ------------------------------------------------------------- the dark panel -- */
.auth-split-aside {
  display: grid;
  align-content: center;
  gap: var(--space-5);
  /* --surface-ink, NOT --text-primary. This is THE dark half of the login and
     signup split; painted with --text-primary it inverted to a near-white panel
     carrying white text in dark mode (measured 1.1:1) -- the most visible
     instance of this defect in the product. Dark in both themes, by design. */
  background: var(--surface-ink);
  color: #ffffff;
  padding: var(--space-7);
  min-width: 0;
}
.auth-aside-kicker {
  margin: 0;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #9db2ff;
}
.auth-aside-title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 2.3rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.1;
}
.auth-aside-card {
  background: #151920;
  border: 1px solid #262b35;
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}
.auth-aside-label {
  margin: 0;
  font-size: 0.65rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #9db2ff;
}
/* Explicit white: this file's base `p` rule paints paragraphs --text-secondary
   (graphite), which on the dark panel would be a real contrast failure, not a style
   preference. */
.auth-aside-headline {
  margin: 0;
  color: #ffffff;
  font-weight: 700;
  font-size: 1.05rem;
  line-height: 1.35;
}
.auth-aside-meta {
  margin: 0;
  font-size: 0.72rem;
  color: #c9cdd6;
  overflow-wrap: anywhere;
}
.auth-aside-chain {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: 0.86rem;
  color: #c9cdd6;
}
.auth-aside-chain li {
  padding-left: var(--space-4);
  position: relative;
}
.auth-aside-chain li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #5e6472;
}
.auth-aside-link {
  align-self: start;
  margin-top: var(--space-1);
  color: #ffffff;
  font-size: 0.86rem;
  font-weight: 600;
}
.auth-aside-foot {
  margin: 0;
  color: #9aa0ad;
  font-size: 0.86rem;
}

@media (max-width: 900px) {
  .auth-split { grid-template-columns: 1fr; min-height: 0; }
  .auth-split-main { padding: var(--space-5) var(--space-5) var(--space-6); gap: var(--space-5); }
  .auth-body { max-width: none; }
  .auth-heading h1 { font-size: 1.7rem; }
  .auth-split-aside { padding: var(--space-6) var(--space-5); }
  .auth-aside-title { font-size: 1.7rem; }
  .sso-btn, .auth-form .btn { min-height: 44px; }
}

/* ===========================================================================
   Guided onboarding (Connect -> Choose -> Learn)
   =========================================================================== */
.onb-shell {
  min-height: 100vh;
  background: var(--sidebar-bg);
  display: flex;
  flex-direction: column;
}

.onb-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  background: var(--card-bg);
  border-bottom: 1px solid var(--border-card);
  padding: var(--space-3) var(--space-6);
}

/* The three steps. A step that is already done or is the current one is a real link
   you can go back to; a step you have not reached yet is a plain <span>, never a
   link that would drop you somewhere unbuilt. */
.onb-steps {
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  flex-wrap: wrap;
}
.onb-step {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 34px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text-secondary);
  text-decoration: none;
}
.onb-step-current { background: var(--action); color: var(--action-text); }
.onb-step-dot {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.72rem;
  background: var(--border-card);
  color: var(--text-secondary);
  flex-shrink: 0;
}
.onb-step-current .onb-step-dot { background: var(--action-text); color: var(--action); }
.onb-step-done .onb-step-dot { background: var(--success-bg); color: var(--success); }
.onb-step:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

.onb-escape { font-size: 0.86rem; color: var(--text-secondary); }

.onb-main {
  flex: 1;
  display: flex;
  justify-content: center;
  padding: var(--space-6) var(--space-5) var(--space-7);
  min-width: 0;
}
.onb-col {
  width: 100%;
  max-width: 980px;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}
.onb-heading h1 {
  margin: 0 0 var(--space-2);
  font-family: var(--font-heading);
  font-size: 1.9rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.15;
}
.onb-heading p { margin: 0; color: var(--text-secondary); font-size: 1rem; }
.onb-kicker {
  margin: 0 0 var(--space-2);
  font-size: 0.65rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

/* ------------------------------------------------------- step 1: tool cards -- */
.onb-tools {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
}
.onb-tool {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  min-width: 0;
}
.onb-tool-connected { border-color: var(--success); }
.onb-tool-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.onb-tool-mark {
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  flex-shrink: 0;
}
.onb-tool-tag {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.onb-tool-name { font-weight: 700; font-size: 1.02rem; }
.onb-tool-why { margin: 0; color: var(--text-secondary); font-size: 0.88rem; flex: 1; }
.onb-tool .btn { width: 100%; justify-content: center; }
.onb-tool-note { margin: 0; font-size: 0.78rem; color: var(--text-tertiary); }

.onb-footrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.onb-footrow p { margin: 0; color: var(--text-secondary); font-size: 0.9rem; }

/* --------------------------------------------- step 2: repo/project table -- */
.onb-table { padding: 0; overflow: hidden; }
.onb-row {
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr) 160px 200px;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
  min-width: 0;
}
.onb-row:last-child { border-bottom: 0; }
.onb-row-head {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  border-bottom: 1px solid var(--border-card);
}
.onb-row-archived { background: var(--sidebar-bg); }
.onb-row-name {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  flex-wrap: wrap;
}
.onb-row-name strong { font-weight: 600; overflow-wrap: anywhere; }
.onb-row-activity { color: var(--text-secondary); font-size: 0.86rem; }
.onb-row-team select { width: 100%; }
.onb-row-team .field { margin-bottom: 0; }
.onb-row-team .field label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.onb-row-check { display: flex; align-items: center; justify-content: center; }

/* ------------------------------------------- step 3: progress + learning feed -- */
.onb-learn {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: var(--space-5);
  align-items: start;
}
.onb-learn-col { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }

.onb-progress { display: flex; flex-direction: column; gap: var(--space-4); }
.onb-progress-row { display: flex; flex-direction: column; gap: var(--space-2); }
.onb-progress-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
.onb-progress-top strong { font-weight: 600; }
.onb-progress-state { font-family: var(--font-mono); font-size: 0.74rem; color: var(--text-secondary); }
/* A real state, never a percentage Soil cannot measure: the bar is either "running"
   (an indeterminate animated sliver) or "done" (full, green). See
   soil/web/templates/_onboarding_sync_panel.html on why no fake percentage exists. */
.onb-bar {
  height: 6px;
  border-radius: var(--radius-sm);
  background: var(--raised-bg);
  overflow: hidden;
}
.onb-bar-fill { height: 100%; border-radius: var(--radius-sm); background: var(--success); }
.onb-bar-done .onb-bar-fill { width: 100%; }
.onb-bar-failed .onb-bar-fill { width: 100%; background: var(--danger); }
.onb-bar-waiting .onb-bar-fill { width: 100%; background: var(--border-input); }
.onb-bar-running .onb-bar-fill {
  width: 30%;
  background: var(--accent);
  animation: onb-indeterminate 1.4s ease-in-out infinite;
}
@keyframes onb-indeterminate {
  0% { margin-left: 0; }
  50% { margin-left: 70%; }
  100% { margin-left: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .onb-bar-running .onb-bar-fill { animation: none; width: 100%; opacity: 0.45; }
}

.onb-feed { padding: 0; overflow: hidden; }
.onb-feed-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-card);
}
.onb-feed-head p { margin: 0; }
.onb-feed-list { list-style: none; margin: 0; padding: 0; }
.onb-feed-list li {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-divider);
  font-size: 0.88rem;
}
.onb-feed-tick { color: var(--success); flex-shrink: 0; }
.onb-feed-foot {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  font-size: 0.78rem;
  color: var(--text-tertiary);
}

/* The "here's what's unusual right now" reveal. Signal blue here is correct: this is
   Soil's own read of the data, not a button. */
.onb-reveal {
  border-color: var(--accent-tint-border);
  background: var(--accent-tint);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.onb-reveal-kicker {
  margin: 0;
  font-size: 0.65rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-text);
}
.onb-warning {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  min-width: 0;
}
.onb-warning-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--danger);
  flex-shrink: 0;
  margin-top: 7px;
}
.onb-warning-body { min-width: 0; }
.onb-warning-body strong { display: block; font-weight: 600; }
.onb-warning-body p { margin: var(--space-1) 0 0; font-size: 0.86rem; color: var(--text-secondary); }
.onb-warning-meta { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-tertiary); overflow-wrap: anywhere; }
.onb-actions { display: flex; gap: var(--space-3); flex-wrap: wrap; }

@media (max-width: 900px) {
  .onb-topbar { padding: var(--space-3) var(--space-4); }
  .onb-main { padding: var(--space-5) var(--space-4) var(--space-6); }
  .onb-heading h1 { font-size: 1.6rem; }
  .onb-tools { grid-template-columns: 1fr; }
  .onb-learn { grid-template-columns: 1fr; }
  .onb-row {
    grid-template-columns: 32px minmax(0, 1fr);
    row-gap: var(--space-2);
    padding: var(--space-4);
  }
  .onb-row-head { display: none; }
  .onb-row-activity,
  .onb-row-team { grid-column: 2; }
  .onb-step { min-height: 44px; }
  .onb-footrow .btn { width: 100%; justify-content: center; }
}

/* Small follow-ons to the appended block above, kept with it rather than edited into
   the rules they refine. */
.onb-invite-url { margin: 0; overflow-wrap: anywhere; }
.onb-escape .btn-ghost { white-space: nowrap; }
.onb-escape { margin: 0; }

/* The repo list is a plain, full-length table, matching design/soil-app-design.html.
   An inner scroll container was tried and dropped: it hid how much there was to
   review, and a nested scroll inside a page scroll is awkward on a phone. The
   wrapper is kept in the markup (display: contents) so the rows stay direct
   children of the card for layout. */
.onb-table-body { display: contents; }

/* The three steps stay centred in the top bar whether or not the "Try the demo
   workspace instead" control is present (it is hidden inside the demo workspace and
   on a deployment with no seeded demo org). Row flex, so `flex: 1` here is safe --
   the auto-margin trap in CLAUDE.md's design notes is a COLUMN-flex problem. */
.onb-steps { flex: 1; justify-content: center; }
@media (max-width: 900px) { .onb-steps { flex: none; justify-content: flex-start; } }

/* Ask history rows: the full question on hover, and a per-row delete. The sidebar
   previously rendered four identical questions with no way to clear any of them. */
.ask-rail-row { display: flex; align-items: center; gap: var(--space-1); }
.ask-rail-row .ask-rail-q { flex: 1 1 0; min-width: 0; }
.ask-rail-del {
  flex-shrink: 0;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--text-tertiary);
  font-size: 1rem;
  line-height: 1;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  opacity: 0;
}
.ask-rail-row:hover .ask-rail-del,
.ask-rail-del:focus-visible { opacity: 1; }
.ask-rail-del:hover { background: var(--danger-bg); color: var(--danger); }
@media (max-width: 900px) { .ask-rail-del { opacity: 1; min-height: 44px; } }

/* ===================================================================
   Re-homed sections (2026-10): Company bets on Updates, release
   forecasts on Track record, Today's meetings in Today's right rail.

   Four rules total. Everything else those sections need already
   exists: .card/.stack-3 for the shell, .srow/.srow-text for a row,
   .status/.badge for the state, .feed/.feed-row for the rail list.
   =================================================================== */

/* A card heading with one piece of supporting text or a link beside it,
   inside a padded .card (where .card-head, which assumes .card-flush,
   would add its own padding and border). */
.srow-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  flex-wrap: wrap;
}

/* The right-hand cluster of a .srow when it holds more than one control
   (a status plus a Remove form; a due date plus a probability badge).
   Without this, .srow's space-between pushes the two apart.

   NOT flex-shrink: 0 -- that is what made Track record 448px wide in a 390px
   viewport: a no-shrink item whose children total 407px cannot give way, so the
   wrap never happens and the page scrolls sideways. min-width: 0 plus the default
   shrink lets the cluster narrow and wrap its own children instead. */
.srow-end {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  flex-wrap: wrap;
  min-width: 0;
}

/* A rare data-entry form folded away behind a disclosure, so a
   read-mostly page is not carrying open inputs (the finding that moved
   "Add a bet" behind one in the first place). Same shape as
   .team-admin/.team-admin-form on Teams. */
.disclosure > summary {
  cursor: pointer;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-secondary);
  list-style: none;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary::before { content: "+ "; color: var(--text-tertiary); }
.disclosure[open] > summary::before { content: "- "; }
.disclosure > summary:focus-visible {
  box-shadow: var(--focus-ring-shadow);
  outline: none;
  border-radius: var(--radius-sm);
}
.disclosure-form {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-3);
}
.disclosure-form .field { margin: 0; min-width: 160px; flex: 1 1 160px; }

/* A meeting's prep note sits under its title in the rail, not beside it:
   the rail is narrow and the note is a full sentence. */
.meeting-prep {
  display: block;
  margin-top: var(--space-1);
  font-size: 0.82rem;
  color: var(--text-tertiary);
  line-height: 1.45;
}

/* Work-graph focus picker. Its own row beneath the page header, because the labels
   are real PR/ticket/person titles from the connected tools and are routinely long
   enough to squeeze the headline out of a shared row. Each tab truncates and carries
   its full label in `title`, so the row stays one line however long the titles are. */
.graph-focus-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.graph-focus-tab {
  max-width: 28ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 900px) { .graph-focus-tab { max-width: 100%; } }

/* ===========================================================================
   2026-10 SECURITY ROUND 3 -- the demo-workspace banner
   =========================================================================== */

/* A shared demo workspace must SAY it is one. Until this pass no template anywhere
   referenced Organization.is_demo, so the one workspace full of fabricated data
   (soil/demo_showcase.py's `acme-demo`: 128 seeded issues, 61 seeded PRs) looked
   exactly like a real one -- which is the honesty rule in CLAUDE.md's design section
   read backwards.

   Strata has no colour for "notice", only Signal blue for Soil's own intelligence and
   ink for action, so this is a plain Canvas strip with a hairline border and the ink
   "DEMO" marker doing the work -- deliberately quieter than .llm-fake-banner above
   (which is red because placeholder prose passing as a real answer is a defect; a demo
   workspace is not a defect, it is a labelled exhibit). */
.demo-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-5);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
  color: var(--text-secondary);
  font-size: 0.9rem;
  line-height: 1.5;
}
.demo-banner-mark {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--action);
  color: var(--action-text);
}
.demo-banner strong { color: var(--text-primary); }
.demo-banner a { color: var(--accent-text); font-weight: 600; }

/* ===========================================================================
   2026-10 SECURITY ROUND 3 -- the invite page's seat counter
   =========================================================================== */

/* Named component classes, not new utilities, per the design rules in CLAUDE.md. The
   invite link itself used to carry an inline style="margin:0;word-break:break-all"
   attribute; it is here now so that attribute could go, which is one fewer thing
   standing between this app and a style-src without 'unsafe-inline'. */
.invite-link {
  word-break: break-all;
}
.invite-seats {
  margin: var(--space-2) 0 0;
}

/* ===========================================================================
   2026-10 SECURITY ROUND 3 -- htmx's loading-indicator rules, moved in-repo
   =========================================================================== */

/* htmx injects these three rules itself, as a <style> element it appends to <head>
   (insertIndicatorStyles in htmx 1.9.12). With `style-src-elem` no longer allowing
   'unsafe-inline', the browser refuses that element -- so every .htmx-indicator would
   sit permanently visible instead of fading in only during a request. That is a real
   visual regression, and it is why `{"includeIndicatorStyles":false}` is now in the
   `htmx-config` meta tag of all three layouts that load htmx (base.html,
   _auth_layout.html, _onboarding_layout.html) and the rules live here instead.
   Copied verbatim from htmx's own source so the behaviour is identical.

   Found by loading the app in a real browser, NOT by a static scan: no template
   contains this <style> block, so nothing in the repo could have revealed it. Worth
   remembering the next time a CSP directive is tightened -- a library can inject
   markup that no grep over the templates will ever show. */
.htmx-indicator { opacity: 0; }
.htmx-request .htmx-indicator { opacity: 1; transition: opacity 200ms ease-in; }
.htmx-request.htmx-indicator { opacity: 1; transition: opacity 200ms ease-in; }

/* Onboarding: an unclaimed sync job. Not an error state -- nothing failed and nothing
   was lost -- so it borrows the at-risk tint rather than the blocked one. */
.onb-stalled { border-left: 3px solid var(--warning); }
.onb-stalled code {
  font-family: var(--font-mono, monospace);
  font-size: 0.85em;
  background: var(--sidebar-bg);
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
}

/* Onboarding progress: the real "N of M repositories read" line under each bar. */
.onb-progress-detail {
  margin: var(--space-2) 0 0;
  font-size: 0.75rem;
  color: var(--text-tertiary);
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
}
.onb-progress-check { color: var(--success); }

/* Teams: the switcher past TEAM_TABS_MAX. A disclosure, not a wrapping tab row --
   one team per repository means a 52-repo org otherwise gets seven rows of tabs
   above the content the page exists to show. Strata: restraint, hairlines, no
   decoration; the current team is marked with ink weight, never a colour. */
.team-switcher { margin-top: var(--space-3); }
.team-switcher > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  background: var(--card-bg);
}
.team-switcher > summary::-webkit-details-marker { display: none; }
.team-switcher > summary::after {
  content: "";
  width: 0.4em;
  height: 0.4em;
  border-right: 1.5px solid var(--text-tertiary);
  border-bottom: 1.5px solid var(--text-tertiary);
  transform: rotate(45deg) translate(-0.1em, -0.1em);
  transition: transform 150ms ease;
}
.team-switcher[open] > summary::after { transform: rotate(-135deg) translate(-0.1em, -0.1em); }
.team-switcher > summary:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
.team-switcher-current { font-weight: 600; color: var(--text-primary); }
.team-switcher-count { font-size: 0.75rem; color: var(--text-tertiary); }
.team-switcher-list {
  margin-top: var(--space-3);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--space-1) var(--space-4);
  max-height: 22rem;
  overflow-y: auto;
  padding: var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--card-bg);
}
.team-switcher-item {
  display: block;
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.875rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.team-switcher-item:hover { background: var(--sidebar-bg); color: var(--text-primary); }
.team-switcher-item.active { color: var(--text-primary); font-weight: 600; }
/* 44px on a phone. Seven of these rows are the whole team switcher, they are the only
   route between team pages at 390px, and each measured 332x36 -- 8px of padding either
   side of one 20px line. Padding rather than a flex box, because `display: block` is
   what makes `text-overflow: ellipsis` work on the long team names above, and
   `min-height` on its own would leave the name pinned to the top of a 44px row. */
@media (max-width: 900px) {
  .team-switcher-item { min-height: 44px; padding-top: 12px; padding-bottom: 12px; }
}
@media (max-width: 600px) {
  .team-switcher-list { grid-template-columns: 1fr; max-height: 60vh; }
}

/* Sidebar: "All repositories N" under the capped quick-list. A quiet link, not a button --
   it is a way out of a truncated list, not an action. */
.side-all-teams {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font-size: 0.8125rem;
  color: var(--text-tertiary);
  text-decoration: none;
  border-radius: var(--radius-sm);
}
.side-all-teams:hover { background: var(--sidebar-bg); color: var(--text-primary); }
.side-all-teams .mono { font-size: 0.6875rem; }

/* Teams: the collapsed "Other repos (N)" list of dormant repo-derived teams. Quieter
   than the switcher above it -- these are present, not proposed. */
.other-repos { margin: 0 0 var(--space-5); }
.other-repos > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: 0.875rem;
  color: var(--text-tertiary);
}
.other-repos > summary::-webkit-details-marker { display: none; }
.other-repos > summary::before {
  content: "";
  width: 0.35em;
  height: 0.35em;
  border-right: 1.5px solid var(--text-tertiary);
  border-bottom: 1.5px solid var(--text-tertiary);
  transform: rotate(-45deg);
  transition: transform 150ms ease;
}
.other-repos[open] > summary::before { transform: rotate(45deg); }
.other-repos > summary:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
.other-repos .note { margin: var(--space-3) 0 var(--space-2); }

/* =====================================================================
   Feature 5, "Finish the MCP tools" (docs/SOIL_FEATURES.md) -- the only two
   screens it touches: the Connections panel's collapsible setup steps for
   ChatGPT / Claude / Cursor, and Settings -> Organization -> Connected AI apps.

   Appended, never edited above: soil.css is append-only. No new colour, font or
   spacing value -- every declaration below resolves to an existing token, except
   the three literals the dark MCP card already established above (#c9cdd6 body
   text, #9db2ff links, #151920 inset) which are reused here rather than invented.
   Settings' "Connected AI apps" needs NO css at all: it is .card + .srow +
   .srow-form + .note + .mono, exactly as the other rows on that tab.
   ===================================================================== */

.mcp-setup { margin: 0; }
.mcp-setup > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: 0.875rem;
  color: #ffffff;
}
.mcp-setup > summary::-webkit-details-marker { display: none; }
/* Same disclosure caret as .other-repos above, in the dark card's own ink. */
.mcp-setup > summary::before {
  content: "";
  width: 0.35em;
  height: 0.35em;
  border-right: 1.5px solid #c9cdd6;
  border-bottom: 1.5px solid #c9cdd6;
  transform: rotate(-45deg);
  transition: transform 150ms ease;
}
.mcp-setup[open] > summary::before { transform: rotate(45deg); }
.mcp-setup > summary:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

.mcp-setup-body {
  margin-top: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: 0.875rem;
  color: #c9cdd6;
}
.mcp-setup-head {
  font-family: var(--font-heading);
  font-weight: 600;
  color: #ffffff;
  margin: var(--space-2) 0 0;
}
.mcp-setup-body > .mcp-setup-head:first-child { margin-top: 0; }
.mcp-setup-body ol {
  margin: 0;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.mcp-setup-body code {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
  background: #151920;
  color: #9db2ff;
  overflow-wrap: anywhere;
}
.mcp-setup-body .note { color: #c9cdd6; }
.mcp-setup-body a { color: #9db2ff; }

/* ---------------------------------------------------------------------------
   Single point of failure: "Areas that depend on one person"
   (soil/dependencies.py, docs/SOIL_FEATURES.md #3). Appended as one block, per
   CLAUDE.md -- nothing above this line was edited.

   Rendered on the Team page inside .panel, and on the Warning detail page inside
   .card. No new colour, font, radius or spacing value: every declaration resolves
   to an existing token, and the only status colour used is the shared .status
   component the markup already renders. Strata has no colour for "this one
   person" -- emphasis is ink and a hairline, like every other row in the app.
   --------------------------------------------------------------------------- */
.spof-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
}
.spof-row:last-child { border-bottom: 0; }

.spof-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.spof-title {
  font-family: var(--font-heading);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--text-primary);
}
.spof-detail {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-secondary);
}

.spof-items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.spof-items li { font-size: 0.875rem; line-height: 1.5; overflow-wrap: anywhere; }
.spof-items a { color: var(--text-secondary); text-decoration: none; }
.spof-items a:hover { color: var(--text-primary); text-decoration: underline; }
.spof-items .mono { font-size: 0.75rem; color: var(--text-tertiary); margin-right: var(--space-1); }

/* The Warning page's card brings its own padding, so the row rules above are for
   the Team page's panel only; inside the card the block sits flush. */
.card#area-dependency .spof-detail { margin-bottom: var(--space-2); }
.card#area-dependency .spof-items { margin-top: var(--space-2); }

@media (max-width: 720px) {
  .spof-row { padding: var(--space-4); }
}

/* ===========================================================================
   Fix-it handoff to coding agents (docs/SOIL_FEATURES.md #4, soil/handoff.py).
   Appended, nothing above this line edited. Existing tokens only -- no new
   colour, font, radius or spacing value.
   =========================================================================== */

/* The Warning page's "exactly what will be sent" preview. A handoff's payload field
   is a whole markdown issue body, and the approver has to read the literal text,
   line breaks included -- a collapsed paragraph would mean approving something that
   is not byte-for-byte what gets posted. pre-wrap is inert for the single-line
   values every other write action previews. */
.fix-preview-value {
  display: block;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin-top: var(--space-1);
  font-size: 0.8125rem;
  line-height: 1.5;
}
/* Deliberately NO max-height and no nested scrollbar here. A scroll box inside a card
   is the classic way text goes unread, and this is the one block a person is being
   asked to approve verbatim -- the whole body is on the page, and they scroll the page. */

/* Track record's handoff list reuses .srow/.badge wholesale; the one thing it needs
   is for the state summary in the section head to wrap on a narrow viewport instead
   of forcing the row wider than a 390px screen. */
.srow-head .meta-sm { overflow-wrap: anywhere; }

/* Ask: the empty state sat in 48px of padding top AND bottom, then the starters row
   added its own gap, leaving a dead band between the lede and the first thing you can
   click. The heading is the top of the page; it needs no padding above it. */
.ask-empty { padding: 0 0 var(--space-4); }

/* Past questions in the rail: a repo-qualified question wrapped to three lines and
   pushed the rest of the rail down. Two lines, then ellipsis -- the full text is still
   the link's own title. */
.ask-rail-q {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
  /* The clamp bounds the CONTENT box, but this element has space-2 of padding top and
     bottom, so a third line showed through the bottom padding -- cut mid-character,
     which reads as broken rather than truncated. An explicit height makes the clip land
     on a line boundary: two lines at the 1.35 line-height set above, plus the padding
     the clamp does not account for. */
  max-height: calc(1.35em * 2 + var(--space-2) * 2);
  box-sizing: border-box;
}

/* Work graph: the real title under the short-handle heading. One line, truncated --
   the full string is the element's own title attribute. */
.graph-focus-fulltitle {
  margin: calc(var(--space-2) * -1) 0 var(--space-4);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 70ch;
}

/* "<Tool> connected" after an OAuth callback. Green because this is the one place a
   success tone is the message -- it uses the existing on-track status tokens, not a new
   colour. Fixed so it reads the same wherever the callback lands you, and it removes
   itself (soil.js) rather than becoming furniture. */
.connected-toast {
  position: fixed;
  top: var(--space-5);
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  background: var(--success-bg);
  color: var(--success);
  border: 1px solid currentColor;
  border-radius: var(--radius-md);
  font-size: 0.9375rem;
  font-weight: 600;
  box-shadow: var(--shadow-card);
}
.connected-toast-tick { font-size: 1rem; }
.connected-toast.is-leaving { opacity: 0; transition: opacity 200ms ease; }
@media (max-width: 600px) {
  .connected-toast { left: var(--space-4); right: var(--space-4); transform: none; justify-content: center; }
}

/* ===========================================================================
   APPENDED BLOCK — typed signals on Today (docs/SOIL_SIGNALS.md Step 4)

   Three small things, no new token and no new colour:
     * the "Not useful" control that mutes a signal type for one person,
     * the signal's own type label next to a card,
     * the "Why am I seeing this?" disclosure listing the importance score's own
       recorded reasons.

   The one override below is deliberate: Today's "Since yesterday" rows gained two
   more children (the type label and the mute form), and the existing `.feed-row`
   grid is two columns wide, so those children would have wrapped onto their own
   grid rows. It is scoped to `.today-col` rather than redefining `.feed-row`
   globally, because `.feed-row` is also used on the Team and Track record screens
   and nothing there changed.
   =========================================================================== */

.attention-pick-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
  min-width: 0;
  border-bottom: 1px solid var(--border-divider);
}
.attention-list > .attention-pick-row:last-child { border-bottom: 0; }
/* The row and its "Not useful" control sit side by side, and the row takes the rest.
   The mute form is deliberately OUTSIDE the <details> (home.html says why), so this
   wrapper is what keeps the two on one line. */
.attention-pick-row > .attn-row { flex: 1 1 auto; }
.attention-pick-row > .signal-mute { padding: var(--space-4) var(--space-4) 0 0; }

.signal-kind {
  font-size: 0.62rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.signal-mute { margin: 0; flex: 0 0 auto; }

.signal-mute-btn {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  font-family: inherit;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--text-tertiary);
  cursor: pointer;
  min-height: 24px;
  white-space: nowrap;
}
.signal-mute-btn:hover {
  color: var(--text-primary);
  border-color: var(--border-input);
}
.signal-mute-btn:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }

.why-score {
  margin: var(--space-3) 0 0;
  border-top: 1px solid var(--border-divider);
  padding-top: var(--space-3);
}
.why-score > summary {
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-secondary);
}
.why-score > summary:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
.why-score-list {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: 0.82rem;
  color: var(--text-secondary);
}
.why-score-points { color: var(--text-tertiary); font-size: 0.72rem; }

.today-col .feed-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
}
.today-col .feed-row .feed-time { flex: 0 0 auto; }
.today-col .feed-row .feed-text { flex: 1 1 14rem; min-width: 0; }

@media (max-width: 600px) {
  .attention-pick-row { flex-wrap: wrap; }
  /* Wrapped onto its own line, the control sits at the card's left edge unless it is
     given the same inset the row above it has -- otherwise one element in the list is
     flush to the border and the rest are not. */
  .attention-pick-row > .signal-mute { padding: 0 var(--space-4) var(--space-4); }
  .signal-mute-btn { min-height: 44px; }
  .today-col .feed-row .feed-text { flex: 1 1 100%; }
}

/* ============================================================================
   Settings -> What Soil ignores (docs/SOIL_SIGNALS.md Step 2)
   ----------------------------------------------------------------------------
   One list of stored noise verdicts, each with the reason it carries and an
   "Include anyway" undo. Appended as one block; nothing above this line edited.
   Tokens only -- no raw px, no new colour, no new font. The included state uses
   the existing on-track status component (via the `status` macro), so this block
   adds no colour of its own.
   ========================================================================== */
.ignore-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}
.ignore-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.ignore-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border-divider);
}
.ignore-row:first-child { border-top: 0; padding-top: 0; }
.ignore-row-text { min-width: 0; }
.ignore-row-name {
  font-weight: 600;
  overflow-wrap: anywhere;
}
.ignore-row-action { flex: 0 0 auto; }
/* An admin has overruled this one: it stays listed (so the reason is still on the
   record) but reads as settled rather than pending. */
.ignore-row.is-included .ignore-row-name { color: var(--text-tertiary); }
@media (max-width: 720px) {
  .ignore-row { flex-direction: column; align-items: stretch; gap: var(--space-2); }
}

/* ===========================================================================
   design/soil-design-v3.pdf — Updates (p3) and Sign up / log in (p4)
   ---------------------------------------------------------------------------
   Appended as one block; nothing above this line is edited. Every value below
   resolves to an existing token: no new colour, font, radius or spacing value.
   =========================================================================== */

/* A KPI's "vs last month" line. Sits in the same slot as .kpi-note and is
   rendered only when soil/investor_update.py measured a real previous month —
   never an em dash, a 0% or a "no change" standing in for an absent comparison.

   Deliberately NOT coloured by direction: Strata has one accent for "the answer"
   and one for "acts on it", and no green/red pair for up and down. The arrow is
   aria-hidden decoration; the words carry the meaning on their own, which is also
   what keeps this readable without colour. */
.kpi-delta {
  display: block;
  margin-top: var(--space-1);
  font-size: 0.78rem;
  color: var(--text-tertiary);
}

/* Sign up / log in: the domain-match panel. The three lines stack inside the
   existing .auth-notice flex row rather than becoming three siblings, so the
   avatar mark stays aligned to the first line. */
.auth-notice-count,
.auth-notice-why,
.auth-notice-switch {
  display: block;
  margin-top: var(--space-1);
}
.auth-notice-why {
  color: var(--text-secondary);
  font-size: 0.82rem;
}
/* Inherits the panel's own text colour: a link inside a tinted panel that reverts
   to the default link colour fails contrast against that tint. */
.auth-notice-switch {
  margin-top: var(--space-2);
  color: inherit;
  font-weight: 600;
  font-size: 0.82rem;
}

/* ===========================================================================
   design/soil-design-v3.pdf — Warning detail (p5) and Connections (p6).

   Appended as ONE block; nothing above this line was edited. Every value here is an
   existing token: the --space-1..7 scale, --radius-sm/md/lg, and the Strata palette
   already declared in :root. No new colour, font, radius or spacing value. Signal
   blue appears nowhere below — every action on these two screens is ink (--action),
   because a blue button is a bug.
   =========================================================================== */

/* --- Warning detail (p5) -------------------------------------------------- */

/* The chain's date column now carries the weekday too ("Mon 28 Jun"), so five steps
   read as a week rather than as five disconnected numbers. 62px fits "28 Jun" and
   not the weekday, so the column grows; nothing else about .why-step changes. */
.why-step { grid-template-columns: 76px 16px minmax(0, 1fr) auto; }
.why-when { line-height: 1.35; }

/* "Fastest" sits on the first fix Soil can actually perform. Ink tint, never Signal
   and never a status colour: it is a claim about step count — which the badge's own
   title attribute spells out — not about risk or outcome. */
.fix-option-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-width: 0;
}
.fix-fastest {
  flex: none;
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--text-primary);
  background: var(--accent-warm-bg);
  border-radius: var(--radius-sm);
  padding: 2px var(--space-2);
}
/* What came of it, after the sentence saying what Soil did. */
.tried-result { color: var(--text-tertiary); font-weight: 400; }
.tried-msg { overflow-wrap: anywhere; }

/* --- Connections: "What Soil can see" (p6) -------------------------------- */

.see-strip {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(4, minmax(0, 1fr));
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  overflow: hidden;
  margin-bottom: var(--space-5);
}
.see-cell {
  padding: var(--space-4) var(--space-5);
  border-right: 1px solid var(--border-divider);
  min-width: 0;
}
.see-cell:last-child { border-right: 0; }
.see-setup { background: var(--sidebar-bg); }
.see-label {
  display: inline-block;
  margin: 0 0 var(--space-2);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.see-value {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.see-unit { margin: var(--space-1) 0 0; font-size: 0.86rem; color: var(--text-secondary); }
.see-note { margin: var(--space-2) 0 0; font-size: 0.78rem; color: var(--text-tertiary); line-height: 1.45; }
.see-note-bad { color: var(--danger); font-weight: 600; }
.see-note-good { color: var(--success); font-weight: 600; }
.see-meter { display: flex; gap: var(--space-1); margin: var(--space-3) 0 0; }
.see-meter-seg {
  flex: 1 1 0;
  height: 4px;
  border-radius: var(--radius-sm);
  background: var(--border-card);
}
.see-meter-seg.is-done { background: var(--success); }

/* "Who can see what" — the permission mode, directly under the counts above.

   It sits here because the tiles above and Today can disagree without either being
   wrong: in Strict mode a tool is connected, synced and counted here and still shows
   nothing on Today, because access._filter_org_scoped hides every org-scoped tool
   from everyone (no admin bypass). This block is what stops that reading as a bug.

   Deliberately plain. The emphasis is carried by the .status chip's word and dot and
   by the copy, not by a tinted panel — Strata has no colour for "careful", and the
   one thing on this card that must stay readable is a long paragraph. The strict
   variant moves the card's hairline to --warning and changes nothing else, which is
   the same treatment .attention-row-promise-atrisk uses, and it keeps --text-tertiary
   body text on --card-bg in both themes rather than on a tint it was never measured
   against. */
.conn-who {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
  /* A tool name or workspace name can appear in this copy, and this card is a flex
     column: without these a long unbroken token pushes the card wider than the grid
     and the page gains a horizontal scrollbar at 390px. */
  min-width: 0;
  overflow-wrap: anywhere;
}
.conn-who-strict { border-color: var(--warning); }
.conn-who-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-3);
  min-width: 0;
}
/* 68ch, the same measure .conn-grant uses: this is the longest run of prose on the
   page and an unmeasured line at 1440px would run the full 1560px page width. */
.conn-who .note { max-width: 68ch; }

/* "Needs you" — the only thing on this page that is a to-do rather than an
   inventory, so it sits above the inventory and reads as rows, not cards. */
.needs-you { margin-bottom: var(--space-5); }
.needs-you-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
}
.needs-you-head h2 { margin: 0; font-size: 1.05rem; }
.needs-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  margin-bottom: var(--space-3);
}
/* Strata marks the one most urgent row with solid ink, never a warm accent. */
.needs-row-first { border-color: var(--action); }
.needs-text { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.needs-text strong { font-weight: 700; overflow-wrap: anywhere; }
.needs-text .note { margin: 0; line-height: 1.45; }
.needs-action { justify-self: end; min-width: 0; }
.needs-action .btn { white-space: normal; text-align: center; }

/* The connected list, and the Manage panel that opens beside it. */
.conn-split { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-5); align-items: start; }
.conn-split-open { grid-template-columns: minmax(0, 1fr) minmax(0, 23rem); }
/* align-items: start, not center. Rows are not all one height -- GitHub's carries
   three sentences of selection accounting, Google Drive's two -- so centring floated
   every other column to the middle of whichever row was tallest, and the tool names
   stopped lining up down the page. Top alignment is what makes a list read as a table.
   (The same fix belongs on .conn-row above; THIS is the class the screen actually
   uses, and fixing only that one is why the first attempt changed nothing.) */
.conn-row2 {
  display: grid;
  /* The name column is CAPPED, not a fraction of the free space. As `1.1fr` it grew
     with the viewport, so on a wide screen "GitHub / Read-only" sat in ~600px of
     nothing and the coverage bar started miles to its right -- the two halves of one
     row read as two unrelated columns.
     13.5rem is measured, not guessed: with the access line on its own row (see
     connections.html) the widest thing this column holds is Slack's "Read + approved
     actions" at 165px, plus the 32px logo and its 12px gap = 209px. 216px leaves a
     little headroom and nothing wraps. It was 17rem while the sync time and the access
     were joined by a middot on one line, which was the longest string on the screen.
     The 1180px rule below still collapses all four to one column. */
  grid-template-columns: minmax(10rem, 13.5rem) minmax(0, 1fr) auto auto;
  align-items: start;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
}
.conn-row2:last-child { border-bottom: 0; }
.conn-row2.is-managed { background: var(--sidebar-bg); }
.conn-cover { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.conn-cover-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: 0.9rem;
  color: var(--text-secondary);
  min-width: 0;
}
/* The line has min-width: 0 but its LABEL child did not, so a long picker noun pushed
   the percentage 2px past the edge. The pct keeps flex: none on purpose -- "0%" must
   never be the thing that truncates. */
.conn-cover-line > :first-child { min-width: 0; overflow-wrap: anywhere; }
.conn-cover-pct { flex: none; font-size: 0.78rem; color: var(--text-tertiary); }
.conn-bar {
  display: block;
  height: 4px;
  width: 100%;
  border-radius: var(--radius-sm);
  background: var(--border-card);
  overflow: hidden;
}
.conn-bar-fill { display: block; height: 100%; background: var(--action); }
.conn-state { justify-self: start; min-width: 0; }
.conn-row2 .conn-stale { font-size: 0.78rem; }

.manage-panel {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  min-width: 0;
}
.manage-head { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-4); }
.manage-head h2 { margin: 0; font-size: 1.05rem; flex: 1 1 auto; min-width: 0; }
.manage-close {
  flex: none;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--text-tertiary);
  text-decoration: none;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-md);
}
.manage-close:hover { color: var(--text-primary); background: var(--sidebar-bg); }
.manage-q { margin: 0 0 var(--space-2); font-weight: 700; }
.manage-panel .note { margin: 0 0 var(--space-3); line-height: 1.5; }
.manage-error {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--warning-bg);
  color: var(--warning);
  border-radius: var(--radius-md);
  font-size: 0.88rem;
  line-height: 1.5;
}
.manage-list {
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
  max-height: 24rem;
  overflow-y: auto;
}
.manage-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-divider);
  min-width: 0;
}
.manage-item:last-child { border-bottom: 0; }
.manage-item > label {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
  cursor: pointer;
}
.manage-item input { margin-top: 3px; flex: none; }
.manage-item-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.manage-item-name { font-weight: 600; overflow-wrap: anywhere; }
.manage-item-text .note { margin: 0; font-size: 0.78rem; }
.manage-item.is-out .manage-item-name { color: var(--text-tertiary); font-weight: 500; }
.manage-badge {
  flex: none;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
}
.manage-badge-good { background: var(--success-bg); color: var(--success); }
.manage-badge-quiet { background: var(--raised-bg); color: var(--text-tertiary); }
.manage-access { margin-top: var(--space-4); border-top: 1px solid var(--border-divider); padding-top: var(--space-3); }
.manage-access > summary { cursor: pointer; font-size: 0.88rem; color: var(--text-tertiary); }
.manage-access[open] > summary { color: var(--text-primary); }
.manage-access-body { display: flex; flex-direction: column; gap: var(--space-3); padding-top: var(--space-3); }
.manage-access-body p { margin: 0; }

.add-tools { margin-top: var(--space-6); }
.add-tools h2 { font-size: 1.05rem; margin: 0 0 var(--space-4); }
.add-tools-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-4);
}
.add-tool { display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; min-width: 0; }
.add-tool-head { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.add-tool .note { margin: 0; flex: 1 1 auto; }

@media (max-width: 1100px) {
  /* The panel below the list rather than beside it: 23rem of chooser next to a
     four-column row is unreadable long before it is narrow. */
  .conn-split-open { grid-template-columns: minmax(0, 1fr); }
  .see-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .see-cell { border-bottom: 1px solid var(--border-divider); }
  .see-cell:nth-child(2n) { border-right: 0; }
  .see-setup { grid-column: 1 / -1; }
}

@media (max-width: 760px) {
  .see-strip { grid-template-columns: minmax(0, 1fr); }
  .see-cell { border-right: 0; }
  .see-cell:last-child { border-bottom: 0; }
  /* align-items stays stretch here deliberately: flex-start would make each row
     fit-content on the cross axis once the row flips to a column. */
  .conn-row2 { grid-template-columns: minmax(0, 1fr); gap: var(--space-3); }
  .conn-state, .conn-action, .needs-action { justify-self: start; }
  .needs-row { grid-template-columns: auto minmax(0, 1fr); }
  .needs-action { grid-column: 1 / -1; }
  .needs-action .btn { width: 100%; }
  .why-step { grid-template-columns: 76px 16px minmax(0, 1fr); }
  .why-step .source-chip { grid-column: 3; justify-self: start; }
}

/* Today's why-chain column is 300px once the 340px right rail and the 300px picker
   are subtracted, which happens at any viewport at or below about 1400px -- far wider
   than the 900px phone breakpoint the stacked layout used to wait for. In between,
   the day / dot / text / source-chip row had nowhere to put the chip and squeezed the
   text to a single character per line. Below 1400px the chip drops to its own row,
   which is what the phone rule already did. */
@media (max-width: 1400px) {
  .today-col .why-step {
    grid-template-columns: 52px 12px minmax(0, 1fr);
    row-gap: var(--space-1);
  }
  .today-col .why-step .source-chip {
    grid-column: 3;
    justify-self: start;
  }
}

/* ============================================================================
   Settings tab strip at phone width
   ----------------------------------------------------------------------------
   Appended as one block; nothing above this line is edited. No colour, no font,
   no spacing value of its own -- two layout properties and nothing else.

   .seg-tabs has always carried `flex-wrap: wrap`, and it has never been able to
   use it: it is an `inline-flex` inside `.page-header-actions`, which is
   `flex-shrink: 0`, so its width is max-content and no wrap is ever triggered.
   At 390px the Settings strip therefore ran 84px past the viewport. The fix is
   the pattern this file already uses twice for the same problem (see
   `.page-header-actions:has(> .btn-row)` and `:has(> .op-tabs)`): let that one
   cell shrink, with min-width 0 so it may shrink below its content, and the
   existing wrap does the rest.

   Not scoped to a media query on purpose -- the strip should wrap whenever it
   genuinely does not fit, which depends on how many tabs there are and what they
   are called, not on a breakpoint. Consolidating "What Soil watches" and "What
   Soil ignores" into one tab took the strip from five entries to four; this is
   what keeps the remaining four honest at any width.
   ========================================================================== */
.page-header-actions:has(> .seg-tabs) {
  min-width: 0;
  flex: 0 1 auto;
}
.page-header-actions > .seg-tabs { max-width: 100%; }

/* --- Weekly value report (soil/value_report.py, docs/SOIL_FEATURES.md #1) ----
   Today's "Last week" rail card and the Settings "Weekly value email" card both
   reuse existing components (.panel / .normal-row / .card / .note), so this block
   adds no card styling at all. It fixes one pre-existing layout bug found while
   verifying the page this feature changed.

   `.page-header-actions` is `flex-shrink: 0` (see the rule near .page-header), so
   Settings' six-item `.seg-tabs` strip could never actually use its own
   `flex-wrap: wrap` -- the row held it at its intrinsic width and pushed the
   document to 612px inside a 390px viewport. Track record and Updates were
   unaffected (two and three tabs, which fit), so only Settings ever showed it.

   This is the same relaxation the file already applies one line below that rule
   for a `.btn-row` child, with the same two declarations and no new value --
   `min-width: 0` is what lets a flex item shrink below its content width at all. */
.page-header-actions:has(> .seg-tabs) { flex-shrink: 1; min-width: 0; }

/* Points at the "Needs you" row that owns the action, instead of repeating its button.
   A link, not a button: the thing that acts is above, this only navigates to it. */
.conn-seeabove {
  font-size: 0.82rem;
  color: var(--accent-text);
  text-decoration: none;
  white-space: nowrap;
}
.conn-seeabove:hover { text-decoration: underline; }

/* "Your company's normal", design/soil-design-v3.pdf p1: label and sublabel on the left,
   figure and its vs-last-month delta on the right. Direction is carried by a word
   ("better", "vs last month") as well as the arrow and colour, never colour alone. */
.normal-row2 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border-divider);
}
.normal-row2:last-child { border-bottom: 0; }
.normal-head { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.normal-head .normal-label { font-size: 0.92rem; color: var(--text-primary); font-weight: 500; }
.normal-sub { font-size: 0.8rem; color: var(--text-tertiary); overflow-wrap: anywhere; }
.normal-figure {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  text-align: right;
  flex: 0 1 auto;
  min-width: 0;
}
/* SOIL_OS M1: "team normals as big numbers". These are the four figures that answer
   "is this unusual?", and at 1.35rem they were smaller than the Track record card's
   own 1.9rem figure directly above them -- so the rail read as one headline followed by
   four captions, when all five are the same kind of claim. The emphasis only; the
   values, their gates and their empty states are untouched.
   `tabular-nums` because these sit in a column and a proportional "1" makes four
   right-aligned figures look misaligned. */
.normal-row2 .normal-value {
  font-family: var(--font-heading);
  font-size: 1.9rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  white-space: nowrap;
}

/* The 8-week shape beside a headline number. currentColor, so it inherits the row's
   text colour and themes in dark mode with no second rule. Muted on purpose: the
   NUMBER is the fact and the line is context -- a sparkline that outweighs its figure
   has the hierarchy backwards. A row with no honest series renders no <svg> at all,
   so there is nothing here to hide or grey out. */
.spark {
  display: block;
  color: var(--text-tertiary);
  margin-top: 2px;
  /* preserveAspectRatio="none" stretches the stroke; cap the box so it cannot. */
  width: 120px;
  max-width: 100%;
  height: 28px;
}
.spark-dot { color: var(--text-secondary); }
.normal-delta { font-size: 0.78rem; white-space: nowrap; }
.normal-delta-better { color: var(--success); }
.normal-delta-worse { color: var(--warning); }
.normal-delta-same { color: var(--text-tertiary); }
/* The honest empty: a sentence where a number would be, never a zero. */
.normal-empty {
  font-size: 0.8rem;
  color: var(--text-tertiary);
  text-align: right;
  overflow-wrap: anywhere;
}

@media (max-width: 1040px) {
  .normal-row2 { flex-direction: column; align-items: flex-start; gap: var(--space-1); }
  .normal-figure { align-items: flex-start; text-align: left; }
  .normal-empty { text-align: left; }
}

/* ---------------------------------------------------------------------------
   A1 backtest (soil/backtest.py, docs/design/A1_BACKTEST.md).

   Its own named block rather than a reuse of .stat-strip, and that is the point:
   a backtest result must never render in the same strip as a live count, so it
   cannot even accidentally be laid out as one. Five cells, because there are five
   honest buckets (warnings / fixed / slipped / still stuck / not resolved yet) and
   no percentage -- a single hit rate off this few resolved warnings would be the
   fake number CLAUDE.md forbids.

   No colour carries meaning here. Strata has blue for "Soil's own intelligence"
   and ink for "this acts on it"; a replayed verdict is neither.
   --------------------------------------------------------------------------- */
.backtest-headline {
  font-family: var(--font-heading);
  font-size: 1.15rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--text-primary);
  margin: 0;
  overflow-wrap: anywhere;
}
.backtest-counts {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--space-4);
}
.backtest-count { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.backtest-count-value {
  font-family: var(--font-heading);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}
.backtest-count-label {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  line-height: 1.4;
  overflow-wrap: anywhere;
}
/* One example per row: the why-chain line and the dates are separate claims, so they
   are separate lines rather than one run-on sentence. */
.backtest-example-line { display: block; }

/* The method block: one sentence per real property of this run. It reads as a list
   because every line is a separate claim a reader can check. */
.backtest-method {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.backtest-method li {
  position: relative;
  padding-left: var(--space-4);
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}
.backtest-method li::before {
  content: "";
  position: absolute;
  left: var(--space-2);
  top: 0.6em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--text-tertiary);
}

@media (max-width: 1040px) {
  .backtest-counts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 680px) {
  .backtest-counts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
}


/* --- Cash leaks (/org/{slug}/cash-leaks and Today's rail card) ---------------
   docs/SOIL_MONEY_FEATURES.md §A3. Tokens only, no raw px, no new utility class,
   and no colour that means anything on its own: a money figure is ink, and the
   one place blue appears is the citation component, which is Soil's own
   intelligence. There is no "urgent" colour here because Strata has none.

   Every name/identifier that comes from a connected tool (a vendor string off a
   card statement, a seat's email address, an AWS service name) carries
   overflow-wrap: anywhere and min-width: 0 -- the trap CLAUDE.md's design-system
   section names, and the reason .graph-node burst out of its card. A statement
   vendor string is exactly the unbroken 60-character token that does it. --- */

.leak-totals { display: flex; flex-wrap: wrap; gap: var(--space-5); }
.leak-total {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-5);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
  min-width: 0;
  flex: 1 1 320px;
}
.leak-total-figure {
  font-family: var(--font-heading);
  font-size: 2.4rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  line-height: 1.05;
}
.leak-total-unit { font-size: 0.85rem; color: var(--text-tertiary); }
.leak-total-note { font-size: 0.85rem; color: var(--text-secondary); line-height: 1.6; }

.leak-list { display: flex; flex-direction: column; }
.leak-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
  min-width: 0;
}
.leak-row:last-child { border-bottom: 0; }
.leak-row-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  min-width: 0;
}
.leak-subject {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 700;
  min-width: 0;
  overflow-wrap: anywhere;
}
.leak-figure { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; min-width: 0; }
.leak-amount {
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--text-primary);
  white-space: nowrap;
}
/* The honest empty: a sentence where a figure would be. NEVER a zero -- no source
   Soil can read returns a seat price, so this is the common case. */
.leak-no-figure { font-size: 0.85rem; color: var(--text-tertiary); }
.leak-line { margin: 0; line-height: 1.6; color: var(--text-secondary); overflow-wrap: anywhere; }
.leak-status, .leak-reason, .leak-seats { margin: 0; overflow-wrap: anywhere; }

.leak-basis { margin: 0; }
.leak-basis > summary {
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary);
}
.leak-basis-list { margin: var(--space-3) 0 0; display: flex; flex-direction: column; gap: var(--space-1); }
.leak-basis-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: 0.85rem;
  min-width: 0;
}
.leak-basis-row dt { color: var(--text-tertiary); min-width: 0; }
.leak-basis-row dd { margin: 0; color: var(--text-primary); min-width: 0; overflow-wrap: anywhere; text-align: right; }
.leak-citations { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin: var(--space-3) 0 0; }

.leak-row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
  min-width: 0;
}
.leak-inline-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-2);
  min-width: 0;
}
.leak-inline-form .field { margin: 0; min-width: 0; flex: 1 1 180px; }

.leak-gap-list { margin: 0; padding: var(--space-4) var(--space-5); display: flex; flex-direction: column; gap: var(--space-2); list-style: none; }
.leak-gap { font-size: 0.9rem; color: var(--text-secondary); line-height: 1.6; overflow-wrap: anywhere; }
.leak-gap-fixed { border-top: 1px solid var(--border-divider); }
.leak-gap-fixed > .panel-label { padding: var(--space-4) var(--space-5) 0; margin: 0; }

.leak-upload-form { display: flex; flex-direction: column; gap: var(--space-3); max-width: 480px; }
.leak-upload-result { margin-bottom: var(--space-4); }
.leak-rejected { margin: var(--space-3) 0 0; padding-left: var(--space-5); display: flex; flex-direction: column; gap: var(--space-2); }
.leak-rejected li { font-size: 0.85rem; color: var(--text-secondary); line-height: 1.6; overflow-wrap: anywhere; }
.leak-upload-history { border-top: 1px solid var(--border-divider); padding: var(--space-3) var(--space-5); }
.leak-upload-reason { margin: 0 0 var(--space-2); overflow-wrap: anywhere; }

.leak-vendor-list { display: flex; flex-direction: column; }
.leak-vendor {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
  min-width: 0;
}
.leak-vendor:last-child { border-bottom: 0; }
.leak-vendor-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-3); min-width: 0; }
.leak-vendor-name { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }

.leak-source-list { display: flex; flex-direction: column; }
.leak-source {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-divider);
  min-width: 0;
}
.leak-source:last-child { border-bottom: 0; }
.leak-source-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); min-width: 0; }
.leak-source-name { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }

.aws-policy {
  margin: var(--space-3) 0;
  padding: var(--space-4);
  background: var(--raised-bg);
  border-radius: var(--radius-md);
  font-size: 0.78rem;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Today's rail card */
.leak-card-figure {
  margin: 0;
  padding: var(--space-4) var(--space-5) 0;
  font-family: var(--font-heading);
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}
.leak-card-figure span { font-family: var(--font-body); font-size: 0.8rem; font-weight: 500; color: var(--text-tertiary); }
.leak-card-list { padding: 0 var(--space-5) var(--space-3); display: flex; flex-direction: column; }

@media (max-width: 768px) {
  .leak-totals { flex-direction: column; }
  .leak-row-head, .leak-vendor-head, .leak-source-head { flex-direction: column; align-items: flex-start; }
  .leak-basis-row { flex-direction: column; gap: var(--space-1); }
  .leak-basis-row dd { text-align: left; }
  .leak-inline-form { flex-direction: column; align-items: stretch; }
  .leak-inline-form .field { flex: 1 1 auto; }
}

/* The page title collapsed to one word per line on a phone. .page-header is a wrapping
   flex row whose text child is `flex: 1 1 0` beside `flex-shrink: 0` actions, so when
   the actions take their natural width the title SHRINKS to whatever is left instead of
   wrapping onto its own line -- measured 75px on Updates, 106px on Warning detail at
   390px. Nothing overflows, so no overflow check could catch it; it is the cross-axis
   trap CLAUDE.md warns about, in its wrapping form. Giving the text a full-width basis
   below the breakpoint makes it take its own line, which is what wrap was there for. */
@media (max-width: 640px) {
  .page-header { gap: var(--space-3); }
  .page-header-text { flex: 1 1 100%; }
  .page-header-actions { flex: 1 1 100%; }
}

/* Three collisions from a real org, all the same shape: a long unbroken token cannot
   wrap, so it overflows its own box and is painted under the element beside it. None
   of them register as horizontal overflow -- the row clips, or the glyphs simply sit
   on top of a sibling -- which is why every "zero overflow" check passed while the
   screen looked broken. */

/* 1. The why-chain row is a grid whose text column is minmax(0,1fr) beside an `auto`
   source chip. min-width:0 lets the column shrink, but without a wrap rule the TEXT
   still cannot break, so "nousonworktechnologies/KredMe" ran under the GITHUB badge. */
.why-text { overflow-wrap: anywhere; }

/* 2. "Your company's normal" rows: the empty-state sentence ("Connect PagerDuty to count
   these") is the widest thing in the right-hand column and pushed past the card edge.
   The figure column may now shrink, and the sentence wraps. */
.normal-figure { min-width: 0; }
.normal-empty { overflow-wrap: anywhere; }

/* 3. The org card in the sidebar: a long workspace name had nothing to break on. */
.org-card-name,
.sidebar-org-name { overflow-wrap: anywhere; min-width: 0; }

/* "Your company's normal": the rows had `padding: var(--space-3) 0` -- vertical only --
   while .panel supplies no padding of its own and every other child brings its own
   var(--space-5) horizontally. So the figures sat flush against the card's border:
   "29 min" touched the right edge and "Connect PagerDuty to count these" ran straight
   through it, with the heading above correctly inset and the rows under it not.

   padding-left/right only, so the vertical rhythm the rows already had is untouched,
   and the border-bottom still spans the full card width -- the dividers are meant to
   be full-bleed; it is the CONTENT that needed insetting. */
.normal-row2 {
  padding-left: var(--space-5);
  padding-right: var(--space-5);
}

/* --- Ask as a conversation -------------------------------------------------------
   The turns before the one in focus. The screen had promised this for months --
   ask.html renders placeholder="Ask a follow-up..." under every answer -- while each
   question was actually planned from scratch and rendered on a page of its own.

   Styled as plainly as possible on purpose: a question, its answer, a way back to the
   full reasoning. No card, no chrome, no per-turn confidence badge. Ask is a place to
   read an answer, and the apparatus that proves the CURRENT answer is what the rest of
   the column is for. */
/* A turn is a pair of messages, not a document section: no divider, because the
   alignment already separates them. */
.ask-turn {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.ask-bubble {
  max-width: 80%;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  line-height: 1.6;
  /* A question or an answer can name a repo, so it can be one long unbreakable
     token -- see CLAUDE.md's note about identifiers from connected tools. */
  overflow-wrap: anywhere;
  min-width: 0;
}

/* Mine: ink, right-aligned. Ink rather than Signal blue deliberately -- blue is
   reserved for Soil's own intelligence, and the person's own words are not that. */
.ask-bubble-mine {
  align-self: flex-end;
  margin: 0;
  background: var(--action);
  /* var(--action-text), NOT a literal #FFFFFF. --action inverts in dark mode
     (#0e1116 -> #f2f4f7), so a hard-coded white here put white text on a near-white
     bubble -- invisible. --action-text is the token that inverts with it, and it
     exists for exactly this pairing. */
  color: var(--action-text);
  font-weight: 500;
  border-bottom-right-radius: var(--radius-sm);
}

.ask-bubble-theirs {
  align-self: flex-start;
  /* var(--card-bg): a literal #FFFFFF here was my own bug from the chat-bubble work,
     the same one .agent-list had -- unreadable the moment dark mode landed. */
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  color: var(--text-secondary);
  border-bottom-left-radius: var(--radius-sm);
}

.ask-bubble-theirs p:first-child { margin-top: 0; }
.ask-bubble-theirs p:last-child { margin-bottom: 0; }

/* The claim-admin button sits inside the banner sentence, so it needs to start a
   line rather than inherit the paragraph flow. */
.claim-admin-form { margin-top: var(--space-3); }

/* The row stays one line; the explanation is one click. Four stacked paragraphs of
   true, useful prose turned a table of six connections into a page of text, and the
   thing a person scans for -- which tool, how much, is it healthy -- was buried under
   the thing they read once. */
.conn-detail { margin-top: var(--space-2); }
.conn-detail summary {
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-tertiary);
  padding: var(--space-1) 0;
}
.conn-detail summary:hover { color: var(--text-primary); }
.conn-detail[open] summary { margin-bottom: var(--space-2); }
.conn-detail > *:not(summary) { display: block; margin-bottom: var(--space-2); }

/* The turn in focus carries its proof disclosure and, opened, a numbered source list.
   80% of the column is right for a conversational reply and too narrow for that, so
   the focused answer gets the full width while still reading as the same bubble. */
.ask-turn.is-focused .ask-bubble-theirs { max-width: 100%; }

/* The answer's own markup inside the bubble: it was styled as a card before, so it
   carried no spacing of its own. */
.ask-bubble-theirs .answer > :first-child { margin-top: 0; }
.ask-bubble-theirs .answer > :last-child { margin-bottom: 0; }

@media (max-width: 1180px) {
  .ask-bubble { max-width: 92%; }
}

.ask-turn-q {
  margin: 0 0 var(--space-3);
  font-family: var(--font-heading);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-primary);
  /* A question can name a repo, so it can be one long unbreakable token. */
  overflow-wrap: anywhere;
}

.ask-turn-a {
  color: var(--text-secondary);
  line-height: 1.6;
  overflow-wrap: anywhere;
}

.ask-turn-a p:first-child { margin-top: 0; }
.ask-turn-a p:last-child { margin-bottom: 0; }

.ask-turn-link {
  display: inline-block;
  margin-top: var(--space-3);
  font-size: 0.82rem;
  font-weight: 600;
  /* Signal: this is a route into Soil's own reasoning, which is exactly what the
     colour is reserved for. */
  color: var(--accent-text);
  text-decoration: none;
}

.ask-turn-link:hover { color: var(--accent-hover); text-decoration: underline; }

/* --- Update draft editing (soil/web/templates/_investor_update_card.html) ---------
   The month's update is Soil's draft until someone edits the wording, at which point
   the document is partly theirs and says so. These are the editor's own classes; the
   textarea itself inherits the base input styling and focus ring above, so nothing
   here redeclares a border, a radius or a focus treatment. */

.iu-edit-row {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  flex-wrap: wrap;
  margin: var(--space-5) 0 0;
}

.iu-editor {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

/* `display: flex` above beats the UA stylesheet's `[hidden] { display: none }`, so
   without this the closed editor stays on screen -- which is how it first shipped:
   saving redirected to a page that still showed the form, as though nothing had been
   saved. Same trap for the edit row, which is a flex container too. */
.iu-editor[hidden],
.iu-edit-row[hidden] {
  display: none;
}

/* Why the words are editable and the numbers are not, said once, where someone is
   about to type. */
.iu-editor-note {
  margin: 0 0 var(--space-3);
}

.iu-field-label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-top: var(--space-3);
}

/* The "one per line" hint is guidance, not part of the field's name -- without this
   it inherits the label's uppercase and reads as "HIGHLIGHTS ONE PER LINE", one long
   shouted phrase instead of a label and an aside. */
.iu-field-label .note {
  text-transform: none;
  letter-spacing: 0;
  font-family: var(--font-body);
}

.iu-textarea {
  resize: vertical;
  line-height: 1.6;
  /* A pasted bullet can carry a long unbroken URL or product name from a connected
     tool; without this it pushes the card wider than the page at 390px. */
  overflow-wrap: anywhere;
  min-width: 0;
}

.iu-editor-actions {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-4);
  flex-wrap: wrap;
}

.iu-saved-note {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--success-bg);
  color: var(--success);
}

.iu-discard {
  margin-top: var(--space-4);
}

/* ---------------------------------------------------------------------------
   Motion. 150-250ms, and every rule here is inside a reduced-motion guard --
   the DEFAULT is no motion, and animation is added only for viewers who have
   not asked for less. Written that way round deliberately: the usual pattern
   (animate by default, disable under the query) leaves anything the query
   forgets still moving.

   Motion's job is to confirm an action or orient the eye. It never decorates,
   never blocks reading, and never moves something a person is trying to read.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  /* Cards settle in on load. `backwards` holds the start frame during the delay,
     so a card cannot flash at full opacity before its turn. */
  .panel-fade {
    animation: soil-fade-up 220ms ease-out backwards;
  }
  .panel-fade-1 { animation-delay: 0ms; }
  .panel-fade-2 { animation-delay: 60ms; }
  .panel-fade-3 { animation-delay: 120ms; }

  /* Approve -> the row acknowledges, then settles. */
  .row-settled {
    animation: soil-settle 240ms ease-out;
  }

  /* Today's "Needs you" row opening its own why-chain. It orients the eye to where
     the new content came from -- the row above it -- and nothing else. The animation
     is on the BODY, not the row, so the title the reader just clicked does not move
     under their cursor; and it ends, rather than running on the height, because
     animating height on a chain of unknown length is what makes a disclosure feel
     slow. */
  .attn-row[open] > .attention-why {
    animation: soil-fade-up 180ms ease-out;
  }
}

@keyframes soil-fade-up {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

@keyframes soil-settle {
  0%   { background: var(--success-bg); }
  100% { background: transparent; }
}

/* The count-up target. JS only animates when the viewer has not asked for reduced
   motion (see soil.js); the element always contains its real final value in the
   markup, so with JS off or motion reduced the number is simply correct and static.
   `tabular-nums` stops the width jittering while digits change. */
.count-up {
  font-variant-numeric: tabular-nums;
}

/* Empty-state line illustration: one per state, inline SVG, stroked with
   currentColor so it themes. Deliberately faint -- it frames the sentence, it is
   not the message. */
.empty-art {
  display: block;
  margin: 0 auto var(--space-4);
  color: var(--border-input);
}

/* Theme switch: a segmented control -- a recessed track with the chosen option raised
   out of it. Real <button>s rather than a styled checkbox, so it is keyboard-reachable
   and announces its pressed state (soil.js sets aria-pressed AND .is-chosen).
   TOKENS ONLY: this is the one control guaranteed to be seen in both themes, so a
   literal hex here would be the exact bug that shipped .ask-bubble-mine as white text
   on a near-white bubble.
   The selection is never colour alone -- it is a raised fill, a border, a shadow and a
   stronger text colour, on top of aria-pressed for assistive tech. Deliberately NOT
   --accent: Signal blue is Soil's own intelligence, and a blue button is a defect. */
.theme-switch {
  display: inline-flex;
  gap: var(--space-1);
  padding: var(--space-1);
  background: var(--raised-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
}
.theme-switch-opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 32px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.theme-switch-opt:hover { color: var(--text-primary); }
.theme-switch-opt:focus-visible { box-shadow: var(--focus-ring-shadow); outline: none; }
.theme-switch-opt.is-chosen {
  background: var(--card-bg);
  border-color: var(--border-input);
  box-shadow: var(--shadow-1);
  color: var(--text-primary);
}

/* 44px minimum touch target on a phone -- the Settings copy is the one a thumb uses,
   since the top-bar copy is hidden below 900px. */
@media (max-width: 900px) {
  .theme-switch-opt { width: 48px; height: 44px; }
}

/* The top bar already sheds .topbar-kbd and .topbar-sync's text at 900px to fit a
   phone, and GAINS .topbar-logout there. Three more touch targets do not fit; Settings
   keeps the switcher, which is where a per-device preference is set. */
.theme-switch-compact { flex-shrink: 0; }


/* The Manage panel's "where this list came from" line. A link sitting in a note, so
   it needs the note's muted size without the note's muted colour on the link itself --
   otherwise "Refresh list" reads as more grey text rather than something to click. */
.manage-age {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: baseline;
  margin-top: var(--space-3);
}
.manage-age a {
  color: var(--text-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Onboarding top bar: the theme switch and the demo escape hatch share the end slot.
   The bar is a 3-column grid (brand / steps / end), so these two need their own row
   wrapper or the second one would claim a fourth column and push the steps off centre. */
.onb-topbar-end {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  justify-content: flex-end;
  min-width: 0;
}

/* ============================================================================
   Connections, Step 0 of docs/SOIL_V1.md: one clear state per connector, the
   four plain facts on every card, the "data synced before picking" question,
   and a searchable picker that is part of connecting.

   Tokens only -- no raw px, no literal colour. Every rule here is layout or a
   token reference, so both themes come out of the same declarations.
   ============================================================================ */

/* --- the connected row's labelled facts ----------------------------------- */

/* The state sentence, written by the branch that chose the state
   (store.connections_table_for_org). It is the first thing in the column on
   purpose: it is the one line that reconciles everything under it. */
.conn-reason {
  font-size: 0.9rem;
  color: var(--text-primary);
  line-height: 1.45;
  overflow-wrap: anywhere;
  min-width: 0;
}

/* Reads / Can do / Last sync / Connected by. A definition list because that is what it
   is -- labelled facts, not grey sentences a person has to decode. */
.conn-facts {
  /* Spans the whole row (see connections.html). Inside the coverage column these
     three facts were squeezed into a track the open Manage panel narrows further --
     "Can do" wrapped to eight lines and the Slack row grew past 400px tall. */
  grid-column: 1 / -1;
  display: grid;
  /* Label column fixed, so Reads / Can do / Last sync / Connected by line up down the
     card and the sentence beside them gets every remaining pixel. `auto` sized it to
     the widest label per row, which is why nothing aligned between cards.
     6.5rem, not the original 5.5rem: `.conn-facts dt` is `white-space: nowrap` inside a
     FIXED track, so a label wider than the track overflows the card rather than
     wrapping -- and "CONNECTED BY" at 0.68rem mono with 0.04em tracking measures just
     over 5.5rem. Widening the fixed track costs nothing; the second one is
     minmax(0, 1fr) and absorbs it. */
  grid-template-columns: 6.5rem minmax(0, 1fr);
  column-gap: var(--space-3);
  row-gap: var(--space-1);
  margin: var(--space-2) 0 0;
  font-size: 0.82rem;
  line-height: 1.45;
  min-width: 0;
}
.conn-facts dt {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  white-space: nowrap;
}
.conn-facts dd {
  margin: 0;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
  min-width: 0;
}
.conn-next {
  grid-column: 1 / -1;
  margin-top: var(--space-2);
  font-size: 0.86rem;
  color: var(--text-secondary);
  line-height: 1.45;
  overflow-wrap: anywhere;
  min-width: 0;
}
.conn-next strong { color: var(--text-primary); }

/* --- data synced before picking ------------------------------------------- */

/* Soil is holding rows nobody confirmed it should keep. The question and both
   answers sit together, because splitting them is how the old screen ended up
   showing the count in one column and "nothing picked" in the next. */
.conn-orphan {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--raised-bg);
  min-width: 0;
}
.conn-orphan-q {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.45;
  overflow-wrap: anywhere;
}
.conn-orphan-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}
.conn-orphan-actions form { margin: 0; }

/* --- the picker that is part of connecting -------------------------------- */

.picker-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}
.picker-search { flex: 1 1 10rem; min-width: 0; display: block; }
.picker-search input {
  width: 100%;
  min-width: 0;
  padding: var(--space-3);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.95rem;
}
.manage-search { margin-bottom: var(--space-3); }

.picker-rows {
  display: flex;
  flex-direction: column;
  /* --space-3, not --space-2: each row is a bordered card about 70px tall, and 8px
     between cards that size reads as "touching" rather than "a list" -- the rows ran
     into one another on the Slack channel picker. 12px is the smallest step that
     separates them without turning a 6-item list into a scroll. */
  /* --space-4. This went 8px -> 12px once and the rows still read as touching, which
     is the useful part: the gap was never the whole problem. A SELECTED row carries a
     blue border AND a blue fill, so six consecutive selected folders merge into one
     striped block however much air is between them. 16px is enough that each card reads
     as its own object, and the fill is now a token rather than four hand-written rgba
     literals, so it can be lightened without hunting hex. */
  gap: var(--space-4);
  max-height: 26rem;
  overflow-y: auto;
  /* Room for a focus ring on the first and last row inside a scroll container,
     which would otherwise be clipped by overflow-y. */
  padding: var(--space-1);
}
/* Three children now (the box, the name plus its activity line, the badge), so
   the row stops centring and starts at the top like every other list in the
   product. Scoped to .picker-rows: the Slack consent rows below the list are
   still single-line .picker-row labels and must stay centred. */
.picker-rows .picker-row {
  align-items: flex-start;
  justify-content: space-between;
}
.picker-item-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  flex: 1 1 auto;
}
.picker-item-name { font-weight: 600; overflow-wrap: anywhere; }
.picker-item-text .note { margin: 0; font-size: 0.78rem; }
/* Hidden until soil/web/static/picker.js has something to say. */
.picker-empty { display: none; margin: var(--space-3) var(--space-1) 0; }
.picker-count { margin: var(--space-3) 0 0; }
.picker-subhead { font-size: 1.05rem; margin: var(--space-6) 0 var(--space-2); }
.picker-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  margin-top: var(--space-5);
}
.picker-note { margin: var(--space-3) 0 0; }
.picker-age { margin: var(--space-4) 0 0; }

@media (max-width: 760px) {
  /* A roomier tap target on a phone, and the labels stack above their values
     rather than fighting a 390px row for a column each. Keep / Remove data are
     left to wrap on their own: .conn-orphan-actions already wraps, and both fit
     side by side at 390px, so forcing them full-width would be a rule that says
     something the screen does not do. */
  .picker-search input { padding: var(--space-3) var(--space-4); }
  .conn-facts { grid-template-columns: minmax(0, 1fr); row-gap: 0; }
  .conn-facts dt { margin-top: var(--space-2); }
}

/* ===================================================================
   SPACING FIXES, 2026-10-05 — reported as "things overlap" and
   "equal spacing between each element"
   =================================================================== */

/* 1. A toast used INLINE in the page flow had no space under it, so the heading
      that followed sat directly against its border. `.toast` was written for
      `.toast-region`, a fixed overlay that spaces its children with `gap` — in the
      flow there is no gap to inherit. The region keeps its own spacing by zeroing
      this again, so the two uses cannot fight. */
.toast { margin-bottom: var(--space-5); }
.toast-region > .toast { margin-bottom: 0; }

/* 2. The warning page's cards inset their HEAD by --space-5 and left every body
      element at padding 0, so the heading sat 24px in while the paragraph under it
      ran flush to the card edge — and past it on the right, which is what made the
      text look like it was escaping the card. Padding the body rather than moving
      the head's, because `.home-card-head` is shared with cards that are laid out
      differently. */
.why-card > :not(.home-card-head),
.tried-card > :not(.home-card-head),
.fix-card > :not(.home-card-head),
.sources-card > :not(.home-card-head) {
  padding-left: var(--space-5);
  padding-right: var(--space-5);
}
.why-card > :last-child,
.tried-card > :last-child,
.fix-card > :last-child,
.sources-card > :last-child {
  padding-bottom: var(--space-5);
}

/* 3. Long identifiers from a connected tool wrap instead of forcing the card wider.
      CLAUDE.md names this one: anything rendering a name from a connected tool needs
      `overflow-wrap: anywhere` AND `min-width: 0`, not just the flex fix. */
.spof-items a { display: block; min-width: 0; overflow-wrap: anywhere; }

/* ===========================================================================
   CONNECTIONS: the four things the owner's own screenshot got wrong.

   Every rule here exists because two facts about ONE connection were drawn
   independently and contradicted each other on the page. See connections.html and
   store.connections_table_for_org for the full account of each.
   =========================================================================== */

/* 1. The coverage bar's FILL carries the row's state. A solid full bar is the only
      visual on the row, and it read "finished" beside "Needs attention" because it
      had exactly one colour whatever the state was. The tone comes from
      store.connection_coverage, which reads it off `health_state` -- so the bar
      cannot disagree with the chip two columns to its right. Colour never carries
      it alone: the line above the bar says "with data" or "had data at the last
      sync" in words, which is the fact. */
.conn-bar-on-track { background: var(--action); }
.conn-bar-at-risk  { background: var(--warning); }
.conn-bar-blocked  { background: var(--danger); }
.conn-bar-neutral  { background: var(--text-tertiary); }

/* 2. WHAT is wrong, under WHICH state it is in. "Needs attention" is one label over
      five problems; this is the word that tells them apart without expanding
      anything. Mono and small, because it is a classification, not a sentence. */
.conn-state { display: flex; flex-direction: column; gap: var(--space-1); align-items: flex-start; }
.conn-problem {
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
  min-width: 0;
}

/* 3. Read-only or not, in one word, at the front of "Can do". Nine connectors cannot
      write at all and two can; that was four sentences of prose. */
/*    The reconnect control, beside the sentence that asks for it. On its own line
      because it is an ACTION sitting inside a definition list of facts -- inline it
      reads as part of the sentence, and a control that looks like prose does not get
      pressed. Tokens only; `btn` carries its own 44px target. */
.conn-write-fix {
  display: block;
  margin-top: var(--space-2);
}

.conn-write {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0 var(--space-1);
  margin-right: var(--space-2);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
/*    Ink for "can write", NOT amber: writing is not a warning and read-only is not a
      success -- Strata has a colour for "the answer" and one for "acts on it", and a
      connector that can act on another tool is the second. Read-only is the default
      and gets the quiet surface, so the eye lands on the two that are different. */
.conn-write-no  { background: var(--sidebar-bg); color: var(--text-secondary); }
.conn-write-yes { background: var(--action); color: var(--action-text); }

/* 3a. "Connected by you" / "Connected by Priya" / "Connected for the whole org"
       (docs/SOIL_CHIEF.md Step 1). Primary ink, because whose Google account Soil is
       reading through is a fact of the same weight as what it reads -- the consequence
       sentence beside it stays `.note`. No colour and no chip: there is nothing good or
       bad about an answer here, and Strata keeps colour for "the answer" and "acts on
       it". */
.conn-owner {
  color: var(--text-primary);
  font-weight: 500;
}
/*     The claim control sits on its own line under the sentence, never inline with it:
       it is a 44px-minimum touch target (see the touch-target pass at the end of this
       file) and a button that tall inside a 0.82rem sentence drags the baseline of
       every fact under it. */
.conn-owner-claim {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
  /* A long caveat beside a control is the classic overflow trap on this card: the
     sentence must be allowed to wrap and to shrink below its content width. */
  min-width: 0;
}
.conn-owner-claim > .note {
  flex: 1 1 14rem;
  min-width: 0;
}

/* 3b. "Soil holds 1 message \u00b7 1 channel" -- the label is what keeps this from
       reading as a second, disagreeing version of the coverage line above it. */
.conn-holds-label {
  font-family: var(--font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-right: var(--space-1);
}

/* 4. The fix strip: the one next step and the one control that carries it out, on one
      line at the foot of the row. The sentence used to sit here and its button two
      columns up and to the right, which is how "Next: Start a sync" came to be offered
      under a state saying the login was rejected. A row with a real problem gets a
      tinted left edge so the strip reads as the thing to act on rather than a
      footnote -- the tone repeats the state, which is already in words above it. */
.conn-next {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.conn-next-text { min-width: 0; overflow-wrap: anywhere; flex: 1 1 14rem; }
.conn-next-do { flex: none; }
.conn-next-at-risk, .conn-next-blocked, .conn-next-neutral {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  border-left: 2px solid var(--border-card);
}
.conn-next-at-risk { background: var(--warning-bg); border-left-color: var(--warning); }
.conn-next-blocked { background: var(--danger-bg); border-left-color: var(--danger); }
.conn-next-neutral { background: var(--sidebar-bg); }
.conn-next-at-risk .conn-next-text, .conn-next-at-risk strong { color: var(--warning); }
.conn-next-blocked .conn-next-text, .conn-next-blocked strong { color: var(--danger); }

/* 5. The Manage panel's error and the control it asks for, in one block. The sentence
      said "Reconnect the tool and it will come back" and the panel held nothing to
      click. */
.manage-error { display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }
.manage-error-text { min-width: 0; overflow-wrap: anywhere; }
.manage-error .btn { white-space: normal; text-align: center; }

/* At 390 the row is already one column (the 760px rule above). The fix strip stacks
      too, and its control goes full width so it is a real 44px target rather than a
      pill squeezed beside a sentence. */
@media (max-width: 760px) {
  .conn-next { flex-direction: column; align-items: stretch; gap: var(--space-3); }
  /* `flex-basis` is measured along the MAIN axis, so the 14rem that keeps the sentence
     from being squeezed beside the button at 1440 becomes a 224px MINIMUM HEIGHT the
     moment the strip flips to a column -- 240px of strip around three lines of text,
     measured, on every broken row at 390. The one trap CLAUDE.md names for flex
     direction changes, one axis over. */
  .conn-next-text { flex: 0 1 auto; }
  .conn-next-do, .conn-next-do .btn, .conn-next-do form { width: 100%; }
  .manage-error .btn { width: 100%; }
}

/* ===========================================================================
   REDUCED MOTION, FOR THE 19 TRANSITIONS THE ANIMATION GUARDS NEVER COVERED.

   CLAUDE.md's design system says motion is "always inside
   @media (prefers-reduced-motion: reduce) guards". Four blocks in this file do
   exactly that -- the thinking dots, the skeleton sheen, onboarding's
   indeterminate bar, and the whole MOTION section, which is written the safe way
   round (`no-preference` ADDS motion rather than `reduce` removing it).

   Every one of them guards an `animation`. Not one guarded a `transition`, and
   there are 19 of those, including `.btn`'s `transform 0.15s` paired with
   `.btn:active { transform: scale(0.97) }`. So a viewer who had asked their
   operating system for less motion still got every hover lift, every button
   squash, every chip slide. They are short and well-mannered -- which is exactly
   why nobody noticed the guard was missing.

   The duration is 0.01ms rather than `none`: a zero-duration transition still
   FIRES its transitionend event, and soil.js's busy-button restore and the
   connected toast's `.is-leaving` teardown both listen for one. `none` would
   leave a toast on screen for ever for precisely the people who asked for calm.

   `animation-duration` is here too as a backstop for any animation added later
   without its own guard; the four blocks above still set `animation: none` and
   their own static end state, and `animation: none` clears the NAME, so this
   cannot resurrect one.

   `scroll-behavior` because a smooth scroll is motion a person did not ask for,
   and `*` because the rule's whole point is to cover what the author forgot.
   `!important` for the same reason -- a later `transition` declaration anywhere
   in this file, or on any element's style attribute, must not win.
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0s !important;
  }
  html { scroll-behavior: auto !important; }
}



/* ---------------------------------------------------------------------------
   Schedule (docs/SOIL_CHIEF.md Step 4, soil/web/templates/schedule.html)

   Named component classes, tokens only, no literal colour -- so the whole screen
   themes from the same :root as everything else and dark mode needs no second rule.
   Deliberately no shadow beyond the .card default: CLAUDE.md's three elevation levels
   are card / popover / modal, and a slot is a card.

   PLACED BEFORE the phone-touch-target pass below, not after it, because that block is
   at the end of the file on purpose. Nothing here targets a selector it sets.

   TWO THINGS HERE ARE OVERFLOW FIXES RATHER THAN DECORATION, and both are the trap
   CLAUDE.md names: every element that renders an email address, a meeting title or a
   zone name gets `overflow-wrap: anywhere` AND `min-width: 0`, because a flex item's
   min-width defaults to auto and a 60-character address out of a connected tool would
   otherwise push the page sideways at 390px. Every grid track is `minmax(0, 1fr)` for
   the same reason -- a bare `1fr` floors at its content.
   --------------------------------------------------------------------------- */
.schedule-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 340px);
  gap: var(--space-5);
  align-items: start;
}

.schedule-main,
.schedule-side {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}

.schedule-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

.schedule-card-title {
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 700;
  margin: 0;
}

.schedule-form-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-4);
}

.schedule-guest-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.schedule-guest {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--border-divider);
}

.schedule-guest:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.schedule-guest-email {
  color: var(--text-primary);
  overflow-wrap: anywhere;
  min-width: 0;
}

/* A refused name is quieter by TOKEN, never by opacity: `--text-tertiary` at full
   strength measures about 6:1, and the same words at opacity .75 measure 3.41:1 --
   CLAUDE.md's third dark-mode pattern, and the one the browser audit actually caught. */
.schedule-guest-skipped .schedule-guest-email {
  color: var(--text-tertiary);
}

.schedule-slots {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.schedule-slot {
  background: var(--card-bg);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  min-width: 0;
}

/* The earliest slot is the one Soil is recommending, so it carries the solid-ink
   emphasis the design system reserves for the single most urgent item in a sorted
   list -- a 2px left edge, never a fill behind the text. */
.schedule-slot-first {
  border-left: 2px solid var(--action);
}

.schedule-slot-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

.schedule-slot-when {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.schedule-slot-zone {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
}

.schedule-slot-time {
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
  min-width: 0;
}

/* The org's own zone leads, so it is the larger inked line; every other zone is the
   same instant restated and reads as supporting text. */
.schedule-slot-zone-primary .schedule-slot-time {
  font-size: 1.2rem;
  color: var(--text-primary);
}

.schedule-agenda {
  font-family: var(--font-body);
  resize: vertical;
}

.schedule-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
  /* A checkbox and its words are one control, and on a phone the whole row has to be
     tappable. `min-height` works here because flex makes this a block-level box -- on a
     non-replaced INLINE box it does nothing, which is what made the nineteen-selector
     `nav a` pass a silent no-op. */
  min-height: 44px;
  padding: var(--space-2) 0;
  color: var(--text-secondary);
}

.schedule-check input {
  width: auto;
  margin-top: var(--space-1);
  flex-shrink: 0;
}

.schedule-slot-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  min-width: 0;
}

.schedule-own-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.schedule-own {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--border-divider);
}

.schedule-own:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.schedule-own-title {
  font-weight: 600;
  color: var(--text-primary);
  overflow-wrap: anywhere;
  min-width: 0;
}

.schedule-own-actions {
  margin: 0;
}

@media (max-width: 1180px) {
  /* The side rail stops being a rail and becomes the section below. 1180px is this
     stylesheet's existing viewport breakpoint and is NOT --page-max. */
  .schedule-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 768px) {
  .schedule-form-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .schedule-slot {
    padding: var(--space-4);
  }
}


/* ===========================================================================
   PHONE TOUCH TARGETS, SECOND PASS -- and this block is at the END of the file
   for a reason.

   The first pass lives in the `@media (max-width: 900px)` block around line 3620
   and was written from a hand audit. scripts/ui_audit.py MEASURES the rendered box
   at 390px instead of reading the rules, and found seven more discrete controls
   below CLAUDE.md's 44px floor, plus three that fail on WIDTH rather than height --
   an axis the first pass never set.

   WHY THE END OF THE FILE. The first attempt at this fix sat inside that same
   900px block, and it did nothing: `.seg-tab { min-height: 32px }` is declared at
   line ~5475, AFTER line 3620, and at equal specificity the later declaration wins.
   A media query does not raise specificity. So a rule that overrides a base
   declaration has to come after it, and the only position that is true for every
   base declaration in a 9,700-line stylesheet is the bottom.

   WHAT IS AND IS NOT IN HERE. Every selector below is a control in its own right --
   a tab, a close glyph, a row's own title link, a card header's link, a back arrow.
   A link sitting inside a sentence is NOT here and is not a defect: growing it to
   44px pushes the sentence apart, which is why WCAG 2.5.8 excepts inline text
   links. `scripts/ui_audit.py` reports those separately, under "Noted", so the
   number stays visible rather than being quietly dropped.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   Meeting prep (docs/SOIL_CHIEF.md Step 3, soil/web/templates/meetings.html)

   The page reuses Today's one-wide-list shell wholesale -- .attention-panel,
   .attention-list, .attention-pick-row, .attn-row/.attn-head and .attention-why
   are all the same rows, and the facts chain is literally .why-chain/.why-step.
   What is below is only the four things that list does not already have: the
   attendee block, the talking points, this page's own note paragraph, and the
   "last time" line.

   Every value is a token. The only colour here is --text-tertiary and --action,
   both of which theme; nothing on this page is blue, because nothing on it is
   Soil's own intelligence except the inline citation markers, which already own
   that colour through the shared .citation component.
   --------------------------------------------------------------------------- */
.prep-people {
  list-style: none;
  margin: 0 0 var(--space-4);
  padding: 0;
  display: grid;
  /* minmax(0, ...) and not a bare 240px: every value in a row -- a name, an
     address, a company -- comes from a connected tool, so the track has to be
     allowed to shrink below its content or one long address widens the page.
     The same reason .why-step uses minmax(0, 1fr) for its text column. */
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: var(--space-3);
}

.prep-person {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: var(--space-3);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-md);
  background: var(--card-bg);
}

.prep-person-name {
  font-weight: 600;
  font-size: 0.95rem;
  overflow-wrap: anywhere;
}

.prep-person-mail {
  font-size: 0.7rem;
  color: var(--text-tertiary);
  overflow-wrap: anywhere;
}

.prep-person-company {
  font-size: 0.8rem;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.prep-points {
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.prep-point {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--sidebar-bg);
  font-size: 0.95rem;
  /* The ink edge, not a fill and not a colour: a talking point is the one thing
     on this row somebody acts on, and Strata gives that emphasis in ink. */
  border-left: 2px solid var(--action);
}

.prep-point-text {
  flex: 1 1 auto;
  min-width: 0;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.prep-point-src {
  font-size: 0.68rem;
  flex: 0 0 auto;
}

.prep-note {
  margin: var(--space-2) 0 var(--space-4);
}

.prep-note:last-child { margin-bottom: 0; }

.prep-lasttime {
  margin: var(--space-2) 0 var(--space-3);
  font-size: 0.95rem;
  overflow-wrap: anywhere;
}

.prep-nothing { font-weight: 500; }



/* ===========================================================================
   WAITING ON YOU (docs/SOIL_CHIEF.md Step 2), 2026-10-08 prose pass.

   Two rules, scoped to two new classes used on that screen only. Nothing a
   shared class already declares is touched: `.why-step`, `.why-chain`,
   `.attention-why` and `.card-head` are the Warning page's and Today's as
   well, and widening one of those to reach a row this pass never looked at
   is how a fix for one screen becomes a regression on another. Both rules
   are tokens only and both inherit their colours, so dark mode needs no
   second declaration.
   =========================================================================== */

/* The chain in a "Waiting on you" row has NO step-number column and no source
   chip: `why[0]` is printed in the row's head, so the chain starts at the
   second sentence and numbering it "2, 3" would be a count of something the
   reader cannot see, and every sentence in one chain shares the one source
   already named beside the title. That leaves dot + text.

   Two classes beats the one-class `.why-step { grid-template-columns: ... }`
   declarations at 4917, 5636, 6581, 9025 and 8715 on specificity, so this
   holds inside those media queries too without being repeated in each -- the
   ordering trap CLAUDE.md records about the touch-target pass (a media query
   does not raise specificity) cuts the other way here. */
.why-chain-bare .why-step { grid-template-columns: var(--space-4) minmax(0, 1fr); }

/* "Not here yet": the source is named, and the exact missing column is the
   `title` on it. `cursor: help` is the only affordance a title has, and it is
   the same one `.pick-team` and the disabled buttons on this page already
   rely on. No colour change and no underline: the row is prose, not a
   control, so it gets no focus ring and needs no 44px box. */
.nb-item { cursor: help; }
.nb-source { font-weight: 600; }

/* The row's one control, and nothing else. It used to sit in `.why-unblock`'s
   tinted band opposite a "Next step" sentence -- and the sentence read "Open it
   where it lives" above a button labelled "Open the ticket". With the sentence
   gone the band is a full-width tint behind one button, which is decoration;
   Strata's default separator is a hairline and its default emphasis is none. The
   two defensive declarations are kept verbatim from `.why-unblock`, because the
   button they protect is the same one: a long provider label ("Approve & send on
   Slack") wraps rather than forcing its container wider. */
.waiting-act { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.waiting-act > form { min-width: 0; }
.waiting-act .btn { white-space: normal; }

/* The NOT_BUILT list on Waiting on you. `summary` is the source name; the missing
   column is inside. See waiting.html for why this is a <details> and not a `title`:
   a title is hover-only and this is a phone screen. */
.nb-detail > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

.nb-detail > summary::-webkit-details-marker { display: none; }

.nb-detail > summary::after {
  content: "";
  width: 6px;
  height: 6px;
  margin-left: var(--space-2);
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
}

.nb-detail[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }

.nb-detail > p { margin-top: var(--space-2); }

/* --------------------------------------------------------------------------
   Connections, 2026-10-08: the two disclosures the word cut created, and the
   touch-target floor the existing one never had.

   This screen printed 1,168 words of visible text; the explanations that are read
   once per workspace (the permission mode's "why", the write-scope reason, the
   setup meter's denominator, the eight MCP tools) are now behind <details> on the
   thing they explain. See connections.html's head comment for the four moves.
   -------------------------------------------------------------------------- */

/* The permission-mode "why" nests INSIDE .conn-who, which is already a .card, so
   .scope-note's own border, padding and margins would draw a card inside a card.
   The class is kept (its summary chevron, focus ring and prose measure are all
   already defined there, and a second copy of them is how two disclosures on one
   page come to look different); only the standalone chrome is undone. */
.conn-who-more {
  border: 0;
  padding: 0;
  margin: 0;
  background: none;
  /* .conn-who is align-items: flex-start, which makes every child fit-content on
     the cross axis -- a disclosure that only reaches as far as its summary. */
  align-self: stretch;
}
.conn-who-more > summary { color: var(--text-secondary); }
.conn-who-more > p:first-of-type { margin-top: var(--space-2); }

/* A <summary> is a control, and this screen now draws up to four of them per
   connector row. `.conn-detail summary` was 0.82rem over var(--space-1) padding --
   about 25px tall -- so every one was under CLAUDE.md's 44px floor, including the
   one that shipped before this pass.

   Same shape `.nb-detail > summary` already uses on Waiting on you, and for the
   same two reasons: the default `list-item` marker cannot be sized, so the box has
   to stop being a list-item, which means supplying the chevron ourselves. 44px
   only where a finger is the pointer, so a desktop row keeps its rhythm. */
.conn-detail > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
}
.conn-detail > summary::-webkit-details-marker { display: none; }
.conn-detail > summary::after {
  content: "";
  width: 6px;
  height: 6px;
  margin-left: var(--space-2);
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
}
.conn-detail[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }
/* ===================================================================
   VIDEO-CALL PICKER on /schedule -- the owner's 2026-10-09 mockup.

   Four radios drawn as tiles inside one recessed tray: the chosen one
   is a raised white card, the rest sit flat on the tray. The control is
   a real radio group, so arrow keys, screen-reader grouping and
   no-JavaScript all come free; the <label> is the hit target, which is
   what makes the whole tile tappable rather than just the dot.

   `.conf-mark` is a LETTER TILE, not a logo file. docs/TODAY_DESIGN.md
   asks for each provider's official SVG in static/logos/ and says
   "letter tiles appear only when a logo file is missing" -- that
   directory does not exist yet, so this is the honest fallback rather
   than a guessed or recoloured mark, which every brand's rules forbid.
   docs/LATER.md carries the logo work.

   Tokens only, no literal colour, and no `opacity` on text: a disabled
   tile is quieted with --text-tertiary and a flat surface, because a
   correct token at reduced opacity is the 3.41:1 defect
   tests/test_screen_a11y_floors.py fails on.
   =================================================================== */

.schedule-conf {
  border: 0;
  padding: 0;
  margin: 0 0 var(--space-4);
  min-width: 0;
}

.conf-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-2);
  padding: var(--space-2);
  margin-top: var(--space-2);
  background: var(--page-bg);
  border: 1px solid var(--border-divider);
  border-radius: var(--radius-md);
}

.conf-tile {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  min-width: 0;
}

/* The radio itself is removed from view, never from the accessibility
   tree: the tile IS the control, and the focus ring below is driven by
   the real input so keyboard focus lands where it should. */
.conf-tile > input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.conf-tile:has(> input:checked) {
  background: var(--card-bg);
  border-color: var(--border-input);
  box-shadow: var(--shadow-1);
}

.conf-tile:has(> input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.conf-tile.is-off {
  cursor: not-allowed;
  color: var(--text-tertiary);
}

/* The mark beside a platform name. `app_logo()` renders either the vendor's
   own SVG (.app-logo) or the letter tile (.app-logo-letters) -- both are
   sized here so the row does not reflow the day a real logo lands. */
.conf-tile .app-logo { flex: 0 0 auto; }

.app-logo-letters {
  /* Three named sizes, no inline style -- CLAUDE.md closes the utility set and
     forbids one, and a component is the worst place to break that. */
  --logo-size: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--logo-size, 20px);
  height: var(--logo-size, 20px);
  border-radius: var(--radius-sm);
  background: var(--card-bg);
  border: 1px solid var(--border-divider);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  color: var(--text-secondary);
}

.conf-tile.is-off .app-logo-letters { color: var(--text-tertiary); }

.conf-name {
  font-size: 0.9rem;
  font-weight: 500;
  overflow-wrap: anywhere;
  min-width: 0;
}

/* ===================================================================
   MEETINGS (/org/{slug}/calendar) -- built to the owner's 2026-10-09
   reference: "This week" over a Mon-Fri grid, "Next up" beside it.

   TOKENS ONLY. The one thing that cannot be a token is a block's
   position in the day, which arrives as --slot-top / --slot-height,
   computed in soil/calendar_view.py. A meeting at 3pm is data.

   No literal hex, so the screen themes with the rest of the app; no
   partial opacity on text, because a correct token at 75% measures
   3.41:1 (tests/test_screen_a11y_floors.py).
   =================================================================== */

.cal-top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.cal-h1 {
  font-family: var(--font-heading);
  font-size: 2rem;
  line-height: 1.1;
  font-weight: 700;
  margin: 0;
}

.cal-sub {
  margin: var(--space-2) 0 0;
  font-size: 0.9rem;
  color: var(--text-tertiary);
}

.cal-top-right {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.cal-fresh {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 0.8rem;
  color: var(--text-secondary);
}

.cal-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--success);
  flex: 0 0 auto;
}

.cal-fresh-off .cal-dot { background: var(--warning); }

.cal-notice { margin: 0 0 var(--space-4); }

.cal-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 340px);
  gap: var(--space-5);
  align-items: start;
}

.cal-grid-wrap, .cal-side { min-width: 0; }

/* ----------------------------------------------------------- the week */

.cal-grid {
  display: grid;
  grid-template-columns: 3.5rem repeat(5, minmax(0, 1fr));
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
  overflow: hidden;
}

.cal-hours { display: flex; flex-direction: column; }

.cal-hours-pad { height: 3.25rem; flex: 0 0 auto; }

.cal-hour {
  flex: 1 1 0;
  min-height: 2.6rem;
  font-size: 0.68rem;
  color: var(--text-tertiary);
  padding-right: var(--space-2);
  text-align: right;
  transform: translateY(-0.5em);
}

.cal-hour:last-child { flex: 0 0 auto; min-height: 0; }

.cal-day {
  display: flex;
  flex-direction: column;
  border-left: 1px solid var(--border-divider);
  min-width: 0;
}

.cal-day.is-today { background: var(--page-bg); }

.cal-day-head {
  height: 3.25rem;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border-bottom: 1px solid var(--border-divider);
}

.cal-day-name { font-size: 0.8rem; color: var(--text-tertiary); }

.cal-day-num {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text-primary);
}

/* Today's number is an ink pill, as in the reference. */
.cal-day-num.is-today {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--action);
  color: var(--action-text);
  font-size: 0.9rem;
}

.cal-day-body {
  position: relative;
  flex: 1 1 auto;
  min-height: 23.4rem;
  display: flex;
  flex-direction: column;
}

.cal-rule { flex: 1 1 0; border-bottom: 1px solid var(--border-divider); }
.cal-rule:last-child { flex: 0 0 auto; border-bottom: 0; }

/* A block: rounded, tinted, with a solid left edge -- the reference's shape. */
.cal-slot {
  --slot-top: 0%;
  --slot-height: 6%;
  position: absolute;
  top: var(--slot-top);
  height: var(--slot-height);
  left: 3px;
  right: 3px;
  display: flex;
  flex-direction: column;
  gap: 0;
  overflow: hidden;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--tint-tickets-text);
  background: var(--tint-tickets-bg);
  color: var(--tint-tickets-text);
  text-align: left;
  min-height: 1.9rem;
}

/* A block is a LINK, and the app's base style underlines links inside prose.
   Here the whole tile is the control, so the underline is noise -- it made every
   meeting in the grid look like a hyperlink in a paragraph. */
.cal-slot, .cal-slot:hover, .cal-slot:focus { text-decoration: none; }

.cal-slot:hover { border-left-color: var(--text-primary); }

/* A 30-minute block is ~2.6rem tall and was clipping its second line mid-glyph.
   The title gets the room; the time drops out when there is genuinely no space,
   rather than both being half-rendered. */
.cal-slot-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.cal-slot-title {
  font-size: 0.76rem;
  font-weight: 600;
  line-height: 1.2;
  overflow-wrap: anywhere;
  min-width: 0;
}

.cal-slot-meta {
  font-size: 0.65rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-slot.is-now {
  background: var(--surface-ink);
  border-left-color: var(--surface-ink);
  color: var(--action-text);
}

.cal-slot.is-past {
  background: var(--page-bg);
  border-left-color: var(--border-input);
  color: var(--text-tertiary);
}

.cal-slot.is-selected { outline: 2px solid var(--accent); outline-offset: -2px; }

.cal-day-outside {
  margin: 0;
  padding: var(--space-1) var(--space-2);
  font-size: 0.66rem;
  border-top: 1px solid var(--border-divider);
}

.cal-grid-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin: var(--space-3) 0 0;
}

.cal-foot-sep { color: var(--text-tertiary); }

/* --------------------------------------------------------- next up */

.cal-next {
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
  padding: var(--space-5);
  box-shadow: var(--shadow-1);
}

.cal-next-label {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

.cal-next-title {
  font-family: var(--font-heading);
  font-size: 1.3rem;
  line-height: 1.25;
  font-weight: 700;
  margin: var(--space-1) 0 var(--space-2);
  overflow-wrap: anywhere;
  min-width: 0;
}

.cal-next-when {
  margin: 0 0 var(--space-4);
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

.cal-platform {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-top: 1px solid var(--border-divider);
  border-bottom: 1px solid var(--border-divider);
  min-width: 0;
}

.cal-platform-name {
  font-size: 0.9rem;
  font-weight: 500;
  flex: 1 1 auto;
  overflow-wrap: anywhere;
  min-width: 0;
}

.cal-platform-link {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 0.82rem;
  font-weight: 500;
}

.cal-side-label {
  margin: var(--space-4) 0 var(--space-2);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
}

.cal-people { list-style: none; margin: 0; padding: 0; }

.cal-person {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  min-width: 0;
}

.cal-avatar {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--raised-bg);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: 0.62rem;
}

.cal-person-text { display: flex; flex-direction: column; min-width: 0; }

.cal-person-name {
  font-size: 0.88rem;
  font-weight: 500;
  overflow-wrap: anywhere;
}

.cal-person-sub {
  font-size: 0.74rem;
  color: var(--text-tertiary);
  overflow-wrap: anywhere;
}

/* The reference's tinted "Soil got you ready" box. Accent TINT, not accent:
   Signal blue is Soil's own intelligence, and this is Soil's own reading --
   but it is a surface, so it takes the tint and keeps ink text. */
.cal-ready {
  margin: var(--space-4) 0 0;
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--accent-tint);
  border: 1px solid var(--accent-tint-border);
}

.cal-ready-head {
  margin: 0 0 var(--space-2);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--accent-text);
}

.cal-ready-line {
  margin: 0 0 var(--space-2);
  font-size: 0.85rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
  min-width: 0;
}

.cal-ready-line:last-child { margin-bottom: 0; }

.cal-ready-when { font-size: 0.72rem; }

.cal-ready-none { margin: var(--space-4) 0 0; }

.cal-next-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-4) 0 0;
}

.cal-blocked { margin: var(--space-3) 0 0; }
.cal-blocked > summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; }
.cal-blocked > p { margin-top: var(--space-2); }

.cal-from {
  margin: var(--space-3) 0 0;
  font-size: 0.74rem;
  color: var(--text-tertiary);
}

.cal-calendars {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-divider);
}

.cal-cal-list { list-style: none; margin: 0; padding: 0; }

.cal-cal-list li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  font-size: 0.84rem;
  overflow-wrap: anywhere;
  min-width: 0;
}

.cal-cal-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  flex: 0 0 auto;
}

.cal-next-empty { color: var(--text-secondary); }

@media (max-width: 1180px) {
  .cal-layout { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 768px) {
  /* The five-day grid does not fit a phone. It scrolls inside its own box on
     purpose -- a declared, labelled scroll region -- never the page scrolling
     sideways, which scripts/ui_audit.py measures and must stay at zero. */
  .cal-grid-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .cal-grid { min-width: 34rem; }
  .cal-top-right { width: 100%; }
}

.cal-sync-form { margin: 0; }

/* A calendar source that cannot be connected: named, quieted by TOKEN rather
   than by fading the words, and carrying its own reason. */
.cal-cal-off { color: var(--text-tertiary); }
.cal-cal-off .cal-cal-dot { background: var(--border-input); }

/* Soil's own app marks. NOT the vendors' logos and not imitations of them: an
   official SVG in static/logos/ wins whenever one exists (see app_logo()), and
   until then this is an honest mark rather than a near-copy that would look
   official while being wrong.

   The tints are the Strata source tints, so a mark and a source chip for the
   same tool agree. Both halves of each pair are declared tokens, which is what
   keeps the contrast correct in dark mode -- a tinted background with inherited
   ink text is the pattern that shipped at 1.1:1 four times. */
.app-logo-letters.tint-code         { background: var(--tint-code-bg);          color: var(--tint-code-text);          border-color: transparent; }
.app-logo-letters.tint-conversations{ background: var(--tint-conversations-bg); color: var(--tint-conversations-text); border-color: transparent; }
.app-logo-letters.tint-tickets      { background: var(--tint-tickets-bg);       color: var(--tint-tickets-text);       border-color: transparent; }
.app-logo-letters.tint-docs         { background: var(--tint-docs-bg);          color: var(--tint-docs-text);          border-color: transparent; }
.app-logo-letters.tint-people       { background: var(--tint-people-bg);        color: var(--tint-people-text);        border-color: transparent; }
.app-logo-letters.tint-finance      { background: var(--raised-bg);             color: var(--text-primary);            border-color: transparent; }

.app-logo-letters { font-weight: 600; letter-spacing: 0.01em; }

/* The closed size set for app_logo(). Three, not arbitrary px. */
.app-logo-16 { --logo-size: 16px; font-size: 0.5rem; }
.app-logo-20 { --logo-size: 20px; }
.app-logo-28 { --logo-size: 28px; font-size: 0.68rem; }

@media (max-width: 900px) {
  .conn-detail > summary,
  .conn-who-more > summary { min-height: 44px; }
}

@media (max-width: 900px) {
  .theme-switch-compact { display: none; }
}

@media (max-width: 900px) {
  /* NOT `.btn`, and that is worth saying because it was the first thing added here
     and it was wrong. The finding-H1 block at line ~3236 already gives `.btn` (and
     `button`, `select`, `input`, `nav a`, `li > a`, `summary`, ...) a 44px
     `min-height`, and it already carries the deliberate `p a, .lede a, .answer a
     { min-height: 0 }` reset so a link inside a sentence keeps its line box. The
     40px-tall links the audit reported inside `<p class="note">` are that reset
     working as designed on a two-line wrapped prose link -- not a button that lost
     its floor. Everything below is a control that block does not reach. */

  /* Height. */
  .seg-tab,
  .seg-tabs-sm .seg-tab,
  .work-title-link,
  .worklist-work-title a,
  .panel-head-link,
  .back-link,
  .why-full-link,
  .auth-brand {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  /* A tab whose label is one short word ("All", "3") is 41px and 20px wide. The
     label does not make the target; the box does. */
  .seg-tab,
  .legal-nav a { min-width: 44px; justify-content: center; }

  /* A LINK THAT IS THE ONLY THING IN ITS PARAGRAPH OR ROW is not an inline text
     link, whatever its `display` says -- there is no sentence around it to break.
     "Forgot your password?" on the login screen is one of these, and it measured
     107x18 at 390px: a primary control at 40% of the floor.

     The finding-H1 block's `p a { min-height: 0 }` reset is what let these through,
     and it is right about the case it was written for (three words inside a
     sentence). `:only-child` is the distinction it could not express, and it is a
     real selector rather than a heuristic: `p > a:only-child` scores (0,1,2) against
     that reset's (0,0,2), so it wins on specificity as well as on order. Note that
     `min-height` alone would do nothing here either way -- it does not apply to a
     non-replaced inline box, which is why `display: inline-flex` comes with it. */
  p > a:only-child,
  /* The right rail's two "go to the full screen" links -- Today's "All warnings →"
     beside the Track record heading, and the Updates rail's "Connect". Each sits
     beside an <h2> or a <span>, so neither is an `:only-child`, and neither is a word
     in a sentence either: they are the one action in their row. 82x16 and 53x18. */
  .rail-row > a,
  .rail-dark-head > a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  /* Width as well as height: a 32px-wide logo mark, a 20px-wide "3" tab and a 27px
     close glyph are each as hard to hit as a 44x20 box. `justify-content: center`
     keeps the mark where it was -- the box grows around it. */
  .auth-logo,
  .legal-logo,
  .op-tab,
  .manage-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    min-width: 44px;
  }

  /* ---------------------------------------------------------------------
     THIRD PASS, 2026-10-06: the 58 the second pass left, and the cascade
     the second pass said it did not understand.

     THE CASCADE, FINALLY READ. `.srow-form input, .srow-form select
     { min-height: 36px }` is declared further up this file and scores
     (0,1,1). The finding-H1 block's bare `select { min-height: 44px }`
     scores (0,0,1). So the 36px won on SPECIFICITY, not on order -- which
     is why moving a copy of the 44px rule to the end of the file (the fix
     that worked for `.seg-tab`) would have changed nothing here either,
     and why `select#rule-*` on Custom agents WAS 48px while the
     byte-identical rule left `select#merge-*` on Settings at 36px: only
     the merge/rename controls live inside a `.srow-form`. One group was
     inside a more specific rule and the other was not. The selectors below
     are (0,1,1) too and sit after it, so they win on order at equal
     specificity -- the ordinary way round, now that the specificity is
     level.

     This also covers `.srow-form input`, which the audit reported at the
     same 36px and which the second pass's itemisation left out. */
  .srow-form input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
  .srow-form select {
    min-height: 44px;
  }

  /* WIDTH on a select, which nothing had set. `select#rule-*` measured
     34x48: tall enough and barely a third as wide as the floor, because
     its three options are "Ask me" / "Never" / "Auto" and a `<select>`
     sizes itself to its widest option inside a table cell that was
     squeezing it. `min-width` rather than `width` so a select with long
     options is untouched. */
  select { min-width: 44px; }

  /* A CHECKBOX IN A TABLE CELL, with no `<label>` around it to grow.
     `table.rules-table > input` measured 16x16 -- the finding-H1 block's
     exemption ("the LABEL is the real target") does not apply, because
     there is no label: Custom agents' Locked column uses `aria-label` on
     the input itself, which names it for a screen reader and gives a thumb
     nothing.

     The TARGET is the cell, not the box. A `td` holding nothing but a
     checkbox becomes a 44px-tall flex cell that centres it, and the
     checkbox itself grows to 22px -- still a native checkbox, still the
     platform control, just not a 16px one on a phone. `:has()` keeps this
     off every other cell in the table; it is the same selector the
     finding-H1 block already relies on for labels, so it is not a new
     dependency.

     Why not 44px of checkbox: a 44px native tick box is not a design, it
     is a bug that passes a test. The pointer-event area is the cell. */
  .rules-table td:has(> input[type="checkbox"]) {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
  }
  /* 44px, and yes that is a large tick box. The first attempt made the CELL 44px and
     left the box at 22px, and the audit was right to keep failing it: the cell is not
     the target, because no `<label>` ties the two together -- Custom agents' Locked
     column names the input with `aria-label`, so a tap on the cell lands on nothing.
     soil.css's own exemption is written as "the LABEL around it is the real tap
     target", and here there is no label.

     The honest fix is markup (wrap it in a `<label>`, which also makes the word
     "Locked" clickable), and markup was not this pass's lane -- custom_agents.html is
     being worked in by another agent. So the box itself carries the floor, at phone
     width only. Recorded in docs/BUILD_LOG.md so the markup fix can shrink it back. */
  .rules-table td > input[type="checkbox"] { width: 44px; height: 44px; }

  /* THE WARNING WHY-CHAIN'S LINKS -- 32 of the 58, and the second pass
     called them "the hardest call in the set" and left them. Reading the
     markup settles it: warning_detail.html renders

         <span class="why-text"><a href=...>{{ step.text }}</a></span>

     so the anchor is the ONLY child and the step's whole sentence IS the
     link. There is no prose around it to push apart, which is the entire
     basis of WCAG 2.5.8's inline exception -- so the exception does not
     apply and these are controls. `:only-child` says exactly that, the
     same distinction `p > a:only-child` above draws, and it leaves
     backtest_report.html's `.why-text` alone, where the link really does
     sit in a sentence with text either side of it.

     `inline-flex` because `min-height` does nothing to a non-replaced
     inline box (the lesson from the second pass). An inline-flex box
     shrink-to-fits against its containing block, so a long step still
     wraps inside the box rather than widening the page -- measured, not
     assumed: `scripts/ui_audit.py` reports 0 horizontal overflow at 390px
     with this in. `.why-text` already carries `overflow-wrap: anywhere`;
     the anchor inherits it, and `min-width: 0` keeps it shrinkable. */
  .why-text > a:only-child {
    display: inline-flex;
    align-items: center;
    min-width: 0;
    min-height: 44px;
  }

  /* `p.m-0 > a` at 21px, and this one needed the markup read too.
     connections.html's Manage panel renders

         <p class="m-0"><a ...>Open the full Slack settings &rarr;</a>
           <span class="note">Everything this panel has, plus ...</span></p>

     -- the one action in the row, followed by a sentence EXPLAINING it, not
     a sentence containing it. So `p > a:only-child` cannot see it (there is
     a sibling) and the audit does not call it prose either (the siblings
     are elements, not words).

     `:only-of-type` was the first selector tried here and it is WRONG: it
     also matches a link sitting inside a real sentence that happens to hold
     exactly one link -- settings.html's "No AI app is connected yet.
     <a>Connections</a> has the server address and the setup steps." --
     where `inline-flex` would break the line the `p a { min-height: 0 }`
     reset exists to protect. It scores (0,1,2) against that reset's
     (0,0,2), so it would have won and nothing would have failed.

     `:has(+ .note)` is the shape itself: an action followed by its own
     explanation. A sentence never has a `.note` span as its link's next
     sibling. */
  p > a:first-child:has(+ .note) {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* ---------------------------------------------------------------------
     FOURTH PASS, 2026-10-06: the two targets MEASURED on the Meetings
     screen (docs/SOIL_CHIEF.md Step 3) at 390px, in both themes --
     185x40, 161x40 and 136x20.

     Both were invisible to `.why-text > a:only-child` and
     `p > a:first-child:has(+ .note)` above, for the same reason those two
     needed writing at all: a prep fact's link is followed by a SIBLING
     span -- the "matched to Nina" line that says why the fact is on that
     row -- so it is `:first-child` with a sibling, never an only child.
     `.prep-lasttime > a` is the same shape in a <p> that is not a `.note`.

     Scoped to `.prep-chain` and NOT to `.why-text` at large: that class is
     the shared why-chain on Today and on the Warning page, and widening a
     layout rule to reach rows this step never looked at is how a fix for
     one screen becomes a regression on another. */
  .prep-chain .why-text > a:first-child,
  .prep-lasttime > a:first-child {
    display: inline-flex;
    align-items: center;
    min-width: 0;
    min-height: 44px;
  }
}


/* The SVG charts' "Not enough data yet" placeholder.
 *
 * This was built as an inline `style=` attribute in soil/web/templates.py -- the last
 * one the app served. It is here rather than in soil.css because these charts are a
 * legacy-design surface; when they are converted this block goes with them.
 *
 * Only two sizes are ever asked for, so the dimensions are a modifier rather than a
 * computed value. `_chart_empty()` raises on anything else rather than rendering an
 * unsized box.
 */
/* `--muted` and `--border` DO NOT EXIST in either stylesheet. The inline style this
 * replaced named both of them, so the placeholder has been rendering with no colour
 * and no border since it was written -- an `var()` with no fallback and no definition
 * computes to nothing. Moving it into a sheet is what made the bug visible, because
 * tests/test_css_theming.py can read a stylesheet and could never read an attribute.
 * These are the tokens legacy.css actually declares. */
.chart-empty { display: flex; align-items: center; justify-content: center;
  color: var(--text-tertiary); font-size: .85em; border: 1px dashed var(--border-card);
  border-radius: var(--radius-sm); width: 100%; }
.chart-empty-560x140 { max-width: 560px; height: 140px; }
