/* ============================================================
   Self-hosted webfonts — the brand voice.
   • Hanken Grotesk (display/brand face — every heading + the wordmark): a humanist
     grotesk, warm but precise. Replaced Baloo 2 (a friendrot hand-me-down) 2026-07-07
     when Dustav got its own homepage voice.
   • JetBrains Mono (the mono micro-labels + glass-box readouts): clean, even, modern.
   Both are VARIABLE (one woff2 covers the whole weight axis) and self-hosted, so no
   third-party CDN sees a user on pageload and the CSP stays tight. Body copy stays
   system-ui. Hanken Grotesk: SIL OFL 1.1. JetBrains Mono: Apache License 2.0.
============================================================ */
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/fonts/hanken-grotesk-latin.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url("/fonts/jetbrains-mono-latin.woff2") format("woff2");
}

/* Icons — self-hosted inline SVG (lib/icons.js via the {{{icon}}} helper); size with
   font-size or width/height, color via currentColor. Replaced Font Awesome. */
.icon { display: inline-block; width: 1em; height: 1em; vertical-align: -0.125em; flex: none; }

/* ============================================================
   Design tokens — cool slate palette, light + dark.
   :root is the light theme (also the no-JS fallback); [data-theme="dark"]
   overrides. theme.js resolves "auto" → light|dark before paint, so the html
   element always carries a concrete data-theme. Components consume tokens only,
   never raw hex — the palette is the single knob.
   The neutrals are a cool slate grey; the brand accent is royal blue.
============================================================ */
:root {
  --bg: #f4f6f9;          /* cool slate off-white — calm, not clinical */
  --surface: #ffffff;     /* cards, bubbles, raised UI */
  --panel: var(--surface); /* legacy alias */
  --ink: #182130;         /* cool slate near-black text */
  --text: var(--ink);     /* legacy alias (inputs, chips) */
  --muted: #667085;       /* slate grey */
  --accent: #3457e0;      /* royal blue — the brand */
  --accent-ink: #ffffff;  /* text/icon on an accent fill */
  --border: #e3e8ef;      /* cool slate hairline */
  --error: #d64545;

  --font-display: "Hanken Grotesk", system-ui, sans-serif;
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* App-shell geometry — a messaging client. Desktop: a fixed left nav rail +
     a full-width pane. Mobile (< 880px): the rail is replaced by a fixed top bar
     + bottom tab bar. --content-max caps reading width WITHIN the pane (chat,
     prose) so full-width text stays legible. The 880px breakpoint is hardcoded in
     media queries (custom props can't be used in media conditions). */
  --rail-w: 248px;        /* desktop nav rail */
  --pub-head-h: 60px;     /* sticky public header height (.pub-head__inner min-height) — sticky
                             sidebars (docs/essays rail + TOC) offset by this so their pinned tops
                             clear the header instead of hiding behind it */
  --content-max: 760px;   /* readable cap inside the pane */
  --chat-max: 1080px;     /* wider cap for the chat thread (full-width, left-aligned msgs) */
  /* Mobile fixed top bar. The TOKEN carries the device's top safe-area inset (viewport-fit=cover makes it
     real on iOS — 0 everywhere else), so every consumer — the bar's own height AND all the content offsets
     that clear it — stays coherent without touching ~20 call sites. The bar pads its content below the inset. */
  --mtop-h: calc(54px + env(safe-area-inset-top, 0px));
  --mrail-w: 56px;        /* mobile always-visible icon-only rail (docs/NAV.md) — the pane insets left by it */
  /* The mobile bottom tab bar was retired 2026-08-03 (docs/NAV.md) for a left nav drawer. The token is
     kept at 0 — retained (not deleted) because ~15 mobile bottom-clearance calcs key off it
     (calc(--tabbar-h + safe + --copilot-peek + …)); zeroing here reclaims the bar's space everywhere at
     once, no per-rule edits. It now means "reserved bottom chrome" (nothing but the safe-area + copilot). */
  --tabbar-h: 0px;
  --copilot-peek: 48px;   /* mobile closed "Ask Dustav" bar height (now sits at the very bottom) — suite content clears it */
  /* The bottom-dock inset (2026-08-27, Curtis on the live iPhone: "it left a gap at the bottom… we also
     moved it up too much — space is precious"). Docked bars EXTEND to the physical bottom edge (their
     background fills the curve — never a lifted bar with a void beneath) and reserve only PART of the
     home-indicator inset for content: the indicator floats over chrome in every native app, so a
     full-inset reserve wastes a row of phone space. 0 everywhere without an inset (Android/desktop). */
  --dock-inset: max(0px, calc(env(safe-area-inset-bottom, 0px) - 12px));
}
[data-theme="dark"] {
  --bg: #10141c;          /* cool slate charcoal, not warm black */
  --surface: #191f2a;
  --ink: #e8ecf3;
  --muted: #8b95a7;
  --accent: #6b8aff;      /* brighter royal for dark contrast */
  --accent-ink: #ffffff;
  --border: #262d3a;
  --error: #ff6b6b;
}

/* ── Accent skins ("skins" in the UI) ────────────────────────────────────────
   The accent color is independent of light/dark — data-accent on <html> swaps
   the --accent token, mirroring how data-theme swaps the palette. Server-rendered
   before paint (layouts) so there's no flash; persisted on the User (POST /api/accent).
   "royal" is the default — it's the :root/[data-theme] values above. Every skin
   keeps white ink (--accent-ink stays #fff), at parity with royal, so the hardcoded
   white-on-accent fills (buttons, chips) stay legible. Each skin declares a LIGHT
   value and a (slightly brighter) DARK value; the two-attribute dark selector
   outranks the single-attribute [data-theme="dark"] base, so dark mode picks the
   dark value. The swatch list in lib/skins.js must stay in sync with the keys here
   (the route validates against it).

   Royal IS the default (the :root/[data-theme] values), but it gets an explicit
   block too so a royal SWATCH (a button carrying its own data-accent="royal") can
   paint itself from --accent regardless of the page's current skin. "blue" (Sky) is
   retired from the picker (too close to royal) but its block STAYS so any stored
   value still resolves + still gets a favicon; coral stays pickable. */
[data-accent="royal"]  { --accent: #3457e0; }
[data-theme="dark"][data-accent="royal"]  { --accent: #6b8aff; }
[data-accent="teal"]   { --accent: #14b8a6; }
[data-theme="dark"][data-accent="teal"]   { --accent: #2dd4bf; }
[data-accent="violet"] { --accent: #8b5cf6; }
[data-theme="dark"][data-accent="violet"] { --accent: #a78bfa; }
[data-accent="blue"]   { --accent: #3b82f6; }
[data-theme="dark"][data-accent="blue"]   { --accent: #5b9bff; }
[data-accent="amber"]  { --accent: #f59e0b; }
[data-theme="dark"][data-accent="amber"]  { --accent: #fbb024; }
[data-accent="rose"]   { --accent: #f43f6b; }
[data-theme="dark"][data-accent="rose"]   { --accent: #ff5c81; }
[data-accent="coral"]  { --accent: #ff6a3d; }
[data-theme="dark"][data-accent="coral"]  { --accent: #ff7a52; }

* { box-sizing: border-box; }
body {
  margin: 0;
  font: 16px/1.5 var(--font-body);
  background: var(--bg);
  color: var(--ink);
}
h1, h2, h3, .brand-wordmark { font-family: var(--font-display); }
/* .wrap is the NON-dashboard page frame (landing, auth, onboarding). The
   dashboard uses the app-shell (rail + pane) instead — see .app-shell below. */
.wrap { max-width: 980px; margin: 0 auto; padding: 10px 24px 24px; }
a { color: var(--accent); }
.muted { color: var(--muted); }
/* The HTML `hidden` attribute must win — any component that sets its own `display`
   (flex/grid/inline-flex) otherwise overrides it, so a JS/template `hidden` toggle
   silently does nothing (this was the storefront's "everything looks installed" bug:
   .skill-installed { display: inline-flex } beat [hidden]). One global rule fixes it
   everywhere and lets the per-component [hidden] overrides retire. */
[hidden] { display: none !important; }
.error { color: var(--error); }
/* Utility: hide regardless of a component's own display rule (the onboarding
   chat box, error lines, etc. carry class="hidden" until JS reveals them). */
.hidden { display: none !important; }
code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
pre { background: var(--panel); padding: 12px 14px; border-radius: 8px; overflow:auto; }

.hero { text-align: center; padding: 64px 0; }
.hero h1 { font-size: 56px; margin: 0; }
.tagline { font-size: 22px; }
.sub { color: var(--muted); }
.cta-row { margin-top: 28px; display: flex; gap: 12px; justify-content: center; }

.btn {
  display: inline-block; padding: 10px 18px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--panel); color: var(--ink);
  text-decoration: none; cursor: pointer; font-size: 15px;
}
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
/* Secondary button: a real raised surface (not a transparent outline that vanishes on the
   warm bg), with a defined border + subtle lift, and a coral accent on hover. */
.btn-ghost { background: var(--panel); border-color: color-mix(in srgb, var(--ink) 25%, transparent); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.07); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn-danger { background: transparent; border-color: var(--error); color: var(--error); }
.btn-danger:hover { background: var(--error); color: #fff; }
/* Disabled buttons: dimmed, no pointer, and hover styles neutralized. */
.btn:disabled, .btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.btn-danger:disabled:hover, .btn-danger[disabled]:hover { background: transparent; color: var(--error); }

/* ── Key-gate page (views/onboarding/key.hbs) — cardless, big, mobile-first ── */
.kg { display: flex; justify-content: center; padding: 56px 20px 88px; }
.kg__inner { width: 100%; max-width: 480px; text-align: left; }
/* Left-aligned, box-less Blink mark — blinks + takes the accent (standard with the login page). No
   resting box; the "waking" glow (kg-pulse) draws its own halo while connecting. */
.kg__face { display: grid; place-items: center; width: 58px; height: 58px; margin: 0 0 18px; border-radius: 16px; }
.kg__face .brand-mark-svg { width: 58px; height: 58px; }
.kg__title {
  font-family: var(--font-display); font-size: 34px; line-height: 1.15;
  letter-spacing: -0.01em; margin: 0 0 14px; color: var(--ink);
}
.kg__lede { font-size: 17px; line-height: 1.55; color: var(--muted); margin: 0 0 28px; max-width: 32em; }
.kg__error { color: #c0392b; font-size: 14.5px; margin: 0 0 16px; }

/* The primary action — big, prominent input + button (the surface fill defines it, no card needed) */
.kg__form { display: flex; flex-direction: column; gap: 12px; text-align: left; }
.kg__input {
  width: 100%; padding: 15px 16px; font: inherit; font-size: 16px; border-radius: 14px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink);
}
.kg__input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.kg__submit { width: 100%; justify-content: center; padding: 14px; font-size: 16px; border-radius: 14px; }
.kg__msg { font-size: 14px; line-height: 1.45; }
.kg__msg.is-err { color: #c0392b; }
.kg__msg.is-ok { color: var(--accent); }
.kg__reassure { font-size: 13.5px; line-height: 1.5; color: var(--muted); margin: 16px 0 0; max-width: 38em; }

/* Drill-downs — native <details>, no cards, just hairline dividers + a rotating chevron */
.kg__more { margin-top: 40px; text-align: left; border-top: 1px solid var(--border); }
.kg__drill { border-bottom: 1px solid var(--border); }
.kg__drill summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 17px 2px; font-size: 16px; font-weight: 600; color: var(--ink);
}
.kg__drill summary::-webkit-details-marker { display: none; }
.kg__drill summary::after {
  content: ""; flex: none; width: 8px; height: 8px; margin-right: 4px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(-45deg); transition: transform .18s;
}
.kg__drill[open] summary::after { transform: rotate(45deg); }
.kg__drill-body { padding: 0 2px 18px; font-size: 15px; line-height: 1.6; color: var(--text); }
.kg__drill-body p { margin: 0; }
.kg__drill-body a { color: var(--accent); }
.kg__drill-body code { font-size: 13px; padding: 1px 6px; border-radius: 5px; background: var(--bg); border: 1px solid var(--border); }
.kg__steps { margin: 0; padding-left: 20px; }
.kg__steps li { margin-bottom: 11px; }
.kg__steps li:last-child { margin-bottom: 0; }

@media (max-width: 560px) {
  .kg { padding: 32px 18px 64px; }
  .kg__title { font-size: 28px; }
  .kg__lede { font-size: 16px; }
}

/* "Waking up" — the connected -> app handoff (public/js/onboarding-key.js). The face breathes, the
   status rides the lede line, the rest fades, then the page fades out into the app. */
@keyframes kg-pulse {
  0%, 100% { transform: scale(1); box-shadow: 0 8px 24px color-mix(in srgb, var(--accent) 20%, transparent); }
  50% { transform: scale(1.07); box-shadow: 0 14px 36px color-mix(in srgb, var(--accent) 36%, transparent); }
}
.kg__inner.is-waking .kg__face { animation: kg-pulse 1.1s ease-in-out infinite; }
.kg__inner.is-waking .kg__lede { color: var(--accent); font-weight: 600; transition: color .25s ease; }
.kg__inner.is-waking .kg__form,
.kg__inner.is-waking .kg__reassure,
.kg__inner.is-waking .kg__more { opacity: 0; pointer-events: none; transition: opacity .35s ease; }
body.kg-leaving { opacity: 0; transition: opacity .4s ease; }

@media (prefers-reduced-motion: reduce) {
  .kg__inner.is-waking .kg__face { animation: none; }
  body.kg-leaving { transition: none; }
}

/* Kill iOS Safari's double-tap-to-zoom on tappable controls (e.g. the chat
   jump-to-latest arrow read as a double-tap → the view zoomed in). Pinch-zoom
   is untouched, so accessibility zoom still works. */
button, a, textarea, .btn { touch-action: manipulation; }

/* ── Account / Settings (the human's profile) ─────────────────────────────── */
.settings { display: flex; flex-direction: column; gap: 16px; max-width: 540px; margin: 0 auto; }
.settings-head { display: flex; align-items: center; gap: 14px; }
.settings-title { margin: 0; font-size: 22px; }
.settings-sub { margin: 2px 0 0; font-size: 14px; }
.settings-section { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 20px; display: flex; flex-direction: column; gap: 14px; }
.settings-section > h2 { margin: 0; font-size: 16px; }
.settings-section > h3 { margin: 0; }
.settings-section > .sub { margin: -8px 0 2px; font-size: 13px; }
.settings-section .field { gap: 6px; }
.settings-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.topup-row { display: flex; gap: 8px; flex-wrap: wrap; }
/* Balance card (Settings) — the eyebrow "BALANCE" is the label, so the number IS the card's hero
   (no redundant "Balance:" pill). Amount + caption group tightly; the top-up buttons sit below. */
.balance-head { display: flex; flex-direction: column; gap: 3px; }
.balance-amount {
  font-size: 34px; font-weight: 800; color: var(--ink); line-height: 1.05;
  letter-spacing: -0.02em; font-variant-numeric: tabular-nums;
}
.balance-amount--comped { font-size: 22px; letter-spacing: 0; color: var(--accent); }
.balance-sub { margin: 0; color: var(--muted); font-size: 13px; }
.balance-sub--muted { font-family: var(--font-mono); font-size: 12px; opacity: 0.8; }
.ff-input:disabled { opacity: 0.65; cursor: not-allowed; }
/* Notifications toggle: label left, switch right. */
.settings-toggle { justify-content: space-between; flex-wrap: nowrap; cursor: pointer; gap: 16px; }
.settings-toggle > span:first-child { font-size: 14px; line-height: 1.45; }
.ff-switch { flex: none; -webkit-appearance: none; appearance: none; width: 44px; height: 26px; border-radius: 999px; background: var(--border); position: relative; cursor: pointer; transition: background 0.15s ease; }
.ff-switch::after { content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; transition: transform 0.15s ease; box-shadow: 0 1px 2px rgba(0,0,0,0.25); }
.ff-switch:checked { background: var(--accent); }
.ff-switch:checked::after { transform: translateX(18px); }
/* INDETERMINATE — the tri-state master's "partially on" face (rev 5, the workflow ping block):
   thumb centered on a half-tinted track, so partial reads as its own state instead of lying "off".
   (Only script can set .indeterminate — notif-settings.js stamps it from data-wf-state.) */
.ff-switch:indeterminate { background: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.ff-switch:indeterminate::after { transform: translateX(9px); }
.ff-switch:disabled { opacity: 0.6; cursor: wait; }
/* Standalone notice page (unsubscribe confirmation). */
.notice { max-width: 480px; margin: 12vh auto 0; text-align: center; display: flex; flex-direction: column; gap: 14px; align-items: center; }
.notice-heading { margin: 0; }
.notice-message { margin: 0; line-height: 1.55; }

.key-status { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0; font-size: 14px; }
.key-status .ok { color: var(--accent); font-weight: 600; }
.key-status .warn { color: var(--error); font-weight: 600; }
.key-status .ok .icon, .key-status .warn .icon { margin-right: 4px; }
/* Paste-a-new-key row: [input] [Save key]. Clean pairing — Remove lives on the status line above. */
.key-rotate { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.key-rotate .ff-input { flex: 1 1 200px; min-width: 0; }

/* Remove key — a quiet action on the CURRENT key, right-aligned on the status line. Muted by
   default, red on hover or when armed (the two-tap confirm swaps the label to "Click to confirm"
   + .is-armed). The double-click width change grows leftward into empty space, so nothing jostles. */
.key-remove-link {
  margin-left: auto; flex: none; white-space: nowrap;
  background: none; border: 0; padding: 0; cursor: pointer; font: inherit; font-size: 13px;
  color: var(--error); text-decoration: none;
}
.key-remove-link:hover, .key-remove-link.is-armed { text-decoration: underline; }
.key-remove-link:disabled { opacity: 0.5; cursor: default; }

/* Quiet "where to get a key" line under the paste field. */
.key-hint { margin: -2px 0 0; font-size: 12.5px; color: var(--muted); }
.key-hint a { color: var(--accent); text-decoration: none; }
.key-hint a:hover { text-decoration: underline; }
/* Inline text button (no chrome) — used for the quiet "Remove key" disconnect. */
.btn-link { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; text-decoration: underline; color: var(--accent); }
.btn-link.danger { color: var(--error); }
.btn-link:disabled { opacity: 0.5; cursor: default; }

.plan-badge { display: inline-block; padding: 5px 14px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg); font-size: 13px; text-transform: capitalize; font-weight: 600; }

/* ── Account settings — cards in a 2-up masonry (desktop) / stacked (mobile) ─────────────────
   Each section is its own card; cards flow into two columns on desktop (CSS multicol → they pack
   without ragged height gaps) and one column on mobile. Inside a card: a header (eyebrow · info
   "i" · inline confirmation note) then stacked rows. Explanations live behind the "i"; a save/
   change flashes a brief note in the card HEADER, so a confirmation never reserves vertical space.
   Account-only via .settings--cards — the shared .settings-section card still serves usage. */
#tab-account { max-width: 980px; }
.settings--cards { max-width: 940px; gap: 0; }

/* A little top breathing room now that the page header is gone (on top of the tabpane padding). */
.set-grid { column-count: 2; column-gap: 18px; margin-top: 18px; }

.set-sec {
  break-inside: avoid; margin: 0 0 18px;
  display: flex; flex-direction: column; gap: 13px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 15px 18px 17px;
}
.set-sec--danger { border-color: color-mix(in srgb, var(--error) 32%, var(--border)); }

/* Card header — eyebrow, optional info "i", and (pushed right) the inline confirmation note. */
.set-sec__head { display: flex; align-items: center; gap: 6px; min-height: 18px; }
/* (`.set-sec__head--agent` DELETED 2026-09-02 with the DUSTAV card husk — the last of the member
   layer's Settings surface; the card had been empty since the Members block went, 2026-08-31.) */
.set-sec__eyebrow {
  margin: 0; font-size: 11.5px; font-weight: 700; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--muted);
}
.set-sec--danger .set-sec__eyebrow { color: var(--error); }
/* Inline confirmation — lives in the header row, so it adds NO vertical space; empty = invisible. */
.set-sec__note {
  margin-left: auto; font-size: 12px; font-weight: 600; color: var(--accent);
  opacity: 0; transition: opacity 0.15s ease;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 62%;
  text-transform: none; letter-spacing: 0;
}
.set-sec__note.is-show { opacity: 1; }
.set-sec__note.is-err { color: var(--error); }

/* Rows read as an aligned form: label (+info "i") on the LEFT, control on the RIGHT, on ONE line
   (the pattern toggles already used, now the default). Interactive controls share a FIXED width so
   sibling dropdowns/inputs line up on both edges (Model ⟷ Honesty check). */
.set-row { display: flex; flex-direction: row; align-items: center; justify-content: space-between; gap: 12px; }
.set-row__head { display: flex; align-items: center; gap: 6px; min-width: 0; }
.set-row__label { display: block; font-weight: 600; font-size: 14px; color: var(--text); white-space: nowrap; }
/* A derived, read-only list inside a settings section (docs/NOTIFICATIONS.md §5 — the notification classes
   that may leave the app). Not a .set-row: these carry no control, because there is nothing to set HERE.
   The class list is the registry's, and a per-workflow subscription is changed by telling Dustav. */
/* The notification-classes modal (account.hbs) — TWO FACES on the modal ladder (2026-08-28 audit:
   desktop windows, phones fill; the cockpit + sync are the only desktop full-screens left). On a
   phone the box IS the screen — the sync recipe it was born on (Curtis, 2026-08-27: "look and feel
   like the google sync modal"). On desktop it floats as a centered 560px dialog: a column of toggles
   never earned a whole screen there, it only inherited sync's dress. Head + body keep the .syncm
   gutter column (it collapses to a plain 18px inside the 560 box); each row is
   [stacked text | ff-switch] so no column can squeeze another (the .set-list lesson). */
/* ⚠️ display lives on [open] ONLY: an author display on a bare <dialog> overrides the UA's
   dialog:not([open]){display:none} (author origin beats UA origin), so the server-rendered modal
   painted permanently over /account and ✕ "didn't work" — it closed a dialog that stayed visible. */
.nfm { position: fixed; inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; margin: 0; border: none; border-radius: 0; padding: 0; background: var(--surface); color: var(--ink); flex-direction: column; overflow-y: auto; }
.nfm[open] { display: flex; }
.nfm::backdrop { background: color-mix(in srgb, #000 45%, transparent); }
.nfm__head { display: flex; align-items: center; justify-content: space-between; padding: 16px max(18px, calc((100% - 560px) / 2)) 10px; }
/* Desktop face: centered window (inset:0 + margin:auto — the UA dialog centering the base's
   full-screen geometry overrode). Height is the content's, capped so a long registry scrolls
   inside (the base overflow-y:auto). No display here — that stays [open]-scoped, above. */
@media (min-width: 880px) {
  /* height: fit-content, NOT auto — the base keeps inset:0, and a fixed box with top+bottom pinned
     stretches an auto height to the whole constraint (the first render sat 3 toggles in a 720px
     tower); fit-content hugs the rows and margin:auto centers what's left. */
  .nfm { width: min(560px, calc(100vw - 40px)); height: fit-content; max-height: min(80dvh, 720px); margin: auto; border: 1px solid var(--border); border-radius: 16px; box-shadow: 0 18px 50px rgba(0,0,0,.28); }
}
.nfm__title { font-family: var(--font-display); font-size: 18px; font-weight: 800; }
.nfm__close { border: none; background: none; color: var(--muted); cursor: pointer; font-size: 15px; padding: 6px 8px; border-radius: 8px; flex: none; }
.nfm__close:hover { color: var(--ink); }
.nfm__body { padding: 4px max(18px, calc((100% - 560px) / 2)) calc(28px + env(safe-area-inset-bottom, 0px)); display: flex; flex-direction: column; gap: 14px; }
.nfm__sub { font-size: 13px; line-height: 1.5; margin: 0; }
.nfm__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.nfm__row { display: flex; align-items: center; gap: 14px; font-size: 13px; }
.nfm__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.nfm__label { font-weight: 600; }
.nfm__desc { font-size: 12.5px; line-height: 1.45; }
.nfm__control { flex: none; }
/* The workflow TRI-STATE block (rev 5 — "workflows can be partially on, or off, or all on"): the
   per-instance class row's master switch sits in the normal control slot; this is the sub-list under
   it, one row per live workflow with its own switch. The inset + hairline say "these belong to the
   row above" without drawing a tree. Negative margin-top tucks it against its class row (the list's
   16px gap is between CLASSES). */
.nfm__wfblock { margin: -8px 0 0; padding: 0 0 0 14px; border-left: 2px solid var(--border); }
.nfm__wflist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.nfm__wfrow { display: flex; align-items: center; gap: 12px; font-size: 12.5px; }
.nfm__wfname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.set-row__control { display: flex; align-items: center; justify-content: flex-end; gap: 10px; min-width: 0; flex-wrap: wrap; }
.set-row__control > .ff-input { flex: 0 0 auto; width: 240px; max-width: 100%; }
/* Toggle: label-head left, switch right (the switch keeps its natural size). */
.set-row--toggle .set-row__control { flex: none; }
/* (DELETED 2026-09-02 — `.set-row--block` + `.set-hr`, the members-roster/invite layout primitives.
   Their last consumer was the Settings Members block, deleted 2026-08-31 (docs/SHARING.md); zero
   consumers repo-wide since.) */

/* Free-form card body (API key) — a flat stack. */
.set-body { display: flex; flex-direction: column; align-items: stretch; gap: 12px; }

/* ── Profile tab — a READ-ONLY window onto what Dustav authored, beside the copilot. Same flex-row shell
      + .lib header as Notes / Workflows / Inbox. --}} */
#tab-profile.profile { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row; }
/* On a phone the fixed top bar overlaps the pane, so a full-bleed shell has to clear it or its header renders
   underneath and is unreadable. Same pattern as #tab-chat / #tab-documents: 100dvh stays, and border-box turns the
   padding into the offset rather than overflow. (verify:mobile-chrome guards that every tab has this.) */
@media (max-width: 879px) {
  #tab-profile.profile { flex-direction: column; padding: var(--mtop-h) 0 0; }
}
.pf-main { flex: 1 1 auto; min-width: 0; display: flex; min-height: 0; }
.pf-main > .lib { flex: 1 1 auto; min-width: 0; min-height: 0; }
.profile .lib__count { font-family: var(--font-mono); font-size: 11px; letter-spacing: .02em; color: var(--muted); margin: 4px 0 0; }
/* Full width, matching the hero / Find us / Still looking above it. The old 720px cap is what made the
   authored cards stop short of the blocks above them (Curtis: "the widths are different — should be full
   width"). */
.profile .set-grid { column-count: 1; margin-top: 2px; }
.pf-prose { margin: 0; font-size: 14px; line-height: 1.6; color: var(--ink); white-space: pre-wrap; }
.pf-prose--sig { font-weight: 600; }
.pf-empty { margin: 0; font-size: 13px; line-height: 1.5; }
/* "Ask Dustav →" (2026-08-24; relabeled from Tell the same day — matches the docked composer's handle) —
   each prose section's conversation starter, far right of the eyebrow row.
   A quiet accent TEXT button (the ripcord grammar: it starts a chat, it doesn't perform an act — so no
   rect box); the → is the affordance. */
.pf-tell { margin-left: auto; padding: 0; border: none; background: none; font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--accent); cursor: pointer; white-space: nowrap; }
.pf-tell:hover, .pf-tell:focus-visible { text-decoration: underline; outline: none; }
/* A header-row note beside an eyebrow (e.g. "appended to every email") — no vertical space added. */
.set-sec__head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.set-sec__note { font-size: 11.5px; }
/* The formal signature preview — renders the stored (sanitized) HTML letterhead as the finished block, flat
   in the card (it USED to carry its own border+padding, a box-in-a-box the .set-sec card already provides —
   Curtis: "remove this extra border… along with the padding"). Links use the accent; tight email line-height. */
.pf-sigblock { font-size: 13px; line-height: 1.55; color: var(--ink); }
.pf-sigblock a { color: var(--accent); text-decoration: none; }
.pf-sigblock a:hover { text-decoration: underline; }

/* ── The designed business profile (docs/PROFILE.md §10) ───────────────────────────────────────────────
   Hero → chips → Find us → Still looking. Everything is theme-var driven so dark mode is correct by
   construction. --pf-brand is the business's OWN colour and is set from JS via style.setProperty (the app
   runs a strict `style-src 'self'` CSP, so an inline style="--pf-brand:…" attribute is BLOCKED — and the
   offline render harness would happily hide that, which is why it's a CSSOM write). It DEFAULTS to the
   app accent, so a business with no brand colour still looks deliberate rather than unstyled. */
/* ⚠️ ONE BOX SPEC for every block on this tab — the hero, Find us, Still looking AND the authored cards.
   Splitting them is how they drift, and they already had: the authored cards were capped at 720px while
   the new blocks ran the full column, and the radii were 16 / 14 / 14 / 12. Only what GENUINELY differs
   per block (the hero's extra vertical room, the dashed gap card) is overridden below. */
.profile .set-sec, .pf-hero, .pf-find, .pf-gaps {
  border: 1px solid var(--border); border-radius: 14px; background: var(--surface);
  margin: 0 0 18px; padding: 15px 18px 17px;
}

.pf-hero { --pf-brand: var(--accent); display: flex; gap: 20px; align-items: center; padding: 20px 18px 22px; position: relative; overflow: hidden; }
/* A quiet band of the business's colour — present enough to feel like theirs, never enough to fight the
   app's own accent or the text contrast. */
.pf-hero::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 4px; background: var(--pf-brand); }
.pf-hero--empty { border-style: dashed; }

.pf-hero__markwrap { position: relative; flex: none; width: 96px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.pf-hero__logo { width: 84px; height: 84px; object-fit: contain; border-radius: 12px; background: #fff; padding: 6px; border: 1px solid var(--border); }
/* The no-logo state: an initial in the business's colour. A designed placeholder, not a broken image. */
.pf-hero__initial { width: 84px; height: 84px; display: grid; place-items: center; border-radius: 12px; border: 1px dashed var(--border); font-family: var(--font-display); font-size: 38px; font-weight: 800; color: var(--pf-brand); background: color-mix(in oklab, var(--pf-brand) 8%, var(--surface)); }
.pf-hero__logobtn { font-size: 11px; font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .04em; color: var(--muted); background: none; border: 1px solid var(--border); border-radius: 999px; padding: 3px 9px; cursor: pointer; }
.pf-hero__logobtn:hover { color: var(--ink); border-color: var(--ink); }

.pf-hero__text { min-width: 0; flex: 1; }
.pf-hero__name { font-family: var(--font-display); font-size: 26px; font-weight: 800; line-height: 1.15; margin: 0; color: var(--ink); }
.pf-hero__tagline { margin: 6px 0 0; font-size: 14px; line-height: 1.5; color: var(--muted); }

.pf-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 12px; }
.pf-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px 5px 9px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg); color: var(--ink); text-decoration: none; font-size: 12.5px; line-height: 1; }
.pf-chip:hover { border-color: var(--pf-brand); }
.pf-chip__ico { display: inline-flex; color: var(--pf-brand); }
.pf-chip__ico svg { width: 15px; height: 15px; }
.pf-chip__sub { color: var(--muted); font-size: 11.5px; max-width: 15ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.pf-find { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 2px 22px; }
.pf-find__row { display: flex; align-items: baseline; gap: 9px; padding: 6px 0; min-width: 0; }
/* Pin the icon to the FIRST text line (not the row's center): on a wrapped value (address, hours) a
   centered icon dips between the lines and falls out of line with its label + the other rows' icons. */
.pf-find__ico { color: var(--muted); flex: none; align-self: flex-start; margin-top: 0.5px; }
.pf-find__ico svg { width: 16px; height: 16px; }
.pf-find__label { font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); flex: none; width: 58px; }
.pf-find__val { font-size: 13.5px; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
a.pf-find__val { color: var(--accent); text-decoration: none; }
a.pf-find__val:hover { text-decoration: underline; }

/* Honest about the gaps — a page that looks complete because the empty fields were hidden is the thing
   we're avoiding. Renders only when something IS missing. */
.pf-gaps { border-style: dashed; background: var(--bg); padding: 13px 18px 15px; }
.pf-gaps__head { margin: 0; font-size: 13px; color: var(--ink); }
.pf-gaps__hint { margin: 3px 0 0; font-size: 12px; }

@media (max-width: 879px) {
  .pf-hero { flex-direction: column; align-items: flex-start; gap: 14px; padding: 18px; }
  .pf-hero__markwrap { width: auto; flex-direction: row; align-items: center; }
  .pf-hero__name { font-size: 22px; }
}
.pf-facts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pf-fact {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 8px;
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 9px; background: var(--surface);
}
.pf-fact__text { font-size: 13.5px; line-height: 1.4; color: var(--ink); }
.pf-fact__x {
  flex: none; border: none; background: none; cursor: pointer; color: var(--muted);
  padding: 1px; border-radius: 6px; line-height: 0; opacity: .5; transition: opacity .12s, color .12s, background .12s;
}
.pf-fact:hover .pf-fact__x { opacity: 1; }
.pf-fact__x svg { width: 15px; height: 15px; }
.pf-fact__x:hover { color: var(--error, #c0392b); background: color-mix(in srgb, var(--error, #c0392b) 12%, transparent); }
.pf-fact__x:disabled { opacity: .3; cursor: default; }

/* (DELETED 2026-08-31 with their last consumer, the Settings Members block — docs/SHARING.md:
   `.join-add*`, `.roster*`, `.agent-avatar`. Dustav is single-player; there is no roster to draw
   and nobody to add. `.fam-form` went too, further down. 2026-09-02 sweep: the household-sharing
   code/PIN dress — `.set-creds`, `.set-cred*`, `.set-copy*` — went the same way, dead since the
   code+PIN join's 2026-08-06 purge; plus the `.roster__*` phone rules the 08-31 pass missed in
   the 640px media query below.) */
.set-fine { margin: 2px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.5; }

/* Info popover — each setting's explanation lives behind a small "i", revealed on click
   (account-settings.js); one open at a time; outside-click / Esc closes. */
.set-info { position: relative; display: inline-flex; }
.set-info__btn {
  display: grid; place-items: center; width: 18px; height: 18px; padding: 0; flex: none;
  border: 0; background: none; color: var(--muted); cursor: pointer; border-radius: 50%;
}
.set-info__btn:hover, .set-info.is-open .set-info__btn { color: var(--accent); }
.set-info__btn .icon { width: 15px; height: 15px; }
.set-info__pop {
  display: none; position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  width: max-content; max-width: min(280px, 74vw);
  background: var(--surface); color: var(--muted); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 12px; box-shadow: 0 8px 26px rgba(0, 0, 0, 0.18);
  font-size: 12.5px; font-weight: 400; line-height: 1.5; letter-spacing: 0; text-transform: none;
  white-space: normal;
}
.set-info__pop a { color: var(--accent); }
.set-info__pop code { font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace); }
.set-info.is-open .set-info__pop { display: block; }

/* Below the shell breakpoint: one column, cards stacked vertically. */
@media (max-width: 880px) {
  .set-grid { column-count: 1; }
}
/* Phones: STAY lined up (label left, control right) instead of stacking — but the control now FILLS
   the remaining row width rather than the desktop fixed 240px (which would overflow a phone), and
   the LABEL may wrap: the desktop nowrap + a long label ("What reaches you outside the app") ran
   the text underneath its own button. Toggles are untouched. (The --block :not()s left with the
   members roster, 2026-09-02.) */
@media (max-width: 520px) {
  .set-row:not(.set-row--toggle) .set-row__control { flex: 1 1 auto; }
  .set-row:not(.set-row--toggle) .set-row__control > .ff-input { flex: 1 1 auto; width: 100%; }
  .set-row__label { white-space: normal; }
}

/* Danger zone (Profile tab) — the data-custody delete affordance. */
/* The danger zone is a .settings-section card like the others — it sits in the
   same flex gap, so it needs NO extra top margin/padding (that double-spaced it).
   A faint error-tinted border keeps it distinct without shouting. */
.danger-zone { border-color: color-mix(in srgb, var(--error) 35%, var(--border)); }
.danger-zone h3 { margin: 0 0 6px; color: var(--error); font-size: 15px; }
.danger-zone .muted { margin: 0 0 12px; }
.acct-delete-confirm { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; max-width: 360px; }
.acct-delete-actions { display: flex; gap: 8px; }

/* Inputs (shared by onboarding + the profile knobs) */
.ff-input {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 10px 12px;
  border-radius: 10px;
  font-size: 16px; /* 16px keeps iOS from auto-zooming on focus */
  font-family: inherit;
}
.ff-input:focus { outline: none; border-color: var(--accent); }

/* Form fields + chips — shared by the account/settings page. */
.field { display: flex; flex-direction: column; gap: 8px; }
.field > label { font-size: 13px; color: var(--text); font-weight: 600; }
.field > label .muted { font-weight: 400; }
.chipset { display: flex; gap: 8px; flex-wrap: wrap; }
.chip {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 8px 16px;
  border-radius: 999px;
  cursor: pointer;
  font-size: 14px;
  transition: border-color 0.12s, background 0.12s;
}
.chip:hover { border-color: var(--accent); }
.chip.active { background: var(--accent); border-color: var(--accent); color: #fff; }

/* Accent skin picker — a row of color swatches. Each swatch carries its OWN
   data-accent="<key>", so the [data-accent] blocks above re-set --accent on that
   button and it paints itself (no inline styles — the CSP forbids them, and this
   reuses the one accent palette so the preview can't drift from the applied color).
   The selected one gets a ring. Same data-field wiring shape as the theme chipset. */
.swatchset { display: flex; gap: 12px; flex-wrap: wrap; }
.swatch {
  width: 34px; height: 34px; border-radius: 50%; padding: 0; cursor: pointer;
  background: var(--accent); border: 2px solid var(--surface);
  box-shadow: 0 0 0 1px var(--border);
  transition: box-shadow 0.12s, transform 0.08s;
}
.swatch:hover { transform: scale(1.06); }
.swatch.active { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }
.swatch:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }

.pf-status { min-height: 18px; font-size: 13px; margin: 0; }
.pf-status.err { color: var(--error); }

/* (The .about-* read-only profile page was retired 2026-06-15 — Delete moved into
   Settings → Agents; its styles were removed with it.) */

.card { background: var(--panel); border: 1px solid var(--border); border-radius: 14px; padding: 28px; }
.card.narrow { max-width: 420px; margin: 48px auto; }
.stack { display: flex; flex-direction: column; gap: 14px; }
.stack label { display: flex; flex-direction: column; gap: 6px; font-size: 14px; }
.stack input {
  padding: 10px 12px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--bg); color: var(--ink); font-size: 15px;
}

/* ── Auth (sign-in / check-email) — the one door. Same warm chrome as the
   landing (pub-header/footer wrap these pages), one centered card. ── */
.auth { display: flex; justify-content: center; padding: 56px 0 72px; }
/* No card — the form sits flat on the page background (we don't card everything). Just a centred,
   width-capped column. */
.auth__card {
  width: 100%; max-width: 420px; padding: 0 24px;
}
.auth__face {
  display: grid; place-items: center; width: 52px; height: 52px; border-radius: 15px;
  background: color-mix(in srgb, var(--accent) 12%, var(--bg));
  font-size: 28px; line-height: 1; margin-bottom: 16px;
}
/* Login: the live Blink mark, no tinted box — it blinks + takes the accent (matches the theme).
   Fixed size so it left-aligns with the title (not a full-width centred grid). */
.auth__face--mark { background: none; width: 58px; height: 58px; margin-bottom: 18px; }
.auth__face--mark .brand-mark-svg { width: 58px; height: 58px; }
.auth__title { margin: 0 0 10px; font-size: 30px; letter-spacing: -0.01em; }
.auth__lede { margin: 0 0 22px; color: var(--muted); line-height: 1.55; font-size: 15.5px; }
.auth__lede strong { color: var(--ink); }
.auth__form { display: flex; flex-direction: column; gap: 9px; }
.auth__label { font-size: 13px; font-weight: 700; color: var(--ink); }
.auth__input {
  width: 100%; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface); color: var(--ink); font-size: 15px;
}
.auth__input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.auth__submit { width: 100%; margin-top: 4px; }
.auth__steps {
  list-style: none; margin: 22px 0 0; padding: 18px 0 0; border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 11px;
}
.auth__steps li { display: flex; align-items: center; gap: 11px; font-size: 14.5px; color: var(--muted); }
.auth__stepE { flex: none; width: 1.5em; text-align: center; font-size: 16px; }
.auth__fine { margin: 18px 0 0; }
.auth__fine a, .auth__lede a { color: var(--accent); }
/* check-email variant: centered, no form */
.auth__card--center { text-align: center; }
.auth__card--center .auth__face { margin-left: auto; margin-right: auto; }
.auth__card--center .auth__submit { margin-top: 8px; }

/* The /join profile picker — a real "pick a face" list: uniform full-width, left-aligned
   tiles (not content-width buttons) with a fixed emoji column so the names line up. An
   "or" divider sets the returning members apart from the new-person tile, which reads as
   a distinct, lighter action. */
.join-who { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
.join-who__list { display: flex; flex-direction: column; gap: 10px; }
.join-who form { margin: 0; }
.join-who__btn {
  display: flex; align-items: center; gap: 12px; width: 100%;
  justify-content: flex-start; text-align: left;
  padding: 14px 16px; font-size: 16px; font-weight: 600;
}
.join-who__btn:hover { border-color: var(--accent); color: var(--ink); }
.join-who__emoji { flex: none; width: 1.6em; text-align: center; font-size: 22px; line-height: 1; }
.join-who__or {
  display: flex; align-items: center; gap: 12px; margin: 2px 2px;
  color: var(--muted); font-size: 13px;
}
.join-who__or::before, .join-who__or::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.join-who__btn--new { border-style: dashed; color: var(--muted); }
.join-who__btn--new:hover { border-color: var(--accent); color: var(--ink); }

/* ════════════════════════════════════════════════════════════════════════════
   APP SHELL — a messaging client. Desktop (≥880px): a fixed left nav rail + a
   scrolling pane. Mobile (<880px): the rail is hidden; a fixed top bar (.mtop) +
   bottom tab bar (.tabbar) take over. The 880px breakpoint is hardcoded (custom
   props can't drive media queries).
   ════════════════════════════════════════════════════════════════════════════ */
.app-shell { display: flex; min-height: 100dvh; }
.pane { flex: 1; min-width: 0; }

.brand { display: inline-flex; align-items: center; gap: 5px; text-decoration: none; }
.brand .icon { width: 24px; height: 24px; color: var(--accent); }
/* Brand mark = an <img> of Blink (public/brand-mark.png, rasterized from brand-mark.svg), so the
   wordmark face is pixel-identical on every platform. (This comment once said "the Fluent emoji" —
   a fossil of the retired emoji marks; the PNG has been Blink since the mark settled.) */
.brand-face { width: 24px; height: 24px; flex: none; object-fit: contain; }
/* Other brand-face images (the same Blink brand mark), each sized to match the glyph it
   replaced. Nested forms sit INSIDE their tinted container; standalone forms are the img itself. */
.auth__face img { width: 30px; height: 30px; object-fit: contain; }
.land-badge__face img { width: 15px; height: 15px; object-fit: contain; }
.lchat-av--agent img { width: 20px; height: 20px; object-fit: contain; }
img.onb-choose__face { width: 40px; height: 40px; object-fit: contain; }
img.onb-choose__emoji { width: 30px; height: 30px; object-fit: contain; }
/* line-height:1 tightens the text box to the glyph so the mark centres on the cap height, not the
   line box (whose descender padding was pushing the mark optically low). Brand name has no
   descenders, so nothing clips. */
.brand-wordmark { font-weight: 700; font-size: 22px; line-height: 1; letter-spacing: -0.02em; color: var(--ink); }
.brand-dot { color: var(--accent); }

/* ── The rail (desktop) ─────────────────────────────────────────────────────── */
.rail {
  position: sticky; top: 0; align-self: flex-start; flex: none;
  width: var(--rail-w); height: 100dvh;
  display: flex; flex-direction: column; gap: 6px;
  padding: 18px 14px; background: var(--bg); border-right: 1px solid var(--border);
}
.rail__brand { padding: 4px 12px 12px; gap: 12px; } /* mark 20px + 12px gap + 12px pad = aligns with the section rows */
/* The rail head: the lockup + the global bell on one row. The bell sits in the CHROME beside the mark rather
   than in the section list, because it belongs to no tab (docs/NOTIFICATIONS.md). */
.rail__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.rail__head .rail__brand { min-width: 0; }
/* When the rail is an icon strip (collapsed on desktop, or pinned on a phone) the wordmark is gone and the
   head holds one or two glyphs — space-between would shove the bell against the left edge, off the strip's
   optical centre line that every icon below it sits on. On a phone that leaves ONE glyph (the brand lives in
   the top bar there); on the collapsed desktop rail there are two, and they stack — see the collapse block. */
html.nav-collapsed .rail__head { justify-content: center; }
@media (max-width: 879px) { .rail .rail__head { justify-content: center; } }

/* ── THE GLOBAL BELL (docs/NOTIFICATIONS.md §9b) ──────────────────────────────────────────────────────────────
   "A global notification icon that lights up when a notification is there" (Curtis, 2026-08-26). It LIGHTS, it
   never counts: the tab badges count WORK (dates to book, to-dos owed) and a number here would read as more of
   the same, when what it actually means is "Dustav has something to tell you". Dark is the normal state — the
   classes that light it are the ones with nowhere else to go. */
.bell { position: relative; flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: var(--btn-h-md); height: var(--btn-h-md); border-radius: 8px; color: var(--muted); transition: color .14s; }
.bell:hover { color: var(--ink); }
.bell svg { width: 18px; height: 18px; }
/* The dot: absent until lit, so an unlit bell is a plain quiet glyph rather than a control with an empty slot. */
.bell__dot { display: none; position: absolute; top: 5px; right: 5px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 2px var(--bg); }
.bell.is-lit { color: var(--accent); }
.bell.is-lit .bell__dot { display: block; animation: bellPulse 2.4s ease-in-out 3; }
/* Three pulses, then still. Long enough to catch an eye that's elsewhere on the screen, bounded so it never
   becomes a thing to tune out — the same reasoning as the mark's blink being an identity, not an alarm. */
@keyframes bellPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.35); }
}
@media (prefers-reduced-motion: reduce) { .bell.is-lit .bell__dot { animation: none; } }

/* The rail sync control — a STATUS PILL that opens the sync modal (docs/GMAIL_WATCH.md). An instrument
   readout, not a CTA: the filled-accent look survives ONLY for the setup state (data-state="cta", the one
   genuine action); live/busy/warn are a quiet ghost chip whose label is the brand's mono micro-label
   (LIVE · READING… · RECONNECT — text-transform does the caps) with a proper dot/spinner element (the old
   "● Live" glued a text glyph into the label). States ride data-state, set server-side (syncPill) + by sync.js. */
.rail__sync { padding: 2px 0 10px; } /* 0 horizontal → the pill spans the same width as the nav items below */
.rail__sync-pill { width: 100%; display: flex; align-items: center; gap: 8px; color: var(--ink); background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 9px 12px; cursor: pointer; text-align: left; }
.rail__sync-pill:hover { border-color: color-mix(in srgb, var(--ink) 25%, var(--border)); }
.rail__sync-pill svg { display: none; width: 15px; height: 15px; flex: none; } /* the rotate glyph — cta only */
.rail__sync-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-mono); font-weight: 600; font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; }
.rail__sync-count { font-family: var(--font-mono); font-size: 11px; font-variant-numeric: tabular-nums; flex: none; color: var(--muted); }
.rail__sync-dot { display: none; width: 8px; height: 8px; border-radius: 50%; flex: none; margin: 0 3px; }
.rail__sync-spin { display: none; width: 13px; height: 13px; flex: none; margin: 0 1px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--accent) 30%, transparent); border-top-color: var(--accent); animation: railSpin .7s linear infinite; }
/* CTA — not set up (or auto-sync off): the one true action state keeps the filled-accent button look. */
.rail__sync[data-state="cta"] .rail__sync-pill { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.rail__sync[data-state="cta"] .rail__sync-pill:hover { filter: brightness(1.06); }
.rail__sync[data-state="cta"] .rail__sync-pill svg { display: block; }
.rail__sync[data-state="cta"] .rail__sync-label { font-family: var(--font-display); font-weight: 600; font-size: 13px; letter-spacing: 0; text-transform: none; }
/* LIVE — ambient: a green pulsing dot. */
.rail__sync[data-state="live"] .rail__sync-dot { display: block; background: #16a34a; animation: syncPulse 2.4s ease-in-out infinite; }
/* WARN — reconnect / sync issue: the one real alarm, amber. */
.rail__sync[data-state="warn"] .rail__sync-dot { display: block; background: #d97706; box-shadow: 0 0 0 3px color-mix(in srgb, #d97706 20%, transparent); }
.rail__sync[data-state="warn"] .rail__sync-pill { border-color: color-mix(in srgb, #d97706 45%, var(--border)); color: color-mix(in srgb, #d97706 75%, var(--ink)); }
/* BUSY — the accent spinner + "READING… 2/5" (work-indexed; no count until the denominator is known). */
.rail__sync[data-state="busy"] .rail__sync-spin { display: block; }
@keyframes railSpin { to { transform: rotate(360deg); } }
@keyframes syncPulse { 0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, #16a34a 25%, transparent); } 50% { box-shadow: 0 0 0 4px color-mix(in srgb, #16a34a 10%, transparent); } }
@media (prefers-reduced-motion: reduce) { .rail__sync-spin, .rail__sync[data-state="live"] .rail__sync-dot { animation: none; } }

/* ── The sync MODAL — the one Gmail surface (docs/GMAIL_WATCH.md) ── */
.syncm[hidden] { display: none; }
/* FULL SCREEN on every form factor (Curtis, 2026-08-24, right after the Blink-peek went full screen:
   "anything of significant detail deserves the full screen") — the panel IS the screen (the scrim is
   covered and unreachable; ✕ and Esc are the exits), content held to a readable ~560px column by the
   max() side gutters, which collapse to a plain 18px on a phone. Same recipe as .rpk. */
.syncm { position: fixed; inset: 0; z-index: 60; display: flex; }
.syncm__scrim { position: absolute; inset: 0; background: color-mix(in srgb, #000 45%, transparent); }
.syncm__panel { position: relative; width: 100%; height: 100%; overflow-y: auto; background: var(--surface); }
.syncm__head { display: flex; align-items: center; justify-content: space-between; padding: 16px max(18px, calc((100% - 560px) / 2)) 10px; }
/* Desktop headroom — the .rpk twin above says why. */
@media (min-width: 880px) {
  .syncm__head { padding-top: 9vh; }
}
.syncm__title { font-family: var(--font-display); font-size: 18px; font-weight: 800; margin: 0; }
.syncm__x { background: none; border: none; color: var(--muted); cursor: pointer; padding: 4px; line-height: 0; }
.syncm__x svg { width: 18px; height: 18px; }
.syncm__x:hover { color: var(--ink); }
.syncm__body { padding: 4px max(18px, calc((100% - 560px) / 2)) calc(28px + env(safe-area-inset-bottom, 0px)); display: flex; flex-direction: column; gap: 14px; }
.syncm__note, .syncm__loading { color: var(--muted); font-size: 13px; margin: 0; }
.syncm__connect { align-self: flex-start; }
.syncm__status { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; color: var(--ink); }
.syncm__dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.syncm__dot--live { background: #16a34a; box-shadow: 0 0 0 3px color-mix(in srgb, #16a34a 22%, transparent); }
.syncm__dot--idle { background: var(--muted); }
.syncm__dot--warn { background: var(--error); }
.syncm__last { color: var(--muted); font-family: var(--font-mono); font-size: 11px; margin-left: auto; }
.syncm__toggle { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px; border: 1px solid var(--border); border-radius: 12px; cursor: pointer; }
.syncm__toggle > span { display: flex; flex-direction: column; gap: 2px; }
.syncm__toggle strong { font-size: 14px; }
.syncm__toggle small { color: var(--muted); font-size: 12px; }
.syncm__toggle input { width: 18px; height: 18px; flex: none; accent-color: var(--accent); cursor: pointer; }
/* The batch-size picker (50/100/250) — lives in the catch-up row AND inline in the first-run CTA's sub-line. */
.syncm__catchup select, .syncm__cta select { flex: none; font: inherit; font-size: 13px; color: var(--ink); background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 4px 6px; cursor: pointer; }
/* "Read older mail" — the repeatable catch-up: picker + a quiet mono verb (the house rect grammar).
   ⚠ The selector must be (0,2,0): the generic `.syncm__toggle > span` column rule above is (0,1,1) and
   beats a bare `.syncm__catchup-ctl` (0,1,0) REGARDLESS of file order — the pair silently stacked
   vertically twice before the specificity was actually raised (caught by rendering, both times). */
.syncm__catchup { flex-wrap: wrap; } /* narrow: the controls drop below the hint instead of squeezing it to a sliver */
.syncm__catchup > span:first-child { flex: 1 1 220px; min-width: 0; }
.syncm__catchup > .syncm__catchup-ctl { display: flex; flex-direction: row; gap: 8px; align-items: center; flex: none; margin-left: auto; }
.syncm__read { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; height: 30px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface); color: var(--ink); cursor: pointer; white-space: nowrap; line-height: 1; }
.syncm__read:hover { border-color: var(--accent); color: var(--accent); }
.syncm__read[disabled] { opacity: .5; cursor: default; }
/* The auto-sync FINISH LINE at the modal's bottom — one big accent button when off; a quiet live row when on. */
.syncm__cta { display: flex; flex-direction: column; gap: 8px; padding-top: 2px; }
.syncm__go { font-family: var(--font-display); font-weight: 700; font-size: 15px; padding: 12px 16px; border-radius: 10px; border: 1px solid var(--accent); background: var(--accent); color: var(--accent-ink); cursor: pointer; width: 100%; }
.syncm__go:hover { filter: brightness(1.06); }
.syncm__go[disabled] { opacity: .6; cursor: default; }
.syncm__cta small { color: var(--muted); font-size: 12px; text-align: center; }
/* The in-card email-state sentence (round four: the sentence lives with its settings, the act in the foot). */
.syncm__cta--inline { padding-top: 4px; }
.syncm__cta--inline small { text-align: left; }
.syncm__live { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px; font-size: 13px; color: var(--ink); }
.syncm__live-note { flex: 1; min-width: 0; }
.syncm__off { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; height: 30px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface); color: var(--muted); cursor: pointer; white-space: nowrap; flex: none; line-height: 1; }
.syncm__off:hover { border-color: var(--error); color: var(--error); }
/* The "recommended off" tag beside Promotions/Social — the seeded default, worn as a quiet pill. */
.syncm__rec { font-family: var(--font-mono); font-weight: 400; font-size: 9.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 1px 7px; margin-left: 7px; vertical-align: 1px; white-space: nowrap; }
/* "What Dustav reads" — the Gmail-tab opt-outs (services/gmailCategories.js). Compact rows inside one
   bordered card (not N toggle cards): label+hint left, the checkbox right (Primary shows a muted "always"
   instead — never excludable). No counts (cut 2026-08-24). Theme-safe by construction (tokens only). */
.syncm__cats { border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; }
/* The card's BORDERED sub-rows (Read older mail · the auto-sync live/cta row) breathe apart from the
   tight checkbox list and from each other — the 2px list gap is the checkbox rows' rhythm, not theirs. */
.syncm__cats .syncm__catchup, .syncm__cats .syncm__live, .syncm__cats .syncm__cta--inline { margin-top: 10px; }
.syncm__cats-head { display: flex; flex-direction: column; gap: 2px; padding-bottom: 6px; }
.syncm__cats-head strong { font-size: 14px; }
.syncm__cats-head small { color: var(--muted); font-size: 12px; }
.syncm__cat { display: flex; align-items: center; gap: 10px; padding: 6px 0; cursor: pointer; }
.syncm__cat--pinned { cursor: default; }
.syncm__cat-main { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.syncm__cat-main strong { font-size: 13px; }
.syncm__cat-main small { color: var(--muted); font-size: 11.5px; }
.syncm__cat-always { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 2px 8px; }
.syncm__cat input { width: 16px; height: 16px; flex: none; accent-color: var(--accent); cursor: pointer; }
/* The Calendar section's Turn-on act — an <a> (the consent redirect), wearing the .syncm__read rect-act recipe. */
.syncm__calbtn { display: inline-flex; align-items: center; align-self: flex-start; margin-top: 6px; text-decoration: none; font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; height: 30px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface); color: var(--ink); white-space: nowrap; line-height: 1; }
.syncm__calbtn:hover { border-color: var(--accent); color: var(--accent); }
/* The PRODUCT CARD headers (the Google-sync re-cut, 2026-08-25): each card leads with its product's
   mark + name — "so it's clear what is what". The card box itself is the shared .syncm__cats recipe. */
.syncm__title { display: flex; align-items: center; gap: 10px; }
.syncm__gmark { width: 20px; height: 20px; flex: none; }
.syncm__prodhead { display: flex; align-items: center; gap: 10px; padding-bottom: 8px; margin-bottom: 4px; border-bottom: 1px solid var(--border); }
.syncm__prodmark { width: 20px; height: 20px; flex: none; }
.syncm__prodhead-t { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.syncm__prodhead-t strong { font-size: 14px; }
.syncm__prodhead-t small { color: var(--muted); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.syncm__progress { font-family: var(--font-mono); font-size: 12px; color: var(--accent); margin: 0; }
.syncm__running { display: flex; gap: 8px; align-items: center; }
.syncm__btn { font-family: var(--font-display); font-weight: 600; font-size: 13px; padding: 8px 14px; border-radius: 10px; border: 1px solid var(--border); background: var(--surface); color: var(--ink); cursor: pointer; }
.syncm__btn:hover { filter: brightness(1.04); }
.syncm__btn[disabled] { opacity: .5; cursor: default; }
.syncm__stop { color: var(--error); border-color: color-mix(in srgb, var(--error) 40%, var(--border)); }
/* The foot actions — SOLID buttons in their honest colors (Reconnect = accent, Disconnect = danger), no
   divider rule above (the card's own spacing carries the separation). */
.syncm__foot { display: flex; justify-content: space-between; align-items: center; padding-top: 4px; }
.syncm__act { font-family: var(--font-display); font-weight: 600; font-size: 13px; padding: 8px 14px; border-radius: 10px; border: 1px solid var(--accent); background: var(--accent); color: var(--accent-ink); cursor: pointer; text-decoration: none; }
.syncm__act:hover { filter: brightness(1.06); color: var(--accent-ink); }
.syncm__act--danger { background: var(--error); border-color: var(--error); color: #fff; }
.syncm__act--danger:hover { filter: brightness(1.06); color: #fff; }
.syncm__err { color: var(--error); font-size: 12px; margin: 0; }
body.syncm-open { overflow: hidden; }

/* The nav is the scrollable middle of the rail: it takes the space between the sync row and the pinned
   account foot, and scrolls when the sections don't fit (so a short screen can't push the foot off-bottom). */
.rail__nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; gap: 2px; margin-top: 6px; scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.rail__nav::-webkit-scrollbar { width: 7px; }
.rail__nav::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.rail__nav::-webkit-scrollbar-thumb:hover { background: var(--muted); }
.rail__item {
  display: flex; align-items: center; gap: 12px; padding: 10px 12px;
  border-radius: 10px; color: var(--muted); text-decoration: none; font-size: 15px;
}
.rail__item .icon { width: 20px; height: 20px; color: var(--accent); }
.rail__item:hover { background: var(--surface); color: var(--ink); }
.rail__item.is-active { background: var(--surface); color: var(--ink); font-weight: 600; }
.rail__item.is-active .icon { color: var(--accent); }
.rail__count {
  margin-left: auto; font-size: 12px; color: var(--muted);
  background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 0 7px;
  /* Always 600 so the active state's font-weight bump (.rail__item.is-active) doesn't
     re-weight the digits and jiggle the pill's width when you click between sections. */
  font-weight: 600;
}
/* Workflow nav POPOVER (dash-nav-items.hbs + nav.js) — the Workflows item navigates; the caret beside it
   opens a body-appended popover of direct /workflows/<id> links (not always-visible sub-items — Curtis's
   call). The caret hides wherever labels do (collapsed rail, mobile icon strip); the drawer keeps it.
   ⚠️ .navpop is BODY-APPENDED (the rail nav is a scroll container — an in-place dropdown would clip), so
   it must use GLOBAL tokens only — a tab-scoped custom property can't reach it (the .inb-menu trap). */
.rail__wfwrap { position: relative; }
.rail__wfwrap .rail__item { padding-right: 36px; } /* room for the caret riding the row's right edge */
.rail__disclose {
  /* right:12px + width:24px centers the chevron on the same column as the .rail__count pills
     (badge = 12px inset, ~24px wide at one digit), so the caret lines up with the numbers above it. */
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center; width: 24px; height: 24px;
  border: none; border-radius: 8px; background: none; color: var(--muted); cursor: pointer; padding: 0;
}
.rail__disclose .icon { width: 15px; height: 15px; }
/* Hover/open chip mixed from --ink, not a flat token — the caret sits on VARYING grounds (idle row = the
   rail's bg, active row = --surface, the drawer) and a --bg chip disappears against most of them. */
.rail__disclose:hover, .rail__disclose.is-open { background: color-mix(in srgb, var(--ink) 12%, transparent); color: var(--ink); }
.navpop {
  position: fixed; z-index: 80; /* above the mobile drawer (60) */
  min-width: 190px; max-width: 300px; padding: 5px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0,0,0,.24);
}
.navpop .navpop__link {
  display: block; padding: 8px 11px; border-radius: 8px;
  color: var(--ink); text-decoration: none; font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.navpop .navpop__link:hover { background: var(--bg); }
.navpop .navpop__link.is-active { font-weight: 600; color: var(--accent); }
.rail__foot { flex: none; padding-top: 10px; margin-top: 6px; border-top: 1px solid var(--border); } /* pinned below the scrollable nav; the account popover trigger (acct--up) sits here */

/* ── Mobile top bar (.mtop) — brand left, agent + account right. Hidden ≥880px. ── */
.mtop {
  display: none; position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  align-items: center; justify-content: space-between; gap: 10px;
  height: var(--mtop-h); padding: 0 14px; padding-top: env(safe-area-inset-top, 0px);
  background: var(--bg); border-bottom: 1px solid var(--border);
}
.mtop__left { display: flex; align-items: center; gap: 8px; min-width: 0; }
.mtop__brand { flex: none; gap: 10px; } /* match the desktop rail lockup's breathing room (base .brand's 5px jams the mark against the wordmark) */
.mtop__right { display: flex; align-items: center; gap: 8px; }
/* Top-bar hamburger — opens the nav DRAWER (2026-08-24: the rail defaults to hidden on a phone, so this is
   the everyday menu control). Shown only while the rail is hidden (html.nav-hidden) — a pinned-on rail
   brings its own hamburger. BARE at rest (Curtis, 2026-08-27: "remove the circles on the nav") — the circle
   exists only as FEEDBACK now; see the .mtop interaction block below. */
.mtop__burger {
  flex: none; display: none; place-items: center; width: 38px; height: 38px; padding: 0;
  border: 1px solid transparent; border-radius: 999px; background: none;
  color: var(--ink); cursor: pointer;
}
html.nav-hidden .mtop__burger { display: grid; }
/* THE SAME RULE FOR THE BELL, and for the same reason (Curtis, 2026-08-26: "there are two bells when the
   side rail is visible"). Both chrome surfaces render it — that's what makes it global — but on a phone
   they can be on screen AT ONCE when she pins the icon rail on, and then the one control appears twice.
   The rail's copy wins there, exactly as its hamburger does: whichever chrome is showing owns the bell. */
html:not(.nav-hidden) .mtop .bell { display: none; }
.mtop__burger .icon { width: 20px; height: 20px; color: var(--ink); }
/* ⭐ THE TOP BAR'S GLYPHS ARE BARE; THE CIRCLE IS FEEDBACK (Curtis, 2026-08-27: "remove the circles… can we
   also add little animations on click hover?"). Gmail's chrome grammar: quiet at rest, a soft circle fades
   in under the cursor, and a press squishes the control. One block covers the whole chrome family —
   hamburger · account emoji · agent switcher · the bell — so no control invents its own feedback. */
.mtop__burger, .mtop .acct-btn, .mtop .bswitch-btn, .mtop .bell {
  transition: background-color .16s ease, transform .09s ease;
}
.mtop__burger:hover, .mtop .acct-btn:hover, .mtop .bswitch-btn:hover { background: var(--surface); }
.mtop__burger:active, .mtop .acct-btn:active, .mtop .bswitch-btn:active, .mtop .bell:active {
  transform: scale(.86); background: var(--surface);
}
@media (prefers-reduced-motion: reduce) {
  .mtop__burger, .mtop .acct-btn, .mtop .bswitch-btn, .mtop .bell { transition: none; }
  .mtop__burger:active, .mtop .acct-btn:active, .mtop .bswitch-btn:active, .mtop .bell:active { transform: none; }
}

/* ── Agent switcher (.bswitch) — rail row by default; avatar circle in the mtop.
      The menu (switch / new / About) is identical in both. ───────────────────── */
.bswitch { position: relative; }
.bswitch-btn {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px;
  border: 1px solid transparent; border-radius: 12px; background: none;
  color: var(--ink); font-family: inherit; text-align: left; cursor: pointer;
}
.bswitch-btn:hover { background: var(--surface); }
/* Mid-birth: a non-interactive switcher (no menu, no caret) — just the newborn's
   ✨+name, filling in live. Mirrors .bswitch-btn padding so it sits identically. */
.bswitch--newborn { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px; }
.bswitch-avatar { font-size: 28px; line-height: 1; flex: none; }
.bswitch-label { display: flex; flex-direction: column; min-width: 0; }
.bswitch-name { font-family: var(--font-display); font-weight: 700; font-size: 17px; }
.bswitch-sub { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bswitch-caret { margin-left: auto; flex: none; font-size: 12px; color: var(--muted); }
/* Mobile top bar: avatar-only trigger — bare at rest, the circle is hover/press feedback (see .mtop block). */
.mtop .bswitch-btn { width: 38px; height: 38px; padding: 0; justify-content: center; border-color: transparent; border-radius: 999px; background: none; }
.mtop .bswitch-avatar { font-size: 22px; }
.mtop .bswitch-label, .mtop .bswitch-caret { display: none; }

.bswitch-menu {
  position: absolute; left: 0; top: calc(100% + 8px); z-index: 60;
  min-width: 220px; padding: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
}
.bswitch-menu[hidden] { display: none; }
.mtop .bswitch-menu { left: auto; right: 0; }
.bswitch-menu__label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); padding: 6px 10px 4px; }
.bswitch-menu__sep { height: 1px; background: var(--border); margin: 6px 4px; }
.bswitch-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; border-radius: 10px; text-decoration: none; color: var(--ink); font-size: 15px;
}
.bswitch-item:hover { background: var(--bg); }
.bswitch-item.is-active { font-weight: 600; }
.bswitch-item__avatar { display: grid; place-items: center; flex: none; width: 28px; height: 28px; border-radius: 999px; background: var(--bg); font-size: 16px; }
.bswitch-item__avatar--new, .bswitch-item__avatar--ghost { color: var(--muted); font-size: 12px; }
.bswitch-item__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bswitch-item__check { margin-left: auto; color: var(--accent); font-size: 12px; }

/* ── Account cluster (shared: rail foot + mobile top bar) ───────────────────── */
.acct { position: relative; }
.acct-btn {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px;
  border-radius: 12px; border: 1px solid transparent; background: none;
  color: var(--ink); font-family: inherit; text-align: left; cursor: pointer; line-height: 1.2;
}
.acct-btn:hover { background: var(--surface); }
.acct-avatar { font-size: 22px; line-height: 1; flex: none; }
.acct-avatar--lg { font-size: 26px; }
/* Both account + member avatars are now the .acct-avatar SPAN with their own emoji (the face
   Dustav set for the person — NOT the brand mark). The img form is kept sized in case any
   surface still points at a raster mark. */
img.acct-avatar { width: 22px; height: 22px; object-fit: contain; flex: none; }
img.acct-avatar--lg { width: 26px; height: 26px; }
.acct-btn__label { display: flex; flex-direction: column; min-width: 0; }
.acct-btn__name { font-size: 14px; font-weight: 600; }
.acct-btn__email { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 150px; }
/* Mobile top bar: avatar-only trigger (no label row) — bare at rest, the circle is hover/press feedback
   (see the .mtop interaction block). */
.mtop .acct-btn { width: 38px; height: 38px; padding: 0; justify-content: center; border-color: transparent; border-radius: 999px; background: none; }
.mtop .acct-btn__label { display: none; }

.acct-menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
  min-width: 210px; padding: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
}
/* Rail foot opens UPWARD and stretches to the rail width. */
.acct--up .acct-menu { top: auto; bottom: calc(100% + 8px); left: 0; right: 0; }
.acct-menu[hidden] { display: none; }
.acct-menu__head {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px 12px; margin-bottom: 6px; border-bottom: 1px solid var(--border);
}
.acct-email { font-size: 13px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-menu__form { margin: 0; }
.acct-menu__item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px; border: none; background: none; border-radius: 10px;
  color: var(--ink); font-size: 15px; font-family: inherit; text-align: left;
  text-decoration: none; cursor: pointer;
}
.acct-menu__item:hover { background: var(--bg); }
.acct-menu__item .icon { width: 18px; height: 18px; color: var(--accent); }
.acct-menu__item--dev { color: var(--muted); }
/* Theme toggle icon shows the ACTION: moon (→ go dark) in light mode, sun (→ go light) in dark.
   app-header.js flips .is-dark; the sun is hidden until then. */
.theme-toggle__i--sun { display: none; }
.theme-toggle.is-dark .theme-toggle__i--moon { display: none; }
.theme-toggle.is-dark .theme-toggle__i--sun { display: inline-block; }

/* ── The desktop rail collapse toggle + collapsed (icons-only) state ─────────── */
/* The foot button that shrinks the rail. html.nav-collapsed is applied pre-paint by nav-collapse.js
   (from localStorage) and toggled by nav.js. Chevron points left (collapse) → flips right when collapsed. */
.rail__collapse {
  flex: none; display: flex; align-items: center; justify-content: flex-start; width: 100%;
  padding: 6px 12px; margin-bottom: 6px; border: 1px solid transparent; border-radius: 10px;
  background: none; color: var(--muted); cursor: pointer;
}
.rail__collapse:hover { background: var(--surface); color: var(--ink); }
.rail__collapse .icon { width: 18px; height: 18px; transition: transform 0.2s; }
@media (prefers-reduced-motion: reduce) { .rail__collapse .icon { transition: none; } }

/* The rail's own hamburger (mobile only — desktop uses the foot collapse chevron). Shown at the top of
   the icon strip by the mobile query below; hidden on desktop. */
.rail__burger { display: none; }
/* The drawer's "Hide sidebar" button (in the labeled drawer foot). A quiet full-width action. */
.navdrawer__hide {
  display: flex; align-items: center; gap: 10px; width: 100%; margin-bottom: 8px;
  padding: 10px; border: none; border-radius: 10px; background: none; color: var(--muted);
  font: inherit; font-size: 14px; text-align: left; cursor: pointer;
}
.navdrawer__hide:hover { background: var(--surface); color: var(--ink); }
.navdrawer__hide .icon { width: 16px; height: 16px; }
/* The pin toggle's chevron points the way the rail will MOVE: ‹ hide (rail visible) · › show (rail hidden —
   the default since 2026-08-24). nav.js swaps the label text; the flip is pure CSS off the same class. */
html.nav-hidden .navdrawer__hide .icon { transform: rotate(180deg); }

/* Collapsed: the rail narrows to a 60px icon strip. Labels hide, the wordmark hides, count badges
   become a dot pinned on the icon, and the sync + account foot go icon-only. Scoped to <html> (not body)
   so the pre-paint script in <head> can apply it before <body> exists. EVERY descendant rule is scoped
   under `.rail` because the mobile nav drawer reuses the same .rail__item / .rail__count / .rail__sync /
   .acct-btn markup (shared partials) — an unscoped html.nav-collapsed would collapse the drawer (and the
   mtop account button) too. And the WHOLE block is desktop-only (min-width:880px): on mobile the rail is
   ALWAYS an icon strip (see the mobile query below), governed there — so the collapse pref must not reach
   it (else its width/padding would fight the mobile strip's). */
@media (min-width: 880px) {
  html.nav-collapsed .rail { width: 60px; padding: 18px 8px; }
  html.nav-collapsed .rail .rail__brand { justify-content: center; padding: 4px 0 12px; gap: 0; }
  html.nav-collapsed .rail .brand-wordmark { display: none; }
  /* The bell FALLS BELOW BLINK on the icon strip (Curtis, 2026-08-26, on the live rail). Side by side the
     pair is 20px mark + 8px gap + 30px bell = 58px in a 44px strip: it overflows, and centring the overflow
     puts BOTH glyphs off the optical centre line every icon below them shares. Stacked, the mark stays the
     head of the strip and the bell is the first glyph under it, dead centre like the rest. The brand's 12px
     bottom pad (which separates the lockup from the section rows on the wide rail) shrinks here — the bell
     now sits in that gap, and the head's own bottom pad carries the separation instead. */
  html.nav-collapsed .rail .rail__head { flex-direction: column; align-items: center; gap: 2px; padding-bottom: 8px; }
  html.nav-collapsed .rail .rail__head .rail__brand { padding: 4px 0 0; }
  html.nav-collapsed .rail .rail__item { justify-content: center; gap: 0; padding: 10px 0; position: relative; }
  html.nav-collapsed .rail .rail__item > span:not(.rail__count) { display: none; } /* the text label (unclassed span) */
  html.nav-collapsed .rail .rail__disclose { display: none; } /* the workflow caret goes with the labels on an icon strip */
  /* The count pill → an 8px accent dot on the icon's top-right. Text hidden via font-size:0; a --bg ring
     lifts it off the icon. Stays `hidden` when the count is 0 (the [hidden] attr still wins). */
  html.nav-collapsed .rail .rail__count {
    position: absolute; top: 4px; right: 10px; min-width: 0; width: 8px; height: 8px; padding: 0;
    font-size: 0; border-radius: 999px; background: var(--accent); color: transparent;
    border: 1.5px solid var(--bg);
  }
  html.nav-collapsed .rail .rail__sync { padding: 2px 0 10px; }
  html.nav-collapsed .rail .rail__sync-label, html.nav-collapsed .rail .rail__sync-count { display: none; }
  html.nav-collapsed .rail .rail__sync-pill { padding: 9px 0; justify-content: center; }
  html.nav-collapsed .rail .rail__collapse { justify-content: center; padding: 6px; } /* icon strip → re-center */
  html.nav-collapsed .rail .rail__collapse .icon { transform: rotate(180deg); } /* points right = expand */
  html.nav-collapsed .rail .acct-btn { justify-content: center; padding: 8px 0; }
  html.nav-collapsed .rail .acct-btn__label { display: none; }
  /* The account popover opens upward; keep its real width instead of stretching to the 60px rail. */
  html.nav-collapsed .rail .acct--up .acct-menu { left: 0; right: auto; min-width: 210px; }
}

/* Section-nav loading state (public/js/nav-loading.js): on click, hide the icon and
   show a small spinner in its place, so a full-page navigation reads as "loading", not
   a dead click. Same ring as the files reader (.glass-spinner / glass-spin), sized to
   the nav icon — 20px on the rail, 18px in the account menu (Settings / Usage / Docs /
   Log out; Dark mode is excluded — it's an in-place toggle). */
.rail__item.is-loading .icon,
.acct-menu__item.is-loading .icon { display: none; }
.rail__item.is-loading::before,
.acct-menu__item.is-loading::before {
  content: ""; flex: none; box-sizing: border-box;
  border: 2px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: glass-spin 0.7s linear infinite;
}
.rail__item.is-loading::before { width: 20px; height: 20px; }
.acct-menu__item.is-loading::before { width: 18px; height: 18px; }
@media (prefers-reduced-motion: reduce) {
  .rail__item.is-loading::before,
  .acct-menu__item.is-loading::before { animation: none; } /* static ring still reads as a status indicator */
}

/* ── The pane content: a readable column, centered. Files/Skills opt wider. ─── */
.tabpane { max-width: var(--content-max); margin: 0 auto; padding: 28px 24px 56px; }
.tabpane.hidden { display: none; }
#tab-skills { max-width: 1080px; }

/* ── Mobile (<880px): a slim top bar + full-width content by DEFAULT (2026-08-24, Curtis from Bre's phone:
      "phone space is precious" — reverses the persistent-icon-rail default). The top-bar hamburger opens the
      full labeled drawer; the icon rail is OPT-IN via the drawer's "Show icon rail" pin (html.nav-hidden is
      the default state, pre-painted by nav-collapse.js). A pinned-on rail works as before: fixed icon strip,
      its own hamburger expands the drawer, full-height BEHIND the copilot, the pane insets to clear it. ── */
@media (max-width: 879px) {
  .mtop { display: flex; }
  .pane { padding-left: var(--mrail-w); } /* clear the fixed icon rail */
  html.nav-hidden .pane { padding-left: 0; } /* rail hidden → reclaim the width */

  /* The rail is a fixed icon strip below the top bar, running to the very bottom (bottom:0) so it sits
     BEHIND the full-width copilot. z-40 = under .mtop (50), the copilot (45), and the expand drawer (60). */
  .rail {
    /* Explicit height (top + calc) instead of top+bottom+auto — a suite page (#tab-chat) sets
       height:100dvh on its column, and the top/bottom/auto stretch was resolving short there, so the
       border-right didn't reach the bottom. An explicit height paints the full-height border reliably. */
    display: flex; position: fixed; top: var(--mtop-h); left: 0;
    width: var(--mrail-w); height: calc(100dvh - var(--mtop-h)); padding: 8px 6px; z-index: 40; gap: 4px;
    transition: transform 0.28s cubic-bezier(0.34, 1.1, 0.64, 1);
  }
  html.nav-hidden .rail { transform: translateX(-100%); } /* hidden = slid offscreen left */
  @media (prefers-reduced-motion: reduce) { .rail { transition: none; } }
  .rail .rail__burger { /* the expand-to-labels hamburger at the top of the strip */
    display: flex; align-items: center; justify-content: center; flex: none; width: 100%;
    padding: 8px 0; margin-bottom: 2px; border: none; border-radius: 10px; background: none;
    color: var(--muted); cursor: pointer;
  }
  .rail .rail__burger:active { background: var(--surface); color: var(--ink); }
  .rail .rail__burger .icon { width: 22px; height: 22px; }
  /* The nav scrolls; pad its bottom so the last icons clear the full-width copilot peek that overlaps
     the rail's bottom edge. */
  .rail .rail__nav { padding-bottom: calc(var(--copilot-peek) + env(safe-area-inset-bottom) + 8px); }
  .rail .rail__brand { display: none; }  /* the brand lives in the top bar */
  .rail .rail__foot { display: none; }   /* account + the collapse toggle are desktop / top-bar chrome */
  .rail .rail__item { justify-content: center; gap: 0; padding: 10px 0; position: relative; }
  .rail .rail__item > span:not(.rail__count) { display: none; } /* the text label (unclassed span) */
  .rail .rail__disclose { display: none; } /* the workflow caret goes with the labels — drawer-only on mobile (scoped .rail = the strip, not the drawer) */
  .rail .rail__count { /* the count pill → an accent dot on the icon */
    position: absolute; top: 2px; right: 6px; min-width: 0; width: 8px; height: 8px; padding: 0;
    font-size: 0; border-radius: 999px; background: var(--accent); color: transparent; border: 1.5px solid var(--bg);
  }
  .rail .rail__sync { padding: 2px 0 8px; }
  .rail .rail__sync-label, .rail .rail__sync-count { display: none; }
  .rail .rail__sync-pill { padding: 9px 0; justify-content: center; }

  .tabpane { max-width: 640px; padding: calc(var(--mtop-h) + 16px) 16px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 16px); }
  #tab-skills { max-width: 640px; }
  /* Re-match the pane's mobile padding so the hero still bleeds to the column edges + the top bar. */
  .fam-hero { margin: calc(-1 * (var(--mtop-h) + 16px)) -16px 24px; padding: 32px 16px 28px; }
}

/* ── Mobile left nav drawer (docs/NAV.md) — mirrors the desktop rail; slides in from the LEFT over a
      scrim, opened by the top-bar hamburger. Same mechanics as the copilot mobile drawer (transform
      slide + fading scrim + Esc/scrim-tap close, driven by nav.js) but its own element + state, so the
      two drawers (nav left, copilot right) never conflict. In the DOM on every dashboard page; hidden
      ≥880px (the desktop rail takes over). The inner nav reuses .rail__nav / .rail__item / .rail__count /
      .rail__sync / .rail__foot (shared partials) so it's pixel-identical to the rail and the live badge
      repaint hits it for free. ── */
.navdrawer, .navdrawer__scrim { display: none; }
body.nav-drawer-open { overflow: hidden; } /* lock the page scroll behind the open sheet */
@media (max-width: 879px) {
  .navdrawer {
    display: block; position: fixed; top: 0; bottom: 0; left: 0; z-index: 60; /* above .mtop (50) */
    width: min(80vw, 340px);
    transform: translateX(-100%); pointer-events: none;
    transition: transform 0.32s cubic-bezier(0.34, 1.15, 0.64, 1); will-change: transform;
  }
  .navdrawer.is-open { transform: translateX(0); pointer-events: auto; }
  @media (prefers-reduced-motion: reduce) { .navdrawer { transition: none; } }
  .navdrawer__panel {
    display: flex; flex-direction: column; height: 100%;
    background: var(--bg); border-right: 1px solid var(--border);
    box-shadow: 6px 0 24px rgba(0, 0, 0, 0.2);
    padding: 12px 12px calc(12px + env(safe-area-inset-bottom));
  }
  .navdrawer__head { flex: none; display: flex; align-items: center; justify-content: space-between; padding: 4px 6px 10px; }
  .navdrawer__head .brand-wordmark { font-size: 20px; }
  .navdrawer__close { flex: none; display: grid; place-items: center; width: 36px; height: 36px; padding: 0; border: none; background: none; color: var(--ink); cursor: pointer; }
  .navdrawer__close .icon { width: 20px; height: 20px; }
  .navdrawer__nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; margin-top: 4px; } /* reuse .rail__nav flex/scroll */
  .navdrawer__foot { flex: none; }
  /* The scrim dims the rest of the screen and closes on tap; z under the drawer (60) but over .mtop (50)
     so the whole background (top bar included) dims while the drawer's open. */
  .navdrawer__scrim {
    display: block; position: fixed; inset: 0; z-index: 55; background: rgba(0, 0, 0, 0.5);
    opacity: 0; transition: opacity 0.28s ease; -webkit-tap-highlight-color: transparent;
  }
  .navdrawer__scrim.is-visible { opacity: 1; }
}

/* ── Chat — a native thread filling the WHOLE pane: full-width header + scroller
      + composer, each with its inner content centered to reading width. The
      scrollbar rides the panel edge; the text stays in a tidy column. ───────── */
#tab-chat {
  max-width: none; margin: 0;            /* fill the pane, edge to edge */
  display: flex; flex-direction: column;
  height: 100dvh; padding: 0;            /* the three bars own their padding */
}

/* Fixed header bar — spans the pane, content centered. */
.chat-head { flex: none; border-bottom: 1px solid var(--border); padding: 12px 24px; }
.agent-strip { display: flex; align-items: center; gap: 8px; max-width: var(--chat-max); margin: 0 auto; }
.agent-strip__avatar { font-size: 22px; }
.agent-strip__name { font-weight: 600; }
.agent-strip__copy { margin-left: auto; font-size: 15px; padding: 5px 12px; }
.agent-strip__restart { font-size: 15px; padding: 5px 12px; color: var(--muted); }
.agent-strip__restart:hover { color: var(--error); }
.onb-restart-msg { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.5; }

/* The scroller IS the panel; each row centers itself to the (wide) chat width. */
.messages { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; padding: 16px 24px; }
.messages > .ce-row { width: 100%; max-width: var(--chat-max); margin-left: auto; margin-right: auto; }
.note { align-self: center; font-size: 12px; color: var(--muted); }

/* Composer docked at the foot — full-width bar, content centered. The input is a
   <textarea> so long messages word-wrap; it auto-grows one line at a time (chat-core)
   up to max-height, then scrolls. align-items:flex-end keeps the send button on the
   last line as it grows. */
.chat-form { flex: none; border-top: 1px solid var(--border); padding: 12px 24px; }
.chat-form__inner { display: flex; gap: 10px; align-items: flex-end; max-width: var(--chat-max); margin: 0 auto; }
/* (The per-composer .chat-cost readout was removed 2026-07-22 — a live money meter under the composer
   made a validated user ration her messages. Cost lives on the dedicated Usage & cost page now.) */
.chat-form textarea {
  flex: 1; padding: 11px 18px; border-radius: 14px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink);
  font-size: 16px; font-family: inherit; line-height: 1.4; box-sizing: border-box;
  resize: none; min-height: calc(1.4em + 24px); max-height: 160px; overflow-y: hidden;
}
.chat-form textarea:focus { outline: none; border-color: var(--accent); }
.chat-form button[type="submit"] {
  flex: none; width: 44px; height: 44px; padding: 0; border-radius: 999px;
  display: grid; place-items: center; font-size: 16px;
}
.chat-tip {
  max-width: var(--content-max); margin: 8px auto 0; padding: 0 6px;
  font-size: 12px; color: var(--muted); text-align: center;
}
.chat-tip kbd {
  font: inherit; background: var(--surface); border: 1px solid var(--border);
  border-radius: 5px; padding: 1px 6px; color: var(--ink);
}

/* ── Timeline home — the chat thread wrapped as a calendar+notes agenda feed
      (TEARDOWN.md). Header + agenda pills + empty-state sit above the existing
      thread; the quick-action chips sit inside the composer. All reuse the chat column. ─ */
.tl-head__inner { display: flex; align-items: center; gap: 12px; max-width: var(--chat-max); margin: 0 auto; }
.tl-head__title { font-weight: 700; font-size: 18px; }
.tl-head__cal { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; font-size: 14px; color: var(--muted); }
.tl-head__cal svg { width: 16px; height: 16px; }
.tl-head__cal:hover { color: var(--accent); }

.tl-keynudge {
  flex: none; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  max-width: var(--chat-max); margin: 12px auto 0; padding: 12px 16px; box-sizing: border-box;
  border: 1px solid var(--accent); border-radius: 12px; background: var(--surface); font-size: 14px;
}
.tl-keynudge > span { flex: 1; min-width: 180px; }
.btn-sm { padding: 6px 14px; font-size: 14px; }

.tl-agenda {
  flex: none; display: flex; gap: 8px; overflow-x: auto; scrollbar-width: thin;
  max-width: var(--chat-max); width: 100%; margin: 12px auto 0; padding: 4px 24px; box-sizing: border-box;
}
.tl-event {
  flex: none; display: flex; flex-direction: column; gap: 2px; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
  min-width: 128px; max-width: 220px;
}
.tl-event:hover { border-color: var(--accent); }
.tl-event__when { font-size: 12px; color: var(--accent); font-weight: 600; white-space: nowrap; }
.tl-event__title { font-size: 14px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.tl-empty { flex: none; max-width: var(--chat-max); margin: 24px auto; padding: 0 24px; box-sizing: border-box; text-align: center; }
.tl-empty__lead { margin: 0 0 6px; font-weight: 600; font-size: 16px; }
.tl-empty__sub { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.5; }

.tl-quick {
  display: flex; gap: 8px; overflow-x: auto; scrollbar-width: thin;
  max-width: var(--chat-max); margin: 0 auto 10px; padding-bottom: 2px;
}
.tl-chip {
  flex: none; display: inline-flex; align-items: center; gap: 6px; padding: 7px 13px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
  color: var(--ink); font-size: 13px; font-family: inherit; cursor: pointer; white-space: nowrap;
}
.tl-chip:hover { border-color: var(--accent); color: var(--accent); }
.tl-chip svg { width: 15px; height: 15px; }

/* ── Suite copilot panel (views/partials/copilot-panel.hbs) — a REUSABLE side panel (calendar
      today, notes next) that mounts the shared chat component (chat-core.js). Desktop: a thin,
      collapsible right rail beside the suite (default open; collapses to a 46px icon rail, saved).
      Mobile: a bottom DRAWER — a peek bar above the tab bar that taps up into a full-height sheet.
      One class drives each mode: .is-collapsed (desktop rail) and .is-open (mobile drawer), each
      scoped to its breakpoint so they never fight. ── */
.copilot {
  /* Scale with the viewport instead of a fixed 340px: a 340 floor (small desktops unchanged), grows
     with the screen, capped at 480 so it never gets absurd. On a wide monitor the chat breathes
     instead of staying pinned narrow next to acres of empty content. (Body min-width matches the 340
     floor, so it never overflows the panel at its narrowest.) */
  flex: none; width: clamp(340px, 24vw, 480px); align-self: stretch;
  display: flex; flex-direction: column; min-height: 0;
  border-left: 1px solid var(--border); background: var(--bg);
  overflow: hidden; /* clip the body while it slides, so nothing spills during the width animation */
}
/* Cheeky slide when you click "Ask Dustav" — the panel width springs open/closed with a little
   overshoot. Gated on .copilot--anim (added by copilot.js one frame after load) so a restored-collapsed
   panel doesn't animate itself open→shut on page load. The body is pinned to full width (below) so the
   chat text is REVEALED by the slide rather than reflowing mid-animation. */
.copilot.copilot--anim { transition: width 0.32s cubic-bezier(0.34, 1.15, 0.64, 1), top 0.32s cubic-bezier(0.34, 1.15, 0.64, 1); }
@media (prefers-reduced-motion: reduce) { .copilot.copilot--anim { transition: none; } }

/* The handle: desktop = a header row (icon · label · collapse chevron); collapsed = just the icon.
   Mobile = the peek bar / drawer header (see the media query). */
.copilot__handle {
  flex: none; display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 11px 14px; border: none; border-bottom: 1px solid var(--border);
  background: var(--bg); color: var(--ink); font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
}
/* "Ask Dustav" stays --ink always — no accent tint on hover/focus. */
.copilot__handle:hover, .copilot__handle:focus { color: var(--ink); }
/* Same box (26px) + mark (21px) + centering as the chat avatar (.copilot .ce-avatar), so the
   mark→name gap on the handle exactly matches the gap on a "Dustav" message row (both + an 8px
   flex gap). */
.copilot__handle-icon { display: grid; place-items: center; width: 26px; flex: none; }
.copilot__handle-icon svg { width: 21px; height: 21px; }
.copilot__handle-label { flex: 1; text-align: left; white-space: nowrap; overflow: hidden; }
/* The span carries the hover STEP (translate), the svg carries the DIRECTION (rotate) — two transforms
   on two elements, because one element can only hold one transform property and the second would
   silently drop the first (the arrow would snap upright mid-hover). */
.copilot__handle-chev { display: inline-flex; color: var(--muted); transition: transform .16s ease, color .14s; }
.copilot__handle-chev svg { width: 16px; height: 16px; transition: transform .15s; }

/* justify-end pins the composer to the bottom even if the message list doesn't grow to fill (few
   messages) — no dead band below the composer — and hugs a short thread to the composer, chat-style. */
.copilot__body { flex: 1; min-height: 0; min-width: 340px; display: flex; flex-direction: column; justify-content: flex-end; }

/* ⭐ COLLAPSED (desktop) RIDES <html>, NOT THE PANEL — so it can be PRE-PAINTED. The preference was
   always saved; it was just APPLIED by the deferred copilot.js, one paint too late, so a collapsed
   panel drew full-width and then snapped shut on every load (Curtis, 2026-08-26: "if I collapse the
   copilot and then reload the page there is a flicker"). Nothing in <body> exists during <head>, so a
   class on <html> is the only thing a pre-paint script can set — the same fix, and the same shape, as
   theme.js and html.nav-collapsed (both applied by /js/nav-collapse.js, which now reads this key too).
   The whole block is DESKTOP-ONLY on purpose: below the breakpoint the panel is a bottom drawer whose
   handle IS the bar, and these rules would restyle it (column handle, hidden label) — so the pre-paint
   class, which can't know the breakpoint it'll be resized into, must be inert there. That used to be
   enforced by copilot.js simply never setting the class on a phone, which left it a live bug across a
   desktop→mobile RESIZE; scoping it here makes the comment above true. */
@media (min-width: 880px) {
  html.copilot-collapsed .copilot { width: 46px; }
  html.copilot-collapsed .copilot__body { display: none; }
  html.copilot-collapsed .copilot__handle { flex-direction: column; gap: 7px; padding: 16px 0; border-bottom: none; }
  html.copilot-collapsed .copilot__handle-label { display: none; }
  /* The chevron SURVIVES the collapse, under the mark, pointing LEFT (Curtis, 2026-08-26: "a little left
     pointing arrow underneath him"). The strip used to be Blink alone — a 46px column with no affordance
     on it, so "pull the panel back out" was something you had to already know. Same grammar as the rail's
     own collapse chevron: it points the way the panel will MOVE. ‹ = come back · › = go away. */
  html.copilot-collapsed .copilot__handle-chev svg { transform: rotate(90deg); }  /* points left = expand */
  /* HOVER: the whole strip is the button, so the whole strip answers — a soft surface fill behind the
     column, and the arrow brightens and steps LEFT, in the direction it's promising to go. The nudge
     rides the SPAN, never the svg: the svg's transform is holding the rotation, and a second transform
     there would overwrite it (the arrow would snap upright mid-hover). */
  html.copilot-collapsed .copilot__handle { border-radius: 10px; transition: background .14s; }
  html.copilot-collapsed .copilot__handle:hover,
  html.copilot-collapsed .copilot__handle:focus-visible { background: var(--surface); }
  /* …and the SAME beat expanded, where the arrow points the other way, so the affordance behaves
     identically in both states and only its direction changes (Curtis: "give some animation to the
     right arrow as well"). The step always follows the arrow: ‹ steps left, › steps right. */
  html.copilot-collapsed .copilot__handle:hover .copilot__handle-chev,
  html.copilot-collapsed .copilot__handle:focus-visible .copilot__handle-chev { transform: translateX(-3px); color: var(--ink); }
  html:not(.copilot-collapsed) .copilot__handle:hover .copilot__handle-chev,
  html:not(.copilot-collapsed) .copilot__handle:focus-visible .copilot__handle-chev { transform: translateX(3px); color: var(--ink); }
  html:not(.copilot-collapsed) .copilot__handle-chev svg { transform: rotate(-90deg); } /* points right = collapse */
}
/* Reduced motion keeps the brighten and drops the step. Same specificity as the two hover rules above
   and later in the file, so it wins on source order; scoped to the span, so the svg's rotation (the
   thing that makes the arrow point anywhere at all) is untouched. */
@media (prefers-reduced-motion: reduce) {
  html .copilot__handle:hover .copilot__handle-chev,
  html .copilot__handle:focus-visible .copilot__handle-chev { transform: none; }
  html .copilot__handle-chev { transition: color .14s; }
}
/* The close X is mobile-drawer chrome only — shown by the open-drawer rule in the media query below.
   (The scrim stays inert via its `hidden` attribute, toggled by copilot.js on mobile open/close.) */
.copilot__close { display: none; }
.copilot__keynudge { flex: none; font-size: 13px; color: var(--muted); padding: 10px 14px; border-bottom: 1px solid var(--border); }
.copilot__keynudge a { color: var(--accent); }

.copilot__messages { flex: 1; min-height: 0; overflow-y: auto; padding: 8px 12px; display: flex; flex-direction: column; }
/* The composer reuses .chat-form's inner row/controls but supplies its own frame (chat-form's
   default border/padding is tuned for the full-width chat page). */
.copilot__composer.chat-form { flex: none; border-top: 1px solid var(--border); padding: 10px 12px calc(10px + var(--dock-inset, 0px)); }
.copilot__composer .chat-form__inner { max-width: none; }

/* Compact chat inside the panel — smaller avatars/type, tighter rows, inline name + time. The
   per-row copy actions (Copy / Copy to end) stay ON here too: hover-reveal on desktop, tap-a-
   message on mobile — so you can grab a single reply or the whole "to-now" transcript (tool
   usages included) to paste elsewhere. */
.copilot .ce-row { gap: 8px; margin: 8px 0; }
.copilot .ce-avatar { width: 26px; height: 14px; font-size: 21px; } /* bigger glyph, box ~ the name line so it centers on it */
/* One uniform 14px chat surface — names, message text, and composer all match. */
.copilot .ce-meta { font-size: 14px; gap: 6px; }
.copilot .ce-text { font-size: 14px; line-height: 1.5; }
.copilot .ce-row.user .ce-text { padding: 7px 11px; }
.copilot .chat-form textarea { font-size: 14px; min-height: calc(1.4em + 20px); }
/* The ONE exception: on phones the composer must be ≥16px or iOS zooms + clips the layout on focus.
   Desktop stays a uniform 14px; only the input nudges up, and only on small screens. */
@media (max-width: 879px) {
  .copilot .chat-form textarea { font-size: 16px; }
}
/* Session boundary — sits below the hydrated (display-only) history; anything below it is the
   fresh session the model actually sees. A centered hairline with a quiet label. */
.copilot__divider {
  flex: none; display: flex; align-items: center; gap: 10px;
  margin: 12px 2px 6px; color: var(--muted); font-size: 11px; user-select: none;
}
.copilot__divider::before, .copilot__divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }

@media (max-width: 879px) {
  /* The panel becomes a bottom drawer: closed = the peek-bar handle above the tab bar; open = a
     full-height sheet (from the content top to just above the tab bar). */
  .copilot {
    /* --copilot-peek is a global token (:root) — the closed drawer height = the docked composer row,
       and suite content reserves the same value as bottom clearance so nothing hides behind it. */
    /* z-index 45 = a CONTENT-overlay layer: above the page/calendar, but BELOW the fixed chrome
       (.mtop + .tabbar at z-50). The account/settings menu drops out of .mtop's own stacking context,
       so the drawer must sit under that layer or it covers the menu. The drawer never overlaps the top
       bar or tab bar anyway (it's anchored between mtop-h and the tab bar), so nothing is lost. */
    /* Full-width, bottom-fixed, ALWAYS (docs/NAV.md, Curtis's call) — it spans the whole width and the
       icon rail runs full-height BEHIND it (rail z-40 < copilot z-45). */
    position: fixed; left: 0; right: 0; z-index: 45;
    background: var(--surface); /* a raised shade, distinct from the page bg, so the bar reads as a bar */
    /* Anchored to the PHYSICAL bottom (the drawer's background fills the home-indicator curve); the
       closed window grows by --dock-inset so the peek row sits clear of the indicator. Never lift the
       bar by the inset — that's the void-under-the-dock bug (2026-08-27). */
    bottom: var(--tabbar-h);
    /* Explicit CLOSED top (peek height above the tab bar) so `top` has a concrete value to animate
       FROM — a bare `bottom` with auto height can't transition (top:auto → mtop-h won't tween). The
       base overflow:hidden clips the body to this height, so the drawer slides open to reveal it. */
    top: calc(100dvh - var(--tabbar-h) - var(--copilot-peek) - var(--dock-inset));
    width: auto; align-self: auto; border-left: none; border-top: 1px solid var(--border);
    /* Rounded top corners on the fly-up, matching the thread reply composer (.ck-comp) so the two
       bottom-sheets read as ONE format. The base .copilot overflow:hidden clips to the radius. */
    border-radius: 15px 15px 0 0; box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.22);
    display: flex; flex-direction: column; /* re-assert: the drawer must be a flex column so the
      body fills top→bottom and the composer pins to the bottom (never inherit-only from desktop). */
  }
  /* (No "neutralize the desktop rail width" rule any more — the collapsed block is scoped to ≥880 now,
     so nothing of the desktop rail reaches the drawer to be undone.) */

  /* ── Mobile interaction model: TAP TO OPEN, THEN TAP TO TYPE ──
     Focusing an input pops the keyboard, which would bury the very conversation the user needs to
     orient on — so opening must NOT focus. The closed peek is a plain "Ask Dustav" BUTTON (mark ·
     label · up-chevron · grabber); one tap opens the 80% modal with NO keyboard, so the user reads
     the thread first. The real composer lives INSIDE the modal — they tap it themselves (a deliberate
     2nd tap) to type. Two taps is the floor here; one tap would be a worse experience. */

  /* The handle IS the bar in both states: closed = the "Ask Dustav" open-button; open = the title bar
     (with the X). The grabber pill rides the top edge in both. */
  .copilot__handle {
    display: flex; position: relative; padding: 13px 14px;
    border-bottom: none; background: var(--surface);
  }
  .copilot__handle::before { /* grabber pill = the bottom-sheet "tap/drag" affordance */
    content: ""; position: absolute; top: 5px; left: 50%; transform: translateX(-50%);
    width: 34px; height: 4px; border-radius: 999px; background: var(--border);
  }
  .copilot__handle-label { font-weight: 600; }
  .copilot__handle-chev { display: inline-flex; color: var(--accent); } /* accent up-chevron = "expand" */
  /* The closed mobile bar's chevron points UP (tap to open). It no longer has to out-specify the
     desktop rail's rotate(-90deg) — that rule lives in the ≥880 block and never matches here. */
  .copilot__handle-chev svg { transform: rotate(180deg); }
  .copilot.is-open .copilot__handle { border-bottom: 1px solid var(--border); padding: 12px 14px 10px; }

  /* Body (messages + composer) only exists when open; closed is just the button bar. */
  .copilot__body { display: none; }
  .copilot.is-open .copilot__body { display: flex; flex: 1; min-width: 0; min-height: 0; }

  /* Open ~80%, NOT full-height — leave a strip of the calendar visible up top for context, dimmed by
     the scrim (a bottom-sheet modal). top:20dvh clears the fixed top bar (54px) on any screen. */
  .copilot.is-open { top: 20dvh; border-top: none; }

  /* Scrim: dims the background above the sheet and closes on tap. z-index 44 = under the sheet (45)
     but over the page; the fixed top bar + tab bar (z50) stay above it, so nav is still usable. */
  .copilot__scrim {
    position: fixed; inset: 0; z-index: 44; background: rgba(0, 0, 0, 0.5);
    opacity: 0; transition: opacity 0.28s ease; -webkit-tap-highlight-color: transparent;
  }
  .copilot__scrim.is-visible { opacity: 1; }

  /* Explicit close X, top-right of the open sheet (the whole title bar also closes; this is the big
     obvious target). Replaces the chevron when open. */
  .copilot.is-open .copilot__handle-chev { display: none; }
  /* Centre the X on the handle's content row (mark + label). The handle is padding 12/10 over a ~21px
     row → its vertical centre sits ~22px from the sheet top; anchor there and translate so the X's own
     height doesn't matter (stays centred if the tap target changes). */
  .copilot.is-open .copilot__close {
    display: grid; place-items: center; position: absolute; top: 22px; transform: translateY(-50%);
    right: 10px; z-index: 3; width: 34px; height: 34px; padding: 0; border: none;
    background: none; color: var(--ink); cursor: pointer;
  }
  .copilot.is-open .copilot__close:active { color: var(--muted); }
  .copilot.is-open .copilot__close svg { width: 19px; height: 19px; }

  /* The drawer already clears the tab bar via its own `bottom`, so the composer must NOT re-add the
     chat page's tab-bar clearance (the shared .chat-form mobile rule sets margin-bottom: tabbar+safe).
     Zero it here and drop the double-counted safe-area padding. */
  .copilot__composer.chat-form { margin-bottom: 0; padding: 10px 12px; }
}

/* ── /files Context tab — reuses the glass box (list → reader). Each section renders as markdown
      prose in the shared .md column (glassbox.renderMd), exactly like the dustav.com glass box. `.is-live`
      accents the volatile section (the live calendar, re-read every turn). ── */
.glass-item.is-live .glass-item__icon { color: var(--accent); }

/* ── Image attachments in chat (docs/IMAGE_INPUT.md) ───────────────────────── */
/* Photo button — a secondary round control matching the send button's footprint. */
.chat-photo {
  flex: none; width: 44px; height: 44px; padding: 0; border-radius: 999px;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--border); background: var(--panel); color: var(--ink);
}
.chat-photo .icon { width: 23px; height: 23px; }
.chat-photo:hover { color: var(--accent); border-color: var(--accent); }
.chat-photo:active { transform: scale(0.96); }
.chat-photo:disabled { opacity: 0.45; cursor: default; }
/* Pending-attachment strip — small thumbnails above the composer row. */
.chat-attachments {
  display: flex; flex-wrap: wrap; gap: 8px;
  max-width: var(--chat-max); margin: 0 auto 8px; padding: 0 6px;
}
.chat-att { position: relative; width: 56px; height: 56px; border-radius: 10px; overflow: hidden; border: 1px solid var(--border); background: var(--panel); }
.chat-att img { width: 100%; height: 100%; object-fit: cover; display: block; }
.chat-att.uploading { opacity: 0.5; }
/* Non-image pending file — a named chip, never a bare <img> (the broken-image-flash fix). */
.chat-att--file {
  width: auto; max-width: 180px; height: 56px; padding: 0 26px 0 10px;
  display: flex; align-items: center; gap: 6px; overflow: hidden;
}
.chat-att__ico { flex: none; width: 18px; height: 18px; color: var(--muted); }
.chat-att__ico svg { width: 100%; height: 100%; display: block; }
.chat-att__name { font-size: 12px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-att__x {
  position: absolute; top: 2px; right: 2px; width: 18px; height: 18px;
  padding: 0; border: none; border-radius: 999px; cursor: pointer;
  background: rgba(0, 0, 0, 0.6); color: #fff; font-size: 14px; line-height: 1;
  display: grid; place-items: center;
}
/* Quiet, auto-dismissing note above the composer (a non-image file, >4 photos, or an upload
   error) — replaces window.alert so the feedback is in-place and non-blocking. */
.chat-note {
  max-width: var(--chat-max); margin: 0 auto 8px; padding: 8px 12px;
  font-size: 13px; line-height: 1.35; color: var(--ink);
  background: color-mix(in srgb, var(--accent) 9%, var(--surface));
  border: 1px solid var(--accent); border-radius: 10px;
}
/* Drag-over cue — highlight the composer while a file is dragged over the chat tab. */
.chat-form.chat-dragover {
  border-top-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
/* Shared photos inside a message bubble. Hidden container when empty so text-only rows are
   unaffected. Left-aligned for everyone (user + agent); a modest thumbnail — click opens the full image. */
.ce-media { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-start; }
.ce-media:empty { display: none; }
.ce-img { display: block; max-width: 170px; border-radius: 12px; overflow: hidden; border: 1px solid var(--border); }
.ce-img img { display: block; width: 100%; max-height: 220px; object-fit: cover; }
/* A shared FILE (PDF/office doc/CSV — chat file uploads) — a named chip; click opens/downloads the original. */
.ce-file {
  display: inline-flex; align-items: center; gap: 6px; max-width: 240px;
  padding: 8px 12px; border-radius: 12px; border: 1px solid var(--border);
  background: var(--panel); color: var(--ink); text-decoration: none; font-size: 13px;
}
.ce-file:hover { border-color: var(--accent); }
.ce-file__ico { flex: none; width: 16px; height: 16px; color: var(--muted); }
.ce-file__ico svg { width: 100%; height: 100%; display: block; }
.ce-file__name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* An image-only turn has no caption — hide the empty text pill so there's no stray bubble. */
.ce-text:empty { display: none; }
.ce-img { cursor: zoom-in; }

/* Full-screen image lightbox (chat-core.js). Opens a shared photo inspectable in place instead
   of navigating away. Fits the viewport by default; .is-zoomed shows it at natural size and the
   overlay scrolls so you can pan. The page behind is scroll-locked via .ce-lb-open. */
html.ce-lb-open { overflow: hidden; }
.ce-lightbox {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .92); padding: 24px;
  overflow: auto; overscroll-behavior: contain;
}
.ce-lightbox[hidden] { display: none; } /* flex would otherwise override the hidden attribute */
.ce-lightbox__img {
  max-width: 100%; max-height: 100%; object-fit: contain;
  border-radius: 8px; cursor: zoom-in;
}
.ce-lightbox.is-zoomed { align-items: flex-start; justify-content: flex-start; }
.ce-lightbox.is-zoomed .ce-lightbox__img {
  max-width: none; max-height: none; width: auto; height: auto;
  border-radius: 0; cursor: zoom-out;
}
.ce-lightbox__x {
  position: fixed; top: 14px; right: 16px;
  width: 40px; height: 40px; border-radius: 50%; border: none; cursor: pointer;
  background: rgba(255, 255, 255, .14); color: #fff; font-size: 26px; line-height: 1;
  display: grid; place-items: center;
}
.ce-lightbox__x:hover { background: rgba(255, 255, 255, .26); }
/* The shared lightbox (public/js/lightbox.js) is a <dialog> when opened from the Media view / calendar
   event modal (top-layer stacking, above an open modal). The base .ce-lightbox rules were written for
   a <div>, so pin the dialog explicitly: fill the viewport, strip the UA chrome, and — critically —
   force display:none when CLOSED. Without this last rule the base `.ce-lightbox { display:flex }` keeps
   the closed dialog visible (the × fires close() but the box lingers). Specificity (0,2,1) + !important
   beats both `.ce-lightbox` and the UA `dialog:not([open])`. */
dialog.ce-lightbox {
  position: fixed; inset: 0; margin: 0; border: 0; padding: 24px;
  width: 100vw; height: 100vh; max-width: 100vw; max-height: 100vh;
  background: rgba(0, 0, 0, .92);
}
dialog.ce-lightbox:not([open]) { display: none !important; }
dialog.ce-lightbox::backdrop { background: rgba(0, 0, 0, .92); }
.ce-lightbox__err { color: rgba(255, 255, 255, .82); font-size: 15px; text-align: center; }
.ce-lightbox__err[hidden] { display: none; }
/* The bottom action bar — crop + "go to email" side by side, centered; each collapses when hidden. */
.ce-lightbox__actions { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; }
.ce-lightbox__crop, .ce-lightbox__email, .ce-lightbox__open, .ce-lightbox__dl {
  border: none; border-radius: 999px; cursor: pointer; white-space: nowrap;
  font-size: 15px; font-weight: 600; padding: 10px 18px; box-shadow: 0 4px 14px rgba(0, 0, 0, .4);
}
/* four buttons now — let the bar wrap as pills on a narrow screen instead of squeezing text */
.ce-lightbox__actions { flex-wrap: wrap; justify-content: center; max-width: calc(100vw - 24px); }
.ce-lightbox__crop { background: var(--accent); color: #fff; } /* the primary action (read the region) is accent */
.ce-lightbox__email, .ce-lightbox__open, .ce-lightbox__dl { background: rgba(255, 255, 255, .92); color: #14181f; } /* navigation + save = white pills */
.ce-lightbox__email:hover, .ce-lightbox__open:hover, .ce-lightbox__dl:hover { background: #fff; }
.ce-lightbox__crop[hidden], .ce-lightbox__email[hidden], .ce-lightbox__open[hidden], .ce-lightbox__dl[hidden] { display: none; }
/* The caption — the Files rows' title + pulled-out facts (they left the card in the 2026-08-24 re-cut and
   surface here, over the page they were read from). Fixed to the top on a dark wash — ink is fixed white,
   theme-independent by construction (the media-tile scrim's rule). Collapses entirely when absent. */
.ce-lightbox__cap {
  position: fixed; top: 0; left: 0; right: 0; z-index: 1;
  display: flex; flex-direction: column; gap: 2px; align-items: center; text-align: center;
  padding: 14px 56px 18px; background: linear-gradient(rgba(0, 0, 0, .6), transparent);
  pointer-events: none; /* never blocks the image click-to-zoom or the × behind it */
}
.ce-lightbox__cap[hidden] { display: none; }
.ce-lightbox__cap-title { color: #fff; font-weight: 600; font-size: 14.5px; line-height: 1.35; }
.ce-lightbox__cap-facts { color: rgba(255, 255, 255, .75); font-size: 12.5px; line-height: 1.4; }

/* ── Crop tool ─────────────────────────────────────────────────────────────
   A full-screen overlay to frame one region of a dense image. The stage holds the image at fit-size
   (width/height auto — the <img> rect IS the rendered image, so the JS normalizes against it with no
   letterbox math), with a draggable/resizable selection box + a bottom action bar. */
.ce-cropper {
  position: fixed; inset: 0; z-index: 1001;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; background: rgba(0, 0, 0, .92); padding: 20px; overscroll-behavior: contain;
  touch-action: none; /* the box drag owns the gesture — no page pan/zoom fighting it */
}
.ce-cropper[hidden] { display: none; }
.ce-cropper__stage { position: relative; display: inline-block; max-width: 100%; max-height: 82vh; line-height: 0; }
.ce-cropper__img { display: block; max-width: 100%; max-height: 82vh; width: auto; height: auto; border-radius: 6px; }
.ce-crop-sel {
  position: absolute; box-sizing: border-box; cursor: move; touch-action: none;
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .55); /* dim everything outside the selection */
}
.ce-crop-sel[hidden] { display: none; }
.ce-crop-h {
  position: absolute; width: 18px; height: 18px; border-radius: 50%;
  background: #fff; border: 2px solid var(--accent); touch-action: none;
}
.ce-crop-h--nw { top: -10px; left: -10px; cursor: nwse-resize; }
.ce-crop-h--ne { top: -10px; right: -10px; cursor: nesw-resize; }
.ce-crop-h--sw { bottom: -10px; left: -10px; cursor: nesw-resize; }
.ce-crop-h--se { bottom: -10px; right: -10px; cursor: nwse-resize; }
.ce-cropper__bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: center;
  color: rgba(255, 255, 255, .9);
  /* The selection box dims everything outside it with a 100vmax box-shadow; that shadow is painted by a
     POSITIONED element, so it lands on top of this static bar and greys out the buttons. Lift the bar
     into its own positioned layer (z-index 1 > the stage's auto) so it sits ABOVE the dimming. */
  position: relative; z-index: 1;
}
.ce-cropper__hint { font-size: 14px; color: rgba(255, 255, 255, .72); }
.ce-cropper__cancel, .ce-cropper__send {
  border: none; border-radius: 999px; cursor: pointer; font-size: 15px; font-weight: 600; padding: 9px 18px;
}
.ce-cropper__cancel { background: rgba(255, 255, 255, .16); color: #fff; }
.ce-cropper__send { background: var(--accent); color: #fff; }
.ce-cropper__send:disabled { opacity: .5; cursor: default; }

/* "Crop another region" — the sticky one-tap reopen above the composer, so regions 2..N never make
   you scroll back to the original image. */
.chat-cropagain {
  display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
  padding: 7px 10px; border-radius: 10px; border: 1px solid var(--border); background: var(--panel);
}
.chat-cropagain__go { flex: 1; text-align: left; border: none; background: none; cursor: pointer; color: var(--accent); font-weight: 600; font-size: 14px; }
.chat-cropagain__x { border: none; background: none; cursor: pointer; color: var(--muted); font-size: 20px; line-height: 1; padding: 0 4px; }

@media (max-width: 879px) {
  /* The mobile top bar already shows the active agent (avatar + name), so the
     in-pane agent header is redundant here — drop it to give chat breathing room.
     Scoped to the dashboard pane (#tab-chat) so other .chat-head hosts keep theirs. */
  #tab-chat .chat-head { display: none; }
  #tab-chat { height: 100dvh; padding: var(--mtop-h) 0 0; }
  .messages { padding: 8px 16px; }
  #tab-chat .messages { padding-bottom: calc(64px + env(safe-area-inset-bottom)); } /* clears the fixed composer */
  .messages > .ce-row { max-width: 640px; }
  /* The /chat composer is FULL-WIDTH + bottom-fixed, matching the copilot bar (docs/NAV.md) — it spans the
     whole width and the icon rail runs behind it (composer z-45 > rail z-40). Scoped to #tab-chat so it
     doesn't hit the copilot's own composer (.copilot__composer.chat-form). The
     pane's left inset holds the messages/header (which clear the rail); the composer escapes it via fixed. */
  #tab-chat .chat-form {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
    background: var(--bg); margin-bottom: 0;
    padding: 8px 16px calc(8px + var(--dock-inset, 0px));
  }
  .chat-form__inner { max-width: 640px; }
  /* Timeline home: the in-pane header is hidden on mobile (rule above), so the agenda + nudge get
     top breathing room under the fixed bar; align their side padding to .messages (16px). */
  .tl-agenda, .tl-empty { padding-left: 16px; padding-right: 16px; }
  .tl-keynudge { margin-left: 16px; margin-right: 16px; }
  .tl-agenda, .tl-keynudge { margin-top: 16px; }
}

/* ── Glass box (Files) — legible, grouped, read-only ──────────────────────── */
.glass-group { margin-bottom: 24px; }
.glass-group__label {
  font-family: var(--font-body); font-weight: 600; font-size: 12px;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 10px;
}

.glass-item {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 12px 14px; margin-bottom: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  color: var(--ink); font-family: inherit; cursor: pointer;
  transition: border-color 0.12s, transform 0.04s;
}
.glass-item:hover { border-color: var(--accent); }
.glass-item:active { transform: scale(0.995); }
.glass-item.is-active { border-color: var(--accent); }
.glass-item__icon {
  display: grid; place-items: center; flex: none; width: 38px; height: 38px;
  border-radius: 10px; background: var(--bg); color: var(--accent); font-size: 16px;
}
/* Match the glyph to the nav rail's section icons (.rail__item .icon = 20px) so the two
   read as the same mark at the same size, not two different scales. */
.glass-item__icon .icon { width: 20px; height: 20px; }
.glass-item__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.glass-item__title { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-weight: 600; font-size: 15px; }
.glass-item__file { background: none; padding: 0; font-size: 11px; font-weight: 400; color: var(--muted); }
.glass-item__blurb { font-size: 13px; color: var(--muted); }
.glass-item__chev { margin-left: auto; flex: none; font-size: 12px; color: var(--muted); }

.glass-timeline {
  margin-top: 4px; padding: 8px 14px 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
}
.glass-timeline__head { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); padding: 6px 0; }
.glass-timeline__head .icon { color: var(--accent); }
.glass-timeline__empty { font-size: 13px; padding: 4px 0 8px; }
.glass-day {
  display: flex; align-items: baseline; gap: 10px; width: 100%; text-align: left;
  padding: 10px 6px; border: none; border-bottom: 1px solid var(--border);
  background: none; color: var(--ink); font-family: inherit; cursor: pointer; border-radius: 8px;
}
.glass-day:last-child { border-bottom: none; }
.glass-day:hover { background: var(--bg); }
.glass-day.is-active { color: var(--accent); }
.glass-day__label { font-weight: 600; font-size: 14px; }
.glass-day__date { margin-left: auto; font-size: 12px; }

/* Reader — BASE = mobile: a full-screen slide-in overlay ("like turning a page").
   files.js toggles .is-open on click / Back. The desktop master-detail override
   follows below (source order: base first, so the media query wins ≥880px). */
.glass-reader {
  position: fixed; inset: 0; z-index: 60; overflow: auto;
  background: var(--bg);
  transform: translateX(100%); transition: transform 0.22s ease;
}
.glass-reader.is-open { transform: translateX(0); }
.glass-reader__inner {
  max-width: 640px; margin: 0 auto;
  padding: calc(var(--mtop-h) + 12px) 16px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 16px);
}
/* Visuals come from the shared .back-btn pill; this keeps only the standalone spacing above the reader. */
.glass-reader__back { margin-bottom: 14px; }
.glass-reader__head { border-bottom: 1px solid var(--border); padding-bottom: 10px; margin-bottom: 14px; }
.glass-reader__title { margin: 0 0 2px; font-size: 20px; }
.glass-reader__path { background: none; padding: 0; font-size: 12px; color: var(--muted); }
.glass-reader__empty { padding: 8px 0; }

/* Desktop (≥880px): Files fills the whole pane like Chat. A fixed file-nav column
   sits flush against the rail (its own scroll — an "expanded menu" off the sidebar);
   the reader fills the rest and behaves like the chat thread — a full-height scroller
   with the bar at the pane edge and the TEXT centered to reading width. The mobile
   slide-in overlay above is untouched (it's the base style; this media query wins). */
@media (min-width: 880px) {
  /* (The Context tab's full-pane grid that led this block retired with that tab, 2026-08-18 —
     the /architecture drill-in lays out its own .arch-glass grid further down. The .glass-list /
     .glass-reader rules below stay GLOBAL: the Library master-detail depends on them.) */
  /* The file nav — an expanded menu against the rail, with its own scroll. */
  .glass-list {
    height: 100dvh; overflow-y: auto;
    padding: 28px 20px 56px;
    border-right: 1px solid var(--border);
  }
  /* The reader — a chat-style full-height scroller. Neutralize the mobile slide-in
     (fixed/transform), let the text center itself like .messages rows do. */
  .glass-reader {
    position: static; inset: auto; z-index: auto; transform: none;
    height: 100dvh; max-height: none; overflow-y: auto;
    background: var(--bg); border: none; border-radius: 0;
  }
  .glass-reader__inner { max-width: none; margin: 0; padding: 0; }   /* full-bleed; head + body self-center */
  /* Match the chat header (.chat-head + .agent-strip): a full-width, bottom-bordered
     bar pinned to the top, its content LEFT-aligned in the same centered reading
     column — title + filename inline, exactly like a menu row / the agent name. */
  .glass-reader__head {
    position: sticky; top: 0; z-index: 1; background: var(--bg);
    border-bottom: 1px solid var(--border);
    padding: 14px 0; margin: 0; display: flex;   /* border full-width; sides padded on the inner */
  }
  .glass-reader__headinner {
    max-width: var(--content-max); width: 100%; margin: 0 auto;
    padding: 0 24px;   /* same 24px side inset as the doc body → title aligns with the text */
    display: flex; align-items: baseline; gap: 10px;
  }
  .glass-reader__title { margin: 0; font-size: 18px; font-weight: 700; }
  .glass-reader__path { font-size: 12px; font-weight: 400; color: var(--muted); background: none; padding: 0; }
  /* The doc body + empty/loading states share the centered reading column. */
  .glass-reader__body .md { max-width: var(--content-max); margin: 0 auto; padding: 28px 24px 56px; }
  .glass-reader__empty { max-width: var(--content-max); margin: 0 auto; padding: 28px 24px; }
}

/* Reader loading spinner — sits in the body while the doc fetches, so the header
   (the file name) renders instantly and never flickers. Centered in either layout. */
.glass-spinner {
  width: 22px; height: 22px; margin: 56px auto;
  border: 2px solid var(--border); border-top-color: var(--accent);
  border-radius: 50%; animation: glass-spin 0.7s linear infinite;
}
@keyframes glass-spin { to { transform: rotate(360deg); } }

/* Reader take-it-with-you action — "Download .md" in the reader header. Quiet pill with the
   download glyph; below the title on mobile, pushed to the right of the head row on desktop. */
.glass-reader__actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.reader-action {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
  padding: 5px 12px; color: var(--ink); font-family: inherit; font-size: 15px; font-weight: 600;
  text-decoration: none; cursor: pointer; white-space: nowrap;
}
.reader-action:hover { border-color: var(--accent); color: var(--accent); }
.reader-action .icon { width: 15px; height: 15px; }
@media (min-width: 880px) {
  .glass-reader__actions { margin-top: 0; margin-left: auto; align-self: center; }
}

/* Rendered markdown body (shared by the reader). */
.md { line-height: 1.6; }
.md h1, .md h2, .md h3 { margin: 0.8em 0 0.3em; }
.md code { background: var(--bg); padding: 1px 5px; border-radius: 4px; }
/* Code blocks (e.g. a tool's JSON schema in the Context tab): a distinct accent-tinted panel with a
   left rule, and WORD-WRAPPED — pre-wrap keeps the JSON indentation but lets long descriptions wrap
   instead of scrolling off the right edge. */
.md pre {
  background: color-mix(in srgb, var(--accent) 6%, var(--surface));
  border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 8px; padding: 12px 14px; margin: 10px 0;
  font-size: 12px; line-height: 1.55; tab-size: 2;
  white-space: pre-wrap; overflow-wrap: break-word;
}
.md pre code {
  background: none; padding: 0; color: var(--ink);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}
/* Context tab: each tool's JSON schema is wrapped (client-side, context.js) in a collapsed <details>
   so the tools read as name + description with an expandable "input schema", not a wall of JSON. */
.md details.schema-toggle { margin: 6px 0 16px; }
.md details.schema-toggle > summary {
  cursor: pointer; list-style: none; user-select: none;
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; color: var(--accent); padding: 3px 2px;
}
.md details.schema-toggle > summary::-webkit-details-marker { display: none; }
.md details.schema-toggle > summary::before { /* a triangle that rotates down when open */
  content: ""; width: 0; height: 0; border-left: 5px solid currentColor;
  border-top: 4px solid transparent; border-bottom: 4px solid transparent; transition: transform .15s;
}
.md details.schema-toggle[open] > summary::before { transform: rotate(90deg); }
.md details.schema-toggle > pre { margin-top: 6px; }
.md ul { padding-left: 20px; }

/* ── Skills (marketplace placeholder) ──────────────────────────────────────── */
.skills-head { margin-bottom: 22px; }
.skills-title { margin: 0 0 6px; }
.skills-soon {
  text-align: center; padding: 48px 28px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 18px;
}
.skills-soon__icon {
  display: inline-grid; place-items: center; width: 64px; height: 64px; margin-bottom: 14px;
  border-radius: 16px; background: var(--bg); color: var(--accent); font-size: 28px;
}
.skills-soon__title { margin: 0 0 6px; }

/* Storefront grid */
.skill-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.skill-card { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 20px; display: flex; flex-direction: column; gap: 12px; }
.skill-card__top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.skill-card__name { margin: 0; font-size: 18px; display: flex; align-items: center; gap: 10px; }
.skill-card__icon { display: grid; place-items: center; flex: none; width: 38px; height: 38px; border-radius: 11px; background: var(--bg); color: var(--accent); font-size: 17px; }
.skill-card__price { font-size: 13px; font-weight: 600; color: var(--accent); flex: none; }
.skill-card__blurb { margin: 0; font-size: 14px; line-height: 1.5; }
.skill-card__action { margin-top: auto; display: flex; flex-direction: column; gap: 6px; }
.skill-installed { display: inline-flex; align-items: center; gap: 10px; color: var(--accent); font-weight: 600; font-size: 14px; }
.skill-installed .btn { font-weight: 400; }

/* Skill tag (Free / Plus) — replaces the old per-skill price chip. */
.skill-card__tag { font-size: 12px; font-weight: 600; color: var(--muted); flex: none; border: 1px solid var(--border); border-radius: 999px; padding: 2px 10px; }
.skill-card__tag--plus { color: var(--accent); border-color: var(--accent); }
.skill-card--locked { opacity: 0.92; }
.skill-card--locked .skill-card__icon { color: var(--muted); }
.skill-unlock { display: inline-flex; align-items: center; gap: 8px; }
.skill-unlock .icon { width: 15px; height: 15px; }

/* Marketplace "unlock with Plus" banner (storefront, when locked). */
.skills-unlock {
  display: flex; align-items: center; gap: 16px; margin-bottom: 20px;
  background: var(--surface); border: 1px solid var(--accent); border-radius: 14px; padding: 16px 18px;
}
.skills-unlock__icon { display: grid; place-items: center; flex: none; width: 42px; height: 42px; border-radius: 11px; background: var(--bg); color: var(--accent); }
.skills-unlock__icon .icon { width: 21px; height: 21px; }
.skills-unlock__copy { display: flex; flex-direction: column; gap: 2px; }
.skills-unlock .btn { margin-left: auto; flex: none; }

/* Billing card (Settings → dustav.com Plus). */
.plan-badge--plus { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.billing-plans { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
.billing-banner { display: flex; align-items: center; gap: 8px; border-radius: 10px; padding: 10px 14px; font-size: 14px; margin: 0 0 12px; }
.billing-banner .icon { width: 17px; height: 17px; flex: none; }
.billing-banner--ok { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--ink); border: 1px solid var(--accent); }
.billing-banner--cancel { background: var(--bg); color: var(--muted); border: 1px solid var(--border); }

/* Calendar — co-owned month grid + agenda. Events + days are clickable; edits go
   through a modal (cal-modal). (The .cal-empty empty-state card is deleted — no template renders it.) */
/* Header = TWO rows, each a left item + a right cluster (space-between):
   Row 1 — back + date · prev/next arrows.   Row 2 — Categories · search + Clear + add.
   No Day/Month/Year switcher and no list toggle — the calendar IS the navigation (drill + back). */
.cal-head { display: flex; flex-direction: column; align-items: stretch; gap: 12px; margin-bottom: 16px; }
.cal-headrow { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.cal-title { margin: 0; font-size: 22px; }
.cal-titlewrap { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* Back / zoom-out (Day → Month → Year). Hidden at the top level; calendar.js sets its label. */
.cal-back { display: inline-flex; align-items: center; gap: 1px; font-family: inherit; font-size: 14px; font-weight: 600; color: var(--accent); background: none; border: none; cursor: pointer; padding: 4px 8px 4px 2px; border-radius: 8px; flex: none; }
.cal-back:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.cal-back .icon { width: 16px; height: 16px; }
.cal-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }  /* arrows · search · grid/list · + */
/* Every header control shares the same slim, pill-round "grain". */
.cal-head .btn { padding: 8px 15px; border-radius: 999px; font-size: 14px; }
#cal-new { padding: 8px 12px; }                                    /* New event = icon-only "+", squared, stays primary/purple */
/* Nav = a pill GROUP like the switcher: one surface container, borderless segments. */
.cal-nav { display: inline-flex; align-items: center; gap: 2px; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 2px; }
.cal-nav .btn { border: none; background: none; padding: 6px 14px; }
.cal-nav .btn:hover { background: var(--panel); }
/* Month = iCal's shape: a DOT grid (a coloured dot per event, NOT titles) + the selected day's
   events listed in a panel below. cal-monthwrap stacks the two; the grid is no longer full-height. */
.cal-monthwrap { display: flex; flex-direction: column; gap: 18px; }
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.cal-dow { background: var(--surface); text-align: center; font-size: 12px; color: var(--muted); padding: 8px 0; font-weight: 600; }
.cal-day { background: var(--panel); min-height: 70px; min-width: 0; padding: 7px 4px 6px; display: flex; flex-direction: column; align-items: center; gap: 5px; cursor: pointer; transition: background 0.1s; }
.cal-day:hover { background: var(--bg); }
.cal-day--out { background: var(--surface); }
.cal-day--out .cal-day__num { color: var(--muted); opacity: 0.55; }
.cal-day--today .cal-day__num { background: var(--accent); color: #fff; }
/* The day the panel below is showing — a ring so it reads as "selected", distinct from today. */
.cal-day--selected { background: color-mix(in srgb, var(--accent) 9%, var(--panel)); box-shadow: inset 0 0 0 2px var(--accent); }
.cal-day--selected:hover { background: color-mix(in srgb, var(--accent) 13%, var(--panel)); }
.cal-day__num { font-size: 13px; color: var(--ink); width: 24px; height: 24px; display: inline-grid; place-items: center; border-radius: 50%; }
.cal-day__dots { display: flex; flex-wrap: wrap; justify-content: center; gap: 3px; min-height: 6px; }
.cal-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--cat, var(--accent)); }
.cal-dot.is-hidden { display: none; }

/* The selected day's events, listed under the Month grid (iCal's month layout). */
.cal-daypanel { display: flex; flex-direction: column; gap: 8px; }
/* The date header is the drill INTO the Day view — it's navigation, so it's the SAME element as the
   .cal-back zoom-out chevron: accent text + a plain chevron, no pill. Just mirrored (chevron trails
   the label, pointing forward, vs .cal-back's leading back-chevron). */
.cal-daypanel__date {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 2px;
  font-family: inherit; font-size: 14px; font-weight: 600; color: var(--accent); cursor: pointer;
  background: none; border: none; padding: 4px 6px 4px 2px; border-radius: 8px;
}
.cal-daypanel__date:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.cal-daypanel__date .icon { width: 16px; height: 16px; }
.cal-daypanel__none { margin: 2px 0 6px; font-size: 14px; opacity: 0.75; }

/* The Month "no day selected" agenda (after Clear): a small label + the whole month's events. */
.cal-monthagenda { display: flex; flex-direction: column; gap: 8px; }
.cal-monthagenda__head { margin: 0 0 2px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }

/* ── Calendar category palette + filter bar ──────────────────────────────────── */
/* Named hues (services/eventCategories.PALETTE). Each sets --cat; chips/agenda/filters read it. */
.cat--coral  { --cat: #ff6a3d; }
.cat--amber  { --cat: #f59e0b; }
.cat--rose   { --cat: #f43f6b; }
.cat--violet { --cat: #8b5cf6; }
.cat--teal   { --cat: #14b8a6; }
.cat--blue   { --cat: #3b82f6; }
.cat--green  { --cat: #22c55e; }
.cat--slate  { --cat: #94a3b8; }

/* ── Categories — a multi-select dropdown (replaces the always-on chip bar) ─────── */
.cal-catwrap { position: relative; display: inline-block; }   /* hugs the button so the popover aligns under it */
.cal-catbtn { display: inline-flex; align-items: center; gap: 8px; font-family: inherit; font-size: 14px; font-weight: 600; color: var(--ink); cursor: pointer; background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 8px 14px; }
.cal-catbtn:hover { border-color: var(--accent); }
.cal-catbtn .icon { width: 15px; height: 15px; color: var(--muted); }
.cal-catbtn.is-active { border-color: var(--accent); color: var(--accent); }   /* a filter is on */
.cal-catbtn__count { display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px; background: var(--accent); color: #fff; font-size: 12px; font-weight: 700; }

/* The popover panel under the button. */
.cal-catmenu { position: absolute; z-index: 40; top: calc(100% + 8px); left: 0; width: min(320px, calc(100vw - 32px)); background: var(--surface); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 16px 40px rgba(0,0,0,0.32); padding: 8px; }
.cal-catmenu[hidden] { display: none; }
.cal-catmenu__head { display: flex; align-items: center; justify-content: space-between; padding: 6px 8px 8px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
.cal-catmenu__title { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.cal-catmenu__x { display: inline-grid; place-items: center; background: none; border: none; cursor: pointer; color: var(--muted); padding: 2px; border-radius: 6px; margin: -2px -2px -2px 0; }
.cal-catmenu__x:hover { color: var(--ink); }
.cal-catmenu__x .icon { width: 16px; height: 16px; }
.cal-catmenu__clear { font-family: inherit; font-size: 13px; font-weight: 600; color: var(--accent); background: none; border: none; cursor: pointer; padding: 2px 4px; border-radius: 6px; }
.cal-catmenu__clear:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.cal-catmenu__clear:disabled { color: var(--muted); cursor: default; background: none; }   /* nothing selected → nothing to clear */
.cal-catmenu__list { display: flex; flex-direction: column; gap: 2px; max-height: min(420px, 56vh); overflow-y: auto; }  /* this scroll is INSIDE the popover, not the page body */
.cal-catopt { display: flex; align-items: center; gap: 11px; width: 100%; text-align: left; font-family: inherit; font-size: 15px; color: var(--ink); cursor: pointer; background: none; border: none; border-radius: 9px; padding: 11px 10px; }
.cal-catopt:hover { background: var(--bg); }
.cal-catopt__dot { width: 11px; height: 11px; border-radius: 50%; background: var(--cat, var(--accent)); flex: none; }
.cal-catopt__name { flex: 1; min-width: 0; }
.cal-catopt__check { display: none; color: var(--accent); flex: none; }
.cal-catopt__check .icon { width: 18px; height: 18px; }
.cal-catopt.is-on { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.cal-catopt.is-on .cal-catopt__check { display: inline-flex; }
/* Today + search sit together at the right of header row 2 (Today used to float over the composer). */
.cal-headright { display: flex; align-items: center; gap: 8px; }
.cal-todaybtn.btn { background: var(--surface); font-weight: 600; padding: 8px 16px; }
.cal-todaybtn.btn:hover { border-color: var(--accent); color: var(--accent); }
/* ✦ FOUND — the queue's door (docs/GCAL_MIRROR.md §6d rev 5). Present exactly while proposals are open,
   so it is never a dead control. Accent-tinted rather than ghost: it's the one thing in this toolbar
   that means "something is waiting on you", and it's what the nav badge points at. The living Blink
   rather than a glyph — the same mark that says "Dustav did this" everywhere else. */
.cal-foundbtn.btn { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; padding: 8px 14px;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); }
.cal-foundbtn.btn:hover { background: color-mix(in srgb, var(--accent) 16%, var(--surface)); }
/* At zero it's still THERE (capability stays visible) but stops claiming attention — a quiet sibling of
   Today, not a lit signal. The moment anything is staged it takes the accent + its count back. */
.cal-foundbtn.is-empty.btn { color: var(--muted); background: var(--surface); border-color: var(--border); }
.cal-foundbtn.is-empty.btn:hover { color: var(--ink); border-color: var(--muted); background: var(--surface); }
.cal-foundbtn.is-empty .cal-foundbtn__mark { opacity: .55; }
.cal-foundbtn__mark { display: inline-flex; width: 17px; height: 17px; }
.cal-foundbtn__mark svg { width: 17px; height: 17px; }
.cal-foundbtn__n { font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  background: var(--accent); color: var(--accent-ink); border-radius: 999px; padding: 1px 7px; }
/* The queue's foot — Book all, right-aligned under the rows. */
.cal-found__foot { display: flex; justify-content: flex-end; margin-top: 14px; }
/* The not-ready line — "these have nowhere to land yet". Amber-tinted: it isn't an error (Dustav did
   its job; the events are real), it's a missing prerequisite, and the button beside it is the fix. */
.cal-found__err { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 4px 0 12px; padding: 12px 14px;
  border-radius: 12px; font-size: 13px; line-height: 1.5; color: var(--ink);
  background: color-mix(in srgb, var(--inb-warn, #b7791f) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--inb-warn, #b7791f) 40%, var(--border)); }
.cal-found__err[hidden] { display: none; }
.cal-found__err > span { flex: 1; min-width: 200px; }
.cal-found__connect { flex: none; }
/* (The .cal-found__ask input retired 2026-08-27 — a batch question goes to the always-present Ask bar,
   which sees the same staged block every turn.) */

/* Categories area (row 2): the dropdown with "Clear" hanging right beside it. */
.cal-catarea { display: flex; align-items: center; gap: 4px; }
/* Clear — a small, borderless text link (not a pill), beside the categories filter. Resets the
   category filter AND (in Month) deselects the day → the panel falls back to the whole month. */
.cal-clearlink { font-family: inherit; font-size: 13px; color: var(--muted); background: none; border: none; cursor: pointer; padding: 6px 8px; border-radius: 7px; }
.cal-clearlink:hover { color: var(--accent); }

/* ── The chronological list (Month's "no day selected" agenda) ─────────────────── */
.cal-list { display: flex; flex-direction: column; gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.cal-list__row { display: flex; gap: 16px; align-items: baseline; width: 100%; text-align: left; font-family: inherit; background: var(--panel); border: none; cursor: pointer; padding: 11px 14px; color: var(--ink); }
.cal-list__row:hover { background: var(--bg); }
.cal-list__row.is-hidden { display: none; }
.cal-list__when { flex: none; width: 210px; font-size: 13px; color: var(--muted); white-space: nowrap; } /* wide enough for a date/time RANGE */
.cal-list__row[class*="cat--"] .cal-list__when::before { content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--cat); margin-right: 8px; vertical-align: middle; }
.cal-list__title { font-size: 14px; min-width: 0; line-height: 1.4; }
.cal-list__empty { background: var(--panel); margin: 0; padding: 16px 14px; font-size: 14px; }

/* ── Full-height layout: header + filters pinned; the grid fills + scrolls inside ──
   "Full screen where we can" (docs/CALENDAR.md). The calendar grid is FULL-WIDTH; there's no
   side panel — search + a day's detail live in modals. */
/* Desktop: the calendar column and the copilot panel sit side by side (row); .cal-main is the
   column (header + scrolling grid), the panel is a fixed-width flex child to its right. */
/* Fill the pane (override the base .tabpane max-width:760 + margin:auto — else the calendar
   gets squeezed to the reading column and floats centered with big side gaps). */
#tab-calendar.calendar { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row; }
.cal-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }

/* ── Documents — a full-width LIBRARY that drills into a DOCUMENT (toolbar + readable page) ──────
   The library (card gallery) and the reader (open document) are the SAME main column, swapped by
   #tab-documents.is-reading (documents.js toggles it): browsing shows the grid; opening a document
   swaps in the reader + toolbar. The copilot rail is a constant sibling (its own width, global
   .copilot rules). On mobile the reader is a fixed slide-in over the library. Replaces the old
   cramped 3-column list|reader|copilot. */
/* ── Workflows tab (docs/WORKFLOWS.md) — the workflow library → one workflow's spec + records table.
   Reuses the shared .lib / .doc chrome (like Documents); only the workflow cards, status pills, field chips,
   spec, and the records table are bespoke. Same library↔detail swap via #tab-workflows.is-reading. */
#tab-workflows.workflows { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row; }
.wf-main { flex: 1 1 auto; min-width: 0; display: flex; min-height: 0; }
.wf-main > .lib, .wf-main > .doc { flex: 1 1 auto; min-width: 0; min-height: 0; }
#tab-workflows.workflows .doc { display: none; }
#tab-workflows.workflows.is-reading .lib { display: none; }
#tab-workflows.workflows.is-reading .doc { display: flex; flex-direction: column; overflow: hidden; }
/* An email-rule ROW — full-width, clickable: name + match + a techy JSON schema on the left, the
   status pill + saved-row count in a right-aligned aside, a chevron on the far right. */
.wf-card { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto auto; column-gap: 16px; align-items: center; text-align: left; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 15px 18px; cursor: pointer; color: var(--ink); font-family: inherit; font-size: inherit; text-decoration: none; transition: border-color .12s, background .12s; }
.wf-card:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 4%, var(--surface)); }
.wf-card.is-active { border-color: var(--accent); }
.wf-card__main { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
/* The rule + schema are a COLLAPSED disclosure (Curtis, 2026-08-21) — the card at rest is one compact row
   (name · status · meta); the chevron expands the detail in place. Same inb-eye/inb-kebab button spec. */
.wf-card__detail { display: none; flex-direction: column; gap: 7px; min-width: 0; }
.wf-card.is-expanded .wf-card__detail { display: flex; }
.wf-card__toggle { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 24px; border: 1px solid var(--border); border-radius: 7px; background: var(--surface); color: var(--muted); cursor: pointer; flex: none; transition: background .12s, color .12s, border-color .12s; }
.wf-card__toggle:hover, .wf-card__toggle:focus-visible { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); outline: none; }
.wf-card__toggle svg { transition: transform .15s ease; }
.wf-card.is-expanded .wf-card__toggle svg { transform: rotate(180deg); }
.wf-card__name { font-weight: 650; font-size: 15.5px; display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.wf-card__emoji { flex: none; }
.wf-card__aside { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; flex: none; }
/* The match rule as condition chips (keyword + value) — reads as the actual rule. Shared card + detail. */
.wf-match { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
.wf-match__lead { font-size: 13.5px; color: var(--muted); }
.wf-match__cond { display: inline-flex; align-items: baseline; gap: 6px; padding: 3px 10px; border-radius: 7px; font-size: 12.5px; background: color-mix(in srgb, var(--accent) 9%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent); }
.wf-match__key { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-size: 11px; font-weight: 600; color: var(--accent); }
.wf-match__val { color: var(--ink); font-weight: 600; }
.wf-match__cond--skip { background: color-mix(in srgb, #e0a52a 13%, transparent); border-color: color-mix(in srgb, #e0a52a 34%, transparent); }
.wf-match__cond--skip .wf-match__key { color: #a6720b; }
[data-theme="dark"] .wf-match__cond--skip .wf-match__key { color: #edc169; }
.wf-match__none { font-size: 12.5px; color: var(--muted); }
/* Action workflow — a "when <trigger> → email <recipient>" line (mirrors the .wf-match chips). */
.wf-action { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
.wf-action__cond { display: inline-flex; align-items: baseline; gap: 6px; padding: 3px 10px; border-radius: 7px; font-size: 12.5px; background: color-mix(in srgb, var(--accent) 9%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent); }
.wf-action__key { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-size: 11px; font-weight: 600; color: var(--accent); }
.wf-action__val { color: var(--ink); font-weight: 600; }
.wf-action__arrow { color: var(--muted); font-weight: 700; }
.wf-action__cond--to { background: color-mix(in srgb, #1a8fd4 12%, transparent); border-color: color-mix(in srgb, #1a8fd4 32%, transparent); }
.wf-action__cond--to .wf-action__key { color: #1273b0; }
[data-theme="dark"] .wf-action__cond--to .wf-action__key { color: #6bc0f0; }
/* Action-workflow card — a subtle ocean left-accent so it reads distinctly from an extraction table at a glance. */
.wf-card--action { border-left: 3px solid color-mix(in srgb, #1a8fd4 55%, var(--border)); }
.wf-card__meta { font-size: 12px; color: var(--muted); white-space: nowrap; }
/* "pings you" — the per-workflow notification subscription (docs/NOTIFICATIONS.md §4), shown only when ON.
   Accent-tinted so it reads as a live property of the rule rather than another grey count beside it. */
.wf-card__ping { color: var(--accent); }
/* The schema as JSON — monospace, brace-wrapped, keys in ink + types in the accent (the techy read). */
.wf-card__json { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-size: 12px; line-height: 1.7; color: var(--muted); word-break: break-word; background: color-mix(in srgb, var(--ink) 4%, transparent); border: 1px solid var(--border); border-radius: 7px; padding: 6px 10px; margin-top: 1px; }
.wf-card__json::before { content: "{ "; }
.wf-card__json::after { content: " }"; }
.wf-json__key { color: var(--ink); }
.wf-json__type { color: var(--accent); }
.wf-json__punc { color: var(--muted); }
/* the owner's per-field instruction (the extractor hint) — quoted prose inside the schema block, muted italic */
.wf-json__hint { color: var(--muted); font-style: italic; }
/* status pills — active (green), draft (amber), paused (slate) */
.wf-status { margin-left: auto; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; padding: 2px 8px; border-radius: 999px; flex: none; border: 1px solid var(--border); color: var(--muted); background: var(--surface); }
.wf-status--active { color: #1c8f52; border-color: #45c17a55; background: #45c17a1a; }
.wf-status--draft { color: #a6720b; border-color: #e0a52a55; background: #e0a52a1a; }
.wf-status--paused { color: var(--muted); }
/* The detail toolbar's Active/Ping-me SWITCHES (built by workflows.js buildToolbar — they replaced the
   status pill + the "Ping me" text button, 2026-08-28: a labeled switch says the state AND is the control).
   .is-on is toggled by JS, never :has() — iOS Safari doesn't re-invalidate :has() on descendant mutation. */
.wf-doc .doc__acts { gap: 16px; align-items: center; }
.wf-toggle { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; }
.wf-toggle__lbl { font-size: 12px; font-weight: 600; color: var(--muted); white-space: nowrap; }
.wf-toggle.is-on .wf-toggle__lbl { color: var(--ink); }
/* the compact switch variant — toolbar-height, same anatomy as the 44×26 house switch */
.wf-toggle .ff-switch { width: 36px; height: 21px; }
.wf-toggle .ff-switch::after { width: 15px; height: 15px; }
.wf-toggle .ff-switch:checked::after { transform: translateX(15px); }
/* detail: spec + table. A FILL-HEIGHT column (2026-08-28): the spec + table toolbar stay put and the
   records grid takes the remaining viewport, scrolling inside itself — so the horizontal scrollbar lives
   at the bottom of the SCREEN, not below 300 rows. The detail widens on larger screens (the table gets
   the room); the spec/schema stay capped at a readable width so a wide viewport doesn't stretch the prose. */
.wf-doc .doc__scroll { display: flex; flex-direction: column; }
.wf-detail { max-width: 1280px; margin: 0 auto; padding: 26px 32px; width: 100%; flex: 1 1 auto; display: flex; flex-direction: column; }
@media (min-width: 1600px) { .wf-detail { max-width: 1460px; } }
.wf-spec { display: flex; flex-direction: column; gap: 12px; margin-bottom: 26px; max-width: 760px; }
.wf-spec__desc { font-size: 13.5px; color: var(--muted); }
/* Action workflow DETAIL — the spec rows (label: value), read-only. */
.wf-spec--action { gap: 11px; }
.wf-aspec__row { display: flex; gap: 14px; align-items: baseline; font-size: 14px; }
.wf-aspec__label { flex: 0 0 148px; color: var(--muted); font-size: 12.5px; font-weight: 600; text-align: right; }
.wf-aspec__val { color: var(--ink); font-weight: 500; }
.wf-aspec__val--mono { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-size: 13px; }
/* Properties as a flat, inline JSON schema block (keys in ink, types in accent) — matches the card;
   braces via ::before/::after. */
.wf-schema { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-size: 12.5px; line-height: 1.85; color: var(--muted); background: color-mix(in srgb, var(--ink) 4%, transparent); border: 1px solid var(--border); border-radius: 10px; padding: 12px 16px; margin: 2px 0 0; word-break: break-word; }
.wf-schema::before { content: "{ "; }
.wf-schema::after { content: " }"; }
.wf-json__pair--orphan { opacity: .5; }
.wf-schema__rm { font-style: italic; }
.wf-empty2 { padding: 40px 0; text-align: center; }
/* Records table — natural column sizing (min-width:100% fills the pane, scrolls when wider). Every cell is
   ONE line by default: a long value gets an ellipsis (full text on hover; clicking the row expands it), an
   empty cell is a quiet — (the amber missing-cell highlight is RETIRED — the review pill carries the signal).
   Headers are the house mono micro-label; every data column + Received is a SORT control. */
/* The wrap is the grid's ONE scroller, both axes (its height is bounded by .wf-tablearea below): the
   header stays sticky at its top, the first column sticky at its left, and the h-scrollbar at its foot —
   which is now the bottom of the viewport, not the bottom of the data. */
.wf-tablewrap { flex: 0 1 auto; min-height: 0; overflow: auto; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); overscroll-behavior-x: contain; }
.wf-table { border-collapse: separate; border-spacing: 0; width: auto; min-width: 100%; font-size: 13px; }
.wf-table th { text-align: left; font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-weight: 600; font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); padding: 10px 14px; border-bottom: 1px solid var(--border); white-space: nowrap; background: var(--surface); position: sticky; top: 0; z-index: 1; user-select: none; }
.wf-table th.wf-th--sort { cursor: pointer; transition: color .12s; }
.wf-table th.wf-th--sort:hover { color: var(--ink); }
.wf-table th.is-sorted { color: var(--accent); }
.wf-sort__arrow { margin-left: 5px; font-size: 10px; opacity: 0; transition: opacity .12s; }
.wf-table th.wf-th--sort:hover .wf-sort__arrow, .wf-table th.is-sorted .wf-sort__arrow { opacity: .9; }
.wf-table td { padding: 9px 14px; border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); vertical-align: top; white-space: nowrap; color: var(--ink); line-height: 1.5; }
.wf-table tbody tr:last-child td { border-bottom: none; }
.wf-table tbody tr:hover td { background: color-mix(in srgb, var(--accent) 4%, transparent); }
/* The FIRST column is the row's anchor — sticky while the other columns pan/page beneath it (everything
   that varies must vary beside something that never moves). Opaque backgrounds cover the sliding columns;
   the seam shadow appears only once actually scrolled (.is-hscrolled, toggled by workflows.js). */
.wf-table th:first-child, .wf-table td:first-child { position: sticky; left: 0; z-index: 2; background: var(--surface); }
.wf-table th:first-child { z-index: 3; } /* above the other sticky headers AND the sticky body cells */
.wf-table tbody tr:hover td:first-child { background: color-mix(in srgb, var(--accent) 4%, var(--surface)); }
.wf-tablewrap.is-hscrolled th:first-child, .wf-tablewrap.is-hscrolled td:first-child { box-shadow: 8px 0 12px -8px rgba(0, 0, 0, .25); }
/* One-line ellipsis on a long value (notes, descriptions); click the row to read it in full. */
.wf-table td.wf-td--long { max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
/* The ?row= deep-link landing — the grid twin of the card flash (a tr has no bordered box to ring, so the
   cells wash accent and fade; same 1.6s beat as card-target). The sticky first cell keeps its surface after
   the animation ends, so the wash must live on the cells, never on the tr background. */
.wf-table tr.is-target > td { animation: wf-row-target 1.6s ease-out; }
@keyframes wf-row-target { 0% { background-color: color-mix(in srgb, var(--accent) 18%, var(--surface)); } 100% { background-color: transparent; } }
.wf-table tbody tr.is-open td.wf-td--long { white-space: normal; overflow-wrap: anywhere; max-width: 360px; }
/* An empty cell reads as a quiet dash, never a highlight. */
.wf-td--empty { color: var(--muted); opacity: .5; }
/* The KEY column (row identity on a keyed workflow) reads a touch heavier. */
.wf-table td.wf-col--key { font-weight: 600; }
/* money / number columns right-align with tabular figures. */
.wf-table th.wf-col--num, .wf-table td.wf-col--num { text-align: right; font-variant-numeric: tabular-nums; }
.wf-table__when { color: var(--muted); white-space: nowrap; font-size: 12px; }
.wf-when__time { display: inline; }
/* Lifecycle STAGE pill (a keyed+states workflow) — quiet outline; green once it reaches the FINAL declared
   stage. Colours are local (the scoped-custom-property trap — another tab's tokens can't reach here). */
.wf-stagepill { display: inline-block; font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted); }
.wf-stagepill--final { background: rgba(15,157,99,.1); border-color: rgba(15,157,99,.38); color: #0d7a4e; }
[data-theme="dark"] .wf-stagepill--final { color: #57c48f; }
/* Source column — a thumbnail of the email attachment the row came from (click → inline Lightbox), then
   the SHARED ✉ N pill on EVERY email-sourced row (.card-srcbtn + SourceModal — the same affordance as the
   TODO / Intelligence / fact cards, no local twin; ✉ 1 on a plain row, ✉ N on a merged keyed row). */
.wf-table__src { width: 1%; white-space: nowrap; }
.wf-table__src .card-srcbtn { vertical-align: middle; }
.wf-src-thumb + .card-srcbtn { margin-left: 4px; }
.wf-src-thumb { display: inline-block; width: 34px; height: 34px; border-radius: 6px; overflow: hidden; border: 1px solid var(--border); vertical-align: middle; margin-right: 4px; cursor: zoom-in; }
.wf-src-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wf-src-more { font-size: 11px; color: var(--muted); vertical-align: middle; }
.wf-src-email { font-size: 15px; cursor: default; opacity: .75; }
.wf-src-chat { font-size: 15px; text-decoration: none; opacity: .75; cursor: pointer; }
.wf-src-chat:hover { opacity: 1; }
/* Table controls — the row search, above the table. (The review/confirmed status filter + the row's
   "review" pill were retired 2026-08-21 — on a keyed lifecycle table "incomplete" is the normal mid-flight
   state, so the pill sat on nearly every row and said nothing; the Stage pill carries the real signal.
   The status survives in the DATA — record_list / record_confirm — just not on this surface.) */
/* BACKFILL progress strip (docs/WORKFLOW_TRIGGERS.md §6b) — one row per live/just-finished backfill, above the
   library. Theme-safe by construction (every colour is a token). SUCCESSES FADE, FAILURES PERSIST: a done strip
   clears itself after a couple of minutes server-side (the rows speak for themselves), a failed one stays. */
/* ⚠️ The success hue is declared ON THE STRIP, not on #tab-workflows. --inb-pos is scoped to #tab-inbox and
   --todo-handled to #tab-todo, so neither reaches here — the same scoped-custom-property trap that rendered
   the body-appended .inb-menu's danger item un-red and drifted the TODO chips. Carrying its own token also
   means the strip survives being moved to another surface. */
.wf-backfill { --wf-ok: #0f9d63; display: flex; align-items: center; gap: 10px; padding: 9px 14px; margin-bottom: 10px; font-size: 13px; color: var(--ink); background: color-mix(in srgb, var(--accent) 7%, var(--surface)); border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border)); border-radius: 10px; }
[data-theme="dark"] .wf-backfill { --wf-ok: #4ade80; }
.wf-backfill { flex-wrap: wrap; }
.wf-backfill__txt { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the named skips/failed of a finished run — a muted second line under the count (bounded in JS) */
.wf-backfill__skips { flex: 1 1 100%; min-width: 0; font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 640px) { .wf-backfill__skips { white-space: normal; } }
.wf-backfill__bar { flex: 0 0 120px; height: 5px; background: color-mix(in srgb, var(--accent) 18%, transparent); border-radius: 999px; overflow: hidden; }
.wf-backfill__fill { display: block; height: 100%; background: var(--accent); border-radius: 999px; transition: width .4s ease; }
.wf-backfill.is-done { background: color-mix(in srgb, var(--wf-ok) 8%, var(--surface)); border-color: color-mix(in srgb, var(--wf-ok) 30%, var(--border)); }
.wf-backfill.is-bad { background: color-mix(in srgb, var(--error) 8%, var(--surface)); border-color: color-mix(in srgb, var(--error) 34%, var(--border)); color: var(--ink); }
/* STAGED — prepared, not run. Deliberately the quietest of the four: nothing is happening and nothing has been
   spent, so it reads as a note rather than as activity (a dashed edge, the same "not committed yet" language
   the queue's suggestion pill used before it became a real button). */
.wf-backfill.is-staged { background: var(--surface); border-style: dashed; border-color: var(--border); color: var(--muted); }
@media (max-width: 640px) { .wf-backfill__bar { display: none; } .wf-backfill__txt { white-space: normal; } }

/* The table toolbar — search + `columns ‹ ›` + `1–50 of N ‹ ›` + the CSV export, ONE row right above the
   grid (2026-08-28, Curtis: "controlled clicks and pagination make the table feel built in"). The column
   pager hides itself when every column fits; the row pager's label doubles as the row count. */
.wf-controls { display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; align-items: center; }
.wf-search { flex: 1 1 200px; min-width: 0; max-width: 340px; padding: 7px 12px; font: inherit; font-size: 13.5px; color: var(--ink); background: var(--surface); border: 1px solid var(--border); border-radius: 8px; }
.wf-search:focus { outline: none; border-color: var(--accent); }
.wf-tools { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.wf-pager { display: inline-flex; align-items: center; gap: 2px; }
.wf-pager__lbl { font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace); font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); margin-right: 5px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.wf-pgbtn { width: var(--btn-h-md); height: var(--btn-h-md); padding: 0; border-radius: 8px; border: 1px solid transparent; background: none; color: var(--muted); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; text-decoration: none; }
.wf-pgbtn svg { width: 15px; height: 15px; }
.wf-pgbtn:hover:not(:disabled) { background: var(--surface); color: var(--ink); border-color: var(--border); }
.wf-pgbtn:disabled { opacity: .35; cursor: default; }
/* The grid area FILLS the remaining detail height (min-height keeps it usable under a tall spec); the
   wrap inside grows only to its content, capped by this — so a short page is a short card and a long one
   scrolls internally. Dimmed while a page is in flight. */
.wf-tablearea { flex: 1 1 auto; min-height: 300px; display: flex; flex-direction: column; align-items: stretch; transition: opacity .15s; }
.wf-tablearea.is-loading { opacity: .55; pointer-events: none; }
/* Orphaned column (a field removed after rows were collected) — muted, so it reads as legacy data. */
.wf-table th.wf-col--orphan { font-style: italic; opacity: .7; }
@media (max-width: 900px) {
  #tab-workflows.workflows { flex-direction: column; padding: var(--mtop-h) 0 0; }
  #tab-workflows.workflows.is-reading .doc { position: fixed; inset: var(--mtop-h) 0 0 var(--mrail-w); z-index: 30; background: var(--bg); } /* left inset clears the persistent icon rail */
  html.nav-hidden #tab-workflows.workflows.is-reading .doc { left: 0; } /* rail hidden → full width */
  .wf-detail { padding: 20px 16px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--copilot-peek) + 16px); }
  /* the sticky anchor column can't eat a phone — cap it; the rest pans (swipe OR the ‹ › arrows) */
  .wf-table th:first-child, .wf-table td:first-child { max-width: 44vw; overflow: hidden; text-overflow: ellipsis; }
}
/* Phone: the card's right-hand aside (status pill + meta) was squeezing the trigger prose / schema into a
   narrow column (reported 2026-08-21) — the card stacks instead: content full width, then ACTIVE · meta as
   one row underneath (same degrade as .todo-card / .inb-row). The detail's action-spec rows stack too — a
   148px right-aligned label column leaves a phone's value column squished. */
@media (max-width: 640px) {
  .wf-card { grid-template-columns: minmax(0, 1fr); row-gap: 10px; padding-right: 52px; }
  .wf-card__aside { flex-direction: row; align-items: center; gap: 10px; }
  /* The chevron pins top-right (the inbox ⋯ pattern); the card's right padding keeps the name clear of it. */
  .wf-card__toggle { position: absolute; top: 13px; right: 12px; }
  .wf-aspec__row { flex-direction: column; align-items: flex-start; gap: 2px; }
  .wf-aspec__label { flex: none; text-align: left; }
  /* the table toolbar stacks: search on its own line, then the pagers + export spread across their own
     full line (a right-hugging cluster clipped the export off a 390px screen) */
  .wf-search { flex-basis: 100%; max-width: none; }
  .wf-tools { flex: 1 1 100%; margin-left: 0; justify-content: space-between; }
  /* the detail toolbar on a phone: the back label yields its width to the workflow name + the two
     switches (scoped to .wf-doc — the arrow alone is the affordance everywhere else too) */
  .wf-doc .back-btn span { display: none; }
  .wf-doc .doc__acts { gap: 12px; }
}

#tab-documents.documents { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row; }
.documents-main { flex: 1 1 auto; min-width: 0; display: flex; min-height: 0; }
.documents-main > .lib, .documents-main > .doc { flex: 1 1 auto; min-width: 0; min-height: 0; }
#tab-documents.documents .doc { display: none; }               /* library is the default view */
#tab-documents.documents.is-reading .lib { display: none; }
#tab-documents.documents.is-reading .doc { display: flex; flex-direction: column; overflow: hidden; }

/* Library — a responsive card gallery, its own scroll. Top/side inset matches the Inbox topbar (20px/30px). */
.lib { overflow-y: auto; padding: 20px 30px 64px; }
/* The library header matches the Inbox tab EXACTLY (title 20px/800 display + a mono meta count stacked below —
   .inb-brand h1 / .inb-brand-meta). Documents + Workflows both ride this now; the #tab-workflows override below
   is redundant-but-harmless. */
.lib__head { display: flex; flex-direction: column; align-items: flex-start; gap: 0; margin: 0 0 18px; }
.lib__head h1 { font-family: var(--font-display); font-size: 20px; font-weight: 800; letter-spacing: -.02em; margin: 0; }
.lib__count { font-family: var(--font-mono); font-size: 11px; letter-spacing: .02em; color: var(--muted); margin: 4px 0 0; }
/* (The old #tab-workflows .lib/.lib__head/.lib__count overrides were byte-identical duplicates of the
   shared rules above — deleted 2026-08-24: the id-scoped padding out-specified the 879px mobile .lib
   override, leaving Workflows alone with desktop 30px gutters on a phone.) */
.lib__sub { color: var(--muted); font-size: 14px; margin: 0 0 20px; max-width: 640px; line-height: 1.5; }
.lib__filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 22px; }

/* The STARTER MENU (docs/DOCTYPES.md §5) — the type enum as one-tap copilot seeds. ALWAYS visible
   (never behind a New button — the pills ARE the advertisement); doubles as the empty state. */
.doc-starters { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 18px; margin: 0 0 22px; }
.doc-starters__q { font-family: var(--font-display); font-size: 15.5px; font-weight: 700; margin: 0 0 12px; }
.doc-starters__grid { display: flex; flex-wrap: wrap; gap: 8px; }
.doc-starter { display: inline-flex; align-items: center; padding: 8px 14px; border-radius: 20px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); font-size: 13.5px; font-weight: 550; cursor: pointer; font-family: inherit; transition: border-color .12s, background .12s; }
.doc-starter:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--bg)); }
.doc-starters__hint { color: var(--muted); font-size: 13px; margin: 14px 0 0; line-height: 1.5; }
/* Full-width rows (not a 2-up card grid) — a detail-rich clickable row per item, shared by Documents + Workflows. */
.lib__grid { display: flex; flex-direction: column; gap: 10px; }
.lib__empty { padding: 8px 2px; }

/* Type filter chips — the hard document-type enum (fixed looks: the emoji IS the look, no dot). */
.lib__chip { display: inline-flex; align-items: center; gap: 7px; padding: 6px 13px; border-radius: 20px; border: 1px solid var(--border); background: var(--surface); color: var(--muted); font-size: 13px; font-weight: 500; cursor: pointer; font-family: inherit; }
.lib__chip:hover { border-color: var(--accent); }
.lib__chip.is-active { border-color: transparent; background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--ink); }
.doc-card.is-hidden { display: none; }

/* A document ROW — full-width, clickable; the edited-time meta sits in its own right column, the
   type / title / snippet stack on the left. (Children stay flat so documents.js's live helpers keep
   targeting .doc-card__type / __title / __snip / __meta directly.) */
.doc-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 20px; row-gap: 3px; align-items: center; text-align: left; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 18px; cursor: pointer; color: var(--ink); font-family: inherit; font-size: inherit; text-decoration: none; transition: border-color .12s, background .12s; }
.doc-card:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 4%, var(--surface)); }
.doc-card.is-active { border-color: var(--accent); }
.doc-card > * { grid-column: 1; min-width: 0; }
.doc-card__type { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.doc-card__title { font-size: 16px; font-weight: 650; line-height: 1.3; }
.doc-card__snip { font-size: 13px; color: var(--muted); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.doc-card__meta { grid-column: 2; grid-row: 1 / span 3; align-self: center; justify-self: end; text-align: right; font-size: 12px; color: var(--muted); opacity: .85; white-space: nowrap; }

/* Document — toolbar + a centered readable page. */
.doc__toolbar { display: flex; align-items: center; gap: 12px; padding: 10px 20px; border-bottom: 1px solid var(--border); flex: none; }
/* documents + workflows detail back → the shared .back-btn pill (no per-surface rule needed; the toolbar lays it out). */
.doc__titlewrap { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.doc__title { font-size: 15px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc__type { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }
.doc__state { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 12.5px; white-space: nowrap; }
.doc__state-dot { width: 7px; height: 7px; border-radius: 50%; background: #45c17a; flex: none; }
.doc__state.is-saving .doc__state-dot { background: var(--muted); }
.doc__acts { display: flex; gap: 4px; }
.doc__act { width: 34px; height: 34px; border-radius: 8px; background: none; border: 1px solid transparent; color: var(--muted); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
.doc__act svg { width: 16px; height: 16px; }
.doc__act:hover { background: var(--surface); color: var(--ink); border-color: var(--border); }
.doc__act:disabled { opacity: .45; cursor: default; } /* the attach act mid-render */
.doc__scroll { flex: 1; overflow-y: auto; min-height: 0; }
.doc__page { max-width: var(--content-max); margin: 0 auto; padding: 40px 32px 88px; }
.doc__page > :first-child { margin-top: 0; }
.doc__page h1 { font-size: 28px; margin: 0 0 8px; line-height: 1.2; }
.doc__page h2 { font-size: 18px; margin: 28px 0 10px; }
.doc__page h3 { font-size: 15.5px; margin: 22px 0 8px; }
.doc__page p, .doc__page li { font-size: 15.5px; line-height: 1.7; }
.doc__page ul, .doc__page ol { padding-left: 22px; margin: 10px 0; }
.doc__page li { margin: 4px 0; }
.doc__page a { color: var(--accent); }
.doc__page hr { border: none; border-top: 1px solid var(--border); margin: 24px 0; }
.doc__page .docbody__title { font-size: 28px; margin: 0 0 12px; line-height: 1.2; }
.doc__page .docbody__tags { margin: 0 0 20px; }
.doc__page .docbody__source { margin-top: 34px; }
.doc__spinner { display: flex; justify-content: center; padding: 60px 0; }

/* ── Document furniture + the three gaps (docs/DOCUMENTS_ROADMAP.md §4) ───────────────────────────
   These land precisely on the SENDABLE types — a quote, an invoice, a price list, a contract — which
   is why they're not cosmetic: this markup is what a customer receives. */

/* TABLES had NO rule at all (`.prose` is the public site, `.led-` the Tables tab, `.wf-` Workflows),
   so a quote's line items rendered as a bare browser table with no overflow container — a phone
   scrolled the whole DOCUMENT sideways to see a price column. `display:block` + `width:max-content`
   is the GitHub-markdown solution: the table itself becomes the scroll container, so a wide table
   scrolls INSIDE the page instead of dragging the page with it. */
.doc__page table { display: block; width: max-content; max-width: 100%; overflow-x: auto; border-collapse: collapse; margin: 16px 0; font-size: 14.5px; }
.doc__page th, .doc__page td { border: 1px solid var(--border); padding: 7px 12px; text-align: left; vertical-align: top; }
.doc__page thead th { background: var(--surface); font-weight: 650; white-space: nowrap; }
.doc__page tbody tr:nth-child(even) { background: color-mix(in srgb, var(--surface) 45%, transparent); }
/* A money column reads wrong ragged-left; markdown's `---:` alignment still wins over this. */
.doc__page td:not(:first-child), .doc__page th:not(:first-child) { white-space: nowrap; }

/* A long unbreakable token (a URL, a reference number) must not push the page wide at the ~298px a
   phone actually gives the reader. `break-word` only breaks when it would otherwise overflow. */
.doc__page p, .doc__page li, .doc__page td { overflow-wrap: break-word; }

/* FILL-FIELDS — the playbook's honesty rule made visible. A fabricated contract term is worse than a
   blank, so Dustav leaves `[deposit amount]`; drawing those makes "what's still unanswered" scannable
   instead of buried in prose. Deliberately quiet, not alarming — it's a normal state for a draft. */
.doc-fill { background: color-mix(in srgb, var(--accent) 13%, transparent); border-bottom: 1px dashed color-mix(in srgb, var(--accent) 55%, transparent); border-radius: 3px; padding: 0 3px; color: var(--ink); }

/* SIGNATURE RULES — a hand-typed run of underscores is one unbreakable "word" that overflows a narrow
   column. As a real element it wraps to its own line instead, which is the mobile fix; it also stops
   the rule's length depending on how many underscores the model happened to type. */
.doc-rule { display: inline-block; width: 240px; max-width: 100%; border-bottom: 1px solid var(--ink); margin-bottom: 2px; vertical-align: baseline; }

/* ── THE PRINT SHEET (docs/DOCUMENTS_ROADMAP.md §4) ───────────────────────────────────────────────
   The PDF leads, and this is it: "Print / PDF" prints the REAL document page rather than a second
   hand-rolled renderer, so there is exactly ONE thing that can be wrong. `window.print()` → "Save as
   PDF" is the whole PDF pipeline — no headless browser on a 600MB-capped web process (the memory
   class that has SIGKILLed this app before), and print CSS does not care which device triggered it,
   so she taps print on her phone and still gets a correct letter-size sheet.

   The letterhead is print-ONLY: on screen she's drafting, and the identity band is what the customer
   sees. There is deliberately NO print footer — a contact line at the foot only repeats what the
   letterhead already says, and the browser draws its own footer besides (Curtis, 2026-08-20). */
.doc__letterhead { display: none; }

@media print {
  /* Chrome is not part of the document. */
  .rail, .copilot, .mtop, .navdrawer, .tabbar, #doc-library, .doc__toolbar, .doc__state, .doc__acts,
  .doc-starters, .lib__filters { display: none !important; }
  html, body { background: #fff !important; }
  /* The app is a fixed-height flex shell; printing needs normal document flow or only page 1 prints.
     ⚠️ `padding: 0` is LOAD-BEARING, and the reason is a trap worth remembering: a printed page is
     ~816 CSS px wide (8.5in at 96dpi), which is UNDER this stylesheet's 879px mobile breakpoint — so
     printing from a desktop browser silently applies the whole MOBILE stylesheet. That handed the
     sheet `.pane { padding-left: var(--mrail-w) }`, a 56px inset meant to clear the fixed icon rail,
     on a page where the rail isn't printed at all — visibly more margin on the left than the right.
     Any mobile-only chrome offset will leak in here the same way. */
  .pane, .documents-main, .doc, .doc__scroll, #tab-documents {
    display: block !important; position: static !important; overflow: visible !important;
    height: auto !important; max-height: none !important; inset: auto !important;
    margin: 0 !important; padding: 0 !important;
    /* Same breakpoint trap as the padding above, background edition: the MOBILE stylesheet (print
       width < 879px) paints .doc with var(--bg), which the browser dialog silently hid (backgrounds
       print OFF by default there) but the server engine's CLI prints — a grey wash over the whole
       sheet. Containers go transparent; html/body's #fff below is the paper. */
    background: transparent !important;
  }
  /* Tags are internal filing descriptors ("contract", "template") — how SHE finds it, not something
     the counterparty should read off a signed agreement. Dropping them also lets the title become the
     first child, so the margin reset below closes the band of dead air under the letterhead. */
  .docbody__tags { display: none !important; }
  @page { size: letter; margin: 18mm 16mm; }

  .doc__page { max-width: none; margin: 0; padding: 0; color: #000; }
  /* The body's first heading must sit right under the letterhead rule — its own top margin plus the
     article wrapper's leaves a band of dead air at the top of page 1 otherwise. */
  .doc__page .docbody > :first-child, .doc__page .md > :first-child { margin-top: 0 !important; }
  .doc__page .docbody { margin: 0; }
  /* Force ink on paper regardless of the viewer's theme — a dark-mode reader must not print grey on
     grey. Same reasoning as rendering received email on white: the artifact is paper. */
  .doc__page, .doc__page p, .doc__page li, .doc__page h1, .doc__page h2, .doc__page h3,
  .doc__page td, .doc__page th { color: #000 !important; }
  .doc__page a { color: #000 !important; text-decoration: underline; }
  .doc__page table, .doc__page th, .doc__page td { border-color: #999 !important; }
  .doc__page thead th { background: #f1f1f1 !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .doc__page tbody tr:nth-child(even) { background: transparent !important; }
  /* A table that scrolls on screen must PAGINATE on paper, not clip at the sheet edge. */
  .doc__page table { display: table !important; width: 100% !important; overflow: visible !important; }
  .doc__page td, .doc__page th { white-space: normal !important; }
  /* A fill-field prints as a blank to write on, not as a coloured chip. */
  .doc-fill { background: transparent !important; border-bottom: 1px solid #666 !important; }
  .doc-rule { border-bottom-color: #000 !important; }
  /* Don't strand a heading or split a signature block across the page break. */
  .doc__page h1, .doc__page h2, .doc__page h3 { break-after: avoid-page; }
  .doc__page table, .doc__page tr { break-inside: avoid; }

  .doc__letterhead {
    display: flex !important; align-items: center; gap: 12px; padding: 0 0 10px; margin: 0 0 22px;
    border-bottom: 2px solid var(--doc-brand, #222);
  }
  .doc__lh-logo { height: 42px; width: auto; max-width: 190px; object-fit: contain; }
  .doc__lh-who { display: flex; flex-direction: column; min-width: 0; }
  .doc__lh-name { font-family: var(--font-display); font-size: 16px; font-weight: 800; color: #000; letter-spacing: -.01em; }
  .doc__lh-tag { font-size: 11px; color: #444; }
  .doc__lh-contact { margin-left: auto; text-align: right; font-size: 10px; color: #444; max-width: 46%; }
}

/* See-the-edits — a surgical/whole edit lands with a soft accent highlight on the changed block(s),
   then settles. documents.js diffs the rendered blocks and tags the changed ones (.doc-edited). */
@keyframes docSettle { from { background: color-mix(in srgb, var(--accent) 22%, transparent); } to { background: transparent; } }
.doc-edited { border-radius: 6px; box-decoration-break: clone; margin: 0 -6px; padding: 0 6px; }
.doc-edited.is-settling { animation: docSettle 2.4s ease-out forwards; }

@media (max-width: 879px) {
  #tab-documents.documents { flex-direction: column; padding: var(--mtop-h) 0 0; }
  /* Five toolbar acts crowd a phone's title out — the .md export (a desktop-shaped exit) yields;
     Copy / PDF / Print / Attach stay. */
  #doc-dl { display: none; }
  .lib { padding: 16px 16px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--copilot-peek) + 16px); } /* clear the docked composer */
  /* The reader is a fixed slide-in over the library (clears the fixed top bar + the icon rail). */
  #tab-documents.documents.is-reading .doc { position: fixed; inset: var(--mtop-h) 0 0 var(--mrail-w); z-index: 30; background: var(--bg); }
  html.nav-hidden #tab-documents.documents.is-reading .doc { left: 0; } /* rail hidden → full width */
  .doc__page { padding: 24px 18px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--copilot-peek) + 16px); } /* clear the docked composer */
}

/* ══ Tables tab — the transaction-ledger spine (docs/LEDGER.md). Ported from scripts/ledger-lab/tables-html.js,
      scoped under #tab-ledger + inheriting the app theme/accent. The app has no --pos/--neg/--warn tokens, so
      they're defined locally here (reusing the app's usual amber/green hexes). ══ */
#tab-ledger.ledger { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row;
  --led-pos: #1c8f52; --led-neg: var(--error); --led-warn: #9a6c14; --led-warn-bg: rgba(224, 165, 42, .12); }
[data-theme="dark"] #tab-ledger.ledger { --led-pos: #4ade80; --led-warn: #e3b341; --led-warn-bg: rgba(224, 165, 42, .14); }
/* The Email-log surface (moved out of the Tables tab) — carries the ledger colour vars, but a simple padded
   single-column page (no tables pane, no copilot). */
#tab-log { --led-pos: #1c8f52; --led-neg: var(--error); --led-warn: #9a6c14; display: block; max-width: 900px; margin: 0 auto; padding: 22px 20px 60px; }
[data-theme="dark"] #tab-log { --led-pos: #4ade80; }
.log-head { margin-bottom: 16px; }
.log-title { font-family: var(--font-display); font-weight: 700; font-size: 22px; color: var(--ink); margin: 0; }
.log-title .led-brand-sub { font-weight: 400; color: var(--muted); font-size: 15px; }
.led-content { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; }
.led-ml { font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .06em; font-size: 10.5px; color: var(--muted); }
.led-topbar { padding: 20px 30px 0; display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.led-brand { flex: 1 1 auto; min-width: 0; }
.led-syncbar { flex: none; display: flex; align-items: center; gap: 8px; }
.led-sync-max { width: 64px; font-family: var(--font-mono); font-size: 12px; color: var(--ink); background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 7px 9px; text-align: right; }
.led-sync-max::placeholder { color: var(--muted); }
.led-sync-btn { font-family: var(--font-display); font-weight: 600; font-size: 13px; color: var(--accent-ink); background: var(--accent); border: 1px solid var(--accent); border-radius: 9px; padding: 8px 16px; cursor: pointer; margin-left: 4px; }
.led-sync-btn:hover { filter: brightness(1.06); }
.led-sync-btn:disabled { opacity: .6; cursor: default; }
.led-sync-status { display: flex; align-items: center; gap: 10px; max-width: 420px; }
.led-sync-spin { width: 14px; height: 14px; flex: none; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--accent) 30%, transparent); border-top-color: var(--accent); animation: ledSpin .7s linear infinite; }
@keyframes ledSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .led-sync-spin { animation: none; } }
.led-sync-count { text-transform: none; letter-spacing: .02em; font-size: 12px; color: var(--ink); font-variant-numeric: tabular-nums; }
.led-sync-sub { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px; }
.led-sync-stop { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); background: none; border: 1px solid var(--border); border-radius: 6px; padding: 3px 8px; cursor: pointer; }
.led-sync-stop:hover { color: var(--ink); border-color: var(--muted); }
.led-sync-status.is-error .led-sync-spin, .led-sync-status.is-error .led-sync-stop { display: none; }
.led-sync-status.is-error .led-sync-count, .led-sync-status.is-error .led-sync-sub { color: var(--led-neg); white-space: normal; max-width: 520px; }
.led-brand h1 { font-family: var(--font-display); font-size: 20px; font-weight: 800; letter-spacing: -.02em; margin: 0; }
.led-brand-sub { color: var(--muted); font-weight: 400; }
.led-brand-meta { margin-top: 4px; text-transform: none; letter-spacing: .02em; font-size: 11px; }
.led-tabs { position: sticky; top: 0; z-index: 12; display: flex; gap: 2px; padding: 12px 30px 0; background: color-mix(in srgb, var(--bg) 90%, transparent); backdrop-filter: blur(8px); border-bottom: 1px solid var(--border); }
.led-tab { font-family: var(--font-display); font-weight: 600; font-size: 14px; color: var(--muted); background: none; border: none; border-bottom: 2px solid transparent; padding: 9px 15px; cursor: pointer; margin-bottom: -1px; }
.led-tab.active { color: var(--ink); border-bottom-color: var(--accent); }
.led-tabn { text-transform: none; letter-spacing: 0; }
.led-main { padding: 22px 30px 80px; }
.led-pane { display: none; } .led-pane.active { display: block; }
/* overview */
.led-stats { display: flex; gap: 14px; flex-wrap: wrap; }
.led-stat { flex: 1; min-width: 150px; background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 15px 18px; }
.led-lab { display: flex; gap: 6px; align-items: baseline; }
.led-sub { text-transform: none; letter-spacing: 0; opacity: .7; font-size: 9px; }
.led-val { font-family: var(--font-display); font-weight: 800; font-size: 22px; margin-top: 7px; letter-spacing: -.02em; }
.led-val.pos { color: var(--led-pos); } .led-val.neg { color: var(--led-neg); } .led-val.warn { color: var(--led-warn); }
.led-ccy { font-family: var(--font-mono); font-size: 10px; color: var(--muted); margin-left: 3px; font-weight: 400; }
.led-kcs { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 20px; }
.led-kc { display: flex; align-items: center; gap: 11px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 11px 16px; min-width: 150px; }
.led-kc-ic { font-size: 16px; color: var(--accent); opacity: .85; }
.led-kc-n { font-family: var(--font-display); font-weight: 800; font-size: 18px; }
.led-kc-l { margin-top: 1px; }
.led-sech { font-size: 13px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); font-weight: 700; margin: 30px 0 12px; font-family: var(--font-mono); }
.led-tcards { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px; }
.led-tcard { display: flex; align-items: center; gap: 10px; background: var(--surface); border: 1px solid var(--border); border-radius: 11px; padding: 13px 16px; color: var(--ink); }
.led-tcard:hover { border-color: var(--accent); }
.led-tcard-n { font-family: var(--font-display); font-weight: 600; font-size: 15px; }
.led-tcard-c { margin-left: auto; font-size: 12px; }
.led-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; display: inline-block; background: var(--muted); }
/* Per-spine-table dot colour (was an inline style — CSP style-src 'self' blocks that; see services/ledger.js). */
.led-dot--contacts { background: var(--accent); }
.led-dot--income { background: var(--led-pos, #16a34a); }
.led-dot--expenses { background: var(--led-neg, #e11d48); }
.led-dot--shipments { background: #14b8a6; }
.led-joins { margin-top: 30px; background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 16px 20px; }
.led-jtitle { margin-bottom: 12px; }
.led-joins ul { margin: 0; padding: 0; list-style: none; columns: 2; column-gap: 36px; font-size: 13px; }
.led-joins li { margin-bottom: 9px; break-inside: avoid; color: var(--muted); }
.led-joins b { color: var(--ink); font-weight: 600; }
.led-jar { color: var(--accent); }
.led-via { text-transform: none; letter-spacing: 0; }
.led-jnote { display: block; font-size: 11px; opacity: .75; margin-top: 1px; text-transform: none; letter-spacing: 0; }
/* ledgers */
/* Pinned to the top of the scroll container (.led-content), with a SOLID bg — top:42px left a ~42px gap
   above the pinned pills (the topbar isn't sticky) and the semi-transparent bg bled the rows (and the pink
   key column) scrolling under it. top:0 + an opaque bg = clean, no bleed. */
.led-subtabs { position: sticky; top: 0; z-index: 8; display: flex; gap: 7px; flex-wrap: wrap; padding: 12px 0; margin-bottom: 16px; background: var(--bg); border-bottom: 1px solid var(--border); }
.led-stab { display: inline-flex; align-items: center; gap: 8px; font-family: var(--font-display); font-weight: 600; font-size: 13px; color: var(--muted); background: var(--surface); border: 1px solid var(--border); border-radius: 20px; padding: 6px 14px; cursor: pointer; }
.led-stab:hover { color: var(--ink); }
.led-stab.active { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.led-stab b { font-family: var(--font-mono); font-size: 11px; opacity: .85; }
.led-table { display: none; } .led-table.active { display: block; }
.led-tablehd { display: flex; align-items: baseline; gap: 10px; margin: 0 0 12px; }
.led-tablehd h3 { font-family: var(--font-display); font-size: 17px; font-weight: 700; margin: 0; }
.led-cnt { text-transform: none; letter-spacing: 0; font-size: 11px; }
.led-flow { font-family: var(--font-mono); font-size: 9.5px; padding: 2px 8px; border-radius: 20px; text-transform: uppercase; letter-spacing: .04em; }
.led-flow--in { background: color-mix(in srgb, var(--led-pos) 15%, transparent); color: var(--led-pos); }
.led-flow--out { background: color-mix(in srgb, var(--led-neg) 15%, transparent); color: var(--led-neg); }
.led-scroll { overflow-x: auto; max-width: 100%; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); }
.led-table table { border-collapse: collapse; width: 100%; font-size: 13px; }
.led-table th, .led-table td { text-align: left; padding: 9px 16px; border-bottom: 1px solid var(--border); white-space: nowrap; max-width: 600px; overflow: hidden; text-overflow: ellipsis; }
.led-table tbody tr:last-child td { border-bottom: none; }
.led-table th { font-family: var(--font-mono); color: var(--muted); text-transform: uppercase; letter-spacing: .05em; font-size: 10px; font-weight: 500; vertical-align: bottom; }
.led-ty { display: block; color: var(--accent); font-size: 9px; text-transform: none; letter-spacing: 0; opacity: .75; margin-top: 2px; font-weight: 400; }
.led-table th.led-keyc, .led-table td.led-keyc { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.led-table td.led-keyc { color: var(--accent); font-weight: 500; }
.led-table td.led-num, .led-table th.led-num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
.led-empty { color: var(--muted); text-align: center; padding: 18px; }
/* Contacts' Name cell carries a monogram avatar beside the name. */
.led-name { display: inline-flex; align-items: center; gap: 9px; min-width: 0; }
.led-namec { white-space: nowrap; }
.led-life { font-family: var(--font-mono); font-size: 9.5px; padding: 2px 8px; border-radius: 20px; letter-spacing: .04em; background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
.led-life-open { background: color-mix(in srgb, var(--led-warn) 20%, transparent); color: var(--led-warn); }
.led-life-paid, .led-life-delivered { background: color-mix(in srgb, var(--led-pos) 16%, transparent); color: var(--led-pos); }
tr.led-row { cursor: pointer; }
tr.led-row:hover { background: color-mix(in srgb, var(--accent) 4%, transparent); }
tr.led-row.merged { box-shadow: inset 3px 0 0 var(--led-warn); }
/* Support-emails affordance (docs/INBOX_UX.md §Tables) — a row is a clean canonical line; the "📧 N" button
   opens a modal listing the emails behind it (each a link to its Inbox thread). No inline drill. */
th.led-srch, td.led-srcc { width: 1%; white-space: nowrap; text-align: right; }
.led-srcbtn { font-family: var(--font-mono); font-size: 11px; color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border)); border-radius: 7px; padding: 2px 9px; cursor: pointer; white-space: nowrap; }
.led-srcbtn:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); }
tr.led-row.merged .led-srcbtn { color: var(--led-warn); border-color: color-mix(in srgb, var(--led-warn) 40%, var(--border)); background: color-mix(in srgb, var(--led-warn) 12%, transparent); }

/* the support-emails modal */
.led-modal { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center; padding: 24px; }
.led-modal[hidden] { display: none; }
.led-modal__scrim { position: absolute; inset: 0; background: rgba(0,0,0,.45); } /* the ONE wash alpha (modal audit 2026-08-27) */
.led-modal__panel { position: relative; width: 100%; max-width: 520px; max-height: 80vh; display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; box-shadow: 0 18px 50px rgba(0,0,0,.28); overflow: hidden; }
.led-modal__head { display: flex; align-items: center; justify-content: space-between; padding: 13px 16px; border-bottom: 1px solid var(--border); }
.led-modal__x { background: none; border: none; color: var(--muted); font-size: 15px; cursor: pointer; line-height: 1; }
.led-modal__x:hover { color: var(--ink); }
.led-modal__body { overflow-y: auto; padding: 8px; }
.led-modal__loading { padding: 20px 12px; color: var(--muted); font-size: 13px; text-align: center; }
/* ── THE SOURCE-EMAILS MODAL (Curtis, 2026-08-18) ─────────────────────────────────────────────────────────────
   Was a bare unexplained list: one truncated "Sender · subject" line per row under a tiny mono label. It didn't
   say what it was, and a row didn't read as something you could open. Now: a titled head with an envelope + a
   count, a one-line instruction, and rows shaped like actual email — the sender's SHARED monogram (same colour
   as the Inbox row and the Contacts table for that person), who on top, what beneath, the date, and a chevron.
   The panel is the .led-modal chrome, reused; only the source variant is restyled here. */
.led-modal--src .led-modal__panel { max-width: 480px; }
.led-modal__title { display: inline-flex; align-items: center; gap: 8px; font-family: var(--font-display); font-weight: 700; font-size: 14.5px; color: var(--ink); }
.led-modal__ic { flex: none; color: var(--accent); }
.led-modal__sub { margin: 0; padding: 9px 16px 3px; font-size: 12.5px; color: var(--muted); }
.led-srclist { display: flex; flex-direction: column; gap: 2px; }
/* align-items:center (not baseline) — the row has an avatar and a two-line body now, and baseline hangs them off
   the first text line, which left the monogram floating high. */
.led-srcitem { display: flex; align-items: center; gap: 11px; padding: 9px 10px; border-radius: 9px; text-decoration: none; color: var(--ink); transition: background .12s; }
.led-srcitem:hover, .led-srcitem:focus-visible { background: color-mix(in srgb, var(--accent) 9%, transparent); }
.led-srcitem__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
/* who — the strong line, because "which customer" is what you scan for */
.led-srcitem__who { font-weight: 600; font-size: 13.5px; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* what it was about — clamped to one line, so a long headline can't stretch the row */
.led-srcitem__subj { font-size: 12px; color: var(--muted); line-height: 1.35; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.led-srcitem__date { flex: none; font-family: var(--font-mono); font-size: 10.5px; color: var(--muted); }
/* the chevron is what makes a row read as OPENABLE — it was the missing affordance */
.led-srcitem__go { flex: none; font-size: 17px; line-height: 1; color: var(--muted); opacity: .55; transition: opacity .12s, transform .12s, color .12s; }
.led-srcitem:hover .led-srcitem__go { opacity: 1; color: var(--accent); transform: translateX(2px); }
.led-modal__aliases { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; padding: 12px; border-top: 1px solid var(--border); }
.led-alias { font-family: var(--font-mono); background: var(--bg); border: 1px solid var(--accent); color: var(--accent); border-radius: 6px; padding: 1px 8px; font-size: 11px; }
/* journal */
.led-jbar { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.led-jflag { color: var(--led-warn); }
.led-jtoggle { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; cursor: pointer; margin-left: auto; }
.led-jlist { display: flex; flex-direction: column; gap: 9px; }
.led-jrow { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 15px; }
.led-jrow.flagged { box-shadow: inset 3px 0 0 var(--led-warn); }
.led-jhd { display: flex; align-items: baseline; gap: 9px; margin-bottom: 8px; flex-wrap: wrap; }
.led-jfrom { font-family: var(--font-display); font-weight: 600; font-size: 13.5px; min-width: 0; overflow-wrap: anywhere; }
.led-jsub { color: var(--muted); font-size: 12.5px; flex: 1; min-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.led-jdate { text-transform: none; letter-spacing: 0; }
.led-jposts { display: flex; gap: 7px; flex-wrap: wrap; }
.led-jp { font-size: 11.5px; background: color-mix(in srgb, var(--h) 8%, var(--surface)); border: 1px solid color-mix(in srgb, var(--h) 35%, var(--border)); border-radius: 7px; padding: 3px 9px; color: var(--ink); }
.led-jp b { color: var(--h); font-weight: 600; }
.led-jp .led-jnote { display: block; text-transform: none; letter-spacing: 0; font-size: 10px; color: var(--muted); font-style: italic; margin-top: 1px; }
.led-empty-block { color: var(--muted); text-align: center; padding: 40px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }

/* The action-trace CHIPS (chat affordance vocabulary — inboxTrace.renderActionChips/renderSteps → the
   cockpit's "What Dustav did" panel + the live sync log). (The routing-pill family — .led-route, .led-jtop,
   .led-jhl, .led-jacts, .led-urg — died 2026-08-24 with logCard and the 🧠 activity panel it rendered into.)
   ⚠️ Never write a glob like `.led-route*` followed by a slash in a CSS comment: the star-slash TERMINATES
   the comment mid-sentence, the trailing prose becomes a garbage selector, and the NEXT real rule's brace
   becomes its body — which silently swallowed the whole .led-act base rule below (the chips went plain-text
   on every surface; caught live by Curtis, 2026-08-24, "the text got larger / these should be pills"). */
/* white-space:normal + overflow-wrap so a chip carrying long dynamic text (a to-do title, a search query, a
   drafted-reply summary) WRAPS inside the pill instead of stretching off-screen (the activity panel overflow).
   max-width:100% + min-width:0 lets it shrink within its flex-wrap row. Short chips are unaffected (one line). */
.led-act { --h: var(--muted); font-size: 11.5px; display: inline-flex; align-items: center; gap: 4px; white-space: normal; overflow-wrap: anywhere; max-width: 100%; min-width: 0; padding: 3px 10px; border-radius: 999px; color: var(--ink); background: color-mix(in srgb, var(--h) 9%, var(--surface)); border: 1px solid color-mix(in srgb, var(--h) 32%, var(--border)); }
.led-act-look { --h: #8b5cf6; }
.led-act-resolve { --h: var(--muted); }
.led-act-verify { --h: #0ea5e9; }
.led-act-cal { --h: var(--led-pos, #16a34a); }
.led-act-calrev { --h: #f59e0b; }
.led-act-draft { --h: #f59e0b; }
.led-act-todo { --h: #10b981; }
.led-act-learn { --h: #ec4899; }
.led-act-doc { --h: #6366f1; } /* a document filed to the library (docs/DOCUMENTS.md) — indigo, distinct from
                                  the amber draft and the teal tables it sits beside in a trace */
.led-act-noise { --h: var(--muted); opacity: .7; }
/* A workflow extraction that FAILED (the call, not the model's judgment — services/inboxCommit's honest
   failure chip): the amber warn hue, so an infra error never wears a quiet judgment's clothes. */
.led-act-warn { --h: var(--led-warn, #9a6c14); }
/* ↻ recheck — the act beside the wf none/fail chip (inboxTrace + wf-recheck.js): re-dispatch the sealed
   extraction for that table. A small rect act (pill = state, rect = act), muted until it settles. */
.led-recheck { font-family: var(--font-mono); font-size: 11px; letter-spacing: .03em; padding: 3px 9px; border-radius: 7px; border: 1px solid var(--border); background: var(--surface); color: var(--muted); cursor: pointer; }
.led-recheck:hover:not(:disabled) { color: var(--ink); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.led-recheck:disabled { cursor: default; }
.led-recheck.is-done { color: var(--led-pos, #1c8f52); border-color: color-mix(in srgb, var(--led-pos, #1c8f52) 40%, var(--border)); }
.led-recheck.is-none { color: var(--muted); }
/* Per-spine-table hue for a "file" chip (was an inline --h — CSP blocks inline styles; see services/ledger.js). */
.led-actf--contacts { --h: var(--accent); }
.led-actf--income { --h: var(--led-pos, #16a34a); }
.led-actf--expenses { --h: var(--led-neg, #e11d48); }
.led-actf--shipments { --h: #14b8a6; }
/* Live sync: a chip flashes in as it streams. (The card-level .led-jrow animations died with logCard.) */
.led-act.is-new { animation: ledChipIn .35s ease; }
@keyframes ledChipIn { from { opacity: 0; transform: scale(.9); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .led-act.is-new { animation: none; } }
@media (max-width: 879px) {
  #tab-ledger.ledger { flex-direction: column; padding: var(--mtop-h) 0 0; }
  .led-topbar { padding: 16px 16px 0; }
  .led-tabs { padding: 10px 16px 0; }
  .led-main { padding: 18px 16px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--copilot-peek) + 16px); }
  .led-joins ul { columns: 1; }
}

/* ══ Inbox tab — the reliever (docs/LEDGER.md → the Inbox pivot). Operator cards, one per inbound email. ══ */
#tab-inbox.inbox, #tab-drafts.drafts, #tab-sent.sent { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row;
  --inb-warn: #9a6c14; --inb-pos: #1c8f52; --inb-danger: #c0392b; }
[data-theme="dark"] #tab-inbox.inbox, [data-theme="dark"] #tab-drafts.drafts, [data-theme="dark"] #tab-sent.sent { --inb-warn: #e3b341; --inb-pos: #4ade80; --inb-danger: #f87171; }
/* position:relative so the pull-to-refresh indicator (.pullr, injected as this column's first child) can
   pin itself to the top of THIS scroller rather than to the page. */
.inb-content { position: relative; flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; }
/* The label + archive picker (docs/FOLDERS.md Slice 2) — the "send & label" model. The chip sits beside Send in
   the composer foot and shows the target label (pre-filled with Dustav's suggestion); the popover searches her
   real Gmail labels, creates one from free text, or clears it. Send then files the inbound into it + archives. */
.ck-label { position: relative; margin-left: auto; }
.ck-label + .ck-btn-send { margin-left: 0; } /* the chip already claimed the free space — Send hugs it, not the far edge */
/* the folder chip carries a NAME + a caret, so it justifies apart rather than centring like a verb does.
   (The emoji SURVIVOR IS DEAD — 2026-08-28, Curtis: "use the folder glyph everywhere and replace the
   folder emojis". Chip + every picker row now carry the drawn folder-open glyph — FOLDER_SVG in
   folder-picker.js, lib/icons' path copied with a change-together note — converted as one component,
   exactly the whole-picker pass the old comment here asked for. The glyph inherits each host's colour,
   which is the thing the emoji never could.) */
/* Amber, not accent (2026-08-28 — "inside the inbox message these need to be yellow"): the chip IS a
   folder act, and the folder act's colour is Move's --inb-move everywhere else on the surface.
   ⚠️ `.ck-label .ck-label__pick`, (0,2,0) ON PURPOSE: the button PRIMITIVE registry below declares
   .ck-label__pick and paints color: var(--muted) LATER in source, so a bare (0,1,0) tint here is dead
   on arrival — the original accent tint was exactly that, silently shadowed since the primitive
   landed (is-none always worked because it was already (0,2,0)). */
.ck-label .ck-label__pick { justify-content: space-between; max-width: 240px; text-transform: none; letter-spacing: 0; color: var(--inb-move, #d9a514); background: color-mix(in srgb, var(--inb-move, #d9a514) 10%, transparent); border-color: color-mix(in srgb, var(--inb-move, #d9a514) 30%, transparent); }
.ck-label .ck-label__pick:hover { background: color-mix(in srgb, var(--inb-move, #d9a514) 18%, transparent); }
.ck-label.is-none .ck-label__pick { color: var(--muted); background: var(--bg); border-color: var(--border); }
.ck-label__chip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ck-label__caret { flex: none; font-size: 9px; opacity: .8; }
.ck-label__pop { position: absolute; bottom: calc(100% + 8px); right: 0; width: 280px; max-width: 78vw; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 12px 34px rgba(0,0,0,.22); padding: 8px; z-index: 40; }
.ck-label__search { width: 100%; box-sizing: border-box; font: inherit; font-size: 12.5px; color: var(--ink); background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; margin-bottom: 6px; }
.ck-label__search:focus { outline: none; border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
.ck-label__list { list-style: none; margin: 0; padding: 0; max-height: 260px; overflow-y: auto; }
.ck-label__opt { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--ink); padding: 8px 10px; border-radius: 8px; cursor: pointer; }
.ck-label__opt[hidden] { display: none; }
.ck-label__opt:hover { background: var(--bg); }
.ck-label__opt.is-sel { color: var(--accent); font-weight: 600; }
.ck-label__opt.is-sel::after { content: "✓"; margin-left: auto; }
.ck-label__opt--none { color: var(--muted); border-bottom: 1px solid var(--border); border-radius: 0; margin-bottom: 4px; padding-bottom: 10px; }
.ck-label__opt--none.is-sel::after { content: none; }
.ck-label__opt--create { color: var(--accent); font-weight: 600; }
/* The standing create TEACH (2026-08-28 — "it should be clear they can simply create a new label"):
   STICKY at the scroll's bottom edge, or thirty folders would bury the one row that advertises the
   capability; it hides the moment typing starts (the real ＋ Create row takes over). Wears --create's
   look by copy, NOT by class — filter() adopts whatever carries --create as the dynamic create row. */
.ck-label__opt--newhint { position: sticky; bottom: 0; background: var(--surface); border-top: 1px solid var(--border); border-radius: 0 0 8px 8px; margin-top: 4px; color: var(--accent); font-weight: 600; }
.ck-label__optic { flex: none; opacity: .85; display: inline-flex; }
/* The drawn folder glyph's size in the picker (row rows + the chip) — the .icon class carries no
   global size; each host declares its own (the app-wide pattern). */
.ck-label__optic .icon { width: 14px; height: 14px; }
.ck-label__chip .icon { width: 14px; height: 14px; vertical-align: -2px; }

/* ══ THE BUTTON PRIMITIVE (2026-08-23, Curtis: "we have used different buttons for different things … some
      buttons have round border, some are square, some are different heights, widths … I want to standardize").
      ONE box spec for every control on the Inbox surface. The anchor is the look this surface already had in
      two places — .ck-tri and .fp-modal__confirm: MONO · 11px · UPPERCASE · .05em · 8px radius. So this is
      convergence onto an existing house pill, not a new invention.

      ⚠️ WHY A GROUPED SELECTOR AND NOT A `.btn` CLASS IN MARKUP: these buttons are built in four places
      (hbs partials · inboxView.js template strings · cockpit-modal.js innerHTML · verify:inbox-view's class
      assertions), so adding a class is a four-file change with a verify re-point for zero visual gain. The
      selector list IS the registry — a new button joins it HERE, and grep finds every member.

      ⭐ HEIGHT COMES FROM A TOKEN, NEVER FROM PADDING. Four controls used to share one bar row and derive
      their height four different ways (.back-btn 6px + a 13px icon · .ckm__prev 5px, no icon, no line-height
      · .ck-tri 6px · .ckm__x a fixed 32) — which is precisely the mismatch Curtis circled between "← INBOX"
      and "Next ›". An explicit height + line-height:1 means an ICON CAN NO LONGER CHANGE A BUTTON'S HEIGHT,
      and the phone is ONE token swap instead of the four independent overrides it used to take.

      ⭐ SHAPE CARRIES MEANING: a PILL is a mode you are IN (filter tabs, drawer tabs, the reply-mode switch,
      the 🧠 toggle); a RECT is an act you DO (every verb, every piece of chrome). Tested against all six
      controls that moved — no exceptions, which is the test of whether a grammar is real rather than
      invented. Don't reach for a pill to add emphasis; that's what fill and size are for. */
:root { --btn-h-sm: 26px; --btn-h-md: 30px; --btn-h-lg: 36px; }

.back-btn, .ck-tri,
.ckm__prev, .ckm__next, .ckm__x, .ck-drawer__x,
.inb-verb, .inb-bulkbar__btn, .inb-kebab, .inb-dismiss, .inb-found__btn, .rpk__x, .rpk__pg,
.ck-panel-btn, .ck-tb, .ck-label__pick,
.ck-btn-ghost, .ck-btn-send, .ck-draftit, .ck-need__go, .ck-note__save,
.ck-cal-review, .ck-cal-undo, .ck-superseded__undo, .ck-passed__read, .ck-undo, .ck-eml__show, .ck-torder,
.fp-modal__cancel, .fp-modal__confirm,
.ffc__btn,
.inb-filter, .ck-mode {
  flex: none;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: var(--btn-h-md);
  padding: 0 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .05em;
  line-height: 1;
  white-space: nowrap;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  text-decoration: none;
  cursor: pointer;
  transition: color .12s, border-color .12s, background .12s;
}
/* Icons ride at one size everywhere, and inherit the button's colour so a variant tints its glyph too — the
   thing an emoji structurally cannot do (see the drawn icon set below). */
.back-btn svg, .back-btn .icon, .ck-tri svg, .ckm__prev svg, .ckm__next svg,
.inb-verb svg, .inb-bulkbar__btn svg, .ck-panel-btn svg, .inb-found__btn svg { flex: none; width: 13px; height: 13px; display: block; }

/* SIZES. sm = icon-only chrome · md = the default · lg = the composer's footer actions, where Send has to be
   the biggest thing on the screen. */
.ck-tb { height: var(--btn-h-sm); }
.ck-btn-send, .ck-note__save, .fp-modal__cancel, .fp-modal__confirm, .ffc__btn { height: var(--btn-h-lg); padding: 0 16px; }
.inb-dismiss { height: var(--btn-h-sm); }
/* SQUARE — an icon-only button is as wide as it is tall, so it can't drift wider than its neighbours. */
.ckm__x, .ck-drawer__x, .ck-tb, .inb-kebab, .inb-found__btn, .rpk__x, .rpk__pg { width: var(--btn-h-md); padding: 0; }
.ck-tb { width: var(--btn-h-sm); }
/* BARE — the composer's formatting glyphs: the box is there for the target, not for the look. */
.ck-tb { border-color: transparent; background: none; font-size: 13px; }
.ck-tb:hover { color: var(--ink); background: var(--surface); border-color: var(--border); }
/* PILL — a mode you're in. (See the shape grammar above; the only difference from a rect is the radius.) */
.inb-filter, .ck-mode, .ck-panel-btn { border-radius: 999px; }
/* ⭐ THE PHONE IS ONE LINE. Every control grows to a thumb-sized target together, so nothing in a row can go
   ragged — which is what a dozen independent per-button padding overrides used to produce (Curtis: "on mobile
   different widths etc."). Anything that needs to shed its WORD at this width still does so locally; what it
   must not do is redefine its own height. */
@media (max-width: 879px) { :root { --btn-h-sm: 30px; --btn-h-md: 34px; --btn-h-lg: 40px; } }

/* The ✓ Done modal (docs/QUEUE_RESOLUTION.md) — the card's confirmable clear; reuses the .ck-label folder picker. */
.fp-modal { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,.45); padding: 20px; } /* .45 = the ONE wash alpha (modal audit 2026-08-27) */
.fp-modal__box { width: 400px; max-width: 92vw; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,.3); padding: 20px 22px; }
.fp-modal__head { font-family: var(--font-display); font-size: 16px; font-weight: 700; margin-bottom: 14px; }
/* the item being confirmed (the TODO Done modal shows the to-do title here) */
.fp-modal__pick { margin-bottom: 12px; }
.fp-modal__sub { font-size: 12.5px; color: var(--muted); margin-bottom: 18px; line-height: 1.5; }
.fp-modal__foot { display: flex; justify-content: flex-end; gap: 10px; }
/* box from the button primitive; these keep only their fill (quiet / accent / danger). */
.fp-modal__cancel:hover { color: var(--ink); border-color: var(--muted); }
.fp-modal__confirm { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); font-weight: 700; }
.fp-modal__confirm:hover { filter: brightness(1.05); }
.fp-modal__confirm:disabled { opacity: .6; cursor: default; }
.fp-modal__confirm.is-danger { background: var(--error, #c0392b); border-color: var(--error, #c0392b); color: #fff; }
/* ── BULK confirm modals (bulk-modal.js) — rides the .fp-modal chrome. The Move chooser is three
   radio-style option rows (dot + label + sub); the folder option reveals the shared FolderPicker. ── */
.blk-opts { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.blk-opt { display: flex; align-items: flex-start; gap: 10px; text-align: left; font: inherit; color: var(--ink); background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; cursor: pointer; transition: border-color .12s, background .12s; }
.blk-opt:hover:not(:disabled) { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.blk-opt.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--bg)); }
.blk-opt:disabled { opacity: .5; cursor: default; }
.blk-opt__dot { flex: none; width: 14px; height: 14px; margin-top: 2px; border: 1.5px solid var(--border); border-radius: 50%; background: var(--surface); }
.blk-opt.is-on .blk-opt__dot { border-color: var(--accent); background: radial-gradient(circle, var(--accent) 0 4px, transparent 5px); }
.blk-opt__txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.blk-opt__label { font-size: 13.5px; font-weight: 600; }
.blk-opt__sub { font-size: 12px; color: var(--muted); line-height: 1.4; }
.blk-pick { margin: 2px 0 6px; }
/* Body-appended (the .fp-modal overlay) → OUTSIDE #tab-inbox, so the tab-scoped --inb-warn never cascades
   here; redefine it locally, both themes (the .inb-menu precedent — the documented scoped-var trap). */
.blk-warn { --inb-warn: #9a6c14; font-size: 12.5px; line-height: 1.5; color: var(--inb-warn); background: color-mix(in srgb, var(--inb-warn) 9%, transparent); border: 1px solid color-mix(in srgb, var(--inb-warn) 30%, transparent); border-radius: 8px; padding: 8px 10px; margin-bottom: 14px; }
[data-theme="dark"] .blk-warn { --inb-warn: #e3b341; }
/* the picker fills the modal + its popover opens DOWNWARD (it sits near the top, not the composer's bottom edge). */
/* Review-with-Dustav modal (review-modal.js — Bre, 2026-08-18: give context BEFORE any turn fires). Rides the
   .fp-modal chrome; only the review-specific bits live here. */
.rvm-box { width: 460px; }
.rvm-q { font-size: 13.5px; line-height: 1.5; color: var(--ink); background: color-mix(in srgb, var(--accent) 6%, var(--surface)); border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border)); border-left: 3px solid var(--accent); border-radius: 10px; padding: 10px 12px; margin-bottom: 12px; }
.rvm-input { width: 100%; box-sizing: border-box; resize: vertical; min-height: 68px; font: inherit; font-size: 13.5px; line-height: 1.5; color: var(--ink); background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; }
.rvm-input:focus { outline: none; border-color: var(--accent); }
.rvm-dig { color: var(--ink); }

.fp-modal__pick .ck-label { margin-left: 0; display: block; }
.fp-modal__pick .ck-label__pick { width: 100%; box-sizing: border-box; justify-content: space-between; }
.fp-modal__pick .ck-label__pop { bottom: auto; top: calc(100% + 8px); left: 0; right: 0; width: auto; }

/* THE COCKPIT MODAL (docs/INBOX_COCKPIT.md §2) — the whole thread cockpit over the list, so a message reads
   as a final unit: in, act, out. Nearly full-viewport (it holds the conversation AND the composer, unlike the
   preview's single frame), and the mounted cockpit keeps its own .cockpit layout untouched — .is-modal only
   trims the page chrome that a modal supplies itself. */
/* ⭐ TRUE FULL SCREEN (Curtis, 2026-08-23: "i've made up my mind, no 95% modal. just full screen").
   This supersedes both earlier calls — the 1360×94vh card AND the 24px ring that replaced it.
   ⚠️ The honest note, since I argued the other way twice: the inset was carrying a real job. The ring of scrim
   was the ONLY thing saying "this is a LAYER over your list, not a page you navigated to" — the exact
   confusion these slices exist to remove. Going full-bleed spends that signal for the screen space. What buys
   it back is the "← Inbox" link now pinned top-left in the bar (cockpit-modal.js hoistChrome): an explicit,
   labelled way out is a better answer to "where am I" than a 24px stripe of dimmed list was — it just costs a
   control instead of costing nothing. The two changes are one decision and belong together; don't restore the
   inset without also asking what the back link is then for. */
.ckm { padding: 0; }
.ckm .ckm__box { width: 100%; height: 100%; max-width: none; background: var(--surface); border: 0; border-radius: 0; box-shadow: none; display: flex; flex-direction: column; overflow: hidden; }
/* The bar carries the whole modal's chrome: the WALK on the left, this email's subject in the middle, and the
   triage verbs + ✕ on the right (the verbs are hoisted up out of the cockpit head — cockpit-modal.js
   hoistChrome). Three zones, so nothing here moves as a row changes state. */
.ckm__bar { flex: none; display: flex; align-items: center; gap: 14px; padding: 9px 14px; border-bottom: 1px solid var(--border); background: var(--bg); }
/* The way out, top-left, behind a divider — the rule separates "leave this surface" from "move around inside
   it", which is the one distinction the walk buttons beside it must not blur. :empty guard so a cockpit that
   renders no back link (or a failed hoist) leaves a dangling rule rather than the bar looking broken. */
.ckm__back { flex: none; display: inline-flex; align-items: center; padding-right: 14px; border-right: 1px solid var(--border); }
.ckm__back:empty { display: none; }
.ckm__nav { flex: none; display: flex; align-items: center; gap: 8px; }
.ckm__subject { flex: 1 1 auto; min-width: 0; text-transform: none; letter-spacing: 0; font-family: var(--font-display); font-size: 13px; font-weight: 400; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ckm__subject b { font-weight: 700; }
.ckm__meta { font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .06em; font-size: 10px; color: var(--muted); margin-left: 10px; }
/* ⚠️ margin-left:auto, NOT "the subject's flex-grow pushes them over" — the subject is display:none on a phone,
   so relying on it as the spacer collapsed Move/Delete/✕ back against the pager the moment the bar got narrow.
   Third instance of the same fault in this rework (see .ck-foot, .ck-pager): a control's POSITION must not
   depend on a sibling that isn't always there. Pinning it to the edge itself is the version with nothing to
   depend on. */
.ckm__acts { flex: none; margin-left: auto; display: flex; align-items: center; gap: 10px; }
.ckm__triage:empty { display: none; }
.ckm__prev, .ckm__next { color: var(--ink); }
.ckm__prev:hover:not(:disabled), .ckm__next:hover:not(:disabled) { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); color: var(--accent); }
.ckm__prev:disabled, .ckm__next:disabled { opacity: .4; cursor: default; }
/* The ✕ wears the same box as everything else in the bar (Curtis, 2026-08-23: "add a box around the x so it is
   easier to target on both mobile and desktop"). It was a borderless 28px glyph — the smallest and least
   obvious target in the bar, which is the wrong ranking for the control that leaves the surface. A real
   bordered 32px button reads as a control and is a thumb-sized target on a phone. */
.ckm__x { font-size: 13px; }
.ckm__x:hover { border-color: color-mix(in srgb, var(--ink) 30%, var(--border)); color: var(--ink); }
.ckm__body { flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; }
.ckm__spin { padding: 16px; }
/* The mounted cockpit fills the box. The page version is a full-height tab shell; in here it's a flex child,
   and its own two-column grid + internal scrollers do the rest unchanged. */
.ckm__body .cockpit { flex: 1 1 auto; min-width: 0; height: 100%; }
/* The whole cockpit head is hidden in here: its subject/meta are copied into the bar, its triage group and its
   "← Inbox" link are physically MOVED there (hoistChrome), and what's left is an empty strip stealing height
   from the email. The back link is styled by the shared `.back-btn` pill once it lands in `.ckm__back`. */
.ckm__body .cockpit .ck-head { display: none; }
@media (max-width: 879px) {
  /* The page shell reserves room for the app's FIXED top bar (padding-top: var(--mtop-h)); inside a modal
     there is no top bar, so that inset is just a dead band under our own bar. */
  .ckm__body .cockpit { padding-top: 0; }
  /* (The edge-to-edge box overrides that used to live here are gone: the desktop rule is full-screen now, so
     the phone was just restating it. `100dvh` stays — it's the one thing a phone genuinely needs, since the
     viewport unit has to survive the browser's collapsing address bar.) */
  .ckm .ckm__box { height: 100dvh; }
  /* The subject is the shrinkable half of the bar: at 390px the back link, the walk, MOVE, DELETE and ✕ leave
     nothing for it, and the sender is named on the message header directly below anyway. */
  .ckm__subject { display: none; }
  /* ⭐ EVERY BAR CONTROL GOES ICON-ONLY on a phone (Curtis, 2026-08-23). Six labelled pills don't fit at 390px,
     and the failure mode of trying is the worst of both: each control shrinks toward its text until none of
     them is a comfortable target AND the words are too small to read. Dropping the words instead keeps every
     control at a real tap size. aria-label/title carry the verb throughout, and these glyphs are the
     unambiguous ones — an arrow, a folder, a trash can. */
  /* ⭐ ONE RULE, not five. Every bar control drops its word and becomes the SAME SQUARE — so the row can't go
     ragged, which is what the four independent padding overrides that used to live here kept producing. */
  .ckm__back { padding-right: 7px; }
  .ckm__back .back-btn span, .ckm__navlabel, .ck-tri__label { display: none; }
  /* ⚠️ min-width, NOT width: the triage busy state ("Moving…") replaces the button's whole innerHTML, and a
     hard width would clip it to three letters instead of letting it widen for the moment it's in flight. */
  .ckm__back .back-btn, .ckm__prev, .ckm__next, .ckm__triage .ck-tri { min-width: var(--btn-h-md); padding: 0 8px; }
  /* nowrap + a shrinkable nav: at 390px the walk + MOVE + DELETE + ✕ sit right on the wrap boundary, and a bar
     that wraps to two lines eats a line of the email on every phone. The prev/next labels shorten instead. */
  .ckm__bar { flex-wrap: nowrap; gap: 6px; padding: 7px 8px; }
  /* overflow:hidden is the important half — the nav is the shrinkable child, and without it the position text
     doesn't clip, it OVERFLOWS: "2 OF 4" runs underneath the MOVE pill and reads as a rendering fault rather
     than as a bar that ran out of room. */
  .ckm__nav { flex: 0 1 auto; min-width: 0; gap: 4px; overflow: hidden; }
  .ckm__pos { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .ckm__acts { gap: 6px; }
}
html.ckm-open, html.ckm-open body { overflow: hidden; } /* freeze the list behind — it keeps its scroll */

/* The row "📁 Archive" triage affordance (docs/FOLDERS.md Slice 2) — mirrors .inb-dismiss, accent-tinted. */
.inb-archive { display: inline-flex; align-items: center; gap: 3px; font-family: var(--font-mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); border-radius: 20px; padding: 2px 8px; cursor: pointer; white-space: nowrap; }
.inb-archive:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
.inb-ml { font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .06em; font-size: 10.5px; color: var(--muted); }
.inb-topbar { position: relative; padding: 20px 30px 0; display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.inb-brand { flex: 1 1 auto; min-width: 0; }
.inb-brand h1 { font-family: var(--font-display); font-size: 20px; font-weight: 800; letter-spacing: -.02em; margin: 0; }
/* (.inb-brand-sub is GONE — 2026-08-29, Curtis: "remove the little tagline beside the header". Every tab's
   h1 is just its name now; the meta line below carries the numbers.) */
.inb-brand-meta { margin-top: 4px; text-transform: none; letter-spacing: .02em; font-size: 11px; }
.inb-brand-meta b { color: var(--accent); }
/* (The 🧠 activity toggle + its floating panel + the idle trail — .inb-brain* / .inb-think* / .inb-trail* —
   are RIPPED OUT, 2026-08-24: "what Dustav did" lives in the thread cockpit's trace, and the live rows
   stream straight into the list.) */
/* sync bar (shared shape with the old ledger bar) */
.inb-syncbar { flex: none; display: flex; align-items: center; gap: 8px; }
.inb-sync-max { width: 64px; font-family: var(--font-mono); font-size: 12px; color: var(--ink); background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 7px 9px; text-align: right; }
.inb-sync-max::placeholder { color: var(--muted); }
.inb-sync-btn { font-family: var(--font-display); font-weight: 600; font-size: 13px; color: var(--accent-ink); background: var(--accent); border: 1px solid var(--accent); border-radius: 9px; padding: 8px 16px; cursor: pointer; margin-left: 4px; }
.inb-sync-btn:hover { filter: brightness(1.06); } .inb-sync-btn:disabled { opacity: .6; cursor: default; }
.inb-sync-status { display: flex; align-items: center; gap: 10px; max-width: 460px; }
.inb-sync-spin { width: 14px; height: 14px; flex: none; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--accent) 30%, transparent); border-top-color: var(--accent); animation: inbSpin .7s linear infinite; }
@keyframes inbSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .inb-sync-spin { animation: none; } }
.inb-sync-count { text-transform: none; letter-spacing: .02em; font-size: 12px; color: var(--ink); font-variant-numeric: tabular-nums; }
.inb-sync-sub { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 260px; }
.inb-sync-stop { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); background: none; border: 1px solid var(--border); border-radius: 6px; padding: 3px 8px; cursor: pointer; }
.inb-sync-status.is-error .inb-sync-spin, .inb-sync-status.is-error .inb-sync-stop { display: none; }
.inb-sync-status.is-error .inb-sync-count, .inb-sync-status.is-error .inb-sync-sub { color: var(--error); white-space: normal; max-width: 520px; }
/* filters + tools (search / sort) — one row, tools pushed right */
.inb-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; padding: 16px 30px 0; }
/* PILL (a mode you're in) — box from the button primitive; this keeps only the selected fill. */
.inb-filter:hover { color: var(--ink); }
.inb-filter.is-active { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.inb-filter.is-dim { opacity: .4; }        /* a search is active → the section filter is bypassed server-side */
.inb-search { display: inline-flex; align-items: center; gap: 6px; background: var(--surface); border: 1px solid var(--border); border-radius: 20px; padding: 0 11px; height: 30px; color: var(--muted); }
.inb-search:focus-within { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); color: var(--ink); }
.inb-search__ic { flex: none; }
.inb-search input { border: 0; background: none; outline: none; color: var(--ink); font-size: 13px; width: 150px; padding: 0; }
.inb-search input::placeholder { color: var(--muted); }
.inb-search input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
/* (The ADMIN "start over" act retired entirely 2026-08-30 — its .acct-menu__item--admin dress
   deleted with its last consumer.) */

/* ── THE FULL-WIDTH SEARCH BAR (Curtis, 2026-08-23: "full width, same dimensions as the bulk tool bar,
   tabs above it") — its own row below the filter tabs, wearing the bulk bar's EXACT geometry (margin
   10px 30px 0 · padding 8px 16px · radius 12 · same border/surface) so the two bars read as one system
   and the columns stay stable while scrolling. The pill-shaped compact .inb-search survives for the
   old Outbox topbar; the --wide modifier is the mail tabs'. */
/* ── CHECK NOW — the ↻ beside the search, and the phone's pull-to-refresh (public/js/dashboard/sync-now.js).
      A quiet instrument, not a CTA: it asks the server to do NOW what its timer would do in ~20s, so it
      wears the same square as the card kebabs and spins only while the request is out. The RUN itself is
      the pill's story — this control never grows a second progress state. */
.inb-refresh {
  flex: none; display: grid; place-items: center; width: 46px; height: var(--btn-h-md);
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface); color: var(--muted);
  cursor: pointer; padding: 0;
}
.inb-refresh:hover { color: var(--ink); border-color: color-mix(in srgb, var(--ink) 25%, var(--border)); }
/* The 🔍 toggle while its row is open — a MODE the button is in, so it holds the accent (the ↻ beside it
   never carries state and stays quiet). */
.inb-refresh.is-on { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); }
.inb-refresh .icon { width: 15px; height: 15px; }
.inb-refresh.is-spinning .icon { animation: railSpin .8s linear infinite; }
@media (prefers-reduced-motion: reduce) { .inb-refresh.is-spinning .icon { animation: none; } }
/* The FOLDERS toggle (the folder book's door) — Move's amber, because it IS the folder act's colour
   everywhere else on the surface (Curtis, 2026-08-28: "same color as the move buttons on the email
   cards"), with a visible LABEL on desktop ("so it's a visible toggle") that collapses to the square
   on phones (the .inb-compose pattern; title/aria keep the words). is-on deepens the same amber —
   never the accent: the accent is the search toggle's mode colour, and two different open-modes
   sharing it would read as one. */
.inb-folderbtn { width: auto; padding: 0 12px; display: inline-flex; align-items: center; gap: 7px; color: var(--inb-move, #d9a514); font-family: var(--font-display); font-weight: 700; font-size: 12.5px; white-space: nowrap; }
.inb-folderbtn:hover { color: var(--inb-move, #d9a514); border-color: color-mix(in srgb, var(--inb-move, #d9a514) 55%, var(--border)); background: color-mix(in srgb, var(--inb-move, #d9a514) 10%, transparent); }
.inb-folderbtn.is-on { color: var(--inb-move, #d9a514); border-color: color-mix(in srgb, var(--inb-move, #d9a514) 70%, var(--border)); background: color-mix(in srgb, var(--inb-move, #d9a514) 14%, transparent); }
@media (max-width: 879px) {
  .inb-folderbtn { width: 46px; padding: 0; justify-content: center; }
  .inb-folderbtn__label { display: none; }
}
/* NEW EMAIL (docs/DRAFTS_SENT.md §4) — the one LABELED rect in the tools cluster: composing is the mail
   surfaces' primary ACT (rect = an act you do), the squares beside it are utilities. Accent-flavored at
   rest — it's the act the tab exists for — same height token as its neighbours. The label collapses to
   the icon square on the phone (the inb-verb pattern), where the title/aria carries the words. */
.inb-compose {
  flex: none; display: inline-flex; align-items: center; gap: 7px; height: var(--btn-h-md); padding: 0 13px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 12px;
  background: var(--surface); color: var(--accent); cursor: pointer;
  font-family: var(--font-display); font-weight: 700; font-size: 12.5px; white-space: nowrap;
}
.inb-compose:hover:not(:disabled) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
.inb-compose:disabled { opacity: .55; cursor: default; }
.inb-compose .icon { width: 15px; height: 15px; }
@media (max-width: 879px) {
  .inb-compose { width: 46px; padding: 0; justify-content: center; }
  .inb-compose__label { display: none; }
}

/* The pull indicator lives INSIDE the scroll container, pinned above the first row and revealed by the
   pull itself. It reads as a ring that fills toward the threshold, then spins while the request is out.
   JS sets TWO custom properties via CSSOM (the CSP forbids style attributes in markup — memory:
   render-harness-csp): --pull is the LENGTH the finger has travelled, --pull-n is that same distance as a
   UNITLESS fraction of the threshold. Both exist because calc() is strictly typed, and the first cut got
   it wrong in two places: `opacity: calc(var(--pull) / 64)` computes to a LENGTH (invalid for opacity),
   and `rotate(calc(var(--pull) * 3deg))` multiplies a length by an angle (invalid full stop). An
   invalid-at-computed-value-time declaration falls back to the property's INITIAL value — opacity 1,
   transform none — so the ring rendered fully opaque with its hide-offset dropped: permanently on screen,
   on every touch device, with nobody pulling (Curtis's phone, 2026-08-27). It read as desktop-clean only
   because the module builds the indicator behind an `ontouchstart` gate. Lengths do lengths here; the
   unitless twin does everything else.
   The offset rides the WRAPPER and the rotation rides the SPIN deliberately: .is-working animates
   `transform` on the spin element, and one shared transform would drop the offset the moment it started. */
.pullr {
  --pull: 0px; --pull-n: 0;
  position: absolute; top: 0; left: 0; right: 0; height: 0; display: grid; place-items: center;
  pointer-events: none; z-index: 2;
  transform: translateY(calc(var(--pull) - 26px));
}
.pullr__spin {
  display: block; box-sizing: border-box; width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  transform: rotate(calc(var(--pull-n) * 180deg));
  opacity: min(var(--pull-n), 1); transition: opacity .12s;
}
.pullr.is-ready .pullr__spin { border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); border-top-color: var(--accent); }
.pullr.is-working .pullr__spin { animation: railSpin .8s linear infinite; opacity: 1; }
@media (prefers-reduced-motion: reduce) { .pullr.is-working .pullr__spin { animation: none; } }

.inb-searchbar { margin: 10px 30px 0; display: flex; align-items: center; gap: 8px; }
/* Collapsed by default behind the topbar 🔍 (2026-08-27) — the explicit rule is required because the
   class's display:flex above outranks the UA's [hidden]{display:none}. */
.inb-searchbar[hidden] { display: none; }
/* The topbar tools — 🔍 + ↻ top-right across from the heading (Curtis, 2026-08-27). flex-start keeps
   them on the title's line while the brand block wraps its meta below. */
.inb-topbar__tools { display: flex; gap: 8px; flex: none; }
.inb-searchbar .inb-search--wide { display: flex; width: 100%; height: auto; padding: 8px 16px; border-radius: 12px; gap: 10px; }
.inb-searchbar .inb-search--wide input { width: 100%; flex: 1 1 auto; height: 30px; font-size: 14px; }
@media (max-width: 640px) { .inb-searchbar { margin: 10px 14px 0; } }
/* The injected clear ✕ (queue.js) — visible only while a query exists; replaces the unreliable native one. */
.inb-search__clear { flex: none; border: 0; background: none; color: var(--muted); font-size: 12px; line-height: 1; padding: 2px 0 2px 2px; cursor: pointer; }
.inb-search__clear:hover, .inb-search__clear:focus-visible { color: var(--ink); outline: none; }
/* (The MAILBOX HALF of search — the mirror-section divider + "hasn't read this" row state — is GONE,
   2026-08-28: the Inbox's search + folder surfaces show only what Dustav has read. services/inboxView
   carries the reasoning; verify:inbox-view §6 pins the absence, including of the class names.) */
/* (.inb-sort is GONE — both mailbox lists are always newest-first; the sort dropdowns died 2026-08-19.) */
/* the search results count (only while searching) — and the FOLDER CHIP's row while a folder is open */
.inb-count { padding: 8px 30px 0; font-family: var(--font-mono); font-size: 11px; color: var(--muted); letter-spacing: .02em; }
/* The folder chip (inbox.hbs + queue.js renderFoot — the folder browse's one visible state + its exit).
   Restyled 2026-08-28 (Curtis: "give this filter a clear style — right now it is hard to see"): a real
   AMBER pill — the folder act's colour everywhere on this surface (Move, the Folders toggle) — wearing
   the folder glyph + the name bold in the display voice; the read-&-filed denominator keeps the count
   row's muted mono; ✕ leaves. */
.inb-folderchip { display: inline-flex; align-items: center; gap: 8px; max-width: 100%; padding: 6px 8px 6px 13px; border-radius: 999px; background: color-mix(in srgb, var(--inb-move, #d9a514) 12%, var(--surface)); border: 1px solid color-mix(in srgb, var(--inb-move, #d9a514) 50%, var(--border)); }
.inb-folderchip > svg { width: 14px; height: 14px; flex: none; color: var(--inb-move, #d9a514); }
.inb-folderchip b { font-family: var(--font-display); font-size: 12.5px; color: var(--ink); font-weight: 750; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inb-folderchip__meta { color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inb-folderchip__x { flex: none; border: 0; background: none; color: var(--muted); font-size: 13px; line-height: 1; padding: 3px 6px; border-radius: 6px; cursor: pointer; }
.inb-folderchip__x:hover, .inb-folderchip__x:focus-visible { color: var(--ink); background: color-mix(in srgb, var(--inb-move, #d9a514) 16%, transparent); outline: none; }
/* The SCOPE chip row (todo/intelligence/drafts/files — "what this email produced", the ?email= landing)
   wears the BARE folder chip — AMBER on purpose (Curtis, 2026-08-30: "I would rather have the color of the
   filter chip amber so it doesn't collide with the tab colors"; an accent variant lasted one render — the
   tab strips own those hues, and amber is the app's established "you're in a filtered place" color). ONE
   class across the surfaces so the grammar can't drift. */
.scope-row { padding-top: 10px; }
/* Files hosts the pill inside .file-content, which already carries the 30px page gutter — the .inb-count
   side padding it inherits would double-indent it past the kind chips (Curtis's screenshot, 2026-08-30:
   "fix the css — matching the padding margin for the chip as per the todo page"). Align to the chips'
   left edge; the chips' own 16px bottom margin gives the gap above, 16px below matches their rhythm. */
.file-content .scope-row { padding: 0 0 16px; }
/* the anti-jump "new activity" pill — a live sync never reshuffles the list; this invites a manual refresh */
.inb-newpill { display: block; margin: 12px auto 0; font-family: var(--font-display); font-weight: 700; font-size: 12.5px; color: var(--accent-ink); background: var(--accent); border: 0; border-radius: 20px; padding: 6px 15px; cursor: pointer; box-shadow: 0 2px 10px color-mix(in srgb, var(--accent) 35%, transparent); }
.inb-newpill__dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; margin-right: 7px; vertical-align: middle; animation: inbNewPulse 1.5s ease-in-out infinite; }
@keyframes inbNewPulse { 0%, 100% { opacity: .4; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .inb-newpill__dot { animation: none; } }
/* infinite-scroll sentinel + honest foot count (the foot is now the last element, so it carries the scroll-end
   breathing room the old .inb-list:last-child padding gave — a big bottom pad on mobile to clear the copilot peek) */
.inb-sentinel { height: 1px; }
.inb-foot { text-align: center; color: var(--muted); padding: 4px 30px 44px; min-height: 14px; }
@media (max-width: 620px) {
  /* the search keeps its own full-width row so nothing overflows a narrow phone */
  .inb-search { flex: 1 1 auto; min-width: 0; }
  .inb-search input { width: 100%; min-width: 0; }
  .inb-count, .inb-foot { padding-left: 16px; padding-right: 16px; }
  .inb-foot { padding-bottom: 96px; }
}
/* the card list */
.inb-list { padding: 18px 30px; display: flex; flex-direction: column; gap: 8px; }
/* The big bottom clearance (scroll-end breathing room, clears the mobile copilot peek) belongs ONLY to the LAST
   list — the old two-section Outbox taught this; every mail tab is single-list since the 2026-08-27 split. */
.inb-list:last-child { padding-bottom: 80px; }
/* The shared empty-state box — Inbox · Drafts · Sent · TODO · Workflows all render .inb-empty, so a blank tab reads
   the same everywhere (contained, centered, bordered card). */
.inb-empty { color: var(--muted); text-align: center; padding: 48px; background: var(--surface); border: 1px solid var(--border); border-radius: 14px; margin: 0; font-size: 14px; }

/* ── Compact cockpit queue rows (docs/INBOX_UX.md §2a) — one scannable row per CONVERSATION, linking to the
      thread screen. The draft + full detail live there, not blown out here (that was the flood). ── */
/* The mail-tab queue row (Inbox/Drafts/Sent) — a 3-ZONE slot layout (services/inboxView.rowShell): [avatar] · [body, the ONLY
   flexing zone, min-width:0] · [rail, fixed width, right-aligned]. It degrades by wrapping/clipping, never
   overlapping, from ~380px (copilot open) to full width — one structure, no per-width reflow (docs/INBOX_UX.md §2a).
   The rail RESERVES a slot for the status badge (the priority signal) so a variable-width folder label can't jostle
   it; the label pills live on their own body line, clipped, so they never reach the rail. */
/* Four named slots. WHO and BODY span both rows; the rail's two lines take the right-hand column, state over
   triage. Named areas rather than implicit placement because the PHONE re-arranges the same four boxes into a
   different shape (the verbs span the full card width there) — with one DOM and no per-element reflow. */
/* 2026-08-24 re-cut: FIVE areas — who · body · state (the pills + any WORDY status chip) · top (glyph
   status + time — the corner landmark now that the ⋯ kebab is dead) · foot (a FLEX row: the found line
   left, the verbs right). state and top are SEPARATE cells so the phone can give the chips their own
   full-width line instead of letting them truncate the sender (the corner squeeze was the whole complaint);
   on desktop they sit adjacent, visually where the chips always were. The foot is flex, not two grid areas,
   because the found buttons and the verbs must NEGOTIATE width — a grid column serving the verbs is rigid
   and re-truncated the sender on the first 390 render; flex shrinks and, when genuinely over-full, wraps
   the verbs under the found line instead of clipping a button. */
/* The foot spans ALL columns — the found line starts UNDER the avatar circle, both form factors (Curtis,
   2026-08-24, on desktop: "dustav should be left aligned under the circle"). The who column is row 1 only;
   a threaded row's 🧵 under the avatar just makes row 1 a touch taller. */
.inb-row { position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr) max-content auto; grid-template-areas: "who body state top" "foot foot foot foot"; align-items: start; gap: 8px 14px; padding: 12px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: inset 3px 0 0 transparent; text-decoration: none; color: var(--ink); }
.inb-row__state { grid-area: state; display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-width: 0; }
.inb-row:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
/* THE ACCENT RAIL = NEW, not needs-you (2026-08-31, Bre: the rail on a reply-ready row "is making me think it's
   an email I haven't read"). The rail is the loudest mark on the surface, so it belongs to the one axis she reads
   at a glance — hers. is-needs survives ONLY on the Drafts tab (services/drafts.js: every row there is one owed
   thing, no read axis); the mailbox no longer emits it. Both sit ABOVE .is-urgent so an alarm still wins the rail. */
.inb-row.is-needs, .inb-row.is-unseen { box-shadow: inset 3px 0 0 var(--accent); }
.inb-row.is-urgent { box-shadow: inset 3px 0 0 var(--inb-warn); }
/* Handled / passed — SETTLED, not disabled. A whole-row fade reads as "you can't touch this"; these are live,
   clickable rows. So it recedes via a faintly recessed surface + a softer border (and the already-muted headline
   below), while the avatar / name / time / badge stay full-strength — done, but plainly still there. */
.inb-row.is-quiet { background: color-mix(in srgb, var(--ink) 3.5%, var(--surface)); border-color: color-mix(in srgb, var(--border) 70%, var(--surface)); }
/* ENGAGED (queue.js engageQueuedRow, 2026-08-27) — a queued Send & Move's undo window lives ON the card:
   a surface wash over the row's own content (still faintly legible — it's the same email, mid-act), the
   accent ring saying "in motion", a mono countdown, one Undo. The overlay sits inside the row <a>; its JS
   swallows clicks, this just paints. On release the row leaves via the ordinary Move collapse. */
.inb-row.is-queued { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.inb-qover { position: absolute; inset: 0; z-index: 2; display: flex; align-items: center; gap: 12px; padding: 0 16px; border-radius: inherit; background: color-mix(in srgb, var(--surface) 88%, transparent); backdrop-filter: blur(1.5px); animation: ffTipIn .12s ease-out; }
.inb-qover__msg { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: var(--muted); }
.inb-qover__count { flex: none; min-width: 1.4ch; text-align: center; font-family: var(--font-mono); font-size: 16px; font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; }
.inb-qover__undo { flex: none; margin-left: auto; height: var(--btn-h-md, 30px); padding: 0 14px; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 8px; background: var(--surface); color: var(--accent); font-family: var(--font-display); font-weight: 700; font-size: 12.5px; cursor: pointer; }
.inb-qover__undo:hover:not(:disabled) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
.inb-qover__undo:disabled { opacity: .45; cursor: default; }
@media (prefers-reduced-motion: reduce) { .inb-qover { animation: none; } }
/* Quiet rows soften their resting border above, so re-assert a subtle hover border (else the base .inb-row:hover
   is overridden by that later, equal-specificity rule → no hover cue on handled/passed rows). */
.inb-row.is-quiet:hover { border-color: color-mix(in srgb, var(--accent) 32%, var(--border)); }
/* UNSEEN (part 1 of 2 — the dot + surface; the text rules live AFTER the .is-quiet line-mute below, or the
   equal-specificity mute would win the cascade): the Gmail unread model (EmailFinding.seenAt) — HER read-state,
   orthogonal to Dustav's handled/needs state. An unseen row reads NEW regardless of what Dustav already did
   with it (a new "handled" email is bold like any other untouched email, never pre-greyed: Curtis). The recede
   applies only once SEEN (opening the thread stamps seenAt — emailFindings.markSeen). Since 2026-08-31 EVERY
   read row is is-quiet (needs-you rows included — Bre), so this override is what keeps new mail on the plain
   surface; it is no longer the handled-row special case it was written as. */
.inb-row__dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); align-self: center; }
.inb-row.is-unseen.is-quiet { background: var(--surface); border-color: var(--border); }
/* The WHO column = just the avatar (kept a wrapper so the mobile grid can area-place it). The meta (thread · time)
   sits top-right of the rail, left of the status badge (Curtis's call) — so the card stays TWO lines. */
/* A column, because the 🧵 thread count now sits UNDER the avatar (services/inboxView.rowShell): a thread count
   is a property of the CONVERSATION, and this column is that conversation's identity — the rail is about this
   email's state and what to do with it. Moving it here also freed a slot on the state line for the pills. */
.inb-row__who { grid-area: who; flex: none; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.inb-row__who > .mono { align-self: start; margin-top: 1px; }
.inb-row__body { grid-area: body; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.inb-row__top { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.inb-row__party { font-family: var(--font-display); font-weight: 700; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 1 auto; }
.inb-row__sub { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 0 1 auto; }
.inb-row__line { font-size: 13.5px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inb-row.is-quiet .inb-row__line, .inb-row.is-noise .inb-row__line { color: var(--muted); }
/* UNSEEN (part 2 of 2 — see the dot rule above): bold full-ink headline + full-strength time. Placed AFTER the
   .is-quiet line-mute so unseen wins the cascade at equal specificity — a NEW handled row is bold, not muted. */
.inb-row.is-unseen .inb-row__line { color: var(--ink); font-weight: 600; }
.inb-row.is-unseen .inb-row__time { color: var(--ink); }
/* .inb-row__labels (the pills' old wrapper on its own BODY line) is retired — the pills moved onto the rail's
   state line beside the status badge, which is where §3a's grammar always said state belongs. */
/* The two rail lines are DIRECT grid children now (the .inb-row__rail wrapper is retired) — that wrapper put
   them inside the body column, which is what indented Move and held Delete off the card's edge on a phone.
   Desktop: both stacked in the right-hand column, right-aligned; the areas below place them. */
.inb-row__railtop { grid-area: top; display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
/* THE FOOT (2026-08-24): found line left · verbs right, one flex row that wraps only when genuinely full. */
.inb-row__foot { grid-area: foot; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; min-width: 0; }
/* THE FOUND LINE — what the read produced, visible at list altitude: the ✦ summary peek + one drawn-icon
   deep link per kind of outcome (to-do/workflow/file/question/outbound — the retired kebab's contents, out
   in the open: "strictly better for scanning"). */
.inb-row__found { display: flex; align-items: center; gap: 6px; min-width: 0; }
.inb-row__meta { display: flex; align-items: center; gap: 6px; }
.inb-row__time, .inb-row__thread, .inb-row__clip { font-family: var(--font-mono); font-size: 10px; color: var(--muted); white-space: nowrap; }
/* The standardized triage actions on the rail: FILE (Gmail Move-to, decoupled) + ✓ MARK HANDLED (Dustav-only). */
/* margin-left:auto pins the verbs to the foot's RIGHT edge whatever the found line does (including when the
   found line is absent) — a Move-less row (already filed → Delete only) keeps the straight right edge that
   the fixed corners exist to give. */
.inb-row__actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-left: auto; }
.inb-file { display: inline-flex; align-items: center; gap: 6px; }
.inb-file__folder { font-family: var(--font-mono); font-size: 9px; text-transform: uppercase; letter-spacing: .05em; color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); border-radius: 20px; padding: 2px 8px; max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inb-file__btn { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); background: var(--surface); border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border)); border-radius: 7px; padding: 3px 10px; line-height: 1.4; cursor: pointer; white-space: nowrap; transition: background .12s; }
.inb-file__btn:hover, .inb-file__btn:focus-visible { background: color-mix(in srgb, var(--accent) 14%, transparent); outline: none; }
/* ✓ Done — the one CLEAR action on every row (Gmail Archive → All Mail). A 26px box, accent on hover. */
.inb-handle { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; border: 1px solid var(--border); border-radius: 7px; background: var(--surface); color: var(--muted); font-size: 13px; line-height: 1; cursor: pointer; flex: none; transition: background .12s, color .12s, border-color .12s; }
.inb-handle:hover, .inb-handle:focus-visible { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); outline: none; }
/* (The standalone "MOVE TO | 📁 X" accent pill is RETIRED — 2026-08-22. It was the last control on the row
   wearing its own shape and its own paint, which is exactly what made the verbs shuffle from row to row. It
   is now the second SEGMENT of Move: see .inb-move / .inb-verb--go below.) */
/* ── THE ROW VERBS (docs/INBOX_COCKPIT.md §3) — always visible, real-click-sized, on every row of both
   mailbox surfaces. The design tension is named and accepted: this is chrome × 60 rows against the minimal
   instinct, but it IS the one surface where the verbs are the point — a cockpit's controls being visible is
   its identity. The row BODY stays quiet (muted, unbolded) so headlines still scan first.
   The row is an <a>, so Open is a role=link span (a nested anchor would break the row); the buttons stop the
   row's own click in queue.js.

   ⭐ ONE BOX SPEC, and min-height (not vertical padding) sets it — an icon-only verb and a text verb have
   different content heights, so padding alone would make Delete a different size from Move. Fixing the box
   is what lets the rail's two lines stay put as a row changes state (see inboxView.rowShell's header). ── */
/* RECT (an act you do) — the whole box comes from the button primitive; a row verb is the same KIND of thing
   as the cockpit's Move/Delete and now literally wears the same spec. */
.inb-verb:hover, .inb-verb:focus-visible { color: var(--ink); border-color: color-mix(in srgb, var(--ink) 26%, var(--border)); outline: none; }
/* (.inb-verb--open is RETIRED — the row opens on click, so a dedicated Open button was a second control for
   the same act, and the loudest thing in the top-right corner of sixty rows. Row hover now only has one thing
   to offer, the avatar's checkbox flip; see .inb-who__box below.) */
/* DELETE is red at rest, icon + WORD on desktop, icon-only on the phone (Curtis, 2026-08-23: "on desktop I
   want the full text — garbage can and Delete; on mobile collapse to the icon"). Red stays the earlier call
   (red-on-hover made the one destructive verb indistinguishable from Move); the desktop label came back
   because there's room for it there, and the phone keeps the recognisable red can alone (.inb-verb__label
   collapses in the mobile block below). Border/fill stay neutral at rest either way. */
.inb-verb--del { color: var(--inb-danger, #c0392b); }
.inb-verb--del:hover, .inb-verb--del:focus-visible { border-color: color-mix(in srgb, var(--inb-danger, #c0392b) 55%, var(--border)); background: color-mix(in srgb, var(--inb-danger, #c0392b) 10%, transparent); }
/* MOVE wears FOLDER YELLOW (Curtis, 2026-08-23: "delete is red, move is yellow — makes sense"): the verb
   colors are the folder metaphor's own — a manila folder amber beside the destructive red. Same pattern as
   --del: colored at rest, border/fill lean in on hover. One token, used by the row verb AND the bulk bar. */
.inb-verb--move { color: var(--inb-move, #d9a514); }
.inb-verb--move:hover, .inb-verb--move:focus-visible { border-color: color-mix(in srgb, var(--inb-move, #d9a514) 55%, var(--border)); background: color-mix(in srgb, var(--inb-move, #d9a514) 10%, transparent); }
/* ⭐ MOVE AS A SPLIT BUTTON — [📁 Move][Leaf] when the reader suggested a folder. One box, two segments,
   sharing the .inb-verb spec: MOVE (wearing the folder glyph on EVERY row — Curtis 2026-08-23, "the MOVE
   button always has the folder; the suggestion is just the name") opens the picker; the name segment files
   it where Dustav said. The two halves join at a shared 1px seam (negative margin), hover lifts above it. */
.inb-move { display: inline-flex; align-items: center; flex: none; min-width: 0; }
.inb-move .inb-verb--move { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.inb-move .inb-verb--go { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -1px; }
.inb-move .inb-verb:hover, .inb-move .inb-verb:focus-visible { position: relative; z-index: 1; }
/* The folder segment leans accent on hover — it's the "take Dustav's call" half, the one that acts without a
   picker. At REST it stays the same neutral as every other verb: the row shouldn't advertise the shortcut
   louder than the choice, and the paint is no longer where the safety difference lives (that's the click). */
.inb-verb--go { max-width: 190px; text-transform: none; letter-spacing: 0; }
.inb-verb--go:hover, .inb-verb--go:focus-visible { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.inb-verb__leaf { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* THE ROW GRAMMAR (2026-08-19): pill-shaped = tappable. The "✓ Reply ready" + amber warn chips are calls to
   action — give them a pressed-feel hover so they read as the buttons they are (ready → the composer anchor;
   warn rides the row link into the cockpit). Flat text states (✓ handled/passed/reading…) stay inert. */
.inb-status--ready, .inb-status--warn, .inb-status--review { cursor: pointer; transition: filter .12s ease; }
.inb-status--ready:hover { filter: brightness(1.08); }
.inb-status--warn:hover, .inb-status--review:hover { filter: brightness(1.06); }
/* ⋯ actions MENU — the per-card overflow control. DEAD ON THE MAILBOX ROWS (2026-08-24 — the found line's
   visible buttons replaced it there); these styles survive for the todo/files/intelligence cards' kebabs. */
.inb-kebab { padding-bottom: 4px; font-size: 17px; letter-spacing: 1px; }
.inb-kebab:hover, .inb-kebab:focus-visible, .inb-kebab.is-open { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); outline: none; }
/* Disabled = the card has nothing to offer (Curtis, 2026-08-23) — visibly inert, no hover, no click
   (pointer-events kills both), but still rendered: the corner landmark never moves. */
.inb-kebab:disabled { opacity: .3; cursor: default; pointer-events: none; }
/* THE FOUND LINE's buttons (2026-08-24) — the kebab's replacement on the mailbox rows. Same square spec as
   every icon-only button (the primitive above); muted at rest so sixty rows don't shout, accent on touch. */
.inb-found__btn:hover, .inb-found__btn:focus-visible, .inb-found__btn.is-open { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); outline: none; }
/* The summary peek is A LITTLE DUSTAV (the Blink glyph) in the SENDER's monogram colour — the .ink--cN
   twins beside the mono palette paint him; accent is only the no-ink fallback. He renders a touch larger
   than the 13px icon grid so the eyes stay legible. */
.inb-found__btn--sum { color: var(--accent); }
.inb-found__btn--sum svg { width: 17px; height: 17px; overflow: visible; }
/* …and he's ALIVE (Curtis, 2026-08-24: "dustavs eyes need to be moving!!"; 2026-08-26: "the row level
   dustavs don't look like they are moving (their body) — just their eyes… wire them to the canonical
   animation css"). There is NO row-scoped animation CSS at all now: the glyph carries the mark's own
   parts (.blink__face / .blink__head / .blink__eye — lib/icons.js), so every canonical tic applies for
   free. Two rounds of local copies lived here — first the eye keyframes, restated; then an eyes-only
   subset that no CSS could have widened, because the old glyph had no face group and no head class to
   move. The only thing this host varies is PHASE, on a 4-step stagger so the column doesn't move in
   lockstep, through --blink-delay — the one variable every blink rule reads. */
.inb-row:nth-child(4n+1) .inb-found__btn--sum { --blink-delay: -2.6s; }
.inb-row:nth-child(4n+2) .inb-found__btn--sum { --blink-delay: -5.3s; }
.inb-row:nth-child(4n+3) .inb-found__btn--sum { --blink-delay: -8.1s; }

/* ── THE BLINK-PEEK MODAL (Curtis, 2026-08-24: "show the full card and the work dustav did to get there —
      a full modal with X design"): a tapped little Dustav opens the decoded card + the settled trace in a
      scrim+box shell (row-found.js fetches GET <row>/read → views/partials/read-peek.hbs). Body-appended,
      so it re-scopes the --led-* chip vars itself — a body-anchored element escapes every tab-scoped custom
      property (the KebabMenu lesson; the trace steps reuse the cockpit's .ck-trace__log styling). ── */
/* TWO FACES (2026-08-28 modal audit: desktop windows, phones fill — supersedes 2026-08-24's "full
   screen on both"). PHONE: the box IS the screen (✕ and Esc are the exits), content a readable column
   via the max() side padding collapsing to a plain gutter; bottom padding clears the home-bar inset;
   the 44px top is deliberate BREATHING ROOM (Curtis: "it needs some room to breathe from the top")
   and clears the floating nav's row. DESKTOP: the windowed block below. */
.rpk { position: fixed; inset: 0; z-index: 70; display: flex; }
.rpk__box { width: 100%; height: 100%; overflow: auto; display: flex; flex-direction: column; gap: 12px; background: var(--surface); padding: 44px max(18px, calc((100% - 720px) / 2)) calc(30px + env(safe-area-inset-bottom, 0px)); --led-pos: #1c8f52; --led-neg: var(--error); --led-warn: #9a6c14; }
/* THE READER NAV (Curtis, same day: "make the modal into a reader — review the previous, similar to
   email — pagination"): ‹ older · › newer · ✕, floating at the content column's top-right ON THE SHELL —
   it never re-renders across page swaps. Same square spec as every icon button. */
.rpk__nav { position: fixed; top: 14px; right: max(18px, calc((100vw - 720px) / 2)); display: flex; gap: 8px; z-index: 71; }
.rpk__pg { font-size: 16px; padding-bottom: 2px; }
.rpk__pg:disabled { opacity: .3; cursor: default; pointer-events: none; }
/* DESKTOP (≥880): a floating reader, not a takeover (Curtis, 2026-08-28: "almost every other modal
   on desktop is actually better if it isn't full screen — things like what dustav read"). The 720px
   reading column survives as the box's own width; the 10vh top offset keeps the breathing room the
   full-screen face had (the wide 720 column + 10vh identity is deliberate — don't "standardize" it
   to the 560/9vh recipe); the wash is the one 45% modal wash, and a click on it closes (row-found.js
   already treats a shell click as ✕). The ‹ › ✕ nav stays viewport-fixed ON THE SHELL (it never
   re-renders across page swaps) — its math tracks the box's corner: box width = min(760px,
   100vw − 48px) inside the shell's 24px gutters, so right = max(44px, half the leftover + 20px),
   and top rides the 10vh offset to sit level with the Blink head line inside the box. */
@media (min-width: 880px) {
  .rpk { background: rgba(0,0,0,.45); align-items: flex-start; justify-content: center; padding: 10vh 24px 6vh; }
  .rpk__box { width: min(760px, 100%); height: auto; max-height: calc(100dvh - 16vh); border: 1px solid var(--border); border-radius: 16px; box-shadow: 0 18px 50px rgba(0,0,0,.28); padding: 26px 30px 30px; }
  .rpk__nav { top: calc(10vh + 18px); right: max(44px, calc((100vw - 760px) / 2 + 20px)); }
}
[data-theme="dark"] .rpk__box { --led-pos: #4ade80; --led-warn: #e3b341; }
.rpk__head { display: flex; align-items: center; gap: 9px; }
/* The modal's Blink — sized up, on the shared eye loop (this host is in the one .blink__eye rule; it does
   not restate it), wearing the ink class row-found.js copies from the tapped button (the sender's circle
   colour; accent is the no-ink fallback). */
.rpk__mark { display: inline-flex; color: var(--accent); }
.rpk__mark svg { width: 22px; height: 22px; overflow: visible; }
.rpk__title { font-family: var(--font-display); font-weight: 700; font-size: 15.5px; color: var(--ink); }
.rpk__x { margin-left: auto; }
.rpk__who { font-size: 12.5px; color: var(--muted); overflow-wrap: anywhere; margin-top: -4px; }
.rpk__block { display: flex; flex-direction: column; gap: 6px; }
.rpk__list { margin: 0; padding-left: 18px; font-size: 13.5px; color: var(--ink); }
.rpk__note { margin: 0; font-size: 13px; color: var(--muted); }
.rpk__work { display: flex; flex-direction: column; gap: 10px; border-top: 1px solid var(--border); padding-top: 12px; }
/* The ✦ popover — the AI headline, body-appended + viewport-fixed (the KebabMenu mechanics: escapes every
   scroll-container clip, closes on scroll/outside/Esc — public/js/dashboard/row-found.js). Reading prose,
   not chrome: sentence-case body type, comfortably line-broken, capped at a paragraph's width. */
.inb-sumpop { position: fixed; z-index: 60; max-width: min(380px, calc(100vw - 16px)); background: var(--surface); border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border)); border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.24); padding: 10px 13px; font-size: 13px; line-height: 1.5; color: var(--ink); }
/* ── BULK selection (docs/INBOX_UX.md) — avatar-as-checkbox + the bulk bar. ──
   .inb-check survives ONLY as the bulk bar's select-all (a styled role=checkbox span; aria-checked
   carries the state so CSS + assistive tech read the same source). */
/* Same FACE as the row's avatar-checkbox (.inb-who__box: 34px, radius 10, muted border, 16px ✓) and the same
   COLUMN — the bar's padding-left (16px) matches the row card's, so with matching outer insets (30px desktop /
   14px mobile) the select-all sits exactly over the circles it selects (Curtis, 2026-08-23: "same size as the
   rest, horizontally aligned with the rest"). Change the row box → change this in step. */
.inb-check { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border: 1.5px solid var(--muted); border-radius: 10px; background: var(--surface); color: transparent; cursor: pointer; flex: none; font-size: 16px; font-weight: 800; line-height: 1; transition: background .12s, border-color .12s; }
.inb-check::after { content: "✓"; font-weight: 800; }
.inb-check:hover, .inb-check:focus-visible { border-color: var(--accent); outline: none; }
.inb-check[aria-checked="true"] { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
/* AVATAR-AS-CHECKBOX (Gmail's pattern, 2026-08-22): the monogram IS the row's selection control — the whole
   ~34px circle is the target (this retired the separate 18px box, its invisible hit zone, and the extra grid
   column). Three faces: the monogram at rest; a hollow box on desktop hover / keyboard focus / whenever ANY
   selection is active (.is-selecting on the list — after the first tap the whole list goes overt-checkbox
   mode); a filled accent ✓ when selected. The empty bulk bar teaches the gesture ("Tap a circle to select").
   Hover is gated behind (hover:hover) so a touch tap never leaves a sticky half-flipped avatar. */
.inb-row__who--check { position: relative; display: inline-flex; cursor: pointer; border-radius: 50%; }
.inb-who__box { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; border-radius: 10px; border: 1.5px solid var(--muted); background: var(--surface); color: transparent; font-size: 16px; font-weight: 800; line-height: 1; }
/* The flip fires only when hovering the CIRCLE itself (Curtis, 2026-08-24 — reverses the 2026-08-22
   row-wide hover: flipping on row hover made hovering the list feel unstable). Rows without the check
   control have no .inb-who__box, so a pending row never flips. */
@media (hover: hover) {
  .inb-row__who--check:hover .inb-who__box { display: flex; border-color: var(--accent); }
}
.inb-row__who--check:focus-visible { outline: none; }
.inb-row__who--check:focus-visible .inb-who__box { display: flex; border-color: var(--accent); }
.is-selecting .inb-row__who--check .inb-who__box { display: flex; }
.inb-row.is-selected .inb-who__box { display: flex; background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.inb-row.is-selected { background: color-mix(in srgb, var(--accent) 7%, var(--surface)); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
/* The bulk BAR — ALWAYS visible (stateful, never appear/disappear): quiet at rest (plain border, ops
   disabled), lights up accent when ≥1 selected (.is-active, set by queue.js). Sticky inside the scroll
   column so Move/Delete stay reachable while selecting deep in the list. */
/* Sticky with an 8px inset (not 0) so a scrolled-up bar keeps breathing room from the pane's top edge
   instead of gluing flush to it (Curtis's nit) — 8px = the .inb-list row gap, one rhythm. */
/* THE BAR MIRRORS THE ROW'S GEOMETRY so scrolling is stable (Curtis, 2026-08-23): padding 16px = the row
   card's inset (select-all over the avatar column, the verbs' right edge over the row verbs' right edge),
   gap 8px = the row verbs' own gap (MOVE↔DELETE spacing identical to a row's), and the count nudges +6 so
   its text still starts where row text does (8 + 6 = the 14px who→body gap). One column grid, bar to rows. */
/* CONTEXTUAL (2026-08-27, reversing 08-23's always-visible call — the reversal record is the hbs comment):
   the bar exists only while a selection does. display keys off the .is-active class queue.js already
   toggles at n>0 — no new JS, the first avatar tap materializes it, the last deselect removes it. */
.inb-bulkbar { position: sticky; top: 8px; z-index: 5; display: none; align-items: center; gap: 8px; margin: 10px 30px 0; padding: 8px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; transition: border-color .15s, box-shadow .15s; }
/* Bar → first row matches the ROW gap (8px), not the list's 18px section pad — the bar reads as part of
   the list. Adjacency-scoped: the Drafts/Sent tabs mount no bulk bar and keep the full pad. (display:none does
   not break the adjacency selector, so the Inbox list keeps the 8px pad with the bar collapsed too.) */
.inb-bulkbar + .inb-list { padding-top: 8px; }
.inb-bulkbar.is-active { display: flex; border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); box-shadow: 0 6px 18px rgba(0,0,0,.10); }
.inb-bulkbar__count { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); white-space: nowrap; margin-left: 6px; /* gap 8 + 6 = the rows' 14px who→body gap (12px on the phone: 6 + 6) */ }
.inb-bulkbar.is-active .inb-bulkbar__count { color: var(--ink); }
.inb-bulkbar__spacer { flex: 1; }
/* RECT — a bulk verb is an ACT, and it now speaks the same word in the same voice as the row verb and the
   cockpit's: MOVE, not "Move". One vocabulary for one verb across three surfaces. */
.inb-bulkbar__btn { color: var(--ink); }
.inb-bulkbar__btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.inb-bulkbar__btn--danger { color: var(--inb-danger, #c0392b); }
.inb-bulkbar__btn--danger:hover:not(:disabled) { border-color: var(--inb-danger, #c0392b); color: var(--inb-danger, #c0392b); background: color-mix(in srgb, var(--inb-danger, #c0392b) 8%, transparent); }
.inb-bulkbar__btn--move { color: var(--inb-move, #d9a514); }
.inb-bulkbar__btn--move:hover:not(:disabled) { border-color: var(--inb-move, #d9a514); color: var(--inb-move, #d9a514); background: color-mix(in srgb, var(--inb-move, #d9a514) 8%, transparent); }
.inb-bulkbar__btn:disabled { opacity: .45; cursor: default; }
/* (Clear is BACK — Curtis 2026-08-23, same day it left: "strictly better back in, stable beside Move".
   It rides the shared .inb-bulkbar__btn spec now, neutral ink, left of Move.) */
/* the dropdown (body-appended so the scroller can't clip it) */
/* Body-appended (drift-proof) so it sits OUTSIDE #tab-inbox — redefine --inb-danger here or the danger item's
   color + color-mix hover won't resolve (the var is scoped to the mail tab shells (#tab-inbox / #tab-drafts / #tab-sent)). */
/* FIXED (not absolute): openMenu positions it from the kebab's getBoundingClientRect (viewport coords), so fixed
   maps 1:1 with no scroll math — an absolute body-anchored menu lands `scrollY` off on an already-scrolled page
   (mobile scrolls the document). It closes on any scroll (queue.js), so a pinned-to-viewport menu never lingers. */
.inb-menu { position: fixed; z-index: 60; min-width: 172px; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.24); padding: 5px; --inb-danger: #c0392b; }
[data-theme="dark"] .inb-menu { --inb-danger: #f87171; }
.inb-menu__item { display: flex; align-items: center; gap: 9px; width: 100%; text-align: left; background: none; border: 0; border-radius: 7px; padding: 8px 10px; font: inherit; font-size: 13px; color: var(--text); cursor: pointer; }
.inb-menu__item:hover, .inb-menu__item:focus-visible { background: var(--bg); outline: none; }
.inb-menu__item--danger { color: var(--inb-danger); }
.inb-menu__item--danger:hover, .inb-menu__item--danger:focus-visible { background: color-mix(in srgb, var(--inb-danger) 12%, transparent); }
.inb-menu__ic { font-size: 14px; width: 18px; text-align: center; flex: none; }
/* the LABELED dismiss pill — the base, SHARED by the TODO + Intelligence cards (their "✕ Dismiss" buttons). */
.inb-dismiss__x { font-size: 12px; }
.inb-dismiss:hover, .inb-dismiss:focus-visible { background: color-mix(in srgb, var(--error) 14%, transparent); color: var(--error); border-color: color-mix(in srgb, var(--error) 45%, var(--border)); outline: none; }
/* the mail-tab ROW triage buttons — ICON-only (no label) so the rail stays tight; the aria-label carries the
   action for a screen reader. Dismiss uses the compact modifier; Archive is icon-only + shares the box. */
.inb-archive, .inb-dismiss--icon { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; gap: 0; border: 1px solid var(--border); border-radius: 7px; background: var(--surface); color: var(--muted); font-family: inherit; font-size: 12px; letter-spacing: 0; text-transform: none; line-height: 1; cursor: pointer; flex: none; transition: background .12s, color .12s, border-color .12s; }
.inb-archive:hover, .inb-archive:focus-visible { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); outline: none; }
.inb-row.is-dismissing, .todo-card.is-dismissing, .intel-card.is-dismissing, .fact-card.is-dismissing { opacity: 0; transform: translateX(10px); pointer-events: none; transition: opacity .18s ease, transform .18s ease; }
@media (prefers-reduced-motion: reduce) { .inb-row.is-dismissing, .todo-card.is-dismissing, .intel-card.is-dismissing, .fact-card.is-dismissing { transition: opacity .18s ease; transform: none; } }

/* ── OPTIMISTIC REMOVAL + the 💥 (Curtis, 2026-08-22: "collapse optimistically… make a little explosion —
      the goal is inbox zero"). A Delete/Move collapses the row the MOMENT it's confirmed in the UI; the server
      call runs in the background and a failure ROLLS BACK (.is-returning) with an honest toast — no more
      ghost-dimmed rows waiting on the Gmail round-trip. The height glide is inline (the shared RowFX.collapse,
      via CSSOM setProperty — the CSP-safe channel); these classes carry the pop, the re-entry, and the burst.
      The TODO cards ride the same physics (2026-08-24), hence the paired selectors. */
.inb-row.is-going, .todo-card.is-going { opacity: 0; transform: scale(.88) rotate(-1.2deg); transition: opacity .15s ease-in, transform .15s ease-in; pointer-events: none; }
.inb-row.is-returning, .todo-card.is-returning { animation: inbReturn .28s ease-out; }
@keyframes inbReturn { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: none; } }
/* The burst: a body-appended fixed host at the row's center (escapes any overflow clipping); the 💥 pops and
   ~a dozen confetti dots fly out on per-particle --dx/--dy set by queue.js. Self-removes after the beat. */
.inb-boom { position: fixed; z-index: 1300; width: 0; height: 0; pointer-events: none; }
.inb-boom__pow { position: absolute; transform: translate(-50%, -50%) scale(.3); font-size: 24px; line-height: 1; opacity: 0; animation: inbPow .5s cubic-bezier(.2, .8, .3, 1) forwards; }
@keyframes inbPow { 0% { opacity: 1; transform: translate(-50%, -50%) scale(.3); } 35% { opacity: 1; transform: translate(-50%, -50%) scale(1.18); } 100% { opacity: 0; transform: translate(-50%, -50%) scale(1.45); } }
.inb-boom__p { position: absolute; width: 5px; height: 5px; border-radius: 50%; background: hsl(calc(var(--h) * 1deg) 85% 55%); opacity: 0; animation: inbShrap .6s ease-out forwards; }
@keyframes inbShrap { 0% { opacity: 1; transform: translate(-50%, -50%) scale(1); } 100% { opacity: 0; transform: translate(calc(var(--dx) - 50%), calc(var(--dy) - 50%)) scale(.35); } }
/* Inbox zero — the payoff card queue.js drops in when the last visible row clears (server empty-state takes
   over on the next reload). Rides .inb-empty for the box; this just makes it read like a win. */
.inb-empty--zero { text-align: center; font-size: 16px; font-weight: 800; color: var(--ink); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); animation: inbReturn .3s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .inb-boom { display: none; }
  .inb-row.is-going, .todo-card.is-going { transition: opacity .15s ease; transform: none; }
  .inb-row.is-returning, .todo-card.is-returning, .inb-empty--zero { animation: none; }
}

/* ── shared TOAST (public/js/toast.js) — a transient bottom-center notice with an optional action (Undo). Uses
      ink/bg swapped so it always contrasts the page in both themes. ── */
.toast-host { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 1200; display: flex; flex-direction: column; gap: 8px; align-items: center; pointer-events: none; width: max-content; max-width: 92vw; }
.toast { pointer-events: auto; display: flex; align-items: center; gap: 12px; background: var(--ink); color: var(--bg); border-radius: 10px; padding: 10px 12px 10px 16px; font-size: 13.5px; box-shadow: 0 8px 26px rgba(0,0,0,.28); opacity: 0; transform: translateY(8px); transition: opacity .2s ease, transform .2s ease; }
.toast.is-in { opacity: 1; transform: none; }
.toast.is-out { opacity: 0; transform: translateY(8px); }
.toast__msg { flex: 1 1 auto; }
.toast__action { background: none; border: 0; color: var(--accent); font-family: var(--font-display); font-weight: 700; font-size: 13.5px; cursor: pointer; padding: 2px 4px; white-space: nowrap; }
.toast__x { background: none; border: 0; color: color-mix(in srgb, var(--bg) 55%, transparent); font-size: 12px; cursor: pointer; padding: 2px 4px; line-height: 1; }
.toast__x:hover { color: var(--bg); }

/* HOVER TIP (tooltip.js, 2026-08-27) — the styled replacement for the native title popup, desktop only.
   The toast's inverted-ink bubble at its smallest: same var(--ink)/var(--bg) flip (theme-aware for free),
   tighter radius, its own quiet shadow — deliberately NOT the popover's (a tip is glanceable text, not a
   surface; the ONE-popover-shadow rule governs popovers). z-1400 = one rung above the app's current
   ceiling (1300, mobile chrome) so a tip anchored inside any modal/drawer still paints on top. */
.ff-tip { position: fixed; z-index: 1400; max-width: 260px; padding: 5px 10px; background: var(--ink); color: var(--bg); font-size: 12px; line-height: 1.45; border-radius: 7px; pointer-events: none; box-shadow: 0 4px 14px rgba(0,0,0,.22); animation: ffTipIn .12s ease-out; }
@keyframes ffTipIn { from { opacity: 0; transform: translateY(2px); } }
@media (prefers-reduced-motion: reduce) { .ff-tip { animation: none; } }
@media (prefers-reduced-motion: reduce) { .toast { transition: opacity .2s ease; transform: none; } .toast.is-out { transform: none; } }

/* The monogram avatar — the Gmail-style colored initials circle we render for a contact (there's no real
   profile photo to pull for an external sender). Shared by the mail-tab rows + the Contacts table. The
   colour is a fixed-PALETTE CLASS (.mono--cN, seeded off the person in services/monogram) — NOT an inline
   style, because the CSP is style-src 'self' with no 'unsafe-inline'. Same person → same class → same colour. */
.mono { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; background: var(--muted); color: #fff; font-family: var(--font-display); font-weight: 700; font-size: 12.5px; letter-spacing: .02em; line-height: 1; text-transform: uppercase; user-select: none; }
.mono--sm { width: 26px; height: 26px; font-size: 10.5px; }
/* The 12-colour palette (must match PALETTE_N in services/monogram.js). Even-spread hues, white text reads on all. */
.mono--c0  { background: hsl(2 60% 46%); }
.mono--c1  { background: hsl(32 62% 44%); }
.mono--c2  { background: hsl(45 58% 40%); }
.mono--c3  { background: hsl(96 42% 38%); }
.mono--c4  { background: hsl(150 46% 36%); }
.mono--c5  { background: hsl(174 58% 33%); }
.mono--c6  { background: hsl(196 62% 40%); }
.mono--c7  { background: hsl(212 60% 48%); }
.mono--c8  { background: hsl(232 52% 54%); }
.mono--c9  { background: hsl(266 44% 52%); }
.mono--c10 { background: hsl(310 44% 46%); }
.mono--c11 { background: hsl(338 56% 48%); }
/* The INK twins — the same 12 hues as FOREGROUND colour, for an element that dresses in a person's colour:
   the mailbox row's little-Dustav summary peek + the peek MODAL's own mark (row-found.js copies the class
   across, so the two can't disagree — Curtis, 2026-08-24: "make him the same color as the god damn
   circle"). Must stay hue-identical to .mono--cN above (and PALETTE_N in services/monogram.js).
   Scoped so they outrank the accent fallback + the button's hover recolour. */
.inb-found__btn.ink--c0, .rpk__mark.ink--c0  { color: hsl(2 60% 46%); }
.inb-found__btn.ink--c1, .rpk__mark.ink--c1  { color: hsl(32 62% 44%); }
.inb-found__btn.ink--c2, .rpk__mark.ink--c2  { color: hsl(45 58% 40%); }
.inb-found__btn.ink--c3, .rpk__mark.ink--c3  { color: hsl(96 42% 38%); }
.inb-found__btn.ink--c4, .rpk__mark.ink--c4  { color: hsl(150 46% 36%); }
.inb-found__btn.ink--c5, .rpk__mark.ink--c5  { color: hsl(174 58% 33%); }
.inb-found__btn.ink--c6, .rpk__mark.ink--c6  { color: hsl(196 62% 40%); }
.inb-found__btn.ink--c7, .rpk__mark.ink--c7  { color: hsl(212 60% 48%); }
.inb-found__btn.ink--c8, .rpk__mark.ink--c8  { color: hsl(232 52% 54%); }
.inb-found__btn.ink--c9, .rpk__mark.ink--c9  { color: hsl(266 44% 52%); }
.inb-found__btn.ink--c10, .rpk__mark.ink--c10 { color: hsl(310 44% 46%); }
.inb-found__btn.ink--c11, .rpk__mark.ink--c11 { color: hsl(338 56% 48%); }

.inb-pill { font-family: var(--font-mono); font-size: 9px; text-transform: uppercase; letter-spacing: .05em; padding: 2px 8px; border-radius: 20px; white-space: nowrap; }
.inb-pill--urgent { background: var(--inb-warn); color: #fff; } /* white on the dark-amber light-mode warn */
[data-theme="dark"] .inb-pill--urgent { color: #1a1206; } /* dark ink on the bright-gold dark-mode warn */
.inb-pill--linked { background: color-mix(in srgb, var(--muted) 16%, var(--surface)); color: var(--muted); }
/* The Drafts row's workflow-provenance pill (Curtis 2026-09-01: which drafts a WORKFLOW prepared must be
   obvious — Bre triages these differently from her own). Accent-tinted: Dustav's automation, not an alarm. */
.inb-pill--wf { background: color-mix(in srgb, var(--accent) 14%, var(--surface)); color: var(--accent); }
/* The actionability BADGE — a ready-to-send reply POPS (filled accent, the killer feature); other needs-you
   states are amber; a handled email recedes to muted. Prominence maps to what she must DO, not the category. */
.inb-status { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; font-family: var(--font-display); font-weight: 600; font-size: 11px; }
.inb-status--ready { color: var(--accent-ink); background: var(--accent); padding: 3px 11px; border-radius: 7px; font-weight: 700; }
.inb-status--warn { color: var(--inb-warn); background: color-mix(in srgb, var(--inb-warn) 13%, transparent); border: 1px solid color-mix(in srgb, var(--inb-warn) 45%, var(--border)); padding: 2px 9px; border-radius: 7px; }
.inb-status--review { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); padding: 2px 9px; border-radius: 7px; }
/* Terminal-state flavour (Curtis, 2026-08-24, revised same day: "use one of the accent colors"): the ✓/✕
   glyphs carry the settled/rejected distinction and BOTH states wear the ACCENT — the token the Settings
   picker swaps (royal/teal/violet/…), so the flavour follows whatever accent the operator chose, both
   themes, no semantic red/green shouting on a quiet state.
   Same-evening word-kill ("we can kill those words"): the glyph now stands ALONE beside the time — so it
   sizes up to legible (a 9.5px lone ✓ vanished) and the sentence lives in the title. Still state, never a
   button. */
.inb-status--quiet { font-family: var(--font-mono); font-weight: 600; font-size: 12px; letter-spacing: 0; color: var(--accent); }
/* still being read by Dustav (captured, no card yet) — an in-progress signal, never dimmed or pre-judged */
.inb-row.is-pending { opacity: .9; }
.inb-status--pending { font-family: var(--font-mono); font-weight: 400; font-size: 10px; letter-spacing: .03em; color: var(--accent); animation: inbPending 1.4s ease-in-out infinite; }
@keyframes inbPending { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .inb-status--pending { animation: none; } }
/* Passed by the pre-flight relevance gate (docs/RELEVANCE_GATE.md) — the lone ✕, handled's rejected twin
   (same word-kill + size-up as --quiet above); the WHY now rides the row's Blink peek, and "Read it anyway"
   lives in the cockpit.
   ⭐ RED, not the accent (Curtis, 2026-08-26: "the x in the top right corner should be red btw"). Both glyphs
   wore the accent so the flavour would follow her chosen palette — but that made ✓ handled and ✕ passed the
   SAME colour, which throws away the only distinction the glyph carries. ✓ keeps the accent (settled, hers);
   ✕ takes the error token (rejected). Reusing --error rather than minting a red keeps it theme-correct. */
.inb-status--passed { font-family: var(--font-mono); font-weight: 600; font-size: 12px; letter-spacing: 0; color: var(--error); }
/* "Read it anyway" — the recovery action on a passed row: a small ghost button reading as the override, not noise. */
/* "Read it anyway" — the escalation on a passed row (dispatches a full paid read). Deliberately a DIFFERENT
   color + feel from the accent Move pills it sat camouflaged among (Curtis, 2026-08-19): a soft AMBER fill,
   NO border — reads as "wake Dustav up on this one", not another filing chip. --inb-warn is theme-aware
   (dark amber light / gold dark), scoped on #tab-inbox. */
/* RECT, not the pill it used to be: this is an ACT (it spends money re-reading a passed email), so under the
   shape grammar it can't wear the mode shape. The warn fill is what keeps it distinct from a filing verb. */
/* (.inb-readanyway is RETIRED — "Read it anyway" lives in the row's ⋯ kebab now, 2026-08-23; the cockpit's
   own escalation button is .ck-passed__read, a different control.) */
.inb-row.is-reading { opacity: .75; }
/* Mobile: the row STOPS competing horizontally (the folder pill tipped the 4-column layout into overlap) and
   STACKS — avatar left (spanning), then name / headline / chips / meta each on their own row, so nothing can
   collide (docs/FOLDERS.md). Grid areas by DOM order: avatar, who, mid (line+chips), aside (time+actions). */
/* Mobile: the rail drops BELOW the body (same three blocks, just stacked — no per-element reflow), so the status
   badge + meta run as one row under the headline instead of squeezing the body on a narrow phone. */
@media (max-width: 620px) {
  /* ⭐ THE PHONE CARD, 2026-08-24 re-cut (Curtis, from Bre's phone: "phone space is precious"):
       row 1 — [avatar] [sender, UNCUT] [✓/✕ · time] — the corner holds only compact things, so the sender
               finally gets the width ("never cut off the name").
       row 2 — the SUBJECT, spanning to the card's right edge (the AI summary left for the ✦ peek).
       row 3 — the STATE line (pills + a wordy chip: "Needs you" / "✓ Reply ready" / "reading…"), full-width,
               ONLY when it exists — an active row earns a line; a settled row doesn't pay for one.
       row 4 — the FOOT, full-bleed to both card edges: the FOUND line far-left (✦ + outcome icons — "if an
               email has TODOs then it's the TODO icon, bottom left") · the VERBS far-right, wrapping under
               when a full found line meets a labeled folder segment on a narrow screen.
     The subject escapes the body column via display:contents (body's children become grid items), which is
     what lets it span past the corner's column. Row spacing comes from per-area margins, NOT row-gap — an
     empty state row must cost zero height, and grid gaps charge for empty tracks.
     No absolute pinning, no reserved lanes — every slot is a grid area, which retires the kebab-era
     right-gutter arithmetic (padding-right 52/116, absolute time at right:56) wholesale. */
  .inb-row { grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "who head top" "who line line" "state state state" "foot foot foot"; align-items: start; column-gap: 12px; row-gap: 0; padding: 12px 14px; }
  .inb-row__who { align-self: start; }
  .inb-row__body { display: contents; }
  .inb-row__top { grid-area: head; }
  .inb-row__line { grid-area: line; margin-top: 3px; }
  .inb-row__state { grid-area: state; justify-content: flex-start; flex-wrap: wrap; gap: 4px 7px; margin-top: 7px; }
  /* The corner: glyph + time only — compact by construction, never a squeeze. */
  .inb-row__railtop { gap: 7px; }
  .inb-row__foot { margin-top: 8px; }
  /* ⭐ THE VERBS CLUSTER BOTTOM-RIGHT, ICON-ONLY (Curtis, 2026-08-23: "on mobile collapse both to the icon
     only… put the MOVE button over to the right beside the trash can — it belongs there"): both verbs are
     icon squares (the folder glyph IS Move, the red can IS Delete — labels survive in title/aria-label), so
     the thumb zone is one small cluster at the card's bottom-right, and the suggestion NAME (the leaf
     segment) is the one labelled piece beside them. Height still comes from --btn-h-md, never padding. */
  .inb-row__actions { flex-wrap: nowrap; justify-content: flex-end; gap: 6px; }
  .inb-verb__label { display: none; }
  .inb-verb--move, .inb-verb--del { width: var(--btn-h-md); padding: 0; }
  .inb-verb--go { max-width: 130px; padding: 0 11px; }
  /* The subject can take a second line on a phone (it's the main line now), then clips. */
  .inb-row__line { white-space: normal; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
  /* (.inb-pill--folder is GONE — the rail-kill's last orphan, swept 2026-08-28: the base rule died
     2026-08-13 with the folder rail; this mobile override outlived it with no markup to style.) */
  /* BULK: no extra column — the avatar IS the checkbox (see .inb-row__who--check above). */
  /* The bar NEVER wraps (a two-row bar read as broken chrome): buttons go ICON-ONLY (Gmail's own mobile
     selection bar — the confirm modals name the op anyway; aria-label/title keep the verb), and the
     count/hint is the one shrinkable piece (ellipsis on a truly tiny screen, never a second row). */
  .inb-bulkbar { margin: 10px 14px 0; gap: 6px; flex-wrap: nowrap; } /* gap 6 = the mobile row verbs' gap */
  .inb-bulkbar__count { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .inb-bulkbar__btn { min-width: var(--btn-h-md); padding: 0 8px; }
  .inb-bulkbar__btn-label { display: none; }
}

/* Live activity BAR (SSE §8) — a thin status strip while a sync runs (Dustav reading the current email + its
   tool chips), deliberately NOT a card/row so it never competes with the clickable queue. Reuses the .led-act
   chip look, so redefine its color vars here (they normally live on #tab-log). */
.inb-syncbar { display: flex; flex-direction: column; align-items: stretch; gap: 9px; margin: 16px 30px 2px; max-width: 860px; padding: 12px 15px; border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border)); background: color-mix(in srgb, var(--accent) 7%, var(--surface)); border-radius: 12px; --led-pos: #1c8f52; --led-neg: var(--error); --led-warn: #9a6c14; }
.inb-syncbar[hidden] { display: none; }
[data-theme="dark"] .inb-syncbar { --led-pos: #4ade80; --led-warn: #e3b341; }
.inb-syncbar.is-done { border-color: color-mix(in srgb, var(--led-pos) 45%, var(--border)); background: color-mix(in srgb, var(--led-pos) 7%, var(--surface)); }
.inb-syncbar__head { display: flex; align-items: center; gap: 9px; }
.inb-syncbar__label { font-size: 13px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; min-width: 0; }
/* the conversational live log (public/js/dashboard/sync-live.js) — the play-by-play renders VERTICALLY here
   (chips, wrapped reasoning, self-timing stage lines), not as columns beside the label; bounded so the strip
   stays lean + scrolls to the newest. Reuses the .ck-trace__log flow; tightens its gap for the strip. */
.inb-syncbar__log { max-height: 220px; overflow-y: auto; gap: 9px; }
.inb-syncbar__log:empty { display: none; }
@media (max-width: 879px) { .inb-syncbar { margin: 12px 16px 0; } .inb-syncbar__label { max-width: 46vw; } .inb-syncbar__log { max-height: 44vh; } }

/* ══ THE INSTRUMENT FOOT + THE DRAWER (docs/INBOX_COCKPIT.md §2b — the pane is the email, 2026-08-23) ══
   The left column is a stateful window: a fixed head, the email, and a fixed foot. The foot is the only chrome
   that changes as you work — and it changes CONTENT, never SIZE, so the email's frame is the same shape on
   every message of every thread. Left half walks the thread; right half consults Dustav. */
/* ⚠️ The PANELS come first and the pager is pushed right with margin-left:auto — NOT space-between. With
   space-between a one-message email (no pager) leaves the panels as the only child, and "space between one
   thing" is just flex-start… which is where they already were, so it looks fine — right up until you open a
   THREAD and every button shifts. margin-left:auto pins the panels to the left edge unconditionally. */
/* 12px, not the column's old 26px: the email pane is edge-to-edge now, so the foot's inset no longer lines up
   with anything above it — the buttons just sat 14px inboard of the message. 12px is the .ck-stackmsg__head
   padding, so the first panel button now starts on the same vertical as the sender's avatar. */
.ck-foot { flex: none; display: flex; align-items: center; gap: 12px; padding: 8px 12px; border-top: 1px solid var(--border); background: var(--bg); }
/* (THE PAGER IS RETIRED WHOLE — the foot's .ck-pager container went 2026-08-24 (the ‹ › moved into the
   message head), and the head pager went 2026-08-27 with the conversation feed: the whole thread is ONE
   document in ONE frame (/thread-body), so there is nothing to page. .ck-pager__btn went with it — nothing
   emits the class any more, so its box spec + hover/disabled rules are deleted, not kept as a costume.
   The ORDER TOGGLE (.ck-torder) sits where the pager sat, top right of the feed's head.) */
.ck-torder { margin-left: auto; height: var(--btn-h-sm); padding: 0 10px; font-family: var(--font-mono); font-size: 10.5px; color: var(--muted); }
.ck-torder:hover { color: var(--ink); border-color: var(--muted); }
.ck-panels { flex: 0 1 auto; min-width: 0; display: flex; align-items: center; gap: 6px; overflow-x: auto; scrollbar-width: none; }
/* Blink leads the panel group — the foot's left half is Dustav's, and the living mark says so (2026-08-24). */
.ck-panels__mark { flex: none; display: inline-flex; align-items: center; margin-right: 2px; }
.ck-panels::-webkit-scrollbar { display: none; }
/* Same box spec as the row verbs (.inb-verb) — a control that consults is the same KIND of thing as a control
   that acts, and giving each surface its own pill shape is how a product stops looking like one product. */
/* PILL — these four SELECT which panel the drawer shows; they're modes, not acts. (Box: button primitive.) */
.ck-panel-btn:hover { color: var(--ink); border-color: var(--muted); }
/* Pressed = the drawer is showing THIS panel. Accent-filled, because it's the one control on screen whose state
   you can't read off the thing it controls (the drawer covers the email either way). */
.ck-panel-btn[aria-pressed="true"] { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); background: color-mix(in srgb, var(--accent) 10%, transparent); }
/* .ck-pane is the email + the drawer, and the drawer's positioning context. The foot lives OUTSIDE it, so a
   bottom-anchored drawer rises to meet the foot instead of covering the buttons that opened it. */
.ck-pane { position: relative; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* ── THE NOTICE BANNER (docs/NOTIFICATIONS.md §9c) ────────────────────────────────────────────────────────────
   "When you click into the email it isn't obvious that it needs some help" (Curtis, 2026-08-26). The handoff was
   in the drawer, which renders closed — so the one email on screen that needed a human hid its ask behind a
   toggle. This sits ABOVE the message: flex:none, so it takes its own height and the email keeps the rest.
   Accent-tinted rather than red — "Dustav wants you" is an ASK, not an error, and colouring every ask as a
   failure is how a surface teaches people to dread it. */
.ck-notices { flex: none; display: flex; flex-direction: column; gap: 6px; padding: 10px 16px 0; }
/* THE FOUND LINE (2026-08-30, Bre; rev 3 same day — "put the workflow created down on the right") — a
   workflow's own outcomes ride the FOOT's right edge (.ck-foot__found, the retired pager's old slot);
   the ambient outcomes render as the same chips inside the Activity panel (.ck-didfound). Quiet
   evidence, not an ask: bordered surface chips, never the notice banner's accent wash. The label
   ellipsizes so a long draft subject can't push the panel pills off the row. */
.ck-foot__found { flex: 0 1 auto; min-width: 0; margin-left: auto; display: flex; align-items: center; gap: 6px; overflow: hidden; }
/* The receipt POPS (Curtis, 2026-08-30, on the live foot placement: "lets make it a different color
   that pops out") — the ACCENT tint, because the receipt is Dustav's own signature ("my automation ran
   on this email"): accent wash + border + label, unmissable against the muted foot pills beside it.
   Deliberately still a chip, never the notice banner's block — the banner stays the only accent BLOCK
   because it's the only ask; the Activity panel's ambient chips keep the quiet dress. */
.ck-foot__found .ck-found__chip { min-width: 0;
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  color: var(--accent); font-weight: 600; }
.ck-foot__found a.ck-found__chip:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 20%, var(--surface)); }
.ck-didfound { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.ck-found__chip { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 5px 10px;
  border: 1px solid var(--border); border-radius: 9px; background: var(--surface);
  font-size: 12.5px; line-height: 1.3; color: var(--ink); text-decoration: none; }
.ck-found__chip svg { flex: none; width: 14px; height: 14px; color: var(--accent); }
.ck-found__lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.ck-found__chip:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); background: color-mix(in srgb, var(--accent) 6%, var(--surface)); }
.ck-found__chip.is-static { cursor: default; color: var(--muted); }
.ck-notice { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--accent) 38%, var(--border));
  background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
.ck-notice__icon { flex: none; display: inline-flex; width: 20px; height: 20px; color: var(--accent); }
.ck-notice__icon svg { width: 17px; height: 17px; }
.ck-notice__body { flex: 1; min-width: 0; }
.ck-notice__title { font-family: var(--font-display); font-weight: 650; font-size: 13.5px; line-height: 1.3; color: var(--ink); }
.ck-notice__line { margin: 3px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--muted); }
/* The act is a rect (an act you DO, not a mode you're in — the shape grammar), accent-filled: it's the one
   control here and the reason the banner exists. */
.ck-notice__act { flex: none; align-self: center; height: var(--btn-h-md); padding: 0 12px; border: none; border-radius: 8px; background: var(--accent); color: var(--accent-ink); font-weight: 700; font-size: 12.5px; cursor: pointer; white-space: nowrap; }
.ck-notice__act:hover { filter: brightness(1.06); }
.ck-notice__act:disabled { opacity: .6; cursor: default; }
@media (max-width: 879px) {
  /* The phone gives the act its own line rather than squeezing it beside two lines of prose — the
     three-row card grammar, applied to a banner. */
  .ck-notice { flex-wrap: wrap; }
  .ck-notice__act { width: 100%; margin-top: 8px; }
}

/* .ck-main — the nav column + the scroll, side by side. On a phone the nav is display:none and this is just
   the scroll. The attachment strip sits below it in the pane; the drawer overlays both. */
.ck-main { flex: 1 1 auto; min-height: 0; display: flex; }
.ck-main > .ck-scroll { min-width: 0; }
/* THE THREAD NAV (2026-08-24 v2, same day as the dot rail it replaces — Curtis: "a lovely little side thread
   nav to the left of the viewer", Bre: "likes to scroll back and see what she said"). Desktop, 2+ messages: a
   who · when entry per message, current marked in the accent; clicking scrolls the continuous stack
   (.ck-scroll--thread below) and the foot's ‹ › pager stays in step — nav, scroll position, and pager are
   three hands on wirePager's one show(). Conditional like the pager: it exists only where a thread does. */
.ck-tnav { flex: none; width: 160px; border-right: 1px solid var(--border); background: var(--bg); overflow-y: auto; scrollbar-width: thin; padding: 8px; display: flex; flex-direction: column; gap: 2px; }
.ck-tnav[hidden] { display: none; }
/* the column's title — 🧵 + the word, so the list of names reads as "this thread", not a mystery sidebar.
   (The 🧵 moved here from the retired foot pager — the concept kept its glyph.) */
.ck-tnav__hd { flex: none; padding: 3px 9px 7px; font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
/* An <a> now (2026-08-27): each entry deep-links the feed frame's #dv-m<i> anchor, so the jump works with JS
   off (target="ck-feed-frame"); wireThreadFeed upgrades the click to a smooth in-document scroll + spy. */
.ck-tnav__it { display: block; min-width: 0; text-align: left; padding: 7px 9px; border: 0; border-radius: 8px; background: none; cursor: pointer; text-decoration: none; }
.ck-tnav__it:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
.ck-tnav__it.is-current { background: color-mix(in srgb, var(--accent) 10%, transparent); box-shadow: inset 2px 0 0 var(--accent); }
.ck-tnav__who { display: block; font-size: 12.5px; font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ck-tnav__when { display: block; margin-top: 1px; font-family: var(--font-mono); font-size: 10.5px; color: var(--muted); }
@media (max-width: 879px) { .ck-tnav { display: none; } }
/* ATTACHMENTS, FIRST-CLASS (Bre, 2026-08-24: "if there are attachments they should be visible") — the
   always-on strip at the pane's foot: image thumbs + file chips, both cockpits. Desktop wraps to at most a
   couple of rows (then scrolls); the phone gets ONE thumb-height row that scrolls sideways — chips are the
   one control family where a horizontal scroll reads as more-to-the-side, not as clipped. */
.ck-attstrip { flex: none; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; max-height: 110px; overflow-y: auto; padding: 8px 12px; border-top: 1px solid var(--border); background: var(--surface); }
.ck-attstrip__thumb { flex: none; display: block; width: 44px; height: 44px; border-radius: 8px; overflow: hidden; border: 1px solid var(--border); }
.ck-attstrip__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 879px) {
  .ck-attstrip { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; max-height: none; scrollbar-width: none; }
  .ck-attstrip::-webkit-scrollbar { display: none; }
  .ck-attstrip .ck-attach__name { max-width: 160px; }
}
/* Over the email, capped at half the pane: the email is never fully hidden, so the drawer reads as a glance
   rather than a screen you navigated to. */
.ck-drawer { position: absolute; left: 0; right: 0; bottom: 0; max-height: 52%; display: flex; flex-direction: column; background: var(--surface); border-top: 1px solid var(--border); box-shadow: 0 -14px 34px rgba(0,0,0,.16); z-index: 3; }
[data-theme="dark"] .ck-drawer { box-shadow: 0 -14px 34px rgba(0,0,0,.44); }
.ck-drawer[hidden] { display: none; }
.ck-drawer__body { min-height: 0; overflow-y: auto; padding: 14px 26px 16px; }
/* the drawer's floating ✕ — top-right corner, over the body (Curtis, 2026-08-24: closing meant re-finding
   the pill that opened it). z above the content; the body's top padding keeps most content clear of it. */
.ck-drawer__x { position: absolute; top: 9px; right: 12px; z-index: 2; background: var(--surface); color: var(--muted); }
.ck-drawer__x:hover { color: var(--ink); border-color: var(--muted); }
.ck-panel > *:first-child { margin-top: 0; }
.ck-panel__empty { color: var(--muted); font-size: 13px; margin: 0; }

/* ══ The thread SCREEN / draft cockpit (docs/INBOX_UX.md §2b) — conversation | composer. Fills the pane
      like the inbox; on desktop the composer is the static right pane (echoing the copilot rail); on mobile
      it's a fly-up bottom-sheet matching the copilot drawer (peek → ~80% sheet → scrim). ══ */
.cockpit { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: column; --inb-warn: #9a6c14; }
[data-theme="dark"] .cockpit { --inb-warn: #e3b341; }
.ck-grid { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) clamp(360px, 32vw, 460px); }

/* position: relative — the drawer is absolutely positioned INSIDE this column, so it sits over the email and
   never over the composer. */
.ck-convo { position: relative; min-width: 0; min-height: 0; display: flex; flex-direction: column; border-right: 1px solid var(--border); }
.ck-head { flex: none; display: flex; align-items: center; gap: 14px; padding: 15px 26px; border-bottom: 1px solid var(--border); }
/* Shared back button — the "← SURFACE" pill. Reference format from the inbox/draft cockpit head; used
   on every surface's detail→list back (notes, workflows, media, context, usage). Mono, uppercase, pill.
   Positional margin/align lives on a thin per-surface class alongside it, never here. */
.back-btn:hover { color: var(--ink); border-color: var(--muted); }
/* "Mark handled" in the thread head — a back-btn-style pill pushed to the far right of the head row. */
/* the thread "✓ Done" — accent-filled (same purple as the TODO card's Done), overriding the .back-btn pill it shares */
/* Delete (thread head) — a ghost pill that reads DANGER on hover; pushed to the right so Done stays the primary. */
/* Move / Delete triage pills. They wear their OWN class rather than borrowing `.back-btn`: the modal treats a
   `.back-btn` click as "close", so sharing it made these two close the modal out from under their own action —
   and a rule written for the back LINK rendered them as empty pills. Same pill spec, one class of bugs gone.
   The .ck-title flexes (above) to push the group to the header's right; in the modal the group is re-parented
   into the modal bar entirely (cockpit-modal.js hoistChrome). Delete is a DANGER button: red TEXT + red BORDER
   (the emoji stays its natural color); hover deepens with a red-tinted fill. */
.ck-tri:hover { color: var(--ink); border-color: var(--muted); }
/* Disabled = this email can't do that act. Kept visible (never removed) so the bar's geometry holds as you
   walk from an email that can to one that can't — see cockpit-modal.js syncVerbs. */
.ck-tri:disabled { opacity: .38; cursor: default; }
.ck-tri:disabled:hover { color: var(--muted); border-color: var(--border); }
.ckm__triage { display: inline-flex; align-items: center; gap: 6px; }
/* Move wears the folder act's amber INSIDE the cockpit too (Curtis, 2026-08-28: "inside the inbox
   message these need to be yellow") — the same --inb-move the card rows, the Folders toggle and the
   picker chips carry; Delete keeps its red. One act, one colour, every surface. */
.ck-move { color: var(--inb-move, #d9a514); border-color: color-mix(in srgb, var(--inb-move, #d9a514) 45%, var(--border)); }
.ck-move:hover { color: var(--inb-move, #d9a514); border-color: color-mix(in srgb, var(--inb-move, #d9a514) 60%, var(--border)); background: color-mix(in srgb, var(--inb-move, #d9a514) 8%, transparent); }
.ck-delete { color: #c0392b; border-color: color-mix(in srgb, #c0392b 45%, var(--border)); }
[data-theme="dark"] .ck-delete { color: #f87171; border-color: color-mix(in srgb, #f87171 45%, var(--border)); }
.ck-delete:hover { border-color: color-mix(in srgb, #c0392b 60%, var(--border)); background: color-mix(in srgb, #c0392b 8%, transparent); }
[data-theme="dark"] .ck-delete:hover { border-color: color-mix(in srgb, #f87171 60%, var(--border)); background: color-mix(in srgb, #f87171 12%, transparent); }
/* The triage pills sit in their own tight-gap group (matching the .ck-modes Reply/Reply-all/Forward set), so the
   header's wider 14px gap only separates back-btn ↔ title ↔ group — never the three buttons from one another. */
.ck-triage { flex: none; display: flex; align-items: center; gap: 6px; }
.ck-title { min-width: 0; flex: 1 1 auto; } /* grow so the File / Archive / Delete pills sit at the header's right */
.ck-title h2 { font-family: var(--font-display); font-size: 16px; font-weight: 700; margin: 0; letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ck-title .inb-ml { margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* one line (truncate the long sender·email·count) so the header can't wrap to 3 lines + cramp */
.ck-from { font-family: var(--font-mono); user-select: all; }
/* The email's window. The 40px bottom padding is gone with the pile of blocks that used to sit under the
   message — the foot is the floor now. */
.ck-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 18px 26px 22px; display: flex; flex-direction: column; gap: 16px; }
/* ⭐ --fill: the message FILLS the pane and scrolls INSIDE its own frame, instead of the frame growing to fit
   the message and the column scrolling past it. That one inversion is what makes this a stateful window: the
   email's frame is the same rectangle on a two-line receipt and a forty-screen newsletter, so nothing you
   learn about where things are stops being true on the next email. (The old behavior is still right for the
   plaintext fallback, which has no frame to scroll — hence the class rather than a blanket rule.) */
/* EDGE TO EDGE: no padding, no card border, no radius (Curtis: "we don't need any padding in the left pane
   email holder — just wasted space"). The frame's chrome existed to separate one message from the next in a
   stack of them; with one message owning the pane there is no next, so it was a box drawn around the whole
   column. The message HEAD keeps its rule — that's the sender line, not decoration. */
.ck-scroll--fill { overflow: hidden; padding: 0; gap: 0; }
.ck-scroll--fill .ck-eml { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ck-scroll--fill .ck-eml > .ck-stackmsg { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; border: 0; border-radius: 0; }
.ck-scroll--fill .ck-stackmsg__head { border-bottom: 1px solid var(--border); }
/* ⚠️ .ck-frame stays a BLOCK. It was briefly `display: flex` here, which defaults to flex-direction: ROW — so
   `flex: 1` on the iframe grew its WIDTH while its height fell back to a replaced element's intrinsic 150px,
   and the email rendered in a small box with its own scrollbar. A definite-height block + height:100% is the
   version with no axis to get wrong. */
.ck-scroll--fill .ck-frame { flex: 1 1 auto; min-height: 0; display: block; }
.ck-scroll--fill .ck-eml__frame { width: 100%; height: 100%; min-height: 0; border: 0; border-radius: 0; transition: none; }
/* (Lineage: the continuous-scroll .ck-scroll--thread experiment lived here for a few hours on 2026-08-24 and
   was reverted the same evening — N per-message iframes ARE N scrollers. The 2026-08-27 CONVERSATION FEED is
   not that build: the whole thread is ONE server-concatenated document in ONE frame (/thread-body), so the
   fixed full-height window above serves a thread exactly as it serves a single message — one scroller, no
   height sync, no pager. The .ck-stack--paged/.ck-head-pg machinery retired with the per-message stack.) */

/* DECODE, don't dump (§13) — Dustav's decoded summary is the HERO: the headline + a clean facts card, rendered
   in our own type so a Square report (garbage as raw HTML) reads as a native operator summary. */
.ck-summary { background: color-mix(in srgb, var(--accent) 5%, var(--surface)); border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border)); border-radius: 12px; padding: 14px 16px; }
.ck-summary__head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.ck-summary__head .inb-ml { margin-left: auto; }
.ck-summary__line { margin: 0; font-size: 15px; line-height: 1.4; font-weight: 600; color: var(--ink); }
.ck-facts { margin: 11px 0 0; display: grid; grid-template-columns: minmax(0, auto) 1fr; gap: 6px 16px; }
.ck-fact { display: contents; }
.ck-fact dt { font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); align-self: center; }
.ck-fact dd { margin: 0; font-size: 13.5px; color: var(--ink); font-variant-numeric: tabular-nums; word-break: break-word; }
/* The raw body demoted to a collapsed, PLAINTEXT "view full message" (never the layout-table HTML). */
/* The faithful HTML render (docs/EMAIL_HTML.md) — the sender's email in a sandboxed iframe, so it looks like
   Gmail. Fixed-height, scrolls internally (D3 v1; auto-height is the v2 polish). The frame paints its own white
   background (email HTML assumes light), so the border/radius frame it cleanly in either app theme. */
/* No border-top/padding-top any more: that rule existed to separate the email from Dustav's summary card
   sitting above it. The email is the first — and usually only — thing in this column now, so the line
   separated it from the top of the pane, which is nothing. */
.ck-eml { min-width: 0; }
/* height is a LOADING default only — JS fits each frame to its content on load (docs/EMAIL_HTML.md), clamped
   min 44 / max 720 (a long email scrolls internally). The transition eases the fit instead of snapping. If JS
   never runs this fixed height is the fallback (scrollable, never clipped-broken). */
.ck-eml__frame { display: block; width: 100%; height: 160px; min-height: 44px; border: 1px solid var(--border); border-radius: 10px; background: #fff; transition: height .18s ease; }
@media (prefers-reduced-motion: reduce) { .ck-eml__frame { transition: none; } }
.ck-eml__bar { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; font-family: var(--font-mono); font-size: 11px; }
.ck-eml__note { color: var(--muted); }
/* was `font: inherit` — which meant this button silently changed size with whatever prose it landed beside. */
.ck-eml__show { height: var(--btn-h-sm); color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 26%, transparent); }
.ck-eml__show:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); }

/* The message window's card box — worn by the single-message render AND the conversation feed (one box each).
   (.ck-stack, the per-message pile this box once stacked inside, retired 2026-08-27 with the feed.) */
.ck-stackmsg { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--surface); }
/* cursor:default — the head was a <summary> you clicked to expand; it's a static label now, and a pointer on
   something that doesn't respond is a small lie repeated on every email. */
.ck-stackmsg__head { display: flex; align-items: center; gap: 8px; padding: 9px 12px; font-size: 12.5px; cursor: default; list-style: none; background: color-mix(in srgb, var(--ink) 3%, var(--surface)); }
.ck-stackmsg__from { font-weight: 600; color: var(--ink); white-space: nowrap; flex: none; }
.ck-stackmsg__email { color: var(--muted); font-family: var(--font-mono); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.ck-stackmsg__when { margin-left: auto; color: var(--muted); font-family: var(--font-mono); font-size: 11px; white-space: nowrap; flex: none; }
/* the latest message shows expanded (no <details>); its frame is a touch taller. */
.ck-stackmsg__frame { border: 0; border-top: 1px solid var(--border); border-radius: 0; }

/* Each message frame fetches its HTML at runtime, so show a small "loading" spinner in the top-left corner until
   it loads (docs/EMAIL_HTML.md). The iframe stays fully visible (no opacity gymnastics → never a blank frame);
   the label just overlays the corner. JS adds .is-loading while the frame's in flight (guaranteed min window so
   it registers), removes it on the frame's `load` (fires on the parent even though the frame is sandboxed). */
.ck-frame { position: relative; display: block; }
/* The label sits over the email frame, which ALWAYS renders on white (see docs/EMAIL_HTML.md D4) — so it uses
   fixed light-context colours, NOT the theme vars (a dark chip on the white frame in dark mode is the bug). */
.ck-frame__load { position: absolute; top: 10px; left: 12px; z-index: 2; display: none; align-items: center; gap: 6px; font-family: var(--font-mono); font-size: 11px; color: #6b7280; background: rgba(255, 255, 255, .82); padding: 4px 9px 4px 7px; border-radius: 7px; }
.ck-frame.is-loading .ck-frame__load { display: inline-flex; }
.ck-frame__spin { width: 10px; height: 10px; border: 2px solid rgba(120, 130, 145, .3); border-top-color: #6b7280; border-radius: 50%; animation: ckSpin .7s linear infinite; }
@keyframes ckSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ck-frame__spin { animation-duration: 1.6s; } }

.ck-msg { display: flex; gap: 12px; }
.ck-msg__av { width: 30px; height: 30px; flex: none; border-radius: 50%; display: grid; place-items: center; font-family: var(--font-display); font-weight: 700; font-size: 11.5px; color: #fff; background: var(--accent); }
.ck-msg.is-mine .ck-msg__av { background: var(--muted); }
.ck-msg__body { min-width: 0; flex: 1 1 auto; }
.ck-msg__head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 3px; }
.ck-msg__from { font-weight: 700; font-size: 13.5px; }
.ck-msg__email { font-family: var(--font-mono); font-size: 11px; color: var(--muted); user-select: all; min-width: 0; overflow: hidden; text-overflow: ellipsis; } /* the raw sender address, on the message (not the header) */
.ck-msg__when { font-family: var(--font-mono); font-size: 10px; color: var(--muted); white-space: nowrap; }
/* Cap the raw message body so a long email doesn't bury "What Dustav did" / the summary — scroll within (docs/FOLDERS.md polish). */
.ck-msg__text { font-size: 14px; line-height: 1.55; color: var(--ink); word-break: break-word; max-height: 380px; overflow-y: auto; }
.ck-msg__text p { margin: 0 0 9px; } .ck-msg__text p:last-child { margin-bottom: 0; }
/* the quoted reply chain + signature, collapsed under the person's actual new text (docs/INBOX_UX.md §10) */
.ck-quoted-msg { margin-top: 9px; }
.ck-quoted-msg > summary { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); cursor: pointer; list-style: none; }
.ck-quoted-msg > summary::-webkit-details-marker { display: none; }
.ck-quoted-msg[open] > summary { color: var(--ink); }
.ck-quoted-msg__body { margin-top: 9px; padding-left: 12px; border-left: 2px solid var(--border); color: var(--muted); font-size: 12.5px; }
/* NB: the inline body is always rendered as reliable PLAINTEXT (docs/INBOX_UX.md §13, "decode, don't dump") —
   the sender's HTML is never dumped here (it ladders into garbage). The decode lives in .ck-summary. */

.ck-attach .inb-ml { display: block; margin-bottom: 7px; }
.ck-attach__grid { display: flex; gap: 8px; flex-wrap: wrap; }
.ck-attach__thumb img { max-height: 92px; border: 1px solid var(--border); border-radius: 7px; display: block; }
/* Stored document (PDF) chips — a big PDF has no thumbnail, so it shows as a download pill that opens the bytes. */
.ck-attach__files { display: flex; flex-wrap: wrap; gap: 8px; }
/* chip + its ⬇ stay one unit when the row wraps; the ⬇ docks flush against the chip's right edge */
.ck-attach__pair { display: inline-flex; align-items: stretch; max-width: 100%; }
.ck-attach__file { display: inline-flex; align-items: center; gap: 7px; max-width: 100%; font-size: 13px; color: var(--ink); background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 7px 11px; text-decoration: none; }
.ck-attach__file:hover { border-color: var(--muted); }
/* Drag-a-file-onto-the-composer cue (AttachBox drop zone) — mirrors chat's .chat-dragover. */
.ck-comp.is-dragover { outline: 2px dashed var(--accent); outline-offset: -2px; background: color-mix(in srgb, var(--accent) 7%, var(--surface)); }
/* the per-chip ⬇ — a REAL save (?download=1); the chip itself previews (docs/ATTACHMENTS_UX.md) */
.ck-attach__pair .ck-attach__file { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.ck-attach__dl { display: inline-flex; align-items: center; flex: none; font-size: 13px; color: var(--muted); background: var(--surface); border: 1px solid var(--border); border-left: none; border-radius: 0 8px 8px 0; padding: 7px 9px; text-decoration: none; }
.ck-attach__dl:hover { color: var(--ink); border-color: var(--muted); }
.ck-attach__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ck-attach__size { flex: none; font-family: var(--font-mono); font-size: 10.5px; color: var(--muted); }
/* Lazy preview thumbnail inside a file chip (attach-thumbs.js): hidden until it loads, then it replaces the
   📎 icon so the chip reads "[preview] name  size" on both the inbox thread + the draft composer. */
.ck-attach__ic { flex: none; }
.ck-attach__fthumb { display: none; flex: none; width: 40px; height: 40px; object-fit: cover; border: 1px solid var(--border); border-radius: 6px; }
.ck-attach__file.has-thumb .ck-attach__fthumb { display: block; }
.ck-attach__file.has-thumb .ck-attach__ic { display: none; }
/* "What Dustav did" — the per-email trace chips (reuses the .led-act look; redefine its color vars here). */
.ck-trace { border-top: 1px solid var(--border); padding-top: 13px; --led-pos: #1c8f52; --led-neg: var(--error); --led-warn: #9a6c14; }
[data-theme="dark"] .ck-trace { --led-pos: #4ade80; --led-warn: #e3b341; }
.ck-trace .inb-ml { display: block; margin-bottom: 8px; }
/* Collapsed-by-default disclosure (a <details>): the summary reads like the mono label + a caret, no marker. */
.ck-trace > summary { cursor: pointer; list-style: none; }
.ck-trace > summary::-webkit-details-marker { display: none; }
.ck-trace > summary::before { content: "▸ "; }
.ck-trace[open] > summary::before { content: "▾ "; }
.ck-trace__chips { display: flex; gap: 7px; flex-wrap: wrap; }
/* LIVE (§8) — watch Dustav read THIS email in real time: the reasoning + chips stream in, then the screen
   finalizes to the settled card. Shares the audit's .ck-trace__log step styling. */
.ck-live { border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border)); background: color-mix(in srgb, var(--accent) 5%, var(--surface)); border-radius: 12px; padding: 14px 16px; --led-pos: #1c8f52; --led-neg: var(--error); --led-warn: #9a6c14; }
[data-theme="dark"] .ck-live { --led-pos: #4ade80; --led-warn: #e3b341; }
.ck-live__head { display: flex; align-items: center; gap: 9px; margin-bottom: 11px; }
.ck-live__now { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; font-family: var(--font-mono); font-size: 11px; letter-spacing: .04em; color: var(--muted); text-transform: none; }
.ck-live__spin { flex: none; width: 12px; height: 12px; border: 2px solid color-mix(in srgb, var(--accent) 28%, transparent); border-top-color: var(--accent); border-radius: 50%; animation: ckLiveSpin .7s linear infinite; }
@keyframes ckLiveSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ck-live__spin { animation: none; } }
/* the investigation AUDIT (Part B) — per-hop reasoning interleaved with the chips it fired that hop */
.ck-trace__log { display: flex; flex-direction: column; gap: 13px; }
.ck-trace__log .led-step { display: flex; flex-direction: column; gap: 7px; border-left: 2px solid var(--border); padding-left: 12px; }
.ck-trace__log .led-think { margin: 0; font-size: 13px; line-height: 1.55; color: var(--ink); }
.ck-trace__log .led-step__chips { display: flex; gap: 7px; flex-wrap: wrap; }
/* the LIVE telemetry step lines (public/js/dashboard/inbox-thread.js) — the literal play-by-play, each self-timing */
.ck-trace__log .led-stage { font-family: var(--font-mono); font-size: 11.5px; line-height: 1.5; color: var(--muted); padding-left: 12px; border-left: 2px solid transparent; }
.ck-trace__log .led-stage.is-warn { color: var(--led-warn); font-weight: 600; }

/* Intelligence (docs/INTELLIGENCE.md) — the "help me help you" queue: Dustav asks, you answer once, it learns.
   Two-column shell + copilot rail mirror the Inbox tab (#tab-inbox.inbox); answering is a "Review with Dustav"
   conversation now, so a card carries a Review button (seeds the copilot) instead of a type-in box. */
#tab-intelligence.intel { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row; }
/* The card's left rail hues, declared once on the tab (mirroring #tab-todo's three state hues) so the stripe,
   the tab pill and anything else that wants a state's colour read ONE source — that's what stopped the TODO
   tab pills and rows drifting into different colours. Two states here: an open question (accent) and a fact on
   file (the settled green, the SAME token value TODO uses for handled, because "resolved" should not mean two
   different colours on two surfaces). --inb-danger is NOT needed: the ⋯ menu is body-appended and redeclares its own. */
#tab-intelligence { --intel-open: var(--accent); --intel-fact: #0f9d63; }
[data-theme="dark"] #tab-intelligence { --intel-fact: #4ade80; }
/* The card column fills its container (no centering cap), left-aligned with the same 30px gutter as the
   Notes/Workflows library (.lib) and the Inbox list — so the content doesn't shift when you switch tabs. */
/* Intelligence uses the shared .inb-content/.inb-topbar header (matches Inbox exactly); only the card list is bespoke. */
.intel__list { display: flex; flex-direction: column; gap: 8px; padding: 18px 30px 24px; }
/* The card matches the TODO card (Curtis, 2026-08-17): [body: question + who it's about] · [rail: ✉ N · ⋯],
   with the ONE primary verb on its own foot row. Same surface, border, radius and left accent rail as .todo-card;
   the rail geometry and the ✉ pill are the SHARED .card-rail / .card-srcbtn rules above, so the two cards can't
   drift. It stays a flex column — top row, then foot — rather than a grid, because the foot has to sit under the
   rail on a narrow card without a row-span rule to get wrong. */
.intel-card { display: flex; flex-direction: column; gap: 10px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; box-shadow: inset 3px 0 0 var(--intel-open); transition: opacity .3s ease; }
.intel-card__top { display: flex; flex-direction: row; align-items: flex-start; gap: 16px; }
.intel-card__body { flex: 1; min-width: 0; }
/* The question is the title — matched to .todo-card__title so a row reads the same weight on both surfaces. */
.intel-card__q { font-family: var(--font-display); font-weight: 600; font-size: 14.5px; line-height: 1.35; color: var(--ink); }
/* "ABOUT KRISTA MATHER" — a TAG, not a sentence, so it keeps the mono/uppercase treatment rather than adopting
   the TODO card's muted prose line. Different datum, different voice; same muted weight. */
.intel-card__ctx { min-width: 0; margin-right: auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); }
/* THE ONE DIFFERENCE FROM THE TODO CARD, and it's deliberate: Intelligence keeps a visible primary. A to-do is
   mostly closed by Dustav (20 of 23, measured) so its card is for reading; an Intelligence item is here PRECISELY
   because Dustav couldn't resolve it, so the human tap is the only thing that ever clears one. */
/* The "about" tag sits left, the primary right — one row, so a short question stays a short card. flex-end (not
   space-between) + margin-right:auto on the tag keeps the button right-aligned when there's no tag at all. */
.intel-card__foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; }
.intel-card__review { flex: none; display: inline-flex; align-items: center; gap: 7px; background: var(--accent); color: var(--accent-ink); border: none; border-radius: 9px; padding: 8px 14px; font-weight: 700; font-size: 13px; cursor: pointer; }
.intel-card__review:hover { filter: brightness(1.06); }
.intel-card__review svg { width: 15px; height: 15px; }
/* Dismissing slides the card out (the toast carries the Undo) — same beat as the inbox + TODO. */
.intel-card.is-dismissing { opacity: .5; }
/* Mobile — the shared THREE-ROW card grammar (2026-08-24): row 1 = question + ⋯ (pinned), row 2 = the
   "about" tag as the summary (it un-truncates — on a phone it's a real row, not a foot squeeze), row 3 =
   ✉ bottom-left + Review bottom-right. display:contents dissolves the desktop's top/foot wrappers so the
   grid can re-seat their children without touching the markup. */
@media (max-width: 879px) {
  .intel-card { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "q q" "ctx ctx" "rail review"; gap: 8px 12px; }
  .intel-card__top, .intel-card__foot, .intel-card__body { display: contents; }
  .intel-card__q { grid-area: q; padding-right: 52px; }
  .intel-card__ctx { grid-area: ctx; margin-right: 0; white-space: normal; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; padding-right: 52px; }
  .intel-card .card-rail { grid-area: rail; justify-self: start; align-self: end; padding-top: 0; }
  .intel-card__review { grid-area: review; justify-self: end; align-self: end; }
  /* right = the card's own 16px padding, flush with the foot's edge (the inbox rule). */
  .intel-card .inb-kebab { position: absolute; top: 11px; right: 16px; }
}

/* ── The two tabs (Curtis, 2026-08-18) — the Facts KB moved here off the Profile tab ──────────────────────────
   Same chrome as the TODO tabs, one surface over. .intel-tab.is-active must stay AFTER .inb-filter.is-active
   (up at the shared filter rules) or the accent fill wins and every tab renders the same colour. */
.intel-tabs { gap: 8px; }
.intel-tab { display: inline-flex; align-items: center; gap: 7px; }
.intel-tab--questions { --h: var(--intel-open); }
.intel-tab--facts { --h: var(--intel-fact); }
.intel-tab.is-active { background: color-mix(in srgb, var(--h) 14%, transparent); border-color: color-mix(in srgb, var(--h) 45%, var(--border)); color: var(--h); }
.intel-tab__n { font-family: var(--font-mono); font-size: 11px; opacity: .75; }
.intel__pane[hidden] { display: none; }
/* The facts pane reuses .intel__list as a <ul>, so it needs the list reset the flex rule doesn't carry. */
ul.intel__list { list-style: none; margin: 0; }

/* A FACT card — deliberately the same box as .intel-card (one spec, two states), differing only in its left rail
   hue and in which line is strong. The ANSWER is the title and the QUESTION is the muted line beneath, inverting
   the question card: on this pane you're reading what Dustav knows, and the question is provenance. */
.fact-card { display: flex; flex-direction: column; gap: 10px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; box-shadow: inset 3px 0 0 var(--intel-fact); transition: opacity .3s ease; }
.fact-card__text { font-family: var(--font-display); font-weight: 600; font-size: 14.5px; line-height: 1.35; color: var(--ink); }
/* The question it answered — prose, so it takes the TODO card's muted-detail treatment rather than the
   Intelligence card's mono/uppercase TAG treatment (that one is for a name, this one is a sentence).
   Clamped to 2 lines: a couple of the live questions carry a long parenthetical. */
.fact-card__q { margin-top: 5px; font-size: 12.5px; color: var(--muted); line-height: 1.45; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
/* "3d" — when it was learned. Plain muted meta, NOT a bordered pill: the rail already carries two bordered boxes
   (the ✉ pill and the ⋯), and a third would read as a control you can press. */
.fact-card__age { flex: none; font-family: var(--font-mono); font-size: 10px; color: var(--muted); white-space: nowrap; }
.fact-card.is-dismissing { opacity: .5; }

/* ── NOTIFICATIONS — the feed (docs/NOTIFICATIONS.md §9b) ─────────────────────────────────────────────────────
   The SAME card box as .intel-card / .fact-card / .todo-card, deliberately: this is the fifth list surface and
   the shared grammar IS the spec (CLAUDE.md, 2026-08-24) — surface, 1px border, radius 12, the left accent rail,
   the shared .card-rail on the right. The only bespoke parts are a leading class ICON (a feed mixes classes, so
   the glyph is what makes it scannable) and a muted meta line carrying provenance + what actually reached her. */
/* The full-bleed TWO-COLUMN shell, identical to #tab-todo / #tab-intelligence: the list, then the copilot
   rail. (It was a single column on the reasoning that a notification is already Dustav talking — reversed
   2026-08-26: a notification is the START of a question, and every other surface answers in place.) */
#tab-notifications.notif { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row; }
#tab-notifications { --notif-live: var(--accent); --notif-done: var(--muted); }
.notif__list { display: flex; flex-direction: column; gap: 8px; padding: 18px 30px 24px; }
.notif__pane[hidden] { display: none; }
.notif-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; box-shadow: inset 3px 0 0 var(--notif-live); transition: opacity .3s ease; }
/* A resolved row keeps its place in history but stops shouting — the rail goes muted and the whole card
   recedes. Same "it happened, it's done" treatment the handled TODO rows get. */
.notif-card.is-done { box-shadow: inset 3px 0 0 var(--notif-done); opacity: .72; }
/* UNSEEN is a weight, never a second colour: the row is already accented, and a "new" badge on a surface whose
   entire content is new things would be noise. (Seen gates the announce; the badge is the count — §3.) */
.notif-card.is-unseen .notif-card__title { font-weight: 700; }
.notif-card__top { display: flex; flex-direction: row; align-items: flex-start; gap: 12px; }
.notif-card__icon { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; color: var(--muted); }
.notif-card__icon svg { width: 17px; height: 17px; }
.notif-card__body { flex: 1; min-width: 0; }
/* The title IS the link to the thing — the row is a pointer, so the strongest element on it points. */
.notif-card__title { display: block; font-family: var(--font-display); font-weight: 600; font-size: 14.5px; line-height: 1.35; color: var(--ink); text-decoration: none; }
a.notif-card__title:hover { color: var(--accent); }
.notif-card__line { margin: 4px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.45; }
/* The meta row: which class · which pass raised it · what actually left the app. Mono/uppercase, the same
   TAG treatment .intel-card__ctx uses — these are labels, not sentences. */
.notif-card__meta { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 5px; font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); }
.notif-card__reach--note { color: color-mix(in srgb, var(--ink) 55%, var(--muted)); }
.notif-card__when { flex: none; font-family: var(--font-mono); font-size: 10px; color: var(--muted); white-space: nowrap; }
/* A terminal state is a bare glyph beside the time (the mailbox-row grammar, 2026-08-24): words for the act
   you can do, glyphs for the state you're in. */
.notif-card__state { flex: none; font-size: 13px; color: var(--muted); }
.notif-card.is-resolving { opacity: .5; }
/* Mobile — icon + body on the first row, the rail wrapped onto its own line beneath.
   ⚠️ This was a display:contents grid and it BROKE on a real phone (Curtis, 2026-08-26): dissolving both
   wrappers put the icon and the META in the same grid column, so the column sized itself to that long
   uppercase mono string and squeezed the TITLE into a ~70px sliver running off the screen, with the rail
   overlapping it. The lesson is small and general: display:contents re-parents EVERY child, so a two-column
   grid silently starts sizing itself off whichever dissolved child is widest. Flex-wrap does the same job
   here with nothing to get wrong — the body already flexes, so the rail just needs a full-width basis. */
@media (max-width: 879px) {
  .notif-card__top { flex-wrap: wrap; }
  .notif-card .card-rail { flex-basis: 100%; justify-content: space-between; padding-top: 10px; }
  /* The meta can be two lines on a narrow screen; it must never force a horizontal scroll. */
  .notif-card__meta { white-space: normal; }
}

/* ── THE SHARED CARD RAIL (TODO + Intelligence) ───────────────────────────────────────────────────────────────
   The right-hand slot on a card built out of email: [✉ N] [⋯]. Declared ONCE here, not per surface, because the
   whole point of the format match (Curtis, 2026-08-17) is that the two cards can't drift into two designs — the
   same lesson as the source modal and the ⋯ menu being modules rather than copies.
   ⚠️ The pill and the ⋯ are ONE box spec on purpose: same height, radius, border and surface, differing only in
   width (one holds a count, the other a glyph). Splitting them is how they drift apart. */
.card-rail { display: flex; flex-direction: row; align-items: center; gap: 8px; flex: none; padding-top: 1px; }
/* ⭐ ONE KEBAB EVERYWHERE (Curtis, 2026-08-24 — the standardization pass: "use the same kebab style"). The
   card-rail's ⋯ used to shrink to a 24px 7px-radius box — a second kebab spec beside the inbox's. Deviation
   deleted: every ⋯ in the product is the ONE button-group square (--btn-h-md, radius 8 — §the shape grammar
   above). The ✉ pill matches the height so the rail reads as one control row. */
.card-srcbtn { height: var(--btn-h-md); border-radius: 8px; border: 1px solid var(--border); background: var(--surface); }
/* ✉ N — the source-emails count. A light clickable count (not a heavy pill) that reads as meta beside the state
   chip; opens the shared modal listing every source email (public/js/dashboard/source-modal.js). Sized so it's
   still an easy tap target on a phone. */
.card-srcbtn { flex: none; display: inline-flex; align-items: center; gap: 5px; padding: 0 9px; cursor: pointer; font-family: var(--font-mono); font-size: 12px; color: var(--muted); white-space: nowrap; transition: border-color .12s, color .12s, background .12s; }
.card-srcbtn:hover, .card-srcbtn:focus-visible { border-color: var(--accent); color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }

/* TODO (docs/TODO.md) — the operator's running list of owed actions. Two-column shell + copilot mirror
   Intelligence. Inbox-robust since 2026-08-24: search bar + bulk bar + on-card verbs, all the shared
   primitives (.inb-search / .inb-bulkbar / .inb-check / .inb-verb), never local copies. */
#tab-todo.todo { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row; }
/* TODO uses the shared .inb-content/.inb-topbar header (matches Inbox exactly); only the card list is bespoke. */
.todo__list { list-style: none; margin: 0; padding: 18px 30px 24px; display: flex; flex-direction: column; gap: 8px; }
/* ── TODO cards wear the inbox row's FOUR-ZONE grid (2026-08-24): [◯ check] · [body] · [rail] · [verbs] —
      same shape as .inb-row's who/body/top/acts, so the two surfaces' cards read as one system. The summary
      is CSS-clamped to 2 lines so an accreted detail can't wall the card. position:relative anchors the
      mobile ⋯ pin. ── */
.todo-card { position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "check body rail" "check body acts"; align-items: start; gap: 8px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; box-shadow: inset 3px 0 0 var(--todo-now); transition: opacity .3s ease; }
/* A handled card carries no selection circle (bulk acts on open work) — collapse the check column so the
   body starts flush, not one gap in. */
.todo-card.is-handled { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "body rail" "body acts"; }
.todo-card__check { grid-area: check; align-self: start; }
.todo-card.is-selected { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); background: color-mix(in srgb, var(--accent) 5%, var(--surface)); }
.todo-card__body { grid-area: body; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 5px; }
.todo-card__rail { grid-area: rail; justify-self: end; }
/* The verbs — bottom-right, the inbox acts slot. ✓ Done wears the positive green (it's the productive clear,
   not a destruction); Chase/Reopen stay neutral ink. */
.todo-card__acts { grid-area: acts; justify-self: end; align-self: end; display: flex; gap: 8px; }
.todo-verb--done { color: var(--inb-pos, #0f9d63); }
.todo-verb--done:hover, .todo-verb--done:focus-visible { color: var(--inb-pos, #0f9d63); border-color: color-mix(in srgb, var(--inb-pos, #0f9d63) 55%, var(--border)); background: color-mix(in srgb, var(--inb-pos, #0f9d63) 10%, transparent); }
.todo-card__title { font-family: var(--font-display); font-weight: 600; font-size: 14.5px; line-height: 1.35; color: var(--ink); }
/* the summary — the current-state line, hard-clamped to 2 lines as a backstop */
.todo-card__detail { font-size: 12.5px; color: var(--muted); line-height: 1.45; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
/* rail = TWO rows (inbox-like): r1 = [meta: 📧 N · time] ↔ [✓ Done]; r2 = [Review] [Dismiss]. Done sits above
   Dismiss (both right-aligned), the meta above Review (both left-aligned) — a tidy 2×2 block, half the height. */
/* ONE row — meta then the verb — pinned to the TOP beside the ⋯. Stacking it (the inbox's two-row rail) made
   every card as tall as its rail, and a to-do title is usually one line where an inbox row is three; the actual
   defect was only that the rail was vertically CENTRED, so it floated mid-card and lined up with nothing. */
/* (geometry is the shared .card-rail spec above — the TODO card only keeps a hook for its mobile override) */
/* both rows group to the RIGHT so the meta clusters tight against Done (no orphan gap), aligned above Dismiss */

/* The state chip (docs/TODO_SHAPE.md §5.2) — it REPLACES the bare created-time, because what ranks a row is its
   date or who's in the way, never when it was added. Four tones, and only ONE is loud: `late` is the single
   thing that should pull the eye, so the rest stay at meta weight. All theme vars — dark-safe by construction. */
/* ⚠️ --inb-danger is scoped to the mail tab shells, so it does NOT cascade here — the same trap that made
   the body-appended .inb-menu's danger item render un-red. Redefine it on the TODO tab, both themes, rather
   than hardcoding a hex that would be wrong in dark. */
/* THE THREE STATE HUES, declared once (Curtis: match the tabs to the rows). The tab pill and the card's left
   rail both read these, so a tab and its rows can never end up different colours. --inb-danger/--inb-pos are
   scoped to the inbox tabs and do NOT cascade here, hence the redeclaration. */
#tab-todo { --inb-danger: #c0392b; --inb-pos: #0f9d63; --todo-now: var(--accent); --todo-waiting: #8b5cf6; --todo-handled: #0f9d63; }
[data-theme="dark"] #tab-todo { --todo-waiting: #a78bfa; --todo-handled: #4ade80; }
[data-theme="dark"] #tab-todo { --inb-danger: #f87171; --inb-pos: #4ade80; }
.todo-chip { flex: none; font-family: var(--font-mono); font-size: 10px; letter-spacing: .02em; white-space: nowrap; color: var(--muted); }
.todo-chip--due { color: var(--ink); opacity: .75; }
.todo-chip--waiting { color: var(--todo-waiting); }
.todo-chip--late { color: var(--inb-danger, #c0392b); font-weight: 700; }
/* A waiting row is real work, just not TODAY's — so it recedes slightly rather than competing with the
   actionable list above it. Same "quiet but present" treatment the handled inbox rows get. */
.todo-card.is-waiting { box-shadow: inset 3px 0 0 var(--todo-waiting); background: color-mix(in srgb, var(--surface) 92%, var(--bg)); }
/* The overdue count in the topbar meta — the one number worth a colour up there. */
.todo-overdue { margin-left: 10px; color: var(--inb-danger, #c0392b); font-weight: 700; }
/* (the ✉ N pill is the shared .card-srcbtn above — Intelligence renders the identical affordance) */
.todo-card.is-resolving { opacity: .7; }
/* (.todo-card__resolved / __undo are RETIRED with the inline undo state — 2026-08-24: the clear is the
   shared RowFX collapse + an ffToast Undo, one undo grammar across inbox/files/todo.) */
/* Bulk Done wears the same positive green as the card verb (the bar lives inside #tab-todo, so --inb-pos
   cascades — unlike the body-appended ⋯ menu). */
.inb-bulkbar__btn--done { color: var(--inb-pos, #0f9d63); }
.inb-bulkbar__btn--done:hover:not(:disabled) { border-color: var(--inb-pos, #0f9d63); color: var(--inb-pos, #0f9d63); background: color-mix(in srgb, var(--inb-pos, #0f9d63) 8%, transparent); }
/* Mobile — the shared THREE-ROW card grammar (2026-08-24, one spec with inbox/files/intel; breakpoint 879
   like the rest of the app, not a private 620): row 1 = ◯ + title + ⋯, row 2 = the summary (kebab lane
   reserved), row 3 = the state chip + ✉ bottom-LEFT, the verbs bottom-RIGHT, one row. (The chip stays in
   the foot rather than playing the inbox's absolute top-right time: a to-do has no timestamp by design —
   the chip IS its state, which is exactly what the foot's left half is for.) */
@media (max-width: 879px) {
  .todo-card { grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "check body body" "rail rail acts"; row-gap: 8px; }
  .todo-card.is-handled { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "body body" "rail acts"; }
  .todo-card__rail { justify-self: start; align-self: end; flex-wrap: wrap; padding-top: 0; }
  .todo-card__acts { align-self: end; }
  /* right = the card's own 16px padding, flush with the foot verbs' edge (the inbox rule). */
  .todo-card .inb-kebab { position: absolute; top: 11px; right: 16px; }
  .todo-card__title { padding-right: 52px; }
  .todo-card__detail { padding-right: 52px; }
  .todo-verb--done, .todo-verb--chase, .todo-verb--reopen { width: var(--btn-h-md); padding: 0; }
}
/* ── The three tabs (docs/TODO_SHAPE.md §5) ──────────────────────────────────────────────────────────────────
   Each carries its own hue so which one you are in is legible at a glance, per Curtis. --h is the only thing
   that varies; the active fill/border derive from it, so a fourth tab is one line. Inactive tabs stay neutral —
   three coloured pills at once would be a rainbow, which is the thing the house style avoids. */
.todo-tabs { gap: 8px; }
.todo-tab { display: inline-flex; align-items: center; gap: 7px; }
.todo-tab--now { --h: var(--accent); }
.todo-tab--waiting { --h: #8b5cf6; }
.todo-tab--handled { --h: #0f9d63; }
[data-theme="dark"] .todo-tab--waiting { --h: #a78bfa; }
[data-theme="dark"] .todo-tab--handled { --h: #4ade80; }
.todo-tab.is-active { background: color-mix(in srgb, var(--h) 14%, transparent); border-color: color-mix(in srgb, var(--h) 45%, var(--border)); color: var(--h); }
.todo-tab__n { font-family: var(--font-mono); font-size: 11px; opacity: .75; }
.todo__pane[hidden] { display: none; }
/* A handled card recedes — it is history, not work. The accent rail goes green/muted so a wall of them doesn't
   read as urgent, and the title is NOT struck through: the tab already says these are done, and striking every
   line makes the record harder to read than it is worth. */
.todo-card.is-handled { box-shadow: inset 3px 0 0 var(--todo-handled); background: color-mix(in srgb, var(--surface) 94%, var(--bg)); }
.todo-card.is-handled .todo-card__title { color: color-mix(in srgb, var(--ink) 78%, var(--bg)); }
/* Two lines now: the struck title, and UNDER it what actually happened. align-items:start so the ✓ and Undo
   sit level with the title rather than centring against a two-line block. */
/* The resolution is NOT struck through — the title is the thing that's finished; this is the record of how,
   and it should read as live text. Clamped to two lines: Dustav occasionally writes a long one. */
/* .ck-quoted / .ck-quoted__raw are GONE with the "▸ full conversation" plaintext dump (2026-08-23). Not to be
   confused with .ck-quoted-msg above, which is LIVE: the per-message "▸ show quoted" in the plaintext fallback. */
.ck-empty, .ck-nodraft { color: var(--muted); font-size: 13px; }

/* Sent receipt — the composer column shows a "you sent this" panel instead of the reply form. */
.ck-sent-note { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.ck-sent-note p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5; }

/* calendar affordances (§5.5) — review-a-schedule handoff / auto-added + undo, in the conversation column */
.ck-cal { border-radius: 11px; padding: 12px 14px; }
.ck-cal .inb-ml { display: block; }
.ck-cal--review { background: color-mix(in srgb, var(--accent) 6%, var(--surface)); border: 1px solid color-mix(in srgb, var(--accent) 34%, var(--border)); }
.ck-cal--review .inb-ml { color: var(--accent); }
.ck-cal--added { background: color-mix(in srgb, #14b8a6 8%, var(--surface)); border: 1px solid color-mix(in srgb, #14b8a6 34%, var(--border)); }
.ck-cal--added .inb-ml { color: #0d9488; }
.ck-cal__note { margin: 6px 0 10px; font-size: 13px; color: var(--muted); }
.ck-cal ul { margin: 6px 0 10px; padding-left: 18px; }
.ck-cal li { font-size: 13px; color: var(--ink); margin-bottom: 2px; }
/* The date was ALREADY on the calendar (the exact-duplicate guard hit) — a quiet marker, not an error: the
   outcome is right, the block just shouldn't claim it made a fresh event. */
.ck-cal__dup { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); background: color-mix(in srgb, var(--muted) 12%, transparent); border-radius: 5px; padding: 1px 6px; margin-left: 6px; vertical-align: middle; }
.ck-cal-review { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); font-weight: 700; }
.ck-cal-review:hover { filter: brightness(1.06); }
.ck-cal-review:disabled, .ck-cal-undo:disabled { opacity: .55; cursor: default; }
.ck-cal-undo:hover { color: var(--ink); border-color: var(--muted); }

/* the self-healing queue (docs/QUEUE_RESOLUTION.md) — the reversible "handled · superseded · undo" banner
   (muted/receding, it's a done thing) and the quieter "related to X" note (the linked, not-hidden case). */
.ck-superseded { display: flex; align-items: center; gap: 12px; border-radius: 11px; padding: 11px 14px; background: color-mix(in srgb, var(--muted) 8%, var(--surface)); border: 1px solid var(--border); }
.ck-superseded__main { flex: 1; min-width: 0; }
.ck-superseded .inb-ml { display: block; color: var(--muted); }
.ck-superseded__line { margin: 4px 0 0; font-size: 13px; color: var(--ink); }
.ck-superseded__undo:hover { color: var(--ink); border-color: var(--muted); }
.ck-superseded__undo:disabled { opacity: .55; cursor: default; }
/* Passed by the pre-flight relevance gate (docs/RELEVANCE_GATE.md) — the thread banner + "Read it anyway". The
   button is the accent recovery action (it costs a real read), the banner itself understated. */
.ck-passed { display: flex; align-items: center; gap: 12px; border-radius: 11px; padding: 11px 14px; background: color-mix(in srgb, var(--muted) 8%, var(--surface)); border: 1px dashed var(--border); }
.ck-passed__main { flex: 1; min-width: 0; }
.ck-passed .inb-ml { display: block; color: var(--muted); }
.ck-passed__line { margin: 4px 0 0; font-size: 13px; color: var(--ink); }
.ck-passed__read { color: var(--accent); background: transparent; border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.ck-passed__read:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.ck-passed__read:disabled { opacity: .6; cursor: default; }
.ck-related { border-radius: 11px; padding: 10px 14px; background: color-mix(in srgb, var(--muted) 5%, var(--surface)); border: 1px dashed var(--border); }
.ck-related .inb-ml { display: block; color: var(--muted); }
.ck-related ul { margin: 5px 0 0; padding-left: 18px; }
.ck-related li { font-size: 13px; color: var(--ink); margin-bottom: 2px; }

/* the generic "did I miss something?" ripcord — a quiet line handing the email to Dustav in chat */
.ck-ripcord { margin-top: 6px; font-size: 12.5px; color: var(--muted); }
.ck-ripcord__btn { font: inherit; color: var(--accent); background: none; border: none; padding: 0; cursor: pointer; }
.ck-ripcord__btn:hover { text-decoration: underline; }
/* "Add to Todo" (docs/TODO.md) — the background author-or-update route; swaps to the "on your to-do list" link. */
.ck-todo { margin-top: 6px; font-size: 12.5px; }
.ck-todo__on { color: var(--muted); }
.ck-todo__on a { color: var(--accent); }
/* Context notes (docs/CONTEXT_NOTES.md) — pin first-party context to an email; saved instantly, fed to later reads. */
/* No box-in-a-box (Curtis, 2026-08-24: "we don't need the extra border & padding — the card pop up is the
   border"): the DRAWER is the container; the notes block sits flush in its body. */
.ck-notes { margin-top: 0; }
.ck-notes__head { font-family: var(--font-display); font-weight: 600; font-size: 13px; margin-bottom: 9px; }
.ck-notes__list { list-style: none; margin: 0 0 9px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.ck-notes__list:empty { display: none; }
.ck-note { display: flex; align-items: baseline; gap: 8px; font-size: 13px; line-height: 1.4; padding: 7px 9px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; }
.ck-note__text { flex: 1 1 auto; min-width: 0; white-space: pre-wrap; word-break: break-word; }
.ck-note__meta { flex: none; color: var(--muted); font-size: 11px; font-family: var(--font-mono); white-space: nowrap; }
.ck-note__x { flex: none; background: none; border: none; color: var(--muted); cursor: pointer; font-size: 12px; line-height: 1; padding: 2px 3px; }
.ck-note__x:hover { color: var(--error); }
.ck-note__x:disabled { opacity: .5; cursor: default; }
.ck-notes__add { display: flex; gap: 8px; }
.ck-notes__add input { flex: 1 1 auto; min-width: 0; border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; font: inherit; font-size: 13px; background: var(--bg); color: var(--ink); }
.ck-note__save { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); font-weight: 700; }
.ck-note__save:disabled { opacity: .6; cursor: default; }
.ck-notes__hint { margin: 9px 0 0; color: var(--muted); font-size: 11.5px; line-height: 1.4; }

/* attachments on the outgoing email (§12) — chips + a picker label */
.ck-attach { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.ck-attach__list:empty { display: none; }
.ck-attach__list { display: flex; flex-wrap: wrap; gap: 6px; }
.ck-att-chip { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; font-size: 12px; color: var(--ink); background: var(--bg); border: 1px solid var(--border); border-radius: 20px; padding: 4px 6px 4px 10px; }
.ck-att-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 200px; }
.ck-att-x { flex: none; font-size: 11px; line-height: 1; color: var(--muted); background: none; border: none; cursor: pointer; padding: 2px 4px; border-radius: 50%; }
.ck-att-x:hover { color: var(--ink); background: color-mix(in srgb, var(--muted) 18%, transparent); }
.ck-att-x:disabled { opacity: .5; cursor: default; }

.ck-comp { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--surface); }
.ck-comp__peek, .ck-comp__close, .ck-scrim { display: none; }  /* mobile fly-up chrome — hidden on desktop */
.ck-comp__scroll { flex: 1 1 auto; overflow-y: auto; padding: 18px 20px 10px; }
.ck-needs { border: 1px solid color-mix(in srgb, var(--inb-warn) 40%, var(--border)); background: color-mix(in srgb, var(--inb-warn) 8%, var(--surface)); border-radius: 11px; padding: 11px 13px; margin-bottom: 15px; }
.ck-needs .inb-ml { color: var(--inb-warn); }
/* The bullet indent is for the READ-ONLY decision list (the no-draft branch's plain ul) ONLY — unscoped it
   also caught the answer checklist, shoving the questions/inputs 18px right of the header (Curtis 2026-08-24:
   "the padding on the type your answers left should be aligned with the header"). The checklist's own
   .ck-needs__list rule (list-style none, padding 0) owns that variant. */
.ck-needs ul:not(.ck-needs__list) { margin: 6px 0 0; padding-left: 18px; }
.ck-needs ul:not(.ck-needs__list) li { font-size: 12.5px; color: var(--ink); margin-bottom: 3px; }
/* No draft to scalpel → a "Review with Dustav" handoff instead of an inline answer input (reuses the ripcord
   link look; the DECISION is agent work, prepared in chat). */
.ck-needs__review { display: inline-block; margin-top: 9px; font-size: 12.5px; }
.ck-field { display: flex; align-items: baseline; gap: 10px; font-size: 12.5px; padding: 6px 0; border-bottom: 1px solid var(--border); }
.ck-field b { font-family: var(--font-mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); width: 46px; flex: none; }
.ck-field span { color: var(--ink); word-break: break-word; }
/* Reply / Reply-all / Forward switch + editable recipient fields (§12) */
.ck-modes { display: flex; gap: 4px; margin: 4px 0 10px; }
/* PILL — Reply / Reply all / Forward is literally a MODE switch, so it wears the mode shape. */
.ck-mode { background: var(--bg); }
.ck-mode:hover { color: var(--ink); }
.ck-mode.is-on { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.ck-field--edit { align-items: center; }
.ck-field--edit input { flex: 1 1 auto; min-width: 0; background: transparent; border: none; color: var(--ink); font: inherit; font-size: 12.5px; padding: 2px 0; }
.ck-field--edit input:focus { outline: none; }
.ck-field--edit input::placeholder { color: var(--muted); }
/* Contact typeahead (docs/SPINE_TEARDOWN.md §5) — the To/Cc suggestion list, shared by both composers via
   public/js/dashboard/contact-suggest.js. Anchored to the .ck-field row, which is position:relative for it. */
.ck-field--edit { position: relative; }
.ck-suggest { position: absolute; z-index: 30; top: 100%; left: 56px; right: 0; margin: 2px 0 0; padding: 4px; list-style: none; max-height: 232px; overflow-y: auto; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,.14); }
.ck-suggest__opt { display: flex; align-items: baseline; gap: 8px; padding: 7px 9px; border-radius: 7px; cursor: pointer; font-size: 12.5px; }
.ck-suggest__opt:hover, .ck-suggest__opt.is-active { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.ck-suggest__name { color: var(--ink); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ck-suggest__addr { color: var(--muted); font-family: var(--font-mono); font-size: 10.5px; margin-left: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The address-book door (contact-book.js) — a quiet icon button riding INSIDE the To row, after the
   input; the row is already flex so it slots in without new structure. */
.ck-bookbtn { flex: none; display: grid; place-items: center; width: 26px; height: 26px; border: none; background: none; color: var(--muted); cursor: pointer; border-radius: 7px; padding: 0; }
.ck-bookbtn:hover, .ck-bookbtn:focus-visible { color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); outline: none; }
.ck-bookbtn .icon { width: 15px; height: 15px; }
/* The book itself — compact <dialog> on desktop (a quick pick beside the composer; native ::backdrop
   + UA centering), FULL SCREEN on phones (2026-08-28 modal audit: it's a searchable, SCROLLING browse
   surface — 440×72vh on a phone was a keyhole, and "quick pick" was only true of the tap, not the
   finding). */
/* Same [open]-scoped display as .nfm — the address book is JS-built so it never showed pre-open, but
   after the first close it would have STAYED painted (author display beats the UA's closed-dialog
   display:none). */
.cbk { width: 440px; max-width: 92vw; max-height: min(72vh, 640px); background: var(--surface); color: var(--ink); border: 1px solid var(--border); border-radius: 16px; box-shadow: 0 18px 50px rgba(0,0,0,.28); padding: 18px 20px; flex-direction: column; gap: 10px; }
.cbk[open] { display: flex; }
.cbk::backdrop { background: rgba(0,0,0,.45); } /* the ONE wash alpha (modal audit 2026-08-27) */
.cbk__head { display: flex; align-items: center; justify-content: space-between; font-family: var(--font-display); font-size: 16px; font-weight: 700; }
.cbk__close { border: none; background: none; color: var(--muted); cursor: pointer; font-size: 14px; padding: 4px 6px; border-radius: 7px; }
.cbk__close:hover { color: var(--ink); }
.cbk__sub { font-size: 12.5px; line-height: 1.5; margin: 0; }
.cbk__search { font: inherit; font-size: 14px; color: var(--ink); background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 9px 12px; }
.cbk__search:focus { outline: none; border-color: var(--accent); }
.cbk__list { list-style: none; margin: 0; padding: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.cbk__row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px; cursor: pointer; }
.cbk__row:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.cbk__who { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.cbk__name { font-weight: 600; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cbk__addr { font-family: var(--font-mono); font-size: 10.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cbk__meta { flex: none; font-family: var(--font-mono); font-size: 10.5px; color: var(--muted); }
.cbk__empty { padding: 14px 8px; font-size: 13px; }
/* Phone face: the box is the screen; the list takes the leftover height and scrolls (flex:1 —
   the base's max-height scrolling only worked because the box hugged its content). */
@media (max-width: 879px) {
  .cbk { width: 100%; max-width: none; height: 100dvh; max-height: none; margin: 0; border: none; border-radius: 0; box-shadow: none; padding: 18px 18px calc(18px + env(safe-area-inset-bottom, 0px)); }
  .cbk__list { flex: 1; min-height: 0; }
}
/* ── The FOLDER BOOK (folder-book.js — docs/FOLDERS.md 2026-08-28): her real Gmail tree behind the inbox's
   📁, each folder counting what Dustav read & filed there. A SLIDE-OUT PANEL, not a dialog (Curtis, same
   day, on the first centered cut: "what we used to have is a full height panel… this is better and it can
   be toggled on and off. on mobile, folders slide out from the left similar to the main nav — classic
   behaviour for a folder structure"). DESKTOP: full-height, anchored beside the nav rail (left = --rail-w,
   following the collapsed rail), PERSISTENT while she clicks folder to folder — and the inbox RESIZES
   beside it rather than sitting underneath (Curtis, on the live overlay: "we have enough space that the
   inbox should just resize and we see them all"): body.fbk-open shifts #tab-inbox right by the panel's
   width, on the same spring as the slide, so open reads as a dock, not a cover. No scrim, list stays
   interactive; the 📁/✕/Esc toggle it. PHONE: the navdrawer's exact pattern (left sheet min(80vw,340px) +
   scrim, z 60/55 over .mtop's 50, body scroll lock), closing on pick because there it DOES cover the list.
   A plain div on purpose — a <dialog> would modal-block the page. Nesting is an indent off --fbk-depth,
   not a tree control. */
.fbk {
  position: fixed; top: 0; bottom: 0; left: var(--rail-w); z-index: 40;
  width: 300px; background: var(--bg); border-right: 1px solid var(--border);
  box-shadow: 12px 0 28px rgba(0,0,0,.10);
  display: flex; flex-direction: column; gap: 10px;
  padding: 18px 16px;
  transform: translateX(-105%); pointer-events: none; /* -105%: clear its own shadow while parked */
  transition: transform 0.32s cubic-bezier(0.34, 1.15, 0.64, 1); will-change: transform;
}
.fbk.is-open { transform: translateX(0); pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .fbk { transition: none; } }
html.nav-collapsed .fbk { left: 60px; } /* the collapsed rail's width — the panel stays flush beside it */
/* THE DOCK (desktop ≥880): the open panel pushes the inbox right instead of covering it — the same
   0.32s spring as the slide, so the two read as one motion. Only the Inbox hosts the 📁, so only its
   pane needs the shift; the copilot column rides the flex and narrows with the list.
   SERVER-HYDRATED RESTORE (2026-08-28 — the client restore flickered: the page painted full-width,
   then JS shoved it): with the dock cookie open the server stamps `is-docked` on the panel +
   `fbk-docked` on #tab-inbox, so the docked state IS the first paint — no class flip, nothing for the
   transitions to replay. Both classes are inert under 880 (a phone must never load with the drawer
   open); folder-book.js strips them there and ADOPTS them on desktop (body.fbk-open + is-open take
   over for the runtime toggles — same properties, so adoption moves nothing). */
@media (min-width: 880px) {
  #tab-inbox { transition: margin-left 0.32s cubic-bezier(0.34, 1.15, 0.64, 1); }
  @media (prefers-reduced-motion: reduce) { #tab-inbox { transition: none; } }
  body.fbk-open #tab-inbox { margin-left: 300px; } /* = the panel's width */
  #tab-inbox.fbk-docked { margin-left: 300px; }    /* the server-stamped twin (first paint) */
  .fbk.is-docked { transform: none; pointer-events: auto; }
}
/* The rail sits ABOVE the panel (z 41 > 40) so the slide emerges from beneath it — "from beside the left
   panel", not over it. The rail had no stacking context before this; its own popovers are its children and
   ride the 41 with it. */
.rail { z-index: 41; }
.fbk__scrim { display: none; }
.fbk__head { flex: none; display: flex; align-items: center; justify-content: space-between; font-family: var(--font-display); font-size: 16px; font-weight: 700; }
.fbk__close { border: none; background: none; color: var(--muted); cursor: pointer; font-size: 14px; padding: 4px 6px; border-radius: 7px; }
.fbk__close:hover { color: var(--ink); }
.fbk__sub { flex: none; font-size: 12.5px; line-height: 1.5; margin: 0; }
.fbk__body { flex: 1; min-height: 0; overflow-y: auto; }
.fbk__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.fbk__row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; font-family: inherit; font-size: 13px; color: var(--ink); background: none; border: none; cursor: pointer; padding: 8px 10px; padding-left: calc(10px + var(--fbk-depth, 0) * 16px); border-radius: 8px; }
/* Nesting depth comes in as data-depth (server rows AND client rows — clamped 0–6 at both producers),
   mapped to the indent var here. ⚠️ NEVER a style="--fbk-depth" attribute: the app CSP (style-src 'self')
   silently drops inline styles, which left server-painted rows flat until the client re-rendered — the
   late-indent flicker (2026-08-28). */
.fbk__row[data-depth="1"] { --fbk-depth: 1; }
.fbk__row[data-depth="2"] { --fbk-depth: 2; }
.fbk__row[data-depth="3"] { --fbk-depth: 3; }
.fbk__row[data-depth="4"] { --fbk-depth: 4; }
.fbk__row[data-depth="5"] { --fbk-depth: 5; }
.fbk__row[data-depth="6"] { --fbk-depth: 6; }
.fbk__row:hover, .fbk__row:focus-visible { background: color-mix(in srgb, var(--accent) 12%, transparent); outline: none; }
.fbk__row.is-active { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.fbk__row.is-active .fbk__name { color: var(--accent); }
.fbk__name { flex: 1; min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fbk__count { flex: none; font-family: var(--font-mono); font-size: 10.5px; color: var(--muted); }
/* Zero read-&-filed: the folder is real (the tree never lies about her structure), the row just recedes —
   and wears NO count ("zeros here are just noise"; folder-book.js only mints the span for a real number). */
.fbk__row.is-zero .fbk__name { font-weight: 400; color: var(--muted); }
/* The Inbox row — the tree's root and the browse's exit; a hairline sets it off from the tree below. */
.fbk__row--inbox { border-bottom: 1px solid var(--border); border-radius: 8px 8px 0 0; margin-bottom: 4px; }
.fbk__empty { padding: 14px 8px; font-size: 13px; }
@media (max-width: 879px) {
  .fbk { left: 0; width: min(80vw, 340px); z-index: 60; box-shadow: 6px 0 24px rgba(0,0,0,.2); padding-bottom: calc(18px + env(safe-area-inset-bottom, 0px)); }
  .fbk__scrim { display: block; position: fixed; inset: 0; z-index: 55; background: rgba(0,0,0,.5); opacity: 0; pointer-events: none; transition: opacity 0.28s ease; -webkit-tap-highlight-color: transparent; }
  .fbk__scrim.is-visible { opacity: 1; pointer-events: auto; }
  body.fbk-open { overflow: hidden; } /* lock the page scroll behind the open sheet (the navdrawer rule) */
}
.ck-toolbar { display: flex; align-items: center; gap: 2px; margin-top: 14px; padding: 6px; border: 1px solid var(--border); border-bottom: none; border-radius: 10px 10px 0 0; background: var(--bg); }
.ck-tb b { font-weight: 800; } .ck-tb i { font-style: italic; }
.ck-tb .icon { width: 15px; height: 15px; } /* link / paperclip glyphs — a touch larger than the B/I text so they read clearly */
.ck-tb--attach { cursor: pointer; } /* the attach <label> wraps a hidden file input; it inherits the .ck-tb button look */
.ck-tb__sep { width: 1px; height: 16px; background: var(--border); margin: 0 4px; }
.ck-tb__saved { margin-left: auto; padding-right: 4px; font-family: var(--font-mono); font-size: 10px; letter-spacing: .04em; color: var(--muted); }
.ck-editor { border: 1px solid var(--border); border-radius: 0 0 10px 10px; padding: 13px 14px; font: 14px/1.6 var(--font-body); color: var(--ink); min-height: 130px; background: var(--bg); }
.ck-editor:focus { outline: none; border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.ck-editor:empty::before { content: attr(data-placeholder); color: var(--muted); pointer-events: none; } /* contenteditable has no native placeholder */
/* Empty-state help row: the ✨ Draft a reply button + the "tell Dustav what to say" scalpel box, side by side. */
.ck-compose-actions { margin-top: 10px; display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
.ck-compose-actions .ck-instruct { flex: 1 1 auto; margin-top: 0; }
.ck-draftit { color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.ck-draftit:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
/* The little Dustav leading the compose-actions ROW (not the Draft Reply button — that hides once a draft
   exists, and he must survive it): the summary-peek treatment (17px so the eyes read; eyes only, on the
   shared loop — this host is in the one .blink__eye rule and doesn't restate it; the global
   reduced-motion kill covers it). Accent — no sender ink on a composer act. */
/* The composer's Dustav is the canonical partial (2026-08-29 — the footer's .ck-panels__mark twin): the
   .blink base rules own its 20px box + accent self-colour; the wrapper only seats it in the flex row. */
.ck-compose-mark { display: inline-flex; flex: none; align-items: center; }
.ck-draftit:disabled { opacity: .7; cursor: default; }
.ck-draftit[hidden] { display: none; } /* the display:inline-flex above would otherwise beat the [hidden] UA rule */
.ck-editor a { color: var(--accent); }
.ck-editor p { margin: 0 0 11px; } .ck-editor p:last-child { margin-bottom: 0; }
.ck-brack { color: var(--inb-warn); background: color-mix(in srgb, var(--inb-warn) 14%, transparent); border-radius: 4px; padding: 0 3px; }
.ck-instruct { margin-top: 10px; display: flex; align-items: center; gap: 8px; }
.ck-instruct__ic { color: var(--muted); font-size: 12px; }
.ck-instruct input { flex: 1 1 auto; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 7px 10px; color: var(--ink); font: inherit; font-size: 12.5px; }
.ck-instruct input::placeholder { color: var(--muted); }
.ck-instruct input:disabled { opacity: .7; }
/* edit-by-instruction (§12.4): the scalpel spinner, the changed-block highlight, the undo, the checklist. */
.ck-instruct__spin { flex: none; width: 13px; height: 13px; border: 2px solid color-mix(in srgb, var(--accent) 30%, var(--border)); border-top-color: var(--accent); border-radius: 50%; animation: ck-spin .7s linear infinite; }
@keyframes ck-spin { to { transform: rotate(360deg); } }
.ck-editor.is-rewriting { opacity: .6; pointer-events: none; }
.ck-editor .ck-edited { background: color-mix(in srgb, var(--accent) 16%, transparent); border-radius: 4px; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); transition: background 1.2s ease, box-shadow 1.2s ease; }
.ck-undo { margin-top: 8px; }
.ck-undo:hover { color: var(--ink); border-color: var(--muted); }
.ck-needs__hint { margin: 5px 0 0; font-size: 11.5px; color: color-mix(in srgb, var(--inb-warn) 55%, var(--muted)); }
.ck-needs__list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.ck-need { display: flex; flex-direction: column; gap: 4px; transition: opacity .3s ease, transform .3s ease; }
.ck-need.is-done { opacity: 0; transform: translateX(8px); }
.ck-need__q { font-size: 12.5px; color: var(--ink); }
.ck-need__row { display: flex; gap: 6px; align-items: stretch; }
.ck-need__a { flex: 1; min-width: 0; background: var(--bg); border: 1px solid color-mix(in srgb, var(--inb-warn) 30%, var(--border)); border-radius: 7px; padding: 6px 9px; color: var(--ink); font: inherit; font-size: 12.5px; }
.ck-need__a::placeholder { color: var(--muted); }
.ck-need__a:focus { outline: none; border-color: var(--accent); }
.ck-need__a:disabled { opacity: .6; }
/* was `font: inherit` — a button whose type changed with its surroundings. Warn-filled: it's the amber
   "answer this and Dustav rewrites the draft" commit, deliberately not the accent send. */
.ck-need__go { border-color: var(--inb-warn); background: var(--inb-warn); color: #fff; font-weight: 700; }
.ck-need__go:hover { filter: brightness(1.06); }
.ck-need__go:active { filter: brightness(.95); }
.ck-need__go:disabled { opacity: .55; cursor: default; }
[data-theme="dark"] .ck-need__go { color: #1a1204; }
/* flex-wrap: at a narrow width (Bre's desktop-app window, 2026-08-24) the no-wrap row shoved Send OFF-SCREEN
   with no way to scroll to it — Copy/Discard wrap onto their own line and the label chip + Send keep the
   bottom-right, so Send is on screen at every width. */
.ck-comp__foot { flex: none; border-top: 1px solid var(--border); padding: 12px 20px calc(12px + var(--dock-inset, 0px)); display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; background: var(--surface); }
/* QUIET, deliberately (Curtis, 2026-08-27: "we need more elegant copy draft and discard draft buttons — they
   are too bulky"): Copy/Discard are housekeeping beside the one act that matters (Send), so they wear the BARE
   treatment — the .ck-tb grammar, where the box exists for the target, not for the look. md height, muted ink,
   no box at rest; hover draws the quiet box back so they still read as buttons under the cursor. */
.ck-btn-ghost { height: var(--btn-h-md); padding: 0 10px; font-size: 12px; color: var(--muted); background: none; border-color: transparent; }
.ck-btn-ghost:hover { color: var(--ink); background: var(--surface); border-color: var(--border); }
/* The icon-only ghosts (Copy/Discard draft, 2026-08-28) — square to the height token, glyph sized here
   (the .icon class carries no global size). */
.ck-btn-ico { width: var(--btn-h-md); padding: 0; display: inline-flex; align-items: center; justify-content: center; }
.ck-btn-ico .icon { width: 15px; height: 15px; }
/* One height for the label chip + Send — they carry different fonts (display 12.5/13px, mono 11.5px) so
   padding alone left Send taller; pin the height + center so they match. (Copy/Discard dropped to the quiet
   md treatment above — flex align-items centers the mixed heights on one baseline.) */
.ck-comp__foot .ck-label__pick { height: var(--btn-h-lg); padding-top: 0; padding-bottom: 0; box-sizing: border-box; display: inline-flex; align-items: center; }
/* Send is the loudest thing on the surface, and it gets there with SIZE (lg) + FILL (accent) — not with a
   bespoke radius. A one-off radius on the primary button is how the other seven radii got here. */
.ck-btn-send { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); font-weight: 700; margin-left: auto; padding: 0 18px; }
.ck-btn-send:hover { filter: brightness(1.06); }

/* ── The Drafts/Sent tabs (docs/DRAFTS_SENT.md; ex-Outbox) — mirror the Inbox: a compact clickable queue (reusing the
   .inbox / .inb-* shell + rows) that opens each prepared email into the cockpit below. ── */
/* The prepared-email SCREEN now COPIES the Inbox thread cockpit (.cockpit two-column): the reconcile "active
   surface" on the left, the compose composer (.ck-comp — reused wholesale) on the right. So it needs no layout
   overrides; only the small bits below. */
.ck-btn-discard:hover { border-color: var(--error); color: var(--error); }
.ob-status { font-size: 12px; color: var(--muted); }
.ob-status.is-error { color: var(--error); }
/* Attachments carried from the source (an intern-prepared forward's own files) — shown but not removable, they
   ARE the send. Your own uploads sit below in the removable .ck-attach__list. */
.ck-attach__carried .inb-ml { display: block; margin-bottom: 6px; }
.ck-attach__carried .ck-attach__list { margin-bottom: 4px; }
.ck-att-chip--fixed { opacity: .85; }
.ck-btn-send:disabled { opacity: 0.6; cursor: default; filter: none; }
/* "Sent ✓" state — replaces the Send button once the human has sent the reply. */
.ck-sent { margin-left: auto; font-family: var(--font-display); font-weight: 700; font-size: 13px; color: var(--ok, #1a8f4c); display: inline-flex; align-items: center; gap: 6px; }

@media (max-width: 879px) {
  .cockpit { flex-direction: column; padding: var(--mtop-h) 0 0; }
  .ck-grid { display: block; }
  /* Fill the grid rather than computing a viewport height: the same column now renders BOTH as a page (under
     the fixed top bar) and inside the modal (under the modal's own bar), and a hardcoded 100dvh-minus-chrome
     was only ever right for the first one. padding-bottom clears the composer's fixed 60px peek so the
     instrument foot lands just above it instead of underneath it — PLUS the dock inset, which grew the
     peek's closed window (without it the Reply bar covered the foot's Dustav pills — Curtis, 2026-08-27). */
  .ck-convo { border-right: none; height: 100%; min-height: 0; box-sizing: border-box; padding-bottom: calc(60px + var(--dock-inset, 0px)); }
  .ck-head { padding: 12px 16px; }
  /* 84px of bottom padding used to hold the scroll clear of the peek; the foot does that now.
     ⚠️ --fill has to be restated here: it's a single class, so it has the SAME specificity as .ck-scroll and
     this rule is later in the file — the desktop "edge to edge" would silently lose on every phone. */
  .ck-scroll { padding: 16px 16px 18px; }
  .ck-scroll--fill { padding: 0; }
  .ck-foot { padding: 7px 12px; gap: 8px; } /* same 12px as the message head, so the two line up on a phone too */
  .ck-drawer__body { padding: 12px 16px 14px; }
  /* Short labels so all four fit — a clipped row of controls reads as broken, not scrollable. */
  /* All four drop their word TOGETHER and become the same square — the modal bar's rule, one surface over.
     (They used to shorten to different-length text, which left the row half-labelled at 390px.) */
  .ck-panel-btn { width: var(--btn-h-md); padding: 0; }
  .ck-panel-btn__long { display: none; }

  /* the composer flies up like the copilot: closed = a 54px "Reply" peek bar; open = a ~80% sheet.
     Full-width, bottom-fixed (docs/NAV.md) — the icon rail runs behind it, like the copilot drawer. */
  .ck-comp {
    position: fixed; left: 0; right: 0; z-index: 45;
    /* Physical-bottom anchor + a --dock-inset-taller closed window (see the token's comment) — the
       accent Reply bar fills the curve instead of floating above a void. */
    bottom: var(--tabbar-h);
    top: calc(100dvh - var(--tabbar-h) - 60px - var(--dock-inset, 0px));
    background: var(--surface); border-top: 1px solid var(--border);
    border-radius: 15px 15px 0 0; box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.22);
    overflow: hidden; transition: top .32s cubic-bezier(0.34, 1.15, 0.64, 1);
  }
  .ck-comp.is-open { top: 20dvh; }
  @media (prefers-reduced-motion: reduce) { .ck-comp { transition: none; } }
  .ck-comp__peek { display: flex; position: relative; align-items: center; gap: 9px; width: 100%; padding: 15px 16px 13px; border: none; background: var(--surface); color: var(--ink); font-family: var(--font-display); font-weight: 600; font-size: 14.5px; cursor: pointer; }
  .ck-comp__peek::before { content: ""; position: absolute; top: 6px; left: 50%; transform: translateX(-50%); width: 34px; height: 4px; border-radius: 999px; background: var(--border); }
  .ck-comp__peek-sub { color: var(--muted); font-weight: 400; font-size: 12.5px; }
  .ck-comp__peek-chev { margin-left: auto; color: var(--accent); display: inline-flex; }
  .ck-comp__peek-chev svg { width: 15px; height: 15px; transform: rotate(180deg); }  /* chevron-down → points up (expand) */
  .ck-comp.is-open .ck-comp__peek-chev { display: none; }
  .ck-comp.is-open .ck-comp__peek { border-bottom: 1px solid var(--border); }
  /* CLOSED state — the peek reads as a PRIMARY ACTION bar (Bre: "make the Reply area more noticeable… it took me
     a while to find it; it should look like an action"). The surface-colored bar blended into the sheet above it;
     an accent fill + white label makes it an unmistakable tap target. Reverts to the neutral sheet header on open. */
  .ck-comp:not(.is-open) { border-top-color: transparent; }
  .ck-comp:not(.is-open) .ck-comp__peek { background: var(--accent); color: var(--accent-ink); font-weight: 700; font-size: 15.5px; letter-spacing: .2px; padding: 17px 18px calc(16px + var(--dock-inset, 0px)); }
  .ck-comp:not(.is-open) .ck-comp__peek::before { background: color-mix(in srgb, var(--accent-ink) 50%, transparent); }
  .ck-comp:not(.is-open) .ck-comp__peek-sub { color: color-mix(in srgb, var(--accent-ink) 82%, transparent); font-weight: 500; }
  .ck-comp:not(.is-open) .ck-comp__peek-chev { color: var(--accent-ink); opacity: .9; }
  .ck-comp:not(.is-open) .ck-comp__peek:active { background: color-mix(in srgb, var(--accent) 88%, #000); }
  .ck-comp__close { display: none; position: absolute; top: 11px; right: 13px; z-index: 3; width: 30px; height: 30px; border-radius: 8px; background: none; border: none; color: var(--muted); cursor: pointer; }
  .ck-comp__close svg { width: 18px; height: 18px; }
  .ck-comp.is-open .ck-comp__close { display: grid; place-items: center; }
  .ck-comp__scroll, .ck-comp__foot { display: none; }
  .ck-comp.is-open .ck-comp__scroll { display: block; }
  .ck-comp.is-open .ck-comp__foot { display: flex; }
  .ck-scrim { display: block; position: fixed; left: 0; right: 0; top: 0; bottom: var(--tabbar-h); z-index: 44; background: rgba(0, 0, 0, 0.42); opacity: 0; pointer-events: none; transition: opacity .3s; }
  .ck-scrim.is-visible { opacity: 1; pointer-events: auto; }
}
/* The ROUTING PILL on the thread cockpit header (inbox-thread.hbs) — the reader's `enrichment.action`
   category for the email. This is the only place `action` reaches a screen; the queue rows lead with the
   ACTIONABILITY badge (.inb-status--*) instead. The rest of this block used to style inboxView.cardHtml, the
   pre-2026-08-10 blown-out card that rowShell/rowHtml replaced — deleted with it. */
.inb-action { font-family: var(--font-mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; padding: 2px 8px; border-radius: 20px; background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }
.inb-action--shipment, .inb-action--order, .inb-action--payment { background: color-mix(in srgb, #14b8a6 16%, transparent); color: #0d9488; }
.inb-action--booking { background: color-mix(in srgb, #6366f1 16%, transparent); color: #4f46e5; }
.inb-action--document { background: color-mix(in srgb, #0ea5e9 16%, transparent); color: #0284c7; }
.inb-action--bill { background: color-mix(in srgb, var(--inb-warn) 20%, transparent); color: var(--inb-warn); }
.inb-action--fyi { background: color-mix(in srgb, var(--muted) 14%, transparent); color: var(--muted); }
.inb-action--noise { background: transparent; color: var(--muted); opacity: .7; }
.inb-urgent { font-family: var(--font-mono); font-size: 9px; text-transform: uppercase; letter-spacing: .06em; color: var(--accent-ink); background: var(--inb-warn); border-radius: 5px; padding: 1px 6px; margin-right: 7px; vertical-align: middle; }
@media (max-width: 879px) {
  #tab-inbox.inbox, #tab-drafts.drafts, #tab-sent.sent { flex-direction: column; padding: var(--mtop-h) 0 0; }
  .inb-topbar { padding: 16px 16px 0; } .inb-filters { padding: 12px 16px 0; }
  .inb-list { padding: 14px 16px; }
  .inb-list:last-child { padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--copilot-peek) + 16px); }
  .inb-subject { max-width: 60vw; }
  #tab-intelligence.intel { flex-direction: column; padding: var(--mtop-h) 0 0; }
  /* TODO shares the .inb-content/.inb-topbar header, so it needs the same clearance for the FIXED top bar —
     without it the tab title sits underneath the bar and is simply unreadable (reported on the deployed app,
     2026-08-17). Every full-bleed shell needs this and it is per-tab-id, which is exactly why it gets forgotten;
     verify:mobile-chrome now fails the build when a tab is added without it. */
  #tab-todo.todo { flex-direction: column; padding: var(--mtop-h) 0 0; }
  /* Notifications is a full-bleed shell on the same .inb-content header, so it needs the same clearance —
     verify:mobile-chrome caught this one the day it was added, which is exactly what it's for. */
  #tab-notifications.notif { flex-direction: column; padding: var(--mtop-h) 0 0; }
  /* The card lists must CLEAR the fixed "Ask Dustav" copilot peek at the bottom, like .inb-list does above —
     without this the last card sits underneath the fly-up bar (reported on the deployed app, 2026-08-21).
     NB: this rule used to target .intel-content, a selector the tabs rework REMOVED from the markup — it
     matched nothing, which is exactly how both tabs lost their clearance silently. The lists (not the panes)
     carry it because a hidden pane is display:none and the visible list is what meets the bar. */
  .todo__list, .intel__list { padding: 18px 16px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--copilot-peek) + 16px); }
  /* The foot keeps its one row on a phone — the primary stays a FIXED width so every card's button lines up on
     the same right edge. Letting it flex to fill made each button start at a different x (the "about" tag varies
     in length), which reads as ragged; the tag ellipsises instead. The rail (✉ N · ⋯) stays pinned top-right
     beside the question — two small controls, and dropping them below would push the card taller for nothing.
     So there is deliberately NO mobile override here: the desktop foot rule already does the right thing. */
}

.cal-head { padding: 24px 24px 0; margin: 0 0 14px; }
.cal-body { position: relative; flex: 1; min-height: 0; overflow-y: auto; padding: 0 24px 80px; } /* relative: the Day grid scrolls itself into view via offsetTop; big bottom pad clears the floating Today FAB */
.cal-view { min-width: 0; }
/* Search = a matching single pill on the same surface as the nav group + switcher. */
.cal-iconbtn.btn { background: var(--surface); padding: 8px 12px; }
.cal-iconbtn.btn:hover { background: var(--panel); }

/* ── Schedule card (week/day columns) ─────────────────────────────────────────── */
.cal-card { display: flex; gap: 12px; align-items: baseline; width: 100%; text-align: left; font-family: inherit; background: none; border: none; cursor: pointer; padding: 6px 8px; border-radius: 8px; color: var(--ink); }
/* A distinct accent-tinted hover so a card stands out on ANY row background — including the
   tinted today-row (the plain page-bg hover used to vanish into the row's own hover). */
.cal-card:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.cal-card.is-hidden { display: none; }
.cal-card__when { flex: none; width: 62px; font-size: 13px; color: var(--muted); }
.cal-card[class*="cat--"] .cal-card__when::before { content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--cat); margin-right: 7px; vertical-align: middle; }
.cal-card__title { font-size: 14px; min-width: 0; line-height: 1.4; }
/* Inside a cal-list (the selected-day panel), a card takes the SAME chrome as a Month-agenda row:
   a solid panel row, separated by the container's 1px --border gaps, with the roomier list padding —
   so the "day selected" list and the "all of month" list look identical. (The all-day strip's cards
   aren't in a cal-list, so they keep the compact schedule-card look.) */
.cal-list .cal-card { background: var(--panel); border-radius: 0; padding: 11px 14px; gap: 16px; }
.cal-list .cal-card:hover { background: var(--bg); }

/* ── Day: a REAL hour-grid timeline (iCal's day view) ──────────────────────────
   A 24-hour ruler; each event is an absolutely-positioned block whose HEIGHT = its
   duration, overlapping events split into side-by-side lanes, a red "now" line. The
   block top/height (px = time/duration) + left/width (% = lane) are set inline by
   calendar.js; this just styles the ruler + the blocks. */
.cal-daygrid { display: flex; flex-direction: column; padding: 12px 0 28px; } /* breathing room at the top (clears the filter bar) + bottom of the scroll */
.cal-daygrid__allday { display: flex; flex-direction: column; gap: 2px; padding: 2px 0 10px; border-bottom: 1px solid var(--border); margin-bottom: 6px; } /* all-day events pinned on top, off the ruler */
.cal-hours { position: relative; padding-left: 58px; } /* relative box: blocks position against it; gutter holds hour labels */
.cal-hourrow { position: relative; border-top: 1px solid var(--border); }
.cal-hourrow:first-child { border-top: none; }
/* The hour label lives in the GUTTER, LEFT of where the line starts (the row's content begins at
   x=58 due to the parent's padding, so left:-58 places the label at x=0; the 8px short of the gutter
   keeps it clear of the line). top:-7 vertically straddles the line. */
.cal-hourrow__label { position: absolute; left: -58px; top: -7px; width: 50px; text-align: right; font-size: 11px; color: var(--muted); }
.cal-blocks { position: absolute; top: 0; left: 58px; right: 6px; bottom: 0; } /* the event layer, inset past the gutter (matches cal-hours padding) */
.cal-block { position: absolute; overflow: hidden; display: flex; flex-direction: column; gap: 1px; text-align: left; font-family: inherit; cursor: pointer; border: none; border-left: 3px solid var(--cat, var(--accent)); border-radius: 5px; padding: 2px 6px; background: color-mix(in srgb, var(--cat, var(--accent)) 18%, var(--panel)); color: var(--ink); }
/* GCal-mirror BUSY BLOCKS (docs/GCAL_MIRROR.md §11, re-cut 2026-08-25): her ticked calendars'
   events — read-only but ATTRIBUTED and alive. The source calendar's name is the block's category
   (the cat--hue tint flows), the DASHED edge / hollow dot is the read-only signal, and a click opens
   the read-only detail (they were disabled before — an inert block read as broken). One family
   across the card/dot/hour-block/list faces. */
.cal-card--busy, .cal-list__row--busy { cursor: pointer; border-left: 3px dashed var(--cat, var(--muted)); }
.cal-dot--busy { background: transparent; border: 1px solid var(--cat, var(--muted)); width: 6px; height: 6px; }
.cal-block--busy { cursor: pointer; border-left-style: dashed; border-left-color: var(--cat, var(--muted)); background: color-mix(in srgb, var(--cat, var(--muted)) 12%, var(--panel)); }
/* The acceptance queue's GHOST face (docs/GCAL_MIRROR.md §6d — "found, tap to book"; re-cut
   2026-08-25 on Curtis's live read: NO dashes — the card grammar governs). Staged-not-booked reads
   as a SOLID accent left rail + a light accent tint: distinct from a real event (category dot, no
   rail) and from the mirror's read-only face (the DASHED rail stays hers). */
/* ⭐ DECLINED — the Google face (2026-08-31, Curtis: "I had already declined and the event is still
   there… perhaps a strike through as well? this is what google does"). Google KEEPS a declined
   invitation on the calendar and renders it struck through, and keeping it is right for us too: it's
   her store, and removing it would be a second write she never asked for — the decline is the act
   she consented to. So the fix is the FACE, not the data: it recedes and reads as "not going",
   instead of occupying her week looking exactly like a meeting she's attending.
   A state, not a kind — it composes with the busy/ghost faces rather than replacing one. */
.cal-card.is-declined, .cal-list__row.is-declined, .cal-block.is-declined { opacity: .55; }
.cal-card.is-declined .cal-card__title, .cal-list__row.is-declined .cal-list__title,
.cal-block.is-declined .cal-block__title { text-decoration: line-through; }
.cal-dot.is-declined { opacity: .4; }
.cal-card--ghost, .cal-list__row--ghost { cursor: pointer; border-left: 3px solid var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--panel)); }
.cal-dot--ghost { background: transparent; border: 1.5px solid var(--accent); width: 6px; height: 6px; }
.cal-block--ghost { cursor: pointer; border-left: 3px solid var(--accent); background: color-mix(in srgb, var(--accent) 10%, var(--panel)); }
.cal-prop__acts { display: flex; gap: 8px; margin-top: 14px; }
/* The FOUND modal (§6d's notification mechanic): open proposals reviewed in one place on arrival.
   Rows are CARDS (panel · border · radius-12, the one card grammar) wearing the ghost's solid
   accent rail so the modal and the grid read as the same objects. The title's mark is the living
   Blink (the brand partial), never a glyph stand-in. */
.cal-found__list { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 10px; }
.cal-found__row { display: flex; align-items: center; gap: 12px; background: var(--panel); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 12px; padding: 12px 14px; }
.cal-found__main { flex: 1; min-width: 0; display: grid; gap: 2px; }
.cal-found__title { font-weight: 600; }
.cal-found__when { font-size: 13px; }
.cal-found__note { font-size: 12px; }
/* ⭐ THE LOCATION CHOICE on an invite row (docs/MEETING_INVITES.md §4: the chip carries both options,
   the tap decides). PILLS, because this is a mode you're in, not an act you do (the house grammar) —
   and quieter than the row's own buttons, since the decision she came here to make is still Book vs
   Not-this; where it happens is an adjustment on the way. Wraps on a phone; the place pill may carry
   a real address, so it truncates rather than pushing the actions off the row. */
.cal-found__where { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.cal-where__opt { font: inherit; font-size: 12px; line-height: 1; padding: 5px 10px; border-radius: 999px; border: 1px solid var(--border); background: transparent; color: var(--muted); cursor: pointer; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-where__opt:hover { border-color: var(--accent); color: var(--text); }
.cal-where__opt.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--text); font-weight: 600; }
/* The row's evidence link — where this date came from. Quiet by design (the decision is Book/Skip; the
   source is there for the "wait, which email?" moment), one line, never louder than the note. */
.cal-found__srclink { font-size: 12px; color: var(--muted); text-decoration: none; justify-self: start; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-found__srclink:hover { color: var(--accent); text-decoration: underline; }
.cal-found__acts { display: flex; gap: 8px; flex: none; flex-wrap: wrap; justify-content: flex-end; }
.cal-found__mark { display: inline-block; width: 21px; height: 21px; margin-right: 8px; vertical-align: -4px; }
.cal-found__mark--sm { width: 15px; height: 15px; margin-right: 5px; vertical-align: -3px; }
.cal-found__mark .brand-mark-svg { display: block; width: 100%; height: 100%; }
/* The read-only detail dialog — the compact .cal-modal chrome, prose body (no form). */
.cal-busy__inner { padding: 16px 18px 18px; }

/* (.cal-modal--full lives BELOW, after .cal-modal's own width/border rules — it has to beat them on
   source order, and putting it up here is exactly why the first attempt rendered as a 720px column
   instead of a full screen.) */
.cal-busy__when { margin: 8px 0 0; font-weight: 600; }
.cal-busy__loc { margin: 4px 0 0; }
.cal-busy__src { margin: 12px 0 0; font-size: 13px; }
.cal-busy__cal { display: inline-block; padding: 1px 8px; border-radius: 999px; font-weight: 600; border: 1px dashed var(--cat, var(--muted)); color: var(--ink); background: color-mix(in srgb, var(--cat, var(--muted)) 14%, var(--panel)); }
.cal-block:hover { background: color-mix(in srgb, var(--cat, var(--accent)) 30%, var(--panel)); }
.cal-block.is-hidden { display: none; }
.cal-block__time { font-size: 10px; color: var(--muted); white-space: nowrap; }
.cal-block__title { font-size: 12px; font-weight: 600; line-height: 1.2; }
.cal-now { position: absolute; left: 0; right: 0; height: 0; border-top: 2px solid var(--error); z-index: 2; }
.cal-now::before { content: ""; position: absolute; left: -4px; top: -4px; width: 7px; height: 7px; border-radius: 50%; background: var(--error); }

/* ── Year navigator: 12 mini-months, a dot on any day that has an event ────────── */
.cal-year { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
/* The WHOLE month is one clickable card → drills into Month (never a day). Days are inert. */
.cal-yr { display: block; width: 100%; text-align: left; font-family: inherit; cursor: pointer; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; color: var(--ink); transition: border-color 0.12s, background 0.12s; }
.cal-yr:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--panel)); }
.cal-yr__name { display: block; font-weight: 600; font-size: 14px; color: var(--ink); padding: 0 0 6px; }
.cal-yr:hover .cal-yr__name { color: var(--accent); }
.cal-yr__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.cal-yr__dow { text-align: center; font-size: 9px; color: var(--muted); padding: 2px 0; }
.cal-yr__day { font-size: 11px; color: var(--ink); aspect-ratio: 1; display: grid; place-items: center; border-radius: 50%; position: relative; }
.cal-yr__day--out { visibility: hidden; }
.cal-yr__day--today { background: var(--accent); color: #fff; }
.cal-yr__day--has::after { content: ""; position: absolute; bottom: 1px; left: 50%; transform: translateX(-50%); width: 4px; height: 4px; border-radius: 50%; background: var(--cat, var(--accent)); }
.cal-yr__day--today.cal-yr__day--has::after { background: #fff; }

.cal-searchmodal::backdrop { background: rgba(0,0,0,0.45); }

/* ── Search modal (the search button / "/") — a FIXED-height palette (cmd-K style) so it doesn't
   grow/shrink as you type; the results scroll inside a constant box. ───────────────────────────── */
.cal-searchmodal { width: min(560px, calc(100vw - 32px)); height: min(520px, calc(100dvh - 80px)); border: 1px solid var(--border); border-radius: 16px; padding: 0; background: var(--surface); color: var(--ink); box-shadow: 0 18px 50px rgba(0,0,0,0.28); }
.cal-searchmodal__inner { height: 100%; box-sizing: border-box; padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 10px; }
.cal-searchmodal__bar { display: flex; align-items: center; gap: 10px; flex: none; }
.cal-searchmodal__icon { display: inline-flex; color: var(--muted); flex: none; }
.cal-searchmodal__icon svg { width: 18px; height: 18px; }
.cal-searchmodal__bar input { flex: 1; min-width: 0; font-family: inherit; font-size: 16px; color: var(--ink); background: var(--bg); border: 1px solid var(--border); border-radius: 9px; padding: 9px 11px; }
.cal-searchmodal__bar input:focus { outline: none; border-color: var(--accent); }
.cal-searchmodal__empty { padding: 6px 4px; font-size: 13px; flex: none; }
.cal-searchmodal__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto; }
.cal-searchmodal__btn { display: flex; flex-direction: column; gap: 1px; width: 100%; text-align: left; font-family: inherit; background: none; border: none; border-bottom: 1px solid var(--border); cursor: pointer; padding: 9px 4px; color: var(--ink); border-radius: 6px; }
.cal-searchmodal__btn:hover { background: var(--bg); }
.cal-searchmodal__when { font-size: 12px; color: var(--muted); }
.cal-searchmodal__btn[class*="cat--"] .cal-searchmodal__when::before { content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--cat); margin-right: 7px; vertical-align: middle; }
.cal-searchmodal__title { font-size: 14px; }

/* Edit/create modal (native <dialog>). */
/* The styled confirm (public/js/confirm.js — window.ffConfirm): the quick-confirm rung of the per-device
   modal ladder, compact on BOTH form factors (small is correct — don't inflate). The compact dress
   (r16 · the 18/50 shadow · the one 45% wash). No bare display on the dialog class (the closed-dialog
   pin in verify:mobile-chrome). */
.ffc { width: min(420px, calc(100vw - 32px)); border: 1px solid var(--border); border-radius: 16px; padding: 0; background: var(--surface); color: var(--ink); box-shadow: 0 18px 50px rgba(0,0,0,0.28); }
.ffc::backdrop { background: rgba(0,0,0,0.45); }
.ffc__box { padding: 20px 22px 18px; display: flex; flex-direction: column; gap: 10px; }
.ffc__title { margin: 0; font-size: 16px; font-weight: 700; letter-spacing: -0.01em; }
.ffc__body { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5; }
.ffc__row { display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; }
/* .ffc__btn's BOX comes from the button primitive's registry above (mono · uppercase · 8px radius ·
   --btn-h-lg, the confirm-foot scale the fp-modal confirms use) — a new button joins the registry, it
   doesn't mint a dress (Curtis's own audit question caught the first draft of this doing exactly that).
   Only the VARIANT colors live here. */
.ffc__btn:hover { color: var(--ink); border-color: color-mix(in srgb, var(--ink) 30%, var(--border)); }
.ffc__btn--go { background: var(--accent); border-color: var(--accent); color: #fff; }
.ffc__btn--go:hover { color: #fff; filter: brightness(1.06); }
.ffc--danger .ffc__btn--go { background: var(--error); border-color: var(--error); }

.cal-modal { width: min(480px, calc(100vw - 32px)); border: 1px solid var(--border); border-radius: 16px; padding: 0; background: var(--surface); color: var(--ink); box-shadow: 0 18px 50px rgba(0,0,0,0.28); }
.cal-modal::backdrop { background: rgba(0,0,0,0.45); }
.cal-modal form { padding: 18px 20px 20px; display: flex; flex-direction: column; gap: 12px; }
.cal-modal__head { display: flex; align-items: center; justify-content: space-between; }
.cal-modal__title { margin: 0; font-size: 18px; }
.cal-modal__x { background: none; border: none; cursor: pointer; color: var(--muted); font-size: 18px; padding: 4px; line-height: 1; }
.cal-modal__x:hover { color: var(--ink); }
.cal-modal__err { margin: 0; color: var(--error); font-size: 13px; }

/* ── .cal-modal--full — the found QUEUE's two faces (2026-08-28 modal audit: desktop windows, phones
   fill — supersedes the 2026-08-27 "full screen on BOTH form factors" cut; the cockpit + sync are the
   only desktop full-screens now). Above 620 it's a WIDE compact dialog: 600px, because a list she
   reads and acts down earns more width than the 480px edit card, but not a whole desktop. At ≤620
   the generic .cal-modal full-screen rule in the phone block takes it back to the screen (source
   order — that rule declares later), and the busy-recompact rule's :not(.cal-modal--full) guard
   keeps the queue OUT of the compact carve-out there.

   ⚠️ MUST STAY BELOW `.cal-modal` — same specificity, so source order decides the 600px width
   beating the base 480px (the first full-screen attempt learned this exact lesson in the other
   direction: declared above, it lost the width war and rendered a floating column).

   The old per-child gutter whitelist (src/list/foot each carrying max() side padding, err by margin —
   the "remember-to-add" pattern that let the not-ready banner escape the column, 2026-08-27) is
   RETIRED: everything below the head rides ONE .cal-found__col wrapper whose plain 18px padding is
   what the max() gutters collapsed to anyway once the box stopped being screen-wide. The head stays
   OUTSIDE the wrapper — it's sticky and must paint the full box width while the list scrolls under. */
.cal-modal--full { width: min(600px, calc(100vw - 32px)); }
.cal-modal--full .cal-busy__inner { max-height: min(80dvh, 820px); box-sizing: border-box; overflow-y: auto; padding: 0; }
.cal-modal--full .cal-modal__head {
  position: sticky; top: 0; z-index: 1; background: var(--surface);
  padding: 16px 18px 10px;
  /* margin: 0 is load-bearing on phones: the ≤620 block's generic .cal-modal__head compensates the
     compact form's inner padding with NEGATIVE margins — but --full's inner padding is 0, so that
     offset dragged this head past the screen edge (Blink flush left, Curtis's 2026-08-27 phone
     screenshot). This rule's (0,2,0) specificity is what holds it. */
  margin: 0;
}
.cal-found__col { padding: 0 18px calc(28px + env(safe-area-inset-bottom, 0px)); }
.cal-field { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
.cal-field > span { color: var(--muted); }
.cal-field input, .cal-field select, .cal-field textarea {
  font-family: inherit; font-size: 14px; color: var(--ink); background: var(--bg);
  border: 1px solid var(--border); border-radius: 9px; padding: 8px 10px; width: 100%;
}
.cal-field textarea { resize: vertical; }
.cal-field input:focus, .cal-field select:focus, .cal-field textarea:focus { outline: none; border-color: var(--accent); }
.cal-field-row { display: flex; gap: 12px; flex-wrap: wrap; }
.cal-field-row > .cal-field { flex: 1; min-width: 120px; }
.cal-times { display: flex; gap: 12px; flex: 2; min-width: 180px; }
.cal-times > .cal-field { flex: 1; }
.cal-check { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; }
.cal-check input { width: auto; }
/* ── .cal-modal--event — the EDIT modal's wide desktop face (2026-08-31, Curtis on the invitee
   render: "rethink this modal now that we have attendees… 3/4 screen on desktop so it can
   breathe"). Two columns: the event's own fields left, the people-and-context column right
   (category · reminder · who's going · the organizer note). Still a FLOATING dialog — the settled
   per-device ladder keeps desktop takeovers to the cockpit + sync — and the phone block below
   takes the whole thing back to the full-screen stack (source order: it declares later).
   The column wrappers are plain flex stacks at every size, so the phone face is just the same
   children in one column; only the form's grid is desktop-scoped. */
.cal-modal__main, .cal-modal__side { display: flex; flex-direction: column; gap: 12px; min-width: 0; }

/* CHROME + BODY (2026-08-31, Curtis's bleed screenshot: sticky bars over a scrolling FORM let
   content slide through the padding gaps around them and into the border). The form is a
   non-scrolling flex column; the head and actions are FIXED chrome with opaque backgrounds and
   honest hairlines; `.cal-modal__body` is the ONE scroll region between them. Both faces share
   this skeleton — the phone stacks the body's columns, the desktop grids them.
   ⚠️ The form selector is DOUBLED (.cal-modal.cal-modal--event) on purpose: `.cal-modal--event` is
   ONE class, so the bare modifier TIES the phone block's `.cal-modal form` rule and loses on source
   order — which re-applied the phone padding/gap/scroll to the form and stacked a second gutter
   inside the chrome (Curtis's devtools screenshot, 2026-08-31). (0,2,1) beats it everywhere; and
   the form is overflow:hidden so it can never become a second scroller — the body is the only one. */
/* The dialog itself is the flex column ([open]-scoped — the display-discipline pin: a bare display
   would paint the CLOSED dialog), so its max-height binds the FORM, and the form's body — not the
   dialog — is what scrolls. Without this, the dialog becomes the scroller and the chrome scrolls
   away with the content, which is the bleed bug wearing a different face. */
.cal-modal--event[open] { display: flex; flex-direction: column; }
.cal-modal.cal-modal--event form { flex: 1 1 auto; min-height: 0; height: auto; display: flex; flex-direction: column; padding: 0; gap: 0; box-sizing: border-box; overflow: hidden; }
.cal-modal--event .cal-modal__head { margin: 0; padding: 14px 16px 11px; border-bottom: 1px solid var(--border); background: var(--surface); flex: 0 0 auto; }
.cal-modal--event .cal-modal__err { margin: 10px 16px 0; flex: 0 0 auto; }
.cal-modal--event .cal-modal__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 14px 16px; display: flex; flex-direction: column; gap: 12px; }
.cal-modal--event .cal-modal__actions { margin: 0; padding: 11px 16px calc(12px + env(safe-area-inset-bottom)); border-top: 1px solid var(--border); background: var(--surface); flex: 0 0 auto; }
@media (min-width: 621px) {
  .cal-modal--event { max-height: min(88dvh, 860px); }
  .cal-modal--event .cal-modal__actions { padding-bottom: 12px; }
  /* The notes field is what actually needs the breathing room (Zoom boilerplate, agendas). */
  .cal-modal--event #cal-notes { min-height: 150px; resize: vertical; }
}
/* The TWO-COLUMN grid needs real width to earn its keep — at 621–899px it crushed the left column
   against the fixed 300px side (Start/End overlapping the divider at ~700px), so the mid range
   stays a single-column window and the grid engages only where both columns breathe. */
@media (min-width: 900px) {
  .cal-modal--event { width: min(940px, 78vw); }
  .cal-modal--event .cal-modal__body { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 14px 26px; align-items: start; padding: 16px 22px; }
  .cal-modal--event .cal-modal__side { padding-left: 24px; border-left: 1px solid var(--border); align-self: stretch; }
  /* The roster gets real height in its own column instead of a 5-row scroll box. */
  .cal-modal--event .cal-guests__list { max-height: 320px; }
}
@media (min-width: 621px) and (max-width: 899px) {
  .cal-modal--event { width: min(560px, calc(100vw - 32px)); }
}

/* WHICH CALENDAR — the modal's provenance line, first thing in the side column so it is read before
   Save/Delete rather than discovered after. Quiet by design: it is context, not a control. */
.cal-oncal { display: block; font-size: 12px; margin: 0 0 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* HOW SHE JOINS — the conference row (2026-08-31). It sits in the fields column right above Location
   because that is where the eye goes looking for "where is this", and it is deliberately a LINK, not
   a field: the conference belongs to whoever created the meeting, so there is nothing here to edit.
   Sized like a field's own text so it reads as part of the form rather than a banner. */
.cal-join { display: flex; flex-direction: column; gap: 2px; margin: 10px 0 4px; min-width: 0; }
.cal-join__link { font-size: 13px; font-weight: 600; color: var(--accent); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-join__link:hover { text-decoration: underline; }
.cal-join__dial { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* WHO'S GOING — the edit modal's guest list (display-only; every calendar client shows who's on a
   meeting). Bounded height so a big meeting scrolls inside the modal instead of stretching it. */
.cal-guests { margin: 2px 0 4px; }
.cal-guests__head { display: block; font-size: 12.5px; font-weight: 600; margin-bottom: 4px; }
.cal-guests__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; max-height: 138px; overflow-y: auto; }
.cal-guests__list li { font-size: 12.5px; line-height: 1.45; display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.cal-guests__who { flex: 0 1 auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cal-guests__email { flex: 0 1 auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; }
.cal-guests__rsvp { flex: 0 0 auto; width: 14px; text-align: center; color: var(--muted); }
.cal-guests__rsvp--accepted { color: var(--accent); }
.cal-guests__rsvp--declined { color: var(--error); }

.cal-modal__actions { display: flex; align-items: center; gap: 10px; margin-top: 4px; }
.cal-modal__spacer { flex: 1; }
.btn-danger-ghost { background: transparent; border: 1px solid transparent; color: var(--error); cursor: pointer; font-family: inherit; padding: 8px 12px; border-radius: 9px; }
.btn-danger-ghost:hover { border-color: var(--error); }

/* ── Calendar mobile (<880px): single-row header, dot grid + panel, floating Today above the tabbar ── */
@media (max-width: 879px) {
  /* Mobile: back to a plain column — the copilot is a fixed bottom drawer (overlay), not a flex
     child, so the calendar column owns the full width. */
  #tab-calendar.calendar { height: 100dvh; max-width: none; padding: var(--mtop-h) 0 0; flex-direction: column; }
  .cal-head { padding: 14px 16px 0; gap: 10px; } /* (column + stretch inherited from the base rule) */
  .cal-title { font-size: 18px; } /* a touch smaller so the date + tools share the top row without wrapping */
  .cal-body { padding: 0 16px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--copilot-peek) + 16px); } /* clear the docked composer above the tab bar */
  /* Stack the row on phones so a long date/time range isn't cramped beside the title. */
  .cal-list__row { flex-direction: column; gap: 3px; }
  .cal-list__when { width: auto; }
  /* Day hour-grid: tighten the label gutter on narrow screens (label stays left of the line). */
  .cal-hours { padding-left: 48px; }
  .cal-blocks { left: 48px; }
  .cal-hourrow__label { left: -48px; width: 40px; }
  /* Year: 3 mini-months per row on mobile (iCal's default), shrunk to fit. */
  .cal-year { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
  .cal-yr { padding: 8px 7px; }
  .cal-yr__name { font-size: 12px; padding-bottom: 4px; }
  .cal-yr__dow { font-size: 7px; padding: 1px 0; }
  .cal-yr__day { font-size: 9px; }
  .cal-yr__day--has::after { width: 3px; height: 3px; bottom: 0; }
}

@media (max-width: 620px) {
  .cal-day { min-height: 56px; }
  /* Year stays 3-up even on the smallest screens (the <880px rule's repeat(3,...) carries through). */

  /* Full-screen edit modal on phones — a centered 480px card is cramped to fill in on a
     small screen. Fill the viewport, pin the header, let the fields scroll under it. */
  .cal-modal { width: 100vw; max-width: none; height: 100dvh; max-height: none; margin: 0; border: none; border-radius: 0; }
  /* The read-only mirror detail stays COMPACT on phones (the modal-size ladder: a two-line glanceable
     detail is not "significant detail" — full screen is the edit FORM's rung, not this one).
     ⚠️ :not(.cal-modal--full) so it can't drag the found QUEUE back down a rung — that one is full
     screen on BOTH form factors, and this rule would otherwise re-compact it on a phone. */
  .cal-modal.cal-busy:not(.cal-modal--full) { width: min(480px, calc(100vw - 32px)); height: auto; margin: auto; border: 1px solid var(--border); border-radius: 16px;
    /* Re-fence the height: the generic rule above set max-height:none, and without this a longer
       busy detail would overflow the viewport with no scroll (modal audit 2026-08-27 — latent, the
       current details are 3 lines). */
    max-height: calc(100dvh - 32px); overflow-y: auto; }
  .cal-modal form { height: 100%; box-sizing: border-box; overflow-y: auto; gap: 14px; padding: 16px 18px calc(18px + env(safe-area-inset-bottom)); }
  .cal-modal__head { position: sticky; top: 0; z-index: 1; margin: -16px -18px 2px; padding: 14px 18px; background: var(--surface); border-bottom: 1px solid var(--border); }
  .cal-modal__title { font-size: 20px; }
  /* Comfortable tap targets for the on-screen-keyboard fields. */
  .cal-field input, .cal-field select, .cal-field textarea { padding: 11px 12px; font-size: 16px; }
  .cal-modal__actions { position: sticky; bottom: 0; background: var(--surface); padding-top: 10px; margin-top: auto; }
  .cal-save, .cal-modal__actions .btn-primary { min-height: 44px; }
  /* The found queue at phone width: the generic full-screen rule above owns the box (100vw × 100dvh,
     no border/radius — the :not(.cal-modal--full) guard keeps the busy carve-out off it); the inner
     just uncaps so the box's height is the scroll height. */
  .cal-modal--full .cal-busy__inner { height: 100dvh; max-height: none; }
  /* The search palette fills a phone (2026-08-28 modal audit) — keyboard up + a results list want
     the screen; the fixed cmd-K palette shape is a desktop luxury. 100dvh tracks the keyboard. */
  .cal-searchmodal { width: 100vw; max-width: none; height: 100dvh; max-height: none; margin: 0; border: none; border-radius: 0; }
  .cal-searchmodal__inner { padding: 14px 16px calc(16px + env(safe-area-inset-bottom, 0px)); }
}

/* Document DETAIL body styles — used by the document-body partial injected into the
   shared glass-box reader. The Library list itself reuses the glass-box classes
   (.glass-list / .glass-group / .glass-item) for true parity — see the Library block further down. */
.docbody__tag { font-size: 11px; background: var(--bg); border: 1px solid var(--border); color: var(--muted); border-radius: 6px; padding: 1px 7px; }

/* The rendered document body (injected into the shared reader). */
.docbody__tags { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.docbody h3 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 20px 0 8px; }
.wf-ingredients { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 4px; }
.wf-method { margin: 0; padding-left: 22px; display: flex; flex-direction: column; gap: 8px; line-height: 1.5; }
.docbody__notes { margin: 0; line-height: 1.55; }
.docbody__source { margin: 18px 0 0; font-size: 12px; font-style: italic; }

/* Desktop (≥880px): the rendered document body shares the centered reading column (the
   parallel of .glass-reader__body .md). The Library list's master-detail grid lives in the
   Library block below; the .glass-list / .glass-reader desktop rules are global, so they
   already style both columns. */
@media (min-width: 880px) {
  .glass-reader__body .docbody { max-width: var(--content-max); margin: 0 auto; padding: 28px 24px 56px; }
}

/* (The onboarding-ceremony CSS — .stage/.step-actions/.onb-back and the provider-picker comment — is
   deleted: the ceremony died with the companion cut, and no template emits those classes.) */
.small { font-size: 13px; }

/* Per-agent settings (account settings): ONE table — a row per agent, a dropdown for the
   model (Opus/Sonnet). Agents down the left, dropdown cells across. */
.agent-table { width: 100%; border-collapse: collapse; }
.agent-table th, .agent-table td { text-align: left; padding: 8px 10px; vertical-align: middle; }
.agent-table thead th { font-size: 12px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; border-bottom: 1px solid var(--border); }
.agent-table tbody tr + tr { border-top: 1px solid var(--border); }
.agent-table th[scope="row"] { font-weight: 600; }
/* Keep the <th> a real table-cell (so it honors the row's vertical-align:middle) and put
   the avatar+name in an inline-flex INNER wrapper — making the cell itself flex drops it
   out of table layout and mis-aligns it against the dropdown cells. */
.agent-who { display: inline-flex; align-items: center; gap: 10px; min-width: 0; vertical-align: middle; }
.agent-avatar { font-size: 20px; line-height: 1; flex: none; }
/* Dustav's fixed mark shown inline where the old emoji glyph used to be — sized 1em so it inherits
   the surrounding font-size in every avatar span (account table, usage feed/chips). */
.agent-face-inline { width: 1em; height: 1em; object-fit: contain; vertical-align: -0.12em; display: inline-block; overflow: visible; }
.agent-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Delete column — a quiet trash icon that warms to danger on hover. */
.agent-th-del, .agent-cell-del { width: 1%; text-align: right; white-space: nowrap; }
.agent-del-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; border: 1px solid transparent;
  border-radius: 8px; background: none; color: var(--muted); cursor: pointer;
}
.agent-del-btn:hover { color: var(--error); background: color-mix(in srgb, var(--error) 12%, transparent); }
.agent-del-btn:focus-visible { outline: none; border-color: var(--error); }
.agent-del-btn .icon { width: 18px; height: 18px; }


/* (The onboarding bootstrap-chat CSS — the .ceremony.chatting family + .onb-chat — is deleted with the
   ceremony itself; the shared .chat-head/.messages/.chat-form chrome below is what it borrowed and what
   survives.) */

/* ── The chat component (public/js/chat-core.js) ───────────────────────────── */

/* A messaging-client thread, but laid out like Claude Code: BOTH senders start at
   the left margin (avatar → name → text), and the text fills the full column width
   instead of a narrow 78% bubble — so long replies read horizontally, not as a tall
   vertical ribbon. The user is distinguished by a subtle card; the agent is plain
   page text. */
.ce-row { position: relative; display: flex; gap: 10px; align-items: flex-start; margin: 14px 0; }
.ce-row.grouped { margin-top: 4px; }

/* Per-message hover actions (copy this / copy to end) — a floating pill toolbar at
   the row's top-right. Hidden until you hover the row (or focus a button); on touch
   (no hover) it sits faintly visible since there's no hover to reveal it. A surface
   background keeps it legible where it overlaps the start of a full-width reply. */
/* Per-message actions: a small, PERSISTENT ⋯ at each row's top-right → a popover menu (Copy / Copy to end /
   That's wrong). Persistent on every platform so the actions are discoverable; full-opacity muted grey (not
   dimmed), brighter on hover/open. The menu is one shared popover appended to <body> (can't be clipped). */
.ce-menu-btn {
  position: absolute; top: -3px; right: 6px; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; border: 1px solid transparent; border-radius: 7px; cursor: pointer;
  color: var(--muted); opacity: 1; transition: background 0.12s, color 0.12s, border-color 0.12s; z-index: 2;
  background: transparent;
}
.ce-menu-btn:hover, .ce-menu-btn[aria-expanded="true"] { background: color-mix(in srgb, var(--ink) 13%, transparent); border-color: var(--border); color: var(--ink); }
.ce-menu {
  position: fixed; min-width: 152px; padding: 4px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.24); z-index: 60; display: flex; flex-direction: column; /* the ONE popover shadow (modal audit 2026-08-27) */
}
.ce-menu__item {
  border: none; background: none; color: var(--ink); cursor: pointer; font-family: inherit; font-size: 13px;
  text-align: left; padding: 8px 10px; border-radius: 7px; white-space: nowrap;
}
.ce-menu__item:hover { background: var(--bg); }
.ce-menu__sep { height: 1px; margin: 4px 2px; background: var(--border); }
/* That's wrong (docs/FEEDBACK.md) — a feedback act, set apart from the utility Copies by a divider + tint. */
.ce-menu__item--danger { color: #e5484d; }
.ce-menu__item--danger:hover { background: color-mix(in srgb, #e5484d 12%, transparent); }
.lie-ov {
  position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,0.45); padding: 20px; /* the ONE wash alpha (modal audit 2026-08-27) */
}
.lie-modal {
  width: 100%; max-width: 440px; box-sizing: border-box; background: var(--surface);
  border: 1px solid var(--border); border-radius: 16px; padding: 20px; box-shadow: 0 18px 50px rgba(0,0,0,.28);
}
.lie-modal__head { font-family: var(--font-display); font-weight: 800; font-size: 18px; color: var(--ink); }
.lie-modal__sub { color: var(--muted); font-size: 13px; margin-top: 4px; line-height: 1.4; }
.lie-modal__quote {
  margin: 14px 0; padding: 10px 12px; border-left: 3px solid var(--border); background: var(--bg);
  border-radius: 8px; color: var(--muted); font-size: 13px; font-style: italic; max-height: 96px; overflow: auto;
}
.lie-modal__label { display: block; font-size: 13px; font-weight: 600; color: var(--ink); margin-bottom: 6px; }
.lie-modal__input {
  width: 100%; box-sizing: border-box; resize: vertical; font-family: inherit; font-size: 14px;
  padding: 9px 11px; border: 1px solid var(--border); border-radius: 9px; background: var(--bg); color: var(--ink);
}
.lie-modal__input:focus { outline: none; border-color: var(--accent); }
.lie-modal__err { color: #e5484d; font-size: 12.5px; margin-top: 8px; }
.lie-modal__foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.lie-btn {
  font-family: inherit; font-size: 13px; font-weight: 600; padding: 8px 16px; border-radius: 9px;
  cursor: pointer; border: 1px solid transparent;
}
.lie-btn--ghost { background: none; border-color: var(--border); color: var(--muted); }
.lie-btn--ghost:hover { color: var(--ink); background: var(--bg); }
.lie-btn--go { background: #e5484d; color: #fff; }
.lie-btn--go:hover { background: #d33c41; }
.lie-btn--go:disabled { opacity: 0.6; cursor: default; }

/* "Loading older messages" spinner — absolute (out of flow, so toggling it never
   shifts the scroll-position math) at the top of the thread during a scroll-up load. */
.ce-top-loader { position: absolute; top: 8px; left: 50%; transform: translateX(-50%); z-index: 3; }

/* Jump-to-latest — a floating round button anchored to the chat pane (NOT the
   scroller, so it stays put while you scroll). The host gets position:relative; the
   button's `bottom` is set by JS to sit just above the composer at any height. */
.ce-has-jump { position: relative; }
.ce-jump {
  position: absolute; right: 24px; /* bottom set inline by chat-core.js */
  width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface); color: var(--ink); border: 1px solid var(--border);
  box-shadow: 0 3px 12px rgba(0,0,0,0.16); cursor: pointer; z-index: 5;
  transition: border-color 0.12s, color 0.12s, transform 0.08s;
}
.ce-jump:hover { border-color: var(--accent); color: var(--accent); transform: translateY(1px); }
.ce-jump svg { width: 20px; height: 20px; }
@media (max-width: 879px) { .ce-jump { right: 16px; } }

/* No bubble — just the emoji. Height is pinned to ~the name's line box (not the glyph), with the row
   top-aligned (align-items:flex-start), so the emoji CENTERS on the name row; the larger glyph simply
   overflows the short box symmetrically. */
.ce-avatar { flex: 0 0 auto; width: 30px; height: 16px; display: grid; place-items: center; font-size: 24px; line-height: 1; overflow: visible; }
/* Dustav's face as the baked brand image — sized 1em so it matches the emoji glyph in each context
   (24px in chat, 21px in the copilot) and overflows the short box the same way, centered by the grid. */
.ce-avatar-img { width: 1em; height: 1em; object-fit: contain; display: block; }
.ce-row.grouped .ce-avatar { visibility: hidden; }

/* Dustav's mark, inline SVG — INHERITS the theme accent (so it matches the nav section icons, which
   are also var(--accent)), with a border derived from it and white eyes. Rendered inline everywhere
   in-app so the colour follows the accent. Animated wherever it renders inline: the nav lockup, the
   header marks (.agent-face-inline — Usage & cost + the per-turn drill-down), and the chat avatars all
   run the idle tics; the live chat avatar (.is-live) + the nav bob while a turn's in flight
   (body.dustav-working). The baked PNGs stay Ocean for favicon/PWA/email. */
/* ⭐ COLOUR COMES FROM `color`, like every other icon in the system (2026-08-26). The head used to fill
   var(--accent) directly, which is why the row-level Dustav couldn't wear the mark's own classes: taking
   .blink__head would have overridden its per-sender ink tint, so it carried an unclassed rect instead —
   and an unclassed rect has nothing for the body tics to animate. Inverting it fixes both at once: the
   canonical instances (every one carries `blink` on the svg) self-colour to the accent, and any other
   host just sets `color` — the mailbox row's sender ink, the org chart's department hue. */
.blink { color: var(--accent); }
.blink__head { fill: currentColor; }   /* single solid colour — no border */
.blink__eye { fill: #fff; }
/* The corner-morph's two rx values, per geometry — the mark's 54-unit viewBox by default, the 24-grid
   icon twin overriding. (The keyframes read these; see blinkMorph.) */
.icon--dustav .blink__head { --blink-rx: 5px; --blink-rx-up: 6px; }
.blink__face, .blink__eye { transform-box: fill-box; transform-origin: center; }
.brand-mark-svg { width: 20px; height: 20px; flex: none; overflow: visible; }   /* match the 20px rail section icons */
.ce-avatar .blink { width: 1em; height: 1em; display: block; overflow: visible; }

/* ── Idle "tics" — the mark feels alive, not just a logo. They COMPOSE because each rides a DIFFERENT
      property: blink/double-blink → transform (eye), glance + look-around → translate (eye), breathe →
      scale (face), waddle → rotate (face), float → translate (face), fade → opacity (face), corner-morph
      → rx (head), and the working-bob → transform (face). So they stack, never overwrite (the ONE shared
      property — translate on the eyes — carries both the side-glance AND the up/down look in a single
      track). Desynced per-mark via --blink-delay (set in chat-core) so they don't move in lockstep. ── */

/* ⭐ THE CANONICAL ANIMATION — keyed to THE MARK'S OWN PARTS, not to its hosts (Curtis, 2026-08-26:
   "are the row level dustav's using THE EXACT SAME css classes as the dustav in the logo?… if it isn't
   the same, we should kill the CSS that is currently running them and wire them to the canonical
   animation css"). They weren't. These rules used to name every host — .brand-mark-svg, .ce-avatar,
   and three copy-pasted local variants — which meant a Blink that nobody had added to a list was a
   DEAD Blink, and hosts silently drifted into different subsets of the tics. Now: anything carrying
   .blink__face / .blink__head / .blink__eye is alive, identically, wherever it renders. There is no
   list to join and no per-host subset to get wrong.
     eyes  — a blink with the odd double, plus a glance around, sideways AND up/down on the one
             `translate` track (blinkGlance)
     face  — breathe (scale) + waddle (rotate) + float (translate) + a soft opacity pulse
     head  — a subtle corner-round (rx)
   They COMPOSE because each rides a different property (see the tics note above).
   ⭐ --blink-delay is THE desync mechanism — every rule reads it, so a host that wants to be off the
   shared clock sets one inherited variable and never touches animation-delay (a raw override would
   have to out-specify these rules, a cascade race nobody should have to win). chat-core randomises it
   per avatar; the mailbox rows and the org chart stagger with it. */
.blink__eye {
  animation: blinkIdle 11s ease-in-out infinite, blinkGlance 10s ease-in-out infinite;
  animation-delay: var(--blink-delay, 0s);
}
.blink__face {
  animation: blinkBreathe 4s ease-in-out infinite, blinkWaddle 6s ease-in-out infinite, blinkFloat 4.5s ease-in-out infinite, blinkFade 5.5s ease-in-out infinite;
  animation-delay: var(--blink-delay, 0s);
}
.blink__head {
  animation: blinkMorph 5s ease-in-out infinite;
  animation-delay: var(--blink-delay, 0s);
}
/* Working (a turn in flight): the nav lockup + the live answer's avatar BOB — this replaces the idle
   breathe/waddle for the duration (a clear "busy" beat), then they resume. */
.dustav-working .brand-mark-svg .blink__face,
.dustav-working .agent-face-inline .blink__face,
.ce-avatar.is-live .blink__face { animation: blinkBob 1.4s ease-in-out infinite; }

@keyframes blinkIdle {
  0%, 26%, 100% { transform: scaleY(1); }
  28% { transform: scaleY(0.08); } 30% { transform: scaleY(1); }                       /* a blink */
  58% { transform: scaleY(1); } 60% { transform: scaleY(0.08); } 62% { transform: scaleY(1); }  /* another */
  88% { transform: scaleY(1); }
  89.5% { transform: scaleY(0.08); } 91.5% { transform: scaleY(1); }                    /* the odd */
  93.5% { transform: scaleY(0.08); } 95.5% { transform: scaleY(1); }                    /* double-blink */
}
/* glance + look-around, all on the one eye `translate` track: right, left, then down, then up */
@keyframes blinkGlance {
  0%, 11% { translate: 0 0; } 16%, 24% { translate: 3px 0; }        /* glance right */
  29%, 41% { translate: 0 0; } 46%, 54% { translate: -3px 0; }      /* glance left */
  59%, 68% { translate: 0 0; } 72%, 79% { translate: 0 2.5px; }     /* look down */
  84%, 91% { translate: 0 -2px; }                                   /* look up */
  96%, 100% { translate: 0 0; }
}
@keyframes blinkBreathe { 0%, 100% { scale: 1; } 50% { scale: 1.05; } }
@keyframes blinkWaddle { 0%, 74%, 100% { rotate: 0deg; } 80% { rotate: 6deg; } 88% { rotate: -6deg; } 94% { rotate: 2deg; } }
/* slow up/down float of the whole head (its own `translate` track — composes with breathe/waddle) */
@keyframes blinkFloat { 0%, 100% { translate: 0 0; } 25% { translate: 0 -4%; } 75% { translate: 0 4%; } }
/* soft opacity pulse of the whole mark — a slow "breathing" dim, its own property so it composes */
@keyframes blinkFade { 0%, 100% { opacity: 1; } 50% { opacity: 0.8; } }
/* Subtle corner-rounding pulse — the ONE tic whose values are geometry-bound, so they ride variables
   instead of literals: the mark's own viewBox is 54 units (rx 15 at rest), the icon-grid twin is 24
   (rx 5). Hardcoding 15→18px would have inflated the little one into a circle and back. */
@keyframes blinkMorph { 0%, 100% { rx: var(--blink-rx, 15px); } 50% { rx: var(--blink-rx-up, 18px); } }
@keyframes blinkBob { 0%, 100% { transform: translateY(6%); } 50% { transform: translateY(-10%); } }
/* ⚠️ !important is still load-bearing, even though the idle rules are bare classes now: the WORKING bob
   above is host-scoped (0,3,0) and would outrank this kill on its own, so a mark stuck mid-turn would
   keep bobbing under reduced-motion. (Historically it covered the idle rules too — they were host-scoped
   until the canonical collapse, 2026-08-26.) */
@media (prefers-reduced-motion: reduce) { .blink__eye, .blink__face, .blink__head { animation: none !important; } }

/* Fill the row so text uses the full width (both senders left-aligned). */
.ce-body { display: flex; flex-direction: column; gap: 4px; flex: 1 1 auto; min-width: 0; }

/* Steps — the multi-hop play-by-play (reason → tool → reason). A one-step turn is a transparent
   passthrough (no chrome), so ordinary + fast-path turns look EXACTLY as before; the dot + connector
   rail appear only once a 2nd step arrives (.multi). .ce-step keeps the old 4px text→events gap. */
.ce-steps { display: flex; flex-direction: column; }
.ce-step { display: flex; flex-direction: column; gap: 4px; }
.ce-steps.multi { gap: 2px; }
/* The dot/connector rail sits in the LEFT GUTTER, CENTERED under Dustav's avatar (a separate .ce-avatar
   column — the dots can only OVERLAY it via a negative offset, not live inside it). padding-left:0 so the
   narration reclaims the full width (room back on mobile). The offset centres the 8px dot under the avatar
   centre: /chat avatar 30px + 10px gap → body-left 40, centre row-x 15 → left -29; copilot 26px + 8px gap
   → centre row-x 13 → left -25 (override below). The connector runs BEHIND the dots (the next dot paints
   over it → no masking ring, so no dark-mode halo). */
.ce-steps.multi .ce-step { position: relative; padding-left: 0; padding-bottom: 10px; }
.ce-steps.multi .ce-step:last-child { padding-bottom: 0; }
/* The dot sits at the step's FIRST line: a bullet in line with the narration text (the common case). */
.ce-steps.multi .ce-step::before {
  content: ""; position: absolute; left: -29px; top: 9px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent);
}
.ce-steps.multi .ce-step::after {
  content: ""; position: absolute; left: -25.5px; top: 17px; bottom: -11px; width: 1px; background: var(--border);
}
.ce-steps.multi .ce-step:last-child::after { display: none; }
/* Copilot rail: a smaller 26px avatar + 8px gap (body-left 34, centre row-x 13). */
.copilot .ce-steps.multi .ce-step::before { left: -25px; }
.copilot .ce-steps.multi .ce-step::after { left: -21.5px; }
/* A step that leads with a chip/card (no narration — its .ce-text is empty/hidden): drop the dot to the
   top of that content so it reads as "at the top of the box", not floating above it. */
.ce-steps.multi .ce-step:has(> .ce-text:empty)::before { top: 8px; }
/* Empty-step collapse is done in JS (advanceStep reuses an empty live step; renderSteps drops an empty
   persisted step) — NOT via a :has() display:none. That CSS approach hid the reply on iOS Safari: a step is
   inserted EMPTY and filled a beat later, and older WebKit doesn't re-invalidate :has() on that descendant
   mutation, so the just-emptied reply step stayed display:none forever (desktop engines re-evaluated fine). */

.ce-meta { display: flex; gap: 8px; align-items: baseline; font-size: 12px; color: var(--muted); }
.ce-name { font-weight: 600; color: var(--ink); }
.ce-row.grouped .ce-meta { display: none; }
/* Unprompted (task-woken) message: a soft accent edge on the bubble, so it feels
   like the agent reached out without shouting it. */
.ce-row.proactive .ce-text { border-left: 2px solid var(--accent); padding-left: 12px; }

/* Agent: plain text on the canvas (no bubble), so it reads full-width like a doc. */
.ce-text { padding: 2px 0; word-wrap: break-word; overflow-wrap: anywhere; }
/* User: a subtle card so the alternation stays legible at full width. */
.ce-row.user .ce-text { padding: 10px 14px; border-radius: 14px; background: var(--panel); border: 1px solid var(--border); }
.ce-caret::after { content: "▍"; opacity: 0.5; margin-left: 1px; }
/* Dustav's reaction to a message — a small emoji badge tucked under the bubble's bottom-left edge,
   like a nod on a text. A flex child of .ce-body (appended after the bubble), pulled up to overlap. */
.ce-reaction {
  align-self: flex-start;
  width: fit-content;
  margin: -9px 0 0 10px;
  padding: 1px 5px;
  font-size: 15px;
  line-height: 1.3;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
  position: relative;
  z-index: 1;
}
.copilot .ce-reaction { font-size: 14px; margin-top: -8px; }
.ce-text.err { color: var(--error); }
.ce-text p { margin: 0 0 8px; }
.ce-text p:last-child { margin-bottom: 0; }
.ce-text ul, .ce-text ol { margin: 4px 0; padding-left: 20px; }
.ce-text code { font-family: ui-monospace, Menlo, monospace; font-size: 0.92em; background: rgba(127,127,127,0.18); padding: 1px 5px; border-radius: 5px; }
.ce-text pre { background: rgba(127,127,127,0.14); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; overflow-x: auto; }
.ce-text pre code { background: none; padding: 0; }
.ce-text a { color: var(--accent); text-decoration: underline; }

/* The ■ stop glyph — the send arrow morphs into this while a turn is in flight (chat-core setStopMode).
   Same button chrome (accent fill), just the square; currentColor keeps it on-accent-ink in both themes. */
.ce-stop-square { display: inline-block; width: 11px; height: 11px; border-radius: 2.5px; background: currentColor; }

.ce-events { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
/* "What Dustav did" chips — colored domain PILLS, matching the inbox/sent trace (.led-act): same
   --h hue mechanism (a tinted fill + border), one hue per domain so chat reads as one system with the
   inbox. Result cards in the same strip keep align-self:stretch (full width); chips hug their content. */
.ce-note {
  --h: var(--muted);
  align-self: flex-start; max-width: 100%;
  font-size: 11.5px; line-height: 1.35;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 10px; border-radius: 999px; color: var(--ink);
  background: color-mix(in srgb, var(--h) 9%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--h) 32%, var(--border));
}
.ce-note--cal      { --h: #16a34a; } /* calendar — green (matches the inbox .led-act-cal) */
.ce-note--doc      { --h: #6366f1; } /* documents — indigo */
.ce-note--web      { --h: #0ea5e9; } /* web lookups — sky (matches .led-act-verify) */
.ce-note--email    { --h: #f59e0b; } /* gmail / prepared email — amber (matches .led-act-draft) */
.ce-note--facts    { --h: #8b5cf6; } /* business profile / facts / you — violet */
.ce-note--tables   { --h: #14b8a6; } /* business tables — teal (matches .led-actf--shipments) */
.ce-note--intel    { --h: #ec4899; } /* the learn queue — pink (matches .led-act-learn) */
.ce-note--todo     { --h: #10b981; } /* to-dos — emerald (matches .led-act-todo) */
.ce-note--inbox    { --h: #f97316; } /* inbox cards / drafts — orange */
.ce-note--workflow { --h: #0891b2; } /* workflows — cyan */
.ce-note--look     { --h: #a855f7; } /* an image looked-at / reading check — purple (~.led-act-look) */

/* Prepared-outbound card (docs/DRAFT_EMAIL.md) — Dustav composed a NEW email for the human to send.
   Dustav prepares; the human commits, so the only action is Copy (Send is a later layer). Theme-aware via
   the shared panel/border/ink vars. */
.ce-draft {
  align-self: stretch; margin-top: 6px; padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--surface);
  display: flex; flex-direction: column; gap: 6px; max-width: 560px;
}
.ce-draft__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ce-draft__tag { font-size: 12px; font-weight: 600; letter-spacing: 0.02em; color: var(--accent); }
.ce-draft__copy {
  padding: 3px 12px; border-radius: 999px; border: 1px solid var(--border);
  background: transparent; color: var(--muted); font-size: 12px; font-family: inherit; cursor: pointer;
  transition: border-color 0.12s, color 0.12s;
}
.ce-draft__copy:hover { border-color: var(--accent); color: var(--accent); }
.ce-draft__field { display: flex; gap: 8px; font-size: 13px; line-height: 1.4; }
.ce-draft__k { flex: 0 0 auto; width: 52px; color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; padding-top: 1px; }
.ce-draft__v { flex: 1 1 auto; min-width: 0; color: var(--ink); word-break: break-word; }
.ce-draft__body {
  margin-top: 2px; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: 14px; line-height: 1.5; color: var(--ink); white-space: pre-wrap; word-break: break-word;
}
.ce-draft__atts { display: flex; flex-wrap: wrap; gap: 6px; }
.ce-draft__att { font-size: 12px; color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px; }
/* Result card — a "creation" (event / note / draft reply / workflow / to-do) rendered as a compact
   preview with a deep link straight to it in the app. Shares the surface look with .ce-draft; the
   card replaces the plain chip. .ce-card__link is shared with the draft card's "Open in Drafts". */
.ce-card {
  align-self: stretch; margin-top: 6px; padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--surface);
  display: flex; flex-direction: column; gap: 3px; max-width: 560px;
}
.ce-card__head { display: flex; align-items: center; gap: 7px; }
.ce-card__icon { font-size: 15px; line-height: 1; }
.ce-card__tag { font-size: 12px; font-weight: 600; letter-spacing: 0.02em; color: var(--accent); }
.ce-card__title { font-size: 14px; font-weight: 600; color: var(--ink); word-break: break-word; }
.ce-card__meta { font-size: 13px; color: var(--muted); line-height: 1.4; word-break: break-word; }
.ce-card__link {
  align-self: flex-start; margin-top: 5px; font-size: 12px; font-weight: 600;
  color: var(--accent); text-decoration: none; border-bottom: 1px solid transparent; transition: border-color 0.12s;
}
.ce-card__link:hover { border-color: var(--accent); }
/* The formal-signature card (signature_preview) — the rendered block, boxed like a letter's foot.
   The block itself rides .pf-sigblock (the Profile tab's rendered-signature look — one vocabulary);
   the inner rule separates the head from the "paper". Committed = the accent edge says "live now". */
.ce-sig__block { margin-top: 6px; padding: 9px 11px; border-radius: 8px; border: 1px dashed var(--border); }
.ce-sig--committed { border-color: var(--accent); }
.ce-sig--committed .ce-sig__block { border-style: solid; }
/* The anchor variant of the card action (the corridor's reconnect card links straight to the OAuth
   start — no sync modal on a bare page). Same face as the button, minus link chrome. */
.ce-card__btn--link { display: inline-block; text-decoration: none; }
/* Card ACTION button — a real in-card action (the Gmail Reconnect card's data-sync-open), styled as
   the accent primary so it reads as "tap this", unlike the quiet deep-link text of .ce-card__link. */
.ce-card__btn {
  align-self: flex-start; margin-top: 7px; padding: 6px 16px; border-radius: 999px;
  border: 1px solid var(--accent); background: var(--accent); color: var(--accent-ink);
  font-size: 12.5px; font-weight: 700; font-family: inherit; cursor: pointer;
  transition: filter 0.12s;
}
.ce-card__btn:hover { filter: brightness(1.08); }
.ce-card__btn:active { filter: brightness(0.94); }
.ce-card__btn:disabled { opacity: 0.6; cursor: default; filter: none; }
/* The STAGED-CALENDAR card (docs/GCAL_MIRROR.md §6d) — Dustav prepared it, her tap books it. One act:
   Book. ("Review them" + its .ce-card__btn--ghost dress retired 2026-08-27 — the batch was already
   reviewed before staging; the Calendar tab's queue remains for anyone who goes looking.) */
.ce-card__acts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* The ghost-block family on the grid wears a dashed accent rail; the card echoes that language with
   an accent edge, so a staged thing reads the same wherever she meets it. Once booked it goes calm —
   the edge stays (it IS hers now) but the pending tint drops. */
.ce-card--staged { border-left: 3px solid var(--accent); }
.ce-card--staged-done { opacity: 0.85; }
/* Onboarding setup checklist — the guided-setup card Dustav manages during the first hello
   (services/onboardingChecklist.js → the onboarding_checklist event, chat-core renderChecklist).
   Newest wins: a fresh card collapses every earlier one to its header line (.is-stale), so history
   keeps the trail without a wall of stale checklists. */
.ce-checklist {
  align-self: stretch; margin-top: 6px; padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--surface);
  display: flex; flex-direction: column; gap: 8px; max-width: 420px;
}
/* The head is a BUTTON now — the card is default-collapsed to its summary line (tag + count) and
   unfolds on tap (Curtis, 2026-08-24: "too big and noisy"). */
.ce-checklist__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  width: 100%; background: none; border: none; padding: 0; font-family: inherit; cursor: pointer; text-align: left;
}
.ce-checklist__tag { font-size: 12px; font-weight: 700; letter-spacing: 0.02em; color: var(--accent); }
.ce-checklist__count { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); margin-left: auto; }
.ce-checklist__chev { font-size: 11px; color: var(--muted); transition: transform 0.15s ease; flex: none; }
.ce-checklist.is-open .ce-checklist__chev { transform: rotate(90deg); }
.ce-checklist__rows { display: none; flex-direction: column; gap: 6px; margin-top: 6px; }
.ce-checklist.is-open .ce-checklist__rows { display: flex; }
.ce-checklist__row { display: flex; align-items: center; gap: 9px; font-size: 13.5px; color: var(--ink); }
.ce-checklist__mark {
  flex: none; width: 18px; height: 18px; border-radius: 999px; border: 1.5px solid var(--border);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800; line-height: 1; color: var(--muted);
}
.ce-checklist__row.is-done .ce-checklist__mark { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.ce-checklist__row.is-skipped { opacity: 0.55; }
.ce-checklist__row.is-skipped .ce-checklist__label { text-decoration: line-through; }
.ce-checklist.is-stale { opacity: 0.55; }
.ce-checklist.is-stale .ce-checklist__rows { display: none; }
/* Retry — the recovery affordance under a dropped turn. align-self:flex-start so it
   hugs the text, not the full row width. */
.ce-retry {
  align-self: flex-start; margin-top: 4px; padding: 4px 12px; border-radius: 999px;
  border: 1px solid var(--border); background: transparent; color: var(--muted);
  font-size: 15px; font-family: inherit; cursor: pointer; transition: border-color 0.12s, color 0.12s;
}
.ce-retry:hover { border-color: var(--accent); color: var(--accent); }

/* "Working" status — sits at the BOTTOM of the pending agent bubble (inside .ce-text)
   while the turn is in flight, so the bubble is never an empty panel and a long tool op
   never reads as a dead stall. Spinner + a verb, gently breathing. The :only-child rule
   tightens the gap when the bubble is still empty (spinner alone, pre-first-token). */
.ce-working {
  display: flex; align-items: center; gap: 8px; margin-top: 6px;
  font-size: 13px; color: var(--muted);
  animation: ce-working-pulse 1.6s ease-in-out infinite;
}
.ce-text > .ce-working:only-child { margin-top: 0; }
.ce-spinner {
  flex: none; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: glass-spin 0.7s linear infinite;
}
.ce-working__label { font-style: italic; }
@keyframes ce-working-pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .ce-spinner, .ce-working { animation: none; }
  .ce-spinner { border-top-color: var(--accent); } /* still reads as a status dot */
}

.ce-choices { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
.ce-choice { padding: 7px 13px; border-radius: 999px; border: 1px solid var(--accent); background: transparent; color: var(--accent); font-size: 15px; font-family: inherit; cursor: pointer; }
.ce-choice:hover:not(:disabled) { background: var(--accent); color: #fff; }
.ce-choice:disabled { cursor: default; opacity: 0.55; }
.ce-choice.picked { background: var(--accent); color: #fff; opacity: 1; }

/* ── Library — the Files glass-box shape, one tier deeper: each collection is a
   .glass-group heading with its artifacts as .glass-items beneath. Reuses the glass-box
   classes wholesale; only the per-collection profile peek + empty line are bespoke. ── */

/* Desktop (≥880px): the same master-detail grid the glass box uses. The global .glass-list /
   .glass-reader desktop rules style the two columns; we only need the grid container. */
@media (min-width: 880px) {
  #tab-library.glass--cols {
    max-width: none; margin: 0; padding: 0; height: 100dvh;
    display: grid; grid-template-columns: 480px minmax(0, 1fr);
  }
}

.lib-empty { font-size: 13px; padding: 4px 2px 8px; }

/* ── In-place editor — the docked composer on the Library reader (public/js/dashboard/library.js).
   The reader becomes a flex column: the doc scrolls, the composer stays pinned at the foot, and a
   agent edit re-renders the body live (.lib-flash). Scoped to #lib-viewer so the Files reader (no
   composer) is untouched — both share the .glass-reader classes. ── */
#lib-viewer { display: flex; flex-direction: column; overflow: hidden; }
#lib-viewer .glass-reader__inner { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

.lib-chat { flex: none; border-top: 1px solid var(--border); background: var(--bg); padding: 10px 16px; }
#lib-viewer:not(.has-artifact) .lib-chat { display: none; }   /* nothing open → no composer */

.lib-chat__form { display: flex; gap: 8px; align-items: flex-end; max-width: var(--content-max); margin: 0 auto; }
.lib-chat__form textarea {
  flex: 1; padding: 10px 14px; border-radius: 13px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink);
  font-size: 16px; font-family: inherit; line-height: 1.4; box-sizing: border-box;
  resize: none; min-height: calc(1.4em + 22px); max-height: 120px; overflow-y: auto;
}
.lib-chat__form textarea:focus { outline: none; border-color: var(--accent); }
.lib-chat__send { flex: none; width: 42px; height: 42px; padding: 0; border-radius: 999px; display: grid; place-items: center; font-size: 16px; }

/* The agent's ephemeral reply — ONE bubble above the input (doc-first, not a thread). */
.lib-chat__reply { position: relative; display: flex; gap: 8px; align-items: flex-start; max-width: var(--content-max); margin: 0 auto 8px; padding-right: 22px; }
.lib-chat__avatar { font-size: 20px; line-height: 1.4; flex: none; }
.lib-chat__bubble { min-width: 0; flex: 1; }
.lib-chat__text { font-size: 14px; line-height: 1.5; color: var(--ink); }
.lib-chat__text p { margin: 0 0 6px; }
.lib-chat__text p:last-child { margin-bottom: 0; }
.lib-chat__text.is-streaming::after { content: "▋"; color: var(--accent); animation: lib-caret 1s steps(1) infinite; }
@keyframes lib-caret { 50% { opacity: 0; } }
.lib-chat__events { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.lib-chat__events:empty { display: none; }
.lib-chat__chip { font-size: 12px; color: var(--muted); background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px; }
.lib-chat__dismiss { position: absolute; top: -2px; right: 0; background: none; border: none; color: var(--muted); font-size: 18px; line-height: 1; cursor: pointer; padding: 2px 4px; }
.lib-chat__dismiss:hover { color: var(--ink); }
.lib-chat__spinner { display: inline-block; width: 13px; height: 13px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: lib-spin 0.7s linear infinite; vertical-align: middle; }
@keyframes lib-spin { to { transform: rotate(360deg); } }

/* The live-edit highlight — a soft accent wash on the doc body that fades as it re-renders. */
.lib-flash { animation: lib-flash 1.1s ease-out; }
@keyframes lib-flash { 0% { background: color-mix(in srgb, var(--accent) 16%, transparent); } 100% { background: transparent; } }

@media (max-width: 879px) {
  /* The reader overlay (z-60) covers the bottom tab bar (z-50) while reading, so the composer
     sits at the very bottom — just clear the home-bar safe area, no tab-bar offset. */
  .lib-chat { padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
}

/* ============================================================
   Public pages — marketing landing + legal/contact. These render
   through the main layout (.wrap, max-width 980px); the header and
   footer partials (pub-head / pub-foot) frame every public page.
============================================================ */

/* Larger CTA button used on the landing page. */
.btn-lg { padding: 13px 26px; font-size: 17px; border-radius: 12px; }

/* ── Public header ──
   Sticky + scroll-docked. .pub-head is the full-width bar shell (it spans the whole page
   on the full-bleed landing); .pub-head__inner is the centered content band. At the top
   it's transparent and flush with the page; once you scroll past a few px, app-header.js
   adds .is-docked and the ::before fades in as a seamless, edge-to-edge translucent/blurred
   bar pinned to the very top — a real fixed header you can watch content scroll (and blur)
   past beneath. Square, no top gap, shadow + hairline only along the bottom edge. Using a
   full-width shell (not a 100vw break-out) keeps position:sticky intact — an ancestor
   overflow-clip needed to contain a 100vw child de-stickies it in Chromium. */
.pub-head { position: sticky; top: 0; z-index: 50; margin-bottom: 20px; }
.pub-head::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.6);
  backdrop-filter: blur(14px) saturate(1.6);
  border-bottom: 1px solid transparent;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
  opacity: 0;
  transition: opacity .22s ease;
  pointer-events: none;
}
.pub-head.is-docked::before {
  opacity: 1;
  border-bottom-color: color-mix(in srgb, var(--border) 80%, transparent);
}
.pub-head__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 10px 0; min-height: 60px;
}
.pub-head__brand { display: inline-flex; align-items: center; gap: 9px; text-decoration: none; color: var(--ink); }
.pub-head__brand .brand-face { width: 22px; height: 22px; }
.pub-head__brand .brand-wordmark { font-family: var(--font-display); font-weight: 700; font-size: 22px; }
/* flex-wrap + nowrap items: if space runs out, whole controls drop to a second
   row right-aligned — never the broken "Log in" / "Get / started" text wrap. */
.pub-head__nav { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 10px 16px; }
.pub-head__links { display: flex; align-items: center; gap: 16px; }
.pub-head__link { color: var(--ink); text-decoration: none; font-size: 15px; white-space: nowrap; }
.pub-head__link:hover { color: var(--accent); }
/* CTA + hamburger share ONE height so the header is the exact same height whether a page
   shows the button (marketing) or just the menu (auth) — with .pub-head__inner's 60px
   min-height (40 control + 10/10 padding), the logo never shifts between screens. */
.pub-head__nav .btn { white-space: nowrap; }
.pub-head__cta { display: inline-flex; align-items: center; height: 40px; padding: 0 18px; }
/* Glyph-only light/dark toggle in the desktop nav (hidden on mobile — the overflow menu
   carries it there). Just the moon/sun mark; app-header.js flips .is-dark to swap them. */
.pub-head__toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0; flex: none;
  background: none; border: 1px solid transparent; border-radius: 11px;
  color: var(--ink); cursor: pointer;
}
.pub-head__toggle:hover { color: var(--accent); border-color: var(--border); }
.pub-head__toggle .icon { width: 19px; height: 19px; }

/* Mobile overflow menu — hidden on desktop (the inline .pub-head__links show there). */
.pub-menu { display: none; position: relative; }
.pub-menu__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0;
  background: var(--surface); border: 1px solid var(--border); border-radius: 11px;
  color: var(--ink); cursor: pointer;
}
.pub-menu__btn:hover { border-color: var(--accent); color: var(--accent); }
/* Two-bar "menu" glyph drawn from the button's currentColor (no icon dependency). */
.pub-menu__bars, .pub-menu__bars::before {
  display: block; width: 17px; height: 1.5px; border-radius: 2px; background: currentColor;
}
.pub-menu__bars { position: relative; }
.pub-menu__bars::before { content: ""; position: absolute; top: -5px; left: 0; }
.pub-menu__panel {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
  min-width: 168px; padding: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18);
}
.pub-menu__panel[hidden] { display: none; }
.pub-menu__item {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 10px;
  color: var(--ink); font-size: 15px; text-decoration: none; white-space: nowrap;
}
.pub-menu__item .icon { width: 16px; height: 16px; color: var(--accent); flex: none; }
.pub-menu__item:hover { background: var(--bg); color: var(--accent); }
/* The light/dark toggle, styled as just another menu item (no separator line) — inherits
   the .theme-toggle icon-swap + label flip from app-header.js. */
.pub-menu__theme {
  width: 100%; border: none; background: none; font: inherit; font-size: 15px;
  text-align: left; cursor: pointer;
}
/* Honor reduced-motion: dock instantly, no fade/shrink easing. */
@media (prefers-reduced-motion: reduce) {
  .pub-head, .pub-head::before { transition: none; }
  .pub-head::before { transform: none; }
}
@media (max-width: 560px) {
  .pub-head__inner { gap: 12px; }
  .pub-head__nav { gap: 8px 12px; flex-wrap: nowrap; }
  /* Collapse the inline links + glyph toggle into the overflow menu — keeps the row to
     brand + CTA + menu button so nothing wraps on a phone. */
  .pub-head__links { display: none; }
  .pub-head__toggle { display: none; }
  .pub-menu { display: block; }
}

/* ── Landing ── */
.land { display: flex; flex-direction: column; gap: 8px; }

/* Hero is a two-column stage: the pitch (left) beside a real agent's insides (right).
   The glass box IS the hero, not an illustration tucked below it. Stacks on mobile. */
.land-hero {
  display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: 46px; align-items: center; text-align: left; padding: 38px 0 60px;
}
.land-hero__title { font-size: 50px; line-height: 1.04; margin: 0 0 16px; letter-spacing: -0.02em; }
.land-hero__title em { color: var(--accent); font-style: normal; white-space: nowrap; }
.land-hero__lede { max-width: 32em; margin: 0; font-size: 19px; line-height: 1.55; color: var(--ink); }
.land-hero__cta { margin-top: 26px; }
.land-hero__note { margin-top: 14px; font-size: 13.5px; }


/* Content sections */
.land-section { padding: 48px 0; border-top: 1px solid var(--border); }
.land-section--alt {
  background: var(--panel); border: 1px solid var(--border); border-radius: 20px;
  padding: 44px 36px; margin: 16px 0;
}
.land-eyebrow {
  margin: 0 0 6px; font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--accent);
}
.land-section h2 { font-size: 30px; margin: 0 0 16px; letter-spacing: -0.01em; }
.land-lead { font-size: 17px; line-height: 1.6; margin: 0; }

/* Steps */
.land-steps { list-style: none; margin: 24px 0 0; padding: 0; display: flex; flex-direction: column; gap: 22px; }
.land-steps li { display: flex; gap: 16px; align-items: flex-start; }
.land-steps__n {
  flex: none; display: grid; place-items: center; width: 34px; height: 34px;
  border-radius: 50%; background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-display); font-weight: 700; font-size: 16px;
}
.land-steps h4 { margin: 4px 0 4px; font-size: 18px; }
.land-steps p { margin: 0; line-height: 1.5; }

/* Glass-box checklist */
.land-checks { list-style: none; margin: 22px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.land-checks li { display: flex; align-items: center; gap: 12px; font-size: 16px; }
.land-checks .icon { color: var(--accent); width: 18px; height: 18px; flex: none; }
/* A paragraph right after a checklist needs air — the list has only a top margin. */
.land-checks + .land-lead { margin-top: 22px; }

/* Final CTA */
.land-final { text-align: center; padding: 60px 0 48px; border-top: 1px solid var(--border); }
.land-final h2 { font-size: 32px; margin: 0 0 8px; }

/* ── Landing spice (emoji-forward + warmth) ─────────────────────────────────
   Layered on top of the base rules above: a soft hero glow, an emoji badge,
   multi-hue icon chips (coral/teal/violet etc — the accent palette), card
   hover-lift, gradient accents, and CTA polish. The brand leans on emoji as a
   first-class affordance, so they carry the colour and these styles add depth. */

/* Hero: relative + a warm radial glow behind the title */
.land-hero { position: relative; }
.land-hero::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 50%; top: -30px; transform: translateX(-50%);
  width: min(720px, 92%); height: 360px;
  background: radial-gradient(58% 60% at 50% 38%,
    color-mix(in srgb, var(--accent) 24%, transparent), transparent 72%);
  filter: blur(6px);
}

/* Emoji badge above the title */
.land-badge {
  display: inline-flex; align-items: center; gap: 8px; margin: 0 0 18px;
  padding: 6px 14px 6px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 11%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--border));
  font-size: 13.5px; font-weight: 600; color: var(--ink); letter-spacing: -0.01em;
}
.land-badge__face {
  display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%;
  background: var(--surface); font-size: 14px; line-height: 1;
  box-shadow: 0 1px 3px rgba(0,0,0,0.10);
}

/* "see into" reads as a royal→violet gradient; the 👀 gives a gentle bob */
.land-hero__title em {
  color: var(--accent);
  background: linear-gradient(120deg, var(--accent), color-mix(in srgb, var(--accent) 55%, #7b61ff));
  -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
.land-hero__eye { display: inline-block; animation: land-eye-bob 3.2s ease-in-out infinite; }
@keyframes land-eye-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }

/* Eyebrows become little accent pills (the emoji rides inside) */
.land-eyebrow {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 0 0 12px; padding: 5px 11px; border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
}

/* Step number chips: gradient fill + a soft accent shadow */
.land-steps__n {
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 68%, #ff3d6a));
  box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 30%, transparent);
}

/* The emoji that rides each glass-box check */
.land-checks__e { font-size: 15px; line-height: 1; }

/* ── Family "two ways in" visual ─────────────────────────────────────────────
   The two join routes made literal: a big code + PIN on a shared device, or an
   email-invite mock. Echoes the friendrot teachers code/PIN card. */
.land-join__head {
  margin: 28px 0 0; font-family: var(--font-display); font-weight: 700;
  font-size: 18px; color: var(--ink);
}
.land-join {
  display: grid; grid-template-columns: 1fr auto 1fr; gap: 16px;
  align-items: stretch; margin: 14px 0 6px;
}
.land-join__route {
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  padding: 20px; box-shadow: 0 1px 2px rgba(0,0,0,0.03);
}
.land-join__rlabel {
  display: flex; align-items: center; gap: 8px; margin: 0 0 14px;
  font-family: var(--font-display); font-weight: 700; font-size: 15.5px;
}
.land-join__re { font-size: 18px; line-height: 1; }

/* The big code + PIN */
.land-join__creds {
  flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 10px;
}
.land-join__cred {
  text-align: center; padding: 12px 10px; border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border));
}
.land-join__credlabel {
  display: block; margin-bottom: 3px; font-size: 10px; font-weight: 800;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--muted);
}
.land-join__credval {
  display: block; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-weight: 800; font-size: clamp(30px, 5.2vw, 44px); line-height: 1.05;
  letter-spacing: 0.12em; color: var(--accent);
}

/* The "or" hinge between the two routes */
.land-join__or {
  display: grid; place-items: center; color: var(--muted);
  font-family: var(--font-display); font-weight: 700; font-size: 12px;
  text-transform: uppercase; letter-spacing: 0.1em;
}
.land-join__or span {
  display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%;
  background: var(--bg); border: 1px solid var(--border);
}

/* Email-invite mock */
.land-join__mail {
  flex: 1; display: flex; flex-direction: column; gap: 7px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px;
}
.land-join__mailto {
  margin: 0; font-size: 12.5px; color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, monospace;
}
.land-join__mailto span { color: var(--ink); }
.land-join__mailsub { margin: 2px 0 0; font-size: 16px; font-weight: 700; line-height: 1.3; color: var(--ink); }
.land-join__mailbtn {
  align-self: flex-start; margin-top: auto;
  padding: 8px 14px; border-radius: 9px;
  background: var(--accent); color: #fff; font-size: 13px; font-weight: 700;
  box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 28%, transparent);
}
.land-join__cap { margin: 12px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--muted); }

@media (max-width: 720px) {
  .land-join { grid-template-columns: 1fr; gap: 12px; }
  .land-join__or { padding: 2px 0; }
  .land-join__or span { width: 30px; height: 30px; }
}

/* Alt sections get a barely-there accent wash */
.land-section--alt {
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 5%, var(--surface)), var(--surface));
}

/* CTA buttons: an accent glow + a small lift on hover */
.land-hero__cta .btn, .land-final .btn { transition: transform .15s ease, box-shadow .15s ease; }
.land-hero__cta .btn-primary, .land-final .btn-primary {
  box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 34%, transparent);
}
.land-hero__cta .btn:hover, .land-final .btn:hover { transform: translateY(-2px); }
.land-hero__cta .btn-primary:hover, .land-final .btn-primary:hover {
  box-shadow: 0 10px 26px color-mix(in srgb, var(--accent) 46%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .land-hero__eye { animation: none; }
  .land-hero__cta .btn, .land-final .btn { transition: none; }
}

/* ── Landing pricing (two tiers) + FAQ ──────────────────────────────────── */
.land-plans {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px; margin-top: 26px;
  align-items: stretch;
}
.land-plan {
  position: relative; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border); border-radius: 18px;
  padding: 26px 24px; box-shadow: 0 1px 2px rgba(0,0,0,0.03);
}
.land-plan--featured {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: 0 16px 36px color-mix(in srgb, var(--accent) 16%, transparent);
}
.land-plan--soon { border-style: dashed; }
.land-plan__badge {
  position: absolute; top: -11px; left: 24px;
  display: inline-flex; align-items: center; padding: 4px 11px; border-radius: 999px;
  font-size: 12px; font-weight: 700; letter-spacing: -0.01em;
  background: var(--accent); color: #fff;
}
.land-plan--soon .land-plan__badge { background: var(--muted); }
.land-plan__name { margin: 2px 0 6px; font-size: 20px; }
.land-plan__price { margin: 0 0 6px; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.land-plan__amt { font-family: var(--font-display); font-weight: 800; font-size: 34px; letter-spacing: -0.02em; }
.land-plan__per { color: var(--muted); font-size: 15px; }
.land-plan__tag { margin: 0 0 16px; color: var(--muted); font-size: 14.5px; line-height: 1.45; }
.land-plan__list { list-style: none; margin: 0 0 20px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.land-plan__list li { display: flex; align-items: flex-start; gap: 10px; font-size: 15px; line-height: 1.4; }
.land-plan__e { flex: none; width: 1.35em; text-align: center; font-size: 15px; line-height: 1.4; }
.land-plan--featured .land-plan__list li:first-child .land-plan__e { color: var(--accent); font-weight: 800; }
.land-plan__cta { margin-top: auto; align-self: flex-start; }

/* BYOK section — the "get a key" CTA row (steps reuse .land-steps) */
.land-byok__cta { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 24px; }
.land-byok__cta code, .land-steps code, .land-twocosts code {
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
  border: 1px solid var(--border); border-radius: 6px; padding: 1px 6px; font-size: 0.9em;
}

/* "Two costs, plainly" honesty note */
.land-twocosts {
  display: flex; gap: 14px; align-items: flex-start; margin-top: 24px;
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 20%, var(--border));
  border-radius: 14px; padding: 18px 20px;
}
.land-twocosts__icon { font-size: 22px; line-height: 1.3; flex: none; }
.land-twocosts p { margin: 0; font-size: 15px; line-height: 1.55; }

/* FAQ — native details/summary, no JS */
.land-faq { display: flex; flex-direction: column; gap: 10px; margin-top: 24px; }
.land-faq__item {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 0 18px;
  transition: border-color .18s ease;
}
.land-faq__item[open] { border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); }
.land-faq__q {
  cursor: pointer; list-style: none; padding: 16px 0; font-weight: 700; font-size: 16.5px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.land-faq__q::-webkit-details-marker { display: none; }
.land-faq__q::after {
  content: "+"; flex: none; color: var(--accent); font-size: 22px; font-weight: 700; line-height: 1;
}
.land-faq__item[open] .land-faq__q::after { content: "\2212"; }
.land-faq__a { margin: 0; padding: 0 0 18px; color: var(--muted); line-height: 1.6; font-size: 15px; }

/* Decorative emoji marquee — CSS-only infinite loop, edges faded out. The track
   holds the (server-shuffled) set TWICE; translateX(-50%) scrolls exactly one
   copy so the wrap is seamless. Uniform size on purpose — varying per-item would
   break the seam, since the two copies must match. */
.land-marquee {
  overflow: hidden; margin: 6px 0 2px; padding: 8px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.land-marquee__track { display: flex; width: max-content; animation: land-marquee 46s linear infinite; }
.land-marquee:hover .land-marquee__track { animation-play-state: paused; }
.land-marquee__e { flex: none; padding: 6px 16px; font-size: 26px; line-height: 1; user-select: none; }
@keyframes land-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .land-marquee__track { animation: none; } }

/* ── Landing: glass-box hero visual, voice bubbles, "built in the open" ──────── */

/* The money shot — a CSS-only glass box (file tree + a rendered identity page) */
/* ── The hero glass box — the money shot ────────────────────────────────────
   A real agent's insides: a file tree beside a SOUL.md page the agent wrote about
   itself. The schematic pins (coral pill tags) annotate the transparency; the
   blink caret implies the live self-authoring. Warm paper + ink, gently tilted —
   the deliberate opposite of the dark-glass-gradient cliché every other AI ships. */
/* Left-align always — the hero centers its text on mobile, which must NOT leak into
   the glass box (it would centre the tree headings + page prose). */
.glass { position: relative; text-align: left; }
.glass__win {
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  overflow: hidden; transform: rotate(-0.6deg);
  box-shadow: 0 1.5px 0 color-mix(in srgb, var(--accent) 28%, transparent),
              0 30px 60px -20px rgba(0,0,0,0.30);
}
.glass__bar {
  display: flex; align-items: center; gap: 12px; padding: 10px 15px;
  background: color-mix(in srgb, var(--panel) 60%, var(--accent) 5%);
  border-bottom: 1px solid var(--border);
}
.glass__dots { display: inline-flex; gap: 6px; }
.glass__dots i { width: 10px; height: 10px; border-radius: 50%; background: color-mix(in srgb, var(--muted) 40%, var(--border)); }
.glass__crumb {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12.5px; color: var(--muted); font-weight: 600;
}
.glass__crumb .glass__sep { opacity: 0.45; margin: 0 3px; }
/* minmax(0, 1fr) on the page track: without the 0 min, the track sizes to its widest
   panel's content, so the whole box would resize each time you switch files. */
.glass__body { display: grid; grid-template-columns: 188px minmax(0, 1fr); }
.glass__tree {
  border-right: 1px solid var(--border); padding: 15px 13px;
  background: color-mix(in srgb, var(--panel) 45%, var(--surface));
}
.glass__group {
  margin: 14px 0 6px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--muted);
}
.glass__group:first-child { margin-top: 0; }
/* Each file is a real button (a tab) — click to open it in the page panel. */
.glass__file {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  font-size: 13.5px; padding: 6px 9px; margin: 1px 0; border-radius: 7px;
  color: var(--ink); background: none; border: 0; cursor: pointer;
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  transition: background-color .12s ease, color .12s ease;
}
.glass__file:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
.glass__file.is-active {
  background: color-mix(in srgb, var(--accent) 15%, var(--surface));
  color: var(--accent); font-weight: 600;
}
.glass__file:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.glass__page { padding: 20px 22px; min-width: 0; }
.glass__panel[hidden] { display: none; }
.glass__panel:focus { outline: none; }
/* IDENTITY.md — the spine, as key/value rows */
.glass__spine { margin: 0; display: flex; flex-direction: column; gap: 9px; }
.glass__spine > div { display: flex; gap: 12px; align-items: baseline; }
.glass__spine dt { flex: none; width: 86px; color: var(--muted); font-size: 12.5px; }
.glass__spine dd { margin: 0; font-size: 14.5px; line-height: 1.4; }
/* MEMORY.md — two bands + notes */
.glass__band {
  margin: 2px 0 7px; font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12.5px; font-weight: 600; color: var(--accent);
}
.glass__notes, .glass__agenda, .glass__lib {
  list-style: none; margin: 0 0 14px; padding: 0; display: flex; flex-direction: column; gap: 7px;
}
.glass__notes:last-child, .glass__agenda:last-child, .glass__lib:last-child { margin-bottom: 0; }
.glass__notes li, .glass__lib li { font-size: 14px; line-height: 1.45; }
.glass__notes li { display: flex; gap: 8px; }
.glass__notes li::before { content: "•"; color: var(--accent); flex: none; }
/* Calendar — a mini agenda */
.glass__agenda li { display: flex; gap: 12px; font-size: 14px; line-height: 1.4; align-items: baseline; }
.glass__when {
  flex: none; width: 54px; color: var(--accent); font-weight: 600;
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12.5px;
}
.glass__fname {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12px; color: var(--muted); margin: 0 0 13px;
  padding-bottom: 10px; border-bottom: 1px solid var(--border);
}
.glass__fname span { opacity: 0.8; }
.glass__panel > p:not(.glass__fname) { margin: 0 0 11px; font-size: 14.5px; line-height: 1.6; }
.glass__panel > p:last-child { margin-bottom: 0; }
.glass__caret {
  display: inline-block; width: 2px; height: 1.05em; vertical-align: -0.18em;
  margin-left: 2px; background: var(--accent); animation: glass-blink 1.1s steps(1) infinite;
}
@keyframes glass-blink { 50% { opacity: 0; } }
/* Schematic call-outs — the transparency, annotated. Hidden when the box stacks. */
.glass__pin {
  position: absolute; z-index: 1; white-space: nowrap;
  font-family: var(--font-display); font-size: 12px; font-weight: 700;
  color: var(--accent-ink); background: var(--accent); padding: 4px 11px; border-radius: 999px;
  box-shadow: 0 8px 20px -6px color-mix(in srgb, var(--accent) 65%, transparent);
}
.glass__pin--tree { left: 12px; top: -13px; transform: rotate(-1.6deg); }
.glass__pin--page { right: 12px; bottom: -13px; transform: rotate(1.6deg); }
@media (prefers-reduced-motion: reduce) { .glass__caret { animation: none; } }

/* Voice — a real thread: left-aligned messages with avatar, name, and time (matches the
   app's chat, where BOTH speakers are left-aligned — not opposing bubbles). */
.land-voice { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 26px; }
.land-chat {
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
  padding: 16px; display: flex; flex-direction: column; gap: 15px;
}
.lchat-msg { display: flex; gap: 11px; align-items: flex-start; }
.lchat-av {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center; font-size: 16px; line-height: 1;
  background: var(--bg); border: 1px solid var(--border);
}
.lchat-av--agent {
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  border-color: color-mix(in srgb, var(--accent) 24%, var(--border));
}
.lchat-body { min-width: 0; }
.lchat-meta { margin: 2px 0 3px; font-size: 12.5px; color: var(--muted); }
.lchat-meta b { color: var(--ink); font-weight: 700; font-family: var(--font-display); font-size: 13.5px; margin-right: 2px; }
.lchat-text { margin: 0; font-size: 14.5px; line-height: 1.5; }

/* Built in the open — essay cards */
.land-essays { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 26px; }
.land-essaycard {
  display: flex; flex-direction: column; gap: 8px; text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 20px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.03);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.land-essaycard:hover {
  transform: translateY(-4px); box-shadow: 0 14px 32px rgba(0,0,0,0.09);
  border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
}
.land-essaycard__meta { font-size: 12px; color: var(--muted); font-weight: 600; }
.land-essaycard h4 { margin: 0; font-size: 17px; line-height: 1.3; }
.land-essaycard p { margin: 0; font-size: 14px; line-height: 1.5; color: var(--muted); flex: 1; }
.land-essaycard__more { font-size: 14px; font-weight: 700; color: var(--accent); }
.land-openlinks { margin-top: 22px; }

/* BYOK "how to get a key" — collapsed by default to cut mid-page friction */
.land-byok__how { margin-top: 16px; }
.land-byok__how > summary {
  cursor: pointer; list-style: none; font-weight: 700; font-size: 15.5px; color: var(--accent);
  display: inline-flex; align-items: center; gap: 8px;
}
.land-byok__how > summary::-webkit-details-marker { display: none; }
.land-byok__how > summary::before { content: "+"; font-size: 18px; line-height: 1; }
.land-byok__how[open] > summary::before { content: "\2212"; }
.land-byok__how[open] > summary { margin-bottom: 4px; }

/* Hero stacks before the rest — the two-column stage needs width to breathe. */
@media (max-width: 880px) {
  .land-hero { grid-template-columns: 1fr; gap: 30px; text-align: center; padding-top: 22px; }
  .land-hero__lede { margin-left: auto; margin-right: auto; }
  .land-hero__cta { justify-content: center; }
  /* width:100% makes the width DEFINITE (viewport-driven, capped at 540). Without it,
     the auto side-margins de-stretch the grid item → it shrinks to content and the box
     resizes every time you open a different file. */
  .glass { width: 100%; max-width: 540px; margin: 18px auto 0; }
  .glass__win { transform: none; }
}
@media (max-width: 760px) {
  .glass__body { grid-template-columns: minmax(0, 1fr); }
  .glass__tree { border-right: none; border-bottom: 1px solid var(--border); }
  .land-voice, .land-essays { grid-template-columns: 1fr; }
}

/* ── Public footer ── */
.pub-foot { margin-top: 40px; padding: 28px 0 12px; border-top: 1px solid var(--border); }
.pub-foot__inner { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
.pub-foot__lockup { display: inline-flex; align-items: center; gap: 8px; text-decoration: none; color: var(--ink); }
.pub-foot__brand .brand-wordmark { font-family: var(--font-display); font-weight: 700; font-size: 18px; }
.pub-foot__tag { margin: 4px 0 0; font-size: 14px; }
.pub-foot__links { display: flex; flex-wrap: wrap; gap: 16px; }
.pub-foot__links a { color: var(--muted); text-decoration: none; font-size: 14px; }
.pub-foot__links a:hover { color: var(--accent); }

/* ============================================================
   Public docs site (/docs) — views/docs/page.hbs + services/docs.js.
   A three-column shell (sidebar nav · article · on-page TOC) inside the shared
   `main` layout. The docs need more room than the 980px reading frame, so we
   widen the .wrap ONLY when it contains .docs (header/footer stay aligned because
   they live in the same .wrap). :has() is universally supported now; if it ever
   isn't, docs just render in the narrower frame — cramped but functional.
============================================================ */
.wrap:has(.docs) { max-width: 1180px; }

.docs {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr) 196px;
  gap: 40px;
  align-items: start;
}
/* Below ~1080px the TOC is the first thing to go (it's a convenience, not nav). */
@media (max-width: 1080px) {
  .docs { grid-template-columns: 232px minmax(0, 1fr); gap: 32px; }
  .docs__toc { display: none; }
}

/* ── Sidebar (sticky on desktop) ─────────────────────────────────────────── */
.docs__rail {
  position: sticky; top: calc(var(--pub-head-h) + 16px);
  max-height: calc(100vh - var(--pub-head-h) - 32px); overflow-y: auto;
  padding-right: 4px;
}
.docs__search { position: relative; margin-bottom: 20px; }
.docs__search-field {
  display: flex; align-items: center; gap: 8px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); padding: 8px 11px;
}
.docs__search-field:focus-within { border-color: var(--accent); }
.docs__search-ic { color: var(--muted); font-size: 16px; }
.docs__search-input {
  border: 0; background: transparent; color: var(--ink);
  font: inherit; font-size: 14px; width: 100%; outline: none;
}
.docs__results {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 30;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18); padding: 6px; max-height: 60vh; overflow-y: auto;
}
.docs__result {
  display: block; padding: 9px 11px; border-radius: 8px;
  text-decoration: none; color: var(--ink);
}
.docs__result:hover, .docs__result.is-active { background: var(--bg); }
.docs__result-title { display: block; font-size: 14px; font-weight: 600; }
.docs__result-sub { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
.docs__result-title em { font-style: normal; color: var(--accent); }
.docs__results-empty { padding: 12px; font-size: 13px; color: var(--muted); }

.docs__nav-section { margin-bottom: 18px; }
.docs__nav-label {
  margin: 0 0 7px; font-size: 12px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--muted);
}
.docs__nav-list { list-style: none; margin: 0; padding: 0; }
.docs__nav-link {
  display: block; padding: 5px 10px; border-radius: 7px; margin-left: -10px;
  font-size: 14px; color: var(--ink); text-decoration: none; line-height: 1.35;
  border-left: 2px solid transparent;
}
.docs__nav-link:hover { background: var(--bg); }
.docs__nav-link.is-current {
  color: var(--accent); font-weight: 600;
  border-left-color: var(--accent); border-radius: 0 7px 7px 0; background: var(--bg);
}

/* Mobile nav toggle — revealed by docs.js on small screens (hidden otherwise). */
.docs__navtoggle {
  display: none; width: 100%; align-items: center; justify-content: space-between;
  gap: 8px; padding: 10px 12px; margin-bottom: 14px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--ink); font: inherit; font-size: 14px; cursor: pointer;
}
.docs__navtoggle-ic { transition: transform 0.15s ease; }
.docs__navtoggle[aria-expanded="true"] .docs__navtoggle-ic { transform: rotate(180deg); }

/* ── Article ─────────────────────────────────────────────────────────────── */
.docs__article { min-width: 0; max-width: 760px; padding-bottom: 8px; }
.docs__crumb { margin: 0 0 6px; font-size: 13px; font-weight: 600; }
.docs__title { font-size: 36px; line-height: 1.12; margin: 0 0 10px; letter-spacing: -0.015em; }
.docs__lede { margin: 0 0 8px; font-size: 18px; color: var(--muted); line-height: 1.5; }

/* ── Prose (rendered markdown) ───────────────────────────────────────────── */
.prose { line-height: 1.68; font-size: 16px; }
.prose > :first-child { margin-top: 0; }
/* No divider rule above section headings — it made one continuous essay/doc read
   like separate stacked sections. Spacing alone (the top margin) signals a new
   section; the page stays visibly continuous. */
.prose h2 {
  font-size: 25px; line-height: 1.25; margin: 44px 0 12px; scroll-margin-top: calc(var(--pub-head-h) + 24px);
}
.prose h3 { font-size: 19px; margin: 28px 0 8px; scroll-margin-top: calc(var(--pub-head-h) + 24px); }
.prose h4 { font-size: 16px; margin: 22px 0 6px; scroll-margin-top: calc(var(--pub-head-h) + 24px); }
.prose p { margin: 0 0 16px; }
.prose ul, .prose ol { margin: 0 0 16px; padding-left: 22px; }
.prose li { margin: 6px 0; }
.prose li > ul, .prose li > ol { margin: 6px 0; }
.prose a { color: var(--accent); text-decoration: none; }
.prose a:hover { text-decoration: underline; }
.prose strong { font-weight: 700; }
.prose code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.88em;
  background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 1px 5px;
}
.prose pre {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 14px 16px; overflow-x: auto; margin: 0 0 16px;
}
.prose pre code { background: none; border: 0; padding: 0; font-size: 13.5px; }
.prose hr { border: 0; border-top: 1px solid var(--border); margin: 32px 0; }
/* Blockquotes double as status callouts (e.g. "designed, not yet shipped"). */
.prose blockquote {
  margin: 0 0 16px; padding: 12px 16px;
  border-left: 3px solid var(--accent); border-radius: 0 8px 8px 0;
  background: var(--bg); color: var(--ink);
}
.prose blockquote p { margin: 0 0 6px; }
.prose blockquote p:last-child { margin-bottom: 0; }
.prose table {
  width: 100%; border-collapse: collapse; margin: 0 0 16px; font-size: 14.5px;
}
.prose th, .prose td { text-align: left; padding: 8px 12px; border: 1px solid var(--border); }
.prose th { background: var(--bg); font-weight: 700; }

/* ── Prev / next pager ───────────────────────────────────────────────────── */
.docs__pager {
  display: flex; justify-content: space-between; gap: 16px;
  margin-top: 40px; padding-top: 20px; border-top: 1px solid var(--border);
}
.docs__pager-link {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 16px; border: 1px solid var(--border); border-radius: 12px;
  text-decoration: none; color: var(--ink); max-width: 48%;
}
.docs__pager-link:hover { border-color: var(--accent); }
.docs__pager-next { margin-left: auto; text-align: right; }
.docs__pager-text { display: flex; flex-direction: column; }
.docs__pager-dir { font-size: 12px; color: var(--muted); }
.docs__pager-title { font-size: 15px; font-weight: 600; }
.docs__pager-ic { color: var(--muted); font-size: 18px; flex: none; }

/* ── On-page TOC (sticky, scroll-spied) ──────────────────────────────────── */
.docs__toc { position: sticky; top: calc(var(--pub-head-h) + 16px); max-height: calc(100vh - var(--pub-head-h) - 32px); overflow-y: auto; }
.docs__toc-label {
  margin: 0 0 8px; font-size: 12px; font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--muted);
}
.docs__toc-list { list-style: none; margin: 0; padding: 0; }
.docs__toc-item--h3 { padding-left: 12px; }
.docs__toc-link {
  display: block; padding: 4px 0; font-size: 13px; line-height: 1.4;
  color: var(--muted); text-decoration: none;
}
.docs__toc-link:hover { color: var(--ink); }
.docs__toc-link.is-active { color: var(--accent); font-weight: 600; }

/* ── Mobile (< 880px) ────────────────────────────────────────────────────── */
@media (max-width: 880px) {
  .docs { display: block; }
  .docs__rail {
    position: static; max-height: none; overflow: visible;
    margin-bottom: 24px; padding-bottom: 20px; border-bottom: 1px solid var(--border);
  }
  .docs__title { font-size: 30px; }
  .docs__pager-link { max-width: 49%; }
  /* The nav toggle is desktop-hidden by its base rule; show it on mobile. docs.js
     collapses .docs__nav and wires the toggle to open/close it. Without JS the nav
     just stays visible below the (inert) toggle, so it still degrades gracefully. */
  .docs__navtoggle { display: flex; }
}
.pub-foot__copy { margin: 20px 0 0; font-size: 13px; }

/* ── Post library (/posts) — private builder tool: an X posting schedule with copy
      buttons + live char counts. ── */
.pl { max-width: 760px; margin: 0 auto; }
.pl-head { padding: 4px 0 24px; border-bottom: 1px solid var(--border); margin-bottom: 8px; }
.pl-back {
  display: inline-flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 600;
  color: var(--muted); text-decoration: none; margin-bottom: 10px;
}
.pl-back:hover { color: var(--accent); }
.pl-back__ic { width: 14px; height: 14px; }
.pl-head__title { font-size: 34px; margin: 0 0 6px; }
.pl-head__sub { font-size: 15px; margin: 0 0 12px; }
.pl-rules { font-size: 13.5px; line-height: 1.5; margin: 0; padding-left: 18px; }
.pl-rules li { margin: 3px 0; }

.pl-week { margin: 28px 0 0; }
.pl-week__label {
  font-size: 13px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); margin: 0 0 12px; font-weight: 700;
}

.pl-card {
  border: 1px solid var(--border); border-radius: 14px; background: var(--panel);
  padding: 16px 16px 18px; margin: 0 0 16px;
}
.pl-card--over { border-color: var(--accent); }
.pl-card.is-posted { opacity: 0.5; }
.pl-card__head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.pl-day {
  font-size: 12px; font-weight: 700; color: var(--accent);
  background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px;
}
.pl-type {
  font-size: 12px; font-weight: 600; color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px; white-space: nowrap;
}
.pl-type--thread { color: var(--accent); }
.pl-card__topic { font-size: 17px; margin: 0; flex: 1 1 auto; min-width: 0; }
.pl-done { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--muted); cursor: pointer; user-select: none; }
.pl-done__cb { accent-color: var(--accent); }
.pl-note {
  font-size: 13.5px; line-height: 1.5; color: var(--ink); margin: 10px 0 0;
  padding: 9px 12px; background: var(--bg); border-left: 3px solid var(--accent); border-radius: 0 8px 8px 0;
}
.pl-card__actions { margin: 12px 0 0; }

.pl-tweets { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.pl-tweet { border: 1px solid var(--border); border-radius: 10px; background: var(--bg); overflow: hidden; }
.pl-tweet.is-over { border-color: var(--accent); }
.pl-tweet__text {
  white-space: pre-wrap; word-break: break-word; font-size: 14.5px; line-height: 1.5;
  padding: 12px 13px; margin: 0;
}
.pl-tweet__foot {
  display: flex; align-items: center; gap: 8px; padding: 7px 10px;
  border-top: 1px solid var(--border); background: var(--surface);
}
.pl-tweet__count { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.pl-tweet.is-over .pl-tweet__count { color: var(--accent); font-weight: 700; }
.pl-tweet__spacer { flex: 1 1 auto; }

.pl-btn {
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  border: 1px solid var(--border); border-radius: 8px; padding: 5px 11px;
  background: var(--bg); color: var(--ink); text-decoration: none; white-space: nowrap;
}
.pl-btn:hover { border-color: var(--accent); color: var(--accent); }
.pl-btn--ghost { background: transparent; color: var(--muted); }
.pl-btn.is-flash { border-color: var(--accent); color: var(--accent); }
.pl-copyall { background: var(--bg); }

@media (max-width: 560px) {
  .pl-head__title { font-size: 27px; }
  .pl-card__topic { flex-basis: 100%; order: 3; }
}

/* ── Essays (/essays) — reuses the docs three-column shell; only the dated byline
      under the title is essay-specific. ── */
.esy-byline { font-size: 13.5px; margin: 0 0 14px; letter-spacing: 0.01em; }
.esy-byline time { white-space: nowrap; }

/* Reframe banner on pal.fun-era essays (the pre-pivot chapter). A quiet accent-tinted
   note under the lede that places the essay in the timeline and points at the pivot —
   the essay body itself is preserved untouched, so this chrome does all the reframing. */
.esy-era-note {
  margin: 4px 0 18px; padding: 11px 14px; font-size: 14px; line-height: 1.5;
  color: var(--muted); border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 8px; background: color-mix(in srgb, var(--accent) 5%, var(--surface));
}
.esy-era-note strong { color: var(--ink); font-weight: 600; }
.esy-era-note a { color: var(--accent); font-weight: 600; text-decoration: none; white-space: nowrap; }
.esy-era-note a:hover { text-decoration: underline; }

/* ── Legal / contact pages ── */
.legal { max-width: 760px; margin: 0 auto; line-height: 1.65; }
.legal--narrow { max-width: 620px; }
.legal__head { padding: 8px 0 28px; border-bottom: 1px solid var(--border); margin-bottom: 24px; }
.legal__head h1 { font-size: 38px; margin: 0 0 6px; }
.legal__intro { font-size: 17px; }
.legal__section { margin: 28px 0; }
.legal__section h2 { font-size: 21px; margin: 0 0 10px; }
.legal__section ul { padding-left: 22px; display: flex; flex-direction: column; gap: 7px; }
.legal__section li { line-height: 1.55; }
.contact-email {
  display: flex; align-items: center; gap: 10px; font-size: 20px; margin: 18px 0 28px;
}
.contact-email .icon { color: var(--accent); width: 22px; height: 22px; }

/* ── Public responsive ── */
@media (max-width: 760px) {
  .land-hero__title { font-size: 38px; }
  .land-hero__lede { font-size: 18px; }
  .land-plans { grid-template-columns: 1fr; }
  .land-section { padding: 36px 0; }
  .land-section--alt { padding: 32px 22px; }
  .land-section h2 { font-size: 25px; }
  .legal__head h1 { font-size: 30px; }
}

/* ── Admin-gated pages (/architecture) — shared page + picker + detail chrome ──
   (Was the pal.fun-era /admin ops+context CSS; trimmed to what /architecture reuses when
   that surface was retired 2026-08-13. .adm-* = the page shell; .ictx-* = the picker + the
   runtime detail's <details> section/tool chrome.) */
.adm { max-width: 980px; margin: 0 auto; padding: 8px 0 40px; }
.adm-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 24px; }
.adm-head h1 { font-family: var(--font-display); font-weight: 800; font-size: 26px; margin: 0; }
.adm-back { color: var(--muted); text-decoration: none; font-size: 14px; }
.adm-back:hover { color: var(--accent); }
.adm-stat {
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 16px 18px; display: flex; flex-direction: column; gap: 2px;
}
.adm-num { font-family: var(--font-display); font-weight: 800; font-size: 28px; letter-spacing: -0.02em; }
.adm-lbl { color: var(--muted); font-size: 13px; }
.adm-note {
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 20%, var(--border));
  border-radius: 12px; padding: 14px 16px; font-size: 14px; line-height: 1.55; color: var(--muted);
  margin: 0 0 26px;
}
.adm-note strong { color: var(--ink); }
.adm-empty { color: var(--muted); font-size: 14px; }

/* ── /admin/access — the access-request queue (routes/adminAccess.js) ──
   Rides the .adm shell + shared .btn buttons; theme-safe by vars only. */
.acq .adm-head { justify-content: flex-start; align-items: center; gap: 16px; }
.acq-banner {
  border-radius: 12px; padding: 12px 16px; font-size: 14px; line-height: 1.5; margin: 0 0 18px;
  background: color-mix(in srgb, var(--accent) 9%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--border));
}
.acq-banner.is-err {
  background: color-mix(in srgb, var(--error) 9%, var(--surface));
  border-color: color-mix(in srgb, var(--error) 30%, var(--border));
  color: var(--error);
}
.acq-sec { margin: 0 0 30px; }
.acq-sec__title {
  font-family: var(--font-mono); font-size: 12px; font-weight: 600; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 10px;
}
.acq-row {
  display: flex; gap: 16px; align-items: flex-start; justify-content: space-between;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px 16px; margin: 0 0 10px;
}
.acq-row.is-handled { opacity: 0.75; background: color-mix(in srgb, var(--surface) 60%, var(--bg)); }
.acq-row__body { min-width: 0; flex: 1 1 auto; }
.acq-row__who { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; font-size: 15px; }
.acq-row__email { font-family: var(--font-mono); font-size: 13px; color: var(--muted); user-select: all; }
.acq-row__biz { font-size: 14px; margin-top: 3px; }
.acq-row__link {
  display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13px; color: var(--accent); text-decoration: none; margin-top: 2px;
}
.acq-row__link:hover { text-decoration: underline; }
.acq-row__note { font-size: 13.5px; color: var(--muted); line-height: 1.5; margin: 6px 0 0; white-space: pre-wrap; }
.acq-row__meta { font-family: var(--font-mono); font-size: 11.5px; color: var(--muted); margin-top: 8px; }
.acq-pill {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px; color: var(--muted); white-space: nowrap;
}
.acq-pill--granted { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); color: var(--accent); }
.acq-pill--active { border-color: color-mix(in srgb, seagreen 45%, var(--border)); color: color-mix(in srgb, seagreen 70%, var(--ink)); }
.acq-pill--dismissed { opacity: 0.8; }
.acq-row__acts { display: flex; flex-direction: column; align-items: stretch; gap: 8px; flex: none; }
.acq-row__acts .btn { text-align: center; white-space: nowrap; }
.acq-fund { display: flex; gap: 6px; }
.acq-fund__amt {
  width: 72px; padding: 6px 10px; font-size: 14px; font-family: var(--font-mono);
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px; color: var(--ink);
}
@media (max-width: 640px) {
  .acq-row { flex-direction: column; }
  .acq-row__acts { flex-direction: row; flex-wrap: wrap; align-items: center; }
}

.ictx-picker { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 22px; }
.ictx-pick {
  display: flex; flex-direction: column; gap: 1px; text-decoration: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 8px 12px; color: var(--ink); font-size: 14px;
}
.ictx-pick:hover { border-color: var(--accent); }
.ictx-pick.is-active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 9%, var(--surface)); }
.ictx-pick-name { font-weight: 700; }
.ictx-pick-meta { color: var(--muted); font-size: 12px; font-family: var(--font-mono, ui-monospace, monospace); }
.ictx-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 22px; }
.ictx-stats .adm-num { font-size: 18px; }
.ictx-block { margin-bottom: 28px; }
.ictx-block > h2 { font-size: 18px; margin: 0 0 4px; }
.ictx-hint { color: var(--muted); font-size: 13px; line-height: 1.5; margin: 0 0 12px; }
.ictx-chars { color: var(--muted); font-size: 12px; font-weight: 400; font-family: var(--font-mono, ui-monospace, monospace); }
.ictx-sec {
  border: 1px solid var(--border); border-radius: 10px; margin-bottom: 8px; background: var(--surface);
}
.ictx-sec > summary {
  cursor: pointer; padding: 10px 14px; font-weight: 600; list-style: none;
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
}
.ictx-sec > summary::-webkit-details-marker { display: none; }
.ictx-sec[open] > summary { border-bottom: 1px solid var(--border); }
.ictx-sec .ictx-hint { padding: 10px 14px 0; }
.ictx-pre {
  margin: 0; padding: 12px 14px; white-space: pre-wrap; word-break: break-word;
  font-family: var(--font-mono, ui-monospace, monospace); font-size: 12.5px; line-height: 1.5;
  color: var(--ink); overflow-x: auto;
}
@media (max-width: 760px) { .ictx-stats { grid-template-columns: repeat(2, 1fr); } }

/* ── Architecture (/architecture) — the COMPANY ORG CHART of all model runtimes ──
   Dustav.com as a company: the corner office on top, departments beneath, connector lines drawn
   with pseudo-elements (no JS, CSP-safe — no style attrs anywhere). Every hire is a living Blink
   mark ({{> brand-mark-svg}} inside .org-card__face) tinted by --cat: the .cat--<hue> classes
   from the shared calendar palette set it per seat; .org supplies the accent default so the CEO
   (and the bar track's color-mix) always resolve. The idle-tic animations ride .brand-mark-svg
   for free (app.css above); the per-seat NEGATIVE animation-delays below desync the blinking so
   the office doesn't blink in lockstep. Size bar = inline SVG, rect width is a presentation attr. */
/* Full-bleed, full-height shell (index only — the drill-in .arch-detail has its own layout below):
   the page escapes .wrap entirely, the header row pins on top, and .org-scroll fills the REST of
   the viewport — so its scrollbars hug the window edges instead of floating mid-page. */
.wrap:has(.adm.arch:not(.arch-detail)) { max-width: none; padding: 0; }
.adm.arch:not(.arch-detail) {
  max-width: none; margin: 0; padding: 14px 0 0;
  height: 100dvh; display: flex; flex-direction: column; overflow: hidden;
}
.adm.arch:not(.arch-detail) > .adm-head,
.adm.arch:not(.arch-detail) > .ictx-picker,
.adm.arch:not(.arch-detail) > .adm-empty { flex: none; padding-left: 24px; padding-right: 24px; }
/* One header row: the back pill sits inline with the title (no stacked headspace). */
.arch .adm-head { justify-content: flex-start; align-items: center; gap: 16px; margin-bottom: 12px; }
.arch-back { margin: 0; }
.arch .adm-head h1 { display: flex; align-items: center; gap: 9px; }
.arch .adm-head .brand-mark-svg { width: 23px; height: 23px; }
/* The large canvas: fills the viewport below the header, scrolls BOTH axes, and pans by grab
   (public/js/org-chart.js flips .is-panning; touch pans natively). The chart keeps a roomy
   min-width and scrolls sideways rather than squeezing. */
.org-scroll { flex: 1 1 0; min-height: 0; overflow: auto; cursor: grab; }
.org-scroll.is-panning { cursor: grabbing; user-select: none; }
.org { --cat: var(--accent); min-width: 1608px; padding: 6px 24px 40px; }   /* 1560px canvas + the 24px side gutters */
.org-ceo { display: flex; justify-content: center; }
.org-ceo .org-card { width: min(500px, 100%); }
.org-drop { width: 2px; height: 34px; background: var(--border); margin: 0 auto; }
.org-depts { display: flex; align-items: stretch; }
.org-dept { flex: 1 1 0; min-width: 0; position: relative; padding: 32px 14px 0; }
/* The rail: each dept carries a full-width top line; the first/last halves are trimmed so the
   line spans exactly from the first dept's stub to the last's. Plus a stub down to each head. */
.org-dept::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: var(--border); }
.org-dept:first-child::before { left: 50%; }
.org-dept:last-child::before { right: 50%; }
.org-dept::after { content: ""; position: absolute; top: 0; left: 50%; margin-left: -1px; width: 2px; height: 16px; background: var(--border); }
.org-dept__head { text-align: center; margin: 0 0 14px; }
.org-dept__head h2 {
  margin: 0; font-family: var(--font-mono, ui-monospace, monospace); font-size: 12px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink);
}
.org-dept__blurb { display: block; color: var(--muted); font-size: 12.5px; line-height: 1.45; margin-top: 4px; }
.org-card {
  position: relative; display: block; text-decoration: none; color: inherit; background: var(--surface);
  border: 1px solid var(--border); border-top: 3px solid var(--cat); border-radius: 14px;
  padding: 20px 22px; transition: border-color 0.12s, transform 0.12s;
}
.org-card:hover { border-color: var(--cat); transform: translateY(-1px); }
.org-card.is-off { opacity: 0.55; }
/* A second hire in the same dept chains below the first with its own connector stub. */
.org-card + .org-card { margin-top: 30px; }
.org-card + .org-card::before { content: ""; position: absolute; top: -31px; left: 50%; margin-left: -1px; width: 2px; height: 30px; background: var(--border); }
.org-card__id { display: flex; align-items: center; gap: 13px; margin-bottom: 14px; }
.org-card__face { flex: none; display: flex; }
.org-card .brand-mark-svg { width: 50px; height: 50px; }
.org-card--ceo .brand-mark-svg { width: 68px; height: 68px; }
/* Each seat's Blink wears its department hue — set as `color`, since .blink__head fills currentColor now. */
.org-card .blink { color: var(--cat); }
.org-card__who { min-width: 0; }
.org-card__title { display: block; font-weight: 800; font-size: 16px; line-height: 1.22; }
.org-card--ceo .org-card__title { font-size: 19px; }
.org-card__role {
  display: block; color: var(--cat); font-size: 11px; font-family: var(--font-mono, ui-monospace, monospace);
  letter-spacing: 0.06em; text-transform: uppercase; margin-top: 4px;
}
.org-card__num { display: flex; align-items: baseline; gap: 7px; }
.org-card__tok {
  font-family: var(--font-display); font-weight: 800; font-size: 30px; letter-spacing: -0.02em;
  line-height: 1; color: var(--ink);
}
.org-card--ceo .org-card__tok { font-size: 36px; }
.org-card__tok--na { color: var(--muted); }
.org-card__toklbl { color: var(--muted); font-size: 12px; }
.arch-bar { display: block; width: 100%; height: 5px; margin: 10px 0 13px; }
.arch-bar__track { fill: color-mix(in srgb, var(--cat) 14%, transparent); }
.arch-bar__fill { fill: var(--cat); }
.org-card__meta {
  display: flex; flex-wrap: wrap; gap: 5px 12px; color: var(--muted); font-size: 12px;
  font-family: var(--font-mono, ui-monospace, monospace);
}
.org-card__meta code { font-family: inherit; color: var(--ink); }
.org-card__when { color: var(--muted); font-size: 13px; line-height: 1.5; margin: 10px 0 0; }
/* Desync the office: stagger each dept's idle tics (eyes + face together) so nobody blinks in lockstep —
   through --blink-delay, the one desync mechanism every blink rule reads. These used to override
   animation-delay directly, which only landed because a :nth-child descendant out-specifies the
   host-scoped rules; an inherited variable doesn't have to win a cascade race to work. The sibling rule
   still sits AFTER the dept rules so, at equal specificity, it wins on the second card. */
.org-dept:nth-child(2) { --blink-delay: -1.3s; }
.org-dept:nth-child(3) { --blink-delay: -2.7s; }
.org-dept:nth-child(4) { --blink-delay: -4.1s; }
.org-dept:nth-child(5) { --blink-delay: -5.6s; }
.org-card + .org-card { --blink-delay: -7.9s; }
/* Narrow: drop the canvas floor + the connectors, wrap/stack the departments instead of scrolling. */
@media (max-width: 1080px) {
  .org { min-width: 0; }
  .org-drop, .org-dept::before, .org-dept::after { display: none; }
  .org-depts { flex-wrap: wrap; gap: 22px 14px; margin-top: 26px; }
  .org-dept { flex: 1 1 300px; padding: 0; }
}

/* ── /architecture drill-in — the glass-box reader, tinted per seat ──
   Folded from the retired Context tab (2026-08-18). TWO PANELS on desktop, exactly the old tab's
   full-pane grid: the section menu left (runtime identity at its top), the reader right — the
   GLOBAL ≥880 .glass-list/.glass-reader rules already draw both as full-height scrollers with the
   sticky head + centered reading column, so this block only makes the page full-bleed, lays the
   grid, and points the accents at --cat (the seat's org-chart hue). No stats row — those numbers
   live on the org-chart cards. */
.cat--accent { --cat: var(--accent); }   /* the "brand" seat — what the CEO wears */
.arch-detail .blink { color: var(--cat); }   /* the drill-in's mark, same seat hue (colour, not fill) */
.arch-detail__top { margin: 2px 2px 22px; }
.arch-detail__id { display: flex; align-items: center; gap: 13px; margin: 16px 0 10px; }
.arch-detail__id .brand-mark-svg { width: 46px; height: 46px; flex: none; }
.arch-detail__who { min-width: 0; }
.arch-detail__who h1 { font-family: var(--font-display); font-weight: 800; font-size: 22px; line-height: 1.15; margin: 0; }
.arch-detail__role {
  display: block; margin-top: 4px; color: var(--cat);
  font-family: var(--font-mono, ui-monospace, monospace); font-size: 11px;
  letter-spacing: 0.06em; text-transform: uppercase;
}
.arch-detail__when { color: var(--muted); font-size: 12.5px; line-height: 1.55; margin: 0 0 6px; }
.arch-detail__when code { font-family: var(--font-mono, ui-monospace, monospace); font-size: 11.5px; color: var(--ink); background: none; padding: 0; }
.arch-glass .glass-item__icon { color: var(--cat); }
.arch-glass .glass-item:hover, .arch-glass .glass-item.is-active { border-color: var(--cat); }
.arch-glass .glass-item.is-live .glass-item__icon { color: var(--cat); }
@media (min-width: 880px) {
  .wrap:has(.arch-detail) { max-width: none; padding: 0; }
  .adm.arch.arch-detail { max-width: none; margin: 0; padding: 0; }
  .arch-glass { display: grid; grid-template-columns: 480px minmax(0, 1fr); }
  .arch-glass .glass-list { padding-top: 12px; }   /* the global 28px reads as dead air above the back pill */
  .arch-glass .glass-reader__back { display: none; }
  /* Full-width reading: kill the centered --content-max column the global reader rules draw —
     the prose spans the pane with only a slim inset off the divider. */
  .arch-glass .glass-reader__headinner,
  .arch-glass .glass-reader__body .md,
  .arch-glass .glass-reader__empty { max-width: none; margin: 0; }
  .arch-glass .glass-reader__headinner { padding: 0 22px; }
  .arch-glass .glass-reader__body .md { padding: 24px 22px 56px; }
  .arch-glass .glass-reader__empty { padding: 24px 22px; }
}

/* ── Usage & cost (/app/usage) — the human-facing cost report ──────────────────
   Card-less "spec sheet": the hero + the recent feed sit flat on the page background (no boxes).
   The house voice — a heavy display headline over spaced-out uppercase MONO micro-labels (the
   eyebrow, the section tag, the column heads), Ocean used sparingly, tabular figures everywhere so
   dollar amounts align. Mirrors the landing-page aesthetic (memory: homepage-direction). */
.usage { display: flex; flex-direction: column; gap: 30px; }
/* Kill the .settings-section card the recent feed rides in — flat on the background, tight stack. */
.usage .settings-section { background: none; border: none; border-radius: 0; padding: 0; gap: 0; }

/* A shared mono micro-label — the spaced uppercase tag that IS the brand signature. */
.usage-hero__eyebrow,
.usage .settings-section h2,
.usage-chart__eyebrow,
.usage-feed__head {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.14em; color: var(--muted);
}

/* Hero — the lifetime headline, flat on the background so the big number just sings. */
.usage-hero { display: flex; flex-direction: column; gap: 6px; }
.usage-hero__eyebrow { margin-bottom: 4px; }
.usage-hero__num {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(44px, 10vw, 60px); line-height: 0.95; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -0.03em;
}
.usage-hero--empty { align-items: flex-start; }
.usage-empty { margin: 4px 0 0; color: var(--muted); font-size: 15px; line-height: 1.55; max-width: 52ch; }

/* ── Spending-over-time bar chart ─────────────────────────────────────────────
   Flat on the page like the rest of the spec sheet. Bars paint from --accent (geometry set as SVG
   attributes by dashboard/usage.js — CSP-safe), the axis + labels are muted hairlines/mono. */
.usage-chart { display: flex; flex-direction: column; gap: 12px; }
.usage-chart__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
/* The day/week/month/year segmented toggle — outlined mono segments, the active one accent-washed. */
.usage-chart__toggle { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.usage-chart__seg {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--muted); background: var(--surface);
  border: 0; border-left: 1px solid var(--border); padding: 6px 12px; cursor: pointer;
  transition: color 0.12s, background 0.12s;
}
.usage-chart__seg:first-child { border-left: 0; }
.usage-chart__seg:hover { color: var(--ink); }
.usage-chart__seg.is-on { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
/* The hovered-bucket / summary readout — a mono line under the toggle. */
.usage-chart__readout {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.02em; color: var(--muted);
  font-variant-numeric: tabular-nums; min-height: 16px;
}
.usage-chart__svg { width: 100%; height: auto; display: block; overflow: visible; }
/* Bars — accent fill, a touch translucent at rest so the hovered one pops to full. */
.usage-chart__bar { fill: var(--accent); opacity: 0.82; transition: opacity 0.12s; }
.usage-chart__bar:hover, .usage-chart__bar.is-hover { opacity: 1; }
.usage-chart__grid { stroke: var(--border); stroke-width: 1; stroke-dasharray: 3 4; }
.usage-chart__axis { stroke: var(--border); stroke-width: 1; }
.usage-chart__peak { fill: var(--muted); font-family: var(--font-mono); font-size: 10px; font-variant-numeric: tabular-nums; }
.usage-chart__xlabel { fill: var(--muted); font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.02em; }

@media (prefers-reduced-motion: reduce) {
  .usage-chart__bar, .usage-chart__seg { transition: none; }
}

/* Recent feed — a spec-sheet table: mono column heads under a hairline, one slim row per turn. */
.usage .settings-section > h2 { margin: 0 0 14px; }
/* Drill-down section head — the mono label + its little "i" explanation on one row (the prose that
   used to sit inline now lives behind the info popover, dashboard/info-pop.js). */
.usage-sec-head { display: flex; align-items: center; gap: 7px; margin: 0 0 14px; }
.usage-sec-head h2 { margin: 0; }
.usage-sec-head .set-info__btn { width: 16px; height: 16px; }
.usage-sec-head .set-info__btn .icon { width: 14px; height: 14px; }
/* The header summary line's "i" sits inline with the muted text. */
.usage-turn-head .set-info { vertical-align: -2px; margin-left: 2px; }
.usage-feed__head {
  display: grid; grid-template-columns: 72px auto 1fr auto 16px; align-items: center; gap: 12px;
  padding: 0 4px 8px; border-bottom: 1px solid var(--border);
}
.usage-feed__head-cost { text-align: right; }
.usage-feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.usage-feed__row {
  display: grid; grid-template-columns: 72px auto 1fr auto; align-items: center; gap: 12px;
  padding: 12px 4px; border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  font-size: 14px;
}
.usage-feed__row:last-child { border-bottom: none; }
.usage-feed__when {
  color: var(--muted); font-family: var(--font-mono); font-size: 11px;
  letter-spacing: 0.03em; text-transform: uppercase; font-variant-numeric: tabular-nums;
}
.usage-feed__agent { display: flex; align-items: center; gap: 7px; min-width: 0; }
.usage-feed__face { font-size: 18px; line-height: 1; }
.usage-feed__name { font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.usage-feed__model {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted); opacity: 0.75;
}
.usage-feed__act { display: flex; flex-wrap: wrap; gap: 5px; min-width: 0; }
.usage-feed__chat { color: var(--muted); font-family: var(--font-mono); font-size: 11px; opacity: 0.6; }
.usage-feed__cost {
  font-family: var(--font-mono); font-weight: 700; font-size: 15px; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em; text-align: right;
}
/* Metered ledger: a Balance column (the prepaid credits AFTER each row) slots in between Cost and the
   chevron — so the grid grows from 5 to 6 columns only under metered billing (.usage.is-metered). */
.usage.is-metered .usage-feed__head,
.usage.is-metered .usage-feed__row--link { grid-template-columns: 72px auto 1fr auto auto 16px; }
.usage-feed__bal {
  font-family: var(--font-mono); font-weight: 600; font-size: 13px; color: var(--muted);
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em; text-align: right;
}
/* A prepaid top-up row — a credit, not a cost: the amount reads green (added to balance), not ink. */
.usage-feed__cost--credit { color: #1c8f7a; }
.usage-tag--credit { color: #1c8f7a; border-color: color-mix(in srgb, #1c8f7a 40%, transparent); }
/* The hero "Balance" headline — the current prepaid credits, under the total-billed number. */
.usage-hero__bal { display: inline-flex; align-items: baseline; gap: 8px; margin: 2px 0 4px; }
.usage-hero__bal-label {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted);
}
.usage-hero__bal-num { font-size: 20px; font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; }
/* "Top up →" — a small accent link beside the balance → the account billing card (the Stripe top-up flow). */
.usage-hero__topup {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
  color: var(--accent); text-decoration: none; margin-left: 6px; white-space: nowrap;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); border-radius: 5px; padding: 2px 8px;
  transition: background 0.12s, border-color 0.12s;
}
.usage-hero__topup:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
/* The per-category breakdown, tucked behind a native <details> disclosure so the hero stays clean. */
.usage-hero__details { margin-top: 10px; }
.usage-hero__summary {
  display: inline-flex; align-items: center; gap: 5px; width: fit-content; cursor: pointer;
  list-style: none; user-select: none;
  font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted); transition: color 0.12s;
}
.usage-hero__summary::-webkit-details-marker { display: none; }
.usage-hero__summary::marker { content: ""; }
.usage-hero__summary:hover { color: var(--ink); }
.usage-hero__summary-chev { display: inline-flex; transition: transform 0.15s; }
.usage-hero__summary-chev .icon { width: 14px; height: 14px; }
.usage-hero__details[open] .usage-hero__summary-chev { transform: rotate(90deg); }
.usage-hero__breakdown { display: grid; gap: 0; margin-top: 8px; max-width: 400px; }
.usage-hero__stat {
  display: grid; grid-template-columns: 1fr auto auto; align-items: baseline; gap: 12px;
  padding: 7px 2px; border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
}
.usage-hero__stat:last-child { border-bottom: none; }
.usage-hero__stat-lbl { color: var(--ink); font-weight: 600; font-size: 13px; }
.usage-hero__stat-n {
  font-family: var(--font-mono); font-weight: 700; font-size: 14px; color: var(--ink);
  font-variant-numeric: tabular-nums; text-align: right;
}
.usage-hero__stat-per {
  font-family: var(--font-mono); font-size: 11px; color: var(--muted); opacity: 0.85;
  font-variant-numeric: tabular-nums; text-align: right; min-width: 70px;
}
/* Activity tags — outlined mono micro-chips (the spec-sheet tag, not a filled pill). */
.usage-tag {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
  border-radius: 5px; padding: 2px 7px; white-space: nowrap;
}
.usage-tag--reach { color: var(--muted); border-color: color-mix(in srgb, var(--border) 90%, var(--muted)); }
/* A daily inbox-scan row (a Gmail digest run) — a background cost with no spoken turn. The tag reads
   accent (it's real agent work), with a muted "N scanned · M flagged" tail beside it. */
.usage-tag--scan { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
/* email-rule extraction (docs/PLAYGROUND.md) — a background cost source like a scan; teal to set it
   apart from the accent-coloured inbox scan, sharing the muted "N rows filed" tail. */
.usage-tag--extract { color: #1c8f7a; border-color: color-mix(in srgb, #1c8f7a 40%, transparent); }
/* A relevance-gate "passed" step in the sync drill-down (docs/USAGE_AUDIT.md F2-triage) — muted (it's a
   SKIPPED read, not spend), and the reason is a sentence, so let it wrap + drop the uppercase/nowrap. */
.usage-tag--pass {
  color: var(--muted); border-color: color-mix(in srgb, var(--border) 90%, var(--muted));
  text-transform: none; letter-spacing: 0; white-space: normal; font-weight: 500;
}
/* The scan/extract detail line (e.g. "1 row filed · Fwd: <long subject>"). Sits on its own line under
   the tag (flex-basis:100%) and truncates with an ellipsis so a long email subject can't spill past the
   activity column into the cost/context columns. Full detail is on the row's drill-down. */
.usage-feed__scan { color: var(--muted); font-family: var(--font-mono); font-size: 11px; opacity: 0.7; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex-basis: 100%; }

/* By-agent filter chips — scope the hero/windows/feed to one agent. */
.usage-filter { display: flex; flex-wrap: wrap; gap: 8px; }
.usage-chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 13px; font-weight: 600; color: var(--muted); text-decoration: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 6px 13px;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.usage-chip:hover { color: var(--ink); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.usage-chip--on {
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

/* Feed row as a link → the per-turn drill-down. A trailing chevron + hover wash; the grid gains
   a chevron column on top of the base .usage-feed__row layout. */
.usage-feed__row--link {
  text-decoration: none; color: inherit; border-radius: 8px;
  grid-template-columns: 72px auto 1fr auto 16px;
  box-shadow: inset 0 0 0 0 var(--accent);
  transition: background 0.12s, box-shadow 0.12s;
}
/* Hover: a faint accent wash + a crisp accent bar down the left edge — the row "selects". */
.usage-feed__row--link:hover {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  box-shadow: inset 3px 0 0 0 var(--accent);
}
.usage-feed__row--link:hover .usage-feed__chev { opacity: 0.9; color: var(--accent); transform: translateX(2px); }
.usage-feed__chev { color: var(--muted); opacity: 0.4; display: inline-flex; transition: opacity 0.12s, transform 0.12s, color 0.12s; }
.usage-feed__chev .icon { width: 16px; height: 16px; }

/* ── Per-turn drill-down (usage-turn.hbs) ─────────────────────────────────────── */
/* Visuals from the shared .back-btn pill; this keeps only the flex-column start alignment. */
.usage-back { align-self: flex-start; }

.usage-turn-head { display: flex; align-items: center; gap: 14px; }
.usage-turn-head__id { min-width: 0; }
.usage-turn-head .settings-title {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  font-family: var(--font-display); font-size: 34px; line-height: 1; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
/* Model chip — the same outlined mono micro-tag as the feed's OPUS + activity chips. */
.usage-turn-head__model {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted); background: none;
  border: 1px solid color-mix(in srgb, var(--border) 88%, var(--muted)); border-radius: 5px; padding: 2px 7px;
  align-self: center;
}
.usage-turn-head .settings-sub { font-variant-numeric: tabular-nums; }

/* Cost breakdown — one row per priced component: label · bar · tokens · cost. */
.usage-bd { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
/* Stacked: the full label sits on its own line (no truncation), with the bar · tokens · cost
   beneath it. */
.usage-bd__row {
  display: grid; grid-template-columns: 1fr auto auto;
  grid-template-areas:
    "label label label"
    "bar   tok   cost";
  align-items: center; gap: 7px 14px; font-size: 14px;
}
.usage-bd__label { grid-area: label; color: var(--ink); line-height: 1.35; display: flex; align-items: center; gap: 6px; }
.usage-bd__label .set-info__btn { width: 16px; height: 16px; }
.usage-bd__label .set-info__btn .icon { width: 14px; height: 14px; }
.usage-bd__row--top .usage-bd__label { font-weight: 700; }
.usage-bd__bar { grid-area: bar; height: 8px; background: var(--border); border-radius: 999px; overflow: hidden; }
.usage-bd__fill { display: block; height: 100%; background: color-mix(in srgb, var(--accent) 55%, var(--surface)); border-radius: 999px; }
.usage-bd__row--top .usage-bd__fill { background: var(--accent); }
.usage-bd__tok { grid-area: tok; color: var(--muted); font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.02em; font-variant-numeric: tabular-nums; text-align: right; }
.usage-bd__cost { grid-area: cost; color: var(--ink); font-family: var(--font-mono); font-weight: 700; font-variant-numeric: tabular-nums; text-align: right; }
/* Bucketed widths (CSP forbids inline style="width:…"; the route picks .uw-N). */
.uw-0 { width: 0; } .uw-10 { width: 10%; } .uw-20 { width: 20%; } .uw-30 { width: 30%; }
.uw-40 { width: 40%; } .uw-50 { width: 50%; } .uw-60 { width: 60%; } .uw-70 { width: 70%; }
.uw-80 { width: 80%; } .uw-90 { width: 90%; } .uw-100 { width: 100%; }

/* Hop ledger — a spec-sheet table (card-less): a numbered step · label+activity+tokens · cost,
   one per divider-separated row (matches the recent-messages feed). */
.usage-hops { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.usage-hop {
  display: grid; grid-template-columns: 26px 1fr auto; align-items: start; gap: 13px;
  padding: 14px 4px; border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.usage-hop:last-child { border-bottom: none; }
.usage-hop__n {
  width: 24px; height: 24px; border-radius: 999px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border); font-variant-numeric: tabular-nums;
}
.usage-hop__body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.usage-hop__label { font-weight: 600; color: var(--ink); font-size: 14px; }
.usage-hop__act { display: flex; flex-wrap: wrap; gap: 5px; }
.usage-hop__tok {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.02em;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
.usage-hop__note {
  display: flex; align-items: flex-start; gap: 7px; margin-top: 3px;
  font-size: 12.5px; color: var(--muted); line-height: 1.45;
  border-left: 2px solid color-mix(in srgb, var(--accent) 35%, var(--border)); padding-left: 9px;
}
.usage-hop__note .icon { flex: none; width: 14px; height: 14px; margin-top: 2px; color: var(--accent); }
.usage-hop__cost { font-family: var(--font-mono); font-weight: 700; font-size: 14px; color: var(--ink); font-variant-numeric: tabular-nums; }
.usage-hop--corrective .usage-hop__label { color: var(--muted); }
/* A per-email step links to the email it priced. Same "drills in" language as the Usage feed row
   (.usage-feed__chev): a muted chevron marks it as reachable at rest, then accent + slide on hover — so a
   linked row is distinguishable from an older, unlinked one without shouting in a column of numbers. */
.usage-hop__label--link { text-decoration: none; display: inline-flex; align-items: center; gap: 3px; }
.usage-hop__chev { color: var(--muted); opacity: 0.4; display: inline-flex; transition: opacity 0.12s, transform 0.12s, color 0.12s; }
.usage-hop__chev .icon { width: 15px; height: 15px; }
.usage-hop__label--link:hover, .usage-hop__label--link:focus-visible { color: var(--accent); }
.usage-hop__label--link:hover .usage-hop__chev,
.usage-hop__label--link:focus-visible .usage-hop__chev { opacity: 0.9; color: var(--accent); transform: translateX(2px); }

/* Action-timeline fallback (turns without a hop ledger) — flat divider rows, same table language. */
.usage-timeline { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.usage-timeline li {
  font-size: 14px; color: var(--ink); padding: 11px 4px;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.usage-timeline li:last-child { border-bottom: none; }

@media (max-width: 760px) {
  /* The column-head row assumes the desktop 5-col grid; on mobile rows reflow to two lines, so drop it. */
  .usage-feed__head { display: none; }
  /* The per-row Balance column can't fit the cramped mobile reflow — the hero "Balance" headline covers it. */
  .usage-feed__bal { display: none; }
  .usage.is-metered .usage-feed__row--link { grid-template-columns: 56px 1fr auto; }
  /* Feed row → two lines: [when | who | cost] on top, the activity chips below. Every cell is
     placed EXPLICITLY so the cost stays top-right — relying on auto-flow (with act spanning 2/-1)
     bumped the cost onto its own left-aligned line. */
  .usage-feed__row, .usage-feed__row--link {
    grid-template-columns: 56px 1fr auto; column-gap: 10px; row-gap: 2px;
  }
  .usage-feed__when { grid-column: 1; grid-row: 1; }
  .usage-feed__agent  { grid-column: 2; grid-row: 1; }
  .usage-feed__cost { grid-column: 3; grid-row: 1; }
  /* Row 2: activity chips on the left. */
  .usage-feed__act  { grid-column: 2; grid-row: 2; }
  .usage-feed__chev { display: none; }
  /* Agents table: tighten cell + control padding and pull the edge columns flush so all
     columns (incl. the trash) fit INSIDE the card instead of spilling past its right border. */
  .agent-table th, .agent-table td { padding: 10px 5px; }
  .agent-table th:first-child, .agent-table td:first-child { padding-left: 0; }
  .agent-th-del, .agent-cell-del { padding-right: 0; }
  .agent-select { padding: 6px 7px; }
}

/* Narrow phones: a 4-column table can't fit, so STACK each agent into its own card — nothing
   can spill past the section border. Headers are hidden, so each control cell shows its label
   via data-label (set in account.hbs), and the trash tucks into the card's top-right. */
@media (max-width: 600px) {
  .agent-table, .agent-table tbody { display: block; }
  .agent-table thead { display: none; }
  /* Each agent is a card, laid out as a 2-col grid: [name | trash] on the first row, then each
     labeled control spanning the full width below. The trash lives in a real grid column, so
     it stays INSIDE the card (the earlier absolute-positioned version escaped to the right). */
  .agent-table tr {
    display: grid; grid-template-columns: 1fr auto; align-items: center; column-gap: 10px;
    border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; margin-bottom: 10px;
  }
  .agent-table th, .agent-table td { display: block; padding: 0; }
  .agent-table th[scope="row"] { grid-column: 1; grid-row: 1; }
  /* width:auto undoes the base `.agent-cell-del { width: 1% }` table-shrink trick — as a grid
     item that 1% meant ~3px, so the 34px trash button overflowed its cell past the card. */
  .agent-cell-del { grid-column: 2; grid-row: 1; justify-self: end; width: auto; }
  .agent-who { font-size: 16px; }
  .agent-table td[data-label] {
    grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between;
    gap: 12px; margin-top: 10px;
  }
  .agent-table td[data-label]::before {
    content: attr(data-label); font-size: 13px; font-weight: 600; color: var(--muted);
  }
  .agent-table td[data-label] .agent-select { min-width: 140px; }
}

/* ── The family channel feed (FAMILY.md Phase 4/7) ───────────────────────────── */
.channel-wrap { max-width: 720px; margin: 0 auto; width: 100%; padding: 8px 16px 40px; }
.channel-lede { margin: 4px 0 16px; }
.ff-feed { display: flex; flex-direction: column; gap: 10px; }
.ff-post {
  display: flex; align-items: flex-start; gap: 12px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 14px 16px; line-height: 1.5;
}
.ff-post__emoji { font-size: 20px; line-height: 1.2; flex: 0 0 auto; }
.ff-post__body { flex: 1 1 auto; }
.ff-post--new { animation: ff-post-in .35s ease; }
@keyframes ff-post-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.ff-feed__empty { padding: 24px 4px; }

/* (DELETED — the family-era CSS, in two sweeps. 2026-08-31: `.fam-share`, `.fam-code`, `.fam-form`,
   `.fam-row*` (docs/SHARING.md — the Members block's deletion orphaned `.fam-form`; the rest had
   been dead since the code+PIN purge of 2026-08-06). 2026-09-02: the REST of the old Family
   dashboard page — `.fam-hero*`, `.fam-wrap`, `.fam-stats`/`.fam-stat*`, `.fam-section`, `.fam-h`,
   `.fam-more`, `.fam-roster`, `.fam-member*`, `.fam-list`/`.fam-item*`, `.fam-empty`,
   `.fam-facts`/`.fam-fact*`, plus the `.onb-choose*` "[Just me] / [My family]" onboarding knob —
   a whole page's dress for a surface that died with the Household→Business pivot (2026-08-06).
   Verified zero consumers repo-wide before removal, both times.) */

/* ============================================================
   HOMEPAGE — the "spec-sheet" brand voice (rebuilt 2026-07-07).
   Hanken display + JetBrains mono micro-labels, cool-slate, ROYAL accent — the app
   default, inherited straight from <html data-accent="royal"> (no override needed).
   The living {{> brand-mark-svg}} is the hero; the glass-box "readout" is a recurring
   device. All scoped to .dhome so nothing collides. Buttons reuse the shared .btn/etc.
============================================================ */
.dhome { --dh-mono: var(--font-mono); color: var(--ink); overflow-x: hidden; }
.dh-band { max-width: 1080px; margin: 0 auto; padding-left: 24px; padding-right: 24px; }
@media (max-width: 640px) { .dh-band { padding-left: 16px; padding-right: 16px; } }
/* The homepage is full-bleed: .wrap's 980px cap + 24px side padding fought the .dhome design (double
   padding on mobile — the crowding — plus a too-narrow cap on desktop). Zero .wrap's HORIZONTAL
   padding + lift its max-width so .dh-band owns the width; keep .wrap's vertical padding for top/bottom
   air. Align the shared header + footer to the same 1080 band with the same responsive gutter. */
/* Every "before you're in the app" page whose content self-centers (landing .dhome, the
   auth/join .auth cards, the onboarding .kg key-gate) goes full-bleed, so the header shell
   + its 1080 content band — and therefore the Dustav logo — land in the EXACT same spot on
   every one of them. The page's own section handles centering its content, so zeroing the
   wrap gutter here doesn't disturb it. */
.wrap:has(.dhome, .auth, .kg) { max-width: none; padding-left: 0; padding-right: 0; }
/* The header shell (.pub-head) stays full-bleed so its docked bar spans edge-to-edge;
   the CONTENT band lives on .pub-head__inner. The footer keeps its band on itself. */
.wrap:has(.dhome, .auth, .kg) > .pub-foot,
.wrap:has(.dhome, .auth, .kg) > .pub-head > .pub-head__inner {
  max-width: 1080px; width: 100%; margin-left: auto; margin-right: auto;
  padding-left: 24px; padding-right: 24px; box-sizing: border-box;
}
@media (max-width: 640px) {
  .wrap:has(.dhome, .auth, .kg) > .pub-foot,
  .wrap:has(.dhome, .auth, .kg) > .pub-head > .pub-head__inner { padding-left: 16px; padding-right: 16px; }
}
/* The remaining padded pages (legal / docs / essays) keep their narrow content column;
   break the header shell out of .wrap's 24px gutter so the docked bar still reaches the
   column edges, then restore the gutter on the inner band. */
.wrap:not(:has(.dhome, .auth, .kg)) > .pub-head { margin-left: -24px; margin-right: -24px; }
.wrap:not(:has(.dhome, .auth, .kg)) > .pub-head > .pub-head__inner { padding-left: 24px; padding-right: 24px; }
.dh-eyebrow { font-family: var(--dh-mono); font-size: 11.5px; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; color: var(--accent); margin: 0; }
.dh-title { font-family: var(--font-display); font-weight: 800; font-size: clamp(26px, 3.6vw, 40px); line-height: 1.06; letter-spacing: -0.025em; margin: 14px 0 0; text-wrap: balance; color: var(--ink); }
.dh-lede { color: var(--muted); font-size: clamp(15.5px, 1.4vw, 17.5px); line-height: 1.6; max-width: 60ch; margin: 18px 0 0; }
.dh-lede em, .dh-title em { font-style: normal; color: var(--accent); }
.dh-lede strong { color: var(--ink); font-weight: 600; }
.dh-cta { display: flex; gap: 12px; margin-top: 28px; flex-wrap: wrap; }
.dh-note { font-family: var(--dh-mono); font-size: 11.5px; letter-spacing: .02em; color: var(--muted); opacity: .85; margin: 20px 0 0; line-height: 1.7; }
.dh-section { padding-top: clamp(48px, 7vw, 92px); }
.dh-shead { max-width: 64ch; }
/* Full-bleed, tinted objection-killer band ("Read-only. Nothing lands without you.").
   The outer section spans the viewport with a soft accent wash + hairline rules to break
   up the page; the inner .dh-band re-caps the content and centers it. */
.dh-reassure {
  background: color-mix(in srgb, var(--accent) 8%, var(--bg));
  border-top: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border));
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border));
  text-align: center;
  /* Transparent margin above the tint so the band separates from the cards instead of
     butting straight up against them. (Below it, the next section's own top padding is
     the gap — no margin-bottom needed, or it'd double up.) */
  margin-top: clamp(56px, 8vw, 104px);
  padding-top: clamp(52px, 7vw, 96px);
  padding-bottom: clamp(52px, 7vw, 96px);
}
.dh-reassure .dh-title { margin: 0 auto; max-width: 20ch; }
.dh-reassure .dh-lede { margin: 18px auto 0; }
.dh-reassure .dh-cta { justify-content: center; }
/* A large Blink mark sits beside the copy (the agent, present but restrained). The pair is
   centered as one lockup on desktop; on mobile it stacks — mark on top — and shrinks. */
.dh-reassure__row { display: flex; align-items: center; justify-content: center; gap: clamp(24px, 5vw, 64px); }
.dh-reassure__mark { flex: none; display: grid; place-items: center; }
.dh-reassure__mark .brand-mark-svg { width: clamp(112px, 15vw, 200px); height: auto; }
.dh-reassure__body { max-width: 48ch; }
@media (max-width: 720px) {
  .dh-reassure__row { flex-direction: column; gap: 20px; }
  .dh-reassure__mark .brand-mark-svg { width: clamp(92px, 26vw, 128px); }
}

/* Hero */
/* Cap the text column to its content (the lede maxes at 46ch) so it doesn't trail dead
   space, and let the disc column take the rest — with a big disc that fills it (below),
   the two sides sit close instead of stranding ~190px of empty middle. */
.dh-hero { display: grid; grid-template-columns: minmax(0, 31rem) 1fr; gap: 32px; align-items: center; padding-top: clamp(34px, 5vw, 76px); padding-bottom: clamp(20px, 3vw, 40px); }
.dh-hero__title { font-family: var(--font-display); font-weight: 800; font-size: clamp(40px, 6.4vw, 72px); line-height: 1.0; letter-spacing: -0.03em; margin: 18px 0 0; text-wrap: balance; color: var(--ink); }
.dh-hero__title em { font-style: normal; color: var(--accent); }
.dh-hero__lede { color: var(--muted); font-size: clamp(16px, 1.5vw, 18.5px); line-height: 1.6; max-width: 46ch; margin: 22px 0 0; }
.dh-hero__stage { display: grid; place-items: center; }
.dh-disc { display: grid; place-items: center; }   /* no card — the living mark IS the hero, floating on the page */
.dh-disc .brand-mark-svg { width: min(72vw, 330px); height: auto; }
.dh-cap { font-family: var(--dh-mono); font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); opacity: .7; margin: 6px 0 0; text-align: center; }
.dh-cap__wave { font-size: 2em; vertical-align: middle; margin-left: 4px; position: relative; top: -0.06em; }
.dh-cap--left { text-align: left; margin-top: 14px; }
@media (max-width: 860px) { .dh-hero { grid-template-columns: 1fr; } .dh-hero__stage { order: -1; margin-bottom: 22px; } .dh-disc .brand-mark-svg { width: min(58vw, 240px); } }

/* Three moves */
.dh-moves { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 40px; }
@media (max-width: 760px) { .dh-moves { grid-template-columns: 1fr; } }
.dh-move { background: var(--surface); border: 1px solid var(--border); border-radius: 18px; padding: 26px 24px; }
.dh-move--lead { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 22px 54px -34px var(--accent); }
.dh-move__n { display: flex; align-items: center; gap: 8px; line-height: 1; font-family: var(--dh-mono); font-size: 11px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase; color: var(--accent); margin: 0; }
.dh-move__icon { font-size: 19px; line-height: 1; }
.dh-move__t { font-family: var(--font-display); font-weight: 700; font-size: 19px; letter-spacing: -0.01em; margin: 14px 0 0; color: var(--ink); }
.dh-move__d { color: var(--muted); font-size: 14.5px; line-height: 1.55; margin: 10px 0 0; }

/* "Who it's for" — a wrapping cloud of persona pills (emoji + label). Extensible: add an <li> to grow it.
   ⚠️ LOAD-BEARING (Curtis, 2026-08-30 — restored by veto after a first-principles cut): the cloud IS the
   is-it-for-me answer; don't delete it as "a section doing a sentence's job" again. */
.dh-who { display: flex; flex-wrap: wrap; gap: 10px; margin: 34px 0 0; padding: 0; list-style: none; }
.dh-who__tag { display: inline-flex; align-items: center; gap: 8px; padding: 9px 15px 9px 12px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
  font-size: 14px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); line-height: 1; }
.dh-who__tag span { font-size: 17px; line-height: 1; }
@media (max-width: 520px) { .dh-who__tag { font-size: 13px; padding: 8px 13px 8px 10px; } .dh-who__tag span { font-size: 15px; } }

/* Request-access page — the invite funnel form, in the .dh spec-sheet language. */
.dhome--form { min-height: 62vh; }
.dh-ra { max-width: 620px; margin: 0 auto; }
.dh-ra__head { max-width: none; }
.dh-form { margin: 34px 0 0; display: flex; flex-direction: column; gap: 18px; }
.dh-field { display: flex; flex-direction: column; gap: 7px; }
.dh-label { font-family: var(--dh-mono); font-size: 11px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); }
.dh-req { color: var(--accent); margin-left: 6px; letter-spacing: .1em; }
.dh-input { width: 100%; box-sizing: border-box; background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; padding: 13px 15px; font: inherit; font-size: 15px; color: var(--ink); }
.dh-input::placeholder { color: var(--muted); opacity: .7; }
.dh-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
.dh-textarea { resize: vertical; min-height: 84px; line-height: 1.5; }
.dh-form__err { margin: 0; padding: 11px 14px; border-radius: 10px; font-size: 14px; color: var(--error);
  background: color-mix(in srgb, var(--error) 12%, transparent); border: 1px solid color-mix(in srgb, var(--error) 40%, transparent); }
.dh-form__submit { margin-top: 6px; align-self: flex-start; }
.dh-ra__alt { font-size: 13.5px; margin: 4px 0 0; }
.dh-hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.dh-ra__done { text-align: center; max-width: 540px; margin: 20px auto; }
.dh-ra__mark { display: grid; place-items: center; margin: 0 auto 22px; }
.dh-ra__mark .brand-mark-svg { width: 88px; height: auto; }
.dh-ra__done .dh-lede { margin-left: auto; margin-right: auto; }
.dh-ra__done .dh-cta { justify-content: center; margin-top: 26px; }
@media (max-width: 520px) { .dh-form__submit { width: 100%; align-self: stretch; } }

/* Gmail lockup chip (Google trademark, used descriptively under "works with"). White bg so the
   colored mark reads on both themes. NOTE: public launch needs Google brand permission + OAuth verification. */
.dh-gmail { display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px 6px 9px;
  border: 1px solid #dadce0; border-radius: 999px; background: #fff; line-height: 1; }
.dh-gmail svg { width: 21px; height: 17px; display: block; }
.dh-gmail svg.gcal-mark { width: 17px; height: 17px; } /* the Calendar mark is square — 21×17 would squash it */
.dh-gmail b { font-family: var(--font-display); font-weight: 700; font-size: 14px; color: #3c4043; letter-spacing: -0.01em; }
.dh-gmail--sm { padding: 5px; border-radius: 8px; gap: 0; }
.dh-gmail--sm svg { width: 20px; height: 16px; }

/* Hero "works with" trust strip */
.dh-trust { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 26px 0 0; }
.dh-trust__k { font-family: var(--dh-mono); font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); }
.dh-trust__rest { font-size: 13.5px; color: var(--muted); }
@media (max-width: 860px) { .dh-trust { justify-content: center; } }

/* Business / family split — two feature cards (the business one leads with an accent border) */
.dh-duo { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 40px; align-items: start; }
@media (max-width: 760px) { .dh-duo { grid-template-columns: 1fr; } }
.dh-fcard { background: var(--surface); border: 1px solid var(--border); border-radius: 20px; padding: 30px 28px; }
.dh-fcard--lead { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 22px 54px -34px var(--accent); }
.dh-fcard__k { display: flex; align-items: center; gap: 8px; line-height: 1; font-family: var(--dh-mono); font-size: 11px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase; color: var(--accent); margin: 0; }
.dh-fcard__k .ic { font-size: 18px; }
.dh-fcard__t { font-family: var(--font-display); font-weight: 800; font-size: clamp(21px, 2.4vw, 26px); letter-spacing: -0.02em; margin: 14px 0 0; color: var(--ink); }
.dh-fcard__d { color: var(--muted); font-size: 15px; line-height: 1.6; margin: 12px 0 0; }
.dh-fcard__list { list-style: none; padding: 0; margin: 20px 0 0; display: flex; flex-direction: column; gap: 10px; }
.dh-fcard__list li { font-size: 14px; color: var(--ink); display: flex; gap: 10px; align-items: flex-start; line-height: 1.5; }
.dh-fcard__list li::before { content: "→"; color: var(--accent); font-weight: 700; flex: none; }

/* Proof chats (photo→calendar, notes) */
.dh-proofs { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 40px; }
@media (max-width: 760px) { .dh-proofs { grid-template-columns: 1fr; } }
.dh-proof { background: var(--surface); border: 1px solid var(--border); border-radius: 18px; padding: 20px; display: flex; flex-direction: column; gap: 16px; }
/* Chat rows the same shape as the in-app chat: BOTH the person and Dustav left-aligned,
   avatar + name · relative-time + bubble, so it reads instantly as a conversation. */
.dh-msg { display: flex; gap: 10px; align-items: flex-start; }
.dh-msg__av { flex: none; width: 28px; height: 28px; display: grid; place-items: center; border-radius: 9px; font-size: 16px; line-height: 1; }
.dh-msg__av .brand-mark-svg { width: 28px; height: 28px; }
.dh-msg__av--you { background: var(--bg); border: 1px solid var(--border); }
.dh-msg__body { min-width: 0; }
.dh-msg__meta { display: flex; align-items: baseline; gap: 8px; margin: 1px 0 4px 2px; }
.dh-msg__name { font-size: 12.5px; font-weight: 700; color: var(--ink); }
.dh-msg__time { font-family: var(--dh-mono); font-size: 10.5px; letter-spacing: .02em; color: var(--muted); }
.dh-msg__bubble { font-size: 14px; line-height: 1.5; color: var(--ink); background: var(--bg); border: 1px solid var(--border); border-radius: 12px; padding: 9px 13px; }

/* Split (household) */
.dh-split { display: grid; grid-template-columns: 1fr 1fr; gap: 44px; align-items: center; }
@media (max-width: 820px) { .dh-split { grid-template-columns: 1fr; gap: 28px; } }

/* Readout device (mono) */
.dh-readout { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 20px 22px; font-family: var(--dh-mono); font-size: 13px; line-height: 1.9; color: var(--muted); }
.dh-readout__head { display: flex; justify-content: space-between; color: var(--ink); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; font-size: 11.5px; border-bottom: 1px solid var(--border); padding-bottom: 10px; margin-bottom: 10px; }
.dh-readout__head b { color: var(--accent); font-variant-numeric: tabular-nums; }
.dh-readout__li { margin: 0; }
.dh-readout__li::before { content: "─ "; color: var(--muted); opacity: .5; }
.dh-readout__li--none { color: var(--muted); opacity: .7; margin-top: 6px; }

/* Details + steps (BYOK) */
.dh-details { margin-top: 30px; border: 1px solid var(--border); border-radius: 14px; background: var(--surface); overflow: hidden; }
.dh-details__q { font-family: var(--font-display); font-weight: 600; font-size: 15px; color: var(--ink); padding: 16px 20px; cursor: pointer; list-style: none; }
.dh-details__q::-webkit-details-marker { display: none; }
.dh-details__q::before { content: "+ "; color: var(--accent); font-family: var(--dh-mono); }
.dh-details[open] .dh-details__q::before { content: "− "; }
.dh-steps { display: grid; gap: 4px; padding: 0 20px 18px; }
.dh-step { display: flex; gap: 14px; padding: 12px 0; border-top: 1px solid var(--border); }
.dh-step__n { font-family: var(--dh-mono); font-size: 12px; font-weight: 600; color: var(--accent); flex: none; padding-top: 2px; }
.dh-step__t { font-weight: 600; font-size: 15px; margin: 0; color: var(--ink); }
.dh-step__d { color: var(--muted); font-size: 14px; line-height: 1.5; margin: 4px 0 0; }
.dh-step__d a, .dh-lede a, .dh-faq__a a { color: var(--accent); }
.dh-step__d code { font-family: var(--dh-mono); font-size: 12.5px; background: var(--bg); padding: 1px 5px; border-radius: 4px; }

/* Typed-extraction CAROUSEL — 3 examples (receipt / Square sign-up / school email). Each slide is
   the whole pipeline: you ASK Dustav in plain words → it writes a typed JSON SCHEMA → the email's
   fields flow, BY NUMBER + COLOUR ("paint by numbers"), into the captured row. public/js/landing-
   extract.js switches + cycles; the .xf (doc highlight) / .xn (schema + record #) pairs pulse in
   sequence. Themes with the app tokens. */
/* per-field colours, shared everywhere a field appears (schema #, doc highlight, record #) */
.xf--1, .xn--1 { --c: #3b82f6; }   /* blue  */
.xf--2, .xn--2 { --c: #14b8a6; }   /* teal  */
.xf--3, .xn--3 { --c: #8b5cf6; }   /* violet*/
.xf--4, .xn--4 { --c: #f59e0b; }   /* amber */

/* carousel shell */
.dh-ex { margin-top: 40px; }
.dh-ex__tabs { display: flex; gap: 6px; padding: 5px; margin: 0 auto 26px; width: max-content; max-width: 100%;
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px; flex-wrap: wrap; justify-content: center; }
.dh-ex__tab { font-family: var(--dh-mono); font-size: 12px; border: 0; background: transparent; color: var(--muted);
  padding: 9px 14px; border-radius: 999px; cursor: pointer; white-space: nowrap; transition: color .18s, background .18s; }
.dh-ex__tab:hover { color: var(--ink); }
.dh-ex__tab[aria-selected="true"] { background: var(--accent); color: var(--accent-ink); }
.dh-ex__dots { display: flex; gap: 8px; justify-content: center; margin-top: 24px; }
.dh-ex__dot { width: 8px; height: 8px; padding: 0; border: 0; border-radius: 50%; background: var(--border); cursor: pointer; transition: background .2s, transform .2s; }
.dh-ex__dot[aria-current="true"] { background: var(--accent); transform: scale(1.25); }

/* structuring — the prompt → the typed schema */
.exsay { display: grid; grid-template-columns: 1fr auto 1fr; gap: clamp(14px, 3vw, 36px); align-items: center; }
.exsay > * { min-width: 0; }
@media (max-width: 760px) { .exsay { grid-template-columns: 1fr; gap: 10px; } }
.exsay__q { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px;
  font-size: 14px; line-height: 1.5; color: var(--ink); display: flex; gap: 10px; align-items: baseline; }
.promptmark { flex: none; font-family: var(--dh-mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); padding: 2px 8px; border-radius: 6px; }
.exsay__schema { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; font-family: var(--dh-mono); font-size: 12.5px; }
.exsay__schema .brace { color: var(--muted); display: block; }
.exf { display: flex; align-items: center; gap: 10px; padding: 5px 0 5px 14px; }
.exf__k { color: var(--ink); }
.exf__t { color: var(--accent); margin-left: auto; }
.exsay__arrow { display: flex; flex-direction: column; align-items: center; gap: 4px; color: var(--muted); }
.exsay__arrow svg { width: 20px; height: 20px; color: var(--accent); transform: rotate(-90deg); }
.exsay__arrow span { font-family: var(--dh-mono); font-size: 10px; font-style: italic; text-align: center; max-width: 15ch; }
@media (max-width: 760px) { .exsay__arrow svg { transform: none; } }
.exsay__arrow--wide { flex-direction: row; justify-content: center; gap: 10px; margin: 20px 0; }
.exsay__arrow--wide svg { transform: none; }
.exsay__arrow--wide span { max-width: none; }

/* extraction — the document → the captured record */
.exrun { display: grid; grid-template-columns: 1fr auto 1fr; gap: clamp(14px, 3vw, 40px); align-items: center; }
.exrun > * { min-width: 0; }
@media (max-width: 860px) { .exrun { grid-template-columns: 1fr; gap: 6px; } }
.exdoc { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 20px 22px; box-shadow: 0 26px 64px -42px rgba(20, 45, 80, .32); }
.exdoc__head { display: flex; align-items: center; gap: 9px; font-family: var(--font-display); font-weight: 800; font-size: 17px; color: var(--ink); padding-bottom: 14px; border-bottom: 1px solid var(--border); }
.exdoc__mark { color: var(--accent); }
.exdoc__tag { margin-left: auto; font-family: var(--dh-mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); font-weight: 500; border: 1px solid var(--border); border-radius: 999px; padding: 3px 9px; }
.exdoc__rows { padding-top: 14px; display: flex; flex-direction: column; gap: 12px; }
.exdoc__row { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; font-size: 13.5px; }
.exdoc__lbl { font-family: var(--dh-mono); font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); flex: none; padding-top: 2px; }
.exdoc__v { color: var(--ink); text-align: right; }
.exdoc__total { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; margin-top: 14px; padding-top: 13px; border-top: 1px solid var(--border); font-weight: 700; font-size: 15px; color: var(--ink); }
.exrun__arrow { display: grid; place-items: center; color: var(--muted); }
.exrun__arrow svg { width: 26px; height: 26px; }
@media (max-width: 860px) { .exrun__arrow svg { transform: rotate(90deg); } }
.exrec { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; overflow: hidden; box-shadow: 0 26px 64px -42px rgba(20, 45, 80, .32); }
.exrec__bar { display: flex; align-items: center; gap: 10px; padding: 11px 15px; background: var(--bg); border-bottom: 1px solid var(--border); }
.exrec__dots { display: flex; gap: 5px; }
.exrec__dots i { width: 8px; height: 8px; border-radius: 50%; background: var(--border); }
.exrec__name { font-family: var(--dh-mono); font-size: 11.5px; color: var(--muted); letter-spacing: .02em; }
.exrec__rows { padding: 6px 16px 10px; }
.exrec__row { display: flex; align-items: center; gap: 11px; padding: 11px 0; border-bottom: 1px solid var(--border); }
.exrec__row:last-child { border-bottom: 0; }
.exrec__k { font-family: var(--dh-mono); font-size: 11px; color: var(--ink); }
.exrec__v { margin-left: auto; font-family: var(--dh-mono); font-size: 12px; color: var(--ink); text-align: right; overflow: hidden; text-overflow: ellipsis; }
.exrec__v--money { color: #d98216; }

/* a highlighted field on the document + its numbered badge (shared) */
.xf { position: relative; background: color-mix(in srgb, var(--c) 15%, transparent); color: var(--ink);
  border-radius: 5px; padding: 1px 6px; box-decoration-break: clone; -webkit-box-decoration-break: clone; font-weight: 500; }
.xf::after { content: attr(data-n); position: absolute; top: -8px; right: -8px; width: 15px; height: 15px;
  border-radius: 50%; background: var(--c); color: #fff; font-family: var(--dh-mono); font-size: 9px; font-weight: 700;
  display: grid; place-items: center; box-shadow: 0 0 0 2px var(--surface); }
/* a numbered badge (schema + record) */
.xn { flex: none; width: 18px; height: 18px; border-radius: 50%; background: var(--c); color: #fff;
  font-family: var(--dh-mono); font-size: 10px; font-weight: 700; display: grid; place-items: center; }
/* (static — the numbers + colours do the "paint by numbers"; no pulse.) */

/* From the founder — a real photo (left) + the earned pivot story (right); stacks on mobile. */
.dh-founder { display: grid; grid-template-columns: minmax(0, 320px) 1fr; gap: clamp(28px, 5vw, 56px); align-items: center; margin-top: 8px; }
@media (max-width: 820px) { .dh-founder { grid-template-columns: 1fr; gap: 24px; max-width: 460px; } }
.dh-founder__frame { border-radius: 16px; overflow: hidden; background: var(--surface); box-shadow: 0 20px 50px -28px rgba(20, 45, 80, .4); }
.dh-founder__photo { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; object-position: center 42%; }
.dh-founder__quote { font-family: var(--font-display); font-weight: 500; font-size: clamp(17px, 1.7vw, 20px); line-height: 1.5; letter-spacing: -0.005em; color: var(--ink); margin: 14px 0 0; padding: 0; border: 0; }
.dh-founder__quote a { color: var(--accent); }
.dh-founder__sig { margin: 22px 0 0; line-height: 1.45; }
.dh-founder__sig b { display: block; font-size: 16px; color: var(--ink); }
.dh-founder__sig span { font-family: var(--dh-mono); font-size: 12px; letter-spacing: .02em; color: var(--muted); }

/* Essays */
.dh-essays { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 40px; }
@media (max-width: 820px) { .dh-essays { grid-template-columns: 1fr; } }
.dh-essay { display: block; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 22px; text-decoration: none; transition: border-color .15s, transform .15s; }
.dh-essay:hover { border-color: var(--accent); transform: translateY(-2px); }
.dh-essay__meta { font-family: var(--dh-mono); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin: 0; }
.dh-essay__t { font-family: var(--font-display); font-weight: 700; font-size: 17px; line-height: 1.2; margin: 12px 0 0; color: var(--ink); }
.dh-essay__d { color: var(--muted); font-size: 14px; line-height: 1.5; margin: 8px 0 0; }
.dh-essay__more { display: inline-block; margin-top: 14px; font-family: var(--dh-mono); font-size: 12px; color: var(--accent); }

/* Plans */
/* One plan now (Plus) — center it as a single card rather than a 2-up grid. */
.dh-plans { display: flex; justify-content: center; margin-top: 40px; }
.dh-plans > .dh-plan { width: 100%; max-width: 480px; }
/* Desktop: pitch left, the single card right — the same two-column shape as the hero. */
.dh-pricing { display: grid; grid-template-columns: 1.05fr .95fr; gap: 48px; align-items: center; }
.dh-pricing .dh-shead { max-width: none; }
.dh-pricing .dh-plans { margin-top: 0; }
@media (max-width: 860px) { .dh-pricing { grid-template-columns: 1fr; } .dh-pricing .dh-plans { margin-top: 28px; } }
.dh-plan { background: var(--surface); border: 1px solid var(--border); border-radius: 20px; padding: 30px 28px; display: flex; flex-direction: column; }
.dh-plan--featured { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 22px 54px -32px var(--accent); }
.dh-plan__badge { align-self: flex-start; font-family: var(--dh-mono); font-size: 10px; letter-spacing: .14em; text-transform: uppercase; color: var(--accent); border: 1px solid var(--accent); border-radius: 999px; padding: 4px 11px; margin-bottom: 18px; }
.dh-plan__name { font-family: var(--dh-mono); font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); margin: 0; }
.dh-plan__price { font-family: var(--font-display); font-size: 42px; font-weight: 800; letter-spacing: -0.03em; margin: 10px 0 0; line-height: 1; color: var(--ink); }
.dh-plan__price span { font-size: 15px; font-weight: 600; color: var(--muted); opacity: .8; letter-spacing: 0; margin-left: 4px; }
.dh-plan__tag { color: var(--muted); font-size: 14px; margin: 10px 0 0; }
.dh-plan__list { list-style: none; padding: 0; margin: 22px 0 26px; display: flex; flex-direction: column; gap: 11px; }
.dh-plan__list li { font-size: 14.5px; color: var(--ink); display: flex; gap: 10px; align-items: flex-start; line-height: 1.45; }
.dh-plan__list li::before { content: "✓"; color: var(--accent); font-weight: 800; flex: none; }
.dh-plan__cta { margin-top: auto; text-align: center; }

/* FAQ */
.dh-faq { margin-top: 34px; border-top: 1px solid var(--border); }
.dh-faq__item { border-bottom: 1px solid var(--border); }
.dh-faq__q { font-family: var(--font-display); font-weight: 600; font-size: 16.5px; color: var(--ink); padding: 18px 0; cursor: pointer; list-style: none; display: flex; justify-content: space-between; gap: 16px; }
.dh-faq__q::-webkit-details-marker { display: none; }
.dh-faq__q::after { content: "+"; color: var(--accent); font-family: var(--dh-mono); flex: none; }
.dh-faq__item[open] .dh-faq__q::after { content: "−"; }
.dh-faq__a { color: var(--muted); font-size: 15px; line-height: 1.6; margin: 0 0 18px; max-width: 68ch; }
.dh-faq__a strong { color: var(--ink); font-weight: 600; }

/* Final CTA */
.dh-final { text-align: center; padding-top: clamp(64px, 9vw, 116px); padding-bottom: clamp(64px, 9vw, 116px); }
.dh-final__title { font-family: var(--font-display); font-weight: 800; font-size: clamp(30px, 4.6vw, 52px); line-height: 1.04; letter-spacing: -0.025em; margin: 0 auto; max-width: 18ch; text-wrap: balance; color: var(--ink); }
/* The living Blink mark sits inline beside the closing line — em-sized so it tracks the heading. */
.dh-final__title .brand-mark-svg { width: 0.9em; height: 0.9em; vertical-align: -0.1em; margin-right: 0.32em; }
.dh-final .dh-cta { justify-content: center; }
.dh-final .dh-note { text-align: center; }

/* ── The interactive glass box, re-skinned to the spec-sheet look ── */
.dhome .glass { margin-top: 40px; }
.dhome .glass__win { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; overflow: hidden; box-shadow: 0 24px 60px -40px rgba(20, 60, 90, .5); }
.dhome .glass__bar { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--border); background: var(--bg); }
.dhome .glass__dots { display: flex; gap: 6px; }
.dhome .glass__dots i { width: 10px; height: 10px; border-radius: 50%; background: var(--border); }
.dhome .glass__crumb { font-family: var(--dh-mono); font-size: 12px; color: var(--muted); letter-spacing: .02em; }
.dhome .glass__crumb #glass-crumb { color: var(--accent); }
.dhome .glass__sep { opacity: .5; }
.dhome .glass__body { display: grid; grid-template-columns: 200px 1fr; min-height: 260px; }
@media (max-width: 680px) { .dhome .glass__body { grid-template-columns: 1fr; } .dhome .glass__tree { display: flex; flex-wrap: wrap; gap: 6px; border-right: none; border-bottom: 1px solid var(--border); } .dhome .glass__group { display: none; } }
.dhome .glass__tree { padding: 14px; border-right: 1px solid var(--border); display: flex; flex-direction: column; gap: 3px; }
.dhome .glass__group { font-family: var(--dh-mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); opacity: .7; margin: 12px 0 4px; }
.dhome .glass__group:first-child { margin-top: 0; }
.dhome .glass__file { text-align: left; background: none; border: 1px solid transparent; border-radius: 9px; padding: 8px 11px; font-family: var(--font-body); font-size: 14px; color: var(--ink); cursor: pointer; }
.dhome .glass__file:hover { background: var(--bg); }
.dhome .glass__file.is-active { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); font-weight: 600; }
.dhome .glass__page { padding: 22px 24px; }
.dhome .glass__fname { font-family: var(--dh-mono); font-size: 12px; color: var(--ink); font-weight: 600; margin: 0 0 14px; }
.dhome .glass__fname span { color: var(--muted); font-weight: 400; }
.dhome .glass__agenda, .dhome .glass__lib, .dhome .glass__notes { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 10px; }
.dhome .glass__agenda li, .dhome .glass__lib li, .dhome .glass__notes li { font-size: 14.5px; color: var(--ink); line-height: 1.45; }
.dhome .glass__when { font-family: var(--dh-mono); font-size: 12px; color: var(--accent); margin-right: 8px; }
.dhome .glass__page p { font-size: 14.5px; color: var(--muted); line-height: 1.6; margin: 0 0 10px; }
.dhome .glass__caret { display: inline-block; width: 7px; height: 15px; background: var(--accent); margin-left: 2px; vertical-align: -2px; animation: dhBlinkCaret 1.1s step-end infinite; }
@keyframes dhBlinkCaret { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .dhome .glass__caret { animation: none; } }

/* ── "Watch it work" — the phone live-demo (public/js/landing-demo.js) ──────────────
   A scripted playback in a device frame: the same chat markup as the app, cycling
   three scenarios (snap a schedule · forward a receipt · just say it). Everything is
   scoped under .dhp so nothing collides with the in-app chat (.ce-) or the .dh- page
   sections. Inherits the page's royal --accent. */
.dhp { --dhp-good: #1a9d6b; --dhp-good-bg: color-mix(in srgb, #1a9d6b 15%, transparent);
  margin-top: 8px; display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 32px 52px; align-items: center; }
/* Left column: the copy + scenario controls, as a stacked panel. Right column: the phone. */
.dhp__intro { display: flex; flex-direction: column; align-items: flex-start; gap: 26px; }
.dhp__intro .dh-shead { max-width: none; }
@media (max-width: 860px) {
  .dhp { grid-template-columns: 1fr; justify-items: center; gap: 26px; }
  .dhp__intro { align-items: center; text-align: center; }
}

/* Scenario tabs (mono pills) */
.dhp__tabs { display: flex; gap: 6px; padding: 5px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 999px; flex-wrap: wrap; justify-content: center; }
.dhp__tab { font-family: var(--font-mono); font-size: 12px; letter-spacing: .02em; border: 0;
  background: transparent; color: var(--muted); padding: 9px 15px; border-radius: 999px;
  cursor: pointer; transition: color .18s, background .18s; }
.dhp__tab:hover { color: var(--ink); }
.dhp__tab[aria-selected="true"] { background: var(--accent); color: var(--accent-ink); }
.dhp__tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The device */
.dhp__phone { width: 344px; max-width: 90vw; aspect-ratio: 344 / 712; background: #05070c;
  border-radius: 44px; padding: 10px; position: relative;
  box-shadow: 0 30px 70px -26px rgba(20, 45, 80, .5), 0 0 0 2px rgba(255,255,255,.04) inset; }
.dhp__phone::before { content: ""; position: absolute; right: -3px; top: 158px; width: 3px;
  height: 58px; background: #05070c; border-radius: 0 3px 3px 0; }
.dhp__screen { position: absolute; inset: 10px; border-radius: 35px; overflow: hidden;
  background: var(--bg); display: flex; flex-direction: column; }
.dhp__notch { position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 124px;
  height: 25px; background: #05070c; border-radius: 0 0 15px 15px; z-index: 6; }

.dhp__statusbar { display: flex; justify-content: space-between; align-items: center;
  padding: 9px 22px 5px; font-family: var(--font-mono); font-size: 11px; color: var(--muted); flex: none; }
.dhp__appbar { display: flex; align-items: center; gap: 9px; padding: 6px 16px 10px;
  border-bottom: 1px solid var(--border); background: var(--surface); flex: none; }
.dhp__appbar .brand-mark-svg { width: 26px; height: 26px; flex: none; }
.dhp__who { display: grid; line-height: 1.15; margin-right: auto; }
.dhp__who b { font-size: 14px; letter-spacing: -.01em; color: var(--ink); }
.dhp__who span { font-family: var(--font-mono); font-size: 10px; color: var(--dhp-good); letter-spacing: .02em; }
.dhp__live { width: 6px; height: 6px; border-radius: 50%; background: var(--dhp-good); }

/* Thread */
.dhp__thread { flex: 1; overflow: hidden; padding: 15px 13px 6px; display: flex;
  flex-direction: column; gap: 10px;
  background: radial-gradient(140% 55% at 100% 0%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 55%), var(--bg); }
.dhp-row { display: flex; gap: 8px; align-items: flex-end; max-width: 90%;
  animation: dhpPop .32s cubic-bezier(.2,.8,.3,1) both; }
.dhp-row--me { align-self: flex-end; flex-direction: row-reverse; }
@keyframes dhpPop { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
.dhp-av { width: 26px; height: 26px; flex: none; border-radius: 50%; display: grid;
  place-items: center; font-size: 15px; }
.dhp-av--me { background: var(--bg); border: 1px solid var(--border); }
.dhp-av .brand-mark-svg { width: 26px; height: 26px; }
.dhp-bubble { padding: 9px 12px; border-radius: 15px; font-size: 13.5px; line-height: 1.42;
  background: var(--surface); border: 1px solid var(--border); color: var(--ink); }
.dhp-row--me .dhp-bubble { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.dhp-bubble strong { font-weight: 700; }
.dhp-chip { display: inline-flex; align-items: center; gap: 7px; padding: 8px 12px; border-radius: 13px;
  font-family: var(--font-mono); font-size: 12px; background: var(--surface); border: 1px dashed var(--border); color: var(--muted); }
.dhp-row--me .dhp-chip { background: color-mix(in srgb, var(--accent) 80%, #000); border-color: transparent; color: var(--accent-ink); }
/* Glass-box action chip (what Dustav DID) */
.dhp-act { align-self: flex-start; font-family: var(--font-mono); font-size: 11px; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  padding: 5px 10px; border-radius: 999px; margin-left: 34px; animation: dhpPop .3s both; }
/* Typing dots */
.dhp-typing { display: inline-flex; gap: 4px; padding: 12px 13px; }
.dhp-typing i { width: 6px; height: 6px; border-radius: 50%; background: var(--muted);
  opacity: .4; animation: dhpDot 1s infinite; }
.dhp-typing i:nth-child(2) { animation-delay: .16s; }
.dhp-typing i:nth-child(3) { animation-delay: .32s; }
@keyframes dhpDot { 0%, 60%, 100% { opacity: .3; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-2px); } }

/* Result card (rises over the composer) */
.dhp__result { flex: none; margin: 0 10px 10px; padding: 13px 14px; border-radius: 16px;
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: 0 12px 30px -16px rgba(20,45,80,.3); display: none; }
.dhp__result.is-show { display: block; animation: dhpRise .42s cubic-bezier(.2,.85,.3,1) both; }
@keyframes dhpRise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
.dhp-rhead { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; font-family: var(--font-mono);
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.dhp-badge { margin-left: auto; text-transform: none; letter-spacing: .02em; color: var(--dhp-good);
  background: var(--dhp-good-bg); padding: 3px 9px; border-radius: 999px; font-size: 11px; }
/* mini agenda */
.dhp-ev { display: flex; gap: 11px; align-items: center; padding: 7px 0; }
.dhp-ev + .dhp-ev { border-top: 1px solid var(--border); }
.dhp-ev__d { width: 42px; flex: none; text-align: center; border-radius: 10px; padding: 5px 0; line-height: 1;
  background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
.dhp-ev__d b { display: block; font-size: 17px; letter-spacing: -.02em; }
.dhp-ev__d span { font-family: var(--font-mono); font-size: 9px; text-transform: uppercase; letter-spacing: .12em; }
.dhp-ev__b { min-width: 0; }
.dhp-ev__b b { font-size: 13.5px; color: var(--ink); }
.dhp-ev__b span { display: block; font-size: 11.5px; color: var(--muted); margin-top: 1px; }
.dhp-ev__c { margin-left: auto; font-size: 15px; }
/* email-rule table */
.dhp-tblwrap { overflow-x: auto; }
.dhp-tbl { width: 100%; border-collapse: collapse; font-size: 11.5px; }
.dhp-tbl th { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); text-align: left; padding: 0 8px 6px 0; font-weight: 600; white-space: nowrap; }
.dhp-tbl td { padding: 6px 8px 6px 0; border-top: 1px solid var(--border); color: var(--ink); white-space: nowrap; }
.dhp-tbl td.n, .dhp-tbl th.n { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.dhp-tbl tr.fresh td { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.dhp-tbl tr.fresh td:first-child { border-radius: 8px 0 0 8px; }
.dhp-tbl tr.fresh td:last-child { border-radius: 0 8px 8px 0; }
/* composer (idle) */
.dhp__composer { flex: none; display: flex; align-items: center; gap: 9px; padding: 10px 14px 14px;
  border-top: 1px solid var(--border); background: var(--surface); }
.dhp__field { flex: 1; border: 1px solid var(--border); border-radius: 999px; padding: 9px 14px;
  color: var(--muted); font-size: 12.5px; background: var(--bg); }
.dhp__send { width: 34px; height: 34px; flex: none; border-radius: 50%; background: var(--accent);
  display: grid; place-items: center; color: var(--accent-ink); }
.dhp__send svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }

/* caption + progress dots */
.dhp__cap { font-family: var(--font-mono); font-size: 12.5px; letter-spacing: .01em; color: var(--muted);
  max-width: 46ch; line-height: 1.5; min-height: 20px; margin: 0; }
.dhp__cap strong { color: var(--ink); font-weight: 600; }
.dhp__dots { display: flex; gap: 8px; }
.dhp__dot { width: 8px; height: 8px; padding: 0; border: 0; border-radius: 50%; background: var(--border);
  cursor: pointer; transition: background .2s, transform .2s; }
.dhp__dot[aria-current="true"] { background: var(--accent); transform: scale(1.25); }

@media (prefers-reduced-motion: reduce) {
  .dhp-row, .dhp__result.is-show, .dhp-act { animation: none !important; }
  .dhp-typing i { animation: none; opacity: .5; }
}

/* ── Media view — the ingestion audit log (docs/SOURCES.md). A chronological ledger of every photo
   shared into chat + every web search/fetch Dustav ran. Its own read-only surface. ── */
/* A flex-row shell like the Inbox tab (#tab-inbox.inbox): the scrolling content column + the reused copilot
   rail. This also fixes the old "not left-aligned" bug (Media used to be a centred .tabpane). */
#tab-media.media { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row; }
.media-content { flex: 1 1 auto; min-width: 0; min-height: 0; overflow-y: auto; padding: 20px 30px 56px; }
.media-head { margin-bottom: 18px; }
/* Match the Inbox header: title 20px/800 display + the .inb-brand-meta stat (the "— sub" taglines died 2026-08-29). */
.media-title { font-family: var(--font-display); font-size: 20px; font-weight: 800; letter-spacing: -.02em; margin: 0; }
.media-sub { font-size: 14px; line-height: 1.5; max-width: 640px; margin: 6px 0 0; }
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; align-items: start; }

/* A COMPACT photo tile (2026-08-21 — the described 300px cards were billboards of boilerplate for what is
   now a conversation-photo trail). The tile IS the anchor: image + a date scrim; the ✉ badge marks a
   mail-sourced one; everything else (Go to email · Download · the inspect read) is one tap away in the
   shared lightbox. The date scrim + badge sit OVER the image, so their ink is fixed white on a dark wash —
   theme-independent by construction, like the badge always was. */
.media-tile { position: relative; display: block; aspect-ratio: 1 / 1; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); overflow: hidden; cursor: zoom-in; transition: border-color .15s ease; }
.media-tile:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.media-tile__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-tile__badge { position: absolute; top: 7px; right: 7px; width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; border-radius: 999px; background: rgba(0,0,0,.55); color: #fff; }
.media-tile__badge svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2; }
.media-tile__when {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 14px 8px 6px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .03em; color: #fff;
  background: linear-gradient(transparent, rgba(0,0,0,.55));
}
/* A freshly-arrived tile during a live sync — a soft accent flash so you see it land. The FILE row wears
   the same flash for the same reason (2026-08-26): both halves of the Files tab fill from the same push,
   so a receipt and a photo should land looking like the same event. */
.media-tile.is-fresh,
.file-row.is-fresh { animation: media-land 1.2s ease-out; }
@keyframes media-land { 0% { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); } 100% { border-color: var(--border); box-shadow: none; } }

.media-empty { font-size: 14px; margin-top: 4px; } /* the box (padding/border/centered) comes from .inb-empty */
.media-more {
  display: block; margin: 20px auto 0; padding: 9px 18px; font: inherit; font-size: 14px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: var(--ink); cursor: pointer;
}
.media-more:hover { border-color: var(--accent); }

/* Mobile: stack the shell + drop the copilot to a bottom drawer (matches Inbox/Notes). */
@media (max-width: 879px) {
  #tab-media.media { flex-direction: column; padding: var(--mtop-h) 0 0; }
  .media-content { padding: 16px 16px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--copilot-peek) + 16px); }
}

/* ── Files — the inbound file library (docs/DOCUMENTS.md §5; the surface is the Files tab) ───────────
   Reuses the Media shell (same flex-row surface + copilot rail), but a file is a ROW, not a tile: the
   scannable content is typed text (kind · title · amount · reference), and ~20% of real files have no
   preview at all. So the row leads with the words and treats the thumbnail as decoration it can lose. */
#tab-files.files { max-width: none; margin: 0; height: 100dvh; padding: 0; display: flex; flex-direction: row; }
.file-content { flex: 1 1 auto; min-width: 0; min-height: 0; overflow-y: auto; padding: 20px 30px 56px; }
.file-head { margin-bottom: 14px; }
.file-title { font-family: var(--font-display); font-size: 20px; font-weight: 800; letter-spacing: -.02em; margin: 0; }
.file-sub { font-size: 13px; margin: 4px 0 0; }

/* The filter chips are GENERATED from the doc.kind enum (services/files.KINDS) — the vocabulary is the UI.
   Only kinds WITH documents render (the route filters — 2026-08-24: six "0" chips pushed Photos onto a
   lonely second line); Photos sits inline as one chip among the others, and only when photos exist. */
/* Search over what Dustav READ inside each file — the reason this surface exists for a scanned receipt
   (docs/DOCUMENTS.md §7). A plain GET form, so no JS and a shareable URL. */
.file-search { display: flex; gap: 8px; align-items: center; margin: 0 0 12px; }
.file-search__in {
  flex: 1 1 auto; min-width: 0; padding: 8px 12px; font: inherit; font-size: 13.5px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: var(--ink);
}
.file-search__in:focus { outline: none; border-color: var(--accent); }
.file-search__go {
  padding: 8px 16px; font: inherit; font-size: 13px; cursor: pointer; flex: none;
  border: 1px solid var(--accent); border-radius: 999px; background: var(--accent); color: var(--accent-ink);
}
.file-search__clear { font-size: 12.5px; color: var(--muted); text-decoration: none; flex: none; }
.file-search__clear:hover { color: var(--ink); }
/* The matching bit of the reading. Quoted + accent-edged so it reads as "from inside the file", not as
   more metadata — it's the evidence that Dustav opened the page. */
.file-row__snip {
  margin-top: 6px; padding-left: 9px; border-left: 2px solid color-mix(in srgb, var(--accent) 45%, transparent);
  font-size: 12.5px; color: var(--muted); line-height: 1.5;
}

.file-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 16px; }
.file-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface); color: var(--ink);
  font-size: 12.5px; text-decoration: none; transition: border-color .12s, background .12s;
}
.file-chip:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.file-chip.is-active { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.file-chip__n { font-family: var(--font-mono); font-size: 11px; opacity: .7; font-variant-numeric: tabular-nums; }

.file-list { display: flex; flex-direction: column; gap: 8px; }
/* The ROW is a CARD now (2026-08-24 re-cut — the .inb-row/.todo-card box, one shared grammar): click → the
   shared preview lightbox, drag → the desktop (attach-drag.js). Body is TWO lines (title + muted meta); the
   facts/per-file chips left the row (lightbox caption / ⋯ menu). position:relative anchors the mobile ⋯ pin. */
.file-row {
  position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px 14px; align-items: start;
  padding: 12px 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  cursor: pointer;
}
.file-row:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
/* Being removed (the ⋯ menu's Remove from Files) — a quiet fade before it hides; Undo brings it back. */
.file-row.is-removing { opacity: 0; transition: opacity .18s ease; }
/* The deep-link landing flash — every "/surface/<id> lands ON the row" target shares it (Files was first,
   2026-08-24; TODO + Intelligence joined 2026-08-29 when the found-line chips went row-level). One keyframes,
   one meaning: "this is the thing your link pointed at". */
.file-row.is-target, .todo-card.is-target, .intel-card.is-target, .fact-card.is-target { animation: card-target 1.6s ease-out; }
@keyframes card-target { 0% { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); } 100% { border-color: var(--border); box-shadow: none; } }
/* Fixed-size preview slot so a row with a thumbnail and one without line up — the no-preview case is the
   norm for exactly the biggest files, so it can't be allowed to look broken. */
.file-row__pre { width: 44px; height: 56px; display: flex; align-items: center; justify-content: center; border-radius: 6px; background: var(--bg); border: 1px solid var(--border); overflow: hidden; }
.file-row__thumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.file-row__glyph { color: var(--muted); opacity: .55; display: inline-flex; }
.file-row__glyph .icon { width: 20px; height: 20px; }
.file-row__body { min-width: 0; display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }
.file-row__title { font-family: var(--font-display); font-weight: 600; font-size: 14.5px; line-height: 1.35; color: var(--ink); }
.file-row__meta { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 12px; color: var(--muted); }
.file-row__file { font-family: var(--font-mono); font-size: 11px; opacity: .8; }
/* A BODY document has no file — the meta line says so instead of sitting empty (docs/FILE_CAPTURE.md). */
.file-row__file--body { font-style: italic; opacity: .65; }
/* The rail is [kind · date · ✉ 1 · ⋯] — geometry comes from the SHARED .card-rail spec (one box spec with
   the TODO/Intelligence cards; splitting it is how they drift). Only the date's type is local. */
.file-row__when { font-family: var(--font-mono); font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The kind pill. One hue family — this is a taxonomy, not a status, so the pills shouldn't read as severity;
   money-shaped kinds share the accent, the rest are neutral. */
.file-pill {
  font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .06em;
  padding: 2px 7px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted); background: var(--bg); flex: none;
}
.file-pill--receipt, .file-pill--invoice, .file-pill--statement, .file-pill--quote {
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.file-empty { font-size: 14px; margin-top: 4px; }

@media (max-width: 879px) {
  #tab-files.files { flex-direction: column; padding: var(--mtop-h) 0 0; }
  .file-content { padding: 16px 16px calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--copilot-peek) + 16px); }
  /* The shared THREE-ROW card grammar (2026-08-24): row 1 = thumb + title + the filed DATE (it takes the
     inbox time's exact home — absolute, left of the ⋯) + ⋯, row 2 = the meta line (kebab lane reserved),
     row 3 = the kind pill + ✉ bottom-left. No on-card verbs on this surface — the foot's right half is
     simply empty, same grammar, fewer players. */
  .file-row { grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "pre body" "rail rail"; gap: 8px 12px; }
  .file-row__pre { grid-area: pre; }
  .file-row__body { grid-area: body; }
  .file-row__rail { grid-area: rail; justify-self: start; align-self: end; flex-wrap: wrap; padding-top: 0; }
  /* right = the card's own 16px padding, flush with the foot's edge (the inbox rule). */
  .file-row .inb-kebab { position: absolute; top: 11px; right: 16px; }
  .file-row__when { position: absolute; top: 20px; right: 58px; }
  .file-row__title { padding-right: 130px; }
  .file-row__meta, .file-row__snip { padding-right: 52px; }
}

/* Found in email — the daily-digest review queue (docs/GMAIL.md §Stage 1). A vertical list of surfaced
   emails (what it is + the receipt), each with Review-with-Dustav / Dismiss. Surface, not extract. */
.review-page { max-width: 720px; }
.review-title { margin: 0 0 4px; }
.review-sub { margin: 0 0 18px; font-size: 14px; }
.finding-list { display: flex; flex-direction: column; gap: 12px; }
.finding {
  display: flex; gap: 14px; align-items: flex-start; justify-content: space-between;
  padding: 14px 16px; border: 1px solid var(--border); border-radius: 14px; background: var(--surface);
  transition: opacity .18s ease, transform .18s ease;
}
.finding.is-leaving { opacity: 0; transform: translateY(-4px); }
.finding__main { min-width: 0; flex: 1; }
.finding__topline { display: flex; align-items: center; gap: 8px; }
.finding__kind {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 8px; border-radius: 999px; white-space: nowrap;
  background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent);
}
.finding__kind--other { background: color-mix(in srgb, var(--muted) 20%, transparent); color: var(--muted); }
.finding__clip { display: inline-flex; color: var(--muted); }
.finding__clip .ic { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; }
.finding__summary { margin: 6px 0 5px; font-weight: 600; font-size: 15px; color: var(--ink); line-height: 1.35; }
/* Attachment previews on a finding card — small rounded thumbs that inspect + crop via the shared
   window.Lightbox/window.Cropper (the same click-to-zoom as chat + Media). */
.finding__thumbs { display: flex; flex-wrap: wrap; gap: 8px; margin: 2px 0 9px; }
.finding__thumb {
  display: block; width: 68px; height: 68px; border-radius: 9px; overflow: hidden;
  border: 1px solid var(--border); background: var(--panel); cursor: zoom-in; flex: none;
  transition: border-color 0.12s;
}
.finding__thumb:hover { border-color: var(--accent); }
.finding__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.finding__src { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; font-size: 12px; color: var(--muted); }
.finding__from { font-weight: 600; color: var(--ink); }
.finding__subj { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.finding__open { color: var(--accent); text-decoration: none; }
.finding__open:hover { text-decoration: underline; }
.finding__actions { display: flex; flex-direction: column; align-items: stretch; gap: 6px; flex-shrink: 0; }
.finding__actions .btn { padding: 6px 14px; font-size: 13px; white-space: nowrap; }
.finding-empty { margin-top: 22px; font-size: 14px; }
.finding-more {
  display: block; margin: 18px auto 0; padding: 9px 18px; font: inherit; font-size: 14px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface); color: var(--ink); cursor: pointer;
}
.finding-more:hover { border-color: var(--accent); }
@media (max-width: 560px) { .finding { flex-direction: column; } .finding__actions { flex-direction: row; align-self: stretch; } .finding__actions .btn { flex: 1; } }

/* Gmail "Run now" manual trigger in Settings (docs/GMAIL.md §Stage 1). */
.gmail-runrow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 8px 0 2px; }
.gmail-runrow .btn { padding: 6px 16px; font-size: 13px; flex-shrink: 0; }
.gmail-runrow .small { flex: 1; min-width: 180px; }

/* Forward-to-Dustav address row in Settings (docs/GMAIL.md — PR2): [read-only address] [Copy]. */
.fwd-addr { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 6px 0 8px; }
/* The address is DISPLAY text you copy, not an editable field — so it's a plain monospace <code>, not a
   bordered/padded input (which read as a text box). select-all on click/focus for easy manual copying. */
.fwd-addr__field {
  flex: 1 1 220px; min-width: 0; font-family: var(--font-mono, monospace); font-size: 14px;
  color: var(--ink); word-break: break-all; line-height: 1.5; user-select: all; -webkit-user-select: all;
}
.fwd-addr .btn { flex-shrink: 0; padding: 6px 16px; font-size: 13px; }

/* (.cal-media / .cal-media__see — the calendar modal's per-event source link — deleted at the GCal
   cutover with the ?event= scoped Files view they pointed at. GCAL_MIRROR §9b commit 6.) */

/* A field's honest footnote in the calendar modal — currently the one on Repeats, shown when Google
   owns a rule richer than the dropdown can express. Sits under the control, not beside it, so it
   reads as a consequence of the field rather than a second label. */
.cal-field__note { display: block; margin-top: 5px; font-size: 12.5px; line-height: 1.4; }
/* ⭐ A READ-ONLY FIELD HAS TO LOOK READ-ONLY (2026-08-31, from Bre's "Brave Boss Babes Meet-up").
   On a meeting someone else organizes, setInviteeMode disables Title/Date/Start/End/Location/Notes —
   the fence is real and the server enforces it too — but only the SELECT had a disabled treatment.
   The base rule above sets an explicit `color` and `background` on every input, which overrides the
   browser's own greying, so six dead fields rendered pixel-identical to live ones: she clicks the
   title of a meeting she's a guest on, types, and nothing happens, with nothing on screen having
   said why. The organizer note explains the situation; the fields have to agree with it.
   The readonly half carries the SAME dress: those fields are equally not-hers-to-change, and a
   different look would say a difference that isn't there. Matched on the attribute rather than the
   :read-only pseudo-class, which also matches ordinary disabled and non-editable elements. */
.cal-field select:disabled,
.cal-field input:disabled,
.cal-field textarea:disabled,
.cal-field input[readonly],
.cal-field textarea[readonly] {
  opacity: 0.6;
  background: color-mix(in srgb, var(--ink) 4%, var(--bg));
}
/* Only the truly inert ones refuse the pointer — a readonly field still takes a caret, because
   selecting the text is exactly what it is for. */
.cal-field select:disabled, .cal-field input:disabled, .cal-field textarea:disabled { cursor: not-allowed; }
/* The "← All media" back link on the Media view's scoped (?event=) mode. */
/* Visuals from the shared .back-btn pill; this keeps only the spacing above the media title. */
.media-back { margin-bottom: 8px; }

/* ── THE PDF VIEWER (views/dashboard/pdf-view.hbs — the in-app escape from raw-PDF navigation) ── */
#tab-pdfview { max-width: 900px; margin: 0 auto; padding: clamp(14px, 2.4vw, 28px); }
#tab-pdfview .pdfv__bar { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
#tab-pdfview .pdfv__back, #tab-pdfview .pdfv__dl { font-size: 13px; color: var(--muted); text-decoration: none; white-space: nowrap; }
#tab-pdfview .pdfv__back:hover, #tab-pdfview .pdfv__dl:hover { color: var(--ink); }
#tab-pdfview .pdfv__name { flex: 1; min-width: 0; font-size: 13px; font-weight: 600; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }
#tab-pdfview .pdfv__pages { display: flex; flex-direction: column; gap: 12px; }
#tab-pdfview .pdfv__page { width: 100%; height: auto; display: block; background: #fff;
  border: 1px solid var(--border); border-radius: 8px; }
@media (max-width: 879px) {
  #tab-pdfview { padding-top: calc(var(--mtop-h) + 12px); }
}

/* ============================================================
   OVERVIEW — the business homepage dashboard (services/overview.js, views/dashboard/overview.hbs)
   Dustav (the big Blink mark) sits top-left overseeing; each domain is a card with a live count, a peek at
   the real open items, and a deep link into its tab. Native surface: uses the app tokens (--bg/--surface/
   --accent…) so it follows the theme + accent. Per-card categorical hue via --h, set per data-key BELOW
   (CSP blocks an inline style="" hue, so the CSS owns the values — mirroring node.hue in overview.js).
   Read-only, no client JS: the deep links ARE the interaction.

   ⚠️ The canvas band that took this surface over Aug 2026 was DELETED 2026-09-01 (two design partners
   called it noise; the reasoning is in CLAUDE.md). Its whole class family went with it — the no-client-JS
   line above is load-bearing again, and this page renders identically with scripts off.
   ============================================================ */
#tab-overview { display: grid; grid-template-columns: minmax(220px, 280px) 1fr; gap: clamp(20px, 3vw, 40px);
  align-items: start; padding: clamp(18px, 2.6vw, 34px); min-height: 100%; }

/* ── the hero: Dustav overseeing, top-left, sticky so it presides while the cards scroll ── */
#tab-overview .ovh__hero { position: sticky; top: clamp(18px, 2.6vw, 34px); display: flex; flex-direction: column; align-items: flex-start; }
#tab-overview .ovh__marks { display: flex; align-items: center; gap: 18px; margin-bottom: 16px; }
#tab-overview .ovh__mark { position: relative; width: clamp(96px, 8vw, 128px); height: clamp(96px, 8vw, 128px); }
#tab-overview .ovh__mark .brand-mark-svg { width: 100%; height: 100%; }
/* The business logo beside Dustav — a white chip (arbitrary brand art needs a light ground in dark mode;
   the Profile hero's call) sized to the mark, with a slow gentle bob. Its own keyframes + a negative
   delay so it never moves in lockstep with Blink's float — the desynced-tics doctrine. */
#tab-overview .ovh__logo {
  width: clamp(96px, 8vw, 128px); height: clamp(96px, 8vw, 128px);
  object-fit: contain; background: #fff; padding: 10px; border-radius: 26px; border: 1px solid var(--border);
  animation: ovhLogoBob 4.5s ease-in-out infinite; animation-delay: -2.1s;
}
@keyframes ovhLogoBob { 0%, 100% { translate: 0 0; } 50% { translate: 0 -5px; } }
@media (prefers-reduced-motion: reduce) { #tab-overview .ovh__logo { animation: none; } }
/* (No glow/halo behind the mark — a radial accent ::before lived here and read as a smudgy box shadow;
   removed 2026-08-20, Curtis. The mark sits flat on the page ground.) */
#tab-overview .ovh__eyebrow { margin: 0; font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
#tab-overview .ovh__name { margin: 5px 0 0; font-size: clamp(21px, 2.6vw, 27px); font-weight: 800; letter-spacing: -.02em; line-height: 1.15; color: var(--ink); }
#tab-overview .ovh__readout { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 14px;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
#tab-overview .ovh__readout b { color: var(--ink); font-variant-numeric: tabular-nums; }
#tab-overview .ovh__chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 18px; }
#tab-overview .ov-chip { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; font-size: 12px; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px; transition: border-color .2s, color .2s; }
#tab-overview .ov-chip b { color: var(--ink); font-variant-numeric: tabular-nums; }
#tab-overview .ov-chip:hover { border-color: var(--accent); color: var(--ink); }

/* ── the card grid ── */
#tab-overview .ovh__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 16px; }

#tab-overview .ov-card { --h: 210; display: flex; flex-direction: column; background: var(--surface);
  border: 1px solid var(--border); border-left: 3px solid hsl(var(--h) 60% 52%); border-radius: 14px;
  padding: 14px 16px 12px; transition: border-color .18s, box-shadow .18s, transform .18s; }
#tab-overview .ov-card:hover { box-shadow: 0 8px 26px hsl(var(--h) 45% 40% / .16); transform: translateY(-2px); }
/* categorical hue per domain — the values mirror node.hue in services/overview.js (kept CSS-side for CSP).
   One key per node overview.js can actually push: needs · todos · calendar · questions · knows. (The
   "books" hue that used to sit here died with the financial spine — docs/SPINE_TEARDOWN.md.) */
#tab-overview .ov-card[data-key="needs"] { --h: 6; }
#tab-overview .ov-card[data-key="todos"] { --h: 208; }
#tab-overview .ov-card[data-key="calendar"] { --h: 45; }
#tab-overview .ov-card[data-key="questions"] { --h: 270; }
#tab-overview .ov-card[data-key="knows"] { --h: 322; }

#tab-overview .ov-card__head { display: flex; align-items: center; gap: 10px; text-decoration: none; color: var(--ink); }
#tab-overview .ov-card__ic { width: 20px; height: 20px; flex: none; color: hsl(var(--h) 58% 44%); }
[data-theme="dark"] #tab-overview .ov-card__ic { color: hsl(var(--h) 72% 64%); }
#tab-overview .ov-card__ic svg { width: 100%; height: 100%; display: block; }
#tab-overview .ov-card__label { flex: 1; min-width: 0; font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
#tab-overview .ov-card__head:hover .ov-card__label { color: var(--ink); }
#tab-overview .ov-card__n { font-size: 26px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1; color: hsl(var(--h) 60% 42%); }
[data-theme="dark"] #tab-overview .ov-card__n { color: hsl(var(--h) 74% 66%); }
#tab-overview .ov-card.is-urgent .ov-card__ic { animation: ovPulse 2.4s ease-in-out infinite; }
@keyframes ovPulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }
#tab-overview .ov-card.is-clear { border-left-color: var(--border); }
#tab-overview .ov-card.is-clear .ov-card__n { color: var(--muted); }

#tab-overview .ov-card__blurb { margin: 10px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--muted); }

#tab-overview .ov-card__list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; }
#tab-overview .ov-card__list li + li { border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
#tab-overview .ov-item { display: flex; align-items: center; gap: 9px; padding: 7px 0; text-decoration: none; color: var(--ink); }
#tab-overview .ov-item__em { flex: none; font-size: 14px; }
#tab-overview .ov-item__body { display: flex; flex-direction: column; min-width: 0; }
#tab-overview .ov-item__t { font-size: 13px; line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#tab-overview .ov-item__s { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
#tab-overview .ov-item:hover .ov-item__t { color: hsl(var(--h) 58% 44%); }
[data-theme="dark"] #tab-overview .ov-item:hover .ov-item__t { color: hsl(var(--h) 72% 66%); }

#tab-overview .ov-card__empty { margin: 10px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--muted); }
#tab-overview .ov-card__more { margin-top: 11px; padding-top: 10px; border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); text-decoration: none; transition: color .18s; }
#tab-overview .ov-card__more span { transition: transform .18s; display: inline-block; }
#tab-overview .ov-card__more:hover { color: hsl(var(--h) 58% 44%); }
[data-theme="dark"] #tab-overview .ov-card__more:hover { color: hsl(var(--h) 72% 66%); }
#tab-overview .ov-card__more:hover span { transform: translateX(3px); }

/* ── narrow: hero stacks on top, cards below ── */
@media (max-width: 860px) {
  #tab-overview { grid-template-columns: 1fr; }
  #tab-overview .ovh__hero { position: static; }
}
/* ⚠️ The stacking breakpoint above is 860px but the mobile CHROME (fixed top bar + icon rail) switches at 879 —
   so this clearance is keyed to 879, not 860, or the hero renders under the bar in that 19px band. Overview had
   a uniform clamp() padding that maxes at 34px, which is less than the 54px bar: it looked "nearly right",
   which is why it went unnoticed longer than the tabs with no padding at all. */
@media (max-width: 879px) {
  #tab-overview { padding-top: calc(var(--mtop-h) + 18px); }
}
@media (prefers-reduced-motion: reduce) {
  #tab-overview .ov-card.is-urgent .ov-card__ic { animation: none !important; }
}
