/* Kernel — the interface.
 *
 * Built on the design DNA in tokens.css: a warm neutral canvas, white objects
 * raised off it with a top highlight and a multi-layer shadow, exactly one
 * gradient primary per surface, and colour spent only where it carries meaning.
 *
 * The rules that shape everything below:
 *   1. Depth. Every raised surface gets --inset-highlight plus a real shadow.
 *      Flat white on white is the thing that makes software look unfinished.
 *   2. Restraint. Violet is the one action. Amber means a run is live. Green,
 *      red and blue are states. Nothing else is coloured.
 *   3. Hover is a small lift, press is a real concave, and both are felt more
 *      than seen. No glow blooms.
 *   4. Nothing carries meaning by colour alone; every state also says its name.
 */

@import "tokens.css";

/* ==================================================================== base */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* A class that sets `display` outranks the UA rule for the `hidden` attribute,
   so `.scrim { display: grid }` once kept an invisible backdrop over the whole
   window. Anything hidden must actually be hidden. */
[hidden] { display: none !important; }

/* The window carries an NSVisualEffect layer (sidebar material). Body
   stays transparent so the RAIL can show it; the main column must not —
   a see-through dashboard lets drag-invalidation flash the desktop
   through the dark UI. `.content` is the opaque layer. */
html, body {
  height: 100%;
  background: transparent;
  color: var(--ink-body);
  font: 400 13.5px/1.5 var(--font-ui);
  font-feature-settings: "cv05", "ss03";
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
  cursor: default;
  user-select: none;
}

/* Windows/Linux use an opaque window (macOS keeps its vibrancy), so the body
   must paint the canvas itself. Without this the translucent sidebar would
   reveal the window's default white backing instead of the app's dark base. */
:root[data-os="windows"] body,
:root[data-os="linux"] body,
:root[data-platform="web"] body {
  background: var(--canvas);
}

button { font: inherit; color: inherit; border: 0; background: none; cursor: default; }
input, textarea, select { font: inherit; color: inherit; }

h1, h2, h3 { color: var(--ink); }

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

:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 2px;
  border-radius: var(--radius-chip);
}

/* The one icon system: official Phosphor glyphs from the sprite in index.html.
   currentColor so a glyph always matches the text it sits beside. */
.ic {
  width: 1em; height: 1em;
  flex: 0 0 auto;
  fill: currentColor;
  pointer-events: none;
}

/* One voice for every uppercase micro-label. Two registers only: standalone
   section labels breathe at 0.08em; tags sitting inline with content run a
   touch tighter at 0.06em. Sites override colour when they carry state. */
.rail-label, .eyebrow, .section-label, .stat-label, .learned-head, .learned-key,
.board-head, .note-label, .role-name, .field label, .answer::before {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.verb, .tag, .src-kind, .item-kind, .board-role {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.titlebar {
  position: fixed;
  inset: 0 0 auto 0;
  height: var(--titlebar);
  z-index: 10;
  /* Overlay strip only: the traffic lights sit here. Unpainted so the
     sidebar's glass and the content pane's canvas show through it. */
  background: transparent;
}
/* The traffic lights live over the rail. Keeping the primary drag overlay to
   that same width lets a session use the top-right of the window instead of
   reserving an empty titlebar above its content. Dialog/update overlays keep
   their own full-width drag regions. */
body > .titlebar {
  right: auto;
  width: var(--sidebar-w);
}

/* Windows/Linux draw a native title bar, so the macOS overlay strip is
   redundant and would render as a transparent band across the top. Collapse
   the reserved space and hide the strip there. */
:root[data-os="windows"],
:root[data-os="linux"],
:root[data-platform="web"] {
  --titlebar: 0px;
}
:root[data-os="windows"] .titlebar,
:root[data-os="linux"] .titlebar,
:root[data-platform="web"] .titlebar {
  display: none;
}

.shell { display: flex; height: 100vh; }

/* ================================================================= sidebar */
.sidebar {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  display: flex;
  flex-direction: column;
  padding-top: var(--titlebar);
  /* Frost over the window's sidebar vibrancy. The wash is light on purpose:
     a packaged Mac window supplies the real glass; backdrop-filter is what
     an unpackaged WebView (and Playwright) can still do. */
  background: color-mix(in srgb, var(--canvas-cool) 36%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(22px);
  backdrop-filter: saturate(180%) blur(22px);
  border-right: 1px solid var(--hairline);
}

/* Glass is a Mac look. Windows and Linux keep an opaque rail so the
   transparent window backing never flashes through as white. */
:root[data-os="windows"] .sidebar,
:root[data-os="linux"] .sidebar,
:root[data-platform="web"] .sidebar {
  background: var(--canvas-cool);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Hard against the top-left, as far as macOS allows: --titlebar already
   reserves the traffic lights' row (they end at y=34), so a second 12px inset
   under it just left the mark floating in 20px of nothing. Windows and Linux
   collapse --titlebar to 0 and draw a native bar, so there the brand supplies
   its own top inset instead of sitting on the window edge.
   The 16px below is the brand's whole separation from the nav, and it is now
   unconditional: the health strip that used to appear between them moved to
   the content area (see .notice), so the rail no longer changes shape when
   something needs doing. */
.brand {
  display: flex; align-items: center; gap: 10px;
  padding: 2px 16px 20px;
}
:root[data-os="windows"] .brand,
:root[data-os="linux"] .brand,
:root[data-platform="web"] .brand { padding-top: 14px; }
.brand-logo { width: 24px; height: 22px; flex: 0 0 24px; -webkit-user-drag: none; user-select: none; }
.brand-mark {
  font: 700 17px/1 var(--font-display);
  letter-spacing: -0.022em;
  color: var(--ink);
}
.nav { display: flex; flex-direction: column; gap: 4px; padding: 0 12px; }
.tab {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  padding: 6px 10px;
  border-radius: var(--radius-btn);
  color: var(--ink-body);
  font-size: 11.5px;
  font-weight: 500;
  text-align: left;
  transition: background var(--dur-hover) var(--ease-soft),
              color var(--dur-hover) var(--ease-soft);
}
.tab .ic { font-size: 14px; color: var(--ink-muted); transition: color var(--dur-hover) var(--ease-soft); }
.tab-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tab:hover { background: var(--bg-hover); color: var(--ink); }
.tab:hover .ic { color: var(--ink-body); }
.tab.is-active {
  /* A wash on the glass, not a raised card: Cursor's selected row. */
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  color: var(--ink);
  font-weight: 600;
  box-shadow: none;
}
.tab.is-active .ic { color: var(--violet); }
:root[data-os="windows"] .tab.is-active,
:root[data-os="linux"] .tab.is-active,
:root[data-platform="web"] .tab.is-active {
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest);
}

.count {
  min-width: 19px;
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--ink-body);
  font-size: 10.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: center;
  white-space: nowrap;
  flex-shrink: 0;
}
.tab.is-active .count { background: var(--surface-2); }
/* Explicit, because the badge is a flex item and a UA `display: none` is the
   only thing standing between a hidden count and a visible empty capsule. */
.count[hidden] { display: none; }

.rail-label {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 24px 16px 10px;
  /* The rail runs 15% under the content type scale; the shared 11px
     micro-label rule stays for every label outside it. */
  font-size: 10px;
}
.rail-label[hidden] { display: none; }

.agent-list { flex: 1; overflow-y: auto; padding: 0 12px 10px; }

/* The fold of finished sessions: one quiet line between the live rows and the
   archive, with the broom on the right. Same register as .rail-label so it
   reads as structure, not as another session. */
.agents-fold {
  display: flex; align-items: center; gap: 6px;
  margin: 8px 2px 4px;
}
.agents-fold-toggle {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 6px;
  padding: 4px 6px;
  border-radius: var(--radius-chip);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
  transition: color var(--dur-hover) var(--ease-soft), background var(--dur-hover) var(--ease-soft);
}
.fold-label { overflow: hidden; text-overflow: ellipsis; }
.fold-failures { flex: none; }
.agents-fold-toggle:hover { color: var(--ink-body); background: var(--bg-hover); }
.agents-fold-toggle .ic {
  width: 12px; height: 12px;
  transition: transform 300ms var(--ease-soft);
}
.agents-fold-toggle[aria-expanded="true"] .ic { transform: rotate(180deg); }
.fold-failures { color: var(--ink-danger); letter-spacing: 0.04em; }
.agents-fold-clear {
  flex: none;
  padding: 3px 10px;
  border-radius: var(--radius-chip);
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink-faint);
  transition: color var(--dur-hover) var(--ease-soft), background var(--dur-hover) var(--ease-soft);
}
.agents-fold-clear:hover { color: var(--ink-danger); background: var(--tint-red); }

/* One thin line, like Cursor's chat list: status dot, truncated title,
   a small status word. Activity is a tooltip, not a second line, and the
   selected row is a quiet highlight, not a padded card. */
.agent {
  position: relative;
  display: grid;
  grid-template-columns: 6px minmax(0, 1fr) auto auto;
  align-items: center;
  column-gap: 8px;
  width: 100%;
  padding: 3px 6px;
  margin-bottom: 1px;
  border-radius: var(--radius-chip);
  line-height: 1.25;
  text-align: left;
  transition: background var(--dur-hover) var(--ease-soft);
}
.agent:hover { background: var(--bg-hover); }
.agent.is-active {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  box-shadow: none;
}
.agent .state-dot {
  width: 6px; height: 6px; flex-basis: 6px;
  align-self: center;
}
.agent-name {
  min-width: 0;
  /* Half a point down from the body size. The rail is 216px and the name is
     the first thing to get an ellipsis, so it buys about two characters. */
  font-size: 10.5px; font-weight: 500;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Where it runs: the switch's laptop or cloud, at the weight of a footnote.
   One ink for both. The state word beside it is the row's only colour, and a
   violet cloud on every row of a cloud account would be a second one. */
.agent-where {
  display: inline-flex;
  color: var(--ink-faint);
}
.agent-where .ic { width: 12px; height: 12px; font-size: 12px; }
.agent:hover .agent-where,
.agent.is-active .agent-where { color: var(--ink-muted); }
.agent-word {
  min-width: 0;
  font-size: 10px;
  font-weight: 500;
  color: var(--ink-faint);
  text-transform: capitalize;
  white-space: nowrap;
}
.agent-word[data-state="running"],
.agent-word[data-state="planning"],
.agent-word[data-state="paused"],
.agent-word[data-state="waiting"] { color: var(--ink-pending); }
.agent-word[data-state="starting"],
.agent-word[data-state="queued"] { color: var(--ink-progress); }
.agent-word[data-state="done"] { color: var(--ink-success); }
.agent-word[data-state="failed"],
.agent-word[data-state="exhausted"] { color: var(--ink-danger); }
.agent-word[data-state="stopping"],
.agent-word[data-state="stopped"],
.agent-word[data-state="idle"] { color: var(--ink-muted); }

/* Goal folders: Cursor's project tree, not a second pile of chips. The
   header is the short goal title, sentence case, so it can be scanned. */
.agent-goal { margin-bottom: 4px; }
.agent-goal-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 6px 3px;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-muted);
  min-width: 0;
}
.agent-goal-head .ic {
  width: 12px;
  height: 12px;
  flex: 0 0 12px;
  opacity: 0.65;
}
.agent-goal-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.agent-goal-rows { padding-left: 2px; }
.agent-goal .agent { margin-bottom: 2px; }

.roster-goal-head {
  margin: 18px 2px 8px;
  padding: 0 2px;
  font-size: 12px;
  color: var(--ink-body);
}
.roster-goal-head:first-child { margin-top: 4px; }
.roster-goal-head .ic { width: 14px; height: 14px; flex-basis: 14px; }

.state-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: var(--expired); }
.state-dot[data-state="running"]  { background: var(--pending); box-shadow: 0 0 0 3px var(--tint-amber); animation: breathe 2s ease-in-out infinite; }
.state-dot[data-state="starting"] { background: var(--progress); animation: breathe 1.2s ease-in-out infinite; }
/* Queued holds still on purpose: it is in a line, not in motion. Same blue as
   starting, because the run is about to happen either way. */
.state-dot[data-state="queued"]   { background: var(--progress); opacity: 0.5; }
.state-dot[data-state="paused"]   { background: var(--ink-muted); }
.state-dot[data-state="done"]     { background: var(--success); }
.state-dot[data-state="failed"]   { background: var(--danger); }
.state-dot[data-state="stopped"]  { background: var(--expired); }
.state-dot[data-state="stopping"] { background: var(--ink-muted); animation: breathe 0.9s ease-in-out infinite; }
.state-dot[data-state="idle"]     { background: var(--expired); opacity: 0.5; }

@keyframes breathe {
  0%, 100% { opacity: 0.45; transform: scale(0.84); }
  50%      { opacity: 1;    transform: scale(1); }
}

/* One quiet line, not a paragraph. The list it labels explains itself the
   moment it has a row in it. */
.sidebar-hint {
  padding: 20px 12px 8px;
  font-size: 10.5px;
  color: var(--ink-faint);
}

/* The rail's small furniture, below the tabs. Quieter than a tab, because
   these are not tabs. Sits above the credits card, where the eye goes last. */
.rail-link {
  display: flex; align-items: center; gap: 10px;
  margin: 0 10px 2px;
  padding: 7px 10px;
  border-radius: var(--radius-btn);
  font-size: 11px;
  color: var(--ink-muted);
  transition: background var(--dur-hover) var(--ease-soft),
              color var(--dur-hover) var(--ease-soft);
}
.rail-link:hover { background: var(--bg-hover); color: var(--ink-body); }
.rail-link .ic { font-size: 13px; opacity: 0.85; }
/* Standing on Connections. The same "you are here" a tab gets, one weight
   quieter, because this is furniture and not one of the six section tabs. */
.rail-link.is-active {
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  color: var(--ink);
  box-shadow: none;
}
.rail-link.is-active .ic { color: var(--violet); opacity: 1; }
:root[data-os="windows"] .rail-link.is-active,
:root[data-os="linux"] .rail-link.is-active,
:root[data-platform="web"] .rail-link.is-active {
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest);
}

/* ================================================================== status */
/* Pale tint plus a darker label, never the bright dot hue as text: a bright
   green on a pale green tint is about 1.7:1 and fails outright. */
.chip-state {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  box-shadow: var(--inset-track);
  color: var(--ink-body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.005em;
  text-transform: none;
  white-space: nowrap;
}
.chip-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 6px; }
.chip-state[data-state="running"], .chip-state[data-state="planning"] { background: var(--tint-amber); color: var(--ink-pending); }
.chip-state[data-state="running"] .chip-dot { animation: breathe 2s ease-in-out infinite; }
.chip-state[data-state="starting"],
.chip-state[data-state="queued"] { background: var(--tint-blue); color: var(--ink-progress); }
.chip-state[data-state="done"] { background: var(--tint-green); color: var(--ink-success); }
.chip-state[data-state="failed"], .chip-state[data-state="exhausted"] { background: var(--tint-red); color: var(--ink-danger); }
/* Paused is a held amber: the run is waiting on a person, same family as
   waiting but the dot holds still. Winding-down and settled states go quiet
   grey rather than falling through to the raw default capsule. */
.chip-state[data-state="paused"] { background: var(--tint-amber); color: var(--ink-pending); }
.chip-state[data-state="stopping"],
.chip-state[data-state="stopped"],
.chip-state[data-state="idle"] { background: var(--surface-sunken); color: var(--ink-muted); }
/* A capital on the first letter only. `capitalize` capitalises every word,
   so a paused goal's reason read "Paused · By You", "Paused · Send Hours". */
.chip-text::first-letter { text-transform: uppercase; }

/* ================================================================== notice */
/* The setup / health banner, across the top of the content area.
 *
 * It used to be a strip in the 216px rail, where every message was a sentence
 * and every sentence was too long: "Connect Google · Gmail & Calendar, X and 4
 * more so agents run as you" is 68 characters. Truncating it withheld the
 * instruction; wrapping it pushed the navigation down the rail. Neither is a
 * layout problem, it is a container problem, so the container moved.
 *
 * Here it gets the full content width, which fits every message on one line
 * with the button that fixes it on the right.
 */
.notice {
  flex: none;
  display: flex; align-items: center; gap: 12px;
  margin: 8px var(--gutter) 0;
  padding: 10px 10px 10px 14px;
  border-radius: var(--radius-btn);
  background: var(--tint-amber);
  color: var(--ink-pending);
  cursor: pointer;
  transition: filter var(--dur-hover) var(--ease-soft);
}
.notice[hidden] { display: none; }
.notice:hover { filter: brightness(1.06); }
.notice-art {
  flex: none;
  display: grid; place-items: center;
}
.notice-art .need-logo, .notice-art .notice-logo {
  width: 28px; height: 28px;
  border-radius: 8px;
}
.notice-art .need-logo img, .notice-art .notice-logo img { width: 16px; height: 16px; }
.notice-art .need-logo .ic { width: 14px; height: 14px; }
.notice.is-asks {
  padding: 12px 12px 12px 14px;
  box-shadow: var(--inset-highlight), var(--shadow-rest);
}
.notice-copy { flex: 1; min-width: 0; }
.notice-kicker {
  margin: 0 0 1px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: inherit;
  opacity: 0.72;
}
.notice-dot {
  flex: 0 0 8px;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: currentColor;
}
.notice-text {
  flex: 1; min-width: 0;
  margin: 0;
  font-size: 13px;
  font-weight: 500;
  /* The lines a message needs, up to three. It used to be held to one, but
     the end of a line is the fix ("...and press Enable"), and a paused
     connector's line lost it in any window under about 1340px. The ellipsis
     is the backstop for text with no bound of its own, like a daemon's
     error detail. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  overflow-wrap: anywhere;
  text-wrap: pretty;
}
/* The verb, so the banner is never a dead end. Sized as the small button from
   tokens.css §10, and tinted from the strip rather than given its own colour:
   a second hue here would compete with the message. */
.notice-act {
  flex: none;
  padding: 3px 10px;
  border-radius: var(--radius-chip);
  background: color-mix(in srgb, currentColor 14%, transparent);
  color: inherit;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  transition: background var(--dur-hover) var(--ease-soft);
}
.notice-act[hidden] { display: none; }
.notice-act:hover { background: color-mix(in srgb, currentColor 24%, transparent); }
/* Its ×: quiet until it is wanted. A 26px target around a 12px mark, in the
   strip's own colour, and only as loud as the verb beside it on hover. */
.notice-close {
  flex: none;
  display: grid; place-items: center;
  width: 26px; height: 26px;
  margin-left: -4px;
  border-radius: var(--radius-chip);
  color: inherit;
  opacity: 0.55;
  transition: opacity var(--dur-hover) var(--ease-soft), background var(--dur-hover) var(--ease-soft);
}
.notice-close[hidden] { display: none; }
.notice-close .ic { width: 12px; height: 12px; }
.notice-close:hover, .notice-close:focus-visible {
  opacity: 1;
  background: color-mix(in srgb, currentColor 14%, transparent);
}
.notice-close:focus-visible { outline: 2px solid currentColor; outline-offset: 1px; }
/* A dead daemon is an error. A step still to do is not, and colouring it red
   would spend the alarm on a first run that is going fine. */
.notice[data-tone="bad"] { background: var(--tint-red); color: var(--ink-danger); }
/* Something the owner chose, like pausing the connector: nothing is wrong,
   so it is said in violet, never in amber. */
.notice[data-tone="info"] { background: var(--tint-violet); color: var(--ink-violet); }

/* First run's line, on the page it is about. The notice's footing -- a
   sentence at content width with the verbs on the right -- in violet, because
   nothing is wrong: this is what to do here, not what went wrong. The dot is
   a status light on the kicker row and the sentence hangs under it from the
   same left edge, so nothing has to be centred against two lines of two
   different weights; the buttons centre against the block as a whole. Two
   verbs at most: Skip (its cost in the title) while the step is open, and
   Next or Done once there is somewhere to go. Not clickable as a whole,
   unlike the notice, because with two buttons every miss would be a Skip. */
.guide {
  flex: none;
  display: flex; align-items: center; gap: 16px;
  margin: 8px var(--gutter) 0;
  padding: 10px 12px 11px 14px;
  border-radius: var(--radius-btn);
  background: var(--tint-violet);
  color: var(--violet);
}
.guide[hidden] { display: none; }
.guide-copy { flex: 1; min-width: 0; }
.guide-kicker {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 3px;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: inherit;
  opacity: 0.78;
}
.guide-dot {
  flex: none;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: currentColor;
}
.guide-text {
  margin: 0;
  /* The dot's width and the gap after it, so the sentence starts where the
     kicker's words do and the dot hangs in the margin. */
  padding-left: 15px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--ink);
}
.guide-acts { flex: none; display: flex; align-items: center; gap: 6px; }
.guide-act {
  flex: none;
  padding: 4px 11px;
  border: 0;
  border-radius: var(--radius-chip);
  background: color-mix(in srgb, currentColor 14%, transparent);
  color: inherit;
  font: 600 12px/1.5 var(--font-ui);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-soft);
}
.guide-act[hidden] { display: none; }
.guide-act:hover { background: color-mix(in srgb, currentColor 24%, transparent); }
.guide-act.is-quiet { background: transparent; color: var(--ink-muted); }
.guide-act.is-quiet:hover { background: color-mix(in srgb, currentColor 12%, transparent); color: var(--ink); }
.guide-act.is-loading { opacity: 0.6; }

/* Where to press, on the page itself. While first run's line is up for a
   step, the button it names carries a slow violet ring -- "press New
   profile" is a sentence, a lit button is a place. The attribute is on the
   view, not the button, so the page can repaint under it. An outline rather
   than a shadow: every one of these buttons already has a shadow of its own,
   and an animated shadow would have taken it away for the duration. */
#view-connections[data-guide="sessions"] #connector-install,
#view-connections[data-guide="sessions"][data-connector="none"] #copy-sessions,
#view-profiles[data-guide="company"] #profile-new,
#view-profiles[data-guide="company"] .empty .btn.cta,
#view-goals[data-guide="goal"] .playbook[data-id="find-users"],
#view-goals[data-guide="goal"] .playbook[data-id="post-on-reddit"] {
  outline: 2px solid color-mix(in srgb, var(--violet) 75%, transparent);
  outline-offset: 3px;
  animation: guide-ring 2.2s ease-in-out infinite;
}
/* Breathes between two strengths rather than fading out: a ring that is
   transparent half the time is a ring a glance can miss. */
@keyframes guide-ring {
  0%, 100% { outline-color: color-mix(in srgb, var(--violet) 75%, transparent); outline-offset: 3px; }
  50% { outline-color: color-mix(in srgb, var(--violet) 30%, transparent); outline-offset: 6px; }
}
@media (prefers-reduced-motion: reduce) {
  #view-connections[data-guide="sessions"] #connector-install,
  #view-connections[data-guide="sessions"][data-connector="none"] #copy-sessions,
  #view-profiles[data-guide="company"] #profile-new,
  #view-profiles[data-guide="company"] .empty .btn.cta,
  #view-goals[data-guide="goal"] .playbook[data-id="find-users"],
  #view-goals[data-guide="goal"] .playbook[data-id="post-on-reddit"] {
    animation: none;
    outline-color: var(--violet);
    outline-offset: 3px;
  }
}

/* ================================================================= content */
.content {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  padding-top: var(--titlebar);
  /* Solid paint, own stacking context: Tauri's sidebar effect is a
     full-window NSVisualEffectView under the webview. Without an opaque
     layer here, moving the window invalidates that effect and the
     dashboard flashes the desktop through. Native code also pins the
     effect view to the rail; this is the CSS half of the same split. */
  background-color: var(--canvas);
  isolation: isolate;
  position: relative;
  z-index: 0;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* A session, and the computer it may mount, is a window-edge surface, not
   content inside the document gutter. Let it occupy the macOS titlebar strip
   so the inspector paints cleanly to the top edge. JavaScript owns this
   explicit class because the deployed WKWebView cannot reliably apply
   relational `:has()` selectors.
   Keyed on the view and nothing else: the computer stays mounted when the
   owner walks to another tab, and a second rule keyed on the mount dragged
   every other view's header up into the traffic lights' row with it. */
.content.session-active {
  padding-top: 0;
}
/* `flex: 1` rather than `height: 100%`: the notice banner is now a sibling
   above these, and a child pinned to the full height of a flex column that
   also holds a banner overflows the window by exactly the banner. */
.view { display: none; flex-direction: column; flex: 1 1 auto; min-height: 0; background-color: var(--canvas); }
#view-session {
  /* A monitor beside the work, not half the window: at 44vw the pane out-sized
     the transcript it sits next to, and most of it was letterbox. */
  --computer-pane-width: clamp(320px, 36vw, 600px);
  flex-direction: row;
  background-color: var(--canvas);
  overflow: hidden;
}
#view-session.is-active { display: flex; }
.session-left {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
/* One reveal per view switch, with exactly one owner per property: the view
   owns opacity, the cards own movement (see `.is-entering`). Both used to
   animate opacity, and because the cards' `backwards` fill held them at 0 for
   the whole container fade, the two reveals ran end to end: the chrome faded
   up over an empty content area, then the cards faded in after it. Measured at
   ~940ms across two passes, which is the "loads, flickers, loads again". */
.view.is-active { display: flex; animation: view-in var(--dur-view) var(--ease-soft); }
@keyframes view-in { from { opacity: 0; } to { opacity: 1; } }

.head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;
  padding: 8px var(--gutter) 14px;
  border-bottom: 1px solid var(--hairline);
  margin-bottom: 10px;
}
.head-text { min-width: 0; }
.eyebrow {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 8px;
}
h1 {
  font: 700 clamp(20px, 1.8vw, 24px)/1.15 var(--font-display);
  letter-spacing: -0.02em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Two lines rather than one clipped one: these sentences say what the screen is
   for, and "…every agent knows what you s…" says nothing. */
.sub {
  color: var(--ink-muted);
  font-size: 13px;
  line-height: 1.5;
  margin-top: 8px;
  max-width: 64ch;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.muted { color: var(--ink-body); }


.controls { display: flex; gap: 8px; flex-shrink: 0; padding-top: 4px; }
.computer-toggle {
  width: 34px;
  height: 32px;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  background: transparent;
  box-shadow: none;
  color: var(--ink-muted);
}
.computer-toggle .ic { width: 16px; height: 16px; font-size: 16px; }
.computer-toggle:hover {
  background: var(--bg-hover);
  color: var(--ink);
  box-shadow: none;
}
.computer-toggle.is-active {
  border-color: color-mix(in srgb, var(--violet) 78%, transparent);
  background: var(--tint-violet);
  color: var(--ink-violet);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--violet) 20%, transparent);
}
/* Once the drawer owns the right edge, its matching control lives in the
   drawer header. Avoid showing two copies of the same affordance. */
#agent-controls .computer-toggle.is-active { display: none; }

/* ================================================================= buttons */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border-radius: var(--radius-btn);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  transition: transform var(--dur-hover) var(--ease-soft),
              filter var(--dur-hover) var(--ease-soft),
              box-shadow var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.btn .ic { font-size: 15px; }
.btn:hover { transform: scale(1.012); filter: brightness(1.02); box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-hover); }
.btn:active {
  transform: scale(0.985); filter: brightness(0.985);
  box-shadow: var(--inset-press-top), var(--inset-press-floor), var(--shadow-press);
  transition-duration: var(--dur-press);
}
.btn[disabled] { opacity: 0.42; }
.btn[disabled]:hover, .btn[disabled]:active { transform: none; filter: none; box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest); }

.btn.ghost { background: transparent; box-shadow: none; color: var(--ink-body); }
.btn.ghost:hover { background: var(--bg-hover); color: var(--ink); box-shadow: none; }
.btn.ghost:active { box-shadow: none; }

.btn.danger { color: var(--ink-danger); }

/* Exactly two button sizes. Default is 6px 12px at 13px; dense row actions
   (memory cards, the scoreboard's one action) share this one small variant.
   No thirds. */
.card-memory .card-actions .btn,
.scoreboard-action.btn {
  padding: 3px 10px;
  font-size: 12px;
}

/* One gradient call-to-action per surface. A view header and a card row
   would otherwise put competing violet gradients on screen at once, which
   cancels them out. The accented action here is a tinted pill: unmistakably
   the primary of its row, without pretending to be the primary of the app. */
/* `primary` is the leading action of a repeated row -- Start on one of twelve
   agent cards. It stays a tint on purpose: twelve glowing buttons is not
   emphasis, it is noise. */
.btn.primary {
  background: var(--tint-violet);
  color: var(--ink-violet);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest);
}
.btn.primary:hover { box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-hover); }

/* `cta` is THE action of a surface: a dialog's confirm, the one thing an
   empty screen is asking for. One definition, so those can never drift
   apart again -- which is exactly what had happened. */
.btn.cta,
.dialog .btn.primary {
  background: var(--grad-violet);
  color: #fff;
  box-shadow: var(--inset-cta-top), var(--inset-cta-floor), var(--glow-violet);
}
.btn.cta:hover,
.dialog .btn.primary:hover { box-shadow: var(--inset-cta-top), var(--inset-cta-floor), var(--glow-violet-hi); }
.btn.cta:active,
.dialog .btn.primary:active { box-shadow: var(--inset-press-cta-top), var(--inset-press-cta-floor), var(--shadow-press); }
.btn.cta[disabled] { opacity: 0.42; box-shadow: var(--inset-cta-top); }
/* A working button draws a spinner where its words were (`.btn.is-loading`,
   further down), and the spinner has to be in the ink of the button it is
   on. That rule paints every primary's spinner white, for the gradient a
   primary wears in a dialog, and leaves `cta` grey: the tinted Resume went
   white on near-white and the violet Go grey on violet, both blank. */
.btn.cta.is-loading::after { color: rgba(255, 255, 255, 0.85); }
.btn.primary:not(.cta).is-loading::after { color: var(--ink-violet); }
.dialog .btn.primary.is-loading::after,
.setup .btn.primary.is-loading::after { color: rgba(255, 255, 255, 0.85); }

.btn.cta { padding: 10px 16px; font-size: 13.5px; }
.btn.cta .ic { font-size: 16px; }
.btn .key {
  padding: 1px 5px;
  border-radius: var(--radius-chip);
  background: rgba(255, 255, 255, 0.16);
  font: 600 11px/1.5 var(--font-ui);
}
.btn.danger-solid {
  background: var(--grad-red);
  color: #fff;
  box-shadow: var(--inset-cta-top), var(--glow-red);
}

/* One action with a second way to take it: the way taken last on the face, the
   rest under the caret. Two full buttons side by side read as two separate
   asks, which is what this replaced -- Connect and Bring it from my browser
   are one decision with two answers. */
.split {
  display: inline-flex;
  align-items: stretch;
  /* It sits in flex rows (a need's actions, a service row) where the button it
     replaced was flex: none. Shrinking it instead wraps the label. */
  flex: none;
  border-radius: var(--radius-btn);
  transition: transform var(--dur-hover) var(--ease-soft),
              filter var(--dur-hover) var(--ease-soft);
}
/* The lift belongs to the pair. Left on each half, the seam opened on the way
   in and closed on the way out, and two halves drifting apart is the one thing
   a split button must never look like. */
.split:hover { transform: scale(1.012); filter: brightness(1.02); }
.split:active { transform: scale(0.985); transition-duration: var(--dur-press); }
/* Keyed on the two marker classes rather than on .btn: the same control is
   also worn as the service list's small chip, and both halves have to lose
   .btn's own lift either way. */
.split > .split-face:hover, .split > .split-face:active,
.split > .split-more:hover, .split > .split-more:active { transform: none; filter: none; }
/* The pair lifts, and the half under the cursor darkens on top of that: with a
   split button, "am I about to run it or open the menu" is the question the
   control has to answer before the click, not after it. The overlay is
   layered rather than a swapped colour so it lands on the tint in both
   themes. */
.split > .split-face:hover, .split > .split-more:hover {
  background-image: linear-gradient(var(--bg-hover), var(--bg-hover));
}
/* Nothing moves while the action is running: the spinner is the feedback. */
.split:has(.is-loading), .split:has(.is-loading):hover { transform: none; filter: none; }
.split > .split-face { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.split > .split-more {
  border-top-left-radius: 0; border-bottom-left-radius: 0;
  padding-left: 7px; padding-right: 9px;
  /* The seam is a 1px gap rather than a rule: both halves carry the tint's own
     inset highlight, and a line drawn across them sat proud of both. */
  margin-left: 1px;
}
.split > .split-more .ic { font-size: 13px; opacity: 0.7; }
.split > .split-more:hover .ic, .split > .split-more:focus-visible .ic { opacity: 1; }
/* The chip variant: the service list's own control, which is smaller than a
   .btn and carries no shadow, so the caret half is tightened to match rather
   than left looking like a button that wandered in. */
.split > .service-connect.split-more { padding-left: 5px; padding-right: 6px; }
.split > .service-connect.split-more .ic { font-size: 12px; }

/* -- what it is doing now: a quiet line, not a hero sentence -------------- */
.agent-hero {
  padding: 4px var(--gutter) 10px;
  margin-bottom: 0;
}

/* ------------------------------------------------------- agent computer --
   A real session sidebar, not a card inserted into the transcript. The same
   surface watches the live browser and plays the retained step frames after
   Chrome closes. */
.session-workspace {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.session-main {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.computer {
  flex: 0 0 0;
  width: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
  border-left: 1px solid var(--hairline);
  overflow: hidden;
  background: var(--canvas);
  opacity: 0;
  transform: translateX(18px);
  transition: flex-basis 260ms var(--ease-soft), width 260ms var(--ease-soft),
              opacity 180ms var(--ease-soft), transform 260ms var(--ease-soft);
}
.computer[hidden] { display: none; }
#view-session.has-computer .computer {
  flex-basis: var(--computer-pane-width);
  width: var(--computer-pane-width);
  opacity: 1;
  transform: translateX(0);
}
.computer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex: none;
  min-height: 48px;
  padding: 0 var(--space-4) 0 0;
  border-bottom: 1px solid var(--hairline);
}
.computer-head .computer-toggle { flex: none; }
.computer-title, .computer-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
/* A title, not a tab: there is one computer and nothing to switch to, so the
   strip that drew it as the selected tab of a tab bar promised a second tab
   that never existed. */
.computer-title {
  flex: 1 1 auto;
  min-width: 0;
  /* One line tall and wrapping: whatever does not fit a narrow pane drops to
     a second line that is clipped, so it goes whole instead of as "You ha…".
     The mode word is last in the row and so first to go; the dot, the ring
     and the button already say it. 24px clears the dot's 21px halo. */
  flex-wrap: wrap;
  height: 24px;
  overflow: hidden;
  padding: 0 0 0 var(--space-4);
  white-space: nowrap;
}
.computer-title > * { flex: none; }
.computer-actions { flex: none; }
.computer-name {
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
}
.computer-live {
  position: relative;
  width: 7px;
  height: 7px;
  flex: none;
}
/* The dot is two pseudo-elements: its ring (`::before`), then the green core
   (`::after`) painted over it. The ring used to be a 3px spread box-shadow,
   and the pulse was that shadow growing to 7px and fading. A spread shadow
   with no blur IS a filled disc drawn behind the element, so a disc is the
   same picture -- but box-shadow is a paint property, and animating it
   repainted the pane on the main thread every frame for the length of every
   live run (138 paints per 181 frames, measured). Transform and opacity are
   the compositor's, and cost the main thread nothing. 3px around a 7px dot
   is a 13px disc; 7px around it is 21px, hence scale(1.6154). The disc is
   the ring in every state, not only while it pulses, so the resting frame,
   the frame the owner sees while they hold control, and the old shadow are
   all the same bytes. Two pseudo-elements rather than a negative z-index,
   which would have put the ring under the pane's background or needed a
   stacking context on the title. */
.computer-live::before,
.computer-live::after {
  content: "";
  position: absolute;
  border-radius: 50%;
}
.computer-live::before {
  inset: -3px;
  background: var(--tint-green);
}
.computer-live::after {
  inset: 0;
  background: var(--success);
}
.computer[data-mode="live"]:not([data-control="true"]) .computer-live::before {
  animation: computer-live-pulse 1.8s ease-out infinite;
}
@keyframes computer-live-pulse {
  0%, 45% { opacity: 1; transform: scale(1); }
  75%, 100% { opacity: 0; transform: scale(1.6154); }
}
.computer[data-mode="replay"] .computer-live::before { display: none; }
.computer[data-mode="replay"] .computer-live::after { background: var(--ink-faint); }
.computer-mode {
  color: var(--ink-muted);
  font-size: 12px;
  font-weight: 500;
}
.computer[data-control="true"] .computer-mode { color: var(--ink-violet); }
/* Same words and glyphs as the rail's Local | Cloud switch, at rest. */
.computer-where {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: 24px;
  padding: 0 8px;
  border-radius: var(--radius-chip);
  background: var(--surface-sunken);
  color: var(--ink-body);
  font-size: 12px;
  font-weight: 500;
}
.computer-where .ic { font-size: 13px; color: var(--ink-muted); }
.computer-where[data-where="cloud"] .ic { color: var(--violet); }
/* The frame sits in the pane like any other card, at its own aspect ratio.
   It used to be stretched over a black stage that filled the pane, under a
   drawn-on browser bar: a 16:10 page in a tall pane was mostly letterbox. */
.computer-screen {
  position: relative;
  display: flex;
  flex: 0 1 auto;
  min-height: 0;
  /* One left edge down the pane: title, frame, controls, caption. */
  padding: var(--space-4);
  outline: none;
}
.computer-screen img {
  display: block;
  width: 100%;
  height: auto;
  /* Never taller than the pane can show: a short, wide window letterboxes
     inside the image box, which `computerPoint` already accounts for. */
  max-height: calc(100vh - 160px);
  object-fit: contain;
  border-radius: var(--radius-card);
  background: var(--surface-sunken);
  box-shadow: 0 0 0 1px var(--hairline), var(--shadow-rest);
  user-select: none;
  -webkit-user-drag: none;
}
/* No frame yet: the empty state holds the image's place at a page's shape. */
.computer-screen img:not([src]) { display: none; }
.computer-screen:focus-visible img,
.computer[data-control="true"] .computer-screen img {
  box-shadow: 0 0 0 2px var(--violet);
}
.computer[data-control="true"] .computer-screen img { cursor: crosshair; }
.computer-empty {
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 16 / 10;
  padding: var(--space-6);
  border-radius: var(--radius-card);
  background: var(--surface-sunken);
  color: var(--ink-muted);
  font-size: 12px;
  text-align: center;
  pointer-events: none;
}
.computer-empty[hidden] { display: none; }
.computer-type {
  display: flex;
  gap: var(--space-2);
  padding: 0 var(--space-4) var(--space-3);
}
.computer-type[hidden] { display: none; }
.computer-type input {
  min-width: 0;
  flex: 1;
  padding: 6px 10px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-btn);
  background: var(--surface-sunken);
  color: var(--ink);
  font-size: 13px;
  box-shadow: var(--inset-field);
}
.computer-replay {
  display: grid;
  grid-template-columns: auto minmax(80px, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-4) var(--space-3);
}
.computer-replay[hidden] { display: none; }
.computer-scrub { width: 100%; accent-color: var(--violet); }
.computer-frame-label {
  color: var(--ink-muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* A caption under the frame, not a footer ruled off at the bottom of the pane. */
.computer-hint {
  flex: none;
  margin: 0;
  padding: 0 var(--space-4) var(--space-3);
  color: var(--ink-muted);
  font-size: 12px;
  line-height: 1.5;
}

@media (max-width: 760px) {
  #view-session { --computer-pane-width: min(72vw, 420px); }
  .computer-replay { grid-template-columns: auto minmax(60px, 1fr); }
  .computer-frame-label { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) {
  #view-session, .computer { transition: none; }
  /* No pulse, but still a ring: the disc holds at its resting size. */
  .computer[data-mode="live"]:not([data-control="true"]) .computer-live::before { animation: none; }
}
.hero-status {
  font: 500 13.5px/1.4 var(--font-ui);
  letter-spacing: -0.005em;
  color: var(--ink);
}
/* One quieter line of numbers under the sentence: step, elapsed, spend.
   Tabular figures so the ticking clock does not make the line tremble. */
.hero-meta {
  margin-top: 2px;
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}
.hero-thought {
  margin-top: 6px;
  max-width: 68ch;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-muted);
  user-select: text;
}

/* -- the technical log hides until asked for ------------------------------- */
.stream:not(.show-tech) .row.is-acts { display: none; }
/* Stream foot, Cursor-style: a quiet status line, not a composer header. */
.tech-toggle {
  flex: none;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0 var(--gutter) 4px;
  padding: 5px 0 3px;
  min-height: 22px;
  font-size: 10.5px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.01em;
  color: var(--ink-faint);
  transition: color var(--dur-hover) var(--ease-soft);
}
.tech-toggle:hover { color: var(--ink-muted); }
.tech-toggle[aria-pressed="true"] { color: var(--ink-muted); }

/* -- steering: floating Cursor-grade chat bar ------------------------------ */
/* Soft elevated shell + one action row inside. Not a sunken card, not a
   violet focus bloom, not orphan pills floating in empty slab space. */
.steer {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 var(--gutter) 16px;
  padding: 10px 12px 8px;
  border-radius: 16px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-rest);
  transition: border-color var(--dur-hover) var(--ease-soft),
              box-shadow var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.steer:focus-within {
  background: var(--surface-2);
  border-color: color-mix(in srgb, var(--ink) 18%, var(--hairline));
  box-shadow: var(--shadow-hover);
}
.steer-input {
  display: block;
  width: 100%;
  min-height: 40px;
  max-height: 140px;
  padding: 2px 36px 2px 2px;
  border: 0;
  background: none;
  box-shadow: none;
  outline: none;
  resize: none;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--ink);
  user-select: text;
}
.steer-input::placeholder { color: var(--ink-muted); }
.steer-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 30px;
}
.steer-bar-end {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  padding-right: 0;
}
.steer-remember {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 28px;
  height: 28px;
  padding: 0 10px;
  border-radius: var(--radius-chip);
  border: 1px solid transparent;
  font-size: 12px;
  font-weight: 500;
  line-height: 14px;
  color: var(--ink-faint);
  transition: color var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft),
              border-color var(--dur-hover) var(--ease-soft);
}
/* Fixed 14×14 box + matching line-height — true flex center, no optical nudge. */
.steer-remember .ic {
  width: 14px;
  height: 14px;
  font-size: 14px;
  opacity: 0.65;
  display: block;
  flex: none;
  line-height: 0;
}
.steer-remember-label {
  display: block;
  line-height: 14px;
}
.steer-remember:hover { color: var(--ink-body); background: var(--bg-hover); }
/* On: quiet selected chrome so it reads as a switch, not a static label. */
.steer-remember[aria-pressed="true"],
.steer-remember[aria-checked="true"] {
  color: var(--ink);
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  border-color: color-mix(in srgb, var(--ink) 22%, transparent);
}
.steer-remember[aria-pressed="true"] .ic,
.steer-remember[aria-checked="true"] .ic { opacity: 1; }
/* Circular send — muted when empty, solid ink when there is text. */
.steer-send {
  flex: none;
  width: 28px;
  height: 28px;
  padding: 0;
  display: grid;
  place-items: center;
  border-radius: 9999px;
  background: var(--bg-active);
  color: var(--ink-faint);
  transition: background var(--dur-hover) var(--ease-soft),
              color var(--dur-hover) var(--ease-soft),
              opacity var(--dur-hover) var(--ease-soft);
}
.steer-send .ic { width: 14px; height: 14px; font-size: 14px; }
.steer-send:not(:disabled) {
  background: var(--ink);
  color: var(--canvas);
}
.steer-send:not(:disabled):hover {
  filter: brightness(1.06);
}
.steer-send:disabled {
  opacity: 0.55;
  cursor: default;
}
.steer-send.is-loading { opacity: 0.7; }
/* Pin the dictation mic into the action row (right of Remember, left of Send)
   instead of floating mid-slab over the empty typing area. */
.steer.dictation-host > .dictation-trigger {
  top: auto !important;
  left: auto !important;
  right: 48px !important;
  bottom: 9px !important;
  width: 28px !important;
  height: 28px !important;
  color: var(--ink-muted);
}
.steer.dictation-host > .dictation-trigger:hover { color: var(--ink); }
.steer-input.has-dictation { padding-right: 36px; }
.check-row {
  display: flex; align-items: center; gap: 8px;
  margin-top: 10px;
  font-size: 13px;
  color: var(--ink-body);
  cursor: pointer;
  user-select: none;
}
.check-row input { accent-color: var(--violet); width: 15px; height: 15px; }

/* ============================================================ event stream */
/* Dense transcript, not sparse hero rows. Small marks, quiet titles, body
   text that reads as prose. Site events keep brand marks scaled to the row.
   Mechanical actions stay folded until asked for. */
.stream-wrap {
  position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column;
  border-top: 1px solid var(--hairline);
}
.stream { flex: 1; overflow-y: auto; padding: 6px var(--gutter) 8px; }

.row {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) auto;
  column-gap: 10px;
  align-items: center;
  padding: 6px 0;
  content-visibility: auto;
  contain-intrinsic-size: auto 32px;
}
/* Only rows that actually arrive live animate. Replaying four hundred of them
   on a view switch was a shimmer, not feedback. */
.row.is-new { animation: row-in 180ms var(--ease-soft); }
@keyframes row-in { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }

.row-mark {
  display: grid;
  place-items: center;
  width: 14px;
  height: 14px;
  margin-top: 0;
  color: var(--ink-faint);
}
.row-mark .ic { width: 12px; height: 12px; font-size: 12px; display: block; }
.row-mark.is-brand img {
  width: 12px; height: 12px;
  object-fit: contain;
  display: block;
}

.row-time {
  font-size: 10.5px;
  line-height: 1.35;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  text-align: right;
  white-space: nowrap;
  padding-top: 0;
}

.row-main {
  min-width: 0;
  user-select: text;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 6px;
}
.row-title {
  flex: 0 0 auto;
  font: 500 12px/1.4 var(--font-ui);
  letter-spacing: -0.005em;
  color: var(--ink-muted);
  text-transform: none;
}
.row-body {
  flex: 1 1 12ch;
  min-width: 0;
  margin-top: 0;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--ink-body);
  overflow-wrap: anywhere;
}
.row.is-say .row-main { display: block; }
.row.is-say { align-items: start; }
.row.is-say .row-mark { margin-top: 3px; }
.row.is-say .row-time { padding-top: 2px; }
.row.is-say .row-body {
  color: var(--ink);
  max-width: 72ch;
  white-space: pre-wrap;
  font-size: 13px;
  line-height: 1.5;
}
/* Owner guidance only: a right-aligned bubble. Everything else stays flat. */
.row.is-user {
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 10px;
  padding: 8px 0;
  align-items: end;
}
.row.is-user .row-mark { display: none; }
.row.is-user .row-main {
  display: flex;
  justify-content: flex-end;
}
.row-bubble {
  max-width: min(68ch, 92%);
  padding: 7px 11px;
  border-radius: 12px;
  background: var(--bg-active);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.row.is-user .row-time { align-self: end; padding-top: 0; padding-bottom: 4px; }
/* Memories a run recalled or wrote down, one to a line under the count. A
   row taller than its title hangs from the title's line: centring the mark
   on the whole block floated it between two titles. */
.row.has-list, .row.has-brief { align-items: start; }
.row.has-list .row-mark, .row.has-brief .row-mark { margin-top: 2px; }
.row.has-list .row-time, .row.has-brief .row-time { padding-top: 2px; }
.row-list {
  flex-basis: 100%;
  min-width: 0;
  margin: 3px 0 1px;
  padding-left: 16px;
  list-style: disc;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-body);
  overflow-wrap: anywhere;
}
.row-list li + li { margin-top: 2px; }
.row-list li::marker { color: var(--ink-faint); }
/* The whole brief a worker was handed. Technical, so it shows only with the
   rest of the Technical log, and set as the machine text it is. */
.row-brief {
  flex-basis: 100%;
  min-width: 0;
  max-height: 320px;
  overflow-y: auto;
  margin-top: 6px;
  padding: 8px 10px;
  border-radius: var(--radius-chip);
  background: var(--surface-sunken);
  font: 12px/1.55 var(--font-mono);
  color: var(--ink-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.stream:not(.show-tech) .row-brief { display: none; }
.row.is-bad .row-title { color: var(--ink-danger); }
.row.is-note[data-verb="done"] .row-title,
.row.is-note[data-verb="completed"] .row-title { color: var(--ink-success); }
.row.is-note[data-verb="failed"] .row-title { color: var(--ink-danger); }

/* -- mechanical action: dense, recedes into texture ----------------------- */
.verb {
  flex: 0 0 auto;
  max-width: 136px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: 1.8;
}
.params {
  min-width: 0;
  font: 13px/1.6 var(--font-mono);
  color: var(--ink-body);
  overflow-wrap: anywhere;
}
.params .k { color: var(--ink-muted); font-weight: 400; }
.params .k::after { content: "="; }
.params .v { color: var(--ink); }
.params .pair + .pair { margin-left: 10px; }

/* -- runs of mechanical actions, folded into one quiet line ---------------- */
.row.is-acts .row-main { min-width: 0; }
.acts-toggle {
  display: flex; align-items: baseline; gap: 8px;
  width: 100%;
  max-width: 460px;
  padding: 0;
  text-align: left;
  color: var(--ink-muted);
  border-radius: var(--radius-chip);
  transition: color var(--dur-hover) var(--ease-soft);
}
.acts-toggle:hover { color: var(--ink-body); }
.acts-toggle .row-title { color: inherit; }
.acts-count {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}
.acts-latest {
  min-width: 0;
  margin-top: 0;
  font: 12px/1.4 var(--font-mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acts-toggle .ic { font-size: 11px; align-self: center; flex: 0 0 auto; transition: transform 300ms var(--ease-soft); }
.row.is-acts.is-open .acts-toggle .ic { transform: rotate(180deg); }
.acts-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 320ms var(--ease-soft);
}
.row.is-acts.is-open .acts-body { grid-template-rows: 1fr; }
.acts-inner { overflow: hidden; min-height: 0; }
.act-line {
  display: flex; align-items: baseline; gap: 10px;
  padding: 1px 0;
}
.act-line:first-child { padding-top: 6px; }
.act-line:last-child { padding-bottom: 4px; }

/* -- jump to latest ------------------------------------------------------- */
.jump {
  position: absolute;
  bottom: 16px; left: 50%;
  transform: translateX(-50%);
  z-index: 4;
  display: flex; align-items: center; gap: 8px;
  padding: 10px 16px;
  border-radius: var(--radius-pill);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-card);
  font-size: 14px; font-weight: 600;
  color: var(--ink);
  animation: jump-in var(--dur-hover) var(--ease-soft);
  transition: transform var(--dur-hover) var(--ease-soft), box-shadow var(--dur-hover) var(--ease-soft);
}
.jump:hover { transform: translateX(-50%) scale(1.012); box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-card-hover); }
.jump:active { transform: translateX(-50%) scale(0.985); }
@keyframes jump-in { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translate(-50%, 0); } }
.jump .jump-count {
  min-width: 20px; padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--tint-violet);
  color: var(--ink-violet);
  font-size: 12px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ============================================================ empty states */
.empty {
  padding: 64px 0 48px;
  text-align: center;
  color: var(--ink-body);
}
/* A stub of the same rail the log is built on, so an empty screen still looks
   like part of this app rather than a hole in it. */

.empty p:first-of-type {
  font: 600 16px/1.3 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--ink);
}
.empty .muted {
  font-size: 15px;
  line-height: 1.55;
  max-width: 46ch;
  margin: 8px auto 0;
  color: var(--ink-body);
}
.empty .btn { margin-top: 20px; }

/* The event log is where errors, URLs and safety codes appear. Not being able
   to copy them out was the single most annoying thing about the old build. */
.row-main, .row-body, .params,
.card-title, .sub, .warn, .memory-body, .answer,
.src-url, .learned-summary, .learned-value, .board-text, .item-label { user-select: text; }

.answer {
  position: relative;
  margin: 0 var(--gutter) 16px;
  padding: 14px 16px;
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-card);
  max-height: 34vh;
  overflow-y: auto;
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  overflow-wrap: anywhere;
  animation: rise var(--dur-reveal) var(--ease-soft);
}
/* Static label, so redrawing the report inside cannot wipe it. A worker's
   answer is its report to the lead agent and is called that (`paintAnswer`). */
.answer::before {
  content: "Answer";
  display: block;
  color: var(--ink-violet);
  margin-bottom: 8px;
}
.answer[data-label]::before { content: attr(data-label); }

/* The report, drawn from the agent's markdown (`reportNodes`): the page's
   prose rhythm, a table that reads as one, and links in the ink the People
   table's links use. Nothing here shows a pipe, an asterisk or a bracket. */
.answer p { margin: 0 0 10px; }
.answer > :last-child { margin-bottom: 0; }
.answer strong { font-weight: 600; color: var(--ink); }
.answer .report-h { margin: 14px 0 6px; font-weight: 600; }
.answer .report-h:first-child { margin-top: 0; }
.answer .report-list { margin: 0 0 10px; padding-left: 22px; }
.answer .report-list .report-list { margin: 4px 0 0; }
.answer .report-list li { margin: 3px 0; }
.answer code {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--surface-sunken);
  font: 0.86em/1.5 var(--font-mono);
}
.answer .report-code {
  margin: 0 0 10px;
  padding: 10px 12px;
  border-radius: var(--radius-chip);
  background: var(--surface-sunken);
  white-space: pre-wrap;
}
.answer .report-code code { padding: 0; background: none; }
.answer .report-quote {
  margin: 0 0 10px;
  padding-left: 12px;
  border-left: 2px solid var(--hairline);
  color: var(--ink-body);
}
.answer .report-rule { margin: 12px 0; border: 0; border-top: 1px solid var(--hairline); }
.report-table-wrap { margin: 0 0 12px; overflow-x: auto; }
.report-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
  line-height: 1.45;
  overflow-wrap: normal;
}
.report-table th,
.report-table td {
  padding: 7px 12px 7px 0;
  border-bottom: 1px solid var(--hairline);
  text-align: left;
  vertical-align: top;
}
.report-table th {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
}
.report-table tr:last-child td { border-bottom: 0; }
.report-link { color: var(--ink-progress); text-decoration: none; overflow-wrap: anywhere; }
.report-link:hover { text-decoration: underline; }
/* The entrance .answer declares; it referenced a keyframe that never existed,
   so the panel popped in with no motion at all. */
@keyframes rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* ================================================================= filters */
.filters { display: flex; gap: 12px; align-items: center; padding: 0 var(--gutter) 16px; }
.search-wrap {
  position: relative;
  flex: 1;
  min-width: 180px;
  /* The kbd chip is painted inside the field. Query this box, not the window:
     Memory shares its row with chips, so the wrap can go tight while the
     window is still wide. */
  container-type: inline-size;
  --search-end-pad: 12px;
}
.search-wrap:has(> .kbd) {
  --search-end-pad: 38px;
  /* Win space from the trailing spacer so the hint and the placeholder
     can both sit in the field at a normal window width. */
  flex: 2 1 280px;
}
.search-wrap .ic {
  position: absolute; left: 12px; top: 50%;
  transform: translateY(-50%);
  font-size: 16px;
  color: var(--ink-muted);
}
/* Fields sink; buttons rise. Depth alone tells you raised = act, sunken = type. */
.search {
  width: 100%;
  appearance: none;
  padding: 8px 12px 8px 36px;
  border-radius: var(--radius-btn);
  border: 1px solid var(--hairline);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  outline: none;
  font-size: 14px;
  color: var(--ink);
  user-select: text;
  transition: border-color var(--dur-hover) var(--ease-soft), background var(--dur-hover) var(--ease-soft);
}
.search-wrap .search { padding-right: var(--search-end-pad); }
.search::placeholder { color: var(--ink-muted); }
.search:focus { border-color: var(--violet); background: var(--surface); }
/* WebKit's own clear control lands on the trailing edge, on top of a kbd chip. */
.search::-webkit-search-decoration,
.search::-webkit-search-cancel-button { -webkit-appearance: none; }

/* A second filter row hangs directly off the first: same gutter, no gap above,
   so the two axes read as one bar rather than two unrelated toolbars. */
.filters-sub { padding-top: 0; align-items: center; flex-wrap: wrap; row-gap: 8px; }
.filters[hidden] { display: none; }
.filters-spacer { flex: 1; min-width: 8px; }
/* Kind chips are open-ended, one per kind the agents have actually learned, so
   this is the row that runs out of width first. It scrolls sideways rather
   than wrapping, and it has to out-shrink the sort group beside it: .chips
   sets flex-shrink: 0, which otherwise pushed sort clean off the edge. */
/* A box that scrolls on one axis clips on both, so it cut off everything a
   chip draws outside itself: the focus ring 4px out, the active chip's shadow.
   The scroller keeps 4px of room for them and gives it back with a negative
   margin, so no chip moves; scroll-padding keeps that room when focus scrolls
   a chip in from the edge. */
.chips.scroll-x {
  overflow-x: auto;
  scrollbar-width: none;
  flex-shrink: 1;
  min-width: 0;
  padding: 4px;
  margin: -4px;
  scroll-padding-inline: 4px;
}
.chips.scroll-x::-webkit-scrollbar { display: none; }

.chips { display: flex; gap: 4px; flex-shrink: 0; }
.chip {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 12px;
  border-radius: var(--radius-pill);
  color: var(--ink-body);
  font-weight: 500;
  font-size: 14px;
  white-space: nowrap;
  transition: background var(--dur-hover) var(--ease-soft), color var(--dur-hover) var(--ease-soft);
}
/* The second row carries more chips in the same width, so it runs a step down.
   Two chip sizes, like the two button sizes: no thirds. */
.chips.dense .chip { padding: 5px 10px; font-size: 13px; }
.chips.dense .chip-count { font-size: 10px; }
.chip:hover { background: var(--bg-hover); color: var(--ink); }
.chip.is-active {
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest);
}

.warn {
  margin: 0 var(--gutter) 14px;
  padding: 12px 16px;
  border-radius: var(--radius-chip);
  background: var(--tint-red);
  color: var(--ink);
  font-size: 14px;
}

/* =================================================================== cards */
.list { flex: 1; overflow-y: auto; padding: 4px var(--gutter) 28px; }

.card {
  padding: 14px 16px;
  margin-bottom: 10px;
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-card);
  transition: max-height 220ms var(--ease-soft), opacity 160ms linear,
              margin 220ms var(--ease-soft), padding 220ms var(--ease-soft),
              box-shadow var(--dur-hover) var(--ease-soft);
}
.card:hover { box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-card-hover); }
/* Recessed readout: a well sits into the canvas the way Cursor's usage cards
   do. Shared token, no drop-shadow chrome. Overview cards use this instead of
   the raised stack above; other lists keep their lift. */
.well,
#home-list .card {
  box-shadow: var(--inset-well);
}
.well:hover,
#home-list .card:hover {
  box-shadow: var(--inset-well);
}
/* Entrance is a moment, not a state: applied only on a real view switch, one
   orchestrated stagger down the list, then never again until the next switch.
   Movement only — the view above owns the fade. Two nested opacity animations
   multiplied into a double fade and queued into two visible passes. A card
   past the stagger cap now simply sits in place rather than being the one
   fully-opaque row among a list still fading up. */
.is-entering {
  animation: card-in var(--dur-enter) var(--ease-soft) backwards;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
@keyframes card-in { from { transform: translateY(10px); } to { transform: none; } }
.card.is-leaving {
  max-height: 0 !important;
  opacity: 0;
  margin-bottom: 0;
  padding-top: 0; padding-bottom: 0;
  overflow: hidden;
  pointer-events: none;
}

.card-top { display: flex; align-items: flex-start; gap: 16px; }
.card-heading { flex: 1; min-width: 0; }
.card-title { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); overflow-wrap: anywhere; }
.card-sub {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  color: var(--ink-muted); font-size: 13px; margin-top: 6px;
}
.card-meta {
  display: flex; align-items: center; gap: 14px;
  margin-top: 16px;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
/* The meta line is one joined run ("site · scope · used 3× · from hn"), which
   has no break opportunity a flex row will take on its own. */
.card-meta > span { min-width: 0; overflow-wrap: anywhere; }
/* A line, not a slab. The chip and the rail already fly the state's colour;
   a filled red panel under them was the third telling. The glyph carries the
   tone so the sentence can stay legible ink. */
.card-error {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 14px;
  color: var(--ink-danger);
  font-size: 13.5px; line-height: 1.5;
}
.card-error .ic { font-size: 15px; margin-top: 2px; }
.card-error-text { min-width: 0; overflow-wrap: anywhere; }

/* ------------------------------------------------------------- agent roster
   The fleet view: the same card the goals list speaks, opened by the whole
   row. The chip leads because state is what this page is for; the Open
   button is the affordance that says rows go somewhere. */
.roster-card { cursor: pointer; }
.roster-card .card-top { align-items: center; }
.roster-card .chip-state { flex-shrink: 0; }
/* The name is a short title, so one line is the whole of it. The task
   underneath is the scent trail; the session view owns the full sentence. */
.roster-card .card-title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Two lines, then an ellipsis. A long task's title is its opening words, so a
   one-line task under it ended a word or two past where the title had, and
   the card said the same opening twice and none of the rest. Clamped in
   normal flow, the way .sub is, so WebKit sizes it honestly. */
.roster-task {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  margin-top: 4px;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.list-label.section-label-row { width: 100%; align-items: center; }
.roster-card .card-meta { margin-top: 10px; }
/* "browser open" carries its own quiet pulse: a live Chrome is the one fact
   here that is not bookkeeping. Same dot the state chips use. */
.roster-browser { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-success); }
.roster-browser::before {
  content: "";
  width: 6px; height: 6px; border-radius: var(--radius-pill);
  background: var(--online);
}
/* Group labels: the eyebrow's voice, lowered to a whisper over the list. */
.list-label {
  margin: 20px 2px 8px;
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-faint);
}
.list-label:first-child { margin-top: 4px; }

/* The way back from a session: sits where the eyebrow label sat, speaks like
   a link, works like a button. */
.session-back {
  display: inline-flex; align-items: center; gap: 4px;
  margin: 0; padding: 2px 6px 2px 2px;
  border: 0; border-radius: var(--radius-chip);
  background: none;
  font: 600 12px/1 var(--font-ui);
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  cursor: pointer;
  transition: color var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.session-back:hover { color: var(--ink); background: var(--bg-hover); }
.session-back .ic { font-size: 12px; }

/* Session status sits where the gold pill sat: a rail word and a small dot. */
.agent-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.agent-status[hidden] { display: none; }
.agent-status .state-dot {
  width: 6px;
  height: 6px;
  flex-basis: 6px;
}
.agent-status .agent-word { font-size: 11px; }

.session-left > .head {
  padding: 8px var(--gutter) 10px;
  margin-bottom: 0;
}
/* Sessions use the titlebar area, but the breadcrumb still needs a deliberate
   optical inset from the window edge. This is compact without looking pinned
   to the rounded corner. */
.content.session-active .session-left > .head {
  padding-top: 18px;
}
#view-session .eyebrow { margin-bottom: 4px; }
#view-session .sub { margin-top: 4px; }

.eyebrow-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
/* Everything in the eyebrow that is not the state capsule: bookkeeping set as
   one muted run, so the capsule keeps its rank. */
.eyebrow-meta {
  min-width: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Revealed on keyboard focus too: opacity:0 keeps buttons in the tab order, so
   a keyboard user was landing on four invisible buttons including Delete. */
.card-actions { margin-left: auto; display: flex; gap: 6px; flex-shrink: 0; }
.card-meta .card-actions { opacity: 0; transition: opacity var(--dur-hover) var(--ease-soft); }
.card:hover .card-meta .card-actions,
.card:focus-within .card-meta .card-actions { opacity: 1; }

.pill {
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--ink-body);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.pill-review { background: var(--tint-amber); color: var(--ink-pending); }
/* What a run hands back. The only pill allowed to give up its width: on a
   narrow window it ellipses rather than pushing the row past the card. */
.dt-pill.pill-delivers { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* A heading inside a scrolling list, set like every other label in the app. */
.section-label {
  margin: 2px 0 12px;
}

/* -- feedback: the ask, and the thank-you --------------------------------- */
/* Two states in one dialog. The form is deliberately plain -- a feedback box
   that looks designed makes people write for the box. Everything held back
   there is spent on the receipt below, which is the only moment in this flow
   that can prove the thing the form promised. */
.feedback { display: flex; flex-direction: column; gap: var(--space-4); }
.feedback-head { display: flex; flex-direction: column; gap: 6px; }
.feedback-head h2 { margin: 0; padding: 0; border: 0; }
.feedback-head .dialog-lede { margin: 0; }

/* Optional, and shaped so that it reads as optional: no label above it, no
   asterisk, nothing selected by default.

   These carry a resting fill, unlike the chips on the goal shelf. There a
   chip sits in a labelled row of filters and is plainly a control; here three
   sentences float over a dialog with nothing around them, and without a shape
   they read as prose somebody forgot to finish. */
/* A grid, not a wrapping flex row: equal cells read as one set of options,
   labels of different widths wrapping unevenly read as a mistake. */
.feedback-kinds { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.feedback-kinds .chip {
  justify-content: center;
  padding: 7px 12px;
  font-size: 12.5px;
  background: var(--surface-sunken);
  color: var(--ink-muted);
}
.feedback-kinds .chip:hover { background: var(--bg-hover); color: var(--ink-body); }
.feedback-kinds .chip.is-active {
  background: var(--tint-violet);
  color: var(--ink-violet);
  font-weight: 600;
  box-shadow: none;
}

/* Starts at about three lines and grows with what is typed, up to a ceiling.
   The lede says one line is fine and the box then opened at 132px of empty
   canvas, which reads as a form to fill in rather than a note to dash off. The
   native resizer is gone with it: a drag handle in the corner of a dialog is a
   control nobody wants and the grip lines were the busiest pixels on screen. */
.feedback-text {
  min-height: 76px;
  max-height: 260px;
  resize: none;
  overflow-y: auto;
  line-height: 1.6;
}
/* Two more than the field default: a note box opens on whitespace, and at
   8px the first line sat tight against the top edge. The element in the
   selector outranks .field-input's own padding, which sits later in this
   sheet at the same specificity. */
textarea.feedback-text {
  padding-top: 10px;
  padding-bottom: 10px;
}

/* -- the receipt ---------------------------------------------------------- */
/* Centred, short, and given room. It is the whole reason the dialog does not
   just close on send. */
.thanks {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-4) var(--space-2);
  text-align: center;
}
.thanks-disc {
  display: grid; place-items: center;
  width: 52px; height: 52px;
  border-radius: var(--radius-pill);
  background: var(--grad-violet);
  color: #fff;
  box-shadow: var(--inset-cta-top), var(--glow-violet-hi);
}
.thanks-disc .ic { font-size: 24px; }
/* Beats `.dialog h2`, which draws the rule that separates a dialog's title
   from its form. There is no form under this one, and a hairline across a
   centred thank-you reads as a seam. */
.dialog .thanks-title {
  margin: 0; padding: 0; border: 0;
  font: 700 21px/1.2 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--ink);
  text-align: center;
}
.dialog .thanks-title + * { margin-top: 0; }
.thanks-body {
  max-width: 42ch;
  font-size: 13.5px; line-height: 1.6;
  color: var(--ink-body);
}
.thanks .btn {
  margin-top: var(--space-2);
  min-width: 120px;
  justify-content: center;
}

/* One orchestrated arrival rather than four things easing in separately: the
   disc lands, and the words come up behind it a beat later. */
.thanks-disc { animation: thanks-land 460ms var(--ease-out) both; }
.thanks-title { animation: thanks-rise 420ms var(--ease-out) 90ms both; }
.thanks-body { animation: thanks-rise 420ms var(--ease-out) 150ms both; }
.thanks .btn { animation: thanks-rise 420ms var(--ease-out) 210ms both; }
@keyframes thanks-land {
  from { opacity: 0; transform: scale(0.72); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes thanks-rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .thanks-disc, .thanks-title, .thanks-body, .thanks .btn { animation: none; }
}

/* -- start a run: the one control this page is for ------------------------- */
/* A strip, not a hero. It carried an eyebrow, a headline, a paragraph about
   what agents are, and an animated diagram of the crew: that is a landing
   page, and nobody reading this screen is deciding whether to buy the thing
   they are already using. What is left is the number, the button and what the
   run will cost. The one gradient on the page is the button itself. */
.promise {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 10px 0 var(--space-6);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest);
}
.promise-label { margin: 0; }

/* -- the button ----------------------------------------------------------- */
.promise-go { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3); }
.promise-btn { font-size: 14px; padding: 0 20px; height: 40px; }
.promise-btn .ic { font-size: 16px; }
.promise-go .stepper { height: 40px; }
/* The second half of the same promise, as a link. One primary action. */
.promise-alt {
  margin-left: auto;
  font-size: 13px; font-weight: 600;
  color: var(--ink-muted);
  transition: color var(--dur-hover) var(--ease-soft);
}
.promise-alt:hover { color: var(--ink); }

.promise-meta {
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
/* The offer to arm the follow-ups run, wherever a send is agreed to. In a
   dialog it is a second subject and needs room under it, or its explaining
   line and the run's own size line -- same 12px, same muted ink -- read as one
   paragraph. On the promise card there is no explaining line and the card is
   already a flex column with its own gap, so both margins come back off. */
.follow-ups-offer { margin-bottom: 16px; }
.promise .follow-ups-offer { margin-bottom: 0; }
.promise .check-row { margin-top: 0; }

/* The one condition worth interrupting for, said once here instead of as a
   pill on fifteen cards. */
.promise-warn {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2);
  padding: 10px 12px;
  border-radius: var(--radius-card);
  background: var(--tint-amber);
  font-size: 12.5px; line-height: 1.5;
  color: var(--ink-pending);
}
.promise-warn .ic { flex: none; font-size: 15px; }
.promise-warn span { flex: 1; min-width: 180px; }
.promise-warn-fix {
  flex: none;
  font-weight: 700;
  color: var(--ink-pending);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Once work is running the promise steps back: the running goals are what the
   page is for at that point, and a hero repeating an offer you have already
   taken is in the way. */
/* Once work is running the strip steps back: the running goals are what the
   page is about by then, and the profile warning has been read. */
.promise.is-compact { gap: var(--space-2); padding: var(--space-3) var(--space-4); }
.promise.is-compact .promise-warn { display: none; }

@media (max-width: 560px) {
  .promise { padding: var(--space-4); }
  .promise-alt { margin-left: 0; }
  .promise-btn { flex: 1; justify-content: center; }
}

/* -- the shelf: every play, sized by how much of the machine it books ------ */
/* Twenty-three plays is a catalogue, not a list, so this reads as one: a
   search field and two rows of counted facets over shelves that are ordered by
   commitment. The cards are not peers and are not drawn as peers -- a quick
   look-up gets a quarter of a row, a pipeline gets half of one.

   Nothing new in the palette. The texture comes from real brand marks and from
   the effort block; a second accent would fight the one the app already has. */
.market { margin-bottom: 24px; }

.market-bar {
  display: flex; align-items: center; gap: var(--space-2);
  margin: 10px 0 var(--space-3);
}
/* The field is the shelf's front door, so it takes the width rather than
   sitting as a token box in a corner. */
.market-search {
  position: relative;
  display: flex; align-items: center; gap: var(--space-2);
  flex: 1; min-width: 0;
  padding: 0 12px;
  height: 34px;
  border-radius: var(--radius-btn);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  color: var(--ink-faint);
  transition: box-shadow var(--dur-hover) var(--ease-soft);
}
/* The app's ring (`:focus-visible` above), drawn on the box because the field
   inside it has none of its own. It was a 2px halo of the accent's palest
   tint, which on the light page was close to no ring at all. */
.market-search:focus-within {
  outline: 2px solid var(--violet);
  outline-offset: 2px;
  color: var(--ink-muted);
}
.market-search .ic { flex: none; font-size: 15px; }
.market-search input {
  flex: 1; min-width: 0;
  border: 0; padding: 0; background: none; box-shadow: none;
  font: 13px/1 var(--font-ui);
  color: var(--ink);
}
.market-search input:focus { outline: none; box-shadow: none; }
.market-search input::placeholder { color: var(--ink-faint); }
/* WebKit draws its own clear affordance in a type=search, at a size and a
   grey that belong to Safari rather than to this app. */
.market-search input::-webkit-search-decoration,
.market-search input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* Two axes, two rows. Aisle first because "what kind of work" is the question
   people arrive with; size second because it is the one they answer after.

   These stay chips, not a second nav: no rail, no filled violet bed. The
   selected one is a raised surface; the rest are ghosts; a zero is quieter
   still. The glyph is how you scan the row without reading every label. */
.market-facets {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-bottom: var(--space-2);
}
.market-facets .chip {
  gap: 5px;
  padding: 5px 10px;
  max-width: 100%;
  min-width: 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-muted);
  background: transparent;
  /* The count is its own flex item. Clipping the chip used to shear the
     numeral's box when the label ran long, which is how "Everything 34"
     read as "thing 34" inside a broken oval. */
  overflow: visible;
}
.market-facets .chip .ic {
  font-size: 13px;
  color: var(--ink-faint);
}
.market-facets .chip-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A number, not a badge. A filled pill on the selected count was a plate
   sitting on 4px of chip padding: zero padding, a 1.15em min-width that
   let two digits shrink, and overflow hidden on the parent, so the oval
   clipped the glyph and collided with a truncated label. */
.market-facets .chip-count {
  flex: 0 0 auto;
  margin-left: 0;
  min-width: max-content;
  padding: 0;
  border-radius: 0;
  background: transparent;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--ink-faint);
}
.market-facets .chip:hover {
  background: var(--bg-hover);
  color: var(--ink-body);
}
.market-facets .chip:hover .ic { color: var(--ink-muted); }
.market-facets .chip.is-active {
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest);
}
.market-facets .chip.is-active .ic { color: var(--ink-violet); }
.market-facets .chip.is-active .chip-count {
  background: transparent;
  color: var(--ink-muted);
}
/* A facet that would empty the shelf says so and stops taking the click.
   Quiet, not struck-through: opacity alone used to leave a raised pill that
   still competed with "Everything". No fill, no ring, no hover lift. */
.market-facets .chip:disabled {
  opacity: 1;
  color: var(--ink-faint);
  background: transparent;
  box-shadow: none;
  cursor: default;
  font-weight: 500;
}
.market-facets .chip:disabled .ic { color: var(--ink-faint); }
.market-facets .chip:disabled .chip-count {
  background: transparent;
  color: var(--ink-faint);
}
.market-facets .chip:disabled:hover {
  transform: none;
  filter: none;
  background: transparent;
  color: var(--ink-faint);
}
.market-facets .chip:disabled:hover .ic { color: var(--ink-faint); }

/* A shelf heading. The count sits at the far end, quiet, so the eye reads
   title then note then number, in that order of usefulness. */
.market-shelf {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: 2px var(--space-2);
  margin: var(--space-6) 0 10px;
}
.market-shelf .section-label-note { margin-left: 0; }
.market-shelf-count {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}
/* The first shelf belongs to the facets above it, so it sits closer to them
   than the shelves sit to each other. */
.market-facets + .market-shelf { margin-top: var(--space-5); }

/* Track width IS the commitment. A quick play is a quarter of a row, an
   afternoon is a third, a pipeline is a half: the shelf's shape tells you what
   kind of thing you are looking at before you have read a name. */
.playbooks {
  display: grid;
  gap: 10px;
  margin-bottom: 4px;
}
/* auto-fill, not auto-fit. auto-fit collapses the empty tracks and lets two
   surviving cards stretch across the whole row, so a filtered shelf turned a
   ten-minute look-up into something the width of a pipeline. Here the width
   is the message, so the tracks hold their size and the row runs short. */
.playbooks.is-quick { grid-template-columns: repeat(auto-fill, minmax(min(212px, 100%), 1fr)); }
.playbooks.is-standard { grid-template-columns: repeat(auto-fill, minmax(min(268px, 100%), 1fr)); }
/* A pipeline card's meta line is one row (see .pb-meta below): two pills, the
   meter, the promise and up to three marks. That needs 480px to keep the
   marks on the card and the promise's first word in its pill; at 352 the
   marks ran under the next card in a 1440px window. */
.playbooks.is-pipeline { grid-template-columns: repeat(auto-fill, minmax(min(480px, 100%), 1fr)); }

.playbook {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  min-width: 0;
  gap: 6px;
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest);
  text-align: left;
  transition: transform var(--dur-hover) var(--ease-soft),
              box-shadow var(--dur-hover) var(--ease-soft),
              filter var(--dur-hover) var(--ease-soft);
}
.playbook:hover { transform: scale(1.012); filter: brightness(1.02); box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-hover); }
/* The global ring is drawn at chip radius; a card is rounder than that. */
.playbook:focus-visible { border-radius: var(--radius-card); }
.playbook:active {
  transform: scale(0.985);
  box-shadow: var(--inset-press-top), var(--inset-press-floor), var(--shadow-press);
  transition-duration: var(--dur-press);
}

/* Icon, name, price. The three things in the order they are read. */
.pb-top {
  display: flex; align-items: center; gap: 10px;
  min-width: 0;
}
/* The glyph rides its own tinted tile so it does not float unanchored in the
   corner of an otherwise built card. */
.pb-icon {
  display: grid; place-items: center;
  flex: none;
  width: 28px; height: 28px;
  border-radius: var(--radius-icon);
  background: var(--tint-violet);
  color: var(--ink-violet);
}
.pb-icon .ic { font-size: 15px; }
/* Not ready yet: a dot on the tile, in the pending hue, instead of a pill on
   every card repeating the same sentence. */
.pb-icon.is-waiting { position: relative; }
.pb-icon.is-waiting::after {
  content: "";
  position: absolute; top: -2px; right: -2px;
  width: 7px; height: 7px;
  border-radius: var(--radius-pill);
  background: var(--pending);
  box-shadow: 0 0 0 2px var(--surface);
}
.pb-name {
  flex: 1; min-width: 0;
  font: 600 14px/1.3 var(--font-display); letter-spacing: -0.01em; color: var(--ink);
}
/* Same card as every other method. The word, not a banner. */
.pb-tag {
  flex: none;
  padding: 2px 7px;
  border-radius: var(--radius-chip);
  background: var(--surface-sunken);
  color: var(--ink-muted);
  font-size: 10px;
  font-weight: 600;
}

/* The price tag: browser-hours against the ceiling the app will ever book.
   Sits with the numbers it draws, not off in the card's corner. */
.pb-effort { flex: none; display: block; }
.pb-effort .is-track { fill: var(--ink-faint); opacity: 0.3; }
.pb-effort .is-on { fill: var(--violet); }

.pb-tagline { flex: 1; font-size: 12px; line-height: 1.5; color: var(--ink-muted); }
.pb-meta {
  display: flex; align-items: center; gap: 6px 8px; flex-wrap: wrap;
  margin-top: 2px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
/* What it will cost you, in the type size that fact deserves. It used to be
   the smallest text on the card while being the one thing that decides
   whether clicking is a good idea.

   Given a floor width so the meters beside them start on the same pixel down
   a column. Ragged meters read as a rendering fault rather than as a scale. */
.pb-cost { flex: none; font-weight: 600; color: var(--ink-body); }
/* The delivery promise gives way before the marks do: "what it opens" is one
   glyph and cannot be shortened, and a pill that wrapped the marks onto a
   second line made one card in a row taller than the rest. */
.pb-meta .pill-delivers {
  flex: 0 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The row is one line: cost, meter, promise, marks. Left to wrap, the longest
   promise on the shelf ("qualified prospects with draft") pushed the marks
   down and made one card in a row taller than the two beside it. */
.playbooks.is-pipeline .pb-meta { flex-wrap: nowrap; }

/* The tools it will open, as their own marks. Pushed to the end of the meta
   line: it is the last question you ask, after what and how much. */
.pb-touches { display: flex; flex: none; align-items: center; gap: 4px; margin-left: auto; }
.pb-touch {
  display: grid; place-items: center;
  width: 18px; height: 18px;
  border-radius: var(--radius-chip);
  background: #fff;
  box-shadow: var(--inset-highlight);
  font: 700 9px/1 var(--font-display);
  color: var(--ink-muted);
}
.pb-touch img { width: 12px; height: 12px; object-fit: contain; }
.pb-touch.is-letter { background: var(--surface-2); }

/* A quick play is a small thing and is allowed to look like one: no tagline
   competing for the row, just what it is and what it costs. */
.playbooks.is-quick .playbook { gap: 4px; padding: 10px 12px 12px; }
.playbooks.is-quick .pb-icon { width: 24px; height: 24px; border-radius: var(--radius-chip); }
.playbooks.is-quick .pb-icon .ic { font-size: 13px; }
.playbooks.is-quick .pb-name { font-size: 13px; }
.playbooks.is-quick .pb-tagline {
  font-size: 11.5px; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* Compact: once work is running you know what these are, so the taglines go
   and the running work stays in sight. The effort block survives, because it
   is the fact you are still comparing on. */
.market.is-compact .playbooks { gap: 8px; }
.market.is-compact .pb-tagline { display: none; }
.market.is-compact .playbook { padding: 10px 12px; gap: 6px; }
.market.is-compact .market-shelf { margin-top: var(--space-4); }

/* Under 560 points there is not room for two cards on any shelf, and a
   half-width pipeline card at that size ellipses its own name away.

   The facets stop wrapping and start scrolling. Wrapped, eleven chips on two
   axes took ten lines and the entire first screen: you arrived at a shop and
   were shown the filing system. One swipeable row each keeps the first cards
   above the fold, which is the only reason the filters exist. */
@media (max-width: 560px) {
  .playbooks.is-quick,
  .playbooks.is-standard,
  .playbooks.is-pipeline { grid-template-columns: 1fr; }
  .market-shelf-count { margin-left: 0; }
  .market-facets {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    /* Bleed to the view's edge so the row reads as continuing off-screen
       rather than as a short list that happens to be clipped. */
    margin-right: calc(var(--gutter) * -1);
    padding-right: var(--gutter);
    scroll-snap-type: x proximity;
  }
  .market-facets::-webkit-scrollbar { display: none; }
  .market-facets .chip { flex: none; scroll-snap-align: start; }
  /* The floor that keeps meters aligned in a column is what pushed the marks
     onto a second line when there is only one column. */
  .pb-cost,
  .playbooks.is-pipeline .pb-cost { min-width: 0; }
}

/* The other door. A link, not a button: writing a good goal from scratch is
   the harder path, and the plays exist because of that. */
.playbook-own {
  display: inline-block;
  margin: var(--space-4) 0 0;
  font-size: 13px; font-weight: 600;
  color: var(--ink-muted);
  transition: color var(--dur-hover) var(--ease-soft);
}
.playbook-own:hover { color: var(--ink); }

/* Favicon plate used elsewhere; profiles override below to a small mark. */
.icon {
  width: 40px; height: 40px; flex: 0 0 40px;
  border-radius: var(--radius-icon);
  background: var(--icon-violet);
  box-shadow: var(--inset-icon-top), var(--glow-icon-violet);
  display: grid; place-items: center;
  font: 700 17px/1 var(--font-display);
  color: #fff;
  overflow: hidden;
}
.icon img { width: 100%; height: 100%; object-fit: contain; background: #fff; }

/* -- profile: a quiet identity block, not a dashboard-in-a-card ---------- */
.card-profile {
  padding: 12px 14px;
  margin-bottom: 8px;
  /* Inset well, same voice as Overview — drop the raised card stack. */
  box-shadow: var(--inset-well);
}
.card-profile:hover { box-shadow: var(--inset-well); }
/* The five row actions are about 520px and never shrink. When the card is
   too narrow for them beside a readable name, they move to a line of their
   own; before, the name took whatever was left, and in a 900px window that
   was one or two letters to a line down the whole window. */
.card-profile .card-top { align-items: center; gap: 12px; flex-wrap: wrap; }
.card-profile .card-heading { flex: 1 1 240px; }
.card-profile .card-title {
  font: 600 14px/1.3 var(--font-ui);
  letter-spacing: -0.01em;
}
.card-profile .card-sub {
  margin-top: 2px;
  gap: 6px 12px;
  font-size: 12px;
  /* Clips a dot that a wrap carried past the left edge: see .card-sep. */
  overflow: hidden;
}
.card-profile .card-site {
  font: 12px/1.5 var(--font-mono);
  color: var(--ink-muted);
  overflow-wrap: anywhere;
}
/* The dot between the site and the language sits out in the 12px gap before
   the language, as its first glyph. On one line it reads "site · English". A
   language that wraps to a line of its own starts at the row's left edge,
   which puts its dot past that edge, where the row clips it: never
   "· English" at the start of a line. */
.card-profile .card-sep {
  display: inline-block;
  width: 12px; margin-left: -12px;
  text-align: center;
  color: var(--ink-faint);
}
.card-profile .card-lang { color: var(--ink-muted); }
.card-profile .card-actions { gap: 2px; }
.card-profile .card-actions .btn {
  padding: 3px 10px;
  font-size: 12px;
}

/* Favicon or initial: a small mark, not a violet plate with glow. */
.card-profile .icon {
  width: 28px; height: 28px; flex: 0 0 28px;
  border-radius: 6px;
  background: var(--surface-sunken);
  box-shadow: none;
  font: 600 12px/1 var(--font-ui);
  color: var(--ink-muted);
}
.card-profile .icon img { background: #fff; }

/* Facts as one muted run. Class `.stats` remains so crawl rain can land here. */
.stats {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--hairline);
}
.profile-facts-run {
  min-width: 0;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.stats .crawl-status {
  margin: 0 0 0 auto;
  align-self: baseline;
  text-align: right;
  flex-shrink: 0;
}
.stat { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.stat-value {
  font-size: 16px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.stat-value[data-empty="1"] { color: var(--ink-faint); }

/* Every link the user gave, and how much of it was actually read. Colour is
   spent on state (read, unread, failed), never on the kind: the kind already
   says its own name. */
.sources {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--hairline);
}
.source {
  display: grid;
  grid-template-columns: 68px minmax(0, 1fr) auto;
  column-gap: 14px;
  align-items: baseline;
  padding: 4px 0;
}
.source[data-read="1"] .src-kind { color: var(--ink-body); }
.src-url {
  font: 13px/1.6 var(--font-mono);
  color: var(--ink-body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.source[data-read="0"] .src-url { color: var(--ink-muted); }
.src-count {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  white-space: nowrap;
}
.profile-extra {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--hairline);
}
/* The company's own site could not be read: why, and the two ways round it.
   A quiet, neutral box like any other note on the card: amber here means a
   run is live, and the card's own "reading…" chip already wears it. Only the
   small mark is coloured. */
.profile-read-problem {
  margin-top: var(--space-4);
  padding: 12px 14px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-btn);
  background: var(--surface-2);
  color: var(--ink-body);
}
.profile-read-problem[hidden] { display: none; }
.profile-read-problem-text {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.profile-read-problem-text .ic {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  color: var(--ink-pending);
}
.profile-read-problem.is-answered .profile-read-problem-text .ic { color: var(--ink-muted); }
/* Ghost buttons: pulled left by their own padding, so each icon lines up
   with the sentence above it rather than sitting a step to its right. */
.profile-read-problem-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 0 12px;
}
.profile-read-problem-paste {
  display: grid;
  gap: 8px;
  margin: 10px 0 0 24px;
}
.profile-read-problem-paste[hidden] { display: none; }
.profile-read-problem-area {
  min-height: 96px;
  resize: vertical;
  font: inherit;
  font-size: 13px;
  line-height: 1.55;
}
.profile-read-problem-paste .btn { justify-self: start; }
.profile-extra-head {
  margin-bottom: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
}
.profile-extra-notes {
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink-body);
  max-width: 72ch;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.profile-extra-notes.is-open { -webkit-line-clamp: unset; overflow: visible; }
.profile-extra-links {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 8px;
}
.profile-extra-link {
  font: 13px/1.6 var(--font-mono);
  color: var(--ink-body);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.src-error {
  grid-column: 2 / -1;
  padding-bottom: 4px;
  font-size: 13px; line-height: 1.45;
  color: var(--ink-danger);
  overflow-wrap: anywhere;
}

/* Learned summary: body text, no blue rail plate, no eyebrow chrome. */
.learned {
  margin-top: 10px;
  padding: 0;
  border: 0;
}
.learned-head {
  display: flex; align-items: baseline; gap: 10px;
  margin-bottom: 8px;
  color: var(--ink-muted);
}
.learned-when {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-muted);
}
.learned-summary {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink-body);
  max-width: 72ch;
  /* Three lines is the verdict; the rest is available, not imposed. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.learned-summary.is-open { -webkit-line-clamp: unset; overflow: visible; }
.learned-grid {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr);
  column-gap: 14px; row-gap: 10px;
  margin-top: 14px;
}
.learned-key { line-height: 1.6; }
.learned-value { font-size: 14px; line-height: 1.55; color: var(--ink-body); max-width: 72ch; }
/* Products come back as whole sentences. They used to be set as chips, which
   made four one-line descriptions read as four enormous badges. */
.learned-list {
  display: flex; flex-direction: column; gap: 6px;
  margin: 0; padding: 0;
  list-style: none;
  max-width: 72ch;
}
.learned-list li {
  position: relative;
  padding-left: 14px;
  font-size: 14px; line-height: 1.55;
  color: var(--ink-body);
}
.learned-list li::before {
  content: "";
  position: absolute; left: 0; top: 0.62em;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--ink-faint);
}
.proof { display: flex; flex-direction: column; gap: 6px; }
.proof-item {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr);
  column-gap: 8px;
  font-size: 14px; line-height: 1.55;
  color: var(--ink-body);
  max-width: 78ch;
}
.proof-mark { font-size: 8px; line-height: 1; padding-top: 0.75em; color: var(--ink-faint); }

.crawl-status { margin-top: 14px; font-size: 12px; color: var(--ink-muted); }
.card-profile .crawl-status { margin-top: 0; }
.crawl-status.is-working { color: var(--ink-pending); font-weight: 600; }
.crawl-status.is-working::before {
  content: "";
  display: inline-block;
  width: 6px; height: 6px;
  margin-right: 8px;
  border-radius: 50%;
  background: var(--pending);
  animation: breathe 1.4s ease-in-out infinite;
}

/* Profile folds: thinner contents rows, not padded accordion chrome. */
.card-profile .fold { margin-top: 8px; }
.card-profile .fold-toggle {
  padding: 6px 2px;
  font-size: 12px;
  font-weight: 550;
}

/* -- the scoreboard: outcomes, before anything about deliverability -------- */
/* Five tiles on one line at full width, wrapping to a grid when the window is
   narrow. Not flex with wrapping: a wrapped flex row leaves a ragged last line
   and the numbers stop lining up, which is the one thing a scoreboard has to
   do. The minimum was sized for the six the row once held (about 815px of
   column at the default window), so five always fit on one line. */
.scoreboard {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 2px 0;
  padding: 20px 4px;
}
.scoreboard-tile {
  display: flex; flex-direction: column; gap: 3px;
  padding: 0 12px;
  min-width: 0;
  /* A hairline between tiles rather than a border on each: one rule, and no
     doubled line where two tiles meet. */
  box-shadow: inset 1px 0 0 var(--hairline);
}
.scoreboard-tile:first-child { box-shadow: none; }
.scoreboard-value {
  font-size: 26px;
  font-weight: 640;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  color: var(--ink);
}
.scoreboard-value[data-empty="1"] { color: var(--ink-faint); }
.scoreboard-label {
  font-size: 12.5px;
  font-weight: 550;
  color: var(--ink-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dt-pill.scoreboard-note {
  align-self: start;
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis;
}
/* Under its note, left-aligned with the figure, and never wider than the
   tile: at three columns a tile is barely wider than the chip above it. */
.scoreboard-action.btn {
  align-self: start;
  max-width: 100%;
  margin-top: 4px;
}
.scoreboard-action.btn .ic { font-size: 13px; }
/* Five fit on one line from a window of about 960px, with room for the
   list's scrollbar. Below that the auto-fit grid made four columns and left
   Hours worked alone on a second row, with the divider that belongs between
   two tiles standing at its left and no neighbour beyond it. Three and two
   instead, and a tile that starts a row carries no divider. */
@media (max-width: 959px) {
  .scoreboard { grid-template-columns: repeat(3, 1fr); row-gap: 16px; }
  .scoreboard-tile:nth-child(3n + 1) { box-shadow: none; }
}
/* Below this the tiles would be cutting words in half; one per row reads. */
@media (max-width: 700px) {
  .scoreboard { grid-template-columns: repeat(2, 1fr); }
  .scoreboard-tile { box-shadow: none; padding: 8px 12px; }
}

/* -- reading the links: a contribution grid, raining ---------------------- */
/* Four brightness levels, the way a contribution graph has four: a flat wash
   would read as a loading bar, and the point is that this is discrete work
   landing cell by cell. Sized in ems off the card's own type so the strip
   scales with everything around it. */
.crawl-rain {
  display: grid;
  grid-template-columns: repeat(22, 1fr);
  gap: 3px;
  margin: 12px 0 2px;
  max-width: 250px;
}
.rain-cell {
  aspect-ratio: 1;
  border-radius: 2px;
  background: var(--progress);
  opacity: 0;
  animation: rain-fall 1.9s ease-in-out infinite;
  animation-delay: var(--d, 0s);
}
@keyframes rain-fall {
  0%   { opacity: 0; transform: translateY(-2px); }
  18%  { opacity: var(--peak, 0.6); transform: translateY(0); }
  55%  { opacity: calc(var(--peak, 0.6) * 0.35); transform: translateY(0); }
  100% { opacity: 0; transform: translateY(2px); }
}

@media (prefers-reduced-motion: reduce) {
  /* Still legible as "working", with nothing moving: the cells hold their
     levels so the strip reads as a static contribution grid. */
  .rain-cell {
    animation: none;
    opacity: calc(var(--peak, 0.5) * 0.55);
    transform: none;
  }
}

/* -- goal manage rows: equal, quiet, no road ------------------------------ */
/* Overview owns the progress theater. This page is Pause / Stop / Edit /
   Delete, so every row is the same height and the same voice. The state
   word lives in the chip; a coloured rail was a second telling. */
/* Up to four buttons that never shrink. In a window too narrow for them
   beside a readable name they go to a line of their own, rather than leave
   the name "Find users for the..." and push its resume time off the row. */
.goal-row .card-top { align-items: center; flex-wrap: wrap; }
.goal-row .chip-state { flex-shrink: 0; }
.goal-row .card-heading { flex: 1 1 260px; }
.goal-row .card-title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.goal-row .card-sub {
  display: block;
  margin-top: 3px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A block has no gap, so the row line's room between tokens is put back by
   hand. Without it "0m in" and "resumes 7:00 AM" were one run of two pills. */
.goal-row .card-sub > * + * { margin-left: 10px; }
/* The sentence for a goal that is waiting (its sending hours, the daily cap,
   a sign-in), under the chips. A line of its own, because the chip line is
   pills and ends in an ellipsis: a sentence in it was cut to "Outside your…"
   or pushed the resume time off the row. */
.goal-row .card-why {
  margin-top: 3px;
  color: var(--ink-muted);
  font-size: 13px;
}


.tasks { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--hairline); }
.task {
  display: grid;
  grid-template-columns: 24px 16px minmax(0, 1fr);
  column-gap: 10px;
  align-items: baseline;
  padding: 4px 0;
  font-size: 14px;
}
.task-index {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  text-align: right;
}
/* A glyph as well as a colour: state survives greyscale and colour blindness. */
.task-mark { font-size: 13px; color: var(--ink-faint); text-align: center; }
.task[data-state="done"] .task-mark { color: var(--ink-success); }
.task[data-state="running"] .task-mark { color: var(--ink-pending); }
.task[data-state="failed"] .task-mark { color: var(--ink-danger); }
.task-title { color: var(--ink-body); overflow-wrap: anywhere; line-height: 1.5; }
.task[data-state="done"] .task-title { color: var(--ink); }
.task[data-state="running"] .task-title { color: var(--ink); font-weight: 600; }
.task[data-state="skipped"] { opacity: 0.45; }
.task[data-state="skipped"] .task-title { text-decoration: line-through; }

/* -- team: a pipeline, watched -------------------------------------------- */
/* The roles in the order work moves through them, which is the one thing about
   a team you cannot get from a list of numbers. Each cell is a live meter: one
   dot per worker slot, lit and breathing while that worker is out there. */
.pipeline {
  display: flex; align-items: stretch; flex-wrap: wrap;
  gap: 8px;
  margin-top: 20px;
}
.role {
  display: flex; flex-direction: column; gap: 8px;
  min-width: 0;
  padding: 12px 16px;
  border-radius: var(--radius-chip);
  background: var(--surface-2);
  box-shadow: var(--inset-floor);
}
.role-name { color: var(--ink-body); }
.slots { display: flex; gap: 4px; }
.slot { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-faint); }
.slot.is-on { background: var(--pending); box-shadow: 0 0 0 2px var(--tint-amber); animation: breathe 2s ease-in-out infinite; }
.role-queue {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  white-space: nowrap;
}
.role.is-off { opacity: 0.6; }
.role.is-off .role-name { color: var(--ink-danger); }
.pipe { align-self: center; font-size: 13px; color: var(--ink-faint); }


.items { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--hairline); }
.item {
  display: grid;
  /* 116px fits "approved draft", the longest stage name, without ellipsis. */
  grid-template-columns: 16px 116px minmax(0, 1fr) auto;
  column-gap: 12px;
  align-items: baseline;
  padding: 4px 0;
  font-size: 14px;
}
.item-mark { font-size: 13px; color: var(--ink-faint); text-align: center; }
.item[data-state="done"] .item-mark { color: var(--ink-success); }
.item[data-state="running"] .item-mark { color: var(--ink-pending); }
.item[data-state="failed"] .item-mark { color: var(--ink-danger); }
.item-kind {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.item-label {
  min-width: 0;
  color: var(--ink-body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.item[data-state="running"] .item-label { color: var(--ink); font-weight: 600; }
.item[data-state="failed"] .item-label { color: var(--ink); }
.item-role { font-size: 12px; color: var(--ink-muted); white-space: nowrap; }
.items-more { margin-top: 10px; font-size: 13px; color: var(--ink-muted); }


/* == the road ==============================================================
 *
 * The one progress object in this app, and the reason the product is called
 * Lanes. Work is a trip: a destination the owner named, a road run left to
 * right, and the brand's own mark sitting exactly where the work has got to.
 * Three sizes of one object -- `is-page` on the overview, `is-card` inside a
 * goal, `is-strip` on a worker's page -- so it is learned once and then read
 * everywhere without being read again.
 *
 * The road is deliberately the only thing in this interface that moves on its
 * own. Everything else here is Linear's school: crisp hairlines, flat fills,
 * nothing blooming. One travelling element in the whole app means motion still
 * MEANS something, and what it means is "your work is happening right now" --
 * which is the single fact a person opens this app to check. A paused trip's
 * road holds perfectly still, and that stillness is the message.
 */
.route {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}

/* ---- who is going where ---- */
.route-head { display: flex; align-items: center; gap: 12px; min-width: 0; }
.route-dest {
  flex: 1; min-width: 0;
  font: 600 15px/1.35 var(--font-display);
  letter-spacing: -0.015em;
  color: var(--ink);
  text-align: left;
  /* The destination is a short name, so one line is the whole of it. The
     briefing is on hover. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
button.route-dest { transition: color var(--dur-hover) var(--ease-soft); }
button.route-dest:hover { color: var(--violet); }
.route-head-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.route-age {
  font-size: 12px; font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* ---- the numbers, set above the road so they never move with the mark ----
 *
 * The count was tried ON the marker first, riding it down the lane. At 3 of
 * 500 the label is crushed against the origin and at 500 of 500 it hangs off
 * the end; a figure that is sometimes unreadable is worse than one that never
 * moves. So the road carries position and the line above carries the number. */
.route-figures {
  display: flex; align-items: baseline; gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}
.route-count {
  font: 700 28px/1 var(--font-display);
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.route-of {
  font-size: 15px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
.route-unit { font-size: 14px; color: var(--ink-body); }
/* A claim nobody has checked is never added to the count. It rides beside it,
   in the ink its meaning earns and nothing louder. */
.route-checking {
  font-size: 12.5px; font-weight: 500;
  color: var(--ink-progress);
  white-space: nowrap;
}
.route-checking::before {
  content: "·";
  margin-right: 8px;
  color: var(--ink-faint);
}
/* An open run: no denominator exists, so none is invented. What it can say
   honestly is how far it has actually got, which is steps and elapsed. */
.route-open {
  font: 600 14px/1.2 var(--font-display);
  font-variant-numeric: tabular-nums;
  color: var(--ink-body);
}
/* The arrival, pushed to the far end of the line: it is the answer to the
   second question anybody asks, never the first. */
.route-eta {
  margin-left: auto;
  font-size: 12.5px; font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  white-space: nowrap;
}
/* "It stops short of the target" is not an arrival time, it is a warning, and
   it is the one thing on this object somebody has to act on. */
.route-eta[data-short="1"] { color: var(--ink-pending); font-weight: 600; }
.route-out {
  font-size: 12.5px; font-weight: 600;
  color: var(--ink-muted);
  white-space: nowrap;
  transition: color var(--dur-hover) var(--ease-soft);
}
.route-out:hover { color: var(--violet); }
.route-eta + .route-out { margin-left: 0; }
.route-figures .route-eta + .route-out::before {
  content: "·";
  margin-right: 8px;
  color: var(--ink-faint);
}

/* ---- the road ----------------------------------------------------------- */
/* Ten pixels, square-ended, with the fill running the full height of it. A
   pill-shaped 8px rail with a round token on it is the shape of a volume
   control; a road is a surface with a paved part and an unpaved part. */
.route-lane {
  position: relative;
  height: 10px;
  border-radius: 2px;
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  /* The mark and the finish line overhang by design. */
  overflow: visible;
}
/* The lane markings. A centre line, dashed, at a fifth of the road's height,
   because that is what a road looks like from above and because a full-height
   pattern under a 4px track reads as a hatch rather than as a road.
 *
 * Two elements for one line, because the dashes move. The outer one is the
 * window -- the exact strip the markings are allowed to occupy -- and the
 * inner one is the pattern, one dash-period wider than the window and
 * travelling under it. `.route-lane` is deliberately `overflow: visible` (the
 * mark and the finish line overhang it), so the clip has to be its own box;
 * that is the whole reason this is an element and not the lane's `::before`. */
.lane-dash {
  position: absolute;
  inset: 4px 8px 4px 0;
  border-radius: 1px;
  overflow: hidden;
  opacity: 0.8;
  pointer-events: none;
}
.lane-dash::before {
  content: "";
  position: absolute;
  /* One period of overhang on the right: what a full period of travel to the
     left pulls into view, and nothing more. Clipped by the window above. */
  inset: 0 -21px 0 0;
  background: repeating-linear-gradient(
    90deg,
    var(--ink-faint) 0 9px,
    transparent 9px 21px
  );
}
/* Moving only while the trip is. The dashes run BACKWARDS, toward the mark,
   because that is what the road does when you are the one driving.
 *
 * The run is a transform, and it has to stay one. It used to slide
 * `background-position-x`, which is a paint property: Blink cannot composite
 * it, so every frame of it came back to the main thread to rasterize the
 * gradient again -- 60 a second, per road on screen, for as long as the road
 * is moving, and a road moves for as long as its run does. Six roads over 181
 * frames: 1274 paints and 546 raster tasks -> 0 and 0, and that is before a
 * compositor gets to take the whole animation off the main thread.
 * `tests/test_lane_dashes.py` holds it there; docs/performance.md has the
 * table.
 *
 * The pattern's period is 21px and the travel is exactly one period, so the
 * end frame is pixel-identical to the start and the loop has no seam. */
.route[data-moving="1"] .lane-dash::before {
  animation: lane-run 1.2s linear infinite;
}
@keyframes lane-run {
  from { transform: translateX(0); }
  to   { transform: translateX(-21px); }
}

/* Road already travelled. The one saturated fill on the object. */
.lane-done {
  position: absolute; left: 0; top: 0; bottom: 0;
  border-radius: 2px 0 0 2px;
  background: var(--grad-violet);
  transition: width 520ms var(--ease-soft);
}
/* Claimed, unverified. Hatched and unsaturated on purpose: it is on the road,
   it is not yet distance covered, and merging the two is the single most
   common lie in this category of product. */
.lane-claimed {
  position: absolute; top: 0; bottom: 0;
  background:
    repeating-linear-gradient(135deg,
      var(--tint-violet) 0 3px, transparent 3px 7px),
    var(--tint-violet);
  transition: left 520ms var(--ease-soft), width 520ms var(--ease-soft);
}
.route[data-arrived="1"] .lane-done { background: var(--grad-green); }

/* The mark: one petal of the logo, turned to point down the lane.
 *
 * It is the NOSE OF THE FILL, not a token riding on top of it. That
 * distinction is the whole difference between a picture of progress and a
 * control somebody can drag, and the first version got it wrong: a knockout
 * stroke in the page colour separated the mark from the paved road behind it,
 * which is exactly how a slider thumb is drawn. Same ink as the fill, no
 * outline, flush against its leading edge — one shape, a road paved this far
 * and pointing on. Nothing here is draggable and nothing should look it. */
.lane-mark {
  position: absolute; top: 50%; left: 0;
  width: 16px; height: 16px;
  margin: -8px 0 0 -7px;
  display: grid; place-items: center;
  color: var(--violet);
  pointer-events: none;
  transition: left 520ms var(--ease-soft);
}
.lane-mark .ic { width: 16px; height: 16px; fill: currentColor; }
.route[data-arrived="1"] .lane-mark { color: var(--success); }

/* An open run has no destination to sit against, so the mark drives instead:
   it is honest about the one thing it knows, that work is happening, and
   silent about the one it does not, how much is left. The road fades out at
   the right rather than ending, because an end would be a claim. */
.route[data-open="1"] .route-lane {
  -webkit-mask-image: linear-gradient(90deg, #000 40%, transparent 100%);
  mask-image: linear-gradient(90deg, #000 40%, transparent 100%);
}
/* Quieter than a road with a destination on it. There is nothing to measure
   here, and a full-strength dashed line across the whole width read as a
   ruler somebody had left on the page. */
.route[data-open="1"] .lane-dash { opacity: 0.5; }
/* The drive travels on the compositor, not on the layout engine.
 *
 * It used to animate `left`, and `left` is an inset: every frame of it laid
 * the lane out again. That is 60 layout passes a second, for as long as the
 * run is open, and an open run is open for hours -- the one animation in this
 * file that costs a layout, on the one road that never stops. A transform
 * moves the same pixels on the compositor: Chromium runs it off the main
 * thread entirely, so no layout, no style recalc, no repaint.
 * Six roads on screen for six seconds: 363 layout passes -> 0, and 290ms of
 * main-thread work -> 4.8ms, against 0.6ms for the same six roads standing
 * still. tests/test_animation_cost.py holds it there; docs/performance.md has
 * the table, and a warning about measuring this with a rAF loop, which is
 * itself most of what such a loop would measure.
 *
 * `left` is a percentage of the LANE, `translateX` a percentage of the MARK,
 * so the box is widened to the travel distance and the petal pinned to its
 * trailing edge: translateX(-100%)..0 then covers exactly the ground
 * `left: 0 -> 92%` covered. The 9px start replaces the -7px margin the 16px
 * box carried, so the petal lands on the same pixel at both ends (9 + 92% -
 * 16 + 8 = 92% + 1, and 92% + 1 - 92% = 1: the two positions `left` gave).
 * The box is `pointer-events: none` and paints nothing but the petal, so
 * widening it is invisible and un-hittable. */
.route[data-open="1"][data-moving="1"] .lane-mark {
  width: 92%;
  place-items: center end;
  margin-left: 9px;
  transform: translateX(-100%);
  animation: lane-travel 3.2s var(--ease-soft) infinite;
  transition: none;
}
@keyframes lane-travel {
  0%   { transform: translateX(-100%); opacity: 0; }
  12%  { opacity: 1; }
  78%  { opacity: 1; }
  100% { transform: translateX(0);     opacity: 0; }
}

/* The destination: a finish line.
 *
 * It was a ring, and a ring at the end of a track with a thumb on it is the
 * far handle of a range slider — the single strongest wrong signal this
 * object was sending. A chequer cannot be read as a control by anyone. It is
 * six pixels of it, at the weight of a hairline until the moment it is
 * reached, which is the only celebration this interface allows itself. */
.lane-end {
  position: absolute; right: 0; top: -3px; bottom: -3px;
  width: 6px;
  border-radius: 1px;
  background:
    repeating-conic-gradient(var(--ink-faint) 0% 25%, transparent 0% 50%)
    0 0 / 3px 3px;
  transition: background var(--dur-hover) var(--ease-soft);
}
.route[data-arrived="1"] .lane-end {
  background:
    repeating-conic-gradient(var(--success) 0% 25%, transparent 0% 50%)
    0 0 / 3px 3px;
}

/* ---- what the road cannot say in a number ---- */
.route-meta {
  display: flex; align-items: baseline; gap: 12px;
  flex-wrap: wrap;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
.route-meta-text { min-width: 0; }
.route-failed {
  font-size: 12.5px; font-weight: 600;
  color: var(--ink-danger);
  padding: 1px 6px;
  margin: -1px -6px;
  border-radius: var(--radius-chip);
  transition: background var(--dur-hover) var(--ease-soft);
}
.route-failed:hover { background: var(--tint-red); }
/* The live lines belong to the road, not to the card, so they tuck under it
   rather than restating the card's own top margin. */
.route .now { margin-top: 0; }

/* ---- the overview's roads ---- */
.route-card { padding: 16px 18px; }
.route-more {
  display: block;
  margin: -2px 0 var(--space-4);
  padding: 6px 2px;
  font-size: 13px; font-weight: 600;
  color: var(--ink-muted);
  transition: color var(--dur-hover) var(--ease-soft);
}
.route-more:hover { color: var(--ink); }
/* Stop all rides the same quiet section-add as Clear finished. Only drawn
   while something is actually running; no banner, no filled pill. Sentence
   case so it reads as the Pause/Watch actions do, not as another kicker. */
.roads-head { align-items: center; margin: 0 0 10px; }
.roads-head .section-add {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  text-transform: none;
  letter-spacing: 0;
  font-size: 12px;
}
.roads-head .section-add .ic { width: 13px; height: 13px; font-size: 13px; }

/* ---- the strip on a worker's page ---- */
/* Tighter, and under the header rather than a second hero: it is a fact
   about this page, not an object on it. */
.route.is-strip {
  gap: 6px;
  padding: 8px var(--gutter) 4px;
}
.route.is-strip .route-count {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.02em;
}
.route.is-strip .route-of { font-size: 12px; font-weight: 500; }
.route.is-strip .route-unit { font-size: 12px; }
.route.is-strip .route-eta { font-size: 12px; }
.route.is-strip .route-lane { height: 6px; }
.route.is-strip .lane-dash { inset: 2px 8px 2px 0; }
.route.is-strip .lane-mark {
  width: 12px; height: 12px;
  margin: -6px 0 0 -5px;
}
.route.is-strip .lane-mark .ic { width: 12px; height: 12px; }

/* ---- "Where to?" -------------------------------------------------------- */
/* With nothing on the road, the overview is not a report with no rows in it.
   It is the order form.
 *
 * No road on this card. The first version drew an empty lane under a numeric
 * dial, and a track with a marker on it sitting directly beneath a stepper is
 * a range slider by every convention there is — people read it as "drag to ask
 * for more customers". There is also nothing to draw: no distance has been
 * covered, because no trip has been named yet. A field and a button. */
.pickup { padding: 20px 22px 18px; display: flex; flex-direction: column; gap: 14px; }
.pickup-head {
  margin: 0; padding: 0; border: 0;
  font: 700 clamp(20px, 1.8vw, 24px)/1.15 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--ink);
}
.pickup-go { display: flex; align-items: stretch; gap: var(--space-3); }
.pickup-input {
  flex: 1; min-width: 0;
  resize: none;
  font-size: 14px;
  line-height: 1.5;
  /* Two lines of a real destination. These sentences run long -- "Reach 10000
     people who fit us, each one researched, and book every call that comes
     back" -- and a single-line field turns the one thing this screen is for
     into a peephole. */
  min-height: 62px;
}
.pickup-btn {
  align-self: stretch;
  padding: 0 20px;
  font-size: 14px;
}
.pickup-btn .ic { font-size: 16px; }
.pickup-foot {
  display: flex; align-items: baseline; gap: var(--space-4);
  flex-wrap: wrap;
}
.pickup-note { flex: 1; min-width: 200px; font-size: 12.5px; color: var(--ink-muted); }
/* The company Go runs for, in the sentence under it: read at a glance. */
.pickup-for { color: var(--ink); font-weight: 600; }
.pickup-alt {
  font-size: 13px; font-weight: 600;
  color: var(--ink-muted);
  white-space: nowrap;
  transition: color var(--dur-hover) var(--ease-soft);
}
.pickup-alt:hover { color: var(--ink); }
.pickup .promise-warn { margin-top: 2px; }

@media (prefers-reduced-motion: reduce) {
  .lane-dash::before,
  .route[data-open="1"][data-moving="1"] .lane-mark { animation: none; }
  .lane-done, .lane-claimed, .lane-mark { transition: none; }
}

/* -- the fold: a card's working, one deliberate click away ----------------- */
.fold { margin-top: 14px; border-top: 1px solid var(--hairline); }
/* Folds in a row are one index, not several widgets: their shared hairlines
   rule the group like a contents list, so the closed card tells you what it is
   holding without holding it open. */
.fold + .fold { margin-top: 0; }
.fold-toggle {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 10px 2px 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-muted);
  text-align: left;
  border-radius: var(--radius-chip);
  transition: color var(--dur-hover) var(--ease-soft);
}
.fold-toggle:hover { color: var(--ink); }
.fold-toggle .ic {
  font-size: 13px;
  margin-left: auto;
  transition: transform 300ms var(--ease-soft);
}
.fold.is-open .fold-toggle { color: var(--ink-body); }
.fold.is-open .fold-toggle .ic { transform: rotate(180deg); }
.fold-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 320ms var(--ease-soft);
}
.fold.is-open .fold-body { grid-template-rows: 1fr; }
.fold-inner { overflow: hidden; min-height: 0; }
.fold-inner > :first-child { margin-top: 4px; }
.fold-inner > :last-child { margin-bottom: 6px; }
/* Sections inside a fold already sit under its rule; their own top borders
   would draw double lines. */
.fold-inner .sources, .fold-inner .items, .fold-inner .tasks { border-top: 0; padding-top: 4px; margin-top: 8px; }
.fold-inner .pipeline { margin-top: 8px; }
.fold-inner .board, .fold-inner .ledger { margin-top: 10px; }
.fold-meta {
  margin: 12px 2px 2px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
/* A typed goal's plan: a label column and the words, the way a brief reads.
   Wraps rather than truncates, because a plan cut mid-sentence is a different
   plan. */
.team-plan {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: 14px;
  row-gap: 6px;
  margin: 4px 2px 8px;
  font-size: 13px;
  line-height: 1.5;
}
.team-plan dt { color: var(--ink-muted); font-weight: 600; }
.team-plan dd { margin: 0; color: var(--ink-body); overflow-wrap: anywhere; }

/* -- now: what it is doing, in one live line ------------------------------- */
.now {
  display: flex; align-items: baseline; gap: 8px;
  margin-top: 12px;
  min-width: 0;
}
.now-dot {
  width: 7px; height: 7px; border-radius: 50%;
  flex: 0 0 7px;
  align-self: center;
  background: var(--pending);
  box-shadow: 0 0 0 3px var(--tint-amber);
  animation: breathe 2s ease-in-out infinite;
}
.now-text {
  min-width: 0;
  font-size: 14px;
  color: var(--ink-body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.ledger-row[data-verified="false"] .ledger-mark { color: var(--ink-progress); }
.ledger-row[data-verified="false"] .ledger-label { color: var(--ink-muted); }

/* -- needs: the run is waiting on the owner -------------------------------- */
/* No state rail here, unlike a goal or a team. Theirs carries information --
   the colour is the state and it changes with the chip beside it. A need has
   exactly one state, so the rail was the same amber on every card in the
   stack: a stripe that never varies says nothing, and the amber pill and the
   logo plate already say it. */
.pill-need { background: var(--tint-amber); color: var(--ink-pending); }
/* Outranks `.tab.is-active .count`, which would otherwise mute the one badge
   that must never be muted. Violet, not amber: amber is the pending hue and
   its solid fill is tuned for a light rail, so on the dark sidebar it read as
   a second palette shouting. The accent is already the rail's only colour. */
.count.is-urgent, .tab.is-active .count.is-urgent {
  background: var(--icon-violet);
  color: #ffffff;
  box-shadow: var(--inset-icon-top), var(--glow-icon-violet);
}

.chip-state[data-state="waiting"] { background: var(--tint-amber); color: var(--ink-pending); }
.chip-state[data-state="waiting"] .chip-dot { animation: breathe 1.2s ease-in-out infinite; }
.chip-state[data-state="proposed"] { background: var(--tint-amber); color: var(--ink-pending); }
.chip-state[data-state="active"] { background: var(--tint-green); color: var(--ink-success); }
.chip-state[data-state="archived"] { background: var(--surface-sunken); color: var(--ink-muted); }

/* -- the ledger: receipts, one per delivered unit -------------------------- */
/* Rows, not a green panel. The ✓ in success ink on every verified row already
   says these are the good ones; a tinted bed said it a second time and was the
   largest block of colour anywhere in the app. */
.ledger {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: var(--space-3);
  max-height: 220px;
  overflow-y: auto;
}
.ledger-row {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  column-gap: 8px;
  align-items: baseline;
  font-size: 14px;
}
.ledger-mark { color: var(--ink-success); font-weight: 700; text-align: center; }
.ledger-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ledger-label { color: var(--ink); overflow-wrap: anywhere; user-select: text; }
.ledger-state {
  margin-left: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-progress);
}
.ledger-row[data-verified="false"] .ledger-state { color: var(--ink-progress); }
/* The receipt under the claim: full width, one line, the rest on hover. */
.ledger-evidence {
  font: 12px/1.7 var(--font-mono);
  color: var(--ink-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  user-select: text;
}

/* What the workers told each other. Chatter, so it is set as chatter: quiet
   rows under the same hairline the rest of the fold's sections use, not its
   own filled panel. */
.board {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--hairline);
  max-height: 220px;
  overflow-y: auto;
}
.board-note {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 12px;
  align-items: baseline;
}
.board-role { color: var(--ink-progress); }
.board-text { font-size: 14px; line-height: 1.55; color: var(--ink-body); overflow-wrap: anywhere; }

/* -- memory: a list of lessons, one row each ------------------------------- */
/* A three-column wall of paragraph cards made six hundred similar lessons
   unreadable. One row: kind, title, one-line excerpt, status. The body waits
   behind a click, the way a People row waits behind its trail. */
.memory-toolbar { flex-wrap: wrap; row-gap: 8px; }
.memory-toolbar #memory-status { flex-shrink: 1; min-width: 0; }
.memory-toolbar .btn .chip-count {
  margin-left: 2px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

.memory-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}
.memory-rows .card { margin: 0; }

/* -- memory: a claim awaiting judgement ----------------------------------- */
/* The state word lives in the chip. A coloured rail on the card was a second
   telling of the same fact, and every colour of it read as decoration. */
.card-memory[data-status="archived"] { opacity: 0.6; }
.card-memory {
  position: relative;
  padding: 8px 12px;
  cursor: pointer;
}
.card-memory.is-open { padding-bottom: 10px; }
.card-memory .card-title {
  font: 600 13.5px/1.35 var(--font-ui);
  letter-spacing: -0.005em;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-memory.is-open .card-title { white-space: normal; }

.memory-face {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr) auto;
  column-gap: 10px;
  align-items: center;
}
.memory-kind-mark {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  color: var(--ink-faint);
}
.memory-kind-mark .ic { font-size: 15px; }
.memory-copy { min-width: 0; }
/* The sentence and the chips take turns for the line. The chips used to
   shrink with the sentence while every chip inside kept its own width, so
   they spilled out of this column and under the status pill: nearly every
   row, and all of them on a narrow window. Now the chips keep their size
   and the sentence gives way down to 12em; below that the chips drop onto
   a line of their own, still inside the column. */
.memory-excerpt {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  min-width: 0;
  min-height: 1.4em;
  margin-top: 1px;
  color: var(--ink-muted);
  font-size: 12px;
  line-height: 1.4;
}
.memory-excerpt-text {
  flex: 1 1 12em;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.memory-excerpt > .memory-src { max-width: 100%; }
/* The last resort, for a column narrower than one chip: the chip shortens
   its own label rather than leave its box. */
.memory-excerpt > .memory-src > * {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.card-memory.is-open .memory-excerpt { display: none; }
.memory-status { flex-shrink: 0; }
.memory-face-flags {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.memory-face-end {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.memory-caret {
  display: grid;
  place-items: center;
  color: var(--ink-faint);
  transition: transform 180ms var(--ease-soft);
}
.memory-caret .ic { font-size: 12px; }
.card-memory.is-open .memory-caret { transform: rotate(180deg); }

/* Archive, Edit and Delete show on hover and on focus. Hidden, they used to
   keep their width: every row held some 200px of nothing between the pill
   and the chevron while its title and excerpt were cut short. Now they take
   no room until they show, and then float in over the end of the text,
   beside the status, on a fade of the card's own surface. Opacity, never
   visibility or display: Tab still reaches them, and focus inside the row
   is what shows them. An open row sets them in line; a promoted row's
   Archive (`.memory-face-kept`) is always in line.

   The surface they float on is as tall as the row, not as tall as a
   button. A band one button high, centred, stopped short of the excerpt
   line's foot, so the chips on that line showed below it and through its
   fade: the site's mark and "used 38×" were drawn under Archive, Edit and
   Delete. And while the actions are up the chips step out, so the actions
   take their place rather than sit on them. Chips run back from the end of
   the line as far as their labels need, often past the fade, and a chip
   the surface reaches is cut in two or seen through it. A sentence that
   runs under the fade just ends. */
.memory-face-end { position: relative; align-self: stretch; }
.memory-face-actions {
  position: absolute;
  top: 0;
  bottom: 0;
  right: calc(100% + 4px);
  align-items: center;
  margin-left: 0;
  padding-left: 28px;
  background: linear-gradient(90deg, transparent, var(--surface) 28px);
  opacity: 0;
  transition: opacity var(--dur-hover) var(--ease-soft);
}
.card-memory:hover .memory-face-actions,
.card-memory:focus-within .memory-face-actions { opacity: 1; }
.memory-excerpt > .memory-src { transition: opacity var(--dur-hover) var(--ease-soft); }
.card-memory:hover .memory-excerpt > .memory-src,
.card-memory:focus-within .memory-excerpt > .memory-src { opacity: 0; }
.card-memory.is-open .memory-face-actions {
  position: static;
  padding-left: 0;
  background: none;
  opacity: 1;
}

.memory-detail { display: none; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--hairline); }
.card-memory.is-open .memory-detail { display: block; }

.kind {
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-body);
  white-space: nowrap;
}
.memory-src {
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12px;
  flex: 0 1 auto;
}
.card-memory .card-meta { margin-top: 10px; gap: 8px; }
.card-memory .card-meta .kind { flex: 0 0 auto; }
.kind[data-kind="writing-style"] { background: var(--tint-blue); color: var(--ink-progress); }
.kind[data-kind="dom-issue"]     { background: var(--tint-red); color: var(--ink-danger); }
.kind[data-kind="strategy"]      { background: var(--tint-amber); color: var(--ink-pending); }
.kind[data-kind="site-recipe"]   { background: var(--tint-green); color: var(--ink-success); }

.memory-body {
  color: var(--ink-muted);
  font-size: 12px;
  line-height: 1.55;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  max-height: 240px;
  overflow-y: auto;
}
/* Clamped by max-height, not -webkit-line-clamp: WebKit's grid track sizing
   measures a line-clamp box as near-zero, freezing every row at title height
   (measured: 150px of content in a 51px row). A hard height plus a fade-out
   mask reads the same and sizes honestly. Shared with the words on a person's
   trail, which readAllToggle() clamps the same way. */
.memory-body.is-clamped, .trail-said.is-clamped {
  max-height: calc(3 * 1.55em);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 55%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 55%, transparent 100%);
}


/* ================================================================== dialog */
/* The one surface that interrupts, so it earns the most care: a darkened,
   blurred world behind it, a panel that settles rather than pops, a header
   with its own weight, fields that light up under the caret, and a footer
   rail that makes the commit action unmistakable. */
.scrim {
  position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: center;
  padding: 24px;
  background: rgba(12, 13, 18, 0.52);
  backdrop-filter: blur(10px) saturate(120%);
  animation: fade 180ms ease-out;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.dialog {
  width: min(540px, 100%);
  max-height: calc(100vh - 96px);
  overflow-y: auto;
  padding: 24px 24px 20px;
  border-radius: var(--radius-dialog);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--shadow-dialog);
  animation: dialog-in 240ms var(--ease-out);
}
@keyframes dialog-in {
  from { opacity: 0; transform: translateY(10px) scale(0.97); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
/* The dialog scrolls its own content, so its scrollbar would otherwise run
   flush along the edge and paint across the rounded corners. A slim inset
   thumb with the track stopped short of both corners keeps the silhouette. */
.dialog::-webkit-scrollbar { width: 10px; }
.dialog::-webkit-scrollbar-track { background: transparent; margin: 18px 0; }
.dialog::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--ink-muted) 32%, transparent);
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 8px;
}
.dialog::-webkit-scrollbar-thumb:hover {
  background-color: color-mix(in srgb, var(--ink-muted) 50%, transparent);
}

/* The title's rule runs edge to edge, out through the dialog's own 24px
   padding, the way the footer's does (`.dialog-actions`). It used to stop
   28px short of the right-hand side to clear the close button, which the
   right padding here does for the words while the rule runs on under it. */
.dialog h2 {
  font: 600 16px/1.3 var(--font-display);
  letter-spacing: -0.01em;
  margin: 0 -24px 4px;
  padding: 0 54px 12px 24px;
  border-bottom: 1px solid var(--hairline);
}
/* The first block after the header breathes; everything else keeps rhythm. */
.dialog h2 + * { margin-top: 14px; }
.dialog > p.muted { margin: 14px 0 6px; font-size: 13.5px; line-height: 1.55; max-width: 56ch; }

/* A playbook keeps its glyph in the dialog, so what you clicked and what opened
   are obviously the same thing. */
.pb-head {
  display: flex; align-items: center; gap: 12px;
  margin: 0 -24px 4px;
  padding: 0 54px 12px 24px;
  border-bottom: 1px solid var(--hairline);
}
.pb-head h2 { margin: 0; padding: 0; border: 0; }
.pb-head + .dialog-lede { margin-top: 12px; }
.pb-head .pb-icon { width: 36px; height: 36px; }
.pb-head .pb-icon .ic { font-size: 18px; }
.dialog-lede {
  margin: 14px 0 18px;
  font-size: 13px; line-height: 1.6;
  max-width: 56ch;
  color: var(--ink-muted);
}
/* Said before the run starts, because after it starts it is four wasted hours. */
.dialog-note {
  margin: 2px 0 14px;
  padding: 10px 12px;
  border-radius: var(--radius-chip);
  background: var(--tint-amber);
  color: var(--ink);
  font-size: 12px; line-height: 1.55;
}
/* The same block with nothing to warn about: a count of what will happen,
   in the surface's own recessed grey. The amber note read as something to
   fix before pressing, and "90 of the 491 can be emailed" is not that. */
.dialog-info {
  margin: 2px 0 14px;
  padding: 10px 12px;
  border-radius: var(--radius-chip);
  background: var(--surface-sunken);
  color: var(--ink-body);
  font-size: 12px; line-height: 1.55;
}
/* The choice over the people with no address (`drawFindChoice`): two rows,
   each with its own line under it, indented past the dot so the line reads
   as the row's and not as a paragraph of the dialog's. */
.unreached-find { margin: 0 0 16px; }
.unreached-find .check-row { margin-top: 0; align-items: flex-start; }
.unreached-find .check-row input { flex: none; margin: 2px 0 0; }
.unreached-find .check-row:has(input:disabled) { color: var(--ink-muted); cursor: default; }
.unreached-find .check-row + .hint { margin: 2px 0 10px 23px; max-width: 56ch; }
.unreached-find .hint:last-child { margin-bottom: 0; }
.dialog-meta {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
/* A setting inside a dialog that is mostly about something else: its own line,
   its own switch, and a rule above it so it reads as a second subject rather
   than a footnote to the first. */
.dialog-split {
  display: flex; align-items: center; gap: 16px;
  margin: 0 0 18px;
  padding-top: 14px;
  border-top: 1px solid var(--hairline);
}
/* Same row layout without the “second subject” rule — used inside Account
   settings cards where a top border + padding read as empty dead space. */
.settings-inline {
  display: flex;
  align-items: center;
  gap: 16px;
}
.dialog-split-text { flex: 1; min-width: 0; }
.dialog-split-title { font-size: 13px; color: var(--ink); }
.dialog-split-sub {
  margin-top: 2px;
  font-size: 12px; line-height: 1.5;
  max-width: 48ch;
  color: var(--ink-muted);
}
/* Never squeezed by the sentence beside it. */
.dialog-split .btn,
.settings-inline .btn { flex: none; }
.field { margin-bottom: 14px; }
.field label {
  display: block;
  margin-bottom: 6px;
}
/* In a dialog a label is a short question or a plain noun ("Write as",
   "What to lead with (optional)"), read as words in sentence case. Shouted
   in capitals, "SEND AS" read as a code rather than a question. */
.dialog .field label {
  text-transform: none;
  letter-spacing: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.field input, .field textarea, .field-input {
  width: 100%;
  padding: 8px 12px;
  border-radius: var(--radius-btn);
  border: 0;
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  outline: none;
  font-size: 13.5px;
  color: var(--ink);
  user-select: text;
  transition: box-shadow var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.field input::placeholder, .field textarea::placeholder,
.field-input::placeholder { color: var(--ink-muted); }
/* Rotating examples (see rotateExamples) cross-fade rather than cutting: the
   swap happens while the text is transparent, so no frame ever shows two
   examples spliced together. Opacity rather than colour so the transition does
   not have to know which token the placeholder is painted with. The global
   prefers-reduced-motion rule flattens this to nothing, and the rotation stops
   itself there too. */
.field input::placeholder, .field textarea::placeholder {
  opacity: 1;
  transition: opacity var(--dur-enter) var(--ease-soft);
}
.field textarea { min-height: 88px; resize: vertical; line-height: 1.55; }
/* The lit field: a crisp accent ring plus a soft halo, so where the caret
   lives is never in question. */
.field input:focus, .field textarea:focus, .field-input:focus {
  background: var(--surface);
  box-shadow: inset 0 0 0 1.5px var(--violet), 0 0 0 3px var(--tint-violet);
}
.select-trigger:focus-visible,
.select-trigger[aria-expanded="true"] {
  background: var(--surface);
  box-shadow: inset 0 0 0 1.5px var(--violet), 0 0 0 3px var(--tint-violet);
  outline: none;
}
.stepper:focus-within {
  box-shadow: inset 0 0 0 1.5px var(--violet), 0 0 0 3px var(--tint-violet);
}
.detail-toggle {
  display: flex; align-items: center; gap: 6px;
  margin: 2px 0 14px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
  border-radius: var(--radius-chip);
  transition: color var(--dur-hover) var(--ease-soft);
}
.detail-toggle:hover { color: var(--ink); }
.detail-toggle .ic { font-size: 12px; transition: transform 240ms var(--ease-soft); }
.detail-toggle.is-open .ic { transform: rotate(180deg); }
/* The summary that doubles as the disclosure's own label: what the controls
   inside it are currently set to. Tabular figures so the sentence does not
   shuffle sideways under the caret while a stepper behind it counts, and the
   left half stays readable when a long profile name makes the line wrap. */
.detail-summary { font-variant-numeric: tabular-nums; text-align: left; }

.hint { font-size: 12px; line-height: 1.5; color: var(--ink-muted); margin-top: 4px; }
/* The one box a custom recurring goal is made of, and the drafted briefing it
   comes back as. Both are read as prose rather than filled in as a field, so
   both get the room to be read in. */
.field textarea.wish { min-height: 104px; }
.field textarea.goal-draft { min-height: 148px; }
/* The model's own account of what it just wrote, and the only line most people
   will read before arming it. Weighted like a lede, tinted like something the
   app is saying rather than something the user typed. */
.draft-summary {
  margin: 14px 0 12px;
  padding: 12px 14px;
  border-radius: var(--radius-chip);
  background: var(--tint-violet);
  color: var(--ink);
  font-size: 13.5px; line-height: 1.55;
  max-width: 56ch;
}
.draft-summary + .dialog-lede { margin-top: 0; }
/* Hour and minute, one control's worth of space. */
.at-time { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-items: center; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; }
.two-up { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* New / Edit memory: one note, not four shouty fields. Scoped to this dialog
   so the memory grid keeps its own voice. */
.dialog.is-memory-note { width: min(520px, 100%); }

/* Edit profile: identity + a short note, then add-when-needed. Quiet rows,
   not a job application. */
/* New profile: the site is the whole form, so it is one generous field with
   its own glyph, lit like every other field when the caret is in it. */
.site-field { position: relative; }
.site-field > .ic {
  position: absolute; left: 12px; top: 50%;
  transform: translateY(-50%);
  font-size: 15px;
  color: var(--ink-muted);
  pointer-events: none;
  transition: color var(--dur-hover) var(--ease-soft);
}
.site-field input {
  width: 100%;
  height: 44px;
  padding: 0 12px 0 36px;
  border: 0;
  border-radius: var(--radius-btn);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  outline: none;
  font-size: 14px;
  color: var(--ink);
  user-select: text;
  transition: box-shadow var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.site-field input::placeholder { color: var(--ink-muted); }
.site-field input:focus {
  background: var(--surface);
  box-shadow: inset 0 0 0 1.5px var(--violet), 0 0 0 3px var(--tint-violet);
}
.site-field:focus-within > .ic { color: var(--violet); }
.site-field.is-invalid input,
.site-field.is-invalid input:focus {
  box-shadow: inset 0 0 0 1.5px var(--danger), 0 0 0 3px var(--tint-red);
}
.site-field.is-invalid > .ic { color: var(--ink-danger); }
.site-field-error { font-size: 12px; line-height: 16px; color: var(--ink-danger); }
.site-field-error:not(:empty) { margin-top: 6px; }
.dialog.is-profile-edit.is-profile-new .dialog-lede { max-width: none; }
.dialog.is-profile-new .detail-toggle { margin: 12px 0 2px; }
.dialog.is-profile-new .dialog-detail { margin-top: 8px; }

.dialog.is-profile-edit { width: min(520px, 100%); }
.dialog.is-profile-edit .dialog-lede {
  margin: 10px 0 14px;
  max-width: 44ch;
}
.dialog.is-profile-edit .identity-row {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr) minmax(6.5rem, 0.7fr);
  gap: 8px;
  margin-bottom: 2px;
}
.dialog.is-profile-edit .identity-row .field,
.dialog.is-profile-edit .field { margin-bottom: 10px; }
.dialog.is-profile-edit textarea.profile-about,
.dialog.is-profile-edit textarea.profile-style,
.dialog.is-profile-edit textarea.profile-compact {
  min-height: 40px;
  max-height: 160px;
  resize: vertical;
}
.profile-opts { display: flex; flex-direction: column; }
.profile-opt {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px 4px;
  align-items: start;
}
.profile-opt .field { margin-bottom: 10px; }
.profile-opt.is-plain { grid-template-columns: minmax(0, 1fr); }
.profile-opt-remove {
  margin-top: 22px;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-chip);
  color: var(--ink-muted);
}
.profile-opt-remove:hover { background: var(--bg-hover); color: var(--ink); }
.profile-opt-remove .ic { font-size: 12px; }
.profile-adds {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 2px 0 12px;
}
.profile-add {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px 3px 7px;
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-muted);
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--hairline);
  border-radius: var(--radius-chip);
}
.profile-add:hover { color: var(--ink); background: var(--bg-hover); }
.card-adds { margin: 12px 0 2px; }
.profile-add .ic { font-size: 11px; }
.link-list { display: flex; flex-direction: column; gap: 6px; }
.link-list-rows { display: flex; flex-direction: column; gap: 6px; }
.link-list-row { display: flex; align-items: center; gap: 6px; }
.link-list-row input { flex: 1; min-width: 0; }
.link-list-remove {
  flex: none;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-chip);
  color: var(--ink-muted);
}
.link-list-remove:hover { background: var(--bg-hover); color: var(--ink); }
.link-list-remove .ic { font-size: 12px; }
.link-list-add {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 2px 2px 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-muted);
  border-radius: var(--radius-chip);
}
.link-list-add:hover { color: var(--ink); }
.link-list-add .ic { font-size: 12px; }

/* ---- Add context: the drop zone and what a profile holds */
.drop-zone {
  display: grid;
  justify-items: center;
  gap: 4px;
  padding: 22px 16px;
  text-align: center;
  color: var(--ink-muted);
  background: var(--surface-sunken);
  box-shadow: inset 0 0 0 1px var(--hairline);
  border-radius: var(--radius-card);
  cursor: pointer;
  transition: box-shadow 120ms ease, background 120ms ease;
}
.drop-zone .ic { font-size: 18px; }
.drop-zone-text { font-size: 13px; font-weight: 500; color: var(--ink); }
.drop-zone-hint { font-size: 12px; }
.drop-zone:hover,
.drop-zone:focus-visible { box-shadow: inset 0 0 0 1px var(--violet); outline: none; }
.drop-zone.is-dropping {
  background: var(--tint-violet);
  box-shadow: inset 0 0 0 1.5px var(--violet), 0 0 0 3px var(--tint-violet);
}
.card-profile.is-dropping {
  box-shadow: inset 0 0 0 1.5px var(--violet), 0 0 0 3px var(--tint-violet);
}
.doc-list { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
.doc-list:empty { margin-top: 0; }
/* One line per row: a mark, the name cut with a fade, the chips, the time.
   The time column holds every clock chip at 92px so they line up down the
   list, and grows for one that is longer: "read 3d ago" is, and at a fixed
   92px it hung out of its column over the "3 pages" chip beside it. */
.doc-row, .link-row {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr) auto auto minmax(92px, max-content) 28px;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  padding: 2px 4px 2px 8px;
  border-radius: var(--radius-chip);
}
.link-row { grid-template-columns: 16px minmax(0, 1fr) auto minmax(92px, max-content); }
.doc-row:hover { background: var(--bg-hover); }
.doc-row > .ic, .link-row > .ic { font-size: 13px; color: var(--ink-muted); }
.doc-name {
  min-width: 0; font-size: 13px; color: var(--ink);
  white-space: nowrap; overflow: hidden;
  mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
}
.doc-note { font-size: 12px; color: var(--ink-muted); white-space: nowrap; }
.doc-note:empty { display: none; }
.row-chips { display: inline-flex; gap: 4px; align-items: center; }
.row-chips:empty { display: none; }
.dt-pill.row-chip { display: inline-block; padding: 4px 11px 4px 9px; line-height: 16px; white-space: nowrap; }
.row-chip .ic { font-size: 11px; vertical-align: -1px; margin-right: 7px; }
.row-time {
  display: inline-block; justify-self: end;
  padding: 4px 12px 4px 10px; border-radius: var(--radius-pill);
  background: var(--surface-sunken); color: var(--ink-muted);
  font-size: 12px; line-height: 16px; white-space: nowrap; font-variant-numeric: tabular-nums;
}
.row-time:empty { background: transparent; }
.row-time .ic { font-size: 11px; vertical-align: -1px; margin-right: 7px; }
.doc-row[data-state="uploading"] .doc-name { color: var(--ink-muted); }
.doc-row[data-state="refused"] .doc-note,
.doc-row[data-state="failed"] .doc-note { color: var(--danger); }
.doc-row[data-state="duplicate"] .doc-note { color: var(--ink-body); }
.doc-remove {
  width: 28px; height: 28px; display: grid; place-items: center;
  border-radius: var(--radius-chip); color: var(--ink-muted);
}
.doc-remove:hover { background: var(--bg-hover); color: var(--ink); }
.doc-remove .ic { font-size: 12px; }
.context-held { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; }
.context-held:empty { margin-top: 0; }
.context-head { font-size: 12px; font-weight: 500; color: var(--ink-muted); margin-top: 10px; }
/* A link on the profile is a name in a list, set like the documents above
   it. `.src-url` alone is the monospace of the profile card's source list. */
.link-row .src-url {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 13px/1.4 var(--font-ui);
}
.link-row[data-read="0"] .src-url { color: var(--ink-muted); }
.learn-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 12px; margin-top: 4px;
  background: var(--surface-sunken); border-radius: var(--radius-card);
}
.learn-text { font-size: 13px; color: var(--ink-body); }
.learn-row .btn { flex: none; }
.dialog.is-context { width: min(560px, 100%); }
/* A tool named in prose: its mark beside its name, the way the shelf and the
   channel switches draw one. Inline, so it sits in a sentence. */
/* inline-block, not inline-flex: a flex chip blockifies its children and a
   sentence read back as text (innerText, a screen reader) breaks around it. */
.tool-mark {
  display: inline-block; vertical-align: -3px;
  padding: 3px 11px 3px 9px;
  border: 1px solid var(--hairline); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--ink);
  font-size: 12px; font-weight: 500; line-height: 16px; white-space: nowrap;
}
.tool-mark img { width: 14px; height: 14px; object-fit: contain; vertical-align: -3px; margin-right: 7px; }
.tool-mark > span { vertical-align: baseline; }
/* In a sentence the mark is a word: its logo and its name, in the
   sentence's own type. As a bordered chip it stood taller than the line, so
   every line holding one sat apart from the rest, and its padding held each
   comma a gap away from the name it follows. */
.tool-mark.is-inline {
  display: inline;
  vertical-align: baseline;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-weight: 500;
  color: var(--ink);
}
.tool-mark.is-inline img { vertical-align: -2px; margin: 0 4px 0 1px; }
.link-row .link-tool,
.doc-row .link-tool,
.channel-switch img { width: 14px; height: 14px; object-fit: contain; flex: none; }
.doc-row .file-mark { width: 16px; height: 16px; object-fit: contain; flex: none; }
/* A mark drawn in black alone (Notion, X, GitHub) would vanish on a dark
   surface. No plate under it: it is inverted there instead, and every
   coloured mark keeps its colours. `data-mono` is set from MONO_MARKS. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme=light]) img[data-mono] { filter: invert(1); }
}
:root[data-theme=dark] img[data-mono] { filter: invert(1); }
/* Indie Hackers' and Cal.com's marks are a near-black tile: on a dark surface
   the tile's edge disappears and only the letters float. A hairline traces it
   there, the mark itself untouched. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme=light]) img[src$="logos/indiehackers.png"] { box-shadow: 0 0 0 1px rgba(255, 255, 255, .28); }
  :root:not([data-theme=light]) img[src$="logos/calcom.png"] { box-shadow: 0 0 0 1px rgba(255, 255, 255, .28); border-radius: 22%; }
}
:root[data-theme=dark] img[src$="logos/indiehackers.png"] { box-shadow: 0 0 0 1px rgba(255, 255, 255, .28); }
:root[data-theme=dark] img[src$="logos/calcom.png"] { box-shadow: 0 0 0 1px rgba(255, 255, 255, .28); border-radius: 22%; }
.tool-mark-bound { white-space: nowrap; }
.row-line .tool-mark { vertical-align: top; line-height: 16px; }
/* The row vocabulary: tokens with room between them, a clock chip at the
   edge, plain words only where a token would say nothing. */
/* Every token on a row is one height, so a chip, a mark and a word all
   share a centre line. `--row-token` is that height: a chip is its text's
   16px line plus 3px of padding either side. Words between the tokens take
   it as their line-height, and a mark inside those words hangs from the top
   of that line rather than from the text baseline (which sat it 3px low). */
.row-line {
  --row-token: 24px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px; min-width: 0;
}
.row-text { font-size: 12px; color: var(--ink-muted); display: inline; line-height: var(--row-token); }
.row-when { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
/* A token keeps its size; only the words between them give way. Without
   this the clock chip at the end of a crowded row was the first thing to be
   squeezed out of it. */
.row-line > .row-chip,
.row-line > .row-time,
.row-line > .tool-mark,
.row-line > .row-when { flex: none; }
/* Words hold their width like the tokens do. Only a span marked long gives
   way, and it is the one that can afford to. */
.row-line > .row-text { flex: none; }
.row-line > .row-text.is-long {
  flex: 0 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mark-img { width: 14px; height: 14px; object-fit: contain; vertical-align: -3px; margin: 0 3px 0 1px; }
.cell-mark { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
.cell-mark .mark-img { margin: 0; }
.memory-src.row-line { white-space: normal; overflow: visible; text-overflow: clip; }
.crawl-status.row-time { margin-left: auto; }
.crawl-status.row-time.is-working { color: var(--ink-pending); }
.route-meta .row-chip { font-size: 11.5px; }
.fold-meta.row-line, .plan-facts.row-line, .account-sub-line.row-line { font-size: inherit; }
.role-queue.row-line { white-space: normal; }
.trail-when.row-time { font-size: 11.5px; }

/* A play's boxes are questions ("Where should people book a call?"), read
   as sentences, with "Optional" as a tag beside them and the help line under
   the box. */
.dialog.is-play .field label,
.schedule-answers .field label {
  display: flex;
  align-items: baseline;
  gap: 8px;
  text-transform: none;
  letter-spacing: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
}
.dialog.is-play .field-optional,
.schedule-answers .field-optional {
  font-size: 11px;
  font-weight: 500;
  color: var(--ink-muted);
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--surface-sunken);
}
.dialog.is-play .field .hint { margin-top: 6px; }
.dialog.is-play .field input, .dialog.is-play .field textarea { min-height: 0; }
.dialog.is-play .field textarea { min-height: 64px; }

.dialog.is-memory-note .field label,
.dialog.is-profile-edit .field label {
  text-transform: none;
  letter-spacing: 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
}
.dialog.is-memory-note .dialog-lede {
  margin: 12px 0 0;
  max-width: 48ch;
}
.memory-note-compose {
  margin: 16px 0 18px;
  padding: 4px 0 6px;
  border-radius: var(--radius-card);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
}
.memory-note-compose:focus-within {
  background: var(--surface);
  box-shadow: inset 0 0 0 1.5px var(--violet), 0 0 0 3px var(--tint-violet);
}
.memory-note-compose .field { margin-bottom: 0; padding: 10px 14px 4px; }
.memory-note-compose .field + .field { padding-top: 0; padding-bottom: 12px; }
.memory-note-compose input,
.memory-note-compose textarea {
  background: transparent;
  box-shadow: none;
  padding: 4px 36px 4px 0;
}
.memory-note-compose input:focus,
.memory-note-compose textarea:focus {
  background: transparent;
  box-shadow: none;
}
.memory-note-title {
  font: 600 16px/1.35 var(--font-display);
  letter-spacing: -0.01em;
}
.memory-note-body { min-height: 96px; }
/* Icon only: the filled mic chip made the title look like a search bar. */
.dialog.is-memory-note .dictation-trigger {
  background: transparent;
  border-color: transparent;
  color: var(--ink-muted);
}
.dialog.is-memory-note .dictation-trigger:hover {
  background: var(--bg-hover);
  color: var(--ink);
}
.dialog.is-memory-note .dictation-trigger[data-state="listening"] {
  background: transparent;
  color: var(--ink-danger);
}
.dialog.is-memory-note .dictation-trigger[data-state="listening"]:hover {
  background: var(--tint-red);
  color: var(--ink-danger);
}
.dialog.is-memory-note .dictation-trigger[data-state="processing"],
.dialog.is-memory-note .dictation-trigger[data-state="processing"]:hover {
  background: transparent;
  color: var(--ink-violet);
}

/* ============================================== crafted form controls */
/* The native <select> and number spinner are the two elements no styling can
   save; these replace them outright. Same keyboard grammar as the native
   ones, drawn in the system's own language. */
.select { position: relative; width: 100%; }
.select-trigger {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 8px 12px;
  border-radius: var(--radius-btn);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  font-size: 13px;
  color: var(--ink);
  text-align: left;
  transition: box-shadow var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.select-trigger:hover { background: var(--surface-2); }
.select-trigger[aria-expanded="true"] {
  background: var(--surface);
  box-shadow: inset 0 0 0 1.5px var(--violet);
}
.select-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.select-trigger .ic {
  font-size: 12px; color: var(--ink-muted);
  transition: transform var(--dur-hover) var(--ease-soft);
}
.select-trigger[aria-expanded="true"] .ic { transform: rotate(180deg); }
.select-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px);
  z-index: 30;
  max-height: 240px;
  overflow-y: auto;
  padding: 4px;
  border-radius: var(--radius-btn);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--shadow-dialog);
  animation: menu-in 120ms var(--ease-out);
}
@keyframes menu-in { from { opacity: 0; transform: translateY(-3px) scale(0.99); } to { opacity: 1; transform: none; } }
/* Flipped by createSelect when the dialog leaves no room below the trigger:
   the dialog is the scroll container, so a downward menu near its bottom edge
   gets clipped into a half-visible list. */
.select-menu.is-up {
  top: auto;
  bottom: calc(100% + 4px);
  animation-name: menu-in-up;
}
@keyframes menu-in-up { from { opacity: 0; transform: translateY(3px) scale(0.99); } to { opacity: 1; transform: none; } }
.select-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border-radius: var(--radius-chip);
  font-size: 13px;
  color: var(--ink-body);
  text-align: left;
}
.select-item:hover, .select-item:focus-visible { background: var(--bg-hover); color: var(--ink); outline: none; }
.select-item.is-selected { color: var(--ink); font-weight: 500; }
/* The small tile a startup is known by away from its card: in the picker's
   rows, and on the People page's filter chips. One rule, because two of these
   is how the picker and the chips end up disagreeing about what Lanes looks
   like. */
.select-art, .chip-art {
  display: grid; place-items: center;
  width: 18px; height: 18px;
  flex: 0 0 18px;
  border-radius: var(--radius-chip);
  background: var(--surface-sunken);
  overflow: hidden;
  font: 600 11px/1 var(--font-ui);
  color: var(--ink-muted);
}
.select-art img, .chip-art img {
  width: 100%; height: 100%; object-fit: contain; background: #fff;
}
.select-art .ic { font-size: 11px; }
.select-trigger-art { display: grid; flex: 0 0 auto; }
.select-trigger-art[hidden] { display: none; }

.select-check {
  display: grid; place-items: center;
  width: 14px; font-size: 12px;
  color: var(--violet);
  visibility: hidden;
}
.select-item.is-selected .select-check { visibility: visible; }
.select-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.stepper {
  display: inline-flex; align-items: stretch;
  border-radius: var(--radius-btn);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  overflow: hidden;
}
.stepper-btn {
  display: grid; place-items: center;
  width: 32px;
  color: var(--ink-muted);
  font-size: 13px;
  transition: background var(--dur-hover) var(--ease-soft), color var(--dur-hover) var(--ease-soft);
}
.stepper-btn:hover { background: var(--bg-hover); color: var(--ink); }
.stepper-btn:active { background: var(--bg-active); }
.stepper-btn:disabled,
.stepper-btn:disabled:hover {
  background: none;
  color: var(--ink-faint);
  pointer-events: none;
}
.stepper-value {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 1px;
  min-width: 56px;
  padding: 8px 6px;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  border-left: 1px solid var(--hairline);
  border-right: 1px solid var(--hairline);
}
/* The digits ride in a clipped track so a nudge can roll one number out and
   the next one in. The track takes its width from the input alone; the
   outgoing ghost is laid over it and never moves the frame mid-roll. */
.stepper-roll {
  position: relative;
  display: inline-grid;
  align-items: center;
  /* Clipped up and down only. `overflow: hidden` would also shave the sides,
     and the outgoing number is often the wider one — 100 stepping down to 95
     would leave through the top with its first digit already cut off. A mask
     three times the track's width hides exactly the vertical exits. */
  -webkit-mask-image: linear-gradient(#000, #000);
  mask-image: linear-gradient(#000, #000);
  -webkit-mask-size: 300% 100%;
  mask-size: 300% 100%;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.stepper-ghost {
  position: absolute;
  inset: 0;
  display: grid; place-items: center;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.stepper-ghost[hidden] { display: none; }
/* The number is typed, not only nudged. The input dresses as plain text and
   sizes itself to its digits; the stepper frame supplies all the chrome.
   Restated at a specificity that beats `.field input`: inside a dialog field
   that rule lends this input padding, and with border-box sizing against a
   width measured in `ch` the padding eats the content box whole and the
   digits disappear. */
.stepper .stepper-input {
  width: auto;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
  font: inherit;
  font-variant-numeric: tabular-nums;
  color: inherit;
  text-align: center;
  outline: none;
}
.stepper .stepper-input:focus { background: none; box-shadow: none; }
.stepper-suffix { color: var(--ink-muted); }
/* Loop: ∞ is the value, not a dash. Size the glyph, not the frame. */
.stepper.is-infinite .stepper-value { padding-block: 3px; }
.stepper.is-infinite .stepper-roll {
  -webkit-mask-image: none;
  mask-image: none;
}
.stepper.is-infinite .stepper-input {
  font-size: 24px;
  font-weight: 700;
  line-height: 1;
}

/* ---- dialog chrome ---- */
.dialog-close {
  position: absolute; top: 16px; right: 16px;
  display: grid; place-items: center;
  width: 24px; height: 24px;
  border-radius: var(--radius-chip);
  color: var(--ink-muted);
  font-size: 13px;
  transition: background var(--dur-hover) var(--ease-soft), color var(--dur-hover) var(--ease-soft);
}
.dialog-close:hover { background: var(--bg-hover); color: var(--ink); }
.dialog { position: relative; }
/* The X comes first in the dialog, so any positioned content after it paints
   over it: What's new's header hid it, and Feedback's body (a dictation host,
   so position: relative) took its clicks. It always sits on top. */
.dialog > .dialog-close { z-index: 2; }
.dialog-actions {
  margin: 16px -24px 0;
  padding: 16px 24px 0;
  border-top: 1px solid var(--hairline);
}

/* ---- button loading: the work is visible in the control that started it */
.btn { position: relative; }
.btn.is-loading:not(.is-worded) { color: transparent !important; }
.btn.is-loading::after {
  content: "";
  position: absolute; inset: 0;
  margin: auto;
  width: 13px; height: 13px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  color: var(--ink-muted);
  animation: spin 600ms linear infinite;
}
.btn.primary.is-loading::after, .btn.danger-solid.is-loading::after { color: rgba(255, 255, 255, 0.85); }
/* A working button is disabled so a second press cannot start a second
   copy of the work, but it is not a button that cannot be pressed:
   `.btn[disabled]` fades to 42%, and faded the spinner with it, the one
   sign the press was heard, to under 2:1 against its button. A plainly
   disabled button keeps its fade. */
.btn.is-loading[disabled] { opacity: 1; }
/* A press that says what it is doing keeps its words, readable, with the
   spinner in front of them instead of over them. Two blank spinners side by
   side on an ask card said nothing about which one was running. */
.btn.is-loading.is-worded::after {
  position: static;
  order: -1;
  flex: none;
  margin: 0;
  width: 10px; height: 10px;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- empty states: a glyph tile, not a bare hole ---- */
.empty-glyph {
  display: grid; place-items: center;
  width: 40px; height: 40px;
  margin: 0 auto 14px;
  border-radius: var(--radius-card);
  background: var(--tint-violet);
  color: var(--ink-violet);
  font-size: 20px;
}

/* ---- kbd chips ---- */
.kbd {
  display: inline-grid; place-items: center;
  min-width: 18px; height: 18px;
  padding: 0 5px;
  border-radius: var(--radius-chip);
  background: var(--surface);
  box-shadow: var(--inset-highlight);
  font: 600 11px/1 var(--font-ui);
  color: var(--ink-muted);
}
.search-wrap .kbd {
  position: absolute; right: 10px; top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
}
/* Drop the hint before it collides with the query. Padding follows, so the
   field does not keep a gutter for a chip that is no longer there. */
@container (max-width: 200px) {
  .kbd { display: none; }
  .search { --search-end-pad: 12px; }
}

/* -- routines -------------------------------------------------------------- */
/* Thin equal rows, same voice as Memory: a mark, a title, a quiet tag,
   text+icon actions. Not hero boxes, not a violet cadence capsule. */
.routine-row {
  padding: 8px 12px;
  margin-bottom: 6px;
}
.routine-row .card-top {
  align-items: center;
  gap: 10px;
}
.routine-mark {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--ink-faint);
}
.routine-mark .ic { font-size: 15px; }
.routine-name {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}
.routine-row .card-title {
  flex: 1;
  min-width: 0;
  font: 600 13.5px/1.35 var(--font-ui);
  letter-spacing: -0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The facts line is a row of tokens: one line when it fits, and when it
   does not, whole tokens move down a line. It used to hold to one line with
   its overflow hidden, which cut the last clock to "last 9m a" and, on a
   narrow window, took the inbox marks and every clock with it. The clocks
   (cadence, next, last) are one group at the end, and wrap as one. */
.routine-row .card-sub.row-line {
  row-gap: 6px;
  margin-top: 6px;
  font-size: 12px;
}
.routine-row .row-when {
  flex-wrap: wrap;
  row-gap: 6px;
  max-width: 100%;
}
/* A clock can carry the last firing's note ("skipped: the previous run is
   still going"), longer than a narrow row. It wraps inside its own chip
   rather than run out of the row. */
.routine-row .row-when > .row-time {
  max-width: 100%;
  white-space: normal;
}
/* The one clause long enough to need two lines on a narrow row takes them,
   rather than an ellipsis. */
.routine-row .row-line > .row-text.is-long {
  min-width: auto;
  white-space: normal;
  overflow: visible;
}
.routine-row .card-actions .btn {
  padding: 3px 10px;
  font-size: 12px;
}
.card-schedule[data-enabled="false"] { opacity: 0.65; }
.section-label-row {
  display: flex; align-items: baseline; justify-content: space-between;
}
.section-add {
  font-size: 12px; font-weight: 600;
  color: var(--ink-muted);
  padding: 2px 8px;
  border-radius: var(--radius-chip);
  transition: color var(--dur-hover) var(--ease-soft), background var(--dur-hover) var(--ease-soft);
}
.section-add:hover { color: var(--ink); background: var(--bg-hover); }
.recurring-hint {
  padding: 12px 16px;
  margin-bottom: 10px;
  border-radius: var(--radius-card);
  background: var(--surface-2);
  font-size: 12px;
  line-height: 1.55;
  color: var(--ink-muted);
  max-width: 72ch;
}

/* -- the funnel: three numbers, two honest rates --------------------------- */
/* The funnel drawn as bars against "reached", so the drop-off is visible
   before any number is read. One hue: it is one measure at three stages,
   not three series. */
.funnel-bars {
  display: flex; flex-direction: column; gap: 10px;
  padding: var(--space-4) var(--space-5);
}
.fbar-row {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) 44px 48px;
  column-gap: 12px;
  align-items: center;
}
.fbar-label { font-size: 12px; font-weight: 500; color: var(--ink-muted); }
.fbar-track {
  height: 14px;
  border-radius: 4px;
  background: var(--surface-sunken);
  overflow: hidden;
}
.fbar-fill {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: var(--grad-violet);
  transition: width 400ms var(--ease-out);
}
.fbar-value {
  font: 700 14px/1.1 var(--font-display);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-align: right;
}
.fbar-rate {
  justify-self: start;
  padding: 0 6px;
  border-radius: var(--radius-pill);
  background: var(--tint-green);
  color: var(--ink-success);
  font-size: 11px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.fbar-rate.is-blank { visibility: hidden; }
/* A rate of nothing is grey, the way a zero is on the scoreboard. Green is
   replies earned, and "0%" in green said a funnel stage succeeded at nothing. */
.fbar-rate[data-empty="1"] { background: var(--surface-sunken); color: var(--ink-muted); }
.funnel-note {
  margin-left: auto;
  font-size: 12px;
  color: var(--ink-pending);
  white-space: nowrap;
}

/* ---- evidence drill-downs: numbers open into names, names into proof ---- */
.proof-link {
  border: 0;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--tint-blue);
  color: var(--ink-progress);
  font-size: 11px; font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: filter var(--dur-hover) var(--ease-soft);
}
.proof-link:hover { filter: brightness(1.12); }
.fbar-row.is-clickable { cursor: pointer; }
.fbar-row.is-clickable:hover .fbar-label { color: var(--ink); }
.fbar-row.is-open .fbar-label { color: var(--ink); }
.funnel-people {
  margin: 2px 0 6px;
  padding: 4px 0 4px 76px;
  display: grid;
  row-gap: 2px;
}
.fperson-row {
  display: flex;
  column-gap: 10px;
  align-items: center;
  padding: 3px 8px 3px 10px;
  border-left: 2px solid var(--hairline);
}
.fperson-row .fperson-detail { flex: 1; min-width: 0; }
.fperson-row .fperson-who { max-width: 40%; flex-shrink: 0; }
.fperson-who {
  border: 0; background: none; padding: 0;
  font-size: 12.5px; font-weight: 600; color: var(--ink);
  text-align: left; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fperson-who:hover { text-decoration: underline; }
.fperson-detail {
  font-size: 12px; color: var(--ink-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  user-select: text;
}
.fperson-when { margin-left: auto; flex: none; }
.fperson-noproof, .trail-noproof {
  font-size: 11px; color: var(--ink-muted); font-style: italic; white-space: nowrap;
}

/* ---- the 14-day reach chart: columns, one hue, tooltip per day ---- */
/* A note carries real content (a count, what a band of playbooks does), so it
   is muted, not faint: --ink-faint is a 0.30 alpha that misses AA on the dark
   canvas. Case and weight, not contrast, keep it under the label. */
.section-label-note {
  margin-left: 8px;
  font-weight: 500;
  color: var(--ink-muted);
  letter-spacing: 0;
  text-transform: none;
}
.chart-card { padding: var(--space-4) var(--space-5) var(--space-3); }
.chart-cols {
  display: flex; align-items: flex-end; gap: 4px;
  height: 92px;
}
.chart-col {
  position: relative;
  flex: 1 1 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  height: 100%;
  border-radius: 4px;
}
.chart-col:hover { background: var(--bg-hover); }
.chart-bar {
  border-radius: 4px 4px 2px 2px;
  background: var(--grad-violet);
  opacity: 0.72;
  min-height: 3px;
  transition: opacity var(--dur-hover) var(--ease-soft);
}
.chart-col:hover .chart-bar, .chart-bar.is-peak { opacity: 1; }
.chart-bar.is-zero {
  height: 3px;
  background: var(--surface-sunken);
  opacity: 1;
}
/* Per-day tooltip, pure CSS: the value and the date, above the column. */
.chart-col::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(2px);
  padding: 3px 8px;
  border-radius: var(--radius-chip);
  background: var(--ink);
  color: var(--canvas-cool);
  font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-hover) var(--ease-soft),
              transform var(--dur-hover) var(--ease-soft);
  z-index: 5;
}
.chart-col:hover::after { opacity: 1; transform: translateX(-50%) translateY(0); }
.chart-x {
  position: absolute;
  top: calc(100% + 4px);
  font-size: 10.5px;
  color: var(--ink-faint);
  white-space: nowrap;
}
.chart-col:first-child .chart-x { left: 0; }
.chart-col:last-child .chart-x { right: 0; }
.chart-cols { margin-bottom: 18px; } /* room for the x labels below */

/* ---- overview inbox: the asks, as a pile you can open, not a strip ---- */
.asks-inbox {
  padding: var(--space-4) var(--space-5);
  margin-bottom: 22px;
}
.asks-inbox-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}
.asks-inbox-kicker {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-pending);
}
.asks-inbox-kicker .ic { width: 14px; height: 14px; }
.asks-inbox-count {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.ask-row {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  padding: 10px 8px;
  margin: 0 -8px;
  border-radius: var(--radius-btn);
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-soft);
}
.ask-row + .ask-row { border-top: 1px solid var(--hairline); }
.ask-row:hover { background: var(--surface-sunken); }
.ask-row .need-logo { width: 36px; height: 36px; }
.ask-row-copy {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.ask-row-action {
  font-size: 14px;
  font-weight: 650;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ask-row-title {
  font-size: 12.5px;
  color: var(--ink-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ask-row-go {
  flex: none;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink-violet);
  white-space: nowrap;
}

/* ---- work waiting on the owner: a banner, because it is a to-do ---- */
.attention {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  margin-bottom: 12px;
  padding: 11px 14px;
  border-radius: var(--radius-btn);
  background: var(--tint-amber);
  color: var(--ink-pending);
  font-size: 13.5px; font-weight: 600;
  text-align: left;
  box-shadow: var(--shadow-press);
  transition: filter var(--dur-hover) var(--ease-soft);
}
.attention:hover { filter: brightness(1.06); }
.attention .ic { font-size: 15px; flex: 0 0 auto; }
.attention-go { margin-left: auto; font-weight: 500; opacity: 0.85; }
/* Damage in progress, not a to-do: red, because the sending domain is burning
   while this is on screen. */
.attention.is-bad { background: var(--tint-red); color: var(--ink-danger); }

/* -- openers: which first messages earn replies ---------------------------- */
.subject-rows { display: flex; flex-direction: column; }
/* Every track but the opener's own is fixed. Each row is a grid of its own,
   so a count sized to its text ("0 of 3" beside "4 of 22") pushed the bar and
   the rate sideways row by row, and the column stopped being one. */
.subject-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 88px 52px 124px;
  column-gap: 14px;
  align-items: center;
  padding: 8px 4px;
  border-radius: var(--radius-btn);
}
.subject-row.is-clickable { cursor: pointer; }
.subject-row.is-clickable:hover, .subject-row.is-open { background: var(--bg-hover); }
.subject-bar {
  height: 6px;
  border-radius: 3px;
  background: var(--surface-sunken);
  overflow: hidden;
}
/* Under the minimum there is no rate, so there is no bar either. */
.subject-bar.is-blank { visibility: hidden; }
.subject-bar-fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--grad-violet);
}
.subject-row + .subject-row,
.subject-rows .funnel-people + .subject-row { border-top: 1px solid var(--hairline); }
.subject-text {
  min-width: 0;
  font-size: 13.5px; font-weight: 500; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  user-select: text;
}
.subject-rate {
  font-size: 13px; font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink-success);
  text-align: right;
}
/* Nothing earned: grey, not green. See `.fbar-rate[data-empty]`. */
.subject-rate[data-empty="1"] { color: var(--ink-muted); }
.subject-n {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  text-align: right;
  white-space: nowrap;
}
/* The names behind a row, under the opener rather than past the funnel's
   stage labels, which is where `.funnel-people` indents to by default. */
.subject-rows .funnel-people { padding-left: 12px; }
.openers-early, .openers-foot {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-muted);
}
.openers-early { padding: 2px 4px 8px; }
.openers-foot { padding: 8px 4px 0; }

/* ================================================================== tables */
/* People and Companies. The only real <table> in the app, and it is a real one
   because this is genuinely tabular: a screen reader needs the column a cell
   belongs to, and a grid of divs cannot say. Everywhere else in here a "table"
   is a list of rows, which is what those are.
 *
 * Three things carry the design: the header sticks so the columns survive a
 * long scroll, the whole thing scrolls sideways inside its own box so a narrow
 * window never scrolls the page, and every number is tabular-nums so a column
 * of counts lines up. */
.dt-wrap {
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-card);
  overflow: hidden;
}
.dt-scroll { overflow-x: auto; overscroll-behavior-x: contain; }
.dt {
  width: 100%;
  min-width: 760px;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 13.5px;
}
.dt thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
  padding: 10px 12px;
  text-align: left;
  font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-muted);
  white-space: nowrap;
  border-bottom: 1px solid var(--hairline);
}
/* The sort control is the header cell, so the hit target is the whole label
   rather than a caret somebody has to aim at. */
.dt thead th.is-sortable { padding: 0; }
.dt-sort {
  display: flex; align-items: center; gap: 6px;
  width: 100%;
  padding: 10px 12px;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  background: none; border: 0; cursor: pointer;
  transition: color var(--dur-hover) var(--ease-soft);
}
.dt-sort:hover { color: var(--ink); }
/* The button fills its cell, the table's scroller clips the head's top edge,
   and a capped cell (Title) clips its content on every side, so a ring drawn
   outside the button lost its top everywhere and all of itself on Title. */
.dt-sort:focus-visible { outline-offset: -2px; }
.dt-sort .ic { font-size: 12px; opacity: 0; transition: opacity var(--dur-hover) var(--ease-soft); }
.dt-sort:hover .ic { opacity: 0.5; }
.dt thead th.is-sorted .dt-sort { color: var(--violet); }
.dt thead th.is-sorted .dt-sort .ic { opacity: 1; }
.dt thead th.is-sorted[data-dir="asc"] .dt-sort .ic { transform: rotate(180deg); }

.dt tbody td {
  padding: 9px 12px;
  border-bottom: 1px solid var(--hairline);
  color: var(--ink-body);
  vertical-align: middle;
  white-space: nowrap;
}
.dt tbody tr:last-child td { border-bottom: 0; }
.dt tbody tr { transition: background var(--dur-hover) var(--ease-soft); }
.dt tbody tr:hover { background: var(--bg-hover); }
.dt tbody tr.is-selected { background: var(--tint-violet); }
.dt tbody tr.is-open { background: var(--bg-hover); }
.dt tbody tr.is-clickable:focus-visible { outline: 2px solid var(--violet); outline-offset: -2px; }
/* Suppressed reads as struck through, once, on the name. Greying the whole row
   would make it unreadable rather than closed. */
.dt tbody tr.is-suppressed .dt-name { color: var(--ink-muted); text-decoration: line-through; }

/* The checkbox column. Fixed width so the name column starts in the same place
   on both tables. */
.dt .dt-pick { width: 40px; padding-right: 0; }
/* No UA margin: Chromium gives a checkbox 3px top and bottom, which centred
   the margin box and left the box itself riding high of the name beside it. */
.dt .dt-pick input { width: 15px; height: 15px; margin: 0; vertical-align: middle; accent-color: var(--violet); cursor: pointer; }
.dt thead .dt-pick { padding: 10px 0 10px 12px; }

/* Name and company cells: an avatar or a logo, then text that may truncate. */
.dt-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* Capped like title and company, or one long name widens the whole table
   past the window and pushes Status and Fit off every row. */
.dt-name {
  font-weight: 600; color: var(--ink);
  max-width: 240px;
  overflow: hidden; text-overflow: ellipsis;
  user-select: text;
}
.dt-avatar {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  background: var(--tint-violet);
  font: 600 11px/1 var(--font-ui); color: var(--ink-violet);
}
.dt-avatar img { width: 100%; height: 100%; object-fit: cover; }

.dt-link { color: var(--ink-progress); text-decoration: none; user-select: text; }
.dt-link:hover { text-decoration: underline; }
.dt-mono { font-variant-numeric: tabular-nums; }
.dt-num { text-align: right; font-variant-numeric: tabular-nums; }
.dt-faint { color: var(--ink-faint); }
/* A cell that holds text long enough to need a ceiling. */
.dt-clip { max-width: 220px; overflow: hidden; text-overflow: ellipsis; }
.dt-tag {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-chip);
  background: var(--surface-sunken);
  color: var(--ink-body);
  font-size: 12px; font-weight: 500;
  max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  vertical-align: middle;
}
.dt-pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.dt-pill[data-hue="neutral"] { background: var(--surface-sunken); color: var(--ink-body); }
.dt-pill[data-hue="blue"] { background: var(--tint-blue); color: var(--ink-progress); }
.dt-pill[data-hue="violet"] { background: var(--tint-violet); color: var(--ink-violet); }
.dt-pill[data-hue="green"] { background: var(--tint-green); color: var(--ink-success); }
.dt-pill[data-hue="amber"] { background: var(--tint-amber); color: var(--ink-pending); }
.dt-pill[data-hue="red"] { background: var(--tint-red); color: var(--ink-danger); }

/* How much an address is actually worth, as a dot beside it. An inferred
   address rendered identically to a confirmed one is the interface lying to
   the person about to send to it. */
.dt-email { display: inline-flex; align-items: center; gap: 6px; }
.dt-email .dt-link { max-width: 240px; overflow: hidden; text-overflow: ellipsis; }
.dt-dot {
  flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: var(--ink-faint);
}
.dt-dot[data-rank="verified"] { background: var(--success); }
.dt-dot[data-rank="likely"] { background: var(--pending); }
.dt-dot[data-rank="guess"] { background: var(--ink-faint); }

/* -- selection ------------------------------------------------------------- */
/* Appears only when something is selected: a permanently visible bar of
   disabled buttons is furniture, and this one sends email. */
.bulkbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 var(--gutter) 8px;
  padding: 8px 12px;
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-rest);
  animation: bulk-in var(--dur-hover) var(--ease-out);
}
.bulkbar[hidden] { display: none; }
.bulk-count { font-size: 13px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.bulk-note { font-size: 12px; color: var(--ink-muted); }
.bulk-spacer { flex: 1; }
.bulkbar .btn { padding: 5px 12px; font-size: 12.5px; }
@keyframes bulk-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* -- paging ---------------------------------------------------------------- */
.pager {
  display: flex; align-items: center; gap: 10px;
  padding: 8px var(--gutter) 16px;
  color: var(--ink-muted); font-size: 12.5px;
}
.pager[hidden] { display: none; }
.pager-at { font-variant-numeric: tabular-nums; }
.pager-spacer { flex: 1; }
.pager .btn { padding: 4px 10px; }
.pager .btn .ic { font-size: 13px; }

/* -- popovers -------------------------------------------------------------- */
/* Filter and Sort open one of these under their button. Not a dialog: these
   are adjustments to what is on screen, and a modal over the thing being
   adjusted hides the answer. Same species as the select's menu, so it shares
   that menu's corner, entrance, and item metrics rather than the dialog's. */
.popover {
  position: fixed;
  z-index: 60;
  min-width: 208px;
  max-width: 280px;
  padding: 6px;
  border-radius: var(--radius-btn);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--inset-floor), var(--shadow-dialog);
  animation: menu-in 120ms var(--ease-out);
}
.popover.is-up { animation-name: menu-in-up; }
.popover-label {
  padding: 8px 10px 4px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-muted);
}
.popover-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 6px 10px;
  border-radius: var(--radius-chip);
  font-size: 13px; color: var(--ink-body); text-align: left;
  background: none; border: 0; cursor: pointer;
  transition: background var(--dur-hover) var(--ease-soft);
}
.popover-item:hover { background: var(--bg-hover); }
.popover-item:disabled { opacity: 0.48; cursor: default; }
.popover-item:disabled:hover { background: none; }
.popover-item.is-selected { color: var(--ink); font-weight: 500; }
.popover-item .ic { font-size: 14px; color: var(--violet); opacity: 0; }
.popover-item.is-selected .ic { opacity: 1; }
.popover-item.has-mark > .ic { display: none; }
.browser-mark {
  width: 24px; height: 24px; flex: none; object-fit: contain;
}
.popover-item.has-mark { gap: 10px; }
.popover-item .popover-count {
  margin-left: auto;
  font-size: 12px; color: var(--ink-muted); font-variant-numeric: tabular-nums;
}
.popover-sep { height: 1px; margin: 4px 6px; background: var(--hairline); }

/* The window's own hover note (tooltip() in main.js), placed against the
   viewport where a native title= let the system run off the window's edge.
   The chip the charts' day tips wear, allowed to wrap: a memory's note can
   be its whole stored title. Never a target, so it cannot take the hover
   from what it describes. No transition, not even the 0.01ms the
   reduced-motion rule gives everything: with one, the note's first frame
   is drawn where it was last placed, and then it jumps. */
.tooltip {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 70;
  width: max-content;
  max-width: min(360px, calc(100vw - 16px));
  padding: 4px 8px;
  border-radius: var(--radius-chip);
  background: var(--ink);
  color: var(--canvas-cool);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.4;
  overflow-wrap: anywhere;
  pointer-events: none;
  transition: none;
}

/* -- contacts: the ledger ---------------------------------------------------
   The rows are the generic data table (`.dt`), built by dataTable(); only the
   trail under an open contact is bespoke -- what happened, when, and the
   proof. */
.contact-trail {
  padding: 4px 10px 8px 22px;
  border-top: 1px solid var(--hairline);
  display: grid;
  row-gap: 2px;
}
.trail-row {
  display: flex;
  column-gap: 10px;
  align-items: center;
  padding: 3px 0 3px 10px;
  border-left: 2px solid var(--hairline);
}
.trail-row .trail-kind { flex: 0 0 110px; }
.trail-row .trail-note { flex: 1; min-width: 0; }
.trail-row .proof-link, .trail-row .trail-noproof { margin-left: auto; }
.trail-kind {
  font-size: 11.5px; font-weight: 700; color: var(--ink-body); white-space: nowrap;
}
.trail-row[data-kind="booked"] .trail-kind { color: var(--ink-success); }
.trail-row[data-kind="replied"] .trail-kind { color: var(--ink-violet); }
.trail-row[data-kind="bounced"] .trail-kind,
.trail-row[data-kind="unsubscribed"] .trail-kind,
.trail-row[data-kind="do-not-contact"] .trail-kind { color: var(--ink-danger); }
.trail-when {
  font-size: 11.5px; color: var(--ink-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.dt-pill.trail-source { flex: none; }
.trail-note {
  font-size: 12px; color: var(--ink-body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  user-select: text;
}
.trail-row[data-kind="briefed"] .trail-kind { color: var(--ink-violet); }
/* The brief itself, under its row: the one trail entry that is prose the owner
   reads before a call, so it wraps and keeps its lines instead of ellipsising. */
.trail-brief {
  font-size: 12px; line-height: 1.45; color: var(--ink-body);
  white-space: pre-wrap; user-select: text;
  margin: 0 0 4px; padding: 4px 10px 6px 10px;
  border-left: 2px solid var(--ink-violet);
}
.trail-empty { font-size: 12px; color: var(--ink-muted); padding: 6px 0 4px 10px; }
/* The message a drafting run wrote for this person: the words as they would
   go, line breaks kept, selectable, under the row that says where it stands. */
.trail-draft .trail-row[data-kind="draft"] .trail-kind { color: var(--ink-violet); }
.trail-draft-words {
  margin: 2px 0 4px 12px;
  padding: 8px 10px;
  font-size: 12px; line-height: 1.5; color: var(--ink-body);
  white-space: pre-wrap; overflow-wrap: anywhere; user-select: text;
  background: var(--surface-2);
  border-left: 2px solid var(--hairline);
  border-radius: 0 var(--radius-chip) var(--radius-chip) 0;
  max-width: 640px;
}
/* What was said, under the "message sent" row it belongs to: the email as it
   went, line breaks kept, selectable, one reading-width column rather than a
   line stretched across the table. */
.trail-message {
  margin: 0 0 4px;
  padding: 4px 0 4px 10px;
  border-left: 2px solid var(--hairline);
}
.trail-said {
  max-width: 62ch;
  padding: 6px 10px;
  border-radius: 6px;
  background: var(--surface-2);
  font-size: 12px; line-height: 1.55; color: var(--ink-body);
  white-space: pre-wrap; overflow-wrap: anywhere; user-select: text;
}
/* Five lines folded, not the memory card's three: an email opens with a
   greeting and a blank line, and three lines of one is "Hi Samira," and a fade.
   The padding rides inside the height (border-box), so it is added back. */
.trail-said.is-clamped { max-height: calc(5 * 1.55em + 12px); }
.trail-message .btn { margin-top: 4px; padding: 2px 8px; font-size: 12px; }
/* What the owner can do to this one person, under the facts and above the
   trail: which startup they are filed under, and the two doors out of it. */
.trail-actions {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 0 6px 10px;
}
/* Left, with the facts, never pushed to the far edge: the table scrolls
   sideways, and a button at the right edge is a button off the screen. */
.trail-group {
  display: flex; align-items: center; gap: 6px;
  margin-right: 8px;
  font-size: 12px; color: var(--ink-body);
}
.trail-group .ic { font-size: 13px; color: var(--ink-muted); }
.trail-actions .btn { padding: 4px 10px; font-size: 12px; }
.chip-count {
  margin-left: 5px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

/* -- the command palette ---------------------------------------------------- */
/* Three bands under one lid: the field, the grouped list, the keyboard legend.
   The lid is the dialog surface, so the palette inherits the app's one
   interrupting shape rather than inventing a second. */
.dialog.is-palette {
  --palette-row: 34px;
  padding: 0;
  width: min(560px, 100%);
  align-self: start;
  margin-top: 12vh;
  overflow: hidden;
  display: flex; flex-direction: column;
}

.palette-field {
  display: flex; align-items: center; gap: 10px;
  padding: 0 14px;
  height: 48px;
  border-bottom: 1px solid var(--hairline);
  flex-wrap: nowrap;
  container-type: inline-size;
}
.palette-search {
  display: grid; place-items: center;
  flex: 0 0 auto;
  color: var(--ink-faint);
}
.palette-search .ic { font-size: 15px; }
.palette-input {
  flex: 1; min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  font: 400 15px/1.2 var(--font-ui);
  letter-spacing: -0.01em;
  color: var(--ink);
}
.palette-input::placeholder { color: var(--ink-faint); }
/* The badge is a reminder of the key you just pressed, not a control. */
.palette-field .kbd { flex: 0 0 auto; color: var(--ink-faint); }

/* The masks say "there is more this way" without a scrollbar having to. Each
   exists only while there is actually more in that direction. */
.palette-body { position: relative; min-height: 0; }
.palette-body.is-scrolled::before {
  content: "";
  position: absolute; left: 0; right: 0; top: 0;
  height: 22px;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to bottom,
    var(--surface) 15%, color-mix(in srgb, var(--surface) 55%, transparent) 65%, transparent);
}
.palette-body.is-scrollable::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 34px;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to top,
    var(--surface) 20%, color-mix(in srgb, var(--surface) 55%, transparent) 65%, transparent);
}
.palette-list {
  /* Deliberately half a row short of the fold. A list that ends flush on a row
     boundary reads as the end of the list; a row dissolving under the fade
     reads as "keep going", which is the truth. */
  max-height: calc(var(--palette-row) * 9 + 4px);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Keep the selected row clear of the fade at the bottom edge: arrowing onto
     a row that then dissolves is worse than no fade at all. */
  scroll-padding: 8px 0 34px;
  padding: 6px;
}
/* The dialog's scrollbar rules key off `.dialog`; the list scrolls on its own,
   so it needs the same quiet thumb rather than the platform default. */
.palette-list::-webkit-scrollbar { width: 10px; }
.palette-list::-webkit-scrollbar-track { background: transparent; }
.palette-list::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--ink-muted) 26%, transparent);
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 8px;
}
.palette-list::-webkit-scrollbar-thumb:hover {
  background-color: color-mix(in srgb, var(--ink-muted) 44%, transparent);
}

.palette-section {
  padding: 10px 10px 4px;
  font: 600 10.5px/1 var(--font-ui);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.palette-section:first-child { padding-top: 4px; }

.palette-row {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  height: var(--palette-row);
  padding: 0 8px 0 10px;
  border-radius: var(--radius-btn);
  font-size: 13.5px;
  color: var(--ink-body);
  text-align: left;
  /* Colour only. Animating the bed's geometry lags a held arrow key. */
  transition: background var(--dur-press) linear, color var(--dur-press) linear;
}
.palette-row.is-active {
  background: var(--bg-active);
  color: var(--ink);
}
/* The accent is a 2px edge, not a wash: the row stays legible and the eye is
   pointed at where it is rather than repainted. */
.palette-row.is-active::before {
  content: "";
  position: absolute;
  left: 0; top: 50%;
  transform: translateY(-50%);
  width: 2px; height: 18px;
  border-radius: 0 1px 1px 0;
  background: var(--violet);
}
.palette-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Why this row matched. Weight and ink, no highlighter bar. */
.palette-label .hit { font-weight: 600; color: var(--ink); }
.palette-row.is-active .palette-label .hit { color: var(--ink-violet); }

/* Icon tiles carry the section's hue, so a destination and an action are told
   apart before either label is read. */
.palette-art {
  display: grid; place-items: center;
  width: 20px; height: 20px;
  flex: 0 0 20px;
  border-radius: var(--radius-chip);
  background: var(--surface-2);
  color: var(--ink-muted);
}
.palette-art .ic { font-size: 12px; }
.palette-row[data-tone="violet"] .palette-art { background: var(--tint-violet); color: var(--ink-violet); }
.palette-row[data-tone="green"]  .palette-art { background: var(--tint-green);  color: var(--ink-success); }
.palette-row[data-tone="amber"]  .palette-art { background: var(--tint-amber);  color: var(--ink-pending); }

/* The return glyph appears on the row Enter would actually run. */
.palette-enter {
  flex: 0 0 auto;
  width: 14px;
  font: 400 12px/1 var(--font-ui);
  color: var(--ink-faint);
  opacity: 0;
  transition: opacity var(--dur-press) linear;
}
.palette-row.is-active .palette-enter { opacity: 1; color: var(--ink-violet); }
.palette-row .kbd { flex: 0 0 auto; }

.palette-empty {
  display: grid; justify-items: center; gap: 2px;
  padding: 28px 24px 32px;
  text-align: center;
  color: var(--ink-muted);
}
.palette-empty .ic { font-size: 20px; color: var(--ink-faint); margin-bottom: 8px; }
.palette-empty .empty-line { font-size: 13px; color: var(--ink-body); }
.palette-empty .empty-hint { font-size: 12px; color: var(--ink-faint); }

/* The grammar of the thing, stated once, where a beginner will find it and a
   regular will stop seeing it. */
.palette-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 0 12px;
  height: 34px;
  border-top: 1px solid var(--hairline);
  background: var(--canvas-cool);
  font-size: 11.5px;
  color: var(--ink-faint);
}
.palette-legend {
  display: flex; align-items: center; gap: 12px;
  transition: opacity var(--dur-hover) var(--ease-soft);
}
.palette-foot.is-idle .palette-legend { opacity: 0.4; }
.legend-item { display: flex; align-items: center; gap: 4px; }
.legend-what { color: var(--ink-faint); }
.palette-foot .kbd { min-width: 16px; height: 16px; font-size: 10px; }
.palette-count { font-variant-numeric: tabular-nums; }

/* One reveal on open: rows arrive in the order they are read. Retriggering
   this per keystroke would strobe, so only the first render sets --i. */
.palette-list.is-entering .palette-row {
  animation: palette-row-in 220ms var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 14ms);
}
@keyframes palette-row-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .palette-list.is-entering .palette-row { animation: none; }
}

/* The scrim centers dialogs; the palette hangs high like Spotlight. */
.scrim:has(.is-palette) { align-items: start; }

/* ================================================================== toasts */
.toasts {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  /* Above the lock (90) as well as the scrim. A sign-in that fails says why in
     a toast, and a toast behind the thing it is explaining is no message. */
  z-index: 120;
  display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: 8px;
  max-width: min(480px, calc(100vw - 48px));
  padding: 10px 16px;
  border-radius: var(--radius-btn);
  background: var(--grad-dark);
  color: #fff;
  font-size: 13px;
  font-weight: 500;
  box-shadow: var(--inset-cta-top), var(--shadow-toast);
  animation: toast-in var(--dur-hover) var(--ease-soft);
  transition: opacity var(--dur-hover), transform var(--dur-hover) var(--ease-soft);
}
/* One slab, three tones: the hue is a ring and an icon, not a fill. A toast
   asking to be signed in and a toast reporting a dead agent used to be the
   same red rectangle, which taught the eye to read both as neither. */
.toast.is-warn { box-shadow: var(--inset-cta-top), var(--ring-toast-warn), var(--shadow-toast); }
.toast.is-bad { box-shadow: var(--inset-cta-top), var(--ring-toast-bad), var(--shadow-toast); }
.toast.is-leaving { opacity: 0; transform: translateY(6px); }
@keyframes toast-in { from { opacity: 0; transform: translateY(10px) scale(0.98); } to { opacity: 1; transform: none; } }
.toast .ic { font-size: 14px; flex: 0 0 auto; color: var(--toast-icon-ok); }
.toast.is-warn .ic { color: var(--toast-icon-warn); }
.toast.is-bad .ic { color: var(--toast-icon-bad); }
.toast-text { min-width: 0; overflow-wrap: anywhere; }
/* The stack ignores the pointer so a toast never eats a click meant for the
   page under it; a toast with a button has to take clicks back. */
.toast.has-action { pointer-events: auto; }
.toast-action {
  flex: none;
  margin-left: 4px;
  color: #fff;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ============================================================== scrollbars */
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-thumb {
  background: rgba(128, 140, 160, 0.34);
  border-radius: var(--radius-pill);
  border: 3.5px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(128, 140, 160, 0.56); background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }

::selection { background: var(--tint-violet); }

/* ========================================================= narrow windows */
/* The window is resizable and people do make it narrow. Nothing may overflow. */
@media (max-width: 720px) {
  :root { --gutter: 16px; --sidebar-w: 216px; }
  .stats { gap: 8px; flex-wrap: wrap; }
  .profile-facts-run { white-space: normal; }
  .two-up,
  .dialog.is-profile-edit .identity-row { grid-template-columns: 1fr; }
  .item { grid-template-columns: 16px minmax(0, 1fr) auto; }
  .item .item-kind { display: none; }
  .source { grid-template-columns: 60px minmax(0, 1fr); }
  .source .src-count { grid-column: 2; text-align: right; }

  /* The log's icon + title still fit; the clock gives way first. */
  .row { grid-template-columns: 18px minmax(0, 1fr) auto; column-gap: 8px; }
  .row-time { font-size: 10px; }
  .row-title, .verb {
    flex-shrink: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .acts-toggle { max-width: 100%; min-width: 0; }
  .acts-count { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

  /* Every row of controls wraps rather than running off the edge. A button
     half outside its card is the loudest possible "unfinished".
     Wrapping alone was not enough: these rows are flex-shrink:0 at full width
     so the buttons keep their shape, and a rigid child cannot wrap out of an
     overflow. They have to be allowed to give way first. */
  .head { flex-wrap: wrap; }
  .controls, .filters, .chips, .card-top, .card-actions, .card-meta { flex-wrap: wrap; }
  .controls, .chips, .card-actions { flex-shrink: 1; min-width: 0; }

  /* The tables keep every column and scroll sideways inside their own box.
     Hiding columns at width would be quietly deciding which of somebody's data
     matters; a horizontal scrollbar decides nothing. */
  .bulkbar, .pager { flex-wrap: wrap; }
  .bulk-spacer, .pager-spacer { flex-basis: 100%; height: 0; }
  .dt-clip { max-width: 150px; }
  .card-heading { flex-basis: 100%; }
  .card-top .card-actions { margin-left: 0; }
  .search-wrap { flex-basis: 100%; }
  /* Kind chips wrap here rather than scroll, so the spacer that right-aligns
     the sort group would strand it alone against the far edge while every
     other row is flush left. Drop it and let sort follow the chips. */
  .filters-spacer { display: none; }
  .memory-face { grid-template-columns: 28px minmax(0, 1fr); }
  .memory-face-end {
    grid-column: 2;
    justify-content: flex-start;
    flex-wrap: wrap;
  }
  /* The end has a line of its own here, with room to spare: the actions
     wait in line in it rather than float over the text, and the chips,
     which nothing covers, stay where they are. */
  .memory-face-actions {
    position: static;
    padding-left: 0;
    background: none;
  }
  .card-memory:hover .memory-excerpt > .memory-src,
  .card-memory:focus-within .memory-excerpt > .memory-src { opacity: 1; }
  /* The road's figures row already wraps; what it must not do is put the
     arrival time hard against the count. */
  .route-eta { margin-left: 0; }
}

/* Motion is for orientation, never decoration: if it is not wanted, none of it
   is load-bearing. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* -- first run: the one-field welcome -------------------------------------- */
.gate-panel .onboard-row {
  display: flex; gap: 8px;
  margin-top: 16px;
  width: min(360px, 100%);
}

/* -- the lock -------------------------------------------------------------- */
/* The landing page's hero, rebuilt at window scale. Somebody arrives here
   straight off the site, often within the same minute, so this screen keeps
   the site's night sky, its ASCII terrain, its Instrument Serif headline and
   its solid/glass button pair. It is the one surface the two share.
   Deliberately dark in both themes for that reason: it is the product's front
   door, not a panel inside the app. */
.gate {
  position: fixed; inset: 0;
  /* Above the scrim, so a dialog left open cannot land on top of it; below
     toasts, so an error about why sign-in failed is still readable. */
  z-index: 90;
  display: grid; place-items: center;
  overflow: auto;
  color: #fff;
  background:
    radial-gradient(120% 80% at 50% 8%, #1a1b3d 0%, #101127 42%, #08080f 100%);
}
/* The terrain sits under everything, anchored to the bottom the way it is on
   the site. `contain` rather than `cover` so the glyphs stay square: stretched
   ASCII stops reading as characters and starts reading as noise. */
.gate::after {
  content: "";
  position: absolute; inset: auto 0 0 0;
  height: min(38%, 340px);
  background: url("art/hero-drive.png") no-repeat bottom center;
  background-size: cover;
  /* Fades into the sky instead of ending on a hard edge. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 38%, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 38%, #000 100%);
  opacity: 0.85;
  pointer-events: none;
}
.gate-inner {
  position: relative; z-index: 1;   /* over the terrain */
  width: min(560px, calc(100vw - 48px));
  /* Asymmetric on purpose: the extra bottom padding is what lifts the centred
     panel off the horizon, so every control sits against open sky. */
  padding: 32px 0 132px;
}
.gate-inner:focus { outline: none; }
.gate-panel { display: flex; flex-direction: column; align-items: center; text-align: center; }

/* -- hero copy, straight from the site ------------------------------------- */
.gate-head { display: flex; flex-direction: column; align-items: center; }
/* Not the app's h1, which clips to one line: clipped, this line's glyphs
   (taller than its tight line) were cut at top and bottom, and the shadow
   stopped dead at the box and drew a dark rectangle behind the words. */
.gate-title {
  margin: 0;
  max-width: 16ch;
  font: 400 clamp(38px, 7vw, 60px)/1.02 "Instrument Serif", Georgia, serif;
  letter-spacing: -0.015em;
  color: #fff;
  overflow: visible;
  white-space: normal;
  text-wrap: balance;
  text-shadow: 0 2px 40px rgba(10, 10, 30, 0.55);
}
/* The site's accent: the phrase that matters takes the gradient. */
.gate-title em {
  font-style: normal;
  background: linear-gradient(115deg, #cdd3ff 0%, #a9b2f5 45%, #ffc9a1 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.gate-sub {
  margin: 20px auto 0;
  max-width: 46ch;
  font-size: 15px;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.72);
  text-wrap: pretty;
}

/* -- the site's button pair ------------------------------------------------ */
.btn-hero {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 12px 20px;
  border-radius: var(--radius-pill);
  font: 600 14px/1 var(--font-ui);
  white-space: nowrap;
  transition: transform var(--dur-hover) var(--ease-soft),
              box-shadow var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.btn-hero:active { transform: scale(0.988); }
.btn-hero:focus-visible, .gate-quiet:focus-visible {
  outline: 2px solid rgba(169, 178, 245, 0.9);
  outline-offset: 3px;
}
.btn-hero[disabled] { opacity: 0.55; }
.btn-hero[disabled]:active { transform: none; }

/* Solid white pill: unmissable on the dark field. */
.btn-hero.is-solid {
  color: #14162a;
  background: #ffffff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 1),
    inset 0 -1px 0 rgba(0, 0, 0, 0.08),
    0 2px 6px rgba(0, 0, 0, 0.4),
    0 10px 26px rgba(0, 0, 0, 0.42),
    0 22px 60px rgba(94, 106, 210, 0.35);
}
/* Hover deepens the same stack; it does not swap it for a shorter one. The
   landing page's rule drops the violet glow and the inset floor on hover, so
   the button visibly *loses* its lift exactly when it should gain it. Every
   layer stays, and the two that read as height grow. */
.btn-hero.is-solid:hover {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 1),
    inset 0 -1px 0 rgba(0, 0, 0, 0.08),
    0 3px 8px rgba(0, 0, 0, 0.44),
    0 14px 34px rgba(0, 0, 0, 0.46),
    0 26px 70px rgba(94, 106, 210, 0.45);
}
/* Smoked glass: a white pane let the terrain glyphs compete with the label. */
.btn-hero.is-glass {
  color: #fff;
  background: rgba(14, 14, 34, 0.55);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 0 0 1px rgba(255, 255, 255, 0.22),
    0 4px 16px rgba(0, 0, 0, 0.3);
}
.btn-hero.is-glass:hover { background: rgba(24, 24, 52, 0.68); }

/* -- first run: sign in ---------------------------------------------------- */

/* -- first run: sign in ---------------------------------------------------- */
/* Providers stack rather than sit side by side: two equal-width buttons read as
   a choice between halves of one thing, and there is no wrong answer here, so
   full-width rows let each one be read on its own. */
.signin-choices {
  display: flex; flex-direction: column; gap: 10px;
  width: min(360px, 100%);
  /* Straight off the headline now that nothing sits between them, so it wants
     a wider gap than it did under a paragraph. */
  margin: 46px auto 0;
}
.signin-btn { width: 100%; padding: 13px 20px; }
.gate-wide { margin-top: 30px; min-width: 260px; }
.signin-logo { width: 17px; height: 17px; object-fit: contain; }
/* The GitHub mark is one black path, and the gate is always dark. */
.btn-hero.is-glass img[src$="github.png"] { filter: invert(1); }

.signin-or {
  position: relative;
  width: min(360px, 100%);
  margin: 28px auto 0;
  font: 600 10.5px/1 var(--font-ui);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
}
.signin-or::before, .signin-or::after {
  content: ""; position: absolute; top: 50%;
  width: calc(50% - 26px); height: 1px;
  background: rgba(255, 255, 255, 0.14);
}
.signin-or::before { left: 0; }
.signin-or::after { right: 0; }

/* The email field was invisible before: it inherited a light-theme input on a
   near-black panel, so only the placeholder showed and the button floated off
   on its own. It is a glass pane now, like everything else on this screen. */
.gate-email {
  display: flex; align-items: center;
  width: min(360px, 100%);
  margin: 16px auto 0;
  padding: 5px 5px 5px 4px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
  transition: box-shadow var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
/* The ring belongs to the capsule, not to the bare field inside it. */
.gate-email:focus-within {
  background: rgba(255, 255, 255, 0.1);
  box-shadow: inset 0 0 0 1px rgba(169, 178, 245, 0.55);
}
.gate-email .gate-input {
  flex: 1; min-width: 0;
  padding: 10px 14px;
  background: none;
  box-shadow: none;
}
.gate-email-go { padding: 10px 16px; font-size: 13px; }

.gate-input {
  border: 0;
  border-radius: var(--radius-pill);
  font: 400 14px/1 var(--font-ui);
  color: #fff;
}
.gate-input::placeholder { color: rgba(255, 255, 255, 0.38); }
.gate-input:focus { outline: none; }

/* The way out, for the few people who need it: present, never competing. */
.gate-quiet {
  margin-top: 22px;
  background: none;
  font: 500 12.5px/1 var(--font-ui);
  color: rgba(255, 255, 255, 0.5);
  transition: color var(--dur-hover) var(--ease-soft);
}
.gate-quiet:hover { color: rgba(255, 255, 255, 0.82); }

/* -- account --------------------------------------------------------------- */
.account-pill {
  display: flex; align-items: center; gap: 8px;
  margin: 10px 14px 14px;
  padding: 6px 8px;
  border-radius: var(--radius-btn);
  background: transparent;
  text-align: left;
  min-width: 0;
  transition: background var(--dur-hover) var(--ease-soft);
}
.account-pill:hover { background: var(--bg-hover); }
.account-pill.is-active {
  background: color-mix(in srgb, var(--ink) 10%, transparent);
}
.account-avatar {
  flex: none;
  width: 19px; height: 19px;
  border-radius: 50%;
  display: grid; place-items: center;
  overflow: hidden;
  background: var(--tint-violet);
  font-size: 10px; font-weight: 600;
  color: var(--ink-body);
}
.account-avatar img { width: 100%; height: 100%; object-fit: cover; }
.account-text { min-width: 0; display: flex; flex-direction: column; }
.account-name, .account-sub {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.account-name { font-size: 10.5px; font-weight: 500; color: var(--ink); }
.account-sub { font-size: 10px; color: var(--ink-faint); }

.account-body { display: flex; flex-direction: column; gap: 14px; }
.account-identity { display: flex; align-items: center; gap: 12px; }
.account-identity-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  position: relative;
}
.account-identity-avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.account-identity-who { min-width: 0; }
.account-identity-name { font-size: 14px; font-weight: 600; color: var(--ink); }
/* An address is one unbreakable run. A long work address ran out through the
   card's edge in a narrow window; it breaks where it has to instead. */
.account-identity-sub { font-size: 12px; color: var(--ink-faint); margin-top: 1px; overflow-wrap: anywhere; }

.account-meter { height: 3px; border-radius: var(--radius-pill); background: var(--bg-hover); overflow: hidden; }
.account-meter-fill {
  height: 100%; border-radius: var(--radius-pill);
  background: var(--ink-body);
  transition: width var(--dur-hover) var(--ease-soft);
}
/* Nearly out is the one state worth colouring: the number alone does not make
   anybody look. */
.account-meter-fill.is-full { background: var(--ink-pending); }

.account-sub-line { font-size: 12px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.account-heading { font-size: 12px; font-weight: 600; color: var(--ink-body); margin-top: 6px; }
.account-device { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.account-device-text { min-width: 0; }
.account-device-name { font-size: 13px; color: var(--ink); }
.account-device-sub.row-line { margin-top: 4px; }
.account-actions { display: flex; gap: 8px; margin-top: 10px; }

/* -- account settings page -------------------------------------------------
 *
 * Cursor's settings: Back, a left section list, grouped cards in the pane.
 * Hairline cards, equal rows, no filled blobs. The left list is only Account,
 * Plan & Usage, and Devices — facts this page already had. */

#view-account { overflow: hidden; }
.settings {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
  align-items: stretch;
}
.settings-rail {
  flex: none;
  width: 196px;
  padding: 16px 12px 32px 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow: auto;
}
.settings-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  align-self: flex-start;
  margin: 0;
  padding: 4px 8px 4px 2px;
  border: 0;
  border-radius: var(--radius-chip);
  background: none;
  font: 600 12px/1 var(--font-ui);
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  cursor: pointer;
  transition: color var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.settings-back:hover { color: var(--ink); background: var(--bg-hover); }
.settings-back .ic { font-size: 12px; }
.settings-nav { display: flex; flex-direction: column; gap: 2px; }
.settings-nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: var(--radius-btn);
  background: none;
  text-align: left;
  font: 500 13px/1.2 var(--font-ui);
  color: var(--ink-muted);
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-soft),
              color var(--dur-hover) var(--ease-soft);
}
.settings-nav-item:hover { background: var(--bg-hover); color: var(--ink); }
.settings-nav-item.is-active {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  color: var(--ink);
}
.settings-nav-item .ic { font-size: 14px; opacity: 0.85; }
.settings-nav-item.is-active .ic { opacity: 1; }
.settings-pane {
  flex: 1;
  min-width: 0;
  overflow: auto;
  padding: 20px 36px 56px 12px;
}
.settings-title {
  margin: 4px 0 18px;
  font: 600 20px/1.3 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--ink);
}
.settings-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 560px;
}
.settings-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: transparent;
}
.settings-card.is-rows {
  padding: 0;
  gap: 0;
}
.settings-card.is-rows .account-heading {
  margin: 0;
  padding: 12px 16px 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
}
.settings-card.is-rows .account-device {
  padding: 12px 16px;
}
.settings-card.is-rows .account-device + .account-device {
  border-top: 1px solid var(--hairline);
}
.settings-card .credit-figure { font-size: 24px; }
.settings-card .usage-credits {
  margin: 0;
  padding: 0;
  border: 0;
}
.settings-card .account-update { margin: 0; padding: 0; border: 0; }
.settings-card .fold { margin: 0; border: 0; }
.settings-row-btn {
  display: flex;
  align-items: center;
  width: 100%;
  margin: 0;
  padding: 12px 16px;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  font: 500 13px/1.3 var(--font-ui);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-soft);
}
.settings-row-btn:hover {
  background: var(--bg-hover);
  transform: none;
  filter: none;
  box-shadow: none;
}
.settings-row-btn + .settings-row-btn { border-top: 1px solid var(--hairline); }
.settings-row-btn.is-danger { color: var(--ink-danger); }

/* -- the update row at the dialog's foot -----------------------------------
 *
 * Ruled off from the account above it because it is about the app, not the
 * account. The title is the build ("Lanes 1.0.8", or "Lanes 1.0.9 is ready"),
 * the sub is whatever the last check had to say, and the button is the only
 * control. The button keeps its width out of the text's way by never
 * shrinking: "Check for updates" wrapping mid-word reads as a broken row. */
.account-update {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--hairline);
}
.account-update-text { min-width: 0; }
.account-update .btn { flex-shrink: 0; }
.account-update-title { font-size: 12px; font-weight: 600; color: var(--ink-body); }
.account-update-sub { font-size: 12px; color: var(--ink-faint); margin-top: 2px; }
.account-update-sub.is-bad { color: var(--ink-danger); }

/* The update row's way back to the notes this build opened with. A link,
   not a button: the row's one button is the update check. */
.account-update-news {
  display: block;
  margin-top: 2px;
  padding: 0;
  background: none;
  font-size: 12px;
  font-weight: 550;
  color: var(--ink-violet);
}
.account-update-news:hover { text-decoration: underline; text-underline-offset: 2px; }

/* -- what's new: the window a new version opens with ------------------------ */
/* A header lit faintly from its corners under a sparkle kicker, a filter by
   tag, then the changes, each led by its tag. The tags keep one column, so
   every title starts at the same x whichever tag it carries. */
/* The header, the filter and the footer hold still and only the list scrolls:
   a release runs to a dozen changes or more, and a window that scrolled as
   one lost its title and its filter off the top with the first flick. */
.dialog.is-whats-new {
  width: min(580px, 100%);
  padding-top: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.dialog.is-whats-new > * { flex-shrink: 0; }
.dialog.is-whats-new > .whats-new-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Out to the dialog's edges, so its scrollbar runs where the dialog's would. */
  margin-left: -24px;
  margin-right: -24px;
  padding: 0 24px;
  overscroll-behavior: contain;
}
.whats-new-list::-webkit-scrollbar { width: 10px; }
.whats-new-list::-webkit-scrollbar-track { background: transparent; margin: 6px 0; }
.whats-new-list::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--ink-muted) 32%, transparent);
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 8px;
}
/* Nothing in the header but the block itself: a large faint Lanes mark used
   to rise from its foot and be cut by the rule below, which read as a
   drawing fault, not a design. The glow is the only decoration left, and it
   fades out inside the header's own bounds. */
.whats-new-head {
  position: relative;
  overflow: hidden;
  margin: 0 -24px;
  padding: 24px 54px 16px 24px;
  /* Mixed from the brand's violet rather than its tint, which is near black
     in the dark theme and left the glow invisible there. */
  background:
    radial-gradient(120% 160% at 0% 0%, color-mix(in srgb, var(--violet) 16%, transparent) 0%, transparent 60%),
    radial-gradient(90% 140% at 100% 0%, color-mix(in srgb, var(--violet) 9%, transparent) 0%, transparent 55%);
  border-bottom: 1px solid var(--hairline);
}
.whats-new-kicker {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px 3px 8px;
  border-radius: 999px;
  background: var(--tint-violet);
  color: var(--ink-violet);
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.01em;
}
.whats-new-kicker .ic { font-size: 13px; }
/* Kicker, title, count: one block, with the title's own rule and padding
   (`.dialog h2`) taken off since the header draws the rule. */
.dialog.is-whats-new h2 {
  margin: 10px 0 0;
  padding: 0;
  border: 0;
  font: 650 24px/1.15 var(--font-display);
  letter-spacing: -0.02em;
}
.dialog.is-whats-new h2 + * { margin-top: 0; }
.whats-new-sub { margin: 4px 0 0; font-size: 13px; color: var(--ink-muted); }

.whats-new-tabs {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 16px 0 4px;
}
.whats-new-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--hairline);
  font-size: 12.5px; font-weight: 550;
  color: var(--ink-muted);
  transition: background var(--dur-hover) var(--ease-soft),
              color var(--dur-hover) var(--ease-soft),
              box-shadow var(--dur-hover) var(--ease-soft);
}
.whats-new-tab:hover { background: var(--bg-hover); color: var(--ink); }
.whats-new-tab.is-on {
  background: var(--surface-sunken);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink-muted) 45%, transparent);
  color: var(--ink);
}
.whats-new-count {
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}
.whats-new-tab.is-on .whats-new-count { color: var(--ink-muted); }

.whats-new-list { list-style: none; margin: 6px 0 0; padding: 0; }
.whats-new-item {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  padding: 12px 0;
  border-top: 1px solid var(--hairline);
  animation: whats-new-in 360ms var(--ease-out) both;
  animation-delay: calc(var(--at, 0) * 30ms + 60ms);
}
.whats-new-list > li:first-child { border-top: 0; }
.whats-new-tag {
  justify-self: start;
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: 1px;
  padding: 2px 9px 2px 7px;
  border-radius: 999px;
  font-size: 11.5px; font-weight: 600;
  white-space: nowrap;
}
.whats-new-tag .ic { font-size: 12px; }
.whats-new-tag[data-tag="new"] { background: var(--tint-violet); color: var(--ink-violet); }
.whats-new-tag[data-tag="improved"] { background: var(--tint-blue); color: var(--ink-progress); }
.whats-new-tag[data-tag="fixed"] { background: var(--tint-green); color: var(--ink-success); }
.whats-new-tag[data-tag="faster"] { background: var(--tint-amber); color: var(--ink-pending); }
.whats-new-title { font-size: 13.5px; font-weight: 600; line-height: 1.35; color: var(--ink); }
.whats-new-detail {
  margin: 3px 0 0;
  max-width: 60ch;
  font-size: 12.5px; line-height: 1.5;
  color: var(--ink-muted);
}
/* Only when two updates are read at once: which one a change came in. */
.whats-new-version {
  padding: 14px 0 2px;
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-faint);
}
.whats-new-version + .whats-new-item { border-top: 0; }
@keyframes whats-new-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .whats-new-item { animation: none; }
}

/* -- launch update overlay -------------------------------------------------
 *
 * Same interrupt as a dialog (dimmed world, raised card) because it is one:
 * the app is about to restart, and a goal started underneath would run on
 * the build being replaced. Below toasts so a failed apply still says why.
 * Own titlebar strip so the window stays draggable while it is up. */
.update-overlay {
  position: fixed; inset: 0; z-index: 95;
  display: grid; place-items: center;
  padding: 24px;
  background: rgba(12, 13, 18, 0.52);
  backdrop-filter: blur(10px) saturate(120%);
}
.update-overlay-card {
  width: min(380px, 100%);
  padding: 28px 28px 24px;
  border-radius: var(--radius-dialog);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--shadow-dialog);
  text-align: center;
}
.update-overlay-title {
  font: 600 16px/1.3 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--ink);
}
.update-overlay-sub {
  margin: 8px 0 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink-body);
}
.update-overlay-bar {
  margin: 18px 0 0;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--bg-hover);
  overflow: hidden;
}
.update-overlay-fill {
  height: 100%;
  width: 0;
  border-radius: var(--radius-pill);
  background: var(--grad-violet);
  transition: width 180ms var(--ease-soft);
}

/* -- the rail's credit card ------------------------------------------------
 *
 * Recessed rather than raised: it is a readout, not a control, so it reads as
 * set INTO the rail. The inset ring plus a soft inner top shadow is what does
 * that -- a drop shadow here would make it float and compete with the
 * furniture around it.
 */

.credits-card {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Inset to the same 14px gutter as the identity pill below. It was the
     one element in the rail with no side
     margin, so it ran full-bleed into the window edge and its left corners
     were cut off by the frame. */
  margin: var(--space-2) 14px 0;
  padding: 8px 12px;
  border: none;
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--inset-well);
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-soft);
}
.credits-card:hover { background: var(--surface-2); }
.credits-card:active { box-shadow: var(--inset-well); }

.credits-card-icon {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--ink);
  opacity: 0.85;
}
.credits-card-text { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.credits-card-label {
  font-size: 10.5px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.2;
}
.credits-card-value {
  display: flex;
  align-items: baseline;
  gap: 5px;
  /* Tabular, or the balance shifts sideways every time a step lands. */
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
.credits-card-left { font-size: 13px; font-weight: 600; color: var(--ink); }
.credits-card-left.is-warn { color: var(--ink-pending); }
.credits-card-left.is-bad  { color: var(--ink-danger); }
/* The headroom, in the one place green is allowed to mean "available". */
.credits-card-total { font-size: 13px; font-weight: 600; color: var(--ink-success); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme=light]) .credits-card {
    background: var(--surface-2);
    box-shadow: var(--inset-well);
  }
  :root:not([data-theme=light]) .credits-card:hover { background: var(--surface); }
}
:root[data-theme=dark] .credits-card { background: var(--surface-2); box-shadow: var(--inset-well); }
:root[data-theme=dark] .credits-card:hover { background: var(--surface); }

/* -- billing ---------------------------------------------------------------
 *
 * Credits are the product's currency, so the balance is set as a figure and
 * not as body copy: it is the one number in this panel somebody came to read.
 * Everything under it is support for that number, kept quiet.
 */

.credit-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.credit-left { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.credit-figure {
  font-family: var(--font-display);
  font-size: 32px;
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--ink);
  /* Tabular, or the balance jumps sideways every time it ticks down. */
  font-variant-numeric: tabular-nums;
}
.credit-unit { font-size: 12px; color: var(--ink-muted); }

/* Only drawn when there is something to say. A healthy paid account gets no
   chip at all, the same way the connection indicator stays silent. */
.credit-chip {
  flex: none;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
}
.credit-chip.is-ok    { background: var(--tint-green);  color: var(--ink-success); }
.credit-chip.is-trial { background: var(--tint-violet); color: var(--ink-violet); }
.credit-chip.is-warn  { background: var(--tint-amber);  color: var(--ink-pending); }
.credit-chip.is-bad   { background: var(--tint-red);    color: var(--ink-danger); }

/* The meter measures what is LEFT, so an empty bar is the alarm. */
.account-meter-fill.is-low { background: var(--danger); }

.billing-alert {
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--radius-chip);
  background: var(--tint-red);
  color: var(--ink-danger);
  font-size: 12px;
  line-height: 1.45;
}
.billing-note { margin: 0; font-size: 12px; color: var(--ink-pending); }

.billing-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }

/* -- usage credits ---------------------------------------------------------
 *
 * Three bands separated by hairlines, because they answer three questions in
 * descending urgency: is it on, what has it cost, what is the ceiling. The
 * separators are load-bearing -- run together, the spend figure and the limit
 * figure read as one number, which is precisely the thing somebody is trying
 * to tell apart.
 */

.usage-credits {
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
}
.usage-oneline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 8px;
  font-size: 12px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

.usage-head { display: flex; align-items: flex-start; gap: 12px; }
.usage-head-text { flex: 1; min-width: 0; }
.usage-title { font-size: 13px; font-weight: 600; color: var(--ink); }
.usage-lede {
  margin-top: 3px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-muted);
  text-wrap: pretty;
}
.usage-unavailable {
  flex: none;
  align-self: center;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--ink-muted);
  font-size: 11px;
  white-space: nowrap;
}

/* The switch. A real button with role="switch" rather than a checkbox, so it
   is one element to style and it announces its state to a screen reader. */
/* The Account page's on/off switch: one shape for every yes/no setting there
   (usage credits, product analytics), so a setting never reads as an action
   button and two settings never look like two different controls. */
.settings-toggle {
  flex: none;
  position: relative;
  width: 38px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-soft);
}
.settings-toggle.is-on { background: var(--violet); box-shadow: none; }
.settings-toggle-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(24, 25, 32, 0.3);
  transition: transform var(--dur-hover) var(--ease-soft);
}
.settings-toggle.is-on .settings-toggle-knob { transform: translateX(16px); }
/* Saving, or not loaded yet: there, but not pressable. */
.settings-toggle:disabled { opacity: 0.5; cursor: default; }
.settings-toggle.is-loading { cursor: progress; }
/* First paint lands on the saved state instead of sliding into it. */
.settings-toggle.is-settling,
.settings-toggle.is-settling .settings-toggle-knob { transition: none; }
/* Top-aligned with the usage title beside it, not centred on the block. */
.usage-head .settings-toggle { margin-top: 2px; }
@media (prefers-reduced-motion: reduce) {
  .settings-toggle, .settings-toggle-knob { transition: none; }
}

.usage-row {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
}
.usage-row-text { flex: none; min-width: 8.5em; }
.usage-figure {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.usage-sub { margin-top: 1px; font-size: 11px; color: var(--ink-muted); }

.usage-bar {
  flex: 1;
  min-width: 0;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  overflow: hidden;
}
.usage-bar-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--violet);
  transition: width var(--dur-reveal) var(--ease-out);
}
/* Near the ceiling is worth noticing: past it, agents stop. */
.usage-bar-fill.is-full { background: var(--ink-pending); }

.usage-pct {
  flex: none;
  font-size: 12px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.usage-row .btn { flex: none; margin-left: auto; }

.credit-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 5px 0;
  font-size: 12px;
}
.credit-row-what { flex: 1; min-width: 0; color: var(--ink-body); }
.credit-row-when { color: var(--ink-faint); font-size: 11px; white-space: nowrap; }
.credit-row-amount {
  color: var(--ink-success);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  min-width: 4.5em;
  text-align: right;
}
.credit-row-amount.is-out { color: var(--ink-muted); }
.credit-receipt { margin-top: 6px; font-size: 12px; }

/* -- plans ------------------------------------------------------------------
 *
 * A ladder, not four cards. The old version gave every plan identical weight
 * and made the PRICE the figure, so nothing scanned: you had to read all four
 * blocks to learn the one thing you came for.
 *
 * Credits are the product, so credits are the figure. Reading straight down
 * the left gives 50 / 450 / 1,500 / 5,500 and down the right $0 / $20 / $60 /
 * $200 -- the comparison is the layout, not something the reader assembles.
 */

.plan-list { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }

.plan-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 14px 16px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--surface);
  transition: border-color var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.plan-row:hover { border-color: var(--ink-faint); }

/* The one to point at. A ring rather than a scale transform: scaling a row
   misaligns every baseline beside it and the list stops reading as a ladder. */
.plan-row.is-featured {
  border-color: var(--violet);
  box-shadow: inset 0 0 0 1px var(--violet);
  /* The flag hangs 8px above the border, and the list gap is 8px -- without
     this it lands exactly on the row above and reads as a collision. */
  margin-top: 8px;
}
/* Where you already are. Quiet on purpose -- it is context, not an offer. */
.plan-row.is-current {
  background: var(--tint-violet);
  border-color: transparent;
}
.plan-row.is-current:hover { border-color: transparent; }

.plan-flag {
  position: absolute;
  top: -8px;
  left: 14px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--violet);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.5;
}

.plan-left { flex: 1; min-width: 0; }
.plan-name {
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-muted);
  letter-spacing: 0.01em;
}
.plan-credits {
  margin-top: 1px;
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.plan-facts {
  margin-top: 3px;
  font-size: 11px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.plan-right {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
}
.plan-price { display: flex; align-items: baseline; gap: 1px; }
.plan-amount {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.plan-per { font-size: 11px; color: var(--ink-faint); }

/* Every CTA the same width, so the column of buttons is a column and not a
   ragged edge of differently-sized blobs. */
.plan-cta { min-width: 92px; justify-content: center; font-size: 12px; }
.plan-current { font-size: 11px; font-weight: 500; color: var(--ink-violet); }

.plan-note {
  margin: 12px 0 0;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-faint);
  text-wrap: pretty;
}

/* -- credit packs ----------------------------------------------------------- */

.plan-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  background: var(--surface);
}
.plan-card-top { flex: 1; display: flex; align-items: baseline; gap: 8px; }
.plan-card-name {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.plan-card-price {
  font-size: 13px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.plan-card .btn { flex: none; min-width: 72px; justify-content: center; }

/* Labels and suffixes for the auto-top-up form. */
.field-label { flex: none; font-size: 12px; color: var(--ink-body); min-width: 8.5em; }
.field-suffix { flex: none; font-size: 12px; color: var(--ink-muted); }

/* The rail pill carries exactly one fact, so its tone has to do the work. */
.account-sub.is-warn { color: var(--ink-pending); }
.account-sub.is-bad  { color: var(--ink-danger); }

/* -- first run: the setup card --------------------------------------------- */
/* Every first-run step happens inside one bounded surface, centred in the view.
   Before this, a title, a button and a progress bar each floated on their own
   in an otherwise empty panel, reading as three unrelated things adrift rather
   than one task. A card gives the step somewhere to happen, and gives the
   progress bar an edge to align to — it used to sit left while the copy above
   it sat centred, because a width alone does not centre a block. */
/* The scroller the setup and connections cards live in. Its own element
   rather than the agent log's, which is what let a live agent event append
   its timeline under the import picker. */
/* A column, so the card's wrapper can take the whole height and centre the
   card in it. It centred in 62vh before, which on any window taller than a
   laptop's parked the card high with the room below it empty. */
.setup-surface {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  overflow-y: auto;
  padding: 4px var(--gutter) 24px;
}

.setup-wrap {
  flex: 1 0 auto;
  display: grid;
  place-items: center;
  min-height: 62vh;
  padding: var(--space-8) var(--space-6);
}
.setup {
  width: min(440px, 100%);
  padding: var(--space-8) var(--space-6) var(--space-6);
  text-align: center;
  background: var(--surface);
  border-radius: var(--radius-dialog);
  box-shadow: var(--inset-highlight), var(--shadow-card);
  /* One entrance, shared by every step, so moving through setup feels like
     one thing advancing rather than three pages loading. The global
     reduced-motion rule flattens it. */
  animation: setup-in var(--dur-reveal) var(--ease-out) both;
}
@keyframes setup-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

.setup.is-wide { width: min(540px, 100%); }

/* -- first run: the rail card ----------------------------------------------- */
/* "Setting up · 2 of 4" on the Credits card's chrome, with a four-segment
   hairline bar beneath the text: done segments violet, the active one violet
   at half strength, skipped hollow, still-to-come faint. It holds the place
   for as long as first run is open and fades when it is over. */
.setup-card { flex-wrap: wrap; }
.setup-card .credits-card-icon { color: var(--violet); opacity: 1; }
.setup-card[data-retiring] { opacity: 0; transition: opacity 180ms var(--ease-soft); }
.setup-card-bar {
  flex: 1 0 100%;
  display: flex;
  gap: 3px;
  margin-top: 6px;
}
.setup-card-seg {
  flex: 1;
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--ink-faint);
  opacity: 0.55;
  transition: background var(--dur-hover) var(--ease-soft), opacity var(--dur-hover) var(--ease-soft);
}
.setup-card-seg[data-state="done"] { background: var(--violet); opacity: 1; }
.setup-card-seg[data-state="active"] { background: var(--violet); opacity: 0.5; }
.setup-card-seg[data-state="skipped"] {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--ink-muted);
  opacity: 0.7;
}

/* Children of the card fill it. The service list carried its own max-width
   from when it floated in an open panel, which inside a card just parks it
   off-centre against the left edge — the same bug the progress bar had. */
.setup .service-list { width: 100%; }
.setup.is-wide .setup-lede { max-width: 46ch; }

/* "Copy my sessions" -- the whole import in one button, through the session
   engine (docs/session-engine-redesign.md). One line saying what it does, one
   secondary control, and the daemon's answer underneath as per-service rows:
   copied, needs a native sign-in, failed. There is no granular picker any
   more: it takes every login it finds, so nobody has to choose. */
.session-copy {
  margin-top: var(--space-5);
  display: grid;
  gap: 10px;
}
.session-copy .btn { width: 100%; justify-content: center; }
/* The where-it-lands note, the block's only prose: it sits under the button so
   it reads as a footnote to the action, not a warning above it. */
.session-copy-note {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
}

/* Connections manage: a page, not a wizard card. Title + one sentence + a
   thin equal list + a quiet Copy control. First-run browser install and
   initial setup keeps the centred card with its mark. */
.setup-wrap:has(> .setup[data-manage="true"]) {
  place-items: start stretch;
  min-height: 0;
  padding: 4px 0 24px;
}
.setup[data-manage="true"] {
  width: min(640px, 100%);
  margin: 0;
  padding: 4px 0 0;
  text-align: left;
  background: transparent;
  border-radius: 0;
  box-shadow: none;
  animation: none;
}
.setup[data-manage="true"] h2 {
  margin: 0;
  font: 700 clamp(20px, 1.8vw, 24px)/1.15 var(--font-display);
  letter-spacing: -0.02em;
}
.setup[data-manage="true"] .setup-lede {
  margin: 8px 0 0;
  max-width: 64ch;
  font-size: 13px;
  line-height: 1.5;
}
/* The connector's line and the rows under it are one table: logo, name,
   state, buttons. Every row takes its columns from here (subgrid), so a
   state starts at the same x on every row whatever buttons follow it, and
   the connector's "Connected" starts where each "Signed in" does. Each row
   laid itself out alone before, and its state sat wherever its own buttons
   left room: Google's "Signed in" a Change button's width left of
   Outlook's. The logo column is sized by what is in it, which counts the
   rows' 2px of side padding, so the names stay where they were. The state
   column fits the longest state a row keeps ("Could not verify session");
   a longer one only lasts while the owner is at a sign-in window ("Waiting
   for you in the Lanes browser") and wraps under itself, rather than take
   every name's room while it is up. */
.setup[data-manage="true"] .connections-table {
  display: grid;
  grid-template-columns: [art] auto [name] minmax(0, 1fr) [state] fit-content(140px) [actions] auto [end];
  column-gap: 11px;
  margin-top: 18px;
}
/* Spans by number, not by name: an engine without subgrid (WebKit before
   Safari 16, which macOS 12 can still be running) keeps the one-column
   fallback below, where the names do not exist and a row placed by them
   landed a column's width in from the list's edge. */
.setup[data-manage="true"] .connections-table > * { grid-column: 1 / -1; }
.setup[data-manage="true"] .service-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-columns: subgrid;
  gap: 0 11px;
  margin-top: 0;
  width: 100%;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.setup[data-manage="true"] .service-row {
  display: grid;
  grid-column: 1 / -1;
  /* The same columns row by row, for an engine without subgrid: the states
     go back to sitting beside their own buttons, and nothing overlaps. */
  grid-template-columns: [art] 20px [name] minmax(0, 1fr) [state] auto [actions] auto [end];
  grid-template-columns: subgrid;
  gap: 0 11px;
  align-items: center;
  padding: 7px 2px;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}
/* The first line holds the logo, the name, the state and the buttons; a
   second line (the Google row's sending address) sits under the name. A
   second state (a sign-in waited on while its session is checked) stacks
   under the first rather than pushing the buttons down. */
.setup[data-manage="true"] .service-row > .service-art { grid-area: 1 / art; }
.setup[data-manage="true"] .service-row > .service-name { grid-area: 1 / name; }
.setup[data-manage="true"] .service-row > .service-state { grid-column: state; }
.setup[data-manage="true"] .service-row > .service-actions { grid-area: 1 / actions; justify-self: end; }
.setup[data-manage="true"] .service-row > .service-sub { grid-area: 2 / name / auto / end; margin-top: -3px; }
/* The any-site row has no state, and its address wants every pixel up to
   its button: in the columns, it stopped where the widest row's buttons
   start and cut the placeholder short. Its own line, the old way; the logo
   and the button still land on the table's edges. */
.setup[data-manage="true"] .service-row-custom { display: flex; }
.service-actions {
  display: flex;
  align-items: center;
  gap: 11px;
}
.setup[data-manage="true"] .service-row + .service-row {
  border-top: 1px solid var(--hairline);
}
.setup[data-manage="true"] .service-row:hover {
  background: var(--bg-hover);
  box-shadow: none;
}
.setup[data-manage="true"] .service-art {
  width: 20px;
  height: 20px;
  background: transparent;
  box-shadow: none;
}
.setup[data-manage="true"] .service-art img {
  width: 16px;
  height: 16px;
  object-fit: contain;
}
/* A service with no logo file, or one whose file did not load, draws its
   initial instead. A logo needs no plate, a letter does: alone in the column
   where every other row has a picture, Outlook's O read as a zero until it
   had a logo of its own. */
.setup[data-manage="true"] .service-art.is-letter {
  background: var(--surface-2);
  border-radius: var(--radius-chip);
  font-size: 11px;
}
.setup[data-manage="true"] .service-name {
  font-size: 13px;
  font-weight: 550;
}
.setup[data-manage="true"] .session-copy {
  margin-top: 16px;
  justify-items: start;
  gap: 8px;
}
.setup[data-manage="true"] .session-copy .btn {
  width: auto;
  justify-content: center;
}
.setup[data-manage="true"] .session-copy-note {
  max-width: 52ch;
  color: var(--ink-faint);
}
.setup[data-manage="true"] .session-copy-row {
  background: transparent;
  box-shadow: none;
  padding: 4px 0;
}
/* The rows carry a verdict each, so they read top to bottom like a receipt:
   left-aligned even on the centred wizard card, because "Notion -- copied"
   centred under "GitHub -- cannot be copied" turns a list into confetti. */
.session-copy-summary {
  display: grid;
  gap: 6px;
  text-align: left;
}
.session-copy-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px;
  border-radius: var(--radius-btn);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--shadow-rest);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink);
}
.session-copy-dot {
  width: 7px; height: 7px; flex: none;
  border-radius: var(--radius-pill);
}
/* The real "here" for a can't-be-copied service: a sign-in button pinned to the
   right of its row. */
.session-copy-action {
  margin-left: auto;
  flex: none;
  padding: 4px 11px;
  border: 0;
  border-radius: var(--radius-btn);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--shadow-rest);
  color: var(--ink);
  font: 550 12px/1 var(--font-ui);
  cursor: pointer;
  transition: box-shadow var(--dur-hover) var(--ease-soft);
}
.session-copy-action:hover { box-shadow: var(--inset-highlight), var(--shadow-hover); }
.session-copy-action:disabled { cursor: default; color: var(--ink-muted); }
.session-copy-row[data-state="ok"] .session-copy-dot { background: var(--success); }
.session-copy-row[data-state="warn"] .session-copy-dot { background: var(--amber); }
.session-copy-row[data-state="bad"] .session-copy-dot { background: var(--danger); }
.session-copy-row[data-state="warn"] { color: var(--ink-pending); }
.session-copy-row[data-state="bad"] { color: var(--ink-danger); }
.session-copy-row[data-state="note"] { color: var(--ink-muted); }
.session-copy-row[data-state="note"] .session-copy-dot { background: var(--ink-muted); }

/* The mark states which step this is at a glance, and gives the card a top
   edge to hang from. The one saturated thing on the card, so it reads as the
   subject rather than as decoration. */
.setup-mark {
  width: 38px; height: 38px;
  margin: 0 auto;
  display: grid; place-items: center;
  border-radius: var(--radius-icon);
  background: var(--icon-violet);
  box-shadow: var(--glow-icon-violet);
}
.setup-mark .ic { width: 20px; height: 20px; color: #fff; }

.setup h2 {
  margin: 14px 0 0;
  font: 600 19px/1.25 var(--font-display);
  letter-spacing: -0.022em;
  color: var(--ink);
}
.setup-lede {
  margin: 7px auto 0;
  max-width: 36ch;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-muted);
}
/* The download size belongs on its own line: inside the sentence it read as an
   aside, when it is the one fact that decides whether now is a good moment. */
.setup-meta {
  margin-top: 12px;
  font-size: 12px;
  letter-spacing: 0.01em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
/* A heads-up the owner needs BEFORE the button, not a status line after it:
   Chrome's own "use this account everywhere?" screen has merged the agents'
   login into people's personal profiles. Amber-tinted, low-key, sits above
   the action so it is read as part of deciding to press it. */
.setup-note {
  margin: 14px auto 0;
  max-width: 40ch;
  padding: 9px 12px;
  text-align: left;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-muted);
  background: var(--tint-amber);
  border: 1px solid var(--amber);
  border-radius: var(--radius-card, 10px);
}
/* Side by side at the right, the way out then the action, as every dialog
   sets them. Stretched to equal halves, a ghost button was bare text floating
   in the right half of the card under a primary half as wide. */
.setup-actions {
  margin-top: 18px;
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}
/* The Chrome profile select sits inside a centred card; its label reads as a
   label only when it stays left-aligned with the control it names. */
.setup .field { text-align: left; margin-top: 14px; }

/* The setup card is in the dialog's position — the only surface on screen,
   with one action that moves things forward — so its confirm earns the same
   gradient the dialog's does, and for the same reason. The tinted primary is
   for dense views where a solid violet would shout over the content. */
.setup .btn.primary {
  background: var(--grad-violet);
  color: #fff;
  box-shadow: var(--inset-cta-top), var(--inset-cta-floor), var(--glow-violet);
}
.setup .btn.primary:hover {
  box-shadow: var(--inset-cta-top), var(--inset-cta-floor), var(--glow-violet-hi);
}
.setup .btn { padding-block: 8px; justify-content: center; }

/* -- service rows: what each agent is signed into --------------------------- */
.service-list {
  display: flex; flex-direction: column; gap: 4px;
  width: min(460px, 100%);
  margin-top: 14px;
  text-align: left;
}

/* One row per service: logo, name, state, and its action (Disconnect when
   signed in, Sign in when not). The row itself is not clickable any more --
   the actions live in their own buttons -- so it keeps the hover lift as a
   grouping cue but not a pointer cursor. */
.service-row {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 12px;
  border-radius: var(--radius-btn);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--shadow-rest);
  transition: box-shadow var(--dur-hover) var(--ease-soft), opacity var(--dur-hover) var(--ease-soft);
}
.service-row:hover { box-shadow: var(--inset-highlight), var(--shadow-hover); }
.service-art {
  width: 26px; height: 26px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--radius-icon);
  background: var(--bg-hover);
  font-size: 12px; font-weight: 700; color: var(--ink-muted);
}
.service-art .ic { width: 15px; height: 15px; }
.service-name { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 550; color: var(--ink); }
/* The any-site row: the row is the control and the input is borderless. */
.service-row-custom { cursor: text; }
.service-row .service-url {
  flex: 1;
  min-width: 0;
  width: auto;
  height: auto;
  padding: 0;
  border: 0;
  background: none;
  font: 550 13.5px/1.4 var(--font-ui);
  color: var(--ink);
}
.service-url::placeholder { color: var(--ink-faint); font-weight: 450; }
.service-url:focus { outline: none; }
.service-row-custom[data-busy="true"] { opacity: 0.7; }

/* The display name under an address. Second line, quieter: the address is what
   is being chosen, the name is only there to recognise it by. */
.service-sub {
  display: block;
  margin-top: 1px;
  font-size: 11.5px;
  font-weight: 450;
  color: var(--ink-faint);
}
.service-state { flex: none; font-size: 11.5px; color: var(--ink-faint); }
.service-row[data-connected="true"] .service-state { color: var(--online); }
/* -- a need reads at a glance: whose logo, what is wanted, one button ------- */
.need-card .card-top { gap: 14px; }
/* A white plate, in both themes, because what sits on it is somebody else's
   mark in somebody else's colours: X's black and LinkedIn's navy disappear
   into a dark surface, and Gmail's reds sit on white everywhere it is
   published. Asks with no service behind them keep the same plate so every
   card in the stack starts its text on the same line. */
.need-logo {
  flex: none;
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: var(--radius-card);
  background: #fff;
  box-shadow: var(--inset-highlight), var(--shadow-rest);
  font: 700 15px/1 var(--font-display);
  color: var(--ink-muted);
}
.need-logo img { width: 24px; height: 24px; object-fit: contain; }
/* No brand file to carry: the plate goes back to the app's own surface so a
   letter or the pending glyph is not floating on a white square for no reason. */
.need-logo.is-letter, .need-logo.is-plain { background: var(--surface-2); }
.need-logo.is-plain { color: var(--ink-pending); background: var(--tint-amber); box-shadow: none; }
.need-logo .ic { width: 20px; height: 20px; }

.need-kind {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
}
.need-kind .ic { width: 13px; height: 13px; }
.eyebrow-service {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-body);
}
.need-action {
  margin: 4px 0 0;
  font-size: 14px;
  font-weight: 650;
  color: var(--ink);
  max-width: 74ch;
}
.need-detail {
  margin-top: 8px;
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-muted);
  max-width: 74ch;
  user-select: text;
  /* One line on the card: the draft is long and the action is already named. */
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.need-detail.is-open { -webkit-line-clamp: unset; overflow: visible; }
/* One "there is more of this" control, wherever prose is clamped. An ask's
   link to the page it is about is drawn the same way: a quiet action in the
   card's text, not an underlined line of body copy. */
.need-more, .need-link, .learned-more {
  margin-top: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-violet);
}
.need-link { display: block; width: fit-content; text-decoration: none; }
.need-more:hover, .need-link:hover, .learned-more:hover { text-decoration: underline; }
/* Profile card: Show more is a quiet link, not a violet accent. */
.card-profile .learned-more { color: var(--ink-muted); }
.card-profile .learned-more:hover { color: var(--ink); text-decoration: underline; }

/* The actions close the card, under the prose they answer, and always visible:
   a need is the one card whose buttons must not wait for a hover. Dismiss is
   pushed off on its own so the destructive one is never the neighbour of the
   button you meant to hit. */
.need-actions {
  display: flex; align-items: center; gap: 8px;
  margin-top: 14px;
  /* Bounded to the prose's measure. Pushed to the card's own edge, Dismiss
     ended up most of a window away from the button beside it. */
  max-width: 74ch;
}
.need-actions .need-dismiss { margin-left: auto; }

.service-art img { width: 17px; height: 17px; object-fit: contain; }

/* -- the overview: outcomes first, and confirmed never merged with claimed -- */
.runstrip {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 6px;
  margin: 2px 0 22px;
  font-size: 13px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
/* The calls ahead reuse the funnel's people rows, which indent under a bar
   that is not here; the card's own padding is the indent. */
.upcoming-card { padding: var(--space-3) var(--space-5); margin-bottom: 22px; }
.upcoming-card .funnel-people { margin: 0; padding: 0; }
/* Questions for you: the person's row, their words as a trail message, and
   the field for the owner's answer under it. One block per question. */
.questions-card { display: grid; row-gap: var(--space-3); }
.question-words { margin: 2px 0 0 10px; border-left: 2px solid var(--ink-violet); border-radius: 0 6px 6px 0; }
.question-answer { display: flex; gap: var(--space-2); align-items: flex-start; margin: 6px 0 2px 10px; }
.question-field { flex: 1; min-width: 0; resize: vertical; min-height: 44px; font-size: 13px; line-height: 1.5; }

/* -- the period: the heading names it, these change it -------------------- */
.period {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border-radius: var(--radius-btn);
  background: var(--surface-sunken);
  box-shadow: var(--inset-track);
}
.period-pill {
  padding: 4px 10px;
  border-radius: calc(var(--radius-btn) - 3px);
  font-size: 12px; font-weight: 600;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
  transition: color var(--dur-hover) var(--ease-soft),
              background var(--dur-hover) var(--ease-soft);
}
.period-pill:hover { color: var(--ink); }
.period-pill.is-on {
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-rest);
}

/* -- activity: a year of first-touches, GitHub-style --------------------- */
/* Week columns, weekday rows, empty dots, one accent at four levels. No
   painted chrome: the card is the same surface as Delivered, the cells are
   the chart. */
.heat-card {
  padding: var(--space-4) var(--space-5);
  margin-bottom: 22px;
  overflow-x: auto;
}
.heat { display: flex; flex-direction: column; gap: 10px; min-width: min-content; }
.heat-months { display: flex; align-items: flex-end; gap: 6px; }
.heat-months-gutter { width: 12px; flex: 0 0 12px; }
.heat-month-row { display: flex; gap: 3px; }
.heat-month {
  width: 9px;
  font-size: 10px;
  font-weight: 500;
  color: var(--ink-faint);
  line-height: 1;
}
.heat-body { display: flex; align-items: flex-start; gap: 6px; }
.heat-wdays {
  display: flex; flex-direction: column; gap: 3px;
  width: 12px; flex: 0 0 12px;
}
.heat-wday {
  height: 9px;
  font-size: 9px;
  font-weight: 500;
  color: var(--ink-faint);
  line-height: 9px;
}
.heat-weeks { display: flex; gap: 3px; }
.heat-week { display: flex; flex-direction: column; gap: 3px; }
.heat-cell {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--surface-sunken);
}
.heat-cell[data-level="1"] { background: color-mix(in srgb, var(--violet) 28%, var(--surface-sunken)); }
.heat-cell[data-level="2"] { background: color-mix(in srgb, var(--violet) 50%, transparent); }
.heat-cell[data-level="3"] { background: color-mix(in srgb, var(--violet) 74%, transparent); }
.heat-cell[data-level="4"] { background: var(--violet); }
/* The 14-day reach chart's chip, parked on the section so a year of 9px
   dots can hover without native title= and without overflow clipping today. */
.heat-section { position: relative; }
.heat-tip {
  position: absolute;
  left: 0;
  top: 0;
  padding: 3px 8px;
  border-radius: var(--radius-chip);
  background: var(--ink);
  color: var(--canvas-cool);
  font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
  z-index: 8;
}

/* -- the budget, on the card face ------------------------------------------
   It lived two folds down. For unattended work that costs money and runs for
   hours, how much of the ceiling is gone is the primary fact, and a team at
   7.6 of 8 hours looked identical to one at 0.2. */
.budget {
  display: grid;
  grid-template-columns: max-content minmax(60px, 1fr) max-content max-content;
  column-gap: 10px;
  align-items: center;
  margin-top: 12px;
  font-variant-numeric: tabular-nums;
}
.budget-label {
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink-faint);
}
.budget-track {
  height: 6px;
  border-radius: 3px;
  background: var(--surface-sunken);
  overflow: hidden;
}
.budget-fill {
  display: block; height: 100%;
  border-radius: 3px;
  background: var(--ink-faint);
  transition: width 400ms var(--ease-out);
}
.budget-figure { font-size: 12px; color: var(--ink-muted); }
/* Only the last stretch earns colour: a budget bar that is amber from 10%
   teaches you to ignore it. */
.budget.is-tight .budget-fill { background: var(--pending); }
.budget-warn { font-size: 11.5px; font-weight: 600; color: var(--ink-pending); }

/* Progress channels share one inset so the fill sits in the track, the way
   Cursor's usage meters do. Listed here, after each widget's own rule, so the
   token wins without a per-bar one-off. */
.fbar-track,
.usage-bar,
.account-meter,
.bullet-track,
.budget-track,
.subject-bar,
.route-lane {
  box-shadow: var(--inset-track);
}

.failed-list {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: 12px;
  padding-top: var(--space-3);
  border-top: 1px solid var(--hairline);
}
.failed-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  column-gap: 12px;
  font-size: 12.5px;
}
.failed-who { color: var(--ink-body); font-weight: 500; overflow-wrap: anywhere; }
.failed-why { color: var(--ink-muted); overflow-wrap: anywhere; }
/* The owner's own rate before Lanes: Few's second comparative measure, drawn
   at 75% so it reads as secondary to the published benchmark without
   disappearing. Dashed, because it is a claim they made rather than one we
   measured. */
.bullet-tick.is-own {
  width: 0;
  border-left: 2px dashed var(--ink);
  background: none;
  opacity: 0.55;
}
/* The one line in onboarding that is a warning rather than an instruction. */
.onboarding-warn { color: var(--ink-pending); }
/* A service you are not signed into is a thing to do, not a thing to grey out.
   "Not signed in on Chrome" named our plumbing and offered no way forward; the
   row now carries the action that fixes it. Half-opacity is reserved for the
   moment one is connecting, when it genuinely cannot be touched. */
.service-connect {
  flex: none;
  padding: 4px 10px;
  font: 550 11.5px/1.5 var(--font-ui);
  color: var(--ink-body);
  background: var(--surface-2);
  border: 0;
  border-radius: var(--radius-chip);
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-soft),
              color var(--dur-hover) var(--ease-soft);
}
.service-connect:hover { background: var(--bg-hover); color: var(--ink); }
.service-connect:disabled { cursor: default; color: var(--ink-faint); }
.service-row[data-busy="true"] { opacity: 0.55; cursor: default; }
/* Disconnect sits beside "Signed in": available, never the loud choice. */
.service-connect.is-quiet {
  background: none;
  box-shadow: none;
  color: var(--ink-faint);
  font-weight: 500;
}
.service-connect.is-quiet:hover { color: var(--ink-danger); }

/* A flagged line on an open card (an unattended promotion) and the way out.
   Sits on its own line: it is a decision, not another crumb in the meta row. */
.memory-twin {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* One line. Wrapping let the button drop below instead of letting the twin's
     title give way, which spent three lines saying one thing. */
  flex-wrap: nowrap;
  margin-top: var(--space-2);
}
.memory-twin-of {
  color: var(--ink-muted);
  font-size: 12px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.memory-twin .btn { margin-left: auto; flex-shrink: 0; }
/* The twin's title is the part that can be any length, so it is the part that
   gives way. Without this the row pushed the button past the card's edge and
   the Archive button rendered as "Arch". */
.memory-twin-of { flex: 1 1 auto; }

/* -- rail switches --------------------------------------------------------- */
/* One-line on/off facts in the rail (today: whether agents' windows are on
   screen). Set in the rail-link register — same type, same gutter, same
   hover — because that is what it is: one more line of rail furniture, not a
   settings card that wandered in. The title names the state, the switch
   shows it, the tooltip teaches, and a sub-line appears only when something
   is actually happening. */
.rail-switch {
  display: flex; align-items: center; gap: 10px;
  margin: 0 10px 2px;
  padding: 6px 10px;
  border: none;
  border-radius: var(--radius-btn);
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-soft);
}
.rail-switch:hover { background: var(--bg-hover); }

/* No gap: the sub-line spaces itself when it has something to say, so the
   collapsed state is exactly one line tall with nothing left over. */
.rail-switch-text {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
}
.rail-switch-title { font-size: 11px; font-weight: 500; color: var(--ink-muted); }
.rail-switch:hover .rail-switch-title { color: var(--ink-body); }
.rail-switch[aria-checked="true"] .rail-switch-title { color: var(--ink-body); }
/* Always in the DOM, zero-height until a flip is in flight, so the status line
   ARRIVES as an animated height instead of snapping the rail's footer down a
   line. Two lines at most, clamped by height: -webkit-line-clamp paints its
   overflow outside the box in WKWebView. */
.rail-switch-sub {
  font-size: 10px; line-height: 14px; color: var(--ink-muted);
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: max-height 220ms var(--ease-soft),
              opacity 160ms linear,
              margin-top 220ms var(--ease-soft);
}
.rail-switch.has-status .rail-switch-sub {
  max-height: 28px;
  opacity: 1;
  margin-top: 1px;
}

/* Scaled to the rail's 13px line, not the dialog's: track height matches the
   cap height of the label beside it. */
.rail-switch-track {
  flex: none;
  position: relative;
  width: 26px; height: 14px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
  transition: background var(--dur-hover) var(--ease-soft);
}
.rail-switch[aria-checked="true"] .rail-switch-track {
  background: var(--violet);
  box-shadow: none;
}
.rail-switch-knob {
  position: absolute; top: 2px; left: 2px;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(24, 25, 32, 0.3);
  transition: transform var(--dur-hover) var(--ease-soft);
}
.rail-switch[aria-checked="true"] .rail-switch-knob { transform: translateX(12px); }
/* A switch mid-flight is not a switch: the flip is disabled while it runs,
   and this says so rather than looking merely unresponsive. */
.rail-switch.is-busy { cursor: progress; }
.rail-switch.is-busy .rail-switch-track { opacity: 0.55; }

/* -- where the agents run: Local | Cloud ----------------------------------- */
/* Both places named on the control, because an on/off switch only ever named
   one of them and "off" was a place too. A raised thumb slides to the side
   that is TRUE: mid-move it stays on Local, and Cloud shows a spinner where
   its glyph was, because the agents have not left yet. */
.run-where {
  display: flex; flex-direction: column;
  margin: 2px 10px 6px;
}
.run-where-track {
  position: relative;
  display: grid; grid-template-columns: 1fr 1fr;
  padding: 2px;
  border-radius: var(--radius-btn);
  background: var(--surface-sunken);
  box-shadow: var(--inset-field);
}
.run-where-track::before {
  content: "";
  position: absolute; top: 2px; bottom: 2px; left: 2px;
  width: calc(50% - 2px);
  border-radius: var(--radius-chip);
  background: var(--surface);
  box-shadow: var(--inset-highlight), var(--shadow-rest);
  transition: transform 240ms var(--ease-out), background var(--dur-hover) var(--ease-soft);
}
.run-where[data-where="cloud"] .run-where-track::before { transform: translateX(100%); }
/* On, but not there: the thumb stays on Cloud because that is what the owner
   asked for, and goes amber because it is not true yet. */
.run-where[data-state="waiting"] .run-where-track::before {
  background: color-mix(in srgb, var(--amber) 18%, var(--surface));
}

.run-where-opt {
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  min-width: 0;
  height: 24px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--radius-chip);
  background: none;
  font: 500 12px/1 var(--font-ui);
  color: var(--ink-muted);
  cursor: pointer;
  transition: color var(--dur-hover) var(--ease-soft);
}
.run-where-opt:hover { color: var(--ink-body); }
.run-where-opt[aria-pressed="true"] { color: var(--ink); cursor: default; }
.run-where-opt:focus-visible { outline-offset: 0; }
.run-where-opt .ic { font-size: 13px; }
.run-where[data-where="cloud"] .run-where-glyph { color: var(--violet); }
.run-where[data-state="waiting"] .run-where-glyph { color: var(--ink-pending); }

/* Locked: there, named, and one click from saying what unlocks it. Quieter
   than an available option, never greyed to the point of looking broken. */
.run-where-lock { display: none; font-size: 10px; color: var(--ink-faint); }
.run-where.is-locked .run-where-lock { display: block; }
.run-where.is-locked .run-where-opt[data-where="cloud"] { color: var(--ink-faint); }
.run-where.is-locked .run-where-opt[data-where="cloud"]:hover { color: var(--ink-muted); }
.run-where.is-locked .run-where-opt[data-where="cloud"]:hover .run-where-lock { color: var(--ink-muted); }

/* Mid-move, the Cloud glyph turns into a spinner, and clicking either half
   brings the progress back rather than starting anything. */
.run-where-spin {
  display: none;
  width: 11px; height: 11px;
  box-sizing: border-box;
  border-radius: 50%;
  border: 2px solid var(--violet);
  border-top-color: transparent;
  animation: spin 700ms linear infinite;
}
.run-where.is-busy .run-where-glyph { display: none; }
.run-where.is-busy .run-where-spin { display: block; }
.run-where.is-busy .run-where-opt { cursor: pointer; }
.run-where.is-busy .run-where-opt[data-where="cloud"] { color: var(--ink-body); }

/* Collapsed until there is news: a move in flight, or the reason Cloud is on
   while the agents are still here. Clamped by height, never line-clamp. */
.run-where-sub {
  padding: 0 4px;
  font-size: 10px; line-height: 14px;
  color: var(--ink-muted);
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: max-height 220ms var(--ease-soft),
              opacity 160ms linear,
              margin-top 220ms var(--ease-soft);
}
.run-where.has-status .run-where-sub { max-height: 28px; opacity: 1; margin-top: 4px; }
.run-where[data-state="waiting"] .run-where-sub { color: var(--ink-pending); }

/* The same control inside a dialog (New goal, a playbook run, New agent),
   where it picks the place for THIS goal rather than the default. One size
   down, and only as wide as its two words: stretched to the field it read as
   a tab strip. Two equal columns still, so the thumb's 50% stays true with
   the lock glyph on one side. Locked, the reason sits underneath in the same
   sub-line the rail uses, because a lock with no words is a broken button. */
.run-where.is-compact { margin: 0; align-items: flex-start; }
.run-where.is-compact .run-where-track { display: inline-grid; grid-template-columns: 1fr 1fr; }
.run-where.is-compact .run-where-opt { height: 22px; padding: 0 10px; font-size: 11px; }
.run-where.is-compact .run-where-opt .ic { font-size: 12px; }
.run-where.is-compact.is-locked .run-where-opt[data-where="cloud"] { cursor: default; }
.run-where.is-compact .run-where-sub { padding: 0 2px; }

/* Where a goal that already exists runs: a fact on the Edit and Steer
   dialogs' titles, drawn with the rail row's glyph. Not a control, because
   nothing can move a goal between kernels. */
.where-badge {
  display: inline-flex; align-items: center; gap: 4px;
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: var(--radius-chip);
  background: var(--surface-sunken);
  font: 500 11px/16px var(--font-ui);
  letter-spacing: 0;
  color: var(--ink-muted);
  vertical-align: 3px;
}
.where-badge .ic { font-size: 12px; }
.where-badge[data-where="cloud"] .ic { color: var(--violet); }

/* -- the cloud dialogs: consent, progress, locked --------------------------- */
/* One moment, so one shape: a centred picture of the move, a title, one line,
   then the detail. Narrower than a form dialog because it holds no form. */
.dialog.is-cloud { width: min(440px, 100%); }
.dialog.is-cloud h2 {
  margin: 0;
  padding: 0;
  border: 0;
  text-align: center;
}
.dialog.is-cloud h2 + * { margin-top: 6px; }
.dialog.is-cloud .dialog-lede {
  margin: 6px auto 0;
  text-align: center;
  max-width: 46ch;
  text-wrap: balance;
}

/* This computer, the road, the cloud. A dot travels the road while anything
   is (or is about to be) moving; locked, the road is still. */
.cloud-hero {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 8px 0 16px;
}
.cloud-hero-tile {
  position: relative;
  display: grid; place-items: center;
  width: 44px; height: 44px;
  border-radius: var(--radius-card);
  font-size: 22px;
}
.cloud-hero-tile.is-here {
  background: var(--surface-2);
  color: var(--ink-body);
  box-shadow: var(--inset-highlight);
}
.cloud-hero-tile.is-there {
  background: var(--icon-violet);
  color: #fff;
  box-shadow: var(--inset-icon-top), var(--glow-icon-violet);
}
.cloud-hero-path {
  position: relative;
  width: 64px; height: 2px;
  background: linear-gradient(90deg, var(--ink-faint) 50%, transparent 50%) 0 0 / 6px 2px repeat-x;
}
.cloud-hero-path::after {
  content: "";
  position: absolute; top: -2px; left: 0;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--violet);
  box-shadow: var(--glow-violet);
  animation: cloud-travel 1800ms var(--ease-soft) infinite;
}
@keyframes cloud-travel {
  0%   { transform: translateX(0);    opacity: 0; }
  15%  { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateX(58px); opacity: 0; }
}
.cloud-hero.is-locked .cloud-hero-path::after { display: none; }
.cloud-hero.is-locked .cloud-hero-tile.is-there {
  background: var(--surface-2);
  color: var(--ink-muted);
  box-shadow: var(--inset-highlight);
}
.cloud-hero-badge {
  position: absolute; right: -6px; bottom: -6px;
  display: grid; place-items: center;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-body);
  font-size: 11px;
  box-shadow: var(--inset-highlight), var(--shadow-rest);
}

/* What moving means, one icon and two short lines each. */
.cloud-facts {
  display: flex; flex-direction: column; gap: var(--space-3);
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
}
.cloud-fact {
  display: grid; grid-template-columns: 32px 1fr;
  column-gap: var(--space-3);
  align-items: center;
}
.cloud-fact-icon {
  grid-row: span 2;
  display: grid; place-items: center;
  width: 32px; height: 32px;
  border-radius: var(--radius-icon);
  background: var(--tint-violet);
  color: var(--ink-violet);
  font-size: 16px;
}
.cloud-fact-title { font-size: 13px; font-weight: 560; line-height: 18px; color: var(--ink); }
.cloud-fact-detail { font-size: 12px; line-height: 16px; color: var(--ink-muted); }

/* The move in progress: a bar that never stops moving, and the steps under it
   so a long one reads as a step taking its time, not a hang. */
.cloud-bar {
  position: relative;
  height: 6px;
  margin-top: 20px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  box-shadow: var(--inset-track);
  overflow: hidden;
}
/* Full width, slid in from the left: the bar repaints four times a second
   while it is open, and a transform moves on the compositor where a width
   would lay the dialog out again each time (see tests/test_animation_cost.py). */
.cloud-bar-fill {
  position: absolute; inset: 0;
  border-radius: inherit;
  background: var(--grad-violet);
  transform: translateX(-100%);
  transition: transform 400ms linear;
  overflow: hidden;
}
.cloud-bar-fill::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.38), transparent);
  transform: translateX(-100%);
  animation: cloud-shimmer 1400ms ease-in-out infinite;
}
@keyframes cloud-shimmer { to { transform: translateX(100%); } }
.cloud-bar.is-done .cloud-bar-fill::after { display: none; }

.cloud-steps {
  display: flex; flex-direction: column; gap: 10px;
  margin: 16px 0 0;
  padding: 0;
  list-style: none;
}
.cloud-step {
  display: flex; align-items: center; gap: 10px;
  font-size: 13px; line-height: 18px;
  color: var(--ink-faint);
}
.cloud-step-mark {
  flex: none;
  display: grid; place-items: center;
  width: 16px; height: 16px;
  box-sizing: border-box;
  border-radius: 50%;
  border: 2px solid currentColor;
  font-size: 9px;
}
.cloud-step-mark .ic { display: none; }
.cloud-step.is-active { color: var(--ink); }
.cloud-step.is-active .cloud-step-mark {
  border-color: var(--violet);
  border-top-color: transparent;
  animation: spin 700ms linear infinite;
}
.cloud-step.is-done { color: var(--ink-muted); }
.cloud-step.is-done .cloud-step-mark {
  border: 0;
  background: var(--violet);
  color: #fff;
}
.cloud-step.is-done .cloud-step-mark .ic { display: block; }
.cloud-step-time {
  margin-left: auto;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

.dialog-actions.is-split { justify-content: space-between; align-items: center; }
.cloud-foot-note { font-size: 12px; color: var(--ink-muted); }

@media (prefers-reduced-motion: reduce) {
  .rail-switch, .rail-switch-track, .rail-switch-knob,
  .run-where-track::before, .run-where-sub, .cloud-bar-fill { transition: none; }
  .cloud-hero-path::after, .cloud-bar-fill::after { animation: none; display: none; }
}

/* Dictation is attached to the field. Idle is the glyph alone — no chip —
   so the field stays a field. Listening and processing still colour the
   control so capture and finish are visible. */
.dictation-host { position: relative; }
input.has-dictation, textarea.has-dictation { padding-right: 48px; }
.dictation-trigger {
  position: absolute; z-index: 3; width: 32px; height: 32px;
  display: grid; place-items: center; padding: 0; box-sizing: border-box;
  border: 1px solid transparent; border-radius: 9px;
  background: none; color: var(--ink-violet); cursor: pointer;
  transition: background var(--dur-hover), color var(--dur-hover), box-shadow var(--dur-hover);
}
.dictation-trigger[hidden] { display: none; }
.dictation-trigger svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.dictation-trigger:hover { background: var(--bg-hover); color: var(--ink-violet); }
.dictation-trigger:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.dictation-trigger[data-state="listening"] { background: var(--tint-red); color: var(--ink-danger); border-color: var(--tint-red); }
.dictation-trigger[data-state="listening"]:hover { background: var(--danger); color: var(--surface); }
.dictation-trigger[data-state="processing"], .dictation-trigger[data-state="processing"]:hover { cursor: progress; opacity: 1; background: var(--tint-violet); color: var(--ink-violet); }
.dictation-trigger .dictation-stop-icon, .dictation-spinner { display: none; }
.dictation-trigger[data-state="listening"] .dictation-mic-icon path,
.dictation-trigger[data-state="processing"] svg { display: none; }
.dictation-trigger[data-state="listening"] .dictation-stop-icon { display: block; fill: currentColor; stroke: none; }
.dictation-trigger[data-state="processing"] .dictation-spinner { display: block; }
.dictation-spinner { width: 16px; height: 16px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; box-sizing: border-box; animation: dictation-spin .75s linear infinite; }
@keyframes dictation-spin { to { transform: rotate(360deg); } }
.dictation-row { position: absolute; z-index: 2; display: flex; align-items: center; gap: 10px; min-width: 0; background: var(--surface); border-radius: 8px; }
.dictation-row[hidden] { display: none; }
.dictation-cancel { flex: 0 0 auto; width: auto; height: 100%; aspect-ratio: 1; display: grid; place-items: center; padding: 0; border: 0; border-radius: 50%; background: var(--bg-hover); color: var(--ink); font: 400 24px/1 var(--font-ui); cursor: pointer; }
.dictation-cancel:hover { background: var(--surface-2); }
.dictation-cancel:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.dictation-waveform { flex: 1; min-width: 0; width: 0; height: 100%; color: var(--ink-muted); }
.dictation-status { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-muted); font-size: 12px; }
.dictation-host[data-dictation-state="listening"] .dictation-status { position: absolute; width: 1px; height: 1px; clip-path: inset(50%); overflow: hidden; }
.dictation-host:not([data-dictation-state="listening"]) .dictation-waveform { display: none; }
@media (prefers-reduced-motion: reduce) {
  .dictation-trigger { transition: none; }
  .dictation-spinner { animation: none; }
}

.bundle-dialog-note { margin: 0 0 20px; padding: 12px 14px; border-radius: 9px; background: var(--tint-violet); color: var(--ink-violet); font-size: 13px; line-height: 1.5; }
.task-method { grid-column: 3; margin-left: 0; color: var(--ink-muted); font-size: 11px; }

/* The campaign's methods, one row each, on the card face. Same grid rhythm
   and state glyphs as the plan's task rows below it, so a method reads as a
   task rolled up rather than a second vocabulary; the figure is the one
   column that matters and is the only one set in the card's own ink. */
.methods { margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid var(--hairline); }
.methods-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; }
.methods-label { font-size: 12px; font-weight: 600; color: var(--ink); }
.methods-note { font-size: 11.5px; color: var(--ink-muted); }
.method {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr) auto minmax(0, auto) auto;
  column-gap: 10px;
  align-items: center;
  padding: 3px 0;
  font-size: 13px;
}
.method-mark { font-size: 13px; color: var(--ink-faint); text-align: center; }
.method[data-state="done"] .method-mark { color: var(--ink-success); }
.method[data-state="running"] .method-mark { color: var(--ink-pending); }
.method[data-state="waiting"] .method-mark { color: var(--ink-pending); }
.method-name { color: var(--ink-body); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.method[data-state="running"] .method-name { color: var(--ink); font-weight: 600; }
.method-figure { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.method[data-state="dropped"] { opacity: 0.45; }
.method[data-state="dropped"] .method-name { text-decoration: line-through; }
.method-tally.row-line { flex-wrap: nowrap; overflow: hidden; }
.method-skip { padding: 3px 10px; font-size: 12px; }
@media (max-width: 620px) {
  .method { grid-template-columns: 16px minmax(0, 1fr) auto; }
  .method-tally { grid-column: 2; }
  .method-skip { grid-column: 3; }
}

.bundle-preferences { margin: 8px 0 20px; border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); padding: 12px 0; }
.bundle-preferences summary { color: var(--ink-violet); font-size: 13px; cursor: pointer; }
.bundle-preferences[open] summary { margin-bottom: 16px; }
.bundle-preferences .field:last-child { margin-bottom: 0; }
/* The channel switches: one pill per account or site the library opens, all
   on. Off is muted and struck through rather than removed, so the owner can
   see what they said no to and put it back. The pill radius, the sunken bed
   and the muted ink are the state chip's own. */
.channel-switches { display: flex; flex-wrap: wrap; gap: 6px; }
.channel-switch {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px 4px 7px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--ink);
  font-size: 12px; font-weight: 500;
  cursor: pointer;
}
.channel-switch .channel-count { font-size: 10px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.channel-switch[aria-pressed="false"] {
  background: var(--surface-sunken); color: var(--ink-muted); text-decoration: line-through;
}
.channel-switch[aria-pressed="false"] img { opacity: .35; filter: grayscale(1); }
.is-bundle-dialog .dialog-note { background: transparent; padding: 0; color: var(--ink-muted); font-size: 12px; line-height: 1.5; }

/* Recovery sits beside campaign progress, same hairline as every other card. */
.recovery-notice { margin: 14px 0; padding: 14px 16px; border: 1px solid var(--hairline); border-radius: 8px; background: var(--surface-2); }
.recovery-title { color: var(--ink); font-size: 13px; font-weight: 600; }
.recovery-notice p, .recovery-next, .need-checkpoint, .need-account { margin: 7px 0 0; font-size: 12px; line-height: 1.6; color: var(--ink-body); }
.recovery-next { color: var(--ink-muted); }
.recovery-link { display: block; margin-top: 8px; text-align: left; white-space: normal; color: var(--ink-violet); }
.need-card:focus { outline: 2px solid var(--violet); outline-offset: 3px; }

/* Repeat belongs with the action it modifies, not in a warning panel. */
.goal-loop { margin-right: auto; display: inline-flex; align-items: center; gap: 8px; cursor: pointer; color: var(--ink-muted); }
.goal-loop input { appearance: none; width: 34px; height: 20px; padding: 2px; margin: 0; border: 0; border-radius: 20px; background: var(--surface-sunken); box-shadow: var(--inset-field); cursor: pointer; }
.goal-loop input::before { content: ""; display: block; width: 16px; height: 16px; border-radius: 50%; background: var(--ink-muted); transition: transform 120ms; }
.goal-loop input:checked { background: var(--violet); }
.goal-loop input:checked::before { transform: translateX(14px); background: white; }
.goal-loop input:focus-visible { outline: 2px solid var(--violet); outline-offset: 3px; }

.session-left > .head { align-items: center; padding-top: 8px; padding-bottom: 12px; }
.session-left > .head h1 { font-size: 20px; line-height: 1.4; font-weight: 600; }

/* A page title sits on the brand's line. Both rows hang off the same edge (the
   traffic lights' strip on macOS, a 14px inset under a native bar elsewhere),
   and the brand's 22px mark at +2 puts its centre 13px below that edge. Half
   of a 26px title line is the same 13px, so the head pins the title to the top
   rather than centring the row: centred, a view's buttons decided where its
   title sat (64 with none, 67.75 with some, against the brand's 55). Controls
   centre on the title line and may overhang it. They must never grow it. */
.view > .head { align-items: flex-start; padding-top: 0; padding-bottom: 12px; }
:root[data-os="windows"] .view > .head,
:root[data-os="linux"] .view > .head,
:root[data-platform="web"] .view > .head { padding-top: 12px; }
.view > .head h1 { font-size: 20px; line-height: 26px; font-weight: 600; }
.view > .head .controls { height: 26px; align-items: center; padding-top: 0; }
/* Under a strip (the notice, first run's line) the title is off the brand's
   line anyway, and flush against the strip the controls that overhang the
   title line sat on its lower edge: People's Find customers drew its border
   across the asks strip. Sibling selectors, not :has(), for WKWebView. */
.notice:not([hidden]) ~ .view > .head,
.guide:not([hidden]) ~ .view > .head { padding-top: 12px; }

.pb-header-marks { flex: none; margin: 0; gap: 5px; }
.pb-header-marks .pb-touch { width: 32px; height: 32px; }
.pb-header-marks .pb-touch img { width: 24px; height: 24px; }

/* The Lanes Session Connector, at the head of Connections. It is how
   sign-ins reach Lanes, and setup is not finished without it, so until it has
   synced it is a raised card with its three steps, the one the owner is on in
   violet: what it is, where to press, what their browser is about to ask. Once it
   syncs it becomes the list's first row, in the list's own grammar: the
   browser's logo and name, what it is doing where a row says "Signed in"
   (green while it works, amber once it goes quiet -- a greyed line read as
   "fine"), one line under the name, and the other browsers under that. */
.session-connector { margin: 0 0 18px; }
.session-connector[hidden] { display: none; }
/* The row, in the table's columns (`.connections-table`): logo, name, state
   across the state and button columns, and the lines under the name. The
   logo sits on the name's line, as each row's does; centred on the whole
   block, it sat a line and a half below the name it belongs to. */
.session-connector:is([data-state="on"], [data-state="stale"], [data-state="paused"]) {
  display: grid;
  grid-template-columns: [art] 20px [name] minmax(0, 1fr) [state] auto [actions] auto [end];
  column-gap: 11px;
  row-gap: 2px;
  align-items: center;
}
.session-connector > .service-art { grid-area: 1 / art; }
.session-connector > .session-connector-title { grid-area: 1 / name; }
.session-connector > .session-connector-state { grid-area: 1 / state / auto / end; }
.session-connector:is([data-state="on"], [data-state="stale"], [data-state="paused"]) > .session-connector-note,
.session-connector:is([data-state="on"], [data-state="stale"], [data-state="paused"]) > .connector-others {
  grid-column: name / end;
  max-width: none;
}
/* Working: the table's own first row. Subgrid puts its state on the rows'
   state column, and the list's top hairline is the one line between it and
   the first service. It drew a hairline of its own there too, 18px above
   the list's, with nothing between them. */
.session-connector[data-state="on"] {
  grid-template-columns: subgrid;
  margin: 0;
  padding: 7px 2px 9px;
}
.session-connector .service-art {
  width: 20px;
  height: 20px;
  flex: none;
  display: grid;
  place-items: center;
}
.session-connector .service-art img {
  width: 16px;
  height: 16px;
  object-fit: contain;
}
.session-connector-text {
  display: grid;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.session-connector-title {
  font-size: 13px;
  font-weight: 550;
  color: var(--ink);
}
.session-connector-note {
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-muted);
  max-width: 64ch;
}
.session-connector .btn { flex: none; }
/* What it is doing, in a row's "Signed in" type and colour. */
.session-connector-state {
  justify-self: start;
  font-size: 11.5px;
  white-space: nowrap;
  color: var(--online);
}
/* Gone quiet: a tinted line of its own above the list, so it is not read
   past, with its state in amber. */
.session-connector[data-state="stale"] {
  padding: 10px 12px;
  border-radius: var(--radius-card);
  background: var(--tint-amber);
}
.session-connector[data-state="stale"] .session-connector-state { color: var(--ink-pending); }
/* Paused by the owner: the stale line's shape, without its alarm. */
.session-connector[data-state="paused"] {
  padding: 10px 12px;
  border-radius: var(--radius-card);
  background: var(--tint-violet);
}
.session-connector[data-state="paused"] .session-connector-state { color: var(--ink-violet); }

/* Not connected yet: the card. */
.session-connector[data-state="off"],
.session-connector[data-state="installed"] {
  display: block;
  padding: 18px 18px 14px;
  border: 1px solid color-mix(in srgb, var(--violet) 30%, var(--hairline));
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-card);
}
.session-connector-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}
.session-connector-head .service-art {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-icon);
  background: var(--surface-2);
}
.session-connector-head .service-art img { width: 20px; height: 20px; }
.session-connector-kicker {
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--violet);
}
.session-connector-head .session-connector-title {
  font-size: 15px;
  font-weight: 650;
}
.connector-steps {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
}
.connector-step {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 0;
  border-top: 1px solid var(--hairline);
}
.connector-step-mark {
  width: 22px;
  height: 22px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 12px;
  font-weight: 650;
  background: var(--surface-2);
  color: var(--ink-muted);
}
.connector-step-mark .ic { font-size: 12px; }
.connector-step[data-state="active"] .connector-step-mark { background: var(--violet); color: #fff; }
.connector-step[data-state="done"] .connector-step-mark { background: var(--tint-green); color: var(--ink-success); }
.connector-step-body {
  display: grid;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
.connector-step-title { font-size: 13px; font-weight: 600; color: var(--ink); }
.connector-step[data-state="todo"] .connector-step-title { color: var(--ink-muted); }
.connector-step[data-state="done"] .connector-step-title { color: var(--ink-success); }
.connector-step-note {
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-muted);
  max-width: 64ch;
}
.connector-step .btn { flex: none; }
.connector-foot {
  margin: 8px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--hairline);
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-muted);
}

/* The connector for the owner's other browsers, under the card's steps (or
   on its own line under the synced line): one button each, and what that
   browser will ask first where it asks something (Edge, Opera). */
.connector-others {
  display: grid;
  gap: 8px;
  margin: 12px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
}
.connector-others-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
}
.connector-other {
  display: flex;
  align-items: center;
  gap: 10px;
}
.connector-other .btn { flex: none; }
.connector-other-note {
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-muted);
  max-width: 56ch;
}
/* Connected: one line under the explanation, in the rows' own controls --
   the label, then a quiet button per browser, the size of Change and
   Disconnect. What a browser asks first (Edge, Opera) is its button's hint,
   and a line of its own under the buttons once that button is pressed. It
   was three outlined buttons with Edge's sentence always under them, the
   heaviest thing on the page for the one part of it already done. */
.session-connector:is([data-state="on"], [data-state="stale"], [data-state="paused"]) .connector-others {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 4px;
  margin: 2px 0 0;
  padding: 0;
  border: 0;
}
.session-connector:is([data-state="on"], [data-state="stale"], [data-state="paused"]) .connector-others-label {
  margin-right: 4px;
  font-weight: 500;
}
.session-connector:is([data-state="on"], [data-state="stale"], [data-state="paused"]) .connector-other {
  display: contents;
}
.session-connector:is([data-state="on"], [data-state="stale"], [data-state="paused"]) .connector-other-note {
  order: 1;
  flex-basis: 100%;
  max-width: none;
  margin-top: 2px;
}
/* Adding the connector to another browser is not the destructive quiet
   button Disconnect is: its hover lifts like the rows' Sign in, and never
   turns red. */
.connector-other .service-connect.is-quiet:hover { color: var(--ink); background: var(--bg-hover); }

/* A dot on the Connections link while the connector needs the owner. */
#open-connections[data-alert], .nav [data-view="connections"][data-alert] { position: relative; }
#open-connections[data-alert]::after, .nav [data-view="connections"][data-alert]::after {
  content: "";
  position: absolute;
  right: 10px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -3.5px;
  border-radius: 50%;
  background: var(--amber);
}

/* The sending address and Gmail account action beside a play's Start button. */
.sends-from-line { margin-top: 2px; }
.sends-from-change {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  color: var(--ink-muted);
  cursor: pointer;
  transition: color var(--dur-hover) var(--ease-soft);
}
.sends-from-change:hover { color: var(--ink); }

/* Plan & Usage: how many agents run at once (docs/cloud-scale.md). */
.concurrency{display:flex;flex-direction:column;gap:12px}
.concurrency-rows{display:flex;flex-direction:column;gap:10px}
.concurrency-row{display:flex;align-items:baseline;justify-content:space-between;gap:16px}
.concurrency-label{display:flex;flex-direction:column;gap:2px;font-size:13px;color:var(--ink-body)}
.concurrency-note{font-size:12px;color:var(--ink-muted)}
.concurrency-value{font-size:20px;font-weight:650;letter-spacing:-.3px;white-space:nowrap;color:var(--ink)}
.concurrency-more{align-self:flex-start}

/* Gmail accounts are native integrations, separate from browser sign-ins. */
.gmail-accounts { display: grid; gap: 16px; width: 100%; min-width: 0; margin-bottom: 28px; }
.gmail-intro { display: flex; align-items: flex-start; gap: 14px; }
.gmail-mark { width: 30px; height: 30px; object-fit: contain; margin-top: 3px; }
.gmail-title { font-size: 18px; font-weight: 650; }
.gmail-help { color: var(--ink-muted); max-width: 570px; margin-top: 5px; line-height: 1.55; }
.gmail-connect-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.gmail-add-account { width: 44px; height: 44px; padding: 10px; }
.gmail-add-account svg { width: 20px; height: 20px; }
.gmail-notice, .gmail-empty { color: var(--ink-muted); line-height: 1.6; }
.gmail-error { color: var(--ink-danger); line-height: 1.6; }
.gmail-account-list { display: grid; gap: 10px; }
.gmail-account { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 16px; border: 1px solid var(--hairline); border-radius: 12px; background: var(--surface); }
.gmail-avatar { display: grid; place-items: center; width: 38px; height: 38px; flex: 0 0 38px; background: var(--canvas); border: 1px solid var(--hairline); border-radius: 50%; font-weight: 600; overflow: hidden; }
.gmail-avatar img { width: 100%; height: 100%; object-fit: cover; }
.gmail-account-info { display: flex; flex-wrap: wrap; column-gap: 10px; row-gap: 3px; flex: 1; min-width: 0; }
.gmail-address { flex: 0 0 100%; overflow-wrap: anywhere; font-size: 13px; font-weight: 600; user-select: text; }
@media (max-width: 620px) {
  .gmail-account-info { flex: 1 0 calc(100% - 50px); }
}
.gmail-account-status { font-size: 12px; color: var(--ink-muted); display: flex; align-items: center; gap: 5px; }
.gmail-account-status::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--success); }
.gmail-account-status[data-tone="warn"]::before { background: var(--amber); }
.gmail-primary { color: var(--ink-muted); font-size: 12px; }
.gmail-account-actions { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.gmail-account-actions .btn { font-size: 12px; padding: 5px 9px; }
.gmail-manage { flex: 0 0 100%; min-width: 0; }
.gmail-manage summary { width: fit-content; cursor: pointer; color: var(--ink-muted); font-size: 12px; padding: 3px 0; }
.gmail-manage .gmail-usage { margin: 8px 0; line-height: 1.6; }
.gmail-manage .gmail-account-actions { margin-top: 8px; }
.gmail-account-actions button { min-height: 30px; }
.gmail-disconnect-confirm { flex: 0 0 100%; padding-top: 12px; border-top: 1px solid var(--hairline); display: grid; gap: 8px; font-size: 12px; }
.gmail-disconnect-confirm p { color: var(--ink-muted); line-height: 1.6; }
.gmail-remove { color: var(--danger); border-color: var(--hairline); }
.gmail-beta { color: var(--ink-muted); font-size: 12px; max-width: 620px; }
.gmail-beta summary { cursor: pointer; }
.gmail-beta p { margin-top: 8px; line-height: 1.6; }

.pal-guide-email { padding: 14px 0 0; border-top: 1px solid var(--pal-line); font-size: 12px; color: var(--pal-muted); display: grid; gap: 7px; }

.gmail-usage { flex: 0 0 100%; font-size: 12px; color: var(--ink-muted); }
