/* GMID Platform — shared component styles
   Design tokens (custom properties) are defined in base_team.html / base_portal.html.
   This file adds shared UI components used across tools. */

/* ── Scroll lock: freeze page scroll for a take-over surface ──
   Neutral, view-agnostic lock (#504). The unified workspace's crop take-over
   toggles `scroll-locked`. */
html.scroll-locked,
html.scroll-locked body {
  overflow: hidden !important;
  height: 100vh !important;
}

html.scroll-locked .main {
  padding-bottom: 0 !important;
  margin-bottom: 0 !important;
  overflow: hidden !important;
}

/* ── Base typography ───────────────────────────────────────────────────────
   Default body type: 12px / normal weight, mirroring the People-tab
   density. Components that explicitly declare a larger size (buttons,
   inputs, sidebar nav, cards, modals, page titles, etc.) win via the
   cascade and keep their current sizing until shrunk individually. */
body {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
}

/* ── Text alignment utilities ──────────────────────────────────────────────── */

/* !important so utility classes beat component rules like `.data-table th`
   without needing per-context overrides. Convention matches Tailwind/Bootstrap. */
.text-left {
  text-align: left !important;
}

.text-center {
  text-align: center !important;
}

.text-right {
  text-align: right !important;
}

/* ── View switcher (page-level pill toggle: Cases / Documents) ─────────────── */

.view-switcher {
  display: inline-flex;
  background: var(--color-surface-muted);
  padding: var(--space-1);
  border-radius: var(--radius-md);
  gap: 2px;
}

.view-switcher-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  color: var(--color-subtle);
  text-decoration: none;
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  border: 0;
  background: transparent;
  cursor: pointer;
  transition: background var(--transition-normal), color var(--transition-normal);
}

.view-switcher-btn svg {
  width: 16px;
  height: 16px;
}

.view-switcher-btn:hover {
  color: var(--color-text);
}

.view-switcher-btn--active {
  background: var(--color-surface);
  color: var(--color-text-strong);
  box-shadow: var(--shadow-sm);
}

.view-switcher-btn--active:hover {
  color: var(--color-text-strong);
}

/* ── Unified workspace (#503, Slice 1) ──────────────────────────────────────
   The view-density slider, the span-scaled grid, and the basic reader. Gated
   on `workspace_config.ui == "unified"` (the `.tool-ws-unified` class + the
   `.tool-ws--stop-*` class that slider.js sets). The legacy switcher / grid /
   review CSS above is untouched — this is a parallel path, deleted whole when
   every doc type cuts over. */

/* View-density slider (#513) — a track + tick-stops + a snapping knob that
   slides between them. Semantically still the #503 radiogroup (roving
   tabindex + arrow keys live in slider.js, unchanged); this is the visual
   only. The knob is a single element (`::after`) translated to the active
   tick via a `--ws-knob-i` index set in CSS from which option carries
   `--active` (`:has()`), so the JS never positions it. The step count varies
   (3 for section-tabbed, 5 otherwise) — the unmatched nth-child rules below
   simply don't fire. */
.tool-ws-slider {
  position: relative;
  display: inline-flex;
  align-items: center;
  height: var(--control-h);
  padding: 0 var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface-muted);
  cursor: grab;
  /* Drag is pointer-driven (slider.js); stop the page scrolling on touch-drag. */
  touch-action: none;
  /* Per-step distance + knob geometry; the active index drives the slide. */
  --ws-slider-step: 32px;
  --ws-knob-size: 24px;
  --ws-knob-i: 0;
}

/* While dragging: grabbing cursor, no text selection, and the knob tracks the
   pointer 1:1 (transition off — it animates only on the snap at release). */
.tool-ws-slider--dragging {
  cursor: grabbing;
  user-select: none;
}

.tool-ws-slider--dragging::after {
  transition: none;
}

/* The track line — runs between the first and last tick centres (half a step
   inset on each side, independent of how many ticks there are). */
.tool-ws-slider::before {
  content: "";
  position: absolute;
  top: 50%;
  left: calc(var(--space-1) + var(--ws-slider-step) / 2);
  right: calc(var(--space-1) + var(--ws-slider-step) / 2);
  height: 2px;
  margin-top: -1px;
  background: var(--color-border);
  border-radius: var(--radius-pill);
  pointer-events: none;
}

/* The snapping knob — slides to the active tick on the compositor (transform
   only). Sits behind the icons; the active icon flips to on-accent. */
.tool-ws-slider::after {
  content: "";
  position: absolute;
  top: 50%;
  left: calc(var(--space-1) + (var(--ws-slider-step) - var(--ws-knob-size)) / 2);
  width: var(--ws-knob-size);
  height: var(--ws-knob-size);
  margin-top: calc(var(--ws-knob-size) / -2);
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  transform: translateX(calc(var(--ws-knob-i) * var(--ws-slider-step)));
  transition: transform var(--transition-normal);
  pointer-events: none;
}

.tool-ws-slider-opt {
  position: relative;
  z-index: var(--z-base);
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--control-h);
  width: var(--ws-slider-step);
  color: var(--color-muted);
  background: transparent;
  border: none;
  cursor: pointer;
  transition: color var(--transition-normal);
}

.tool-ws-slider-opt svg {
  width: 16px;
  height: 16px;
}

.tool-ws-slider-opt:hover {
  color: var(--color-text);
}

.tool-ws-slider-opt--active,
.tool-ws-slider-opt--active:hover {
  color: var(--color-surface);
}

.tool-ws-slider-opt:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Knob position = the active option's index. Pure CSS off the `--active`
   class slider.js already toggles, so the slide needs no JS. */
.tool-ws-slider:has(.tool-ws-slider-opt:nth-child(2).tool-ws-slider-opt--active) {
  --ws-knob-i: 1;
}
.tool-ws-slider:has(.tool-ws-slider-opt:nth-child(3).tool-ws-slider-opt--active) {
  --ws-knob-i: 2;
}
.tool-ws-slider:has(.tool-ws-slider-opt:nth-child(4).tool-ws-slider-opt--active) {
  --ws-knob-i: 3;
}
.tool-ws-slider:has(.tool-ws-slider-opt:nth-child(5).tool-ws-slider-opt--active) {
  --ws-knob-i: 4;
}
/* 6th stop = the timeline option, present only on section-tabbed types. Without
   this the knob never reaches it, so the active icon flips to on-accent white
   with no knob behind it — an invisible timeline icon (#571 follow-up). */
.tool-ws-slider:has(.tool-ws-slider-opt:nth-child(6).tool-ws-slider-opt--active) {
  --ws-knob-i: 5;
}

@media (prefers-reduced-motion: reduce) {
  .tool-ws-slider::after {
    transition: none;
  }
}

/* Equal-height, density-driven grid (#513, reverses the fixed-column model).
   The slider sets one card HEIGHT (`--ws-card-h`); every card shares it, so
   rows are even. Each card's WIDTH derives from that height by orientation:
   portrait = 0.75·h (native 3:4); landscape/spread clamp to ≤ 1.3·portrait =
   0.975·h (near-square — wide content letterboxes via `object-fit: contain`,
   the per-image aspect deliberately NOT piped). Cards are fixed-width flex
   items that wrap, so columns derive from the container width: collapsing a
   sidebar adds columns at the same card size, it never enlarges cards (the
   `repeat(24,1fr)` failure this corrects). Rows pack top-aligned, ragged
   right edge — no JS justification pass. The stop class swaps `--ws-card-h`
   only; the reflow is instant (never animate width/flex-basis — it thrashes
   layout every frame). */
.tool-ws-unified .tool-ws-grid {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  align-items: flex-start;
  gap: var(--space-5);
  /* Default = the grid-3 density (slider.js DEFAULT_GRID_STOP); the stop
     class overrides it. Standalone fallback for any unclassed unified grid. */
  --ws-card-h: 220px;
}

.tool-ws-unified .tool-ws-grid .page-card {
  flex: 0 0 auto;
  /* WYSIWYG (revises #520): the card aspect is the page's TRUE *displayed* aspect
     (--page-ar, set inline by displayed_aspect()) — no clamp. The grid is a QA
     surface, so a card must show exactly the image (and therefore the export);
     a clamped/letterboxed card risks judging a page on an approximation. Cards
     stay equal-HEIGHT (--ws-card-h); widths now vary freely with each page's
     aspect (a portrait cover is genuinely narrower than a landscape spread —
     #520's even-width clamp is dropped in favour of fidelity). Fallback 0.75 /
     1.3333 (landscape anchor) only for a missing page with no extracted_file.
     --card-ar is shared by the thumb aspect + rotated swap so all stay in step. */
  --card-ar: var(--page-ar, 0.75);
  width: calc(var(--ws-card-h) * var(--card-ar));
  align-self: flex-start;
}

/* Thumb takes the card's true aspect, so its --ws-card-h height yields the exact
   card width and the contained image fills edge-to-edge with no letterbox. One
   rule for both anchors — --card-ar carries each page's real aspect. */
.tool-ws-unified .tool-ws-grid .page-card-thumb {
  aspect-ratio: var(--card-ar, 3 / 4);
}

/* Rotated cards: size the <img> to the reciprocal of the card aspect so, once
   transform: rotate() spins it, the object-view-box region fills the box with no
   frame (.page-card-thumb is already flex-centred). Generic form of the old
   hardcoded per-bucket swaps; mirrors the filmstrip + reader.js _sizeImage. */
.tool-ws-unified .tool-ws-grid .page-card.ws-page--rotated .page-card-thumb img {
  width: calc(100% / var(--card-ar, 1));
  height: calc(100% * var(--card-ar, 1));
}

/* Grid density stops: ≈8 / 6 / 4 portrait cards per row at a typical width,
   more when the view is wider (columns follow container width). The slider
   sets only the shared card height; widths and column count derive from it. */
.tool-ws--stop-grid-4 .tool-ws-grid {
  --ws-card-h: 160px;
}

.tool-ws--stop-grid-3 .tool-ws-grid {
  --ws-card-h: 220px;
}

.tool-ws--stop-grid-2 .tool-ws-grid {
  --ws-card-h: 340px;
}

/* ── Landscape-anchored fallback (was #520's clamp) ───────────────────────
   Doc types whose pages are predominantly landscape (passport / ID / annex;
   `workspace_config.card_aspect == "landscape"`). With true-aspect cards the
   anchor no longer clamps — it only supplies the missing-page fallback aspect
   (landscape 4:3 instead of portrait 3:4) for a card with no extracted_file.
   Real pages use their own --page-ar via the base rule. */
.tool-ws-unified .tool-ws-grid--landscape .page-card {
  --card-ar: var(--page-ar, 1.3333);
}

/* Density stops re-tuned for the wider landscape cards (~1.78× the portrait
   width), so the stops still read dense / medium / large rather than dropping
   to one card per row. Tuned by eye on a passport in /qualitytest. */
.tool-ws--stop-grid-4 .tool-ws-grid--landscape {
  --ws-card-h: 120px;
}

.tool-ws--stop-grid-3 .tool-ws-grid--landscape {
  --ws-card-h: 175px;
}

.tool-ws--stop-grid-2 .tool-ws-grid--landscape {
  --ws-card-h: 270px;
}

/* Grid ↔ reader visibility. The grid shows at the grid stops; the reader
   (filmstrip rail + reading pane) shows at the two reader stops. Toggling
   `display` is instant — no layout-animating transition. */
.tool-ws-unified .tool-ws-reader {
  display: none;
  gap: 0;
  align-items: flex-start;
}

.tool-ws--stop-reader .tool-ws-grid,
.tool-ws--stop-reader-rail .tool-ws-grid {
  display: none;
}

.tool-ws--stop-reader .tool-ws-reader,
.tool-ws--stop-reader-rail .tool-ws-reader {
  display: flex;
}

/* Filmstrip rail — hidden at the no-rail reader stop. */
.tool-ws-reader-rail {
  flex: 0 0 104px;
  display: flex;
  flex-direction: column;
  /* Breathing room between filmstrip thumbs so adjacent pages don't read as one
     block — restrained so the rail stays a dense nav strip. */
  gap: var(--space-3);
  /* Fill the workspace body height (#518); thumbs scroll within. Was a fixed
     78vh from before the flex height-chain was definite. */
  max-height: 100%;
  overflow-y: auto;
  /* The filmstrip shares the reading stage's surface — same bg, no dividing
     rule — so the thumbs read as floating on one continuous canvas. The only
     line left in the strip is each thumb's own border (below). */
  padding-right: var(--space-3);
  background: var(--color-bg);
}

/* Bleed the filmstrip's surface through the workspace's left gutter (same as
   the no-rail reading stage): the 18px gutter was a white strip beside the now-
   blue rail. Widen the rail by the gutter, pull it left by the gutter, and pad
   the thumbs back — so the blue surface fills the gutter while the thumbs keep
   their size and position (a fixed-width column can't just margin-pad like the
   flexible stage does). */
.tool-ws--stop-reader-rail .tool-ws-reader-rail {
  flex-basis: calc(104px + var(--space-ws-gutter));
  margin-left: calc(-1 * var(--space-ws-gutter));
  padding-left: var(--space-ws-gutter);
}

.tool-ws--stop-reader .tool-ws-reader-rail {
  display: none;
}

/* ── Section-tabbed body (#571) — strip + grid↔timeline by slider stop ──────
   relationship_doc & peers in the unified shell. The slider's far stop is the
   timeline (not a reader); the section-tab strip filters whichever body shows.
   Sidebars are NOT here — they fold into the focus panel (#505). */
.tool-ws-unified .tool-ws-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

/* Tab strip — a separating hairline under the tabs (matches the legacy
   `.tool-ws-tabs-row` border the strip used to sit on). The active tab's
   accent border draws over it. `margin-bottom: 0` cancels the `.section-tabs`
   -1px (which was tuned to overlap a parent border, absent here). */
/* The strip row (#1099) — tabs on the left, the add control docked right. The
   hairline moved from the strip onto this row so it runs the full width instead
   of stopping where the tabs end. The geometry the tabs depend on is unchanged:
   the row has no padding, so its border sits exactly where the strip's used to,
   and `.section-tab`'s own `margin-bottom: -1px` still pulls the active tab's
   2px accent border over it. */
.tool-ws-section-strip-row {
  display: flex;
  align-items: stretch;
  gap: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}

.tool-ws-section-strip {
  margin-bottom: 0;
  flex: 1 1 auto;
  min-width: 0;
}

/* The grid wrapper shows at the grid stops; the timeline at the timeline stop.
   Toggling display is instant — no layout-animating transition. */
.tool-ws-unified .tool-ws-section-timeline {
  display: none;
}

.tool-ws-unified.tool-ws--stop-timeline .tool-ws-section-grids {
  display: none;
}

.tool-ws-unified.tool-ws--stop-timeline .tool-ws-section-timeline {
  display: block;
}

/* A body-takeover tab (synopsis) ignores the slider stop — it owns the body in
   both the grid AND timeline stops. At the timeline stop, keep showing the grids
   wrapper (only the active takeover panel is visible inside it) and hide the
   timeline (#571). */
.tool-ws-unified.tool-ws--stop-timeline .tool-ws-section[data-active-takeover] .tool-ws-section-grids {
  display: block;
}

.tool-ws-unified.tool-ws--stop-timeline .tool-ws-section[data-active-takeover] .tool-ws-section-timeline {
  display: none;
}

/* Only the active section tab's panel renders. */
.tool-ws-unified .tool-ws-section-panel {
  display: none;
}

.tool-ws-unified .tool-ws-section-panel--active {
  display: block;
}

/* Section-tabbed reader stops: the tab strip stays visible (switch sections
   while reading — section-tabs.js re-scopes the reader), the grids hide, and
   the reader (shown by the shared .tool-ws--stop-reader rules) takes the body,
   scoped to the active section. The timeline is already hidden off its stop. */
.tool-ws-unified.tool-ws--stop-reader .tool-ws-section-grids,
.tool-ws-unified.tool-ws--stop-reader-rail .tool-ws-section-grids {
  display: none;
}

/* …unless a body-takeover tab is active (#922). Same exception the timeline
   stop got in #571, for the same reason: a takeover owns the body at every
   stop, which is what "takeover" means.

   Only the mount path can reach this state. section-tabs.js `onStop` already
   bounces a takeover tab to All when the operator *clicks* a view stop, but
   that bounce is guarded to user actions — so a `?tab=_synopsis` deep-link
   landing on a restored reader stop kept the takeover active with its wrapper
   hidden, and the Synopsis tab rendered as an empty body. */
.tool-ws-unified.tool-ws--stop-reader .tool-ws-section[data-active-takeover] .tool-ws-section-grids,
.tool-ws-unified.tool-ws--stop-reader-rail .tool-ws-section[data-active-takeover] .tool-ws-section-grids {
  display: block;
}

/* …and the reader yields, or the two of them claim the body at once. The
   reader is a *sibling* of the section (it is rendered once for the whole
   workspace, not per section), so unlike the timeline exception this one has
   to read the takeover state off their shared root. */
.tool-ws-unified.tool-ws--stop-reader:has(.tool-ws-section[data-active-takeover]) .tool-ws-reader,
.tool-ws-unified.tool-ws--stop-reader-rail:has(.tool-ws-section[data-active-takeover]) .tool-ws-reader {
  display: none;
}

/* Attention dot — a section with undated or redo-flagged pages. */
.tool-ws-section-attention-dot {
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-warning);
  margin-left: var(--space-1);
  flex: 0 0 auto;
}

/* Empty section panel. */
.tool-ws-section-empty {
  padding: var(--space-6);
  color: var(--color-muted);
  text-align: center;
}

/* Per-section "add to this section" affordance (#832) — the section-tabbed
   counterpart to the grid add-tile. Click-to-browse (hidden file input) OR a
   drop target (section-tabs.js `_bindTileDrop`); either way pages land in the
   tab's page_meta.section_tab. Trailing variant sits below a populated grid. */
.tool-ws-section-add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-muted);
  font-size: var(--font-sm);
  cursor: pointer;
  transition: border-color var(--transition-fast), color var(--transition-fast),
    background var(--transition-fast);
}

.tool-ws-section-add:hover,
.tool-ws-section-add--drop {
  border-color: var(--color-accent);
  color: var(--color-accent);
  background: var(--color-hover);
}

/* Empty-section variant: fills the panel as the primary call-to-action so an
   empty section is directly actionable (mirrors the empty-requirement
   dropzone). */
.tool-ws-section-add--empty {
  flex-direction: column;
  gap: var(--space-3);
  margin-top: 0;
  padding: var(--space-8) var(--space-6);
  text-align: center;
}

/* Strip variant (#1099): a compact control docked at the right end of the tab
   strip. The other two variants live inside `.tool-ws-section-grids`, which the
   reader and timeline stops hide — this one sits outside it, which is the whole
   point. Solid border rather than dashed: it reads as a control in a chrome
   rail, not as a dropzone in the body (it still accepts a drop). */
.tool-ws-section-add--strip {
  align-self: center;
  flex: 0 0 auto;
  margin-top: 0;
  padding: var(--space-1) var(--space-3);
  border-style: solid;
  border-radius: var(--radius-pill);
  font-size: var(--font-xs);
  white-space: nowrap;
}

.tool-ws-section-add--strip svg {
  flex: 0 0 auto;
}

.tool-ws-section-add-hint {
  color: var(--color-muted);
  font-size: var(--font-xs);
}

/* Filmstrip "N pages" header — sticky at the top of the rail, hairline under
   (prototype). Covers thumbs scrolling beneath it. */
.tool-ws-reader-rail-head {
  position: sticky;
  top: 0;
  z-index: var(--z-base);
  flex: 0 0 auto;
  /* Symmetric padding so the sticky header keeps an opaque buffer on BOTH sides
     — thumbs scroll cleanly under it instead of butting against the label. The
     rail's flex `gap` would add space-3 below the head only (pushing the label
     high and over-spacing thumb 1), so cancel that one gap with a negative
     margin: the label then reads vertically centered AND the sticky scroll has
     its bottom buffer. */
  padding: var(--space-2) 0;
  margin-bottom: calc(-1 * var(--space-3));
  /* Count label over the thumbs (same content box, so centering lines it up
     with the column). Matches the detail-panel section eyebrows (.ws-rev-eyebrow:
     uppercase, xs, semibold, muted, no tracking) so the filmstrip header reads
     as a section label consistent with the rest of the workspace. */
  text-align: center;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0;
  color: var(--color-muted);
  background: var(--color-bg);
}

.tool-ws-reader-thumb {
  /* Flex-center the image (mirrors .page-card-thumb) so the rotated reciprocal-
     swapped <img> (#524, sized 75% × 133% etc.) is centred before transform:
     rotate() spins it — without this it sat top-left and rotated off-centre,
     leaving a top/right frame. A non-rotated img still fills via 100% × 100%. */
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: 0;
  /* Filmstrip: thumbnails float on the shared stage surface — no card fill,
     just a faint border (the active page carries the accent ring). */
  border: 1px solid var(--color-border-subtle);
  background: transparent;
  cursor: pointer;
  overflow: hidden;
  /* #524: the thumb box takes the *displayed* aspect (crop × raw, rotation-
     swapped — `--page-ar`, set inline by displayed_aspect()), so the cropped
     region fills with no white letterbox frame. Falls back to 3:4 when a page
     has no extracted_file (missing). Supersedes the binary --landscape bucket,
     which followed the raw page and framed cropped / non-bucket pages. */
  aspect-ratio: var(--page-ar, 3 / 4);
  transition: border-color var(--transition-normal);
}

.tool-ws-reader-thumb img {
  width: 100%;
  height: 100%;
  /* The box is the displayed aspect, so the image already nearly fills; cover
     swallows the 1-2px letterbox the aspect-rounding otherwise left (it showed
     the near-white surface as a thin frame inside the thumb). The crop is only
     that rounding hair at thumb scale. */
  object-fit: cover;
  display: block;
}

/* Rotated thumbs (#524): the box is already the *displayed* (post-rotation)
   aspect, but the image is rotated by transform, so its content arrives at the
   reciprocal aspect. Size the <img> to that reciprocal (width/height swapped via
   --page-ar) so contain fills it, then the rotation lands it square in the box —
   the generic form of the grid's hardcoded 75% / 133.33% swap, mirroring
   reader.js _sizeImage. */
.tool-ws-reader-thumb.ws-page--rotated img {
  width: calc(100% / var(--page-ar, 1));
  height: calc(100% * var(--page-ar, 1));
}

.tool-ws-reader-thumb:hover {
  border-color: var(--color-accent);
}

/* Active = the accent border thickened inward into one 3px ring (#1330) — the
   1px border alone matched hover and vanished against white document pages.
   The offset is border + outline width (-3px), which lays the outline flush
   inside the border: shallower and it overlaps the border (a 2px ring), deeper
   and a sliver opens between them that reads as a doubled ring. Inward, not
   outward, because the sticky rail head sits flush on thumb 1 and paints over
   anything that grows above it. */
.tool-ws-reader-thumb--active {
  border-color: var(--color-accent);
  outline: 2px solid var(--color-accent);
  outline-offset: -3px;
}

.tool-ws-reader-thumb.ws-page--excluded {
  opacity: 0.5;
}

/* Rail tile whose thumb the engine gave up on (#1028): a calm dim fill (the
   <img> is still opacity:0 — no bytes) with a retry glyph, injected by
   tool-workspace/image-loader.js. The tile itself is the <button>, so the glyph
   is decoration only and the tile's own click retries (no nested button). */
.tool-ws-reader-thumb:has(img[data-load-failed]) {
  background: var(--color-surface-dim);
}

.tool-ws-reader-thumb-retry {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-danger);
  pointer-events: none;
}

/* Reorder drag state (#506) — mirrors the grid card's dimmed dragging look. */
.tool-ws-reader-thumb.ws-page--dragging {
  opacity: 0.4;
}

/* Refile drag state — timeline rows are refile-draggable onto a section tab,
   like cards + rail thumbs; mirror the same dimmed look. */
.tool-ws-timeline-row.ws-page--dragging {
  opacity: 0.4;
}

/* Drop indicator — a horizontal accent bar in the rail gap (the rail is a
   vertical column). Mirrors the grid card's pseudo-element bar so it stays
   token-clean (no raw box-shadow); `[data-drop]` lifts overflow so the bar
   shows in the gap between thumbs. */
.tool-ws-reader-thumb[data-drop] {
  overflow: visible;
}

.tool-ws-reader-thumb[data-drop]::after {
  content: '';
  position: absolute;
  left: 2px;
  right: 2px;
  height: 4px;
  background: var(--color-accent);
  border-radius: var(--radius-xs);
  pointer-events: none;
}

.tool-ws-reader-thumb[data-drop="before"]::after {
  top: -8px;
}

.tool-ws-reader-thumb[data-drop="after"]::after {
  bottom: -8px;
}

.tool-ws-reader-thumb-missing,
/* Missing page in the reader (#616) — a page-shaped dashed placeholder that
   doubles as a click-to-upload target for THIS slot (replace-missing flow).
   Rendered as a sibling of the (hidden) image wrap so it actually shows. */
.tool-ws-reader-page-missing {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: min(360px, 70%);
  aspect-ratio: 1 / 1.414;
  margin: var(--space-6) auto;
  padding: var(--space-6);
  text-align: center;
  color: var(--color-muted);
  font-size: var(--font-sm);
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  cursor: pointer;
  transition: border-color var(--transition-normal), background var(--transition-normal);
}

/* Filmstrip missing thumb: the dashed placeholder reads as a blank white page
   (matching how a real page thumb fills white), floating on the shared surface
   — only the thumb container around it is transparent. The stage's full-size
   .tool-ws-reader-page-missing keeps its own surface. */
.tool-ws-reader-thumb-missing {
  background: var(--color-surface);
}

.tool-ws-reader-page-missing:hover {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 4%, transparent);
}

.tool-ws-reader-page-missing-label {
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}

.tool-ws-reader-page-missing-hint {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* ── Liquid stacked-snap reading pane (#510) ───────────────────────────
   The reading pane is a scroll-snap container holding ALL pages as a vertical
   stack — free momentum scroll that "clicks into a ledge". Block layout so each
   stage's offsetTop is a clean scroll target for reader.js's active detection
   and programmatic scroll-to-page. Replaces #504's single-active reading
   stage. */
.tool-ws-reader-stack {
  flex: 1 1 0;
  min-width: 0;
  position: relative;
  /* Fill the workspace body (#518) — the height chain is definite (`.tool-ws`
     height:100% → body fills), so the reader no longer floats above a gap. */
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* mandatory (was proximity, the #510 "liquid" pin): every gesture settles
     onto a page instead of momentum-skimming past — compounding trackpad
     flicks made the proximity scroll feel like it ran away (2026-06-12).
     Zoom still suspends snap entirely (--zoomed → none). */
  scroll-snap-type: y mandatory;
  background: var(--color-bg);
  /* Hairline-only: the reading pane is an open area between the rail and panel
     hairlines, not a bordered box (the box-within-hairlines read as clutter). */
}

/* No-rail reader (#504): bleed the reading stage's background through the
   workspace's left gutter. That 18px gutter is the right gap before the
   filmstrip thumbs (grid / reader-rail), but beside the full-bleed reading
   stage it reads as an odd white strip. Standard full-bleed trick — pull the
   stack left by the gutter, pad the content back — so only the BG extends to
   the edge; the toolbar, header slots and completeness banner keep their
   gutter. Scoped to stop-reader (the reader-rail stop keeps the rail + gutter). */
.tool-ws--stop-reader .tool-ws-reader-stack {
  margin-left: calc(-1 * var(--space-ws-gutter));
  padding-left: var(--space-ws-gutter);
}

/* Each page is a snap target reusing the modal image-area classes so the shared
   image-tools globals resolve the active one via `[data-tool-active]`. */
.tool-ws-reader-stack .tool-ws-reader-stage {
  position: relative;
  scroll-snap-align: start;
  /* One gesture, one page: without the stop, a firm flick sails through
     several pages before mandatory snap settles — reads as runaway speed
     (2026-06-13, companion to the proximity→mandatory switch). Far jumps
     are the rail's job. */
  scroll-snap-stop: always;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* A subtle divider between stacked pages (not on the last). */
.tool-ws-reader-stack .tool-ws-reader-stage + .tool-ws-reader-stage {
  border-top: 1px solid var(--color-border);
}

/* Fit page (#527) — the stage takes its height from the page's *displayed*
   aspect (`--page-ar`, set inline by displayed_aspect(): rotation- and crop-
   aware), exactly like the grid cards. So the stage is correctly sized BEFORE
   and regardless of reader.js _sizeImage — the old `height: auto` made the stage
   hug the JS-sized rotator, and any sizing timing gap (cached src whose onload
   won't fire, mid-mount 0-size pane, rAF throttled on a backgrounded tab) left
   the rotator at 0 and collapsed the stage to a ~1px hairline (#527). With a
   CSS-known height the collapse is structurally impossible; _sizeImage then only
   sizes the <img> px (rotation/crop precision) inside an already-stable box.
   `max-height: 100%` caps a page at one stack viewport (the stack height is
   definite via the #518 chain) — the same contain-fit budget _sizeImage applies.
   Keyed on the STAGE's own fit class (reader.js mirrors it onto every stage), NOT
   the stack's — an HTMX swap can momentarily duplicate the stack id and leave the
   new stack without the class. The stage is no longer immune: it has carried an
   id (`ws-stage-N`) since #539, so htmx's settle strips its class too, and
   reader.js re-mounts on `htmx:afterSettle` to put it back (#1331).
   EXACTNESS: the aspect lands height-exact on the collapse axis (portrait, height-
   bound) and width-exact on landscape — see the wrap `padding: 0` below, which
   keeps the 32px breathing inset in _sizeImage (not the wrap padding) so it does
   not eat the flex height and clip a landscape page. */
.tool-ws-reader-stage.tool-ws-fit-page {
  width: 100%;
  aspect-ratio: var(--page-ar, 3 / 4);
  max-height: 100%;
  height: auto;
}

/* Fit width — pages fill width and run tall; the STACK scrolls to read. The
   aspect gives each (even unloaded) stage its correct height up front so the
   windowed loader observes real heights; `min-height: 100%` keeps a wide page at
   least a viewport, and `height: auto` lets a tall page grow past the aspect to
   hug its image rather than clip. */
.tool-ws-reader-stage.tool-ws-fit-width {
  width: 100%;
  aspect-ratio: var(--page-ar, 3 / 4);
  height: auto;
  min-height: 100%;
  padding-block: var(--space-4);
}

/* The image surface fills its stage; the rotator is absolutely sized by
   reader.js (_sizeImage) / image-tools (active page), matching the modal.
   padding: 0 (overrides the modal's 1rem) — the stage now drives height via
   aspect-ratio (#527), and any wrap padding would shrink the flex content box
   below the aspect height, clipping a width-filled landscape page by ~11px. The
   breathing inset is preserved: _sizeImage's pad=32 contain-fits the <img> 16px
   in from each edge, so the visible margin is unchanged. */
.tool-ws-reader-stack .tool-ws-reader-stage .page-modal-image-wrap {
  flex: 1;
  min-height: 0;
  width: 100%;
  padding: 0;
}

/* The reader stage sizes the image explicitly in px (_sizeImage) for rotation +
   crop — a rotated page's PRE-rotation box intentionally exceeds the rotator in
   one axis so that, once rotated, it fills the box. The legacy `--spread`
   `max-width: 100%` clamps that pre-rotation width back to the rotator width,
   collapsing a rotated cropped spread to a square (#524). The stage image is
   always JS-sized, so the clamp is unnecessary here — drop it. Scoped to the
   reader stack; the legacy modal keeps its --spread max-width. */
.tool-ws-reader-stack .tool-ws-reader-stage .page-modal-img {
  max-width: none;
  max-height: none;
}

.tool-ws-reader-stage.tool-ws-fit-width .page-modal-image-wrap {
  overflow: visible;
}

/* Filmstrip thumbs must keep their 3/4 aspect and let the rail scroll. */
.tool-ws-reader-rail .tool-ws-reader-thumb {
  flex-shrink: 0;
}

.tool-ws-fit-seg {
  flex-shrink: 0;
}

/* Zoom pans by scrolling the wrap (overflow:auto) — keep the pan, hide the
   scrollbar chrome: with "always show scrollbars" (or a plugged-in mouse)
   the bars render permanently over the zoomed page. Image-viewer behavior:
   trackpad/drag pans, no bars. */
.tool-ws-reader-imgwrap {
  scrollbar-width: none;
}

.tool-ws-reader-imgwrap::-webkit-scrollbar {
  display: none;
}

/* Progressive upgrade: while a page's /working loads, its cached /preview is
   painted as an upscaled backdrop so there's no blank/blur gap; reader.js
   crossfades the working <img> in and clears the backdrop. */
.tool-ws-reader-imgwrap--preview {
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

/* Loading floor — a reader stage with a real page whose <img> hasn't been
   painted yet (working still queued, preview not yet decoded) shows a controlled
   shimmer instead of the browser's broken-image chrome. Cleared the instant the
   <img> gets a src (preview or working). Reuses the grid's pulse-load keyframe. */
.tool-ws-reader-stage:not(.is-missing)
  .tool-ws-reader-imgwrap:not(:has(.page-modal-img[src])) {
  animation: pulse-load 3s ease-in-out infinite;
}

/* Hide the alt-text + broken-image icon until the <img> actually has a src, so
   the shimmer floor (above) is what shows through, not uncontrolled chrome. */
.page-modal-img:not([src]) {
  opacity: 0;
}

/* "Loading full quality" cue — the crisp preview will upgrade to the full-res
   /working image. reader.js arms it only on a SLOW load (an ~800ms grace, so a
   fast swap never flashes it), active page only, and fades it out the instant
   /working lands. The pill itself is the design-system .chip + .loading-spinner;
   only the float + show/hide live here. reader.js parents it to the ACTIVE stage
   (already position:relative, sized to --page-ar) so it sits on the focused
   image's corner — unambiguous when two landscape pages share the viewport. */
.tool-ws-sharpening-chip.chip {
  position: absolute;
  bottom: var(--space-4);
  right: var(--space-4);
  z-index: var(--z-sticky);
  /* .chip's padding is tuned for an icon + close-button layout; ours is a spinner
     + label, so even it out. */
  padding: var(--space-2) var(--space-4);
  box-shadow: var(--shadow-floating);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-slow) ease-out;
}

.tool-ws-sharpening-chip.is-visible {
  opacity: 1;
}

/* Load gave up (#1028) — the reader's counterpart to .page-card-retry. Injected
   by tool-workspace/image-loader.js on `platform:load-failed` from a stage, in
   the SAME corner the sharpening chip just occupied, so the failure lands where
   the operator was already watching. A real <button> in .chip clothing; the
   sharpening chip's pointer-events:none does not apply (it is a different class),
   and reader.js keeps that spinner down while the mark is on the <img>. */
.tool-ws-retry-chip.chip {
  position: absolute;
  bottom: var(--space-4);
  right: var(--space-4);
  z-index: var(--z-sticky);
  padding: var(--space-2) var(--space-4);
  box-shadow: var(--shadow-floating);
  color: var(--color-danger);
  font-family: inherit;
  cursor: pointer;
}

.tool-ws-retry-chip.chip:hover {
  text-decoration: underline;
}

/* A stage whose working load the engine gave up on stops pulsing — the shimmer
   floor above claims bytes are still coming. (Usually moot: the reader falls
   back to the /preview on failure, which already clears the floor.) */
.tool-ws-reader-stage:has(.page-modal-img[data-load-failed]) .tool-ws-reader-imgwrap {
  animation: none;
}

/* Missing page — hide the empty image surface; the placeholder is a sibling
   rendered only for missing slots (ws_reader_stage.html), styled above. */
.tool-ws-reader-stage.is-missing .page-modal-image-wrap {
  display: none;
}

/* ── Zoom suspend-snap (pane-local) ──
   Zooming the active page (scale > 1) suspends snap-paging so the page can fill
   and pan freely; zoom-out restores the stack and re-snaps. */
.tool-ws-reader-stack--zoomed {
  scroll-snap-type: none;
}

/* Fit-width zoom scales the image via a rotator transform (image-tools). A
   transformed child still extends the reading stack's scrollable overflow, so
   without this the reader's scrollbar grew and resized as you zoomed in. Clip
   the zoomed page to its own box (overflow:clip — a clip, NOT a scroll
   container, so no scrollbar of its own either): the transform stays contained
   and the stack's scroll region is unchanged. Active + zoomed only, so ordinary
   reading scroll is untouched; Fit-page/modal zoom scroll their own wrap. */
.tool-ws-reader-stack--zoomed
  .tool-ws-reader-stage.tool-ws-fit-width[data-tool-active]
  .page-modal-image-wrap {
  overflow: clip;
}

/* ── Crop take-over (the ONE take-over) ──
   Crop lifts the active stage OUT of the stack into a full-bleed fixed overlay;
   the stack locks behind it (scroll already frozen via the neutral
   `scroll-locked` class). Crop is NOT folded into the scrolling pane. */
.tool-ws-reader-stage.is-cropping {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  height: auto;
  /* The full-bleed inset:0 take-over owns its own size — drop the fit-page
     aspect-ratio (#527) so it doesn't shrink the crop surface to the page ratio. */
  aspect-ratio: auto;
  max-height: none;
  background: var(--color-bg);
  scroll-snap-align: none;
}

.tool-ws-reader-stack--cropping .tool-ws-reader-stage:not(.is-cropping) {
  visibility: hidden;
}

/* Crop aspect / action bars default hidden; image-tools.js reveals them inline
   (display: flex) on crop entry. They float over the full-bleed active page —
   one id'd instance, direct children of the reader (not per stacked page). On
   crop entry they pin to the viewport (fixed) above the take-over stage. */
.tool-ws-reader .crop-aspect-bar,
.tool-ws-reader .crop-actions {
  display: none;
}

.tool-ws-reader--cropping .crop-aspect-bar,
.tool-ws-reader--cropping .crop-actions {
  position: fixed;
  z-index: var(--z-toast);
}

.tool-ws-reader .crop-clear-btn {
  color: var(--color-danger);
}

/* Grid card expand affordance — unified shell only, opens the in-place
   reader (#504). Hover/focus-revealed, top-right of the thumbnail. */
.page-card-expand {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-normal);
  z-index: var(--z-base);
}

.page-card-thumb:hover .page-card-expand,
.page-card-expand:focus-visible {
  opacity: 1;
}

.page-card-expand:hover {
  border-color: var(--color-accent);
}

@media (prefers-reduced-motion: reduce) {
  .tool-ws-reader-thumb,
  .tool-ws-slider-opt {
    transition: none;
  }
}

/* ── Utilities ─────────────────────────────────────────────────────────────── */

.text-muted {
  color: var(--color-muted);
}

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

.text-danger {
  color: var(--color-danger);
}

/* ── Page header row ────────────────────────────────────────────────────────── */

.page-header {
  margin-bottom: var(--space-6);
}

.page-header h1 {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
}

.page-header p {
  color: var(--color-muted);
  font-size: var(--font-base);
  margin-top: var(--space-1);
}

/* Row variant: title + description left, status/actions right */
.page-header--row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
}

.page-header__main {
  min-width: 0;
}

.page-header__aside {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

.page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.page-header__status {
  display: inline-flex;
}

/* Tabs / breadcrumbs strip rendered under the header */
.page-header__below {
  margin-top: var(--space-4);
  margin-bottom: var(--space-6);
}

/* ── Card ───────────────────────────────────────────────────────────────────── */

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

/* Cards stacked directly in the content area get the standard gap between
   them (/ux card-gap). Grids and panes that lay cards out themselves are
   untouched: the rule only sees cards that are siblings at the top level. */
.team-shell .main-content > .card + .card {
  margin-top: var(--space-5);
}

/* Full-bleed card — drops the border + radius so the surface runs edge-to-edge.
   Pairs with the `is-flush` body class (zeroes the .main-content gutter) for
   surfaces that should fill the window rather than float as a panel. */
.card--flush {
  border: none;
  border-radius: 0;
}

/* Flush list host fills the (unpadded) content area exactly, so the sticky
   lv-footer sits at the very bottom of the window. The base
   `.lv-host[data-lv-layout]` rule uses `calc(100vh - 11rem)`; at runtime
   list_view_layout.js `setHostHeight()` sets an inline pixel height measured
   off the scroll container's content box. That measurement reads the actual
   gutter, so it no longer assumes the padded one — but `!important` stays as
   the anchor: it outranks the inline height whatever JS computes, and it holds
   with no JS at all. This comment named a hardcoded `- 24px` until the shell's
   10px bottom inset and the 18px gutter made that sum wrong; the flush path
   itself was not re-driven when that changed. */
.team-shell.is-flush .main-content > .lv-host[data-lv-layout] {
  height: 100% !important;
}

/* Pin the table header on the flush list page so it stays put like the
   toolbar while rows scroll. The base resizable rule
   (`.lv-host[data-lv-layout] table[data-lv-resizable] thead th`) sets
   `position: relative` so the col-resizer can anchor to the cell; re-assert
   sticky here — sticky is itself a positioned ancestor, so the resizer keeps
   working. Scoped to `is-flush` so other list pages keep their current
   behaviour. */
.team-shell.is-flush .main-content > .lv-host[data-lv-layout] table[data-lv-resizable] thead th {
  position: sticky;
  top: 0;
  /* White header to match the rows (base is surface-dim). Stays opaque so
     rows don't show through the sticky header while scrolling. */
  background: var(--color-surface);
  /* Drop the collapsed grid border-bottom; the `::after` divider below is the
     single line under the header (the collapsed one would stack with it into a
     double border at the top of the first row). */
  border-bottom: 0;
}

/* With `border-collapse`, the th's `border-bottom` is painted at the table
   level and scrolls away under a sticky header. A pseudo-element pinned to the
   cell's bottom edge rides with the sticky th, so the divider under the header
   stays visible while rows scroll beneath it. */
.team-shell.is-flush .main-content > .lv-host[data-lv-layout] table[data-lv-resizable] thead th::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--color-border);
}

/* Kill the double divider above the footer. The table-wrap scrolls and clips
   at its bottom edge — the row sitting on that edge paints its 1px
   border-bottom right against the footer's 1px border-top, reading as two
   lines (and the clipped row is rarely tr:last-child, so dropping the last
   row's border can't fix it). Instead let the footer's own background paint
   over that 1px via a pseudo-element, leaving the footer's border-top as the
   single divider regardless of scroll position. */
.team-shell.is-flush .main-content > .lv-host[data-lv-layout] .lv-footer {
  position: relative;
}

.team-shell.is-flush .main-content > .lv-host[data-lv-layout] .lv-footer::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -1px;
  height: 1px;
  background: var(--color-surface-dim);
}

/* ── Buttons ────────────────────────────────────────────────────────────────── */

/* Buttons set their own colour: without it the browser default (black) is
   the one text colour on a page that is not a token (/ux text-color-token). */
.btn {
  color: var(--color-text);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--control-h);
  padding: 0 0.75rem;
  border-radius: var(--radius-sm);
  font-size: var(--font-sm);
  font-weight: var(--weight-normal);
  cursor: pointer;
  border: 1px solid transparent;
  text-decoration: none;
  transition: background var(--transition-normal), color var(--transition-normal), border-color var(--transition-normal), opacity var(--transition-normal);
  white-space: nowrap;
  line-height: 1;
  font-family: inherit;
}

.btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.btn:disabled,
.btn[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--color-accent);
  color: var(--color-surface);
  border-color: var(--color-accent);
}

.btn-primary:hover:not(:disabled) {
  background: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
}

.btn-ghost {
  background: transparent;
  color: var(--color-muted);
  border-color: var(--color-border);
}

.btn-ghost:hover:not(:disabled) {
  background: var(--color-hover);
  color: var(--color-text);
  border-color: var(--color-muted);
}

.btn-danger {
  background: var(--color-danger);
  color: var(--color-surface);
  border-color: var(--color-danger);
}

.btn-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-danger) 85%, black);
  border-color: color-mix(in srgb, var(--color-danger) 85%, black);
}

/* ── Danger zone (delete affordances) ───────────────────────────────────────── */

.danger-zone {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

.danger-zone-title {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 0 var(--space-2);
}

.danger-zone-blocked {
  color: var(--color-muted);
  font-size: 0.85rem;
  margin: 0;
}

/* ── Toolbar ────────────────────────────────────────────────────────────────── */

.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0.625rem 0.875rem;
  border-bottom: 1px solid var(--color-border);
  flex-wrap: wrap;
}

.toolbar-group {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.toolbar-divider {
  width: 1px;
  height: 18px;
  background: var(--color-border);
}

.toolbar-spacer {
  flex: 1;
}

/* Single-line toolbar variant — used by list views where the filter row
   should never wrap below itself. Filter selects truncate with ellipsis
   so the active value always fits; the full label still appears in the
   dropdown menu. Tighter inter-control gap saves another ~25px. */
.toolbar--no-wrap {
  flex-wrap: nowrap;
  gap: var(--space-2);
  min-width: 0;
}

.toolbar--no-wrap .toolbar-group {
  flex-wrap: nowrap;
  /* Allow the group to flex-shrink so its filter selects can compress
     when the toolbar runs out of room. Without this `flex: 0 0 auto`
     left the group at content width, pushing the rightmost controls
     off-screen instead of letting them truncate gracefully. */
  flex: 0 1 auto;
  min-width: 0;
  gap: var(--space-2);
}

/* Shrink-and-clip group: sizes to its content's natural width when
   there's room, shrinks below content (truncating filters) when the
   toolbar is tight, and clips overflow against its right edge so it
   never bleeds into the pinned group. `flex: 0 1 auto` — not 1 1 0 —
   because we want filters at their natural width on wide screens, not
   stretched to fill half the toolbar. The toolbar-spacer's `flex: 1`
   absorbs leftover space, keeping the pinned group anchored right.

   `overflow-x: clip` clips horizontal overflow without creating a
   scroll context AND without promoting overflow-y to `auto` (the
   spec quirk that bites `overflow: hidden`). That keeps the
   saved-views popover — absolute-positioned to extend below the
   toolbar group — visible vertically while still hiding any filter
   text that runs past the group's right edge. */
.toolbar--no-wrap .toolbar-group--shrink {
  flex: 0 1 auto;
  min-width: 0;
  overflow-x: clip;
  overflow-y: visible;
}

/* Pinned group: never shrinks, never gets pushed off-screen. The
   filter row's `--shrink` group clips against this group's left edge
   when the toolbar narrows. */
.toolbar--no-wrap .toolbar-group--pinned {
  flex: 0 0 auto;
}

.toolbar--no-wrap .toolbar-spacer {
  min-width: var(--space-2);
}

.toolbar--no-wrap .filter-select {
  /* `min-width: 0` lets the select shrink below its content's natural
     width — combined with text-overflow:ellipsis the label truncates
     ("Updated" → "Upda…") instead of forcing the parent to overflow. */
  min-width: 0;
  max-width: 11rem;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}

/* Note: do NOT set `overflow-x: auto` on the toolbar — it makes the
   toolbar a clipping container, which cuts off absolutely-positioned
   popovers (saved-views, columns) at the toolbar's bottom edge. The
   compression rules above keep the toolbar within reasonable widths. */

/* Filter chip (e.g. active person/assignee filter) */
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: var(--control-h);
  padding: 0 0.5rem 0 0.65rem;
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  border: 1px solid color-mix(in srgb, var(--color-accent) 25%, var(--color-border));
  color: var(--color-text);
  font-size: 0.8rem;
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

.filter-chip-clear {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--color-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  line-height: 1;
  padding: 0;
  transition: background var(--transition-normal), color var(--transition-normal);
}

.filter-chip-clear:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

/* ── Filter selects ─────────────────────────────────────────────────────────── */

.filter-select {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  height: var(--control-h);
  padding: 0 1.6rem 0 0.6rem;
  font-size: 0.8rem;
  cursor: pointer;
  font-family: inherit;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M2 4l3 3 3-3' stroke='%23979797' stroke-width='1.4' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 0.55rem center;
  transition: border-color var(--transition-normal), background-color var(--transition-normal);
}

.filter-select:hover {
  border-color: var(--color-muted);
}

.filter-select:focus {
  outline: none;
  border-color: var(--color-accent);
}

.filter-select:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ── Search input ───────────────────────────────────────────────────────────── */

.search-input {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  height: var(--control-h);
  padding: 0 0.75rem 0 2rem;
  font-size: 0.8rem;
  font-family: inherit;
  min-width: 200px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23979797' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='7'/><path d='M21 21l-4.3-4.3'/></svg>");
  background-repeat: no-repeat;
  background-position: 0.55rem center;
  transition: border-color var(--transition-normal), background-color var(--transition-normal), box-shadow var(--transition-normal);
}

.search-input::placeholder {
  color: var(--color-muted);
}

.search-input:focus {
  outline: none;
  border-color: var(--color-accent);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-focus);
}

.search-input:focus-visible {
  outline: none;
  border-color: var(--color-accent);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-focus);
}

/* Pill variant — icon + clear button, Front-style */
.search-field {
  position: relative;
  display: inline-block;
  width: 300px;
  max-width: 100%;
}

.search-field__icon {
  position: absolute;
  left: 0.85rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-muted);
  pointer-events: none;
  display: flex;
}

.search-input--pill {
  width: 100%;
  background: var(--color-hover);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  padding: 0.55rem 0.85rem 0.55rem 2.5rem;
  font-size: 14px;
  min-width: 0;
}

.search-input--pill:focus,
.search-input--pill:focus-visible {
  background: var(--color-surface);
  border-color: var(--color-accent);
  outline: none;
}

.search-input--pill::-webkit-search-cancel-button,
.search-input--pill::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

.search-field__clear {
  position: absolute;
  right: 0.6rem;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: none;
  background: #c7c9d1;
  color: var(--color-surface);
  cursor: pointer;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 0;
}

.search-field__clear:hover {
  background: var(--color-muted);
}

.search-field:has(.search-input--pill:not(:placeholder-shown)) .search-field__clear {
  display: flex;
}

/* ── Data table ─────────────────────────────────────────────────────────────── */

.table-wrap {
  overflow-x: auto;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
}

/* A table that starts below other content in a card is closed off from it
   by a hairline; a table that is the first thing in the card is not
   (/ux table-top-border). Also covers a wrapped table. */
.card > * + .data-table,
.card > * + .table-wrap > .data-table {
  border-top: 1px solid var(--color-border);
}

/* A table that ends a card leaves the last row open: the card's own border
   closes it, and a row line on top of that reads as a double rule
   (/ux table-bottom-border). */
.card > .data-table:last-child tbody tr:last-child td,
.card > .table-wrap:last-child > .data-table tbody tr:last-child td,
/* …and when the literal last row is a hidden inline-edit row, the visible
   row before it is the one that ends the card. */
.card > .data-table:last-child tbody tr:has(+ tr.hidden:last-child) td {
  border-bottom: 0;
}

.data-table th {
  text-align: left;
  padding: 0.75rem 1rem;
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  background: var(--color-surface-dim);
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
}

.data-table thead th + th {
  position: relative;
}

.data-table thead th + th::before {
  content: "";
  position: absolute;
  left: 0;
  top: 30%;
  bottom: 30%;
  width: 1px;
  background: var(--color-border);
}

.sort-header {
  all: unset;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.75rem 1rem;
  width: 100%;
  cursor: pointer;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  color: inherit;
}

.sort-header:hover {
  color: var(--color-text);
}

.sort-header.active {
  color: var(--color-text);
}

.sort-arrow {
  font-size: 0.6rem;
}

.data-table th:has(.sort-header) {
  padding: 0;
}

.data-table th.col-right .sort-header {
  justify-content: flex-end;
}

/* A column reads as one thing only when header and cells share an alignment
   (/ux table-column-align). */
.data-table th.col-center,
.data-table td.col-center {
  text-align: center;
}

.data-table th.col-right,
.data-table td.col-right {
  text-align: right;
}

.data-table td {
  padding: 12px var(--space-ws-gutter);
  border-bottom: 1px solid var(--color-border-subtle);
  vertical-align: middle;
}

.data-table .clickable-row {
  cursor: pointer;
  transition: background var(--transition-fast);
}

/* Stable row height on the layout-managed list tables (#688). During a live
   per-cell swap, htmx wears the OLD cell attributes + transient bare (unpadded)
   content on the node for one settle frame (~20ms) — shorter than the pill it
   replaces, which otherwise collapses the row (48→40px) and shifts every row
   below it. Frame-captured. `height` on a table row is a MINIMUM, so this holds
   the floor steady through the settle window while still letting any genuinely
   taller content grow. Tables outside the list view are unaffected. */
.lv-host[data-lv-layout] .data-table tr.clickable-row {
  height: 48px;
}

.data-table--resizable {
  table-layout: fixed;
}

.data-table--resizable th,
.data-table--resizable td {
  border-right: 1px solid var(--color-border-subtle);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.data-table--resizable th:last-child,
.data-table--resizable td:last-child {
  border-right: none;
}

.data-table--resizable th {
  position: relative;
}

.data-table--resizable td.cell-wrap {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  word-break: break-word;
}

.col-resizer {
  position: absolute;
  top: 0;
  right: -3px;
  width: 6px;
  height: 100%;
  cursor: col-resize;
  user-select: none;
  z-index: 2;
}

.col-resizer:hover,
.col-resizer.is-dragging {
  background: var(--color-border-subtle);
}

.data-table .clickable-row:hover {
  background: var(--color-surface-dim);
  /* Opaque twin for a pinned cell (§"Pinned columns"). */
  --lv-pin-bg: var(--color-surface-dim);
}

/* Base-service row tint (#1047) — the wash half of the palette.

   Opt-in per reader through the columns picker's Display section, so most
   people never see it. The rule that makes it legible: **the colour appears
   exactly once.** Tint off, the service pill carries it; tint on, the row
   carries it and the pill degrades to plain text (cases_cells.html). Both at
   once reads as an error state rather than as a colour scheme.

   Two carriers, because one could not do both jobs. A full-width wash has to
   stay weak enough not to fight the text sitting on it, so "strong enough to
   tell apart" and "quiet enough to read over" pull against each other — the
   first attempt shipped at 7% and lost that argument: correct, invisible, and
   indistinguishable from the preference not working.

   So the identity moved to the row's leading **edge**, where nothing else
   lives and contrast costs the content nothing, and the wash stayed as
   grouping rather than as the signal. The 3px stroke at 70% of the token is
   what names the service; the 9% wash groups the rows that share one. The
   stroke is deliberately NOT the full token — at full strength it stops
   reading as the wash's own colour and becomes a second UI element, a rule
   drawn beside the row instead of a property of it.

   **9%, and the number was set at real density.** The first pass shipped an
   18% wash tuned against a three-row fixture, and eighteen rows of it tile
   into a patchwork — a wash is judged by how it repeats, not by how one row
   looks. Anke's "soft tones, not bright" lives in these numbers, which is
   exactly why the colour is a palette key and not a hex typed per service:
   nobody re-checks a hand-picked colour against a wash ratio AND a stroke
   opacity at eighteen rows, and a token set does it once.

   **Both carriers are `background` on the <tr>, and that is load-bearing.**
   The stroke was an inset `box-shadow` on `td:first-child` reading a
   `--shadow-row-edge` token, and it never painted once — on staging either.
   A custom property's `var()` references resolve where the property is
   DECLARED, not where it is used: declared on `:root`, its `var(--lv-tint)`
   had nothing to resolve against, so the token computed to the
   guaranteed-invalid value, inherited that everywhere, and `box-shadow` fell
   back to `none`. Nothing raises; the class is present and correct on the row
   the whole time. That is also why there is no token here any more — a value
   parameterised by a runtime custom property is a component pattern, not a
   design token, and minting one was a category error the lint pushed toward.

   Three things this block deliberately does NOT do:

   - It does not use a `border-left`. A border adds width, so under
     `border-collapse` every tinted row's content would shift 3px against its
     untinted neighbours. A gradient paints inside the row's own box.

   - It does not set the `background` SHORTHAND, and re-states the edge in
     EVERY state that changes the background. The shorthand resets
     `background-image`, and `.clickable-row:hover` above is written that way —
     so a tinted hover that only re-declared `background-color` let the neutral
     rule's `background-image: none` through and the stroke vanished under the
     pointer. Measured, after a comment here claimed the opposite: the earlier
     wording said this block "does not touch" that rule, which was true of the
     selector and false of the cascade. `--lv-edge` is declared on the row
     itself — where `--lv-tint` is in scope, unlike the `:root` token that
     started all this — so both states name one gradient.

   - It does not move the tint onto the `<td>`s. The surgical-update pulse
     (`tr.lv-row--updated > td`) animates td backgrounds from transparent, so a
     td-level tint would be wiped for the 1.6s of every live update. On the
     <tr> the pulse plays over the wash and settles back onto it. */
.data-table tr.lv-row--tinted {
  --lv-edge: linear-gradient(
    to right,
    color-mix(in srgb, var(--lv-tint) 70%, transparent) 0 3px,
    transparent 3px
  );
  background-color: color-mix(in srgb, var(--lv-tint) 9%, transparent);
  background-image: var(--lv-edge);
  background-repeat: no-repeat;
  /* Opaque twin for a pinned cell (§"Pinned columns"): the same wash, mixed
     onto the surface it sits on instead of over it. Declared here, on the
     row, where `--lv-tint` is in scope. */
  --lv-pin-bg: color-mix(in srgb, var(--lv-tint) 9%, var(--color-surface));
}

/* A pinned first cell is opaque and covers the row's own stroke, so it
   paints the same gradient itself. Harmless on an unpinned first cell: the
   same stroke, drawn twice in one place. */
.data-table tr.lv-row--tinted > td:first-child {
  background-image: var(--lv-edge);
  background-repeat: no-repeat;
}

/* The row divider, tinted so it survives the wash.

   Without this the board reads as blocks rather than rows, and the cause is
   not spacing: `.data-table td`'s `border-bottom` is `--color-border-subtle`,
   a pale grey that a coloured wash swallows — so two adjacent rows sharing a
   service fuse into one field, and the longer the run the worse it looks. The
   fix is contrast, not air; taller rows leave the runs fused and cost five
   rows a screen.

   22% of the row's own colour: enough to read through the 9% wash, quiet
   enough to stay a divider rather than become a rule. Scoped to tinted rows,
   so the untinted board is untouched — the alternative considered was
   `border-collapse: separate` with a 2px row gap, which separates more
   cleanly but changes width and offset computation for the sticky thead, the
   colgroup and the resize system, and applies to every reader whether or not
   they turned the tint on. */
.data-table tr.lv-row--tinted > td {
  border-bottom-color: color-mix(in srgb, var(--lv-tint) 22%, transparent);
}

/* Outranks `.clickable-row:hover` (0,4,0) against (0,3,0). It must re-state
   `background-image`, not only the colour: that rule uses the `background`
   shorthand, which expands to `background-image: none`, and re-declaring the
   colour alone let the none through — the stroke disappeared under the
   pointer. Verified by hovering, not by reading the selector. */
.data-table .clickable-row.lv-row--tinted:hover {
  background-color: color-mix(in srgb, var(--lv-tint) 16%, transparent);
  background-image: var(--lv-edge);
  --lv-pin-bg: color-mix(in srgb, var(--lv-tint) 16%, var(--color-surface));
}

a.case-id {
  color: inherit;
  text-decoration: none;
}

a.case-id:hover {
  text-decoration: underline;
}

/* Cases table — couple / type / assignee / created cells */
.couple-row {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
}

.couple-avatars {
  display: inline-flex;
  flex-shrink: 0;
}

.couple-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
  color: var(--color-accent);
  font-size: 0.62rem;
  font-weight: var(--weight-semibold);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  letter-spacing: 0.02em;
  border: 2px solid var(--color-surface);
  overflow: hidden;
  flex-shrink: 0;
}

.couple-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

.couple-avatars .couple-avatar+.couple-avatar {
  margin-left: calc(var(--space-2) * -1);
}

.couple-names {
  color: var(--color-text);
  font-weight: var(--weight-medium);
}

.couple-names .couple-sep {
  color: var(--color-muted);
  margin: 0 0.25rem;
  font-weight: var(--weight-normal);
}

.type-text {
  color: var(--color-subtle);
}

/* The service cell's degraded form (#1047) — what the Base service column
   renders as when the row-tint pref is on and the ROW carries the colour
   instead. Matches .type-text beside it: on a tinted row the cell is a label,
   and the tint is already saying which service this is. */
.service-text {
  color: var(--color-subtle);
}

.assignee {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--color-text);
}

.assignee-avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
  color: var(--color-accent);
  font-size: 0.62rem;
  font-weight: var(--weight-semibold);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  letter-spacing: 0.02em;
  overflow: hidden;
  flex-shrink: 0;
}

.assignee-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

.dash {
  display: none;
}

/* The placeholder for an empty value in a table cell or a definition list:
   a middle dot in the faintest text colour, so the cell reads as "nothing
   here" without a dash and without looking unloaded (/ux empty-value). */
.empty-value {
  color: var(--color-text-lavender-grey);
}

/* Cases table — column width locks via <colgroup> */
.col-id {
  width: 110px;
}

.col-couple {
  min-width: 240px;
}

.col-type {
  width: 140px;
}

.col-stage {
  width: 200px;
}

.col-assigned {
  width: 180px;
}

.col-assigned-to {
  width: 180px;
}

.col-created {
  width: 130px;
}

.col-created-at {
  width: 130px;
}

.col-status {
  width: 120px;
}

.col-wedding-date {
  width: 140px;
}

.col-town-hall {
  width: 160px;
}

.col-afl-case-number {
  width: 140px;
}

/* Table footer — summary bar below the table */
.table-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.625rem 1rem;
  border-top: 1px solid var(--color-border);
  background: var(--color-surface-dim);
  color: var(--color-muted);
  font-size: var(--font-xs);
}

.table-footer b {
  color: var(--color-text);
  font-weight: var(--weight-semibold);
}

/* Stage pill */
.stage-pill {
  display: inline-block;
  padding: 0.2rem 0.55rem;
  border-radius: var(--radius-xs);
  font-size: 0.72rem;
  font-weight: var(--weight-medium);
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  color: var(--color-accent);
  white-space: nowrap;
}

/* ── Empty state ────────────────────────────────────────────────────────────── */

.empty-state {
  padding: 4rem 1rem;
  text-align: center;
  color: var(--color-muted);
  font-size: 0.9rem;
}

/* Empty state sitting inside a .card (e.g. the people panel) — the card
   already supplies horizontal padding, so tighten to vertical-only. */
.empty-state--in-card {
  padding: var(--space-6) 0;
}

/* ── Case detail layout ─────────────────────────────────────────────────────── */

.case-detail {}

/* Header */
/* Wraps only when the heading and the toolbar cannot share the row at all.
   A wrap is decided on each item's flex basis clamped by its min-width, not on
   its content, so the heading's basis is 0 and its floor is a fixed 14rem — room
   for a name and the overview chip. Until that floor no longer fits beside the
   toolbar the heading grows into whatever the toolbar leaves, and the title and
   the note under it (.mh-title-stack) ellipsise to fit. The floor is a length
   because the content minimum would be the note's full text: an ellipsising
   element still reports its whole text as its minimum. Without wrapping, the
   heading was squeezed to nothing and the chip slid under the toolbar. */
.case-header {
  justify-content: space-between;
  background: var(--color-team-surface);
  flex-wrap: wrap;
}

.case-header .mh-heading {
  flex: 1 1 0;
  min-width: 14rem;
}

/* Files has no top bar — its controls all moved into the case rail. The header
   element still renders (OOB sync target + host for the fixed upload modal), so
   collapse its box to nothing. Compound selector (.mh.case-header--collapsed)
   outranks the base .mh padding/min-height/border regardless of source order. */
.mh.case-header--collapsed {
  min-height: 0;
  padding: 0;
  border-bottom: 0;
  background: none;
}

.case-header-identity {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.case-back {
  font-size: 0.8rem;
  color: var(--color-muted);
  text-decoration: none;
  transition: color var(--transition-normal);
  margin-right: 0.25rem;
}

.case-back:hover {
  color: var(--color-text);
}

.case-couple-names {
  font-size: 1.15rem;
  font-weight: var(--weight-semibold);
}

.case-header-meta {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.type-pill {
  display: inline-block;
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-xs);
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  color: var(--color-accent);
  white-space: nowrap;
}

.case-assignee {
  font-size: 0.8rem;
  color: var(--color-muted);
}

/* Case nav panel identity block */
.case-nav {
  padding: 0 0.6rem;
}

.portal-case-nav {
  padding-top: calc(var(--space-6) + var(--space-1));
}

.nav-panel-back {
  color: var(--color-muted) !important;
  font-size: 0.85rem;
  margin-bottom: var(--space-1);
}

.nav-panel-divider {
  border: none;
  border-top: 1px solid rgba(0, 0, 0, 0.09);
  margin: 0.6rem 0 0.6rem 0;
}

.nav-panel-item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0.55rem 0.65rem;
  border-radius: var(--radius-md);
  font-size: 0.9rem;
  color: var(--color-text);
  text-decoration: none;
  transition: background var(--transition-normal), color var(--transition-normal);
  cursor: pointer;
}

.nav-panel-item:hover {
  background: var(--color-hover);
}

.nav-panel-item.active {
  background: rgba(0, 0, 0, 0.07);
  font-weight: var(--weight-medium);
}

/* ── Case sidebar (.sb-*) — used by case_nav.html. Replaces the legacy
   .nav-panel-* / .docs-nav-* shell for the case workspace nav. The
   visual spec ships in design/Case nav - handoff.html. ── */

.sb {
  --sb-row-h: 24px;
  --sb-row-pad: 7px;
  --sb-row-margin-x: 10px;
  --sb-group-pad-x: 15px;
  --sb-icon: 14px;
  --sb-radius: 6px;
  color: var(--color-text);
  font-family: var(--font);
  font-size: var(--font-base);
  line-height: 1;
  display: flex;
  flex-direction: column;
  padding: var(--sb-row-pad) 2px;
  height: 100%;
  overflow: hidden;
}

.sb a {
  color: var(--color-muted);
  text-decoration: none;
}

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

.sb-head-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.sb-head-title {
  font-size: var(--font-lg);
  font-weight: var(--weight-bold);
  letter-spacing: -0.01em;
  color: var(--color-text-strong);
}

.sb-head-id {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  padding-top: 2px;
  flex-shrink: 0;
}

/* Presence note. Hidden by default; JS removes [hidden] when other team
   members are viewing. Live region for assistive tech. */
.sb-presence {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--font-xs);
  line-height: 1.3;
  min-width: 0;
  padding: var(--space-3) var(--sb-group-pad-x);
}

.sb-presence[hidden] {
  display: none;
}

.sb-presence[hidden] + .sb-divider {
  display: none;
}

.sb-presence__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.7;
  flex-shrink: 0;
  position: relative;
  margin-top: var(--space-1);
}

.sb-presence__text {
  flex: 1;
  min-width: 0;
  white-space: normal;
  word-break: break-word;
}


/* Pulse — fires once on entry (JS strips .is-pulsing after ~2.8s).
   Dot breathes, halo expands; both use currentColor so they follow tone. */
.sb-presence.is-pulsing .sb-presence__dot {
  animation: sb-presence-dot 1.6s ease-in-out infinite;
}

.sb-presence.is-pulsing .sb-presence__dot::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  background: currentColor;
  opacity: var(--opacity-medium);
  animation: sb-presence-pulse 1.6s ease-out infinite;
}

@keyframes sb-presence-dot {
  0%, 100% { transform: scale(1);    opacity: 1; }
  50%      { transform: scale(1.45); opacity: 0.75; }
}

@keyframes sb-presence-pulse {
  0%   { transform: scale(0.6); opacity: var(--opacity-medium); }
  100% { transform: scale(2.6); opacity: 0; }
}

.sb-presence[data-tone="info"]    { color: var(--color-presence-info); }
.sb-presence[data-tone="accent"]  { color: var(--color-accent); }
.sb-presence[data-tone="warning"] { color: var(--color-warning-text); }

.sb-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--sb-row-h);
  padding: 0 var(--space-1);
  margin: 22px var(--space-3) var(--space-3);
  font-size: var(--font-xs);
  border-radius: var(--sb-radius);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.sb-back:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.sb-back__icon {
  width: 14px;
  height: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.sb-back__icon svg {
  width: 100%;
  height: 100%;
}

.sb-body {
  flex: 1;
  overflow-y: auto;
  padding: 2px 0 0;
  margin: 0 -2px;
}

/* The /system and /design-system rails have no .sb-back header; both lead
   with a group label. Give their bodies the same top inset the case rail's
   back link provides, so the first label starts at the same height as
   "All cases" does under a case. */
.sb--system .sb-body,
.sb--design-system .sb-body,
.sb--insights .sb-body {
  padding-top: var(--space-6);
}

.sb-row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  height: var(--sb-row-h);
  padding: 0 var(--sb-row-pad);
  border-radius: var(--sb-radius);
  cursor: pointer;
  position: relative;
  margin: 1px var(--sb-row-margin-x);
  transition: background var(--transition-fast);
  font-size: var(--font-xs);
  line-height: var(--line-height-normal);
}

.sb-row:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.sb-row:hover .sb-row__icon {
  color: var(--color-text);
}

/* The private "P" badge stays visible at all times — it marks a privacy state
   the reviewer needs to see at a glance, not only on hover. */

.sb-row.is-active {
  background: var(--color-active);
  color: var(--color-text-strong);
}

.sb-row__icon {
  width: var(--sb-icon);
  height: var(--sb-icon);
  flex-shrink: 0;
  color: var(--color-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.sb-row__icon svg {
  width: 100%;
  height: 100%;
}

.sb-row.is-active .sb-row__icon {
  color: var(--color-text-strong);
}

/* Doc nav row dims while its workspace fetch is in flight — htmx adds
   .htmx-request for the request's duration. The dim-in is delayed (mirrors
   the fragment-pending indicator-delay) so a fast switch never flashes it;
   on response the class drops and opacity snaps back via the base rule. */
.sb-row--doc.htmx-request {
  opacity: 0.55;
  transition: opacity var(--transition-fast) ease var(--transition-slow);
}

.sb-row__label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 1px 0;
}

/* Drive views (Browse / Submissions) — sub-rows under the Drive nav row.
   Hidden until Drive is the active section; the adjacent-sibling selector keys
   visibility off the Drive row's .is-active (set server-side on the files tab
   and by caseNavActivateDrive on soft-nav), so there's no expand state. */
.sb-subnav {
  display: none;
}

/* Drive row now sits in a .sb-row-host (for the search-icon sibling), so the
   sub-nav follows the host, not the row — reach the active row through :has.
   Only the Drive host is followed by a .sb-subnav, so this can't false-match
   another active row (e.g. Overview). */
.sb-row-host:has(.sb-row.is-active) + .sb-subnav {
  display: block;
}

/* Sub-rows sit under the Drive row — indent so their icon lines up under the
   parent's label (past the parent icon column + its gap). */
.sb-row--sub {
  padding-left: calc(var(--sb-row-pad) + var(--sb-icon) + var(--space-1));
  color: var(--color-muted);
}

/* Upload is a <button> (fires the modal) but must read as a rail row — strip the
   native button chrome and inherit the row's type. A button sizes to its content
   by default, so pin its margin-box to the full rail width (matching the auto-
   width <a> rows: width = 100% − the row's own horizontal margins). */
.sb-row--action {
  box-sizing: border-box;
  width: calc(100% - 2 * var(--sb-row-margin-x));
  background: none;
  border: 0;
  font-family: inherit;
  text-align: left;
}

/* Inline Drive search — the search icon swaps the Drive row for this field in
   place (parent host gets .is-searching). Mirrors the row box (height, insets,
   radius, type) so the morph is seamless; the input itself is transparent. */
.sb-drive-search {
  display: none;
  align-items: center;
  gap: var(--space-1);
  height: var(--sb-row-h);
  padding: 0 var(--sb-row-pad);
  margin: 1px var(--sb-row-margin-x);
  border-radius: var(--sb-radius);
  background: var(--color-active);
  font-size: var(--font-xs);
}

.sb-row-host.is-searching > .sb-drive-search {
  display: flex;
}

.sb-row-host.is-searching > .sb-row {
  display: none;
}

.sb-drive-search__input {
  flex: 1;
  min-width: 0;
  padding: 0;
  border: 0;
  outline: none;
  background: transparent;
  font-family: inherit;
  font-size: var(--font-xs);
  line-height: var(--line-height-normal);
  color: var(--color-text-strong);
}

.sb-drive-search__input::placeholder {
  color: var(--color-muted);
}

/* Hide the native WebKit clear "x" — the field has its own clear/close button
   (.sb-drive-search__clear), so the built-in one reads as a duplicate. */
.sb-drive-search__input::-webkit-search-cancel-button {
  display: none;
  -webkit-appearance: none;
  appearance: none;
}

.sb-drive-search__clear {
  flex: none;
  width: 18px;
  height: 18px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: var(--radius-xs);
  color: var(--color-muted);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.sb-drive-search__clear:hover {
  color: var(--color-text);
  background: var(--color-hover);
}

/* Search toggle — a right-aligned icon on the Drive row (sibling of the row's
   <a>, inside .sb-row-host). Shown only while Drive's active view is Browse and
   the inline search isn't already open. */
.sb-row-search {
  position: absolute;
  top: 50%;
  right: calc(var(--sb-row-margin-x) + var(--sb-row-pad));
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  padding: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: var(--radius-xs);
  color: var(--color-muted);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.sb-row-host:not(.is-searching) > .sb-row.is-active[data-drive-view="requirement"] + .sb-row-search {
  display: inline-flex;
}

.sb-row-search:hover {
  color: var(--color-text);
  background: var(--color-hover);
}

.sb-divider {
  height: 1px;
  background: #e4e1eb;
  margin: var(--space-2) var(--sb-row-margin-x);
}

/* Pinned bottom-of-rail utility slot — currently the keyboard-shortcuts chip.
   A direct child of .sb (the flex column), so it stays put while .sb-body
   scrolls above it. */
.sb-footer {
  display: flex;
  flex-shrink: 0;
  padding: var(--space-2) var(--sb-row-margin-x);
  border-top: 1px solid var(--color-border-subtle);
}

/* `display: flex` above outranks the [hidden] UA rule, so re-assert it. */
.sb-footer[hidden] {
  display: none;
}

/* Rail pill — share .sb-row's insets so the keycap lines up under the nav-row
   icons (--sb-row-margin-x on the footer + --sb-row-pad here), and keep the
   chord snug to the label. */
.sb-footer .shortcuts-btn {
  gap: var(--space-1);
  padding: var(--space-1) var(--sb-row-pad);
}

/* Flat keys on the rail — drop the raised white surface/border; just the
   muted glyphs, matching the rest of the nav. The cheat-sheet keeps the
   raised .keycap look. */
.sb-footer .keycap {
  min-width: 0;
  height: auto;
  padding: 0;
  background: transparent;
  border: 0;
  color: var(--color-muted);
  font-weight: var(--weight-normal);
}

.sb-group-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1);
  padding: 2px var(--sb-group-pad-x);
  color: var(--color-muted);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
}

.sb-group-label__text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1.5;
  padding: 1px 0;
}

.sb-group-add {
  width: 16px;
  height: 16px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: var(--radius-xs);
  color: var(--color-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}

.sb-row-host {
  position: relative;
}

.sb-row-add {
  position: absolute;
  top: 50%;
  right: calc(var(--sb-row-margin-x) + var(--sb-row-pad));
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: var(--radius-xs);
  color: var(--color-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}

.sb-row-add svg {
  width: 12px;
  height: 12px;
}

.sb-row-host:hover .sb-row-add {
  opacity: 1;
}

.sb-row-add:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.sb-group-add svg {
  width: 12px;
  height: 12px;
}

.sb-group-label:hover .sb-group-add {
  opacity: 1;
}

.sb-group-add:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

/* ── Collapsible parent rows + children tree — used by the Design System
   nav (partials/design_system_nav.html). Layered on the .sb-* primitives
   above. Mirrors design/Navigation - handoff.html. ── */

.sb-row__caret {
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-left: auto;
  color: var(--color-muted);
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  border-radius: var(--radius-xs);
  transition: transform var(--transition-normal) ease, background var(--transition-fast);
}

.sb-row__caret svg {
  width: 11px;
  height: 11px;
}

.sb-row__caret:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.sb-row[aria-expanded="true"] > .sb-row__caret {
  transform: rotate(90deg);
}

.sb-children {
  display: none;
  margin: 2px 0 var(--space-2);
  /* 28px tie-line indent — aligns the vertical rule with the parent icon. */
  padding-left: calc(var(--space-6) + var(--space-1));
  position: relative;
}

.sb-row[aria-expanded="true"] + .sb-children {
  display: block;
}

.sb-children::before {
  content: "";
  position: absolute;
  left: 19px;
  top: 4px;
  bottom: 4px;
  width: 1px;
  background: var(--color-border);
}

/* A child row reads like its parent row: same size, weight and colour, one
   level in, so a group's items are menu items and not a second typeface. */
.sb-child {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 24px;
  padding: 0 var(--space-3);
  /* Same right edge as the parent row above it. */
  margin: 1px var(--sb-row-margin-x) 1px 0;
  border-radius: var(--sb-radius);
  font-size: var(--font-xs);
  color: var(--color-muted);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.sb-child:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.sb-child.is-active,
.sb-child[aria-current="page"] {
  background: var(--color-active);
  color: var(--color-text-strong);
}

.sb-child__label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: var(--line-height-normal);
  padding: 1px 0;
}

/* docs-nav-* inner content of .sb-row--doc inherits its own existing
   styling — leave those rules in place further down the file. */

/* Tab bar */
.tab-bar {
  display: flex;
  background: transparent;
  border-bottom: 1px solid var(--color-border);
  padding: 0;
  margin-bottom: 1.75rem;
}

.tab {
  padding: 0.65rem 1rem;
  font-size: var(--font-base);
  color: var(--color-muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color var(--transition-normal), border-color var(--transition-normal);
  margin-bottom: -1px;
  white-space: nowrap;
}

.tab:hover {
  color: var(--color-text);
}

.tab--active {
  color: var(--color-text);
  border-bottom-color: var(--color-text);
  font-weight: var(--weight-semibold);
}

/* Tab content area */
.tab-content {
  padding: 1.75rem 2rem;
  max-width: 900px;
}

/* ── Field table (Details tab) ──────────────────────────────────────────────── */

.details-panel {}

.field-table {
  width: 100%;
  border-collapse: collapse;
}

.field-row td {
  padding: 0.65rem 0;
  border-bottom: 1px solid var(--color-border);
  vertical-align: middle;
}

.field-row:last-child td {
  border-bottom: none;
}

.field-label {
  width: 220px;
  font-size: 0.78rem;
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  padding-right: var(--space-6);
  white-space: nowrap;
}

.field-value-cell {
  cursor: pointer;
}

.field-value-cell:hover .field-edit-hint {
  opacity: 1;
}

.field-edit-hint {
  margin-left: var(--space-3);
  font-size: 0.7rem;
  color: var(--color-accent);
  opacity: 0;
  transition: opacity var(--transition-normal);
}

.field-input {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-xs);
  padding: 0 var(--space-1);
  /* Match the displayed value: same size, right-aligned, same spot, so the
     text doesn't grow or jump to the left when the field enters edit mode. */
  font-size: var(--font-xs);
  font-family: inherit;
  text-align: right;
  width: 100%;
  max-width: 380px;
  outline: none;
}

.field-input:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Month picker — the months field's editor (#1248, static/js/month_picker.js):
   twelve toggles, three to a row — four left "Oct 2026" no room beside the
   label. Mirrored in the Front sidebar's own <style>
   (front_plugin/sidebar.html), which cannot link this. */
.month-picker {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-1);
  width: 100%;
  max-width: 380px;
  margin-left: auto;
  outline: none;
}

.month-picker__opt {
  appearance: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background: var(--color-surface);
  color: var(--color-muted);
  font-family: inherit;
  font-size: var(--font-xxs);
  font-weight: var(--weight-medium);
  padding: var(--space-1);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.month-picker__opt:hover {
  color: var(--color-text);
}

.month-picker__opt[aria-pressed="true"] {
  background: var(--color-accent-50);
  border-color: var(--color-accent);
  color: var(--color-accent);
  font-weight: var(--weight-semibold);
}

.month-picker__opt:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* ── Activity log ───────────────────────────────────────────────────────────── */

.activity-panel {}

.activity-list {
  list-style: none;
}

.activity-item {
  display: grid;
  grid-template-columns: 160px 150px 1fr;
  gap: var(--space-3);
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--color-border);
  font-size: var(--font-base);
  align-items: baseline;
}

.activity-item:last-child {
  border-bottom: none;
}

.activity-when {
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
}

.activity-who {
  font-size: 0.8rem;
  color: var(--color-muted);
  font-weight: var(--weight-medium);
}

.activity-what {
  color: var(--color-text);
}

/* ── Links tab ──────────────────────────────────────────────────────────────── */

.links-panel {}

.links-list-items {
  list-style: none;
  margin-bottom: var(--space-6);
}

.link-item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--color-border);
}

.link-item:last-child {
  border-bottom: none;
}

.link-url {
  color: var(--color-accent);
  text-decoration: none;
  font-size: var(--font-base);
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.link-url:hover {
  text-decoration: underline;
}

/* Icon-only button */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 5px 3px;
  border: none;
  border-radius: var(--radius-xs);
  cursor: pointer;
  font-size: var(--font-sm);
  line-height: 1;
  font-family: inherit;
  color: var(--color-text); /* not the browser's button black (/ux text-color-token) */
  background: transparent;
  transition: color var(--transition-normal), background var(--transition-normal);
  flex-shrink: 0;
}

.btn-icon:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.btn-danger-ghost:hover {
  color: var(--color-danger);
  background: rgba(224, 83, 83, 0.1);
}

.link-add-form {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: center;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}

/* ── Form (case creation) ────────────────────────────────────────────────────── */

.back-link {
  display: inline-block;
  font-size: var(--font-sm);
  color: var(--color-muted);
  text-decoration: none;
  margin-bottom: 0.4rem;
  transition: color var(--transition-normal);
}

.back-link:hover {
  color: var(--color-text);
}

.form-card {
  max-width: 640px;
  padding: 2rem;
}

.form-section {
  padding-bottom: var(--space-6);
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--color-border);
}

.form-section:last-of-type {
  border-bottom: none;
  margin-bottom: 0;
}

.no-p2-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--color-muted);
  cursor: pointer;
  margin-left: 0.75rem;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/* A column of full-width form groups, one control per line.
   `.form-row` is an auto-fill grid, so a lone child takes one ~200px column
   rather than the full width; and bare sibling `.form-group`s have no gap at
   all, which butts one group's hint against the next group's label. This is
   the one-question-per-line case (the lead form's intake answers). */
.form-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

/* The label: the same voice as a case-sidebar section head ("PEOPLE"), one
   small uppercase style across the platform, whether it sits over a field
   or titles a grid card. This rule alone carries the look; .card-title
   below adds only its inset (/ux label-voice). */
.form-label,
.card-title {
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.07em;
}

.required {
  color: var(--color-danger);
}

/* Inline checkbox/radio row — the label wraps an <input> + caption on one
   line (the design-system class for what older admin forms inline-style). */
.form-check-label {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-weight: var(--weight-normal);
  cursor: pointer;
}

.form-control {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.75rem;
  font-size: var(--font-base);
  font-family: inherit;
  width: 100%;
  transition: border-color var(--transition-normal);
}

.form-control::placeholder {
  color: var(--color-muted);
}

.form-control:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.form-control:disabled,
.form-control[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
  background: rgba(0, 0, 0, 0.03);
}

select.form-control {
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  padding: var(--space-2) var(--space-6) var(--space-2) var(--space-3);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 16 16' fill='none' stroke='%23979797' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  transition: background-color var(--transition-normal), border-color var(--transition-normal);
}

/* A select inside a form group is a field, not a toolbar control: it takes
   the input's padding, size and colour so a row of fields is one height
   (/ux control-row-height). The toolbar look above stays for selects
   outside a form group. */
.form-group > select.form-control {
  padding: 0.5rem var(--space-8) 0.5rem 0.75rem;
  font-size: var(--font-base);
  line-height: var(--line-height-tight);
  color: var(--color-text);
  background-color: var(--color-bg);
}

select.form-control:hover,
select.form-control:focus {
  border-color: var(--color-muted);
  background-color: var(--color-hover);
  box-shadow: none;
  outline: none;
}

.form-control--sm {
  padding: 0.35rem 0.6rem;
  font-size: 0.8rem;
}

.form-hint {
  font-size: var(--font-xs);
  color: var(--color-muted);
  margin-top: 0.15rem;
}

.stage-preview {
  color: var(--color-muted);
}

.form-actions {
  display: flex;
  gap: var(--space-3);
  padding-top: var(--space-5);
}

.btn-link {
  background: none;
  border: none;
  color: var(--color-accent);
  font-size: 0.8rem;
  cursor: pointer;
  padding: 0;
  font-family: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color var(--transition-normal);
}

.btn-link:hover {
  color: var(--color-accent-hover);
}

.btn-link:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.mt-sm {
  margin-top: var(--space-3);
}

.mb-sm {
  margin-bottom: var(--space-3);
}

.mb-lg {
  margin-bottom: var(--space-6);
}

/* ── Client typeahead ────────────────────────────────────────────────────────── */

.client-results-dropdown {
  margin-top: var(--space-1);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.client-results-list {
  list-style: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.client-result-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
  transition: background var(--transition-fast);
}

.client-result-item:last-child {
  border-bottom: none;
}

.client-result-item:hover {
  background: color-mix(in srgb, var(--color-accent) 5%, transparent);
}

.client-result-info {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.client-result-name {
  font-size: var(--font-base);
  font-weight: var(--weight-medium);
}

.client-result-email {
  font-size: var(--font-xs);
}

.client-results-empty {
  font-size: 0.8rem;
  padding: 0.5rem 0;
}

.client-selected-display {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0.5rem 0.75rem;
  background: color-mix(in srgb, var(--color-accent) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-accent) 25%, transparent);
  border-radius: var(--radius-sm);
}

.client-selected-name {
  font-size: var(--font-base);
  font-weight: var(--weight-medium);
  flex: 1;
}

.create-new-section {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-top: var(--space-3);
  padding: 0.75rem;
  background: rgba(0, 0, 0, 0.02);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

/* ── Inline panels (saved views, column picker) ──────────────────────────────── */

.inline-panel {
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
  padding: 0;
}

.inline-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--color-border);
}

.inline-panel-title {
  font-size: 0.8rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-muted);
}

.inline-panel-section {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--color-border);
}

.panel-save-row {
  display: flex;
  gap: var(--space-3);
  align-items: center;
}

.panel-save-row .form-control--sm {
  flex: 1;
}

.inline-panel-empty {
  padding: 1rem;
  font-size: 0.8rem;
  color: var(--color-muted);
}

.inline-panel-footer {
  padding: 0.75rem 1rem;
  border-top: 1px solid var(--color-border);
}

/* Saved views list */
.views-list {
  list-style: none;
}

.view-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--color-border);
}

.view-item:last-child {
  border-bottom: none;
}

.view-name {
  font-size: var(--font-base);
  font-weight: var(--weight-medium);
  flex: 1;
}

.view-name--link {
  color: var(--color-text);
  text-decoration: none;
}

.view-name--link:hover {
  color: var(--color-muted);
}

.view-actions {
  display: flex;
  gap: var(--space-2);
  flex-shrink: 0;
}

.view-rename {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  width: 100%;
  padding-top: var(--space-3);
}

.view-rename.hidden {
  display: none;
}

.view-rename .form-control--sm {
  flex: 1;
}

/* Column picker */
.column-picker-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.1rem;
  padding: 0.75rem 1rem;
}

.column-option {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0.35rem 0.25rem;
  font-size: 0.8rem;
  cursor: pointer;
  border-radius: var(--radius-xs);
  color: var(--color-text);
  user-select: none;
}

.column-option:hover {
  background: var(--color-hover);
}

.column-option input[type="checkbox"] {
  accent-color: var(--color-accent);
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  cursor: pointer;
}

.status-badge--active {
  background: rgba(26, 140, 66, 0.1);
  color: var(--color-success);
}

.status-badge--closed {
  background: rgba(107, 113, 144, 0.1);
  color: var(--color-muted);
}

.status-badge--cancelled {
  background: rgba(214, 59, 59, 0.1);
  color: var(--color-danger);
}

/* ── Docs tool ───────────────────────────────────────────────────────────────── */

.upload-zone {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-md);
  padding: 1rem;
  color: var(--color-muted);
  font-size: 0.82rem;
  cursor: pointer;
  transition: border-color var(--transition-normal), background var(--transition-normal);
}

.upload-zone--over {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 5%, transparent);
}

.upload-browse-link {
  color: var(--color-accent);
  cursor: pointer;
  text-decoration: underline;
  font-weight: var(--weight-medium);
}

.upload-browse-link:hover {
  opacity: 0.8;
}

.upload-hint {
  font-size: var(--font-xs);
  color: var(--color-muted);
  opacity: 0.7;
  width: 100%;
  margin-top: var(--space-1);
}

.upload-section {
  margin-top: var(--space-6);
  padding-top: 1.25rem;
  border-top: 1px solid var(--color-border);
}

.upload-form-client {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.doc-thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}

/* ── Document requirements (nav-panel style) ──────────────────────────────── */

/* Legacy classes kept for badge reuse */
.req-section {
  margin-bottom: var(--space-8);
}

.req-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
}

.req-section-title {
  font-size: 0.82rem;
  font-weight: var(--weight-semibold);
  margin: 0;
}

.req-section-role {
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  margin-left: var(--space-3);
  font-size: var(--font-xs);
}

.req-section-count {
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  margin-left: 0.35rem;
}

.req-empty {
  color: var(--color-muted);
  font-size: 0.82rem;
  padding: 0.75rem 0;
  margin: 0;
}

.req-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

/* ── Requirement item (accordion) ── */

.req-item {
  background: var(--color-surface);
  cursor: pointer;
}

.req-item-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 0.875rem;
  cursor: pointer;
  transition: background var(--transition-fast);
  gap: var(--space-4);
}

.req-item-header:hover {
  background: color-mix(in srgb, var(--color-accent) 3%, transparent);
}

.req-item-left {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.req-private {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-xs);
  background: rgba(107, 113, 144, 0.1);
  color: var(--color-muted);
  font-size: 0.65rem;
  font-weight: var(--weight-semibold);
  flex-shrink: 0;
}

.req-item-label {
  font-size: 0.85rem;
  font-weight: var(--weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.req-item-right {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
}

.req-badge {
  font-size: 0.7rem;
  font-weight: var(--weight-medium);
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-lg);
}

.req-badge--empty {
  background: rgba(107, 113, 144, 0.08);
  color: var(--color-muted);
}

.req-badge--uploaded {
  background: color-mix(in srgb, var(--color-accent) 8%, transparent);
  color: var(--color-accent);
}

.req-badge--approved {
  background: rgba(34, 150, 79, 0.08);
  color: var(--color-success);
}

.req-badge--flagged {
  background: rgba(214, 59, 59, 0.08);
  color: var(--color-danger);
}

.req-item-files {
  font-size: 0.72rem;
  color: var(--color-muted);
}

/* Expand body removed — requirements now load workspace on click */

/* ── Files inside a requirement ── */

.req-file {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-border);
  cursor: pointer;
  transition: background var(--transition-fast);
}

.req-file:last-of-type {
  border-bottom: none;
}

.req-file:hover {
  background: color-mix(in srgb, var(--color-accent) 3%, transparent);
  margin: 0 -0.875rem;
  padding-left: 0.875rem;
  padding-right: 0.875rem;
}

.req-file-thumb {
  width: 36px;
  height: 36px;
  object-fit: cover;
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}

.req-file-name {
  flex: 1;
  font-size: 0.82rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.req-file-status {
  font-size: 0.7rem;
  font-weight: var(--weight-medium);
  flex-shrink: 0;
}

.req-file-status--approved {
  color: var(--color-success);
}

.req-file-status--rejected {
  color: var(--color-danger);
}

.req-file-status--pending {
  color: var(--color-muted);
}

.req-file-empty {
  color: var(--color-muted);
  font-size: 0.82rem;
  padding: 0.6rem 0;
  margin: 0;
}

/* Footer with upload + remove */
.req-item-footer {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}

.req-upload-inline {
  margin: 0;
}

.req-upload-btn {
  cursor: pointer;
}

.req-note {
  flex: 1;
  font-size: 0.72rem;
  color: var(--color-muted);
  font-style: italic;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.req-item-footer .text-danger {
  margin-left: auto;
}

/* ── Add requirement modal form ── */

.add-req-modal-form {
  display: flex;
  flex-direction: column;
}

.add-req-modal-form .modal-body {
  display: flex;
  flex-direction: column;
  gap: calc(var(--space-4) + 2px);
}

.add-req-modal-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.add-req-modal-label {
  color: var(--color-muted);
}

.add-req-modal-form select {
  width: 100%;
  height: var(--control-h);
  padding: 0 28px 0 var(--space-3);
  font-size: var(--font-xs);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23979797' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-size: 14px;
  appearance: none;
  -webkit-appearance: none;
  font-family: inherit;
  cursor: pointer;
  transition: background-color var(--transition-normal), color var(--transition-normal), border-color var(--transition-normal);
}

.add-req-modal-form select:hover {
  background-color: var(--color-hover);
  color: var(--color-text);
  border-color: var(--color-muted);
}

.add-req-modal-actions {
  display: flex;
  justify-content: flex-end;
  padding: 0.75rem 1.25rem;
  border-top: 1px solid #f3f3f3;
}

/* ── Manage requirements modal (#652) ── */

.add-req-modal-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.manage-req-cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
}

.manage-req-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.manage-req-col__head {
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-muted);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border);
}

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

.manage-req-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  transition: background-color var(--transition-normal);
}

.manage-req-item:hover {
  background-color: var(--color-hover);
}

.manage-req-item__label {
  min-width: 0;
  overflow: hidden;
  font-size: var(--font-xs);
  color: var(--color-text);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.manage-req-item__menu {
  flex: none;
}

.manage-req-item__menu > summary {
  width: 24px;
  height: 24px;
}

.mh-picker-item--danger {
  color: var(--color-danger);
}

.mh-picker-item--danger:hover {
  background: var(--color-danger-bg, var(--color-hover));
}

.manage-req-col__empty {
  margin: 0;
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.add-req-modal-divider {
  height: 1px;
  background-color: var(--color-border);
}

.add-req-modal-addbar {
  flex-direction: row;
  align-items: flex-end;
  gap: var(--space-3);
}

.add-req-modal-addbar .add-req-modal-field {
  flex: 1;
  min-width: 0;
}

.add-req-modal-addbar .add-req-modal-checkbox {
  flex: none;
  padding-bottom: var(--space-2);
}

.add-req-modal-addbar .btn {
  flex: none;
}

.add-req-modal-addbar .add-req-modal-field--qty {
  flex: 0 0 56px;
}

.add-req-modal-qty {
  width: 100%;
  height: var(--control-h);
  padding: 0 var(--space-2);
  font-size: var(--font-xs);
  font-family: inherit;
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: transparent;
  transition: border-color var(--transition-normal);
}

.add-req-modal-qty:hover {
  border-color: var(--color-muted);
}


/* ── Unsorted files row ── */

.unsorted-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0.6rem 0.875rem;
  background: var(--color-surface);
}

.unsorted-thumb {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}

.unsorted-info {
  flex: 1;
  min-width: 0;
}

.unsorted-filename {
  display: block;
  font-size: 0.82rem;
  font-weight: var(--weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.unsorted-meta {
  font-size: 0.72rem;
  color: var(--color-muted);
}

.unsorted-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

.unsorted-assign {
  min-width: 160px;
}

/* Notification email delivery state (team-side inbox pill, #594 c2). */
.delivery-badge {
  display: inline-block;
  margin-left: var(--space-2);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-pill);
  vertical-align: middle;
}

.delivery-badge--queued {
  background: color-mix(in srgb, var(--color-muted) 15%, transparent);
  color: var(--color-muted);
}

.delivery-badge--sent {
  background: color-mix(in srgb, var(--color-success) 15%, transparent);
  color: var(--color-success);
}

.delivery-badge--failed {
  background: color-mix(in srgb, var(--color-danger) 15%, transparent);
  color: var(--color-danger);
}

/* ── Docs queue ─────────────────────────────────────────────────────────────── */

.tab-count {
  font-size: 0.78rem;
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  margin-left: 0.2rem;
}

.tab--active .tab-count {
  color: inherit;
}

.tab--empty .tab-count {
  opacity: 0.5;
}

/* A zero count stays mounted (a stable OOB swap target) but hidden, so a
   surgical insert/delete can patch it in place (#570). */
.tab-count--empty {
  display: none;
}

.queue-filters {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0.875rem 1.5rem;
  border-bottom: 1px solid var(--color-border);
}

.table-empty {
  text-align: center;
  color: var(--color-muted);
  font-size: var(--font-base);
  padding: 2.5rem 1rem;
}

/* ── System / Users ──────────────────────────────────────────────────────────── */

.user-row--inactive td {
  opacity: 0.5;
}

.role-select {
  background: transparent;
  border: none;
  color: var(--color-text);
  font-size: var(--font-base);
  font-family: inherit;
  cursor: pointer;
  padding: 0;
  outline: none;
}

.role-select:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.role-select:hover {
  color: var(--color-accent);
}

.role-select option {
  background: var(--color-surface);
  color: var(--color-text);
}

.role-badge {
  font-size: 0.8rem;
  color: var(--color-muted);
}

.status-badge {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  padding: 0.2rem 0.5rem;
  border-radius: var(--radius-pill);
}

.status-badge--active {
  background: rgba(26, 140, 66, 0.1);
  color: var(--color-success);
}

.status-badge--inactive {
  background: rgba(107, 113, 144, 0.1);
  color: var(--color-muted);
}

.btn-sm {
  padding: 0.3rem 0.6rem;
  font-size: var(--font-xs);
}

/* Teammate composite cell — avatar + name + meta lines.
   Used by tools/people/templates/partials/team_row.html. */
.teammate-cell {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.teammate-cell__body {
  min-width: 0;
}

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

.teammate-cell__meta {
  color: var(--color-muted);
  font-size: var(--font-xs);
}

.team-resend-invite {
  display: inline;
  margin-top: var(--space-2);
}

.team-status-case-link {
  color: var(--color-muted);
  font-size: var(--font-xs);
  margin-left: var(--space-2);
}

/* Planned absence under live presence in the team list's status cell
   (#1220) — its own line, so "Online" and "Away" never read as one state. */
.team-status-leave {
  margin-top: var(--space-1);
  font-size: var(--font-xs);
}

.team-status-leave__detail {
  color: var(--color-muted);
}

/* Leave's inline actions — the Team calendar's detail and the Waiting list (#1227). */
.leave-row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.leave-inline-form {
  display: inline;
}

.leave-meta {
  font-size: var(--font-xs);
}

.leave-empty {
  padding: var(--space-8);
  text-align: center;
}

.new-user-modal-buttons {
  display: flex;
  gap: var(--space-3);
}

.people-portal-empty {
  color: var(--color-muted);
  font-size: var(--font-xs);
}

/* Inline "+ New person" form on /people — sits outside the lv-host
   card; appears via a CTA button toggle and stays minimal. */
.new-person-form {
  margin-bottom: var(--space-5);
  padding: var(--space-5);
}

.new-person-form__title {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  margin-bottom: var(--space-4);
}

.new-person-form__actions {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  margin-top: var(--space-4);
  justify-content: flex-end;
}

.new-user-fields {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--space-5);
}

.hidden {
  display: none !important;
}

/* ── Modal ── */

.modal-overlay {
  display: flex;
  position: fixed;
  inset: 0;
  background: rgb(255 255 255 / 59%);
  z-index: var(--z-toast);
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* The blur lives on the open state only. `backdrop-filter` forces a
   viewport-sized compositing layer per element whether or not it is visible,
   and a closed overlay is still laid out (opacity 0, not display: none). With
   the blur on the base rule /system/website-forms rendered 200 closed overlays
   as 203 full-screen layers — ~8 GB of tiles on a retina display — and Chrome
   stopped painting the sidebar, the header and most of the page. Measured with
   CDP LayerTree: 205 layers with the blur on the base rule, 5 without. */
.modal-overlay.modal-visible {
  opacity: 1;
  pointer-events: auto;
  backdrop-filter: blur(1px);
}

.modal-dialog {
  background: var(--color-surface);
  border: 1px solid #f1f1f1;
  border-radius: var(--radius-md);
  max-width: 90vw;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  opacity: 0;
  transform: scale(0.98);
  transform-origin: center center;
  box-shadow:
    0 0 30px 0 rgb(0 0 0 / 5%),
    0 0 20px 0 rgb(0 0 0 / 5%);
  transition: opacity 0.5s cubic-bezier(0.2, 0.9, 0.2, 1), transform 0.3s cubic-bezier(0.67, 0.33, 0.47, 1.56);
}

.modal-overlay.modal-visible .modal-dialog {
  opacity: 1;
  /* `none`, not `scale(1)`: any non-`none` transform makes the dialog the
     containing block for `position: fixed` descendants (e.g. .mh-picker-menu--more
     kebab menus), re-basing them to the centred dialog instead of the viewport so
     more_button.js mis-places them low/off-screen (#725). Entry still animates from
     the hidden scale(0.98) — none interpolates as the identity matrix. */
  transform: none;
}

.modal-dialog--sm {
  width: 420px;
}

.modal-dialog--md {
  width: 540px;
}

.modal-dialog--lg {
  width: 720px;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid #f3f3f3;
}

.modal-title {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  margin: 0;
}

.modal-body {
  padding: 1rem 1.25rem;
  overflow-y: auto;
  flex: 1;
}

.confirm-modal__confirm-word {
  margin-top: var(--space-4);
}

.confirm-modal__extra-field {
  margin-top: var(--space-3);
}

.modal-footer {
  padding: 0.75rem 1.25rem;
  border-top: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

/* ── Filter builder ── */

.filter-section {
  margin-bottom: 1.25rem;
}

.filter-section-title {
  font-size: 0.8rem;
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  margin: 0 0 0.5rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.filter-conditions {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
}

.filter-condition-row {
  display: flex;
  gap: 0.35rem;
  align-items: center;
}

.filter-condition-row select,
.filter-condition-row input {
  min-width: 0;
  flex: 1;
}

.filter-condition-row .filter-value-wrap {
  flex: 1;
  display: flex;
}

.filter-condition-row .filter-value-wrap select,
.filter-condition-row .filter-value-wrap input {
  width: 100%;
}

.filter-save-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1;
}

.filter-actions {
  display: flex;
  gap: 0.4rem;
}

.btn-icon--sm {
  width: 24px;
  height: 24px;
  min-width: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-muted);
  border-radius: var(--radius-xs);
  font-size: var(--font-xs);
}

.btn-icon--sm:hover {
  background: rgba(107, 113, 144, 0.1);
  color: var(--color-danger);
}

/* ── Shared view badges ── */

.view-owner {
  font-size: 0.7rem;
  color: var(--color-muted);
  margin-left: var(--space-1);
}

/* ── HTMX button loading state ───────────────────────────────────────────────── */

.btn.htmx-request,
form.htmx-request .btn[type="submit"] {
  opacity: 0.6;
  cursor: not-allowed;
  pointer-events: none;
}

/* ── Error pages ─────────────────────────────────────────────────────────────── */

.error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
  text-align: center;
  gap: var(--space-4);
}

.error-page .error-code {
  font-size: 4rem;
  font-weight: var(--weight-bold);
  color: var(--color-border);
  line-height: 1;
}

.error-page h2 {
  font-size: 1.1rem;
  font-weight: var(--weight-medium);
}

.error-page p {
  color: var(--color-muted);
  font-size: var(--font-base);
  max-width: 320px;
}

/* ── Responsive — 900px breakpoint ──────────────────────────────────────────── */

@media (max-width: 900px) {
  .card {
    border-radius: 0;
    border-left: none;
    border-right: none;
    margin-left: -1rem;
    margin-right: -1rem;
  }

  .data-table {
    display: block;
    overflow-x: auto;
    white-space: nowrap;
  }

  .case-detail-layout {
    flex-direction: column;
  }

  .doc-panel-inner {
    width: 100%;
  }
}

/* ── System tabs ───────────────────────────────────────────────────────────── */

.system-tabs {
  display: flex;
  gap: 0;
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--color-border);
}

.system-tab {
  padding: 0.6rem 1.25rem;
  font-size: 0.85rem;
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color var(--transition-normal), border-color var(--transition-normal);
}

.system-tab:hover:not(.system-tab--disabled) {
  color: var(--color-text);
}

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

.system-tab--disabled {
  opacity: 0.35;
  cursor: default;
}

/* ── Filter date input ─────────────────────────────────────────────────────── */

.filter-input {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
  font-size: 0.8rem;
  font-family: inherit;
  transition: border-color var(--transition-normal);
}

.filter-input:focus {
  outline: none;
  border-color: var(--color-accent);
}

.filter-input--date {
  border-radius: var(--radius-pill);
  padding: 0.4rem 0.75rem;
  cursor: pointer;
}

.filter-input--date::-webkit-calendar-picker-indicator {
  opacity: 0.5;
  cursor: pointer;
  margin-left: var(--space-1);
}

/* ── Source badges ──────────────────────────────────────────────────────────── */

.source-badge {
  font-size: 0.7rem;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-xs);
  white-space: nowrap;
}

.source-badge--auth {
  background: rgba(59, 130, 246, 0.1);
  color: var(--color-info);
}

.source-badge--case {
  background: rgba(26, 140, 66, 0.1);
  color: var(--color-success);
}

.source-badge--gdpr {
  background: rgba(214, 59, 59, 0.1);
  color: var(--color-danger);
}

.source-badge--system {
  background: rgba(107, 114, 128, 0.1);
  color: #4b5563;
}

/* The hand behind an impersonated audit row — second line under the actor,
   same weight as the e-mail line beside it (#1009). */
.audit-table__hand {
  font-size: var(--font-xs);
}

/* ── Audit footer ──────────────────────────────────────────────────────────── */

.audit-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-5) var(--space-5);
  border-top: 1px solid var(--color-border);
}

/* ── Toggle switch ─────────────────────────────────────────────────────────── */

.toggle-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
}

.toggle-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-switch {
  position: relative;
  display: inline-block;
  width: 2.25rem;
  height: 1.25rem;
  background: var(--color-border);
  border-radius: var(--radius-pill);
  transition: background var(--transition-slow);
  flex-shrink: 0;
}

.toggle-switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 1rem;
  height: 1rem;
  background: var(--color-surface);
  border-radius: 50%;
  transition: transform var(--transition-slow);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

.toggle-input:checked+.toggle-switch {
  background: var(--color-accent);
}

.toggle-input:checked+.toggle-switch::after {
  transform: translateX(1rem);
}

.toggle-input:focus-visible+.toggle-switch {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ── System info ─────────────────────────────────────────────────────────────── */

.info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: var(--space-5);
}

/* Voice comes from the .form-label rule above; this is only the inset. */
.card-title {
  padding: var(--space-5) var(--space-5) var(--space-3);
  margin: 0;
}

/* Second-level heading inside a card, between .card-title sections (a card
   that holds two tables). Carries the card inset itself, like .card-title. */
.card-subheading {
  margin: 0;
  padding: var(--space-4) var(--space-5) var(--space-3);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
}

/* Blocks placed directly in a card that carry no inset of their own
   (/ux card-inset-x + card-inset-bottom). Scoped to direct children so the
   same classes inside a table cell or a modal keep their own spacing. */
.card > .status-dl,
.card > .inline-form,
.card > .card-list {
  padding: 0 var(--space-5) var(--space-5);
}

/* A form grid that is the card's content carries the inset on all sides. */
.card > .form-row {
  padding: var(--space-5);
}

/* A whole form as the card's content, under a .card-subheading: inset on the
   sides and the bottom; the subheading above carries the top. */
.card > .card-form {
  padding: 0 var(--space-5) var(--space-5);
}

.card > .card-form:first-child {
  padding-top: var(--space-5);
}

/* A field that takes the whole row of a .form-row grid (a textarea). */
.form-row__wide {
  grid-column: 1 / -1;
}

/* A row of checkboxes under one label. */
.check-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  padding-top: var(--space-2);
}

/* A back link at the top of a page grid: the next section's own margin is
   the whole gap (/ux section-gap). */
.page-grid .back-link {
  margin-bottom: 0;
}

/* Flash messages at the top of a page grid sit one card gap above the
   content: the row gap less the difference. */
.page-grid > .alert {
  margin-bottom: calc(var(--space-5) - var(--space-8));
}

.card > .card-list {
  margin: 0;
  padding-left: var(--space-8);
  font-size: var(--font-xs);
}

/* The same blocks, when they open a card (no .card-title above since the
   section-head pattern took the title out), carry the top inset too. */
.card > :first-child:is(.status-line, .status-dl, .card-list, .inline-form, .card-subheading) {
  padding-top: var(--space-5);
}

/* And a status line that closes a card carries the bottom inset. */
.card > .status-line:last-child {
  padding-bottom: var(--space-5);
}

/* A control row: input + button on one line, standard control gap. The
   label and the note keep to one line; the field keeps its own width
   instead of the block-level 100% a .form-control defaults to. */
.inline-form {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.inline-form > label,
.inline-form > span {
  white-space: nowrap;
}

.inline-form > .form-control {
  width: auto;
  /* Same height as the button beside it: every control in a row is
     --control-h (/ux control-row-height). */
  height: var(--control-h);
  padding-top: 0;
  padding-bottom: 0;
}

.info-list {
  margin: 0;
  padding: 0 1.25rem 1rem;
}

.info-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--color-border);
  gap: var(--space-5);
}

.info-row:last-child {
  border-bottom: none;
}

.info-row dt {
  font-size: 0.8rem;
  color: var(--color-muted);
  white-space: nowrap;
}

.info-row dd {
  margin: 0;
  font-size: 0.8rem;
  font-weight: var(--weight-medium);
  text-align: right;
}

/* ── Q&A sidebar (slide-out overlay) ──────────────────────────────────────── */

.qa-sidebar {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 360px;
  background: var(--color-surface);
  border-left: 1px solid var(--color-border);
  box-shadow: -4px 0 16px rgba(0, 0, 0, 0.06);
  transform: translateX(100%);
  transition: transform var(--transition-slow) ease;
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
}

.qa-sidebar.open {
  transform: translateX(0);
}

.qa-sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}

.qa-sidebar-title {
  font-size: 0.85rem;
  font-weight: var(--weight-semibold);
}

.qa-sidebar-close {
  background: none;
  border: none;
  color: var(--color-muted);
  cursor: pointer;
  padding: 0.25rem;
  border-radius: var(--radius-xs);
  transition: background var(--transition-normal), color var(--transition-normal);
  display: flex;
  align-items: center;
}

.qa-sidebar-close:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--color-text);
}

.qa-sidebar-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* ── Q&A messages ── */

.qa-context {
  flex-shrink: 0;
}

.qa-context-active {
  padding: 0.5rem 1rem;
  font-size: 0.72rem;
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 6%, transparent);
  border-bottom: 1px solid var(--color-border);
}

.qa-messages {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.qa-welcome {
  text-align: center;
  padding: 2rem 1rem;
  color: var(--color-muted);
  font-size: 0.82rem;
  line-height: 1.5;
}

.qa-msg {
  max-width: 95%;
}

.qa-msg-user {
  align-self: flex-end;
}

.qa-msg-user .qa-msg-content {
  background: var(--color-accent);
  color: var(--color-surface);
  border-radius: 12px 12px 2px 12px;
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

.qa-msg-assistant {
  align-self: flex-start;
}

/* The AI-generated marker on an answer — the ai-suggestion__label look. */
.qa-msg-ai {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-bottom: var(--space-1);
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-info);
  cursor: help;
}

.qa-msg-assistant .qa-msg-content {
  background: var(--color-bg);
  border-radius: 12px 12px 12px 2px;
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

.qa-streaming::after {
  content: "";
  display: inline-block;
  width: 6px;
  height: 14px;
  background: var(--color-accent);
  margin-left: 2px;
  animation: qa-cursor-blink 0.7s infinite;
  vertical-align: text-bottom;
}

@keyframes qa-cursor-blink {

  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0;
  }
}

.qa-error-text {
  color: var(--color-danger);
}

/* ── Q&A sources ── */

.qa-sources {
  margin-top: 0.4rem;
  font-size: 0.7rem;
  color: var(--color-muted);
  line-height: 1.6;
}

.qa-sources-label {
  font-weight: var(--weight-semibold);
}

.qa-source-link {
  display: inline-block;
  color: var(--color-accent);
  text-decoration: none;
  background: color-mix(in srgb, var(--color-accent) 8%, transparent);
  padding: 0.1rem 0.35rem;
  border-radius: var(--radius-xs);
  margin: 0.1rem 0.15rem;
  transition: background var(--transition-normal);
}

.qa-source-link:hover {
  background: color-mix(in srgb, var(--color-accent) 15%, transparent);
  text-decoration: underline;
}

/* A source and its status, kept together on one line when the list wraps. */
.qa-source {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  white-space: nowrap;
}

/* ── Q&A input ── */

.qa-input-wrap {
  border-top: 1px solid var(--color-border);
  flex-shrink: 0;
}

.qa-history-bar {
  display: flex;
  gap: var(--space-1);
  padding: 0.35rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
}

.qa-history-btn,
.qa-new-btn {
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  font-size: 0.68rem;
  color: var(--color-muted);
  cursor: pointer;
  padding: 0.2rem 0.5rem;
  font-family: inherit;
  transition: background var(--transition-normal), color var(--transition-normal);
}

.qa-history-btn:hover,
.qa-new-btn:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.qa-form {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  padding: 0.75rem;
}

.qa-input {
  flex: 1;
  resize: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
  font-family: inherit;
  line-height: 1.4;
  background: var(--color-bg);
  color: var(--color-text);
  transition: border-color var(--transition-normal);
  max-height: 120px;
  overflow-y: auto;
}

.qa-input:focus {
  outline: none;
  border-color: var(--color-accent);
}

.qa-input:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.qa-input::placeholder {
  color: var(--color-muted);
}

.qa-send-btn {
  background: var(--color-accent);
  color: var(--color-surface);
  border: none;
  border-radius: var(--radius-md);
  width: 34px;
  height: 34px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background var(--transition-normal), opacity var(--transition-normal);
}

.qa-send-btn:hover:not(:disabled) {
  background: var(--color-accent-hover);
}

.qa-send-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ── Q&A history ── */

.qa-history-list {
  display: flex;
  flex-direction: column;
}

.qa-history-item {
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--color-border);
  cursor: pointer;
  transition: background var(--transition-normal);
}

.qa-history-item:hover {
  background: color-mix(in srgb, var(--color-accent) 5%, transparent);
}

.qa-history-preview {
  font-size: 0.82rem;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.qa-history-meta {
  display: flex;
  gap: var(--space-3);
  margin-top: 0.2rem;
  font-size: 0.68rem;
  color: var(--color-muted);
}

.qa-history-empty {
  padding: 2rem;
  text-align: center;
  color: var(--color-muted);
  font-size: 0.82rem;
}

.qa-loading {
  padding: 2rem;
  text-align: center;
  color: var(--color-muted);
  font-size: 0.82rem;
}

.qa-error {
  padding: 1rem;
  color: var(--color-danger);
  font-size: 0.82rem;
}

/* ── Responsive ── */
@media (max-width: 768px) {
  .qa-sidebar {
    width: 100%;
  }
}

/* ══════════════════════════════════════════════════════════════════════════════
   Head Engineer sidebar — same pattern as Q&A sidebar
   ══════════════════════════════════════════════════════════════════════════════ */

.engineer-trigger {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--color-accent);
  color: var(--color-surface);
  border: none;
  border-radius: var(--radius-pill);
  padding: 0.6rem 1rem;
  font-size: 0.8rem;
  font-weight: var(--weight-semibold);
  font-family: inherit;
  cursor: pointer;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
  transition: background var(--transition-normal), transform var(--transition-normal);
}

.engineer-trigger:hover {
  background: var(--color-accent-hover);
  transform: translateY(-1px);
}

.engineer-sidebar {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 360px;
  background: var(--color-surface);
  border-left: 1px solid var(--color-border);
  box-shadow: -4px 0 16px rgba(0, 0, 0, 0.06);
  transform: translateX(100%);
  transition: transform var(--transition-slow) ease;
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
}

.engineer-sidebar.open {
  transform: translateX(0);
}

.engineer-sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}

.engineer-sidebar-title {
  font-size: 0.85rem;
  font-weight: var(--weight-semibold);
}

.engineer-sidebar-close {
  background: none;
  border: none;
  color: var(--color-muted);
  cursor: pointer;
  padding: 0.25rem;
  border-radius: var(--radius-xs);
  transition: background var(--transition-normal), color var(--transition-normal);
  display: flex;
  align-items: center;
}

.engineer-sidebar-close:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--color-text);
}

.engineer-sidebar-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* ── Engineer messages ── */

.engineer-messages {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.engineer-welcome {
  text-align: center;
  padding: 2rem 1rem;
  color: var(--color-muted);
  font-size: 0.82rem;
  line-height: 1.5;
}

.engineer-msg {
  max-width: 95%;
}

.engineer-msg-user {
  align-self: flex-end;
}

.engineer-msg-user .engineer-msg-content {
  background: var(--color-accent);
  color: var(--color-surface);
  border-radius: 12px 12px 2px 12px;
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

.engineer-msg-assistant {
  align-self: flex-start;
}

.engineer-msg-assistant .engineer-msg-content {
  background: var(--color-bg);
  border-radius: 12px 12px 12px 2px;
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
  line-height: 1.5;
  word-break: break-word;
}

.engineer-msg-assistant .engineer-msg-content p {
  margin: 0.3em 0;
}

.engineer-msg-assistant .engineer-msg-content p:first-child {
  margin-top: 0;
}

.engineer-msg-assistant .engineer-msg-content p:last-child {
  margin-bottom: 0;
}

.engineer-msg-assistant .engineer-msg-content code {
  background: rgba(0, 0, 0, 0.06);
  padding: 0.1rem 0.3rem;
  border-radius: var(--radius-xs);
  font-size: 0.78rem;
}

.engineer-msg-assistant .engineer-msg-content pre {
  background: rgba(0, 0, 0, 0.04);
  padding: 0.5rem;
  border-radius: var(--radius-xs);
  overflow-x: auto;
  font-size: var(--font-xs);
  margin: 0.4em 0;
}

.engineer-msg-assistant .engineer-msg-content pre code {
  background: none;
  padding: 0;
}

.engineer-msg-assistant .engineer-msg-content table {
  border-collapse: collapse;
  width: 100%;
  margin: 0.4em 0;
  font-size: 0.78rem;
}

.engineer-msg-assistant .engineer-msg-content th,
.engineer-msg-assistant .engineer-msg-content td {
  border: 1px solid var(--color-border);
  padding: 0.25rem 0.5rem;
  text-align: left;
}

.engineer-msg-assistant .engineer-msg-content th {
  background: rgba(0, 0, 0, 0.03);
  font-weight: var(--weight-semibold);
}

.engineer-msg-assistant .engineer-msg-content a {
  color: var(--color-accent);
  text-decoration: underline;
}

.engineer-streaming::after {
  content: "";
  display: inline-block;
  width: 6px;
  height: 14px;
  background: var(--color-accent);
  margin-left: 2px;
  animation: engineer-cursor-blink 0.7s infinite;
  vertical-align: text-bottom;
}

@keyframes engineer-cursor-blink {

  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0;
  }
}

.engineer-error-text {
  color: var(--color-danger);
}

/* ── Engineer input ── */

.engineer-input-wrap {
  border-top: 1px solid var(--color-border);
  flex-shrink: 0;
}

.engineer-history-bar {
  display: flex;
  gap: var(--space-1);
  padding: 0.35rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
}

.engineer-history-btn {
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  font-size: 0.68rem;
  color: var(--color-muted);
  cursor: pointer;
  padding: 0.2rem 0.5rem;
  font-family: inherit;
  transition: background var(--transition-normal), color var(--transition-normal);
}

.engineer-history-btn:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.engineer-form {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  padding: 0.75rem;
}

.engineer-input {
  flex: 1;
  resize: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
  font-family: inherit;
  line-height: 1.4;
  background: var(--color-bg);
  color: var(--color-text);
  transition: border-color var(--transition-normal);
  max-height: 120px;
  overflow-y: auto;
}

.engineer-input:focus {
  outline: none;
  border-color: var(--color-accent);
}

.engineer-input:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.engineer-input::placeholder {
  color: var(--color-muted);
}

.engineer-send-btn {
  background: var(--color-accent);
  color: var(--color-surface);
  border: none;
  border-radius: var(--radius-md);
  width: 34px;
  height: 34px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background var(--transition-normal), opacity var(--transition-normal);
}

.engineer-send-btn:hover:not(:disabled) {
  background: var(--color-accent-hover);
}

.engineer-send-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ── Responsive ── */
@media (max-width: 768px) {
  .engineer-sidebar {
    width: 100%;
  }

  .engineer-trigger {
    bottom: 1rem;
    right: 1rem;
  }
}

/* ── Docs tool: layout ── */

/* ── Docs nav items (admin sidebar style) ── */

.docs-nav-label {
  font-size: 0.72rem;
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  padding: 0.5rem 0.65rem 0.3rem;
  letter-spacing: 0.01em;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.docs-nav-label:first-child {
  padding-top: var(--space-1);
}

.docs-nav-role {
  font-weight: var(--weight-normal);
  opacity: 0.7;
}

.docs-nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0.55rem 0.65rem;
  border-radius: var(--radius-md);
  font-size: 0.85rem !important;
  color: var(--color-text);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--transition-normal), color var(--transition-normal);
}

.docs-nav-item:hover {
  background: var(--color-hover);
}

.docs-nav-item.active {
  background: rgba(0, 0, 0, 0.07);
  font-weight: var(--weight-medium);
}

.docs-nav-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--color-muted);
}

.docs-nav-icon svg {
  width: 100%;
  height: 100%;
}

.docs-nav-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.docs-nav-badge {
  font-size: 0.62rem;
  font-weight: var(--weight-semibold);
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-xs);
  white-space: nowrap;
  flex-shrink: 0;
}

.docs-nav-badge--empty {
  background: rgba(107, 113, 144, 0.1);
  color: var(--color-muted);
}

.docs-nav-badge--uploaded {
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  color: var(--color-accent);
}

.docs-nav-badge--approved {
  background: rgba(26, 140, 66, 0.1);
  color: var(--color-success);
}

.docs-nav-badge--flagged {
  background: rgba(214, 59, 59, 0.1);
  color: var(--color-danger);
}

.docs-nav-badge--private {
  background: rgba(107, 113, 144, 0.1);
  color: var(--color-muted);
  font-size: 0.58rem;
}

/* Expiry warning (#1259) — the document fails an expiry rule. Amber for a
   warning rule (DK-8, against today), danger when a blocking one fails (DK-3,
   against the wedding). The icon carries it; the tooltip is the sentence. */
.docs-nav-badge--expiry {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1);
  background: color-mix(in srgb, var(--color-warning) 18%, transparent);
  color: var(--color-warning-text);
}

.docs-nav-badge--expiry-blocking {
  background: color-mix(in srgb, var(--color-danger) 12%, transparent);
  color: var(--color-danger);
}

/* Docs-nav readiness pip — colour = whose turn: green approved · blue review ·
   amber needs-attention · hollow ring not-started. One 9px dot per the
   document-status scheme (the shortfall count lives in the tooltip, not a
   badge). An ``empty`` requirement (no NavStatus) renders the ``--none`` hollow
   ring — "not started" — and the id stays addressable for a later
   core/fragments patch. */
.docs-nav-status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  position: relative;
}

/* The 9px dot is a fiddly hover target for its floating tooltip — grow the
   hit area with an invisible halo without changing the visual size. */
.docs-nav-status::after {
  content: "";
  position: absolute;
  inset: -6px;
}

/* every state is a 9px pip: green approved · blue review · amber attention ·
   hollow ring not-started */
.docs-nav-status--done,
.docs-nav-status--review,
.docs-nav-status--flag,
.docs-nav-status--none {
  width: 9px;
  height: 9px;
  border-radius: var(--radius-pill);
}

/* Pips anchor on the colour-scale 500 steps (design: pip = 500, tooltip
   bg/border/text = 100/300/700 of the same family). Review has no blue scale
   yet — it stays on --color-info with color-mix tints. */

/* approved → green */
.docs-nav-status--done {
  background: var(--color-green-500);
}

/* uploaded / under review → blue (operator's turn) */
.docs-nav-status--review {
  background: var(--color-info);
}

/* needs attention → amber dot (couple's turn); the shortfall count is on
   the tooltip, not a badge */
.docs-nav-status--flag {
  background: var(--color-amber-500);
}

/* not started → hollow muted ring (no pages uploaded yet) */
.docs-nav-status--none {
  background: transparent;
  border: 1.5px solid var(--color-muted);
}

/* ── Status tooltip (design handoff: status-tooltip-1b) ──────────────────────
   Tone-tinted bubble anchored inside the row (the rail clips overflow, so it
   must not escape the row box — right-aligned it only grows inward, over the
   label). Pure CSS: revealed by hovering the pip (its ::after halo widens the
   9px target), with the designed show delay so a casual mouse-across never
   flashes it; hide is immediate (no delay on the base state).
   Design-locked values with no near token, kept raw: padding-x 11px,
   anchor offset right 26px, entrance offset 10px, easing cubic-bezier.
   Gap mapped 7px → --space-2 (6px, nearest token). */
.docs-nav-tip {
  position: absolute;
  right: 26px;
  top: 50%;
  transform: translateY(-50%) translateX(10px);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
  border-radius: var(--radius-md);
  padding: var(--space-2) 11px;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-normal);
  box-shadow: var(--shadow-md);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-slower) cubic-bezier(0.65, 0, 0.35, 1),
              transform var(--transition-slower) cubic-bezier(0.65, 0, 0.35, 1);
}

.docs-nav-status:hover + .docs-nav-tip,
.docs-nav-status:focus-visible + .docs-nav-tip {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
  transition-delay: var(--transition-slower);
}

.docs-nav-tip__count {
  opacity: 0.75;
}

.docs-nav-tip--flag {
  background: var(--color-amber-100);
  border: 1px solid var(--color-amber-300);
  color: var(--color-amber-700);
}

.docs-nav-tip--review {
  background: color-mix(in srgb, var(--color-info) 8%, var(--color-surface));
  border: 1px solid color-mix(in srgb, var(--color-info) 30%, transparent);
  color: var(--color-info);
}

.docs-nav-tip--done {
  background: var(--color-green-100);
  border: 1px solid var(--color-green-300);
  color: var(--color-green-700);
}

.docs-nav-tip--none {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-muted);
}

.docs-nav-empty {
  font-size: 0.8rem;
  color: var(--color-muted);
  padding: 0.35rem 0.65rem;
  opacity: 0.6;
}

.docs-nav-add {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.4rem 0.65rem;
  font-size: 0.78rem;
  color: var(--color-muted);
  background: none;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background var(--transition-normal), color var(--transition-normal);
  margin-bottom: var(--space-1);
}

.docs-nav-add:hover {
  background: var(--color-hover);
  color: var(--color-accent);
}

.docs-nav-item--unsorted {
  font-size: 0.8rem;
}

.docs-nav-item {
  flex-wrap: wrap;
}

.docs-nav-breakdown {
  flex-basis: 100%;
  padding-left: var(--space-8);
  font-size: 0.7rem;
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 1px;
}

.docs-nav-thumb {
  width: 28px;
  height: 28px;
  object-fit: cover;
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}

.docs-nav-assign {
  font-size: 0.7rem;
  padding: 0.15rem 0.25rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background: var(--color-surface);
  flex-shrink: 0;
}

/* ── Empty state ── */

.docs-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  min-height: 300px;
  color: var(--color-muted);
  text-align: center;
  padding: 2rem;
}

.docs-empty-icon {
  opacity: 0.3;
  margin-bottom: var(--space-5);
}

.docs-empty-title {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  margin: 0 0 0.25rem;
  color: var(--color-text);
}

.docs-empty-hint {
  font-size: 0.82rem;
  margin: 0;
}

/* The doc-switch crossfade rides htmx `transition:true` on the case-nav links
   (app.js swallows the benign skipped-transition AbortError on rapid switches).
   It runs as a ROOT view transition — we deliberately do NOT put a persistent
   `view-transition-name` on #case-main: in newer Chromium (Chrome 148+) a
   `view-transition-name` establishes a containing block for fixed-position
   descendants, which re-anchored the reader's full-bleed crop take-over
   (`.tool-ws-reader-stage.is-cropping { position: fixed }`) to #case-main
   instead of the viewport — the crop overlay then exposed the nav and the
   floating crop toolbar layered over it. Root transition keeps the crossfade
   without that side effect. */

/* ── Tool workspace ── */

.tool-ws {
  padding: 1rem 1.5rem;
  position: relative;
  /* Windowed pane: fill #case-main (the definite-height scroll container)
     and let the active view's columns scroll internally instead of
     scrolling the whole workspace. The height chain is definite from the
     fixed-height team shell down to here, so the internal scroll resolves
     in pure CSS — replacing the per-view ``min-height: calc(100vh …)``
     floors and the review view's JS height sizing. ``.tool-ws`` is the
     sole child of #case-main (tab_workspace.html swaps it in as
     innerHTML), so ``height: 100%`` fills without sibling overflow. */
  height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* ── Detail-panel layout (#505) ─────────────────────────────────────────────
   The unified workspace becomes a two-column row: the main body (grid / reader,
   internal scroll) + the focus-aware detail-panel rail. The body re-pins its
   own header / completeness / view-root, since wrapping it in `.tool-ws-body`
   moved them out of `.tool-ws`'s direct-child flow. Overrides the `.tool-ws`
   column above (later source order, equal specificity). */
.tool-ws-unified {
  flex-direction: row;
  align-items: stretch;
  /* Hairline-only (3 areas): no inter-area gap — the filmstrip / reading pane /
     detail panel are flush columns divided by one faint rule each. Content
     breathes from the rules via internal padding, not a flex gap. */
  gap: 0;
  /* No own padding (#517): the shared shell `.main-content`
     (`padding: var(--space-ws-gutter)`) is the single workspace gutter,
     so the grid/panel line up with the files/cases tabs. The legacy
     `.tool-ws` keeps its own `1rem 1.5rem`; only the unified path defers to
     the shell. The `.tool-ws-tabs-row` full-bleed negatives are re-derived
     for this (below) since they were calibrated to cancel both paddings. */
  padding: 0;
}

.tool-ws-unified > .tool-ws-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.tool-ws-body > .tool-ws-header-slots,
.tool-ws-body > .tool-ws-completeness-reasons,
.tool-ws-body > .tool-ws-empty {
  flex: 0 0 auto;
}

.tool-ws-body > .tool-ws-grid {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Breathing room above the first card row so it doesn't butt against the
     toolbar — matches the inter-card gap so the top margin reads as one rhythm.
     (The fuller-bleed cards from #524 removed the old in-card top letterbox that
     used to stand in for this space.) */
  padding-top: var(--space-5);
}

.tool-ws-body > .tool-ws-reader {
  flex: 1 1 auto;
  min-height: 0;
}

/* The detail-panel rail — the tool's second toggle (the detail sidebar).
   360px on Overview (and every other pane); the Activity tab widens to
   450px for a comfortable thread read — capped at half the viewport so the
   document column keeps its room on small screens. data-active-tab is kept
   in sync by panel.js (_syncActiveTab) on client-side tab swaps. */
.ws-detail-panel-host {
  flex: 0 0 360px;
  min-height: 0;
  display: flex;
}

.ws-detail-panel-host:has(.ws-detail-panel[data-active-tab="activity"]) {
  flex-basis: min(450px, 50vw);
}

/* Collapsed via the header detail-panel toggle — the body reflows full-width. */
.tool-ws--detail-collapsed .ws-detail-panel-host {
  display: none;
}

/* Page toolbar in the doc header (#505) — image tools beside the slider. A tool
   that doesn't apply to the current stop / selection is disabled, not hidden,
   so the strip doesn't reflow as the operator moves the slider. */
.ws-header-tools {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.ws-header-tool:disabled {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}

/* Empty-requirement disabled toolbar (#616) — the full control cluster renders
   greyed + inert at 0 pages so the workspace keeps its chrome; only "+ Add
   pages" stays live. Matches the .btn / .ws-header-tool disabled treatment
   (opacity 0.4, no pointer) for the controls that aren't <button disabled>. */
.tool-ws-slider--disabled {
  opacity: 0.4;
  pointer-events: none;
  cursor: not-allowed;
}

.seg-toggle-opt:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

.tool-ws-panel-toggle:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

.ws-detail-panel {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Hairline-only: a flush right column with one faint left rule, not a
     floating rounded card. The pane's own padding + the inner cards (which we
     keep) carry the structure inside. */
  border-left: 1px solid var(--color-border-subtle);
  background: var(--color-surface);
  overflow: hidden;
  width: 360px;
}

/* Persistent shell: the tab strip pins; only the pane below swaps. */
.ws-detail-panel-tabs {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 0 var(--space-ws-gutter);
  border-bottom: 1px solid var(--color-border);
  /* No filled band (#517) — the design-system tab-bar sits on plain surface
     with just the underline; the wrapper only carries the bottom rule. */
  background: transparent;
}

.ws-detail-panel-tabs .tab-bar {
  border-bottom: none;
  flex: 1;
  margin-bottom: 0;
}

/* Review-sidebar redesign (#505): the panel tabs read as a full-width, centred
   set — 12px / weight 400, compact, with an accent underline (not weight) as
   the active cue, matching the handoff. Scoped here so the shared `.tab`
   component is untouched elsewhere. */
.ws-detail-panel-tabs .tab {
  flex: 1;
  text-align: center;
  padding: var(--space-3) var(--space-1);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-tight);
}

.ws-detail-panel-tabs .tab--active {
  color: var(--color-text-strong);
  border-bottom-color: var(--color-accent);
  font-weight: var(--weight-normal);
}

/* No focus ring on mouse click (the active underline is the state cue);
   keyboard navigation keeps the accent ring, matching .section-tab. */
.ws-detail-panel-tabs .tab:focus {
  outline: none;
}

.ws-detail-panel-tabs .tab:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: var(--radius-xs);
}

.ws-detail-panel-levelup {
  flex: 0 0 auto;
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-sm);
  color: var(--color-muted);
  background: transparent;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: color var(--transition-normal), background var(--transition-normal);
}

.ws-detail-panel-levelup:hover {
  color: var(--color-text);
  background: var(--color-hover);
}

/* The one pane body — the surgical-swap region. Fades to partial opacity
   while loading rather than blanking (arch §"Loading, updating & motion"). */
.ws-detail-pane {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-ws-gutter);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  transition: opacity var(--transition-normal);
}

/* A card's `overflow: hidden` drops its flex min-height to zero, so in this
   column each card shrank to fit the pane and clipped its own content — the
   pane never overflowed and never scrolled (#1328). Cards keep their height;
   the pane scrolls. */
.ws-detail-pane > .card {
  flex-shrink: 0;
}

.ws-detail-pane--loading {
  opacity: 0.5;
}

/* Activity tab: keep the comment composer inside the panel (#517). The shared
   composer carries the class `composer`, which the legacy
   `.case-detail-page .composer { position: absolute }` rule (a different
   surface) would otherwise apply — and since no ancestor between it and
   `.tool-ws` (`position: relative`) is positioned, it would anchor to the
   whole workspace and stretch full-width across the bottom. Force normal flow
   here so it sits in the 320px pane, pinned to the bottom via its own
   `margin-top: auto`; the timeline fills the pane so that pin resolves. */
.tool-ws-unified .ws-detail-pane .page-timeline {
  flex: 1 1 auto;
  min-height: 0;
}

.tool-ws-unified .ws-detail-pane .page-composer {
  position: static;
}

/* Activity tab in rail mode (#505 follow-up): the panel renders the same
   rail thread as the case overview, but there the page scrolls — in the
   320px pane the feed itself must scroll so the composer stays pinned. */
.tool-ws-unified .ws-detail-pane .timeline--rail .timeline-entries {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Activity tab mirrors the overview timeline's layout. On /overview the
   scroll frame is full-bleed (`.overview-timeline-scroll` — no gutter, the
   scrollbar rides the edge), the gutter sits on the timeline content inside
   the scroll area (`.case-detail-page .timeline`), and the composer floats
   over the bottom (`.case-detail-page .composer`). Same setup here, scoped
   by data-pane so Overview (and every other pane) keeps the normal gutter.
   The pane is the positioned ancestor, so the absolute composer anchors to
   the 320px pane — not .tool-ws (the #517 problem). The feed's bottom
   padding is the scroll allowance (3·--space-8 + --space-2 = 102px) so the
   last entries clear the floating composer. */
.ws-detail-pane[data-pane="activity"] {
  padding: 0;
  position: relative;
}

.ws-detail-pane[data-pane="activity"] .timeline-entries {
  padding: var(--space-ws-gutter) var(--space-ws-gutter) calc(3 * var(--space-8) + var(--space-2));
}

/* The pinned section sits outside the scrolling feed (sibling above it), so
   it doesn't inherit the feed's gutter — give it its own. The feed's top
   padding provides the separation below, so bottom margin drops to 0. */
.ws-detail-pane[data-pane="activity"] .page-timeline-pinned {
  margin: var(--space-ws-gutter) var(--space-ws-gutter) 0;
}

.ws-detail-pane[data-pane="activity"] .page-composer {
  position: absolute;
  bottom: var(--space-ws-gutter);
  left: var(--space-ws-gutter);
  right: var(--space-ws-gutter);
  z-index: var(--z-sticky);
  box-shadow: var(--shadow-card);
}

.ws-detail-pane[data-pane="activity"] .ws-panel-empty-pane {
  padding: var(--space-ws-gutter);
}

/* Pane helper blocks. The page status row reuses `.page-modal-status-row` /
   `.page-modal-status-btn` (already styled); these cover the new bits. */
.ws-panel-faces,
.ws-panel-disposition,
.ws-panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.ws-panel-spread {
  margin-bottom: var(--space-2);
}

.ws-panel-face {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  min-width: 0;
}

.ws-panel-face-type {
  font-weight: 500;
  color: var(--color-text);
}

.ws-panel-face-label {
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.ws-panel-selection-num,
.ws-panel-req-num {
  font-size: var(--font-lg);
  font-weight: 600;
  color: var(--color-text);
}

.ws-panel-selection-unit,
.ws-panel-selection-meta,
.ws-panel-req-unit {
  color: var(--color-muted);
  margin-left: var(--space-1);
}

.ws-panel-hint,
.ws-panel-empty-pane {
  color: var(--color-muted);
  font-size: var(--font-sm);
}

/* Pinned chrome above the scrolling view body. */
.tool-ws > .tool-ws-header-slots,
.tool-ws > .tool-ws-completeness-reasons,
.tool-ws > .tool-ws-empty {
  flex: 0 0 auto;
}

/* The active view root fills the remaining height. Hidden roots are
   display:none (JS view switch), so only one is in flex flow at a time. */
.tool-ws > .tool-ws-grid,
.tool-ws > .tool-ws-section-tabs,
.tool-ws > .tool-ws-timeline,
.tool-ws > .tool-ws-review {
  flex: 1 1 auto;
  min-height: 0;
}

/* Passport grid (direct view root) scrolls its cards internally. */
.tool-ws > .tool-ws-grid {
  overflow-y: auto;
}

/* Section-tabbed body: tabs row pins, the active panel fills + scrolls.
   ``.tool-ws-section-tabs`` is already display:flex/column. */
.tool-ws-tabs-row {
  flex: 0 0 auto;
}
/* Timeline mode: the strip collapses to just its tabs row so the
   timeline body (a sibling view root) takes the remaining height.
   Child-combinator selector to match the specificity of the
   ``.tool-ws > .tool-ws-section-tabs`` view-root rule above — otherwise
   that rule's ``flex: 1 1 auto`` wins and the strip grows to absorb
   spare height, opening a gap above the timeline. */
.tool-ws > .tool-ws-section-tabs--timeline-mode {
  flex: 0 0 auto;
}
.tool-ws-section-tab-panel--active {
  flex: 1 1 auto;
  min-height: 0;
}
/* No-sidebar active panel scrolls its grid directly. */
.tool-ws-section-tab-panel--active:not(.tool-ws-section-tab-panel--with-sidebar) {
  overflow-y: auto;
}
/* The body column of a with-sidebar split scrolls; the sidebar's inner
   panes own their own overflow (Overview scrolls, Activity pins its
   composer). min-height:0 lets both shrink below content height. */
.tool-ws-section-tab-content,
.tool-ws-section-tab-panel-main,
.tool-ws-timeline-body {
  overflow-y: auto;
  min-height: 0;
}
.tool-ws-section-tab-panel-content--with-sidebar {
  height: 100%;
  min-height: 0;
}

.tool-ws--review-active {
  padding-bottom: 0 !important;
}

.tool-ws-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem 0.5rem;
  border-bottom: 1px solid var(--color-border);
  margin: -1rem -1.5rem var(--space-4);
  gap: var(--space-3);
  flex-wrap: wrap;
  position: sticky;
  top: 0;
  background: var(--color-surface);
  z-index: var(--z-sticky);
}

.tool-ws-header-info {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.tool-ws-header-titles {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.tool-ws-person {
  font-weight: var(--weight-semibold);
  font-size: 0.9rem;
}

.tool-ws-doctype {
  font-size: 0.82rem;
  color: var(--color-muted);
}

.tool-ws-header-actions {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.tool-ws-upload-btn {
  cursor: pointer;
}

.tool-ws-popover-wrap {
  position: relative;
}

.tool-ws-checks-has-data {
  color: var(--color-success);
  border-color: var(--color-success);
}

.tool-ws-popover {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 0.35rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  padding: 0.75rem;
  min-width: 320px;
  z-index: var(--z-dropdown);
}

.tool-ws-export-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.tool-ws-export-caret {
  flex: 0 0 auto;
  opacity: 0.85;
}

/* Sort menu (#710) — compact popover with two label-only items; reuses the
   export menu's item styling, just narrower than the 320px popover default. */
.tool-ws-sort-menu {
  display: none;
  min-width: 200px;
  padding: var(--space-3);
  box-shadow: var(--shadow-md);
}

/* Sort → By date pending (#1094) — the button flags how many pages were dated
   since the last sort, the way a passport's page numbers wait on By number.
   Same accent treatment as the Checks button's has-data state. */
.tool-ws-sort-pending {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.tool-ws-sort-pending-count {
  font-variant-numeric: tabular-nums;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  line-height: 1;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-accent-50);
  color: var(--color-accent);
}

.tool-ws-export-item {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) var(--space-3);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-normal), border-color var(--transition-normal);
}

.tool-ws-export-item:hover {
  background: var(--color-hover);
  border-color: var(--color-border);
}

.tool-ws-export-item:focus-visible {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.tool-ws-export-item:active {
  background: var(--color-active);
}

.tool-ws-export-item-icon {
  flex: 0 0 auto;
  color: var(--color-subtle);
}

.tool-ws-export-item-label {
  flex: 1 1 auto;
}

.tool-ws-export-item-meta {
  flex: 0 0 auto;
  padding: 2px var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--color-surface-muted);
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-subtle);
  letter-spacing: 0.04em;
}

/* Build-in-progress dots — shown in the gap before the ZIP/PDF badge while the
   server assembles the archive (toolExportSubmit toggles .is-exporting). */
.tool-ws-export-item-dots {
  display: none;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--space-1);
}

.tool-ws-export-item.is-exporting .tool-ws-export-item-dots {
  display: inline-flex;
}

.tool-ws-export-item.is-exporting .tool-ws-export-item-meta {
  opacity: 0.4;
}

.tool-ws-export-item-dots i {
  width: 4px;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-subtle);
  animation: tool-ws-export-dot 1s ease-in-out infinite;
}

.tool-ws-export-item-dots i:nth-child(2) {
  animation-delay: 0.15s;
}

.tool-ws-export-item-dots i:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes tool-ws-export-dot {
  0%, 80%, 100% {
    opacity: 0.25;
    transform: translateY(0);
  }
  40% {
    opacity: 1;
    transform: translateY(-2px);
  }
}

/* ── Export options dialog (#855) ──
   Config-driven replacement for the two-button export popover. The body stacks
   the doc type's declared option rows; each row's format-scoped visibility is
   toggled by wsExportDialogFormat. */
.tool-ws-export-dialog-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.tool-ws-export-option-row[hidden] {
  display: none;
}

.tool-ws-export-dialog-footer {
  justify-content: flex-end;
}

/* Pre-flight (#1018) — pages · size · parts against the caps the export will
   refuse on, one row per deliverable, swapped in above the option rows when the
   dialog opens. Red is the overage, named. */
.tool-ws-export-preflight-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.tool-ws-export-preflight-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-surface-dim);
  font-size: var(--font-sm);
  color: var(--color-text);
}

.tool-ws-export-preflight-row--over {
  background: var(--color-surface-muted);
  border-left: 3px solid var(--color-danger);
}

.tool-ws-export-preflight-label {
  font-weight: var(--weight-medium);
}

.tool-ws-export-preflight-facts {
  color: var(--color-muted);
}

.tool-ws-export-preflight-over {
  color: var(--color-danger);
  font-weight: var(--weight-medium);
}

.tool-ws-export-preflight-hint {
  flex-basis: 100%;
  font-size: var(--font-xs);
  color: var(--color-danger);
}

.tool-ws-export-preflight-hint .btn-link {
  font-size: inherit;
}

.tool-ws-export-preflight-empty {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

/* One option — its label above a segmented radio group. */
.export-option {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.export-option__label {
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--color-text);
}

/* Segmented control — the .seg-toggle visual language, but each segment is a
   <label> wrapping a visually-hidden native radio, so the form submits a value
   and the active segment is driven purely by :has(:checked) (no per-widget JS). */
.export-option__seg {
  display: inline-flex;
  align-self: flex-start;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  overflow: hidden;
}

.export-option__opt {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  cursor: pointer;
  transition: background var(--transition-normal), color var(--transition-normal);
}

.export-option__opt + .export-option__opt {
  border-left: 1px solid var(--color-border);
}

.export-option__opt:hover {
  color: var(--color-text);
}

.export-option__radio {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.export-option__opt:has(.export-option__radio:checked) {
  background: var(--color-accent);
  color: var(--color-surface);
}

.export-option__opt:has(.export-option__radio:focus-visible) {
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* Submit — reuses the "assembling" dots while the queue POST is in flight. */
.tool-ws-export-dialog-submit {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.tool-ws-export-dialog-submit.is-exporting .tool-ws-export-item-dots {
  display: inline-flex;
}

/* ── Completeness bar ── */

.tool-ws-completeness-inline {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.tool-ws-completeness-bar-inline {
  width: 60px;
  height: 4px;
  background: var(--color-border);
  border-radius: var(--radius-pill);
  overflow: hidden;
  display: block;
}

/* Legacy block layout (unused, kept for compatibility) */
.tool-ws-completeness {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.tool-ws-completeness-bar {
  flex: 1;
  height: 6px;
  background: var(--color-border);
  border-radius: var(--radius-xs);
  overflow: hidden;
}

.tool-ws-completeness-fill {
  height: 100%;
  background: var(--color-success);
  border-radius: var(--radius-xs);
  transition: width 0.3s ease;
}

.tool-ws-completeness-fill--gaps {
  background: var(--color-accent);
}

.tool-ws-completeness-label {
  font-size: var(--font-xs);
  color: var(--color-muted);
  white-space: nowrap;
}

/* ── Completeness rule reasons (when a doc-type completeness_rule fails) ── */

.tool-ws-completeness-reasons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  padding: var(--space-2) var(--space-3);
  background: color-mix(in srgb, var(--color-warning) 10%, var(--color-surface));
  border: 1px solid color-mix(in srgb, var(--color-warning) 35%, transparent);
  border-radius: var(--radius-sm, 4px);
  font-size: var(--font-xs);
  color: var(--color-warning-text);
}

.tool-ws-completeness-reason {
  display: inline-block;
}

/* ── Section tabs strip (relationship_doc and future tabbed doc types) ── */

.tool-ws-section-tabs {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* ── Tabs row — section-tabs strip + sidebar-tabs dock ───────────────
   The row hosts both underline strips in a 2-col grid so their
   baselines align. Negative top/side margins on the row pull the
   strip flush against the case header above — negating both the
   ``.tool-ws`` and ``.main-content`` top/side padding so the row's
   bottom rule (provided by the strip's own ``border-bottom``) reads
   as a continuation of the case-header rail. */
.tool-ws-tabs-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  column-gap: 0;
  align-items: flex-end;
  min-height: 42px;
  /* Negate ``.tool-ws`` + ``.main-content`` top padding so the row
     sits flush against the case header. Negative side margins +
     matching internal padding extend the bottom rule to the outer
     edges of the workspace content area while keeping each strip
     aligned with its own column. The row's right padding aligns the
     col-2 (sidebar-tabs dock) right edge with the sidebar's right
     edge below, so dock + sidebar share a column. */
  margin-top: calc(0px - var(--space-5) - var(--space-ws-gutter));
  margin-left: calc(0px - var(--space-6) - var(--space-ws-gutter));
  margin-right: calc(0px - var(--space-6) - var(--space-ws-gutter));
  padding-left: calc(var(--space-6) + var(--space-ws-gutter));
  padding-right: calc(var(--space-6) + var(--space-ws-gutter));
  margin-bottom: var(--space-5);
  border-bottom: 1px solid var(--color-border);
}
/* Unified path (#517): `.tool-ws-unified` drops its own `1rem 1.5rem`, so the
   full-bleed negatives only need to cancel the `.main-content` shell padding
   (`var(--space-ws-gutter)`), not the (now-gone) `.tool-ws` padding. Keeps the
   section-tabbed strip flush against the case header after the margin fix. */
.tool-ws-unified .tool-ws-tabs-row {
  margin-top: calc(0px - var(--space-ws-gutter));
  margin-left: calc(0px - var(--space-ws-gutter));
  margin-right: calc(0px - var(--space-ws-gutter));
  padding-left: var(--space-ws-gutter);
  padding-right: var(--space-ws-gutter);
}
/* Vertical separator between the section-tabs strip and the sidebar-
   tabs dock. The dock's border-left sits on its grid cell's left
   edge — same x as the sidebar's ``border-left`` below — so the two
   hairlines align vertically. A bridge ``::before`` carries the line
   across the row's ``margin-bottom`` gap so it connects to the
   sidebar's border-left without a break. */
.tool-ws-tabs-row .sidebar-tabs-dock {
  position: relative;
  border-left: 1px solid var(--color-border);
  padding-left: var(--space-5);
}
.tool-ws-tabs-row .sidebar-tabs-dock::before {
  content: "";
  position: absolute;
  left: -1px;
  top: 100%;
  width: 1px;
  height: calc(var(--space-5) + 1px);
  background: var(--color-border);
}
/* When no sidebar dock is rendered (doc types without a timeline
   sidebar), the strip takes the full row width. */
.tool-ws-tabs-row:not(:has(.sidebar-tabs-dock)) {
  grid-template-columns: 1fr;
}

.section-tabs {
  display: flex;
  align-items: stretch;
  gap: 0;
  margin-bottom: -1px;
  /* ``overflow-x: clip`` clips horizontal overflow without forcing
     ``overflow-y: auto`` (which the spec does for ``overflow-x: auto``).
     Without this the active tab's 2px accent border-bottom gets clipped
     to 1px because ``margin-bottom: -1px`` pulls the border 1px below
     the strip's content area, into the implicit overflow zone. */
  overflow-x: clip;
  overflow-y: visible;
}

/* Sidebar tabs dock — sits in col 2 of the tabs row, aligned with the
   sidebar column below. Mirrors the section-tabs strip's idiom so the
   two underline strips read as one continuous header rail. */
.sidebar-tabs-dock {
  display: flex;
  align-items: stretch;
}
.sidebar-tabs-dock .sidebar-tabs {
  flex: 1 1 auto;
  border-bottom: 0;
  margin-bottom: -1px;
}
.section-tabs::-webkit-scrollbar { display: none; }

.section-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /* 9 / 14 / 11 px — between token steps, kept as literals per the
     design spec (these are the strip's measured baseline-aligned
     dimensions, not semantic spacing). */
  padding: 9px 14px 11px;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  color: var(--color-muted);
  font-size: var(--font-sm);
  font-family: inherit;
  font-weight: var(--weight-medium);
  cursor: pointer;
  white-space: nowrap;
  transition:
    color var(--transition-normal),
    border-color var(--transition-normal),
    background var(--transition-normal);
  position: relative;
}

.section-tab:hover {
  color: var(--color-text);
}

.section-tab:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

.section-tab--active {
  color: var(--color-text-strong);
  border-bottom-color: var(--color-accent);
  font-weight: var(--weight-semibold);
}

/* Drag-over state: a section tab is a file drop target (section-tabs.js) —
   drop images to upload them straight into that section. */
.tool-ws-section-tab--drop {
  color: var(--color-text-strong);
  background: var(--color-hover);
  border-bottom-color: var(--color-accent);
  outline: 2px dashed var(--color-accent);
  outline-offset: -2px;
  border-radius: var(--radius-xs);
}

.section-tab-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-muted);
  flex-shrink: 0;
  transition: color var(--transition-normal);
}
.section-tab-icon svg { width: 15px; height: 15px; display: block; }
.section-tab:hover .section-tab-icon { color: var(--color-text); }
.section-tab--active .section-tab-icon { color: var(--color-accent); }

/* Count / meta — plain inline number, no pill chrome. Every tab carries
   a number-shaped meta (collection count, ``1/1`` synopsis fraction);
   colour darkens slightly on the active tab so it sits with the
   strengthened label ink. */
.section-tab-count {
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  background: transparent;
  padding: 0;
  line-height: 1;
}
.section-tab--active .section-tab-count {
  color: var(--color-subtle);
}

/* The count + attention dot region (ws_section_count.html). A wrapper only so
   fragments can re-send both; the tab button lays out its children. */
.section-tab-badges {
  display: contents;
}

/* "All" pseudo-tab — leading position with a soft separator. Carries
   a Lucide-stroke "layers" glyph rendered as an inline SVG (same as
   the other section icons) — signals "cross-section view" without
   a bespoke chrome. */
.section-tab--all {
  color: var(--color-subtle);
}

.section-tab-sep {
  width: 1px;
  margin: var(--space-3) var(--space-3) calc(var(--space-3) + 1px);
  background: var(--color-border);
  align-self: stretch;
}

/* Drag-onto-tab drop indicator — issue #482 §6. Pulses the tab while a
   card hovers above it so the operator sees the gesture's about to
   land. Same selector name (legacy ``--drop-target``) so the JS hook
   stays untouched. */
.section-tab--drop-target,
.tool-ws-section-tab--drop-target {
  background: color-mix(in srgb, var(--color-accent) 14%, var(--color-surface));
  outline: 2px dashed var(--color-accent);
  outline-offset: -2px;
}

/* Sidebar Overview / Activity sub-tabs — underline strip, mirrors the
   relationship-doc section-tabs idiom but scoped inside the sidebar
   itself. Each button takes equal width via flex: 1; active state uses
   the accent border-bottom (same trick + ``margin-bottom: -1px`` as the
   section-tabs strip so the underline lands on the container rule). */
.sidebar-tabs {
  display: flex;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}
.sidebar-tab {
  flex: 1 1 0;
  /* 9 / 11 px keep as literals (between token steps, baseline-aligned
     with the section strip); side padding uses ``--space-1`` (4px). */
  padding: 9px var(--space-1) 11px;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-family: inherit;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  cursor: pointer;
  text-align: center;
  transition:
    color var(--transition-normal),
    border-color var(--transition-normal);
}
.sidebar-tab:hover { color: var(--color-text); }
.sidebar-tab:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: var(--radius-xs);
}
.sidebar-tab--active {
  color: var(--color-text-strong);
  border-bottom-color: var(--color-accent);
  font-weight: var(--weight-semibold);
}

/* ── Timeline view — chronological card list (issue #482 §1) ────────── */

.tool-ws-timeline {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-3) 0;
}

.tool-ws-timeline--hidden {
  display: none;
}

.tool-ws-timeline-empty {
  padding: var(--space-6) var(--space-4);
  text-align: center;
  color: var(--color-muted);
  font-size: var(--font-sm);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-sm);
}

/* Sort toggle — segmented Oldest/Newest control. The grouped grid reuses
   `.tool-ws-timeline-sort` for its date-order toggle. */
.tool-ws-timeline-sort {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.tool-ws-timeline-sort-btn {
  border: 0;
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  padding: var(--space-1) var(--space-3);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.tool-ws-timeline-sort-btn + .tool-ws-timeline-sort-btn {
  border-left: 1px solid var(--color-border);
}

.tool-ws-timeline-sort-btn:hover {
  color: var(--color-text);
}

.tool-ws-timeline-sort-btn--active {
  background: var(--color-accent);
  color: var(--color-surface);
}

/* ── Grouped grid view — thumbnails bucketed by month ──────────────────
   Section-tabbed doc types render page_grid_grouped.html: one grid with
   month-group headers + the sort bar as full-width grid items, thumbnails
   flowing under each header. Reuses .tool-ws-timeline-sort for the toggle. */
.tool-ws-grid--grouped {
  align-content: start;
}

.tool-ws-grid-sort {
  grid-column: 1 / -1;
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--space-1);
}

.tool-ws-grid-group-heading {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: var(--space-3) 0 0 var(--space-1);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.tool-ws-grid--grouped > .tool-ws-grid-sort + .tool-ws-grid-group-heading {
  margin-top: 0;
}

.tool-ws-grid-group-heading--undated {
  color: var(--color-warning-text);
}

.tool-ws-grid-group-count {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.tool-ws-grid-group-heading--undated .tool-ws-grid-group-count {
  color: var(--color-warning-text);
  font-style: italic;
}

/* Month / Undated groups */
.tool-ws-timeline-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.tool-ws-timeline-group-heading {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0 0 0 var(--space-1);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.tool-ws-timeline-group--undated .tool-ws-timeline-group-heading {
  color: var(--color-warning-text);
}

.tool-ws-timeline-group-count {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.tool-ws-timeline-group-meta {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  color: var(--color-warning-text);
  font-style: italic;
  font-variant-numeric: tabular-nums;
}

/* The card — thumb · date spine · label · chips */
.tool-ws-timeline-row {
  display: grid;
  grid-template-columns: 56px 92px 1fr auto;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.tool-ws-timeline-row:hover {
  background: var(--color-surface-muted);
  border-color: var(--color-border);
}

.tool-ws-timeline-row:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -1px;
  border-color: var(--color-accent);
}

.tool-ws-timeline-row.ws-page--excluded {
  opacity: 0.55;
}

.tool-ws-timeline-row--undated {
  background: color-mix(in srgb, var(--color-warning) 8%, var(--color-surface));
}

/* Zone A — thumbnail */
.tool-ws-timeline-row-thumb {
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface-muted);
  border-radius: var(--radius-sm);
  overflow: hidden;
  flex-shrink: 0;
}

.tool-ws-timeline-row-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.tool-ws-timeline-row-thumb-missing {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* Zone B — date spine */
.tool-ws-timeline-row-date {
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-align: right;
  padding-right: var(--space-2);
  border-right: 1px solid var(--color-border-subtle);
  align-self: stretch;
  justify-content: center;
}

.tool-ws-timeline-row-date--undated {
  border-right-color: color-mix(in srgb, var(--color-warning) 35%, transparent);
}

.tool-ws-timeline-row-date-day {
  font-size: 1.15rem;
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-strong);
  line-height: 1.05;
}

.tool-ws-timeline-row-date-month {
  font-size: var(--font-xxs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
}

.tool-ws-timeline-row-date-missing {
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-warning-text);
}

/* Month-precision (day unknown) — no big day numeral, just the month. */
.tool-ws-timeline-row-date-monthonly {
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-strong);
  line-height: 1.2;
}

/* Page-details modal: the two date grains (#1096). Both inputs are always in
   the DOM — only one is ever un-`hidden` — so the flex row holds exactly one
   control at a time and the other takes no space. */
.rdoc-date-inputs {
  display: flex;
  gap: var(--space-2);
}

.rdoc-date-inputs .rdoc-date-exact,
.rdoc-date-inputs .rdoc-date-month {
  flex: 1 1 auto;
  min-width: 0;
}

.rdoc-date-unknown {
  margin-top: var(--space-1);
}

/* Zone C — label (section badge + qualifier, then uploader + status) */
.tool-ws-timeline-row-label {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: var(--space-1);
}

.tool-ws-timeline-row-label-main {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.tool-ws-timeline-row-qualifier {
  font-size: var(--font-sm);
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tool-ws-timeline-row-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-muted);
  min-width: 0;
}

.tool-ws-timeline-row-by {
  color: var(--color-subtle);
  font-weight: var(--weight-medium);
}

.tool-ws-timeline-row-dot {
  width: 3px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--color-border);
  flex-shrink: 0;
}

/* Section badge — per-section token tint, 10% fill + full-colour text */
.tool-ws-timeline-row-badge {
  display: inline-flex;
  align-items: center;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
  padding: 2px var(--space-2);
  border-radius: var(--radius-xs);
  white-space: nowrap;
}

.tool-ws-timeline-row-badge--photos {
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
}

.tool-ws-timeline-row-badge--comms {
  color: var(--color-info);
  background: color-mix(in srgb, var(--color-info) 10%, transparent);
}

.tool-ws-timeline-row-badge--tickets {
  color: var(--color-success);
  background: color-mix(in srgb, var(--color-success) 10%, transparent);
}

.tool-ws-timeline-row-badge--unassigned {
  color: var(--color-muted);
  background: transparent;
  border: 1px dashed var(--color-border);
}

/* Zone D — status chips */
.tool-ws-timeline-row-chips {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* ── Page-card date display (issue #482 §2) ────────────────────────── */

.page-card-date {
  font-size: var(--font-xs);
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.page-card-date-label {
  color: var(--color-subtle);
}

.tool-ws-section-tab-panel {
  display: none;
}

.tool-ws-section-tab-panel--active {
  display: block;
}

/* Section panel with sidebar (issue #486) — flex split: grid on the left,
   read-only details sidebar on the right. The --active modifier above
   uses display: block; override here when the sidebar is opted in so
   the children flow side-by-side.

   ``min-height`` gives the sidebar room to pin the Activity-tab
   composer at the bottom even when the section has few pages. Without
   it, ``align-items: stretch`` would collapse the sidebar to the
   page-grid's natural height, leaving the composer floating just
   under the last event. Targets the full visible content area below
   the case header + tabs strip so an empty section (Tickets with no
   pages, etc.) still hosts a full-height sidebar. */
.tool-ws-section-tab-panel--with-sidebar.tool-ws-section-tab-panel--active {
  display: flex;
  align-items: stretch;
  min-height: 0;
}

/* Timeline-mode coexistence: tabs strip stays visible above the timeline
   so the operator can scope it section-by-section, but the per-tab page
   grids underneath are hidden — the timeline below renders the pages.
   Synopsis-style addon panels (``--panel``) are the exception: they
   take over the body in either view, and the JS hides the timeline
   while they're active. */
.tool-ws-section-tabs--timeline-mode .tool-ws-section-tab-panel:not(.tool-ws-section-tab-panel--panel) {
  display: none;
}

/* Panel-tab content layout: addon panel on the left, aggregate
   timeline sidebar on the right (when the doc type opts in). Same
   layout in every view — the operator should see the same context
   regardless of whether they came in via grid, review, or timeline. */
.tool-ws-section-tab-panel-content {
  display: contents;
}
.tool-ws-section-tab-panel-content--with-sidebar {
  display: flex;
  align-items: stretch;
  gap: 0;
  min-height: 0;
}
.tool-ws-section-tab-panel-main {
  flex: 1 1 auto;
  min-width: 0;
}

/* "All" pseudo-tab — same content in every view: union of every
   page-tab's pages, sorted chronologically with undated last. The
   panel grid renders in grid/review; timeline renders its body
   (filtered to ``noFilter``) and the timeline-mode rule above hides
   this panel grid so the two surfaces don't double up. */
.tool-ws-section-tabs--timeline-mode .tool-ws-section-tab-panel[data-tab-panel="_all"] {
  display: none !important;
}

.tool-ws-section-tab-content {
  flex: 1 1 auto;
  min-width: 0;
}

.tool-ws-section-tab-empty {
  padding: var(--space-6) var(--space-4);
  text-align: center;
  color: var(--color-muted);
  font-size: var(--font-xs);
}

/* ── Section-sidebar (issue #486) — per-tab read-only overview ──
   Shell mirrors .review-panel / .page-modal-side (border-left column,
   no card chrome). Inner content uses the existing .page-modal-tabs
   strip + .page-modal-tab-content + card() macro so the surface reads
   as a peer to the modal/filmstrip right-rail. Only this section's
   own bespoke pieces (the count line, the page-link buttons, the
   checklist list) get dedicated classes; everything else delegates to
   the shared card / page-modal-info-list styling. */

.section-sidebar {
  width: 320px;
  min-width: 320px;
  box-sizing: border-box;
  border-left: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  /* Container itself does not scroll — internal panes own their own
     overflow so the composer can pin at the bottom of the Activity
     tab while the timeline above it scrolls independently. */
  min-height: 0;
}

/* The :not([hidden]) clause is load-bearing — without it this rule wins
   over the browser default ``[hidden] { display: none }`` (low specificity),
   leaving the Activity pane visually rendered despite the ``hidden``
   attribute on markup. IntersectionObserver then fires the lazy-load
   ``intersect once`` trigger on mount, which combined with the parent
   ``hx-select=".tool-ws"`` inheritance wipes the workspace. The
   ``hx-disinherit`` on the pane is defence-in-depth; this selector
   guard is the root fix. */
.section-sidebar .page-modal-tab-content:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  /* The visible pane fills the sidebar vertically so internal flex
     children (timeline + composer on the Activity pane) can grow /
     pin correctly. */
  flex: 1 1 auto;
  min-height: 0;
}

/* Overview pane: scroll its card stack when it overflows. */
.section-sidebar .page-modal-tab-content[data-tab-pane="overview"]:not([hidden]) {
  overflow-y: auto;
}

/* Activity pane: flex column wrapping the timeline + composer. The
   wrapping ``#sidebar-activity-pane`` element is the swap target
   from the lazy-load (see ``hx-select`` on the pane). Stretch it to
   fill the pane so the timeline scrolls and the composer pins. */
.section-sidebar .page-modal-tab-content[data-tab-pane="activity"]:not([hidden]) {
  padding: 0;
  gap: 0;
  overflow: hidden;
}

.section-sidebar #sidebar-activity-pane {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* The sidebar's Activity pane now renders the same slim event / comment /
   composer chrome as the page modal's Activity tab. ``.page-timeline-feed``
   scrolls; ``.page-composer`` pins at the bottom; the empty-state card
   centres in the remaining gap. Padding mirrors the rest of the
   sidebar's gutter so the feed lines up with the Overview-pane cards. */
.section-sidebar .sidebar-activity-pane {
  flex: 1 1 auto;
  min-height: 0;
  padding: 0;
}

.section-sidebar .sidebar-activity-pane .page-timeline-feed {
  padding: var(--space-3) var(--space-4) 0;
}

.section-sidebar .sidebar-activity-pane .page-composer {
  padding: var(--space-3) var(--space-4);
}

.section-sidebar-summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.section-sidebar-summary-count {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.section-sidebar-summary-num {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  line-height: 1;
}

.section-sidebar-summary-unit {
  color: var(--color-subtle);
}

.section-sidebar-summary-meta {
  color: var(--color-muted);
  font-size: var(--font-xs);
}

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

.section-sidebar-pagelink {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  text-align: left;
  font: inherit;
  color: var(--color-text);
  cursor: pointer;
  transition: background 0.1s ease-out;
}

.section-sidebar-pagelink:hover {
  background: var(--color-hover);
}

.section-sidebar-pagelink:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.section-sidebar-pagelink-pos {
  flex: 0 0 auto;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.section-sidebar-pagelink-label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.section-sidebar-pagelist-more {
  padding: var(--space-1) var(--space-3);
  color: var(--color-muted);
  font-size: var(--font-xs);
}

.section-sidebar-checks {
  list-style: disc inside;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  color: var(--color-subtle);
  line-height: 1.4;
}

.section-sidebar-checks li {
  padding-left: var(--space-1);
}

.section-sidebar-breakdown dt {
  font-weight: var(--weight-normal);
  color: var(--color-text);
  text-transform: none;
  letter-spacing: 0;
}

.section-sidebar-breakdown dd {
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-semibold);
  color: var(--color-subtle);
}

.section-sidebar-flat-meta {
  margin: 0;
  color: var(--color-warning-text);
}

/* Digest flags (#1017) — one computed fact per row + the existing action it
   points at (add a missing slot / open the page). Same list rhythm as the
   undated / redo page lists above. */
.section-sidebar-flags {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.section-sidebar-flag {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  line-height: 1.4;
}

.section-sidebar-flag-text {
  color: var(--color-text);
}

.section-sidebar-flag-action {
  align-self: flex-start;
}

/* The list folds behind the panel's Show all (.ws-rev-fold, #1328): the
   card is already the box, so the fold's own border and fill come off, and
   only the first three flags show while it is closed. */
.section-sidebar-flags-fold.ws-rev-fold {
  border: 0;
  background: none;
}

.section-sidebar-flags-fold:not(.ws-rev-fold--open) .section-sidebar-flag--extra {
  display: none;
}

.section-sidebar-flags-fold .ws-rev-fold__toggle {
  margin-top: var(--space-3);
}

/* "Use photo date" — the EXIF capture date offered as a one-click fill, on
   the page-meta form and beside each undated page in the section overview. */
.ws-exif-suggest {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-1);
  font-size: var(--font-xs);
}

.section-sidebar-pagelink-exif {
  flex: 0 0 auto;
}

/* Section sidebar Activity tab — reuses .timeline-activity rows from
   page_timeline (same as modal + filmstrip Activity tabs). Only addition
   is the clickable affordance, since these rows reference other pages
   the operator can navigate to. */
.section-sidebar-activity-loading {
  padding: var(--space-4);
  text-align: center;
  color: var(--color-muted);
  font-size: var(--font-xs);
}

.timeline-activity--clickable {
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background 0.1s ease-out;
}

.timeline-activity--clickable:hover {
  background: var(--color-hover);
}

/* Timeline view with aggregate sidebar — same right-column layout as the
   section tabs view. The base ``.tool-ws-timeline`` rule sets
   ``flex-direction: column`` for the stacked Undated / Chronological
   groups; the sidebar opt-in changes direction to row so the timeline
   body and sidebar sit side-by-side. The body keeps the inner column
   stack via its own rule below. */
.tool-ws-timeline--with-sidebar {
  flex-direction: row;
  align-items: stretch;
  gap: 0;
  padding: 0;
  /* Same floor as ``--with-sidebar`` panels — keeps the activity
     composer pinned at the bottom of the visible area even on
     sections with no rows (Tickets-empty, etc.). */
  min-height: 0;
}

.tool-ws-timeline--with-sidebar.tool-ws-timeline--hidden {
  display: none;
}

.tool-ws-timeline-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-3) var(--space-5) var(--space-3) 0;
}

/* Timeline sidebar slots — one per source (aggregate + each section_tab
   that opts into a sidebar). Only the slot matching the active section
   tab is ``--active`` and visible; the rest stay in DOM but collapsed.
   ``display: contents`` removes the slot's box so the inner ``aside``
   (``.section-sidebar``, fixed 320px) behaves as a direct flex child
   of ``.tool-ws-timeline`` — same layout as the single-aside version
   pre-multi-slot. */
/* Narrow viewports — drop the sidebar below the grid/timeline instead of
   beside, so the page thumbnails still fit. Sidebar still renders; it
   just stacks under the content with the divider on top. */
@media (max-width: 1100px) {
  .tool-ws-section-tab-panel--with-sidebar.tool-ws-section-tab-panel--active,
  .tool-ws-timeline--with-sidebar {
    flex-direction: column;
  }
  .section-sidebar {
    width: 100%;
    min-width: 0;
    border-left: 0;
    border-top: 1px solid var(--color-border);
  }
}

/* ── Right-panel collapse toggle (TRIAL — relationship-doc only) ──
   Single toolbar affordance (.tool-ws-panel-toggle) hides the
   Overview/Activity aggregate sidebar; the body reflows full-width via
   the existing flex split. In-memory state only (no persistence) while
   we evaluate the gesture — paired with toolToggleSidePanel() in
   tool_workspace.html. Self-contained: remove this block + the toolbar
   button + the JS to revert. */
.tool-ws-toolbar-divider {
  width: 1px;
  height: 20px;
  background: var(--color-border);
  flex: 0 0 auto;
  margin: 0 var(--space-1);
}
.tool-ws-panel-toggle {
  width: var(--control-h);
  height: var(--control-h);
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  background: none;
  cursor: pointer;
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  transition: background var(--transition-fast),
              color var(--transition-fast),
              border-color var(--transition-fast);
  position: relative;
}
.tool-ws-panel-toggle:hover {
  background: var(--color-hover);
  color: var(--color-text);
}
.tool-ws-panel-toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}
.tool-ws-panel-toggle.is-active {
  color: var(--color-accent);
  border-color: var(--color-border);
  background: var(--color-surface);
}
.tool-ws-panel-toggle svg {
  width: 16px;
  height: 16px;
}
.tool-ws-panel-toggle::after {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  background: var(--color-text);
  color: var(--color-surface);
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--transition-normal) ease;
  /* Above dropdown-level chrome: the list-view "+N hidden" pill is absolute at
     --z-dropdown and later in the DOM, so at the same level it painted over
     this tooltip (#1081 follow-up). A hover tooltip is transient and sits on
     top of everything but a modal, which covers the button anyway. */
  z-index: var(--z-modal);
}
.tool-ws-panel-toggle:hover::after {
  opacity: 1;
}

/* The collapse itself — every aggregate/section sidebar in the
   workspace slides out; the body reflows to the reclaimed width because
   its flex sibling is ``flex: 1 1 auto``. Width + transform animate over
   --transition-slow; the fade over --transition-normal. */
.section-sidebar,
.review-panel {
  transition: width var(--transition-slow) ease,
              min-width var(--transition-slow) ease,
              opacity var(--transition-normal) ease,
              transform var(--transition-slow) ease;
}
/* Collapse whichever right rail the active view renders: the section-
   tabbed Overview/Activity sidebar or the review view's detail panel.
   Each is a fixed-width flex child whose ``flex: 1 1 auto`` sibling (the
   timeline body / page grid / review image column) reclaims the width as
   it slides out. */
.tool-ws--side-collapsed .section-sidebar,
.tool-ws--side-collapsed .review-panel {
  width: 0;
  min-width: 0;
  border-left: 0;
  opacity: 0;
  transform: translateX(16px);
  overflow: hidden;
  pointer-events: none;
}
/* The Overview/Activity dock lives in the tabs-row header (shared
   baseline), not inside .section-sidebar — hide it too and let the
   section-tabs strip span the full row, mirroring the no-dock layout. */
.tool-ws--side-collapsed .sidebar-tabs-dock {
  display: none;
}
.tool-ws--side-collapsed .tool-ws-tabs-row {
  grid-template-columns: minmax(0, 1fr);
}
@media (max-width: 1100px) {
  .tool-ws--side-collapsed .section-sidebar,
  .tool-ws--side-collapsed .review-panel {
    display: none;
  }
}

/* ── Workspace header slots (relationship_doc synopsis, future) ── */

.tool-ws-header-slots {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

/* ── attachment_page addon (#1340) ── */

/* Outer container for the attachment page's tab — stacks the facts cards
   other addons set beside the page (relationship_doc's first meeting) above
   the page card, with platform-standard gap. The cards carry their own
   surface; this wrapper just owns the spacing. */
.attachment-page-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

/* Page file-import form (#914) — carries only a hidden file input and
   the CSRF token, so it must not occupy a row in the card. `contents` drops
   the box entirely rather than leaving a zero-height element that still
   collects the parent's gap. */
.attachment-page-import {
  display: contents;
}

/* The import drop target under the editor. Reuses the shared .dropzone
   component and only compacts it: this is a secondary affordance sitting
   below a filled editor, not the empty-state hero .dropzone is sized for, so
   it lays the icon and label out in a row instead of a stack. */
.attachment-page-dropzone {
  flex-direction: row;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-4);
  margin-top: var(--space-4);
  font-size: var(--font-sm);
  color: var(--color-muted);
}

/* Where the page's text came from (#928) — the files it was imported from,
   listed under the editor and above the drop target. Sized as secondary
   metadata: it answers "where did this come from" for an operator who is
   already reading the page, so it must not compete with it. */
.attachment-page-sources {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-3);
  padding: 0;
  list-style: none;
}

.attachment-page-sources__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-sm);
  color: var(--color-muted);
}

/* Fixed box so a long filename wrapping never drags the glyph off the
   first line. */
.attachment-page-sources__icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-muted);
}

.attachment-page-sources__link {
  color: var(--color-text);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.attachment-page-sources__link:hover {
  text-decoration: underline;
}

/* A refused attach (#1433) says why where the file would have appeared. */
.attachment-page-sources-region > .alert {
  margin-top: var(--space-3);
}

/* Remove sits right after the file's name, as quiet as the row (#1433);
   .btn-icon--sm turns it the danger colour on hover. */
.attachment-page-sources__remove {
  display: inline-flex;
  margin: 0;
}

/* ── The attachment page as a card among the pages (#1345) ──
   Design round: rules/design-system/handoffs/attachment-page/RETURN/. A card
   that looks like a page and is not one, its small render in the reader rail,
   the editor sheet at a scan page's size in the reader, and its panel section. */

/* The page's words at a small size — the grid card reads them (DECISIONS.md
   1a); the rail thumb lays them out the same way and scales them down. */
.attachment-page-mini {
  box-sizing: border-box;
  padding: var(--space-4) var(--space-4) 0;
  text-align: left;
  font-size: var(--font-xxs);
  line-height: var(--line-height-normal);
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.attachment-page-mini > * {
  margin: 0 0 var(--space-2);
}

.attachment-page-mini :is(h1, h2, h3, h4, h5, h6) {
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
}

.attachment-page-mini strong {
  font-weight: var(--weight-semibold);
}

.attachment-page-mini :is(ul, ol) {
  padding-left: var(--space-3);
}

.attachment-page-mini img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-xs);
}

/* A preview, not a document: a link in it must not navigate the workspace
   away from under a click meant for the card. */
.attachment-page-mini a {
  color: inherit;
  pointer-events: none;
}

.page-card .page-card-written-thumb {
  background: var(--color-surface);
}

.page-card-written-text {
  position: absolute;
  inset: 0;
  overflow: hidden;
  mask-image: linear-gradient(to bottom, var(--color-text) 78%, transparent);
}

.page-card .page-card-written-thumb--empty {
  background: var(--color-surface-dim);
  cursor: default;
}

/* The empty card: one frame — the card's own — with the copy and the two
   actions stacked inside it, so a narrow grid stop never pushes a button past
   the card's edge. */
.page-card-written-empty {
  position: absolute;
  inset: 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3);
  text-align: center;
  font-size: var(--font-xxs);
  color: var(--color-subtle);
}

.page-card-written-empty__icon {
  align-self: center;
}

.page-card-written-empty__icon,
.page-card-written-title__icon {
  display: inline-flex;
  color: var(--color-accent);
}

.page-card-written-empty__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-1);
}

.page-card-written-empty__actions .btn {
  width: 100%;
  justify-content: center;
}

/* Block, not flex: `.page-card-pos`'s ellipsis acts on a line of text, and a
   flex container's text clips without one. */
.page-card-written-title {
  display: block;
  min-width: 0;
}

.page-card-written-title__icon {
  margin-right: var(--space-1);
  vertical-align: -0.125em;
}

.page-card--written-empty .page-card-written-title,
.page-card--written-empty .page-card-written-title__icon {
  color: var(--color-muted);
}

/* No scans yet: the empty card beside today's dropzone (DECISIONS.md 2a). */
.tool-ws-body > .tool-ws-empty--placeholder.tool-ws-empty--written {
  flex-direction: row;
  gap: var(--space-ws-gutter);
}

.tool-ws-empty__written {
  display: block;
  flex: 0 0 auto;
  width: 12rem;
}

/* Rail: the small render — the card's layout at four times the thumb's width,
   scaled to fit. Too narrow to read; it says "a written page, this long". */
.tool-ws-reader-thumb--written {
  background: var(--color-surface);
}

.tool-ws-reader-thumb-written {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  mask-image: linear-gradient(to bottom, var(--color-text) 78%, transparent);
}

.tool-ws-reader-thumb-written__page {
  display: block;
  width: 400%;
  transform: scale(0.25);
  transform-origin: top left;
}

/* Reader: the sheet sits where a scan sits, at a scan page's size
   (`--written-sheet-w` / `-h`, kept by attachment-page.js from the stack's
   height, as reader.js sizes a scan) — and grows past it when the text is
   longer than a page, so the stage gives up the scan's fixed aspect. */
.tool-ws-reader-stack .tool-ws-reader-stage--written,
.tool-ws-reader-stage--written.tool-ws-fit-page,
.tool-ws-reader-stage--written.tool-ws-fit-width {
  aspect-ratio: auto;
  height: auto;
  max-height: none;
  min-height: 100%;
  justify-content: flex-start;
  padding-block: var(--space-4);
}

.attachment-page-sheet {
  display: flex;
  flex-direction: column;
  gap: 0;
  box-sizing: border-box;
  width: var(--written-sheet-w, 100%);
  max-width: 100%;
  min-height: var(--written-sheet-h, auto);
  background: var(--color-surface);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

/* The toolbar pinned to the top edge of the page while it scrolls. */
.attachment-page-sheet__bar {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-right: var(--space-3);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}

/* A teammate changed the page under unsaved words: the line rides the pinned
   bar as a row of its own, so it cannot scroll out of sight. The bar wraps
   only while it carries the line. */
.attachment-page-sheet__bar:has(.attachment-page-sheet__live) {
  flex-wrap: wrap;
}

.attachment-page-sheet__live {
  flex: 1 0 100%;
  padding: 0 var(--space-3) var(--space-2);
}

.attachment-page-sheet .attachment-page-sheet__tools {
  flex: 1 1 auto;
  min-width: 0;
  border-bottom: 0;
}

.attachment-page-sheet__actions {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: var(--space-2);
}

.attachment-page-sheet__notice {
  padding: var(--space-3) var(--space-8) 0;
}

/* The sheet IS the page: the editor inside it draws no box of its own and
   fills the sheet's height, so the whole page takes the caret. */
.attachment-page-sheet__form {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
}

.attachment-page-sheet .attachment-page-sheet__editor,
.attachment-page-sheet .attachment-page-sheet__editor:focus-within {
  flex: 1 1 auto;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
}

.attachment-page-sheet__editor .pm-editor-surface {
  display: flex;
  flex-direction: column;
}

.attachment-page-sheet__editor .pm-editor-surface [contenteditable="true"] {
  flex: 1 1 auto;
  padding: var(--space-8);
}

/* At a reader stop the grid is hidden, and the empty card's row with it. */
.tool-ws--stop-reader .tool-ws-empty--written,
.tool-ws--stop-reader-rail .tool-ws-empty--written {
  display: none;
}

/* Panel: the card selected. */
.attachment-page-facts {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
}

.attachment-page-facts__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--weight-semibold);
}

.attachment-page-facts__icon {
  display: inline-flex;
  color: var(--color-accent);
}

.attachment-page-facts__row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--font-xs);
}

.attachment-page-facts__label {
  color: var(--color-muted);
}

.attachment-page-facts__value {
  text-align: right;
}

.attachment-page-facts__actions {
  display: flex;
  gap: var(--space-2);
}

/* In the reader the page is already open — the panel drops Open. */
.tool-ws--stop-reader .attachment-page-facts [data-written-open],
.tool-ws--stop-reader-rail .attachment-page-facts [data-written-open] {
  display: none;
}

/* ── relationship_doc addon ── */

/* First-meeting card — three side-by-side fields plus a "never met"
   tickbox below. Date / city / country share equal width on a single
   row at the panel's natural width; stack at narrow viewports. */
.rdoc-first-meeting-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.rdoc-first-meeting-fields {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.4fr);
  gap: var(--space-4);
}

.rdoc-first-meeting-fields--disabled {
  opacity: 0.5;
}

.rdoc-first-meeting-never {
  /* Mirror the spacing of ``quality-checks-item`` rows from the
     passport checks card so the visual rhythm is consistent. */
  margin-top: var(--space-1);
}

/* Country combobox — relative positioning so the absolute-positioned
   list anchors to the input. Reuses ``.combobox-input`` /
   ``.combobox-list`` / ``.combobox-opt`` rules already shipped for the
   page-type combobox in the page modal. */
.rdoc-country-combobox {
  position: relative;
}

@media (max-width: 700px) {
  .rdoc-first-meeting-fields {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Page-details card on the relationship-doc page modal — design-system
   ``card(compact=True)`` shell with a 2×2 field grid inside, mirroring
   the first-meeting card in the synopsis panel. Falls back to a single
   column on narrow viewports so the modal sidebar stays usable. */
.rdoc-page-meta-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3) var(--space-4);
}

.rdoc-page-meta-field {
  min-width: 0;
}

/* Date (month + day) and the tickets-only passenger field span the full
   row; Section + Date label pair on the row above. */
.rdoc-page-meta-field--date,
.rdoc-page-meta-field--passenger {
  grid-column: 1 / -1;
}

.rdoc-page-meta-na {
  margin-top: var(--space-3);
  font-size: var(--font-sm);
  color: var(--color-muted);
}

/* The design-system ``.form-group`` and ``.checkbox`` rules set an
   explicit ``display`` (flex / inline-flex) which beats the UA
   stylesheet's ``[hidden] { display: none }`` on specificity. Restore
   the attribute's intent on the rows the section-tab JS toggles. */
.rdoc-page-meta-field[hidden],
.rdoc-page-meta-na[hidden] {
  display: none;
}

@media (max-width: 720px) {
  .rdoc-page-meta-fields {
    grid-template-columns: 1fr;
  }
}

/* Wrapper around the page-meta form when it's promoted above the modal
   tab strip (section-tabbed doc types). The slot content is rendered
   as a design-system ``card(compact=True)`` which carries its own
   frame, so this wrapper provides only outer spacing. Issue #482 §10. */
.page-modal-above-tabs {
  margin: var(--space-3) 0;
}

/* Section/Date form folded into the panel's PAGE section (no standalone
   "Page details" card). A hairline divider separates it from the decision
   box above so the one PAGE section still reads as grouped sub-parts. */
.rdoc-page-meta--inline {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
}

/* ── Combined PAGE card (section-tabbed panel) ──────────────────────────────
   `.ws-rev-page-card` is the ONE bordered card (it carries `.ws-rev-box` + the
   status class, so the approved / redo colour wraps the whole thing). Inside it
   the Section/Date meta (top) and the decision box (bottom) sit borderless,
   split by a single divider — mirroring the passport's faces-over-decision. */
.ws-rev-page-card .ws-rev-page-meta {
  /* Match the passport card's faces padding + foot divider (design tokens). */
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-border-subtle);
}

/* The Section/Date form fills the meta slot — the meta div owns the padding. */
.ws-rev-page-card .rdoc-page-meta--inline {
  margin: 0;
  padding: 0;
  border-top: 0;
}

/* Section/Date field labels + spacing on the design-system scale — the base
   `.form-label` / `.form-group` carry raw literals (0.78rem / 0.4rem); pull the
   in-card fields onto tokens so the card reads like the rest of the panel. */
.ws-rev-page-card .form-label {
  font-size: var(--font-xs);
}

.ws-rev-page-card .form-group {
  gap: var(--space-2);
}

/* The decision box no longer draws its own frame — the card owns it. */
.ws-rev-page-card .ws-rev-box--joined {
  border: 0;
  border-radius: 0;
}

.ws-rev-page-card .quality-form {
  margin: 0;
}

.rdoc-page-meta-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-2);
}

/* Bulk "Move to section" control in the selection panel's Actions card. */
.ws-panel-move-section {
  margin-top: var(--space-3);
}

.ws-panel-move-section .form-label {
  display: block;
  margin-bottom: var(--space-1);
}

/* Bulk face-label pills (#653) — the per-face stamp/visa pills applied to the
   whole selection. Reuses .page-modal-face-pills so the row reads identically
   to the page panel's; only the surrounding label + spacing are new. */
.ws-panel-label-pills {
  margin-top: var(--space-3);
}

.ws-panel-label-pills .form-label {
  display: block;
  margin-bottom: var(--space-1);
}

/* Image adjustments (#804) — one control block, both panel focus states. The
   selection wrapper only adds the count label + spacing; the block itself is
   identical to the page panel's so the two read as the same control. */
.ws-panel-adjust {
  margin-top: var(--space-3);
}

.ws-panel-adjust .form-label {
  display: block;
  margin-bottom: var(--space-1);
}

.ws-adjust {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.ws-adjust-actions {
  display: flex;
  gap: var(--space-2);
}

/* ── Text overlays on a page image (#1306) ────────────────────────────────
   The box is drawn to the same numbers the export bakes it with
   (core/files/overlays.py): the one vendored font, a 1.25 line height and
   0.25em / 0.5em padding, centred on its stored point, the font size a share
   of the uncropped page's shorter side via the frame's `cqmin`. Those em and
   line-height figures are the bake's geometry, not spacing choices — a token
   here would let a theme change move text off where the export puts it. No
   kerning and no contextual alternates, because Pillow's basic layout draws
   neither. Colours arrive as custom properties from the stored values.

   Every --ov-* is set inline — by `frame_css` / `box_css` server-side and
   overlays.js client-side — so the fallbacks below only ever show for a box
   rendered without its properties; they are the defaults of a new box. */
@font-face {
  font-family: "GMID Overlay";
  src: url("/static/fonts/Inter-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

/* The two faces are `WEIGHTS` in core/files/overlays.py — Regular and Bold —
   picked per box by its --ov-weight. */
@font-face {
  font-family: "GMID Overlay";
  src: url("/static/fonts/Inter-SemiBold.ttf") format("truetype");
  font-weight: 600;
  font-style: normal;
  font-display: block;
}

/* Covers the tile's displayed (cropped) page and clips to it, as the crop
   clips the export. Never takes a pointer itself. */
.page-ov {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

/* The whole uncropped page, offset by the crop. */
.page-ov__frame {
  position: absolute;
  left: var(--ov-fx, 0%);
  top: var(--ov-fy, 0%);
  width: var(--ov-fw, 100%);
  height: var(--ov-fh, 100%);
  container-type: size;
}

.page-ov__box {
  position: absolute;
  left: var(--ov-x, 50%);
  top: var(--ov-y, 50%);
  transform: translate(-50%, -50%);
  display: block;
  padding: 0.25em 0.5em;
  font-family: "GMID Overlay";
  font-weight: var(--ov-weight, 400);
  font-size: calc(var(--ov-size, 3) * 1cqmin);
  line-height: 1.25;
  letter-spacing: 0;
  font-kerning: none;
  font-feature-settings: "calt" 0, "liga" 0;
  font-synthesis: none;
  text-align: left;
  white-space: pre;
  background: var(--ov-bg, transparent);
  color: var(--ov-fg, currentColor);
}

/* The reader stage is where a box is moved: drag or the arrow keys. */
.page-ov__frame[data-ov-interactive] .page-ov__box {
  pointer-events: auto;
  cursor: move;
  touch-action: none;
  user-select: none;
}

.page-ov__frame[data-ov-interactive] .page-ov__box.is-dragging {
  cursor: grabbing;
}

.page-ov__frame[data-ov-interactive] .page-ov__box.is-active,
.page-ov__frame[data-ov-interactive] .page-ov__box:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Re-cropping shows the whole page to draw on; the boxes would sit in the
   old crop's frame over it. */
.tool-ws-reader-stage.is-cropping .page-ov {
  display: none;
}

/* The page pane's Text section. */
.ws-ov {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
}

.ws-ov__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  align-self: stretch;
}

.ws-ov__list:empty {
  display: none;
}

.ws-ov__hint {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.ws-ov-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.ws-ov-row__text {
  resize: vertical;
}

.ws-ov-row__actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.ws-ov-row__style {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.ws-ov-row__style[hidden] {
  display: none;
}

.ws-ov-row__style-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* The box's colours in miniature, so a folded row still shows its look. */
.ws-ov-row__chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-6);
  height: var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--chip-bg);
  color: var(--chip-fg);
  font-family: "GMID Overlay";
  font-size: var(--font-xs);
  font-weight: var(--chip-weight, 400);
}

.ws-ov-row__caret {
  transition: transform var(--transition-fast);
}

.ws-ov-row__style-toggle[aria-expanded="true"] .ws-ov-row__caret {
  transform: rotate(180deg);
}

.ws-ov-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.ws-ov-field[hidden] {
  display: none;
}

.ws-ov-field .seg-toggle {
  align-self: flex-start;
}

.ws-ov-field__label {
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
}

.ws-ov-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.ws-ov-swatch {
  position: relative;
  width: var(--space-6);
  height: var(--space-6);
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--swatch);
  cursor: pointer;
}

.ws-ov-swatch--active {
  box-shadow: var(--shadow-focus);
  border-color: var(--color-accent);
}

/* The custom swatch wraps the native picker: the input fills it, invisible,
   so a click anywhere on the swatch opens the picker; a dashed ring marks it
   as "any colour" until a custom colour is chosen. */
.ws-ov-swatch--custom {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-style: dashed;
  overflow: hidden;
  background: var(--swatch, var(--color-surface));
  color: var(--color-muted);
}

/* A chosen custom colour fills the swatch; the plus would sit on it. */
.ws-ov-swatch--custom.ws-ov-swatch--active svg {
  display: none;
}

.ws-ov-swatch--custom input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

/* /system/document-types — read-only config display for keys not yet
   surfaced as form inputs (completeness_rule, section_tabs, checks). */

.system-dt-config-readonly {
  font-size: var(--font-xs);
}

/* ── /system/document-types: the inline edit row (/ux pass, 2026-10-09) ──
   Opens under a type's row; the cell carries the card inset, the blocks
   inside stack one section gap apart. */
.dt-edit {
  background: var(--color-bg);
  padding: var(--space-5);
}

.dt-edit__block {
  margin-bottom: var(--space-6);
}

.dt-edit__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.dt-edit__add {
  display: flex;
  gap: var(--space-3);
  align-items: center;
}

.dt-edit__add > .form-control {
  max-width: 260px; /* a page-type label; the row keeps its button beside it */
}

/* One registered addon in the edit row: a checkbox beside its card. */
.dt-addon {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-3);
  background: var(--color-surface);
  border-radius: var(--radius-sm);
}

.dt-addon > .checkbox {
  margin-top: var(--space-1);
}

.dt-addon__body {
  flex: 1;
  min-width: 0;
}

.dt-addon__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.dt-addon__line {
  margin-top: var(--space-1);
}

/* The addon's settings for the type, under its card while it is ticked. */
.dt-addon__config {
  margin-top: var(--space-3);
}

.system-dt-section-tabs-list,
.system-dt-checks-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.system-dt-section-tab-chip,
.system-dt-check-chip {
  font-size: var(--font-xs);
}

/* ── Thumbnail grid ── */

.tool-ws-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-5);
  /* Size each row to its cards' real height. The default `auto` does not
     reserve the height an `aspect-ratio` thumb derives from a percentage
     width, so rows collapse; combined with `.page-card { align-self: start }`
     the content-sized cards then overflow their short row and overlap the row
     below. `max-content` makes each row reserve the full card height. */
  grid-auto-rows: max-content;
}

.tool-ws-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 200px;
  color: var(--color-muted);
  font-size: 0.85rem;
}

/* Empty-requirement placeholder (#616) — the upload dropzone itself fills the
   workspace body, so a 0-page doc is directly actionable (click/drop) and reads
   as an empty workspace, not as seeded pages. The disabled toolbar above frames
   it as a workspace. Stop-independent: the disabled slider can't switch views,
   so this one zone stands in for both grid and reader. */
.tool-ws-body > .tool-ws-empty--placeholder {
  flex-direction: column;
  align-items: stretch;
  justify-content: stretch;
  min-height: 0;
  flex: 1 1 auto;
  /* Align the dropzone with the page grid: the docs workspace's left gutter
     already comes from .main-content (left-only `:has(.tool-ws-unified)`), so
     add no extra left padding here — the dropzone's left edge then lines up
     with the page cards. The other three sides get the standard gutter so the
     box reads as one consistent --space-ws-gutter frame instead of the
     asymmetric default-padded fill. */
  padding: var(--space-ws-gutter) var(--space-ws-gutter) var(--space-ws-gutter) 0;
}

.tool-ws-dropzone--fill {
  flex: 1 1 auto;
}

/* ── Page card ── */

.page-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
  transition: box-shadow var(--transition-normal), border-color var(--transition-normal);
  position: relative;
  cursor: grab;
  grid-column: span 2;
  /* Size container for the info bar (#1136): the card's width is set from
     outside (grid span, or --ws-card-h × aspect in the unified grid), never
     from its content, so inline-size containment is safe. */
  container-type: inline-size;
  /* Size each card to its own content, not stretched to the row height.
     The grid default `align-items: stretch` makes a card grow to match the
     tallest card in its row; the thumb (`flex: 1 1 auto`) then stretches off
     its `aspect-ratio`, which breaks the rotation-fill rule for spreads and
     leaves gutters on the sides. Top-aligning keeps every thumb on-ratio. */
  align-self: start;
}

.page-card:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--color-accent);
}

.page-card.ws-page--landscape {
  grid-column: span 3;
}

.page-card.ws-page--landscape .page-card-thumb {
  aspect-ratio: 4 / 3;
}

/* ── Card size variants ── */

.page-card--spread {
  grid-column: span 3;
}

/* Spreads are inherently landscape content (two facing pages), so force
   a landscape card-thumb shape regardless of the file's native dimensions.
   Avoids tall portrait cards for spreads photographed with phone held
   upright — the file is portrait but the content is a landscape book. */
.page-card--spread .page-card-thumb {
  aspect-ratio: 4 / 3;
}

/* Medium (default): 6 cards per row, spreads span 3 */
.tool-ws-grid--md .page-card {
  grid-column: span 2;
}

.tool-ws-grid--md .page-card.ws-page--landscape {
  grid-column: span 3;
}

.tool-ws-grid--md .page-card--spread {
  grid-column: span 3;
}

/* Large: 4 cards per row, spreads span 4 */
.tool-ws-grid--lg .page-card {
  grid-column: span 3;
}

.tool-ws-grid--lg .page-card.ws-page--landscape {
  grid-column: span 4;
}

.tool-ws-grid--lg .page-card--spread {
  grid-column: span 4;
}

.page-card--missing {
  border-style: dashed;
  opacity: 0.7;
}

/* "Remaining pages" placeholder (#741) — the couple sent a few pages, the team
   needs the rest. Redo-styled (action-needed): a warning-tinted dashed card so
   it reads as an ask, paired with the "Remaining pages needed" badge. */
.page-card--remaining {
  border-style: dashed;
  border-color: var(--color-warning);
}

.page-card-missing-icon--remaining {
  color: var(--color-warning);
}

/* The range the remaining card is waiting on (#1280) — "Pages 10–65". Body
   colour so it reads as the content, the warning tint staying on the ask. */
.page-card-missing-icon__detail {
  padding: 0 var(--space-3);
  color: var(--color-text);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  text-align: center;
  overflow-wrap: anywhere;
}

.page-card-info {
  transition: opacity 220ms ease;
}

.page-card.ws-page--excluded .page-card-info {
  opacity: 0.55;
}

/* Excluded thumb: desaturate + dim so the image stays readable as a
   sanity check (per the issue's "keep visible, more strongly de-
   emphasized" lean). The "Excluded" chip in page-card-chips carries
   the label. */
.page-card.ws-page--excluded .page-card-thumb img {
  filter: grayscale(1);
  opacity: 0.6;
  transition: filter 220ms ease, opacity 220ms ease;
}

.page-card.ws-page--dragging {
  opacity: 0.3;
  outline: 2px dashed var(--color-accent);
  outline-offset: -2px;
  background: color-mix(in srgb, var(--color-accent) 6%, transparent);
}

.page-card.ws-page--dragging .page-card-thumb img {
  opacity: 0.3;
}

.page-card.ws-page--dragging .page-card-info,
.page-card.ws-page--dragging .page-card-controls {
  opacity: 0.3;
}

/* Grid drop indicator — a single bar in the gap, no card shifting. Cards
   stay still so they don't swing as the cursor crosses midlines (which
   would re-target which card holds data-drop). The bar grows in (width
   0→6) when data-drop is set and pulses gently while active. */
.page-card[data-drop],
.page-card[data-file-drop] {
  overflow: visible;
}

.page-card[data-drop]::after,
.page-card[data-file-drop]::after {
  content: '';
  position: absolute;
  top: 4px;
  bottom: 4px;
  width: 6px;
  background: var(--color-accent);
  border-radius: 3px;
  pointer-events: none;
  z-index: 2;
  animation: drop-bar-in 0.14s ease-out both, drop-bar-pulse 1.6s ease-in-out 0.14s infinite;
}

/* Bar centred in the original 16px grid gap. */
.page-card[data-drop="before"]::after,
.page-card[data-file-drop="before"]::after {
  left: -11px;
}

.page-card[data-drop="after"]::after,
.page-card[data-file-drop="after"]::after {
  right: -11px;
}

@keyframes drop-bar-in {
  0% {
    transform: scaleX(0);
    opacity: 0;
    box-shadow: 0 0 0 transparent;
  }

  100% {
    transform: scaleX(1);
    opacity: 1;
    box-shadow: 0 0 10px color-mix(in srgb, var(--color-accent) 60%, transparent);
  }
}

@keyframes drop-bar-pulse {

  0%,
  100% {
    box-shadow: 0 0 8px color-mix(in srgb, var(--color-accent) 50%, transparent);
  }

  50% {
    box-shadow: 0 0 16px color-mix(in srgb, var(--color-accent) 75%, transparent);
  }
}

.page-card-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: var(--color-border-subtle);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1 1 auto;
}

.page-card-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Rotated 90/270: swap pre-rotate dimensions so the post-rotate visual fills
   the card edge-to-edge. Same reciprocal trick as the filmstrip thumb. */
.page-card.ws-page--landscape.ws-page--rotated .page-card-thumb img {
  width: 75%;
  height: 133.3333%;
}

.page-card.ws-page--rotated:not(.ws-page--landscape) .page-card-thumb img {
  width: 133.3333%;
  height: 75%;
}

.page-card-missing-icon {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-muted);
  font-size: var(--font-xs);
}

/* Failed background-upload state (#683) — same layout as missing, danger tint. */
.page-card-missing-icon--error {
  color: var(--color-danger);
}

/* Processing placeholder (#683) — a backgrounded upload's card while the arq
   worker decodes; replaced by the real image on docs_page.derivatives_ready. */
.page-card-processing {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-muted);
  font-size: var(--font-xs);
}

.page-card-info {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.5rem;
  font-size: var(--font-xs);
}

.page-card-pos {
  font-weight: var(--weight-bold);
  color: var(--color-text);
  min-width: 1.2rem;
  /* One line, like .page-card-label below — a spread's structural text
     ("Back cover · Front cover") otherwise wraps the info bar to three
     lines. Full text stays available via the title attribute. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page-card-label {
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  /* Explicit min-width: 0 lets text-overflow: ellipsis fire — flex items
     default to min-width: auto, which keeps the intrinsic content width
     and prevents the configured truncation. */
  min-width: 0;
}

.page-card-controls {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.2rem 0.4rem 0.35rem;
  border-top: 1px solid var(--color-border);
  margin-top: auto;
}

.page-card-type {
  font-size: 0.68rem;
  padding: 0.1rem 0.2rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background: var(--color-surface);
  flex: 1;
  min-width: 0;
}

.page-card-actions {
  display: flex;
  gap: 0.15rem;
  margin-left: auto;
}

.page-card-btn {
  width: 22px;
  height: 22px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: 0.72rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  line-height: 1;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.page-card-btn:hover {
  background: var(--color-border);
  color: var(--color-text);
}

.page-card-btn--active {
  background: var(--color-accent);
  color: var(--color-surface);
  border-color: var(--color-accent);
}

.page-card-btn--danger:hover {
  background: var(--color-danger);
  color: var(--color-surface);
  border-color: var(--color-danger);
}

/* ── Submission status chip on the page card.
   Sits inline at the end of the info bar (the row below the thumbnail).
   The chip itself is a Tag primitive (see partials/submission_status_badge.html);
   this wrapper just right-aligns it after position label + addon badges. ── */

.page-card-info-status {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  /* Never shrink — the chip is the load-bearing status indicator now that
     the bottom-of-thumbnail overlay is gone. Long labels truncate instead. */
  flex-shrink: 0;
}

/* Narrow cards collapse the status pill to its tone dot (#1136). In the
   unified grid a card is --ws-card-h × the page's aspect, so a portrait
   "Single" page at the default density is ~165px wide — the pill
   ("Under review" + dot + padding ≈ 100px) plus the bold position text left
   the page number, which is `flex: 1; min-width: 0`, with nothing. Below
   the threshold the text node is zeroed (font-size: 0 hides a bare text
   node; the ::before dot is sized in px so it survives) and the pill's
   border + padding go with it. The tone still reads — grey under review,
   green accepted, amber redo — and the full label rides `title` on the
   wrapper (page_card.html). 180px: a portrait card at the default stop is
   165px, at the large stop 255px; a landscape card at the default stop is
   233px. Anything a page number cannot fit next to the pill is below it. */
@container (max-width: 180px) {
  .page-card-info-status .tag {
    font-size: 0;
    padding: 0;
    border: 0;
    background: transparent;
    line-height: 0;
  }

  .page-card-info-status .tag--with-dot::before {
    margin-right: 0;
  }
}

/* ── Version History tab — interior layout only.
   Visual frame and "viewing" highlight come from the Card primitive
   (variant="active"). Collapsed older rows use the native `hidden`
   attribute, toggled by version-history.js. ── */

.version-history {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) 0;
}

.version-history-row {
  display: grid;
  grid-template-columns: 64px 1fr auto;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-2);
}

.version-history-thumb img {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: var(--radius-xs);
  cursor: pointer;
}

.version-history-thumb-missing {
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.65rem;
  color: var(--color-muted);
  background: var(--color-surface-muted);
  border-radius: var(--radius-xs);
}

.version-history-meta {
  min-width: 0;
}

.version-history-meta-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  margin-bottom: 0.25rem;
}

.version-history-meta-row .text-muted {
  font-size: var(--font-xs);
}

.version-history-meta-source {
  font-size: 0.75rem;
}

.version-history-actions {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.version-history-expand {
  align-self: flex-start;
}

/* ── Renderings sub-section — interior layout only.
   Card primitive provides the section frame (title slot + body). ── */

.renderings-list {
  list-style: none;
  margin: 0 0 var(--space-2);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.renderings-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  padding: 0.25rem 0;
  font-size: 0.75rem;
}

/* Keep each cell (preset name, "WxH px", format, "Current" flag) intact
   on one line so the "px" suffix doesn't wrap below the dimension. */
.renderings-row>* {
  white-space: nowrap;
}

.renderings-row--current .renderings-preset {
  color: var(--color-accent);
}

.renderings-preset {
  font-weight: var(--weight-bold);
  min-width: 7rem;
}

.renderings-current-flag {
  margin-left: auto;
  /* Pull the badge in from the card's right edge — the row sits flush
     against the card padding, which reads tight against an uppercase
     bold label. */
  padding-right: var(--space-1);
  font-size: 0.65rem;
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-accent);
}

.renderings-upgrade-form {
  margin: 0;
}

/* ── Files tab — Round 2 layout ─────────────────────────────────────────
   No redundant page H2 (the case nav says we're in Files). One tight row:
   segmented toggle + meta count on the right. Banner + drop strip stack
   under it; then the chosen view (submissions log or files-by-raw). ── */

.files-tab-section-filter {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.files-tab-section-filter select {
  font-size: var(--font-sm);
  padding: 0.25rem 0.45rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
}

/* Dropzone — files-tab upload modal. Neutral dashed box that hover- and
   drag-tints to accent. The whole label is the click target; the file
   input is visually hidden. */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-6);
  text-align: center;
  border: 1.5px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  cursor: pointer;
  transition: background var(--transition-normal),
    border-color var(--transition-normal);
}

.dropzone:hover,
.dropzone--active {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 4%, var(--color-surface));
}

.dropzone__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-muted);
}

.dropzone:hover .dropzone__icon,
.dropzone--active .dropzone__icon {
  color: var(--color-accent);
}

.dropzone__title {
  font-size: var(--font-base);
  color: var(--color-text);
}

.dropzone__hint {
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.dropzone__input {
  /* Visually hidden — the whole dropzone label is the click target. */
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip: rect(0 0 0 0);
  overflow: hidden;
}

/* Files-tab upload modal body — stack the dropzone, selectors, actions. */
.files-upload {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.files-upload__selected {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
  word-break: break-word;
}

.files-upload__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
}

/* ── Submissions log — Round 2 batch row ──────────────────────────────
   <details>/<summary> shell. Summary: chevron · id block (title row +
   meta) · count strip (Tag pills). Body: <ul> of files with 3-zone rows
   (icon+name+landing / status+download). ── */

.submissions-log {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.submissions-batch {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.submissions-batch-summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  min-height: var(--pane-head-h); /* shares the pane-header height rhythm */
  cursor: pointer;
  list-style: none;
}

.submissions-batch-summary::-webkit-details-marker {
  display: none;
}

.submissions-batch-chev {
  display: inline-flex;
  color: var(--color-muted);
  transition: transform var(--transition-normal) ease-out;
}

.submissions-batch[open]>.submissions-batch-summary .submissions-batch-chev {
  transform: rotate(90deg);
}

.submissions-batch-id {
  min-width: 0;
  flex: 1;
}

.submissions-batch-title-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.submissions-batch-title {
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  font-size: var(--font-sm);
}

.submissions-batch-source {
  font-size: var(--font-xs);
  color: var(--color-muted);
  padding: 1px var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--color-surface-muted);
}

.submissions-batch-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: 2px;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.submissions-batch-uploader {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.submissions-batch-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--color-surface-muted);
  color: var(--color-subtle);
  font-size: var(--font-xxs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.02em;
}

.submissions-batch-meta-dot,
.submissions-batch-time {
  color: var(--color-muted);
}

.submissions-batch-time {
  font-variant-numeric: tabular-nums;
}

.submissions-batch-counts {
  display: inline-flex;
  gap: var(--space-2);
  align-items: center;
  flex-shrink: 0;
}

.submissions-batch-files {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--color-border);
}

.submissions-batch-file {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
  font-size: var(--font-sm);
  cursor: pointer;
}

.submissions-batch-file:first-child {
  border-top: 0;
}

/* Selected for preview (#742) — shares the inspector's active-row treatment. */
.submissions-batch-file.drive-selected {
  background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
}

.subs-file-left {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  min-width: 0;
}

.subs-file-icon {
  display: inline-flex;
  color: var(--color-muted);
  flex-shrink: 0;
}

/* #714 — first-page thumbnail in a Submissions-log row. Compact, uniform
   frame (browse, not QA) — cover keeps the dense row tidy; the glyph
   fallback centres when the file has no decoded page yet. */
.subs-file-thumb {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border-subtle);
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface-dim);
  color: var(--color-muted);
}

.subs-file-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.subs-file-name {
  font-weight: var(--weight-medium);
  color: var(--color-text);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex-shrink: 1;
  min-width: 0;
}

.subs-file-name:hover {
  color: var(--color-accent);
  text-decoration: underline;
}

.subs-file-landing {
  font-size: var(--font-xs);
  color: var(--color-muted);
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  flex-shrink: 0;
}

.subs-file-landing a {
  color: var(--color-accent);
  text-decoration: none;
  font-weight: var(--weight-medium);
}

.subs-file-landing a:hover {
  text-decoration: underline;
}

.subs-file-landing-pending {
  color: var(--color-muted);
  font-style: italic;
}

/* Batch still decoding — shimmer placeholder file rows. The server-side
   "processing" signal is a follow-up; the treatment ships ready. */
.subs-file-skeleton {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
}

.subs-file-skeleton__thumb {
  width: 28px; /* matches .subs-file-thumb */
  height: 28px;
  border-radius: var(--radius-sm);
}

.subs-file-skeleton__line {
  flex: 1;
  height: 12px;
  max-width: 16rem;
  border-radius: var(--radius-xs);
}

.subs-file-skeleton__thumb,
.subs-file-skeleton__line {
  background: linear-gradient(
    90deg,
    var(--color-surface-muted) 0%,
    var(--color-border-subtle) 50%,
    var(--color-surface-muted) 100%
  );
  background-size: 200% 100%;
  animation: loading-shimmer 1.2s ease-in-out infinite;
}

/* Rejected upload flash on arrival — danger sibling of files-highlight-fade
   (3s one-shot matches that pattern). */
@keyframes drive-row-flash-danger {
  0% {
    background: color-mix(in srgb, var(--color-danger) 12%, transparent);
  }

  100% {
    background: transparent;
  }
}

.subs-file--flash-danger {
  animation: drive-row-flash-danger 3s ease-out forwards;
}

.subs-file-slot {
  color: var(--color-muted);
}

.subs-file-section {
  color: var(--color-muted);
}

.subs-file-right {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

/* Per-file status: flatten the Tag to coloured text + leading dot.
   The pill shape stays on the batch-summary count strip — counts benefit
   from the tinted shape; per-row dispositions read busy when stacked. */
.subs-file-right .tag {
  background: transparent;
  border: none;
  padding: 0;
  border-radius: 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
}

.subs-file-download {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-xs);
  color: var(--color-muted);
  text-decoration: none;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.subs-file-download:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

/* ── Files by raw — Round 2 grid table ────────────────────────────────
   Five-column grid with display:contents rows so each "row" div is just
   a sort handle. Sticky header. Two-line File cell (name + meta sub).
   Sortable on File / Used in / Uploaded — see js/tool-workspace/files-table.js. */

.files-table-v2 {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.files-table-v2-grid {
  display: grid;
  grid-template-columns:
    minmax(220px, 1.8fr) minmax(160px, 1.2fr) auto auto 44px;
  font-size: var(--font-sm);
}

.files-table-v2-grid .cell {
  padding: var(--space-4) var(--space-4);
  border-bottom: 1px solid var(--color-border-subtle);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.files-table-v2-grid .cell.hdr {
  background: var(--color-surface-dim);
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-muted);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
}

.files-table-v2-hdr-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
  white-space: nowrap;
}

.files-table-v2-hdr-sort:hover {
  color: var(--color-text);
}

.files-table-v2-hdr-sort.active {
  color: var(--color-text-strong);
}

.files-table-v2-hdr-arrow {
  display: inline-block;
  width: 9px;
  text-align: center;
  color: var(--color-accent);
  font-size: 10px;
  font-weight: var(--weight-bold);
}

.files-table-v2-hdr-sort:not(.active) .files-table-v2-hdr-arrow {
  color: var(--color-border);
}

.files-table-v2-row {
  display: contents;
}

.files-table-v2-row:hover>.cell:not(.hdr) {
  background: var(--color-surface-dim);
}

.files-table-v2-row.last>.cell {
  border-bottom: none;
}

.files-table-v2-row.highlight>.cell {
  background: color-mix(in srgb, var(--color-accent) 6%, transparent);
  animation: files-row-pulse 2s ease-out;
}

@keyframes files-row-pulse {
  0% {
    background: color-mix(in srgb, var(--color-accent) 20%, transparent);
  }

  100% {
    background: transparent;
  }
}

.cell.files-table-v2-file {
  flex-direction: row;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

/* #714 — first-page thumbnail on each raw row. Fixed, uniform frame (browse,
   not QA) — cover keeps the strip tidy regardless of page orientation. */
.files-table-v2-thumb {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border-subtle);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface-dim);
  color: var(--color-muted);
}

.files-table-v2-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.files-table-v2-file__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.files-table-v2-file .fn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.files-table-v2-file .fn:hover {
  color: var(--color-accent);
  text-decoration: underline;
}

.files-table-v2-file .fn-icon {
  color: var(--color-muted);
  flex-shrink: 0;
}

.files-table-v2-file .sub {
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.cell.files-table-v2-used {
  min-width: 0;
}

.files-table-v2-used a {
  color: var(--color-accent);
  text-decoration: none;
  font-weight: var(--weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

.files-table-v2-used a:hover {
  text-decoration: underline;
}

.files-table-v2-used .unused {
  color: var(--color-muted);
}

.files-table-v2-uploaded {
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.files-table-v2-dl {
  justify-content: center;
  padding: var(--space-2);
}

.files-table-v2-dl-btn {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
  background: transparent;
  border: 1px solid transparent;
  color: var(--color-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  text-decoration: none;
  transition: background var(--transition-fast),
    color var(--transition-fast),
    border-color var(--transition-fast);
}

.files-table-v2-dl-btn:hover {
  background: var(--color-hover);
  color: var(--color-text);
  border-color: var(--color-border);
}

/* #712 — Unfiled pool: page-keyed thumbnail grid with a per-card re-file form.
   A browse/file surface (not the QA workspace grid), so uniform fixed cards. */
.unfiled-pool__lead {
  margin: 0 0 var(--space-4);
  max-width: 60ch;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.unfiled-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--space-4);
}

.unfiled-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.unfiled-card__thumb {
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-surface-dim);
  border: 1px solid var(--color-border-subtle);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-muted);
}

.unfiled-card__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.unfiled-card__meta {
  font-size: var(--font-xs);
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.unfiled-card__refile {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.unfiled-card__select {
  width: 100%;
  font-size: var(--font-sm);
}

.unfiled-card__hint {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* ── Drive shell (#729 slice A) — toolbar search + default grid layout ──── */
.drive-grid-empty {
  padding: var(--space-8) var(--space-5);
  text-align: center;
}

.drive-grid-empty__text {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.drive-grid-empty__action {
  margin-top: var(--space-3);
}

/* ── Drive filter chips (#730 slice B) ─────────────────────────────────── */
/* Visibility utility for the filter/search engine (drive-filters.js). Needs
   !important to beat the List rows' `display: contents` and the cards' flex. */
.drive-hidden {
  display: none !important;
}

/* What belongs in the open folder — one quiet line at the top of the contents
   pane (core/drive/audiences.py folder_purposes). The tree pane is flush, so
   the line carries the list's gutter plus the row's inset and starts where a
   row's content does. The gutter is the panes' one inset, --space-5. */
.drive-folder-purpose {
  margin: 0;
  padding: var(--space-5) var(--space-5) 0 calc(var(--space-5) + var(--space-3));
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.drive-chips {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.drive-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast),
    background var(--transition-fast);
}

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

.drive-chip--active {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-surface);
}

.drive-chip__count {
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-normal);
  color: var(--color-muted);
}

.drive-chip--active .drive-chip__count {
  color: var(--color-surface);
  opacity: 0.85;
}

/* Zero-count chips dim their count so populated facets pop (drive-filters.js
   toggles data-zero on recompute). */
.drive-chip:not(.drive-chip--active) .drive-chip__count[data-zero] {
  color: var(--color-border);
}

/* Only populated facets show — drive-filters.js hides zero-count chips, and the
   whole bar when nothing qualifies. */
.drive-chip--hidden,
.drive-chips--empty {
  display: none;
}

/* ── Unfiled-page re-file form — cloned into the inspector for an Unfiled row ── */
.drive-card__refile {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.drive-card__refile-select {
  width: 100%;
  font-size: var(--font-sm);
}

/* ── Drive group-by sections — All files by requested document (#1368) ──
   The head is inset like a row's content, so the document's name starts where
   its files' names do, and its status tag rides the same line. */
.drive-group + .drive-group {
  margin-top: var(--space-6);
}

.drive-group__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  padding: 0 var(--space-3);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}

.drive-group__count {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
}

/* ── Drive selection inspector (#742) — two-pane frame + panel ──────────── */
/* One framed Finder surface (#742): list column | hairline divider | preview. */
.drive-workspace {
  display: flex;
  align-items: stretch;
  gap: 0;
  /* Fill the already-bounded #case-main (flex chain from the app shell) so each
     column scrolls internally instead of the whole region scrolling as one unit
     — the pattern the overview + workspace tabs use. No magic offset needed. */
  height: 100%;
  min-height: 0;
  overflow: hidden;
  /* No encapsulating box: the Files tab body carries `is-flush`, so the panes
     fill the tab full-width and full-height, edge to edge. */
  background: var(--color-surface);
  /* Shared row height so a folder row and a file row line up across the two
     columns. The file row is the tall one (its thumbnail + row padding); folder
     rows pad up to match. Content floor of 30px + the 6px vertical padding each
     side = 42px total. */
  --drive-row-min-h: calc(30px + 2 * var(--space-2));
}

.drive-main {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-4);
  /* Anchor for .drive-folder-empty's full-pane centring overlay. */
  position: relative;
}

/* Browse contents pane as a flex column so an empty folder's placeholder can
   centre in the free space below the filter chips — the inspector's soft empty
   treatment (glyph + one faint line, no heavy title). Scoped to the Browse pane
   so the Submissions view's .drive-main is untouched; .drive-list already stacks
   as its own flex column, so populated folders are unaffected. */
.drive-main[data-drive-contents] {
  display: flex;
  flex-direction: column;
}

/* Centred as a full-pane overlay (not in the flow below the filter chips) so
   the glyph + line sit at the pane's true vertical centre and align with the
   inspector's "Select a file…" placeholder next door. pointer-events: none
   keeps the chips above it clickable. */
.drive-folder-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  position: absolute;
  inset: 0;
  pointer-events: none;
  padding: var(--space-6) var(--space-4);
  text-align: center;
}

/* The shared .empty-state-icon carries a margin-bottom; here the flex `gap`
   already spaces the glyph from the line, so drop it — otherwise the gap stacks
   on the margin and the air is double the inspector's. */
.drive-folder-empty .empty-state-icon {
  margin-bottom: 0;
}

.drive-folder-empty .empty-state-message {
  margin: 0;
  color: var(--color-text-lavender-grey);
}

.drive-inspector {
  display: flex;
  flex-direction: column;
  flex: 0 0 380px;
  min-height: 0;
  /* Never let content (an unbreakable long filename in the INFO card) set the
     pane's minimum width — the resize seam owns the width, content truncates. */
  min-width: 0;
  overflow-x: hidden;
  padding: 0;
}

.drive-inspector__content {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-5);
}

/* Shared pane header — one chrome baseline across rail / contents / preview,
   in the compact-card title voice. */
/* Browse — the Finder three-pane (#750 + #742 + #757): a persistent folder-tree
   rail | contents pane | always-on preview, all inside the .drive-workspace frame.
   The rail stays mounted (its drag bindings + drop surface survive); #drive-contents
   swaps on folder nav. Each column scrolls internally (min-height:0 + overflow-y)
   within the bounded workspace, so the tree and preview stay in view while the
   contents scroll — not the whole region. */
.drive-tree-rail {
  display: flex;
  flex-direction: column;
  flex: 0 0 17rem;
  min-height: 0;
  padding: 0;
  /* Match the case overview chrome: a uniform white surface across all three
     panes. The seam to the contents pane is drawn by the .drive-col-resizer that
     follows this rail (subtle line, draggable), not a border here. */
  background: var(--color-surface);
}

/* Draggable seam between two Browse panes — carries the subtle divider line and
   a wide invisible grab zone, and turns accent while hovered/dragged. Width is
   1px so it reads exactly like the old border it replaces (drive-resize.js keeps
   RESIZER_W in sync). */
.drive-col-resizer {
  flex: 0 0 1px;
  align-self: stretch;
  position: relative;
  z-index: var(--z-sticky);
  background: var(--color-border-subtle);
  cursor: col-resize;
  user-select: none;
}

.drive-col-resizer::after {
  content: "";
  position: absolute;
  inset: 0 -4px; /* widen the hit target without taking layout width */
}

/* The line stays the subtle divider colour in every state — the col-resize
   cursor is the only affordance; no hover/drag colour change. */
.drive-col-resizer:focus-visible {
  outline: none;
}

/* While a seam is dragged, kill text selection + hover flicker across the panes. */
body.is-col-resizing {
  cursor: col-resize;
  user-select: none;
}

/* The Drive's three panes share one inset, --space-5, in both audiences — the
   house card inset (decided on the portal v1 canvas, 2026-10-18). The rail and
   the contents list set it here; the preview's content already had it. */
.drive-tree-rail .drive-folders {
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-5);
}

/* The middle contents column carries the divider to the preview column. It
   goes flush: padding moves onto the chips strip and list gutter, so the pane
   header + chips dividers run edge to edge. */
.drive-workspace--tree .drive-main {
  display: flex;
  flex-direction: column;
  padding: 0;
  /* Seam to the preview pane is the .drive-col-resizer that follows — no border. */
  /* Positioning context for the round-2 bulk bar, which floats over the
     bottom of the contents list rather than taking a flow slot. */
  position: relative;
}

.drive-workspace--tree .drive-main > .drive-list,
.drive-workspace--tree .drive-main > .empty-state,
.drive-workspace--tree .drive-main > .drive-grid-empty {
  overflow-y: auto;
  padding: var(--space-5);
}

/* Fill the pane and scroll internally. The filter bar is a flow footer below
   (not a floating overlay), so no extra bottom padding is needed to clear it. */
.drive-workspace--tree .drive-main > .drive-list {
  flex: 1;
  min-height: 0;
}

.drive-workspace--tree .drive-main > .empty-state {
  margin: auto 0;
}

/* ── Final documents node (#1257) ──
   The case's outbound end, in the Drive's contents pane: one row per
   requirement with its state, its files and its actions. Not a file list —
   no chips, no row selection — so the preview column has nothing to show and
   steps aside while the node is open. */
.drive-workspace--tree:has(#drive-contents[data-drive-folder="final_documents"]) .drive-inspector,
.drive-workspace--tree:has(#drive-contents[data-drive-folder="final_documents"]) [data-drive-resizer="inspector"] {
  display: none;
}

.drive-workspace--tree .drive-main > .pack-node {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.pack-node__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-border-subtle);
}

.pack-node__title {
  margin: 0;
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.pack-node__progress {
  margin: var(--space-1) 0 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.pack-node__actions,
.pack-row__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.pack-action {
  margin: 0;
}

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

.pack-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border-bottom: 1px solid var(--color-border-subtle);
}

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

.pack-row__name {
  font-weight: var(--weight-medium);
  color: var(--color-text-strong);
}

.pack-row__meta {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.pack-row__meta:empty {
  display: none;
}

.pack-row__warning {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-warning);
}

.pack-row__earlier {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-left: var(--space-3);
  border-left: 2px solid var(--color-border-subtle);
}

.pack-row__actions:empty {
  display: none;
}

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

.pack-file {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  font-size: var(--font-sm);
  min-width: 0;
}

.pack-file__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}

.pack-file__size {
  flex: none;
  color: var(--color-subtle);
}

.pack-file__download {
  flex: none;
}

/* The viewer: a large modal whose body is the file. */
.modal-dialog--viewer {
  width: 90vw;
  height: 85vh;
  max-height: 85vh;
}

.modal-dialog--viewer .modal-body {
  display: flex;
  min-height: 0;
  padding: 0;
  overflow: hidden;
}

.pack-viewer {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.pack-viewer__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--color-border-subtle);
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.pack-viewer__nav {
  display: flex;
  gap: var(--space-2);
}

.pack-viewer__stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface-dim);
}

.pack-viewer__frame {
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--color-surface);
}

.pack-viewer__image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* Filter chips sit as a footer bar below the contents list — a real flow slot
   (order pushes it under the list despite coming first in the DOM), not a
   floating overlay. It wraps onto extra rows when the populated facets overflow
   the width, so every facet stays visible without a horizontal scroll; the list
   above flexes to whatever height is left. */
.drive-workspace--tree .drive-chips {
  order: 1;
  flex: none;
  flex-wrap: wrap;
  margin: 0;
  /* Pin to the pane bottom even when the folder is empty: the empty-state
     overlay (.drive-folder-empty) sits outside the flow, so without this the
     chips would be the only flow child and land at the top. With rows present
     .drive-list's flex:1 already eats the free space, so this is a no-op. */
  margin-top: auto;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
  background: var(--color-surface);
}

.drive-workspace--tree .drive-chip {
  flex: none;
}

/* Three panes get tight on narrower viewports — trim the fixed columns so the
   contents keep a usable width without collapsing the always-on preview. */
@media (max-width: 1100px) {
  .drive-tree-rail {
    flex-basis: 14rem;
  }

  .drive-inspector {
    flex-basis: 20rem;
  }
}

.drive-inspector__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  /* Fill the inspector pane so the glyph + hint sit vertically centred in the
     whole column, not just inside a 12rem box pinned to the top. The min-height
     stays a floor for a very short pane. */
  height: 100%;
  min-height: 12rem;
  color: var(--color-muted);
  text-align: center;
}

.drive-inspector__empty-text {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-text-lavender-grey);
}

.drive-inspector__subtitle {
  margin: 0 0 var(--space-4);
  font-size: var(--font-xs);
  color: var(--color-text);
}

/* Multi-selection summary: the count + hint sit under the centered thumbnail
   fan, so center them too. */
.drive-inspector__title--multi,
.drive-inspector__subtitle--multi {
  text-align: center;
}

/* Section heading ("Information") in the case-overview .fr-section-head voice:
   small, muted, uppercase with wider tracking — a quiet label over the meta
   rows, not a second title. */
.drive-inspector__section {
  margin: 0 0 var(--space-2);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--color-muted);
}

.drive-inspector__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.drive-inspector__title {
  flex: 1;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.drive-inspector__close {
  flex: none;
  padding: 0;
  width: 1.5rem;
  height: 1.5rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font-size: var(--font-lg);
  line-height: 1;
  color: var(--color-muted);
  cursor: pointer;
}

.drive-inspector__close:hover {
  color: var(--color-text);
}

.drive-inspector__preview {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 3 / 4;
  margin-bottom: var(--space-4);
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--color-surface-dim);
  color: var(--color-muted);
}

.drive-inspector__preview img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Preview states (drive-inspector.js): loading → shimmer, unavailable →
   striped placeholder + caption. A broken image never renders. */
.drive-inspector__preview--loading {
  border: 0;
  background: linear-gradient(
    90deg,
    var(--color-surface-muted) 0%,
    var(--color-border-subtle) 50%,
    var(--color-surface-muted) 100%
  );
  background-size: 200% 100%;
  animation: loading-shimmer 1.2s ease-in-out infinite; /* the loading-skeleton shimmer */
}

.drive-inspector__preview--missing {
  flex-direction: column;
  gap: var(--space-2);
  background: repeating-linear-gradient(
    45deg,
    var(--color-surface-dim) 0 6px,
    var(--color-surface-muted) 6px 12px
  );
}

.drive-inspector__preview-caption {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* Quick Look popup (drive_quicklook_modal.html): the dialog hugs the image up
   to the viewport bounds; the body drops the default padding so the image is
   the popup. The image renders at a fixed viewport height (preview upscales,
   then sharpens when the working image swaps in) — no size jump on load. */
.drive-inspector__preview--zoomable img {
  cursor: zoom-in;
}

.modal-dialog--quicklook {
  width: auto;
  max-width: 92vw;
  max-height: 92vh;
}

.modal-dialog--quicklook .modal-body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: 0;
  overflow: hidden;
  /* Clip the image to the dialog's rounded bottom corners. */
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  background: var(--color-surface-dim);
}

.drive-quicklook-img {
  display: block;
  height: 86vh;
  max-width: 100%;
  object-fit: contain;
}

.drive-inspector__meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
}

/* Icon-led rows matching the case-overview field rows (.field-block): a muted
   14px leading icon, a small semibold muted label, and the dark value pushed to
   the right edge. The Status row's icon is a tone-filled dot (below). */
.drive-inspector__meta div {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.drive-inspector__meta .di-meta-ico {
  flex-shrink: 0;
  display: inline-flex;
  width: 14px;
  height: 14px;
  color: var(--color-muted);
}

.drive-inspector__meta .di-meta-ico svg {
  width: 14px;
  height: 14px;
}

/* Status-dot tones — mirror the Drive status legend / row dispositions. */
.drive-inspector__meta .di-meta-ico--success {
  color: var(--color-success);
}

.drive-inspector__meta .di-meta-ico--warning {
  color: var(--color-warning);
}

.drive-inspector__meta .di-meta-ico--danger {
  color: var(--color-danger);
}

.drive-inspector__meta .di-meta-ico--info {
  color: var(--color-info);
}

.drive-inspector__meta .di-meta-ico--muted,
.drive-inspector__meta .di-meta-ico--neutral {
  color: var(--color-muted);
}

/* The couple's tones (core/page_status.status_tone), the same colours their
   status tags use. */
.drive-inspector__meta .di-meta-ico--pending {
  color: var(--color-info);
}

.drive-inspector__meta .di-meta-ico--missing {
  color: var(--color-warning-text);
}

/* Owner qualifier inside each Filed-in link — "Passport · Olga Novak" /
   "Passport · Shared". Inherits the link's accent colour. */
.drive-inspector__req-owner {
  margin-left: var(--space-1);
}

.drive-inspector__meta dt {
  flex-shrink: 0;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
  color: var(--color-muted);
  white-space: nowrap;
}

.drive-inspector__meta dd {
  min-width: 0;
  margin: 0;
  margin-left: auto;
  font-size: var(--font-xs);
  color: var(--color-text);
  text-align: right;
  overflow-wrap: anywhere;
}

.drive-inspector__meta dd a {
  color: var(--color-text);
}

/* Reused INFO fold (routes.py::file_info → page_info_fold.html) injected under
   the meta list — a bordered .ws-rev-fold card. Space it off the actions below;
   the empty placeholder (pre-load) takes no room. */
.drive-inspector__info {
  margin-bottom: var(--space-4);
}

.drive-inspector__info:empty {
  margin-bottom: 0;
}

/* A file's notes (#1253) — the shared thread rail, last in the pane under the
   file's facts. The rail brings its own feed + composer styling; this only
   places it. */
.drive-inspector__notes {
  margin-bottom: var(--space-4);
}

/* A proof's type picker (#1263), above the notes. */
.drive-inspector__proof-type {
  margin-bottom: var(--space-4);
}

/* A money proof's receipt date (#1264): the label in the section voice above
   the field, and one muted line saying how long a rule keeps the proof. */
.drive-inspector__proof-date {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-3);
}

.drive-inspector__proof-date .drive-inspector__section {
  margin-bottom: 0;
}

.drive-inspector__proof-kept {
  margin: 0;
  font-size: var(--font-xxs);
  color: var(--color-muted);
}

.drive-inspector__notes:empty {
  margin-bottom: 0;
}

/* Keep the composer in the notes, in normal flow. The shared composer carries
   `composer`, which the case page's `.case-detail-page .composer
   { position: absolute }` would otherwise reach — no ancestor in the inspector
   is positioned, so it anchored to the page and stretched across its bottom.
   The docs panel undoes the same rule for the same reason (#517). Three
   classes, because that rule has two and sits later in this file. */
.drive-inspector__notes .page-composer.composer {
  position: static;
  box-shadow: none;
}

/* The INFO card is shared with the review sidebar (page_info_fold.html);
   only the Drive preview drops its dim fill. */
.drive-inspector__info .ws-rev-fold {
  background: none;
}

/* Full-width stacked action buttons. */
.drive-inspector__actions .btn {
  width: 100%;
  justify-content: center;
}

/* Actions sit above the file information — space them off the section below.
   An empty block (no download / no workspace link) takes no room. */
.drive-inspector__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.drive-inspector__actions:empty {
  margin-bottom: 0;
}

/* ── cmd/shift multi-select: row-trash · stacked summary ── */

/* N≥2 selection summary — a fanned pile of the selected rows' thumbnails,
   Finder-style, filling the same footprint as the single-file preview
   (drive-inspector.js sets --n = pile size, --i = each tile's index). */
.drive-inspector__stack {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  margin-bottom: var(--space-5);
}

.drive-inspector__stack-item {
  position: absolute;
  top: 50%;
  left: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 66%;
  height: 76%;
  overflow: hidden;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  /* Center, then fan symmetrically around the middle of the pile. */
  transform: translate(-50%, -50%)
    rotate(calc((var(--i) - (var(--n, 1) - 1) / 2) * 6deg));
}

.drive-inspector__stack-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.drive-inspector__stack-item--ph {
  color: var(--color-muted);
}

/* ── Lean Finder list column (#742) ────────────────────────────────────── */
.drive-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Infinite-scroll sentinel (#816): a centred spinner row at the end of the
   window; scrolling it into view fetches + appends the next page. */
.drive-load-sentinel {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4) 0;
}

.drive-list-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  /* Shared height with the folder rows so the two columns align, border-box to
     match their box model exactly. */
  box-sizing: border-box;
  height: var(--drive-row-min-h);
  border-radius: var(--radius-sm);
  cursor: pointer;
  /* Shift-click range-selects rows; without this it would paint a text
     selection across the filenames in the range. */
  user-select: none;
  transition: background var(--transition-fast);
}

.drive-list-row:hover {
  background: var(--color-hover);
}

.drive-list-row.drive-selected {
  background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface));
}

/* Deep-link highlight — a ?file= arrival (page-info "View in Files", the
   transfers-tray "already in your Drive" row) scrolls to the file row and
   pulses it once so the eye catches it. Ports the retired files-table flash. */
.drive-list-row.highlight {
  animation: drive-row-pulse 2s ease-out;
}

@keyframes drive-row-pulse {
  0% {
    background: color-mix(in srgb, var(--color-accent) 20%, transparent);
  }

  100% {
    background: transparent;
  }
}

/* Drag handle removed — the row itself stays draggable (drag-to-file), so the
   6-dot grip was visual noise. Hidden everywhere; the markup is kept so the
   drive-select gutter alignment is undisturbed. */
.drive-list-row__grip {
  display: none;
}

.drive-list-row.is-dragging {
  cursor: grabbing;
}

/* Multi-file drag image (drive-select.js builds it on dragstart, off-screen, and
   removes it next tick). A compact accent badge naming the file count, so a
   multi-row move reads as one bundle rather than a single-row ghost. */
.drive-drag-ghost {
  position: absolute;
  top: -1000px;
  left: -1000px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-accent);
  color: var(--color-surface);
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  box-shadow: var(--shadow-floating);
  white-space: nowrap;
  pointer-events: none;
}

/* Bulk selection (#746) — checkbox per row + the selected state. Distinct from
   .drive-selected (the inspector's single active row). The checkbox stays out of
   the way until it's wanted: hidden until the row is hovered, the row is checked,
   or a selection is already in progress (.has-selection on the contents pane, set
   by drive-select.js). visibility keeps its slot so nothing shifts on reveal. */
.drive-list-row__select {
  display: flex;
  align-items: center;
  /* Larger hit area without moving the box (#746 round 2). */
  padding: var(--space-1);
  margin: calc(-1 * var(--space-1));
  visibility: visible;
  opacity: 1;
  transition: opacity var(--transition-fast);
}

.drive-list-row:hover .drive-list-row__select,
.drive-list-row.is-selected .drive-list-row__select,
.has-selection .drive-list-row__select {
  visibility: visible;
  opacity: 1;
}

/* Row checkbox (#746 round 2): mirrors the design-system .checkbox glyph
   (accent fill + white polyline) instead of the OS-native box. The row uses a
   bare <input class="drive-select-box">, not the .checkbox label wrapper, so
   the glyph is copied here — keep the glyph in sync with design-system.css
   .checkbox. Corners use --radius-xs (the .checkbox.square variant), not the
   round default. */
.drive-select-box {
  appearance: none;
  -webkit-appearance: none;
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  flex-shrink: 0;
  border-radius: var(--radius-xs);
  border: 1.5px solid var(--color-border);
  background: var(--color-surface);
  cursor: pointer;
  transition:
    border-color var(--transition-normal) ease,
    box-shadow var(--transition-normal) ease,
    background-color var(--transition-normal) ease;
}

.drive-select-box:hover:not(:disabled) {
  border-color: var(--color-accent);
}

.drive-select-box:checked {
  border-color: var(--color-accent);
  border-width: 2px;
  background-color: var(--color-accent);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='3.5 8.5 6.5 11.5 12.5 5'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 0.75rem 0.75rem;
}

.drive-select-box:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.drive-list-row.is-selected {
  background: color-mix(in srgb, var(--color-accent) 16%, var(--color-surface));
}

/* Row action — the right-edge button revealed on row hover and on the selected
   row (mirrors the checkbox reveal). The team's is the row menu (kebab), which
   opens the right-click menu (drive-context-menu.js); the couple's is retract,
   the one destructive one (--danger). */
.drive-list-row__action {
  flex: none;
  /* No margin-left:auto — the meta line now grows to fill the row (its container
     queries need that width), and an auto margin would swallow the free space
     before the flex-grow could, pinning the meta to content width. The meta's
     grow pushes the button to the row's right edge on its own. */
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--control-h);
  height: var(--control-h);
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-muted);
  cursor: pointer;
  visibility: hidden;
  opacity: 0;
  transition:
    opacity var(--transition-fast),
    background var(--transition-fast),
    color var(--transition-fast);
}

.drive-list-row:hover .drive-list-row__action,
.drive-list-row.is-selected .drive-list-row__action,
.drive-list-row__action[aria-expanded="true"] {
  visibility: visible;
  opacity: 1;
}

.drive-list-row__action:hover,
.drive-list-row__action[aria-expanded="true"] {
  background: var(--color-hover);
  color: var(--color-text);
}

.drive-list-row__action--danger:hover {
  background: color-mix(in srgb, var(--color-danger) 12%, transparent);
  color: var(--color-danger);
}

.drive-list-row__action:focus-visible {
  visibility: visible;
  opacity: 1;
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Page-less raw file (no extracted pages): the grip is a gutter placeholder only
   — it reserves width so the thumbnail stays column-aligned, but never reveals
   (nothing to drag — no pages to file). The checkbox is NOT hidden: a stranded /
   failed raw can be multi-selected and deleted (#800), so it reveals on
   hover/selection like any other row. */
.drive-list-row--pageless .drive-list-row__grip {
  visibility: hidden;
}

.drive-list-row__thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 30px;
  height: 30px;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-muted);
}

.drive-list-row__thumb img {
  min-width: 100%;
  min-height: 100%;
  object-fit: cover;
}

/* A folder's file rows (#1253): a Finder disclosure triangle before the
   thumbnail opens the file up to the pages filed here. Every row in a folder
   carries the column — an empty spacer where there is nothing to open — so the
   thumbnails stay aligned. Same caret as the nav's .sb-row__caret. */
.drive-list-row__disclosure {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-5);
  height: var(--space-5);
  padding: 0;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
  transition:
    transform var(--transition-normal) ease,
    background var(--transition-fast),
    color var(--transition-fast);
}

.drive-list-row__disclosure svg {
  width: var(--space-4);
  height: var(--space-4);
}

.drive-list-row__disclosure:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.drive-list-row__disclosure:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.drive-list-row__disclosure[aria-expanded="true"] {
  transform: rotate(90deg);
}

.drive-list-row__disclosure--spacer {
  cursor: default;
}

.drive-file-group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* The opened pages sit one disclosure column + gap further in, so their
   thumbnails line up under the file's name. */
.drive-file-group__pages {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-left: calc(var(--space-5) + var(--space-3));
}

.drive-file-group__pages[hidden] {
  display: none;
}

/* Filename-only meta: the list keeps just the name; every other fact lives in
   the preview pane, which follows the selection. */
.drive-list-row__meta {
  /* Fill the row width left of the trash affordance. */
  flex: 1 1 0%;
  display: flex;
  align-items: baseline;
  min-width: 0;
}

.drive-list-row__name {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  overflow: hidden;
  white-space: nowrap;
}

/* A raw whose decode gave up (#1129): the row's one inline status, in the
   danger tone, beside the name — and its thumb glyph in the same tone. */
.drive-list-row__failed {
  flex: 0 0 auto;
  margin-left: var(--space-3);
  font-size: var(--font-xs);
  color: var(--color-danger);
  white-space: nowrap;
}

.drive-list-row--failed .drive-list-row__thumb {
  color: var(--color-danger);
}

/* Finder-style middle truncation: the head yields with an ellipsis while the
   tail (extension + a little context, split in drive_rows.html) never shrinks,
   so the dots land mid-name and the filename's end stays visible. */
.drive-list-row__name-head {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.drive-list-row__name-tail {
  flex: none;
}

/* A split can fall on a space ("Passport - Elena.pdf" → "Passport" + " - Elena.pdf"),
   and a flex item drops leading/trailing whitespace under `nowrap` — the row
   read "Passport- Elena.pdf". `pre` keeps the space and still never wraps. */
.drive-list-row__name-head,
.drive-list-row__name-tail {
  white-space: pre;
}

/* Inline rename (#1282): the name turns into a field in place, Finder-style,
   keeping the row's own type size so the text does not jump. A file's
   extension sits beside the field, fixed — Rename never changes it. */
/* The name hides while the field stands in for it; `display: flex` above
   would otherwise outrank the `hidden` attribute. */
.drive-list-row__name[hidden] {
  display: none;
}

.drive-list-row__rename.form-control {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--space-1) var(--space-2);
  font-size: inherit;
  line-height: inherit;
}

.drive-list-row__rename-ext {
  flex: none;
  margin-left: var(--space-1);
  color: var(--color-muted);
}

/* Tone dot on the status fragment of the sub line — the voice of
   .tag--with-dot without the pill. */
.drive-status-dot {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.drive-status-dot::before {
  content: "";
  width: 6px; /* matches .tag--with-dot::before */
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.drive-status-dot[data-tone="success"] {
  color: var(--color-success);
}

.drive-status-dot[data-tone="warning"] {
  color: var(--color-warning-text);
}

.drive-status-dot[data-tone="danger"] {
  color: var(--color-danger);
}

.drive-status-dot[data-tone="info"] {
  color: var(--color-info);
}

.drive-status-dot[data-tone="muted"] {
  color: var(--color-muted);
}

/* Missing/failed preview never shows a broken image: striped placeholder. */
.drive-thumb-placeholder,
.drive-list-row__thumb--placeholder {
  background: repeating-linear-gradient(
    45deg,
    var(--color-surface-dim) 0 6px,
    var(--color-surface-muted) 6px 12px
  ); /* 6px stripe pitch — decorative */
  color: var(--color-muted);
}

/* Still-decoding upload — animated shimmer thumb (server `processing` signal on
   the Drive row, mirrors the Submissions skeleton). Reuses loading-shimmer. */
.drive-list-row__thumb--processing {
  background: linear-gradient(
    90deg,
    var(--color-surface-muted) 0%,
    var(--color-border-subtle) 50%,
    var(--color-surface-muted) 100%
  );
  background-size: 200% 100%;
  animation: loading-shimmer 1.2s ease-in-out infinite;
}

/* ── By-requirement folder browser (#740) — column 1 folders + breadcrumb ── */
.drive-folders {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.drive-folder-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  /* Match the file rows so the two columns line up (shared token on the
     workspace). border-box so the group-separator border (below) is drawn inside
     this fixed height — it adds no height, keeping every row on the same gridline
     as the file rows opposite. */
  box-sizing: border-box;
  height: var(--drive-row-min-h);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--transition-fast);
}

/* Group separator — a line at the top of the first row in each owner / section
   group. A border (inside the border-box height), not a spacer, so it never
   pushes the rows out of alignment with the file column. */
.drive-folder-row--sep {
  /* Owner-group separators dropped — kept transparent (not removed) so the
     group-start rows stay the same height as the rest and nothing shifts. */
  border-top: 1px solid transparent;
}

/* Don't draw the separator line over the filled active pill — keep the border
   (transparent) so the row height, and thus the alignment, is unchanged. */
.drive-folder-row--sep.is-active {
  border-top-color: transparent;
}

/* Rail section label (Requested · Case folders · Final) — one row tall so the
   rail stays on the file column's gridline; the label sits at the bottom, so
   the space above reads as the gap between sections. */
.drive-folder-band {
  display: flex;
  align-items: flex-end;
  box-sizing: border-box;
  height: var(--drive-row-min-h);
  padding: 0 var(--space-3) var(--space-1);
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
}

.drive-folder-row:hover {
  background: var(--color-hover);
}

/* Folder tree (#745): indent by nesting depth, drag affordances. */
.drive-folder-row {
  position: relative;
  padding-left: calc(var(--space-3) + var(--tree-depth, 0) * var(--space-4));
}

.drive-folder-row.is-dragging {
  opacity: var(--opacity-muted, 0.5);
}

/* Reorder insertion line above / below the hovered row (pseudo-element so it
   adds no layout shift during the drag). */
.drive-folder-row.drive-drop-before::after,
.drive-folder-row.drive-drop-after::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--color-accent);
}

.drive-folder-row.drive-drop-before::after {
  top: 0;
}

.drive-folder-row.drive-drop-after::after {
  bottom: 0;
}

/* Nest target — drop into this folder. */
.drive-folder-row.drive-drop-nest {
  background: var(--color-accent-subtle, var(--color-surface-dim));
  outline: 2px solid var(--color-accent);
  outline-offset: calc(-1 * var(--space-1));
}

/* Active folder in the two-pane rail (#750) — the folder whose contents show.
   Solid accent-hover fill with white text so the open folder reads clearly. */
.drive-folder-row.is-active {
  background: var(--color-accent-hover);
}

.drive-folder-row.is-active .drive-folder-row__name,
.drive-folder-row.is-active .drive-folder-row__sub,
.drive-folder-row.is-active .drive-folder-row__icon,
.drive-folder-row.is-active .drive-folder-row__chev {
  color: var(--color-surface);
}

.drive-folder-row.is-active .drive-folder-row__count,
.drive-folder-row.is-active .drive-folder-row__count[data-zero] {
  color: var(--color-surface);
  font-weight: var(--weight-medium);
}

/* Page dragged from the contents pane onto this container (#750) — file here. */
.drive-folder-row.drive-drop-into {
  background: var(--color-accent-subtle, var(--color-surface-dim));
  outline: 2px solid var(--color-accent);
  outline-offset: calc(-1 * var(--space-1));
}

/* A page row draggable to a rail container (#750). */
.drive-list-row[draggable="true"] {
  cursor: grab;
}

.drive-list-row.is-dragging {
  opacity: var(--opacity-muted, 0.5);
}

.drive-folder-row__icon {
  display: flex;
  flex: none;
  color: var(--color-muted);
}

/* The doc-type glyphs reused from the nav rail carry no width/height attrs
   (they size from CSS), so pin the folder-row icon box. */
.drive-folder-row__icon svg {
  width: 18px;
  height: 18px;
}

.drive-folder-row__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}

.drive-folder-row__name {
  overflow: hidden;
  font-size: var(--font-sm);
  line-height: 1.2;
  font-weight: var(--weight-medium);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.drive-folder-row__sub {
  font-size: var(--font-xs);
  line-height: 1.2;
  color: var(--color-muted);
}

/* A requirement's status on its rail row (#1368), after whose it is. One line:
   a long status ellipsises rather than growing the row off the gridline. */
.drive-folder-row__sub--status {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.drive-folder-row__status[data-tone="success"] {
  color: var(--color-success);
}

.drive-folder-row__status[data-tone="danger"] {
  color: var(--color-danger);
}

.drive-folder-row__status[data-tone="missing"] {
  color: var(--color-warning-text);
}

.drive-folder-row__status[data-tone="pending"] {
  color: var(--color-info);
}

.drive-folder-row.is-active .drive-folder-row__status {
  color: var(--color-surface);
}

.drive-folder-row__count {
  flex: none;
  font-size: var(--font-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
}

/* Zero counts dim so filled folders pop. */
.drive-folder-row__count[data-zero] {
  color: var(--color-border);
}

/* Chevron shows on hover/active only — resting rows drop the arrow noise. */
.drive-folder-row__chev {
  flex: none;
  color: var(--color-muted);
  opacity: 0;
  transition: opacity var(--transition-fast);
}

.drive-folder-row:hover .drive-folder-row__chev,
.drive-folder-row.is-active .drive-folder-row__chev {
  opacity: 1;
}

.drive-breadcrumb {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--color-text);
  text-decoration: none;
}

.drive-breadcrumb:hover {
  color: var(--color-accent);
}

/* ── Drive mobile ≤640px — header strip wraps, rail flattens to a folder-chip
   strip above the list, inspector stacks below as a full-width section. */
@media (max-width: 640px) {
  .drive-workspace--tree {
    flex-direction: column;
  }

  .drive-tree-rail {
    flex: none;
    border-right: 0;
    border-bottom: 1px solid var(--color-border);
  }

  .drive-tree-rail .drive-folders {
    flex-direction: row;
    overflow-x: auto;
    gap: var(--space-2);
    padding: var(--space-3);
  }

  .drive-folder-row {
    flex: none;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    background: var(--color-surface);
  }

  .drive-folder-row.is-active {
    border-color: var(--color-accent);
  }

  /* A requirement chip keeps its status line (#1418): on a phone the chip
     strip is the only place an opened document's status shows. */
  .drive-folder-row__sub:not(.drive-folder-row__sub--status),
  .drive-folder-row__chev {
    display: none;
  }

  /* Horizontal rail on touch — the group-start top border reads wrong sideways. */
  .drive-folder-row--sep {
    border-top: 0;
  }

  /* A section label has no place in a chip strip. */
  .drive-folder-band {
    display: none;
  }

  .drive-workspace--tree .drive-main {
    border-right: 0;
  }

  .drive-list-row {
    min-height: 44px; /* touch target floor */
  }

  /* Selection checkboxes always visible on touch. */
  .drive-list-row__select {
    visibility: visible;
    opacity: 1;
    padding: var(--space-3);
    margin: calc(-1 * var(--space-3));
  }

  .drive-inspector {
    flex: none;
    border-top: 1px solid var(--color-border);
  }

  .drive-inspector__preview {
    aspect-ratio: 4 / 3;
  }
}

.drive-breadcrumb__chev {
  color: var(--color-muted);
}

.page-card-slot {
  position: absolute;
  top: 0.35rem;
  left: 0.35rem;
  font-size: 0.62rem;
  font-weight: var(--weight-bold);
  min-width: 18px;
  height: 18px;
  padding: 0 0.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.1rem;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-surface);
}

.page-card-slot small {
  font-size: 0.55rem;
  font-weight: var(--weight-medium);
  opacity: 0.85;
}

/* ── Original-quality addon: per-page upgrade affordance ── */

.page-card-quality-upgrade-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  margin-left: auto;
  padding: 0.1rem 0.3rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: 0.6rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: background var(--transition-normal) ease-out,
    color var(--transition-normal) ease-out;
}

.tool-ws-grid--select-mode .page-card-quality-upgrade-btn {
  display: none;
}

.page-card-quality-upgrade-btn:hover {
  background: var(--color-accent);
  color: var(--color-surface);
  border-color: var(--color-accent);
}

.page-card-quality-current {
  display: inline-flex;
  align-items: center;
  margin-left: auto;
  padding: 0.1rem 0.35rem;
  border-radius: var(--radius-xs);
  background: var(--color-accent);
  color: var(--color-surface);
  font-size: 0.6rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.04em;
}

.page-modal-raw-actions {
  margin-bottom: 1rem;
  padding: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface-dim);
}

.page-modal-raw-actions-title {
  margin: 0 0 0.25rem;
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
}

.page-modal-raw-actions-subtitle {
  margin: 0 0 0.6rem;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* ── Multi-select mode ── */

.page-card-select {
  display: none;
  position: absolute;
  top: 0.35rem;
  right: 0.35rem;
  z-index: 3;
  cursor: pointer;
}

.tool-ws-grid--select-mode .page-card-select {
  display: block;
}

.page-card-checkbox {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: var(--radius-xs);
  border: 2px solid rgba(255, 255, 255, 0.8);
  background: rgba(0, 0, 0, 0.3);
}

/* Checked = the design-system checkmark (same SVG as .checkbox /
   .drive-select-box), not a font-dependent "✓" glyph. */
.ws-page--selected .page-card-checkbox {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='3.5 8.5 6.5 11.5 12.5 5'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 0.75rem 0.75rem;
}

.tool-ws-grid--select-mode .page-card {
  cursor: pointer;
  user-select: none;
}

/* The selected CLASS lands on every representation of the page (#873) — state
   is a property of the page, not of one view. Whether a view PAINTS it is a
   separate question, gated on the same capability the JS gates on: only a
   select scope shows the ring. The reader rail and reading stage carry the
   class but stay unpainted — they are not selectable surfaces, and a 2px
   accent outline around the full-size reading pane would be nonsense. */
[data-ws-select-scope] .ws-page--selected {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

/* ── Unified mode-less selection (#505) ──────────────────────────────────────
   No select mode: the checkbox shows on hover + when selected; the selection
   reads as a tinted fill + checkbox, and the ACTIVE page (the primary member,
   the one the panel shows) carries the accent ring. */
.tool-ws-unified .page-card {
  cursor: pointer;
}

.tool-ws-unified .page-card-select {
  display: block;
  opacity: 0;
  /* Top-left, mirroring the expand affordance top-right — the legacy
     select-mode position (top-right) collides with .page-card-expand,
     which only exists on the unified shell. */
  top: var(--space-2);
  right: auto;
  left: var(--space-2);
}

.tool-ws-unified .page-card:hover .page-card-select,
.tool-ws-unified .ws-page--selected .page-card-select {
  opacity: 1;
}

/* Selected = a tinted fill overlay (not the ring); the ring is reserved for the
   active page. The overlay is pointer-transparent so clicks reach the card.
   Scoped to actual `.page-card` tiles: `ws-page--selected` is the contract's
   one selection class (#873) and lands on timeline rows too, where a
   full-inset overlay would paint the whole row blue (#571 — given a row style
   below instead). */
.tool-ws-unified .page-card.ws-page--selected {
  outline: none;
}

.tool-ws-unified .page-card.ws-page--selected::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}

/* Selected timeline row — the card overlay above is scoped to `.page-card`, so
   rows fall through to the generic `.ws-page--selected` accent outline (a
   clean inset border, no fill). A faint tint reinforces it without a take-over
   overlay (#571). */
.tool-ws-unified .tool-ws-timeline-row.ws-page--selected {
  background: color-mix(in srgb, var(--color-accent) 8%, transparent);
}

/* Active ring = the card's own 1px border in accent, nothing more.
   Not `outline` (an inset outline sits 2px inside the border and the sliver
   between them reads as a doubled "extra border" over the white footer) and
   no extra shadow ring (reads as a double border on top/right/bottom). */
.tool-ws-unified .page-card--active {
  border-color: var(--color-accent);
}

/* Grid roving focus (#872): a clear keyboard-focus ring on the card as the
   arrow keys move between cards, matching the platform focus-visible
   convention. The --active border marks the same card as the selected page. */
.tool-ws-unified .page-card:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ── Upload drop zone ── */

.tool-ws-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-5);
  border: 2px dashed var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-muted);
  font-size: var(--font-sm);
  cursor: pointer;
  transition: border-color var(--transition-normal), background var(--transition-normal);
}

.tool-ws-dropzone:hover {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 4%, transparent);
}

.tool-ws-dropzone-hint {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* ── Transfers tray (#779) ──────────────────────────────────────────────────
   Persistent, user-scoped I/O tray docked at the viewport corner — reflects
   every upload/export transfer live across navigation. Shares the upload-queue
   row visual language (name / status / 3px accent bar) so the in-workspace
   dropzone popup and this tray read as one family; the two converge in #613.
   Hidden until it has rows (transfers-tray.js toggles --visible on the body
   render's data-count). Responsive: corner card on desktop, bottom sheet on
   phones. */
.transfers-tray {
  position: fixed;
  right: var(--space-5);
  bottom: var(--space-5);
  z-index: var(--z-dropdown);
  width: 22rem;
  max-width: calc(100vw - var(--space-6));
  display: none;
}

.transfers-tray--visible {
  display: block;
}

.transfers-tray__panel {
  display: flex;
  flex-direction: column;
  max-height: min(60vh, 30rem);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-floating);
  overflow: hidden;
}

.transfers-tray__header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface-dim);
}

.transfers-tray__title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.transfers-tray__spin {
  color: var(--color-accent);
  width: 0.75rem;
  height: 0.75rem;
}

.transfers-tray__count {
  min-width: 1.25rem;
  padding: 0 var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  line-height: 1.5;
  text-align: center;
  color: var(--color-subtle);
  background: var(--color-surface-muted);
}

.transfers-tray__toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  background: transparent;
  cursor: pointer;
  transition: background-color var(--transition-normal) ease, color var(--transition-normal) ease;
}

.transfers-tray__toggle:hover {
  color: var(--color-text);
  background: var(--color-hover);
}

.transfers-tray__toggle svg {
  width: 1rem;
  height: 1rem;
  transition: transform var(--transition-slow) ease;
}

/* Collapsed → chevron points up, list hides to just the header bar. */
.transfers-tray--collapsed .transfers-tray__toggle svg {
  transform: rotate(180deg);
}

/* Dismiss (×) — mirrors the collapse toggle but sits at the far right (the
   toggle's margin-left:auto already pushes the pair over). Hides the tray until
   newer activity; see transfers-tray.js dismiss watermark. */
.transfers-tray__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  background: transparent;
  cursor: pointer;
}

.transfers-tray__close:hover {
  color: var(--color-text);
  background: var(--color-hover);
}

.transfers-tray__close svg {
  width: 0.85rem;
  height: 0.85rem;
}

.transfers-tray--collapsed .transfers-tray__list {
  display: none;
}

.transfers-tray__list {
  list-style: none;
  margin: 0;
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  overflow-y: auto;
}

.transfers-tray__row {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--space-3);
  align-items: start;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}

.transfers-tray__row--failed {
  border-color: color-mix(in srgb, var(--color-danger) 40%, var(--color-border));
  background: color-mix(in srgb, var(--color-danger) 6%, var(--color-surface));
}

.transfers-tray__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-pill);
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
}

.transfers-tray__arrow svg {
  width: 1rem;
  height: 1rem;
}

.transfers-tray__row--ready .transfers-tray__arrow,
.transfers-tray__row--fetched .transfers-tray__arrow {
  color: var(--color-success);
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
}

.transfers-tray__row--failed .transfers-tray__arrow {
  color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 10%, transparent);
}

.transfers-tray__row-main {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.transfers-tray__row-top {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
}

.transfers-tray__row-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--color-text);
}

.transfers-tray__row-tag {
  flex: 0 0 auto;
  max-width: 9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Jump-to pill — links the row to where the transfer landed (a page in a
   requirement, the file in the Drive, …). Reads as a link, not a static tag. */
.transfers-tray__row-target {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  max-width: 9rem;
  overflow: hidden;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  color: var(--color-accent);
}

.transfers-tray__row-target:hover {
  background: var(--color-hover);
}

.transfers-tray__row-target svg {
  width: 0.75rem;
  height: 0.75rem;
  flex: 0 0 auto;
}

/* Cancel × on an in-progress row — quiet by default, danger on hover. */
.transfers-tray__cancel {
  flex: 0 0 auto;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  background: transparent;
  cursor: pointer;
  transition: color var(--transition-normal) ease, background-color var(--transition-normal) ease;
}

.transfers-tray__cancel:hover {
  color: var(--color-danger);
  background: var(--color-hover);
}

.transfers-tray__cancel svg {
  width: 0.85rem;
  height: 0.85rem;
}

/* Status line — the state on the left, the timestamp pushed to the right. */
.transfers-tray__row-status {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
}

/* Pills line — the Drive (case) + jump-to (target) tags; wraps when tight. */
.transfers-tray__row-pills {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.transfers-tray__state {
  flex: 0 0 auto;
  color: var(--color-accent);
  font-weight: var(--weight-medium);
}

/* When the transfer started — pushed to the right of the meta line, muted, and
   never wrapping. Inherits --font-xs from .transfers-tray__row-meta. A subtle
   button-like hover swaps the simple relative label for the full date/time in
   place (no floating tooltip — the tray scroll-clips its rows). */
.transfers-tray__when {
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--color-muted);
  white-space: nowrap;
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast), color var(--transition-fast);
}

.transfers-tray__when:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.transfers-tray__when-abs {
  display: none;
}

.transfers-tray__when:hover .transfers-tray__when-rel {
  display: none;
}

.transfers-tray__when:hover .transfers-tray__when-abs {
  display: inline;
}

/* Export "out" download affordance — a ready artifact offers Download; a fetched
   one keeps a quiet re-download link. */
.transfers-tray__download {
  padding: var(--space-1) var(--space-3);
  font-size: var(--font-xs);
}

.transfers-tray__redownload {
  color: var(--color-muted);
  text-decoration: underline;
}

.transfers-tray__redownload:hover {
  color: var(--color-accent);
}

/* "Try again" on a failed produced export (#967) — same quiet inline weight as
   the re-download link, so a failed row reads as recoverable, not as a button
   competing with the Download primary on a ready row. */
.transfers-tray__retry {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--color-muted);
  text-decoration: underline;
  cursor: pointer;
}

.transfers-tray__retry:hover {
  color: var(--color-accent);
}

.transfers-tray__retry:disabled {
  cursor: default;
  opacity: 0.5;
}

/* A follow-up line under an upload (core.transfers.followups): what a tool
   did with it after it landed — the passport reader's ✦ "AI · in line …",
   "reading page numbers · 4 of 27", "40 suggestions to review". The icon
   breathes while it waits or works (ai-breathe); the line links to where it
   happened. */
.transfers-tray__followup {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.transfers-tray__followup-icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--color-accent);
}

.transfers-tray__followup-icon--moving {
  animation: ai-breathe 1.6s ease-in-out infinite;
}

.transfers-tray__followup--failed .transfers-tray__followup-icon {
  color: var(--color-danger);
}

.transfers-tray__followup-label {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: inherit;
  text-decoration: none;
}

a.transfers-tray__followup-label:hover {
  color: var(--color-accent);
  text-decoration: underline;
}

@media (prefers-reduced-motion: reduce) {
  .transfers-tray__followup-icon--moving {
    animation: none;
  }
}

.transfers-tray__bar {
  display: block;
  height: 3px;
  background: var(--color-hover);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.transfers-tray__bar-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--color-accent);
  transition: width var(--transition-normal) linear;
}

/* Footer escape hatch to the full cross-case history (#781). */
.transfers-tray__all {
  display: block;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border);
  background: var(--color-surface-dim);
  color: var(--color-muted);
  font-size: 0.82rem;
  text-align: center;
  text-decoration: none;
}

.transfers-tray__all:hover {
  color: var(--color-accent);
}

/* Phone: dock to the bottom edge as a full-width sheet (rounded top only) —
   the shared responsive form the customer slim (Slice 6) renders as-is. */
@media (max-width: 640px) {
  .transfers-tray {
    right: 0;
    bottom: 0;
    left: 0;
    width: auto;
    max-width: none;
  }

  .transfers-tray__panel {
    max-height: 65vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-bottom: none;
  }
}

/* ── Background work (#781, #1359) ───────────────────────────────────────────
   Full-page, cross-case listing of everything the operator set off — the
   transfers they started and the requests they made of a worker — the
   browsable superset behind the live tray. Reuses the .tag status language
   and .data-table; adds a kind glyph, a relative timestamp, and a pager. */
.transfers-history__card {
  padding: 0;
  overflow: hidden;
}

.transfers-history__empty {
  padding: var(--space-8);
  text-align: center;
}

.transfers-history__dir {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
  color: var(--color-muted);
}

.transfers-history__dir-icon {
  display: inline-flex;
  width: 1rem;
  height: 1rem;
}

.transfers-history__dir-icon svg {
  width: 100%;
  height: 100%;
}

.transfers-history__dir--out,
.transfers-history__dir--request {
  color: var(--color-accent);
}

.transfers-history__label {
  font-weight: 500;
}

/* An upload's follow-up line (the tray's, core.transfers.followups) under its
   label on the Background work page — what happened after it landed. */
.transfers-history__followup {
  margin-top: var(--space-1);
}

.transfers-history__when {
  color: var(--color-muted);
  white-space: nowrap;
}

/* The Case tag links to the case; the Location cell is the jump-to pill (same
   as the tray widget's target pill) — the place the transfer landed, arrowed. */
.transfers-history__crumb-tag {
  text-decoration: none;
}

.transfers-history__location {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-accent);
  text-decoration: none;
  white-space: nowrap;
}

.transfers-history__location:hover {
  text-decoration: underline;
}

.transfers-history__location svg {
  width: 0.75rem;
  height: 0.75rem;
  flex: 0 0 auto;
}

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

.transfers-history__pager-info {
  color: var(--color-muted);
  font-size: 0.9rem;
}

.transfers-history__pager-nav {
  display: flex;
  gap: var(--space-2);
}

/* Floating "Excluded" marker — used on the reader stage's image-area and the
   review working image. Sits at top-center as a quiet chip, doesn't take
   layout space, doesn't shift the image between pages. */
.page-modal-image-area::after,
.review-image-page::after {
  content: "EXCLUDED";
  position: absolute;
  top: 0.75rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  background: rgba(20, 22, 35, 0.7);
  color: rgba(255, 255, 255, 0.95);
  padding: 0.3rem 0.85rem;
  border-radius: var(--radius-pill);
  font-size: 0.7rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.16em;
  pointer-events: none;
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 220ms ease;
}

.tool-ws-reader-stage.ws-page--excluded::after,
.review-image-page--excluded::after {
  opacity: 1;
}

/* Dim the working image itself so the chip lands against muted pixels.
   filter (not opacity) so it doesn't fight the JS-set inline opacity
   the image fade-in uses on load.

   `filter` is last-wins, not additive, so the excluded dimming and the #804
   per-page adjustments MUST be written as one declaration each — an inline
   `style="filter: …"` for adjustments would beat this rule and silently drop
   the excluded treatment. Adjustments therefore arrive as the --page-adjust
   custom property (empty = `none`) and every filter rule composes it first. */
.review-image-img,
.page-modal-img,
.page-card-thumb img,
.tool-ws-reader-thumb img {
  filter: var(--page-adjust, none);
  transition: filter 220ms ease;
}

/* The fallback here is EMPTY, not `none` — `filter: none grayscale(0.5)` is an
   invalid filter list, so the whole declaration would be dropped and an
   excluded page with no adjustment would lose its dimming entirely. */
.review-image-page--excluded .review-image-img,
.tool-ws-reader-stage.ws-page--excluded .page-modal-img {
  filter: var(--page-adjust, ) grayscale(0.5) brightness(0.7);
}

/* The transition above exists for the exclude/include fade and must stay on
   the base rule so it animates in BOTH directions. But it would also animate
   --page-adjust: a slider drag would smear behind the pointer, and a bulk
   apply would run N simultaneous filter animations. JS marks the element
   while an adjustment is being applied, and only that is instant (#804). */
.is-adjusting {
  transition: none !important;
}

/* The pre-minted white-balance filter table (#1075) — an <svg> of <filter>
   elements that --page-adjust references by id. It renders nothing itself;
   zero-size and out of flow so it can't reserve a line in the shell. */
.ws-filter-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ── File drop indicators on review images ── */

.review-image-page[data-file-drop="before"] {
  box-shadow: 0 -4px 0 0 var(--color-accent);
}

.review-image-page[data-file-drop="after"] {
  box-shadow: 0 4px 0 0 var(--color-accent);
}

/* ── Highlight flash for newly inserted/replaced pages ── */

@keyframes fade-in-delayed {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

/* ── Workspace loading skeleton ── */

.workspace-loading-placeholder {
  padding: 1rem 1.5rem;
  opacity: 0;
  animation: fade-in-delayed 0.3s ease-out var(--transition-slow) forwards;
}

.workspace-loading-header {
  height: 2.25rem;
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-4);
  animation: pulse-load 3s ease-in-out infinite;
}

.workspace-loading-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-5);
}

.workspace-loading-card {
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-sm);
  animation: pulse-load 3s ease-in-out infinite;
}

@keyframes page-overlay-fade {
  0% {
    opacity: 1;
  }

  60% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

.review-image-page--highlight {
  position: relative;
}

.review-image-page--highlight::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: var(--z-sticky);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  border: 3px solid var(--color-accent);
  border-radius: var(--radius-sm);
  pointer-events: none;
  animation: page-overlay-fade 2.5s ease-out forwards;
}

/* ── Just-uploaded entrance ── */
/* A new page announces itself with a soft fade+settle of the card and a CALM
   placeholder: the pulse shimmer under an arriving image read as a white
   veil (2026-06-13). The image itself fades in via the loader, on a slower
   curve for just-added cards (image-loader.js / page_card.html onload). */
.page-card[data-just-added] {
  animation: page-enter 0.35s ease-out both;
}

.page-card[data-just-added] .page-card-thumb {
  animation: none;
  background: var(--color-surface-dim);
}

.page-card--highlight {
  position: relative;
}

.page-card--highlight::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: var(--z-sticky);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  border: 3px solid var(--color-accent);
  border-radius: var(--radius-sm);
  pointer-events: none;
  animation: page-overlay-fade 2.5s ease-out forwards;
}

/* ── Page enter animation ── */

@keyframes page-enter {
  0% {
    opacity: 0;
    transform: scale(0.9);
  }

  100% {
    opacity: 1;
    transform: scale(1);
  }
}

.review-image-page--entering {
  animation: page-enter 0.35s ease-out;
}

.page-card--entering {
  animation: page-enter 0.3s ease-out;
}

/* ── Image loading shimmer ── */

@keyframes pulse-load {

  0%,
  100% {
    background: var(--color-surface-dim);
  }

  50% {
    background: var(--color-border);
  }
}

.page-card-thumb {
  animation: pulse-load 3s ease-in-out infinite;
}

.page-card-thumb:has(img[style*="opacity: 1"]) {
  animation: none;
  background: var(--color-surface-dim);
}

/* Missing pages don't need pulse */
.page-card--missing .page-card-thumb {
  animation: none;
  background: var(--color-bg);
}

/* Load gave up (#969) — Platform.loadQueue marks the <img> `data-load-failed`
   once its one bounded retry is spent. Stop the pulse: an image that will never
   arrive kept pulsing as though it still might, which is exactly what a stuck
   thumb looked like to the operator. */
.page-card-thumb:has(img[data-load-failed]) {
  animation: none;
  background: var(--color-surface-dim);
}

/* The way out of that state, injected by tool-workspace/image-loader.js. Mirrors
   .page-card-missing-icon's layout so a failed thumb reads like the card's other
   placeholder states; sits under the expand button + chips by DOM order, so no
   z-index is needed. */
.page-card-retry {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: var(--space-2);
  border: none;
  background: none;
  color: var(--color-danger);
  font-family: inherit;
  font-size: var(--font-xs);
  text-align: center;
  cursor: pointer;
}

.page-card-retry:hover {
  text-decoration: underline;
}

/* ── Staggered load-in animation ── */

@keyframes page-load-in {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

/* Grid cards do NOT fade in. A CSS animation restarts every time the element goes
   display:none → block, and the grid is display:none at the reader stops — so a
   `page-load-in` on the card replayed the opacity fade on every card when you
   switched back to the grid (a white flash across all previews). The card images
   already carry inline opacity:1 from the image-promote, so the grid just appears;
   the pulse-load skeleton (below) still covers genuinely-loading thumbs. */

.review-image-page {
  animation: page-load-in 0.18s ease-out both, pulse-load 3s ease-in-out infinite;
}

.review-image-page:has(img[style*="opacity: 1"]) {
  animation: none;
  background: transparent;
}

/* ── Delete animation ── */

@keyframes page-shrink-out {
  0% {
    opacity: 1;
    transform: scale(1);
  }

  100% {
    opacity: 0;
    transform: scale(0.85);
  }
}

.review-image-page--deleting,
.page-card--deleting {
  animation: page-shrink-out 0.3s ease-in forwards;
  pointer-events: none;
}

/* ── Review view (three-column) ── */

.tool-ws-review {
  display: flex;
  gap: 0;
  overflow: hidden;
}

.review-images {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.review-image-page {
  position: relative;
  padding: 0.25rem 0.5rem;
  text-align: center;
}

.review-image-alts {
  position: absolute;
  top: 0.75rem;
  left: 1rem;
  font-size: 0.75rem;
  font-weight: var(--weight-bold);
  color: var(--color-surface);
  background: var(--color-accent);
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-lg);
  pointer-events: none;
}

.review-image-img {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  max-height: 85vh;
  object-fit: contain;
}

.review-panel {
  width: 320px;
  min-width: 320px;
  border-left: 1px solid var(--color-border-light, rgba(0, 0, 0, 0.06));
  padding: 0;
  overflow-y: auto;
}

.review-panel-inner {
  transition: opacity var(--transition-normal) ease;
}

.review-panel-inner .page-modal-controls {
  flex-wrap: nowrap;
  justify-content: flex-end;
  gap: var(--space-2);
}

.review-panel-title {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.review-panel-pos {
  font-weight: var(--weight-semibold);
  font-size: 0.85rem;
}

.review-panel-label {
  font-size: 0.8rem;
  color: var(--color-muted);
}

.review-panel-loader {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 120px;
  color: var(--color-muted);
  font-size: 0.82rem;
}

.review-panel-loader-spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: review-spin 0.6s linear infinite;
}

@keyframes review-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ── Page detail modal ── */

#page-modal-overlay {
  background: transparent;
}

.page-modal-dialog {
  width: 100vw;
  max-width: 100vw;
  max-height: 100vh;
  height: 100vh;
  border-radius: 0;
  animation: modal-enter var(--transition-normal) ease-out;
}

@keyframes modal-enter {
  0% {
    opacity: 0;
    transform: scale(0.98);
  }

  100% {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes modal-exit {
  0% {
    opacity: 1;
    transform: scale(1);
  }

  100% {
    opacity: 0;
    transform: scale(0.98);
  }
}

.page-modal-dialog>#page-modal-container {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.page-modal {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.page-modal-controls {
  display: flex;
  align-items: center;
  padding: 0.5rem;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
  justify-content: space-evenly;
}

.page-modal-counter {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  color: var(--color-surface);
  background: rgba(0, 0, 0, 0.5);
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-xs);
  pointer-events: none;
}

.page-modal-body {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* ── Image viewer ── */

.page-modal-image-area {
  flex: 7;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  min-width: 0;
  min-height: 0;
  position: relative;
}

.page-modal-spread-labels {
  display: flex;
  justify-content: space-around;
  padding: 0.35rem 1rem;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-border);
}

.page-modal-image-wrap {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 1rem;
  position: relative;
  display: flex;
}

/* Sized by JS to the image's *visual* (post-rotate) dimensions so the
   wrap's scroll behaviour matches what the user sees, independent of
   how the <img> is rotated. margin: auto centers the rotator in the
   wrap when it fits, sticks to start when it overflows. */
.page-modal-image-rotator {
  position: relative;
  margin: auto;
  flex-shrink: 0;
}

.page-modal-image-area--zoom-cursor,
.page-modal-image-area--zoom-cursor * {
  cursor: zoom-in !important;
}

.page-modal-img {
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  transform-origin: center center;
  cursor: grab;
}

.page-modal-img.dragging {
  cursor: grabbing;
}

.page-modal-img--spread {
  max-width: 100%;
}

.page-modal-toolbar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: 0.5rem;
  border-top: 1px solid var(--color-border);
  flex-shrink: 0;
}

.page-modal-toolbar-sep {
  width: 1px;
  height: 1.25rem;
  background: var(--color-border);
  margin: 0 0.5rem;
}

.page-modal-missing {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: var(--color-muted);
  background: var(--color-bg);
  cursor: pointer;
  gap: var(--space-4);
  max-height: none;
  aspect-ratio: auto;
  border-radius: 0;
  border: none;
}

.page-modal-missing:hover {
  background: var(--color-bg);
}

/* ── Side panel ── */

.page-modal-side {
  flex: 3;
  display: flex;
  flex-direction: column;
  border-left: 1px solid var(--color-border);
  min-width: 280px;
  max-width: 400px;
}

.page-modal-page-controls {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  flex-shrink: 0;
}

.page-modal-control-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.page-modal-control-row .form-label--sm {
  font-size: 0.72rem;
  margin: 0;
  min-width: 2.5rem;
  color: var(--color-muted);
}

.page-modal-control-row .form-control--sm {
  font-size: 0.75rem;
  padding: 0.2rem 0.35rem;
  flex: 1;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background: var(--color-surface);
}

.page-modal-control-actions {
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
}

.page-modal-delete-btn {
  margin-left: auto;
  color: var(--color-danger);
}

.page-modal-delete-btn:hover {
  background: rgba(214, 59, 59, 0.08);
}

.page-modal-tabs {
  display: flex;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}

.page-modal-tab {
  flex: 1;
  padding: 0.6rem 0.5rem;
  font-size: 0.78rem;
  font-weight: var(--weight-medium);
  text-align: center;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  color: var(--color-muted);
  transition: color var(--transition-normal), border-color var(--transition-normal);
}

.page-modal-tab:hover {
  color: var(--color-text);
}

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

.page-modal-tab-content {
  flex: 1;
  overflow-y: auto;
  padding: 0.75rem;
}

/* ── Crop overlay ── */

.crop-active {
  border-color: var(--color-accent);
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}

.crop-overlay {
  display: none;
  position: absolute;
  inset: 0;
  cursor: crosshair;
  z-index: 5;
}

.crop-selection {
  position: absolute;
  border: 2px dashed var(--color-surface);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
  cursor: move;
}

.crop-selection-close {
  position: absolute;
  top: -12px;
  right: -12px;
  width: 24px;
  height: 24px;
  border: 0;
  border-radius: 50%;
  background: var(--color-danger);
  color: var(--color-surface);
  font-size: 18px;
  line-height: 1;
  font-family: inherit;
  cursor: pointer;
  padding: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}

.crop-selection-close:hover {
  filter: brightness(1.1);
}

.crop-handle {
  position: absolute;
  width: 12px;
  height: 12px;
  background: var(--color-surface);
  border: 1px solid var(--color-text);
  box-sizing: border-box;
  z-index: 1;
}

.crop-handle--nw {
  top: -6px;
  left: -6px;
  cursor: nw-resize;
}

.crop-handle--ne {
  top: -6px;
  right: -6px;
  cursor: ne-resize;
}

.crop-handle--sw {
  bottom: -6px;
  left: -6px;
  cursor: sw-resize;
}

.crop-handle--se {
  bottom: -6px;
  right: -6px;
  cursor: se-resize;
}

.crop-handle--n {
  top: -6px;
  left: 50%;
  margin-left: -6px;
  cursor: n-resize;
}

.crop-handle--s {
  bottom: -6px;
  left: 50%;
  margin-left: -6px;
  cursor: s-resize;
}

.crop-handle--e {
  right: -6px;
  top: 50%;
  margin-top: -6px;
  cursor: e-resize;
}

.crop-handle--w {
  left: -6px;
  top: 50%;
  margin-top: -6px;
  cursor: w-resize;
}

/* Hide the close button and handles until the selection has a meaningful
   size — saves a stray cluster hanging at (0, 0) before the first drag. */
.crop-selection[style*="width: 0"] .crop-selection-close,
.crop-selection[style*="width:0"] .crop-selection-close,
.crop-selection[style*="width: 0"] .crop-handle,
.crop-selection[style*="width:0"] .crop-handle {
  display: none;
}

/* Pre-init guard — when crop mode has never been entered the selection
   has no inline style at all, so the width:0 match above doesn't fire and
   the close button (top:-10, left:-10) leaks into the top-left of the
   image area. Hide all chrome until the selection has a dimensioned style. */
.crop-selection:not([style]) .crop-selection-close,
.crop-selection:not([style]) .crop-handle {
  display: none;
}

.crop-actions {
  position: absolute;
  bottom: 1rem;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  z-index: 6;
  background: var(--color-bg);
  padding: 0.5rem;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.crop-hint {
  font-size: var(--font-xs);
  color: var(--color-muted);
  padding-left: var(--space-2);
}

.crop-hint kbd {
  font-family: inherit;
  font-size: 0.9em;
  padding: 1px 4px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  background: var(--color-surface);
}

.crop-aspect-bar {
  position: absolute;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  z-index: 6;
  background: var(--color-bg);
  padding: 0.5rem;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.crop-aspect-label {
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.crop-aspect-segment {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.crop-aspect-option {
  border: 0;
  background: transparent;
  color: var(--color-text);
  padding: 4px 10px;
  font-size: var(--font-sm);
  cursor: pointer;
  font-family: inherit;
}

.crop-aspect-option+.crop-aspect-option {
  border-left: 1px solid var(--color-border);
}

.crop-aspect-option:hover {
  background: var(--color-surface);
}

.crop-aspect-option--active {
  background: var(--color-accent);
  color: var(--color-surface);
}

.crop-aspect-option--active:hover {
  background: var(--color-accent);
}

/* ── Quality checklist ── */

.quality-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding-top: 0.5rem;
}

.quality-section {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.quality-section-title {
  font-size: 0.78rem;
  font-weight: var(--weight-semibold);
  margin: 0 0 0.15rem;
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.quality-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  font-size: 0.82rem;
  cursor: pointer;
  line-height: 1.4;
}

.quality-check input[type="checkbox"] {
  margin-top: 0.15rem;
  flex-shrink: 0;
}

.quality-status-buttons {
  display: flex;
  gap: 0.35rem;
}

.quality-status-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.4rem 0.5rem;
  font-size: 0.78rem;
  font-weight: var(--weight-medium);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-normal), border-color var(--transition-normal);
}

.quality-status-btn input[type="radio"] {
  display: none;
}

.quality-status-btn--approved.quality-status-btn--active,
.quality-status-btn--approved:has(input:checked) {
  background: rgba(40, 167, 69, 0.12);
  border-color: var(--color-success);
  color: var(--color-success);
}

.quality-status-btn--needs_replacement.quality-status-btn--active,
.quality-status-btn--needs_replacement:has(input:checked) {
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}

.quality-status-btn--rejected.quality-status-btn--active,
.quality-status-btn--rejected:has(input:checked) {
  background: rgba(214, 59, 59, 0.12);
  border-color: var(--color-danger);
  color: var(--color-danger);
}

.quality-reason-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.4rem 0.5rem;
  font-size: 0.78rem;
  font-weight: var(--weight-medium);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-normal), border-color var(--transition-normal);
}

.quality-reason-btn input[type="checkbox"] {
  display: none;
}

.quality-reason-btn--active,
.quality-reason-btn:has(input:checked) {
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}

/* ── Alternatives ── */

.page-alternatives-intro {
  font-size: 0.82rem;
  color: var(--color-muted);
  margin: 0 0 0.5rem;
}

.page-alternatives-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.page-alt-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.page-alt-card--excluded {
  opacity: 0.5;
}

.page-alt-thumb {
  width: 60px;
  height: 80px;
  object-fit: cover;
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}

.page-alt-missing {
  width: 60px;
  height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f5f5f5;
  border-radius: var(--radius-xs);
  font-size: 0.72rem;
  color: var(--color-muted);
}

.page-alt-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.page-alt-date {
  font-size: 0.72rem;
  color: var(--color-muted);
}

.page-alt-thumb--clickable {
  cursor: pointer;
  transition: outline var(--transition-normal);
}

.page-alt-thumb--clickable:hover {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.page-alt-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex-shrink: 0;
}

.page-alt-card--viewing {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 6%, transparent);
}

.page-alt-card--current .page-alt-thumb--clickable {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.page-alt-card--viewing .page-alt-thumb--clickable,
.page-alt-card--current.page-alt-card--viewing .page-alt-thumb--clickable {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.page-alt-current-label {
  font-size: 0.68rem;
  font-weight: var(--weight-medium);
  color: var(--color-accent);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.page-alt-tier {
  align-self: flex-start;
  padding: 0.05rem 0.3rem;
  border-radius: var(--radius-xs);
  font-size: 0.62rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.04em;
}

.page-alt-tier--hq {
  background: var(--color-accent);
  color: var(--color-surface);
}

/* ── Page info ── */

.page-modal-info-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.35rem 0.75rem;
  font-size: 0.82rem;
  margin: 0;
}

.page-modal-info-list dt {
  font-weight: var(--weight-medium);
  color: var(--color-muted);
}

.page-modal-info-list dd {
  margin: 0;
}

/* ── Page timeline ── */

/* ── Timeline (shared: case overview + page-level) ── */

/* ── Feed rhythm ─────────────────────────────────────────────────────────── */
.timeline-entries {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.timeline-empty {
  color: var(--color-muted);
  text-align: center;
  padding: var(--space-8) 0;
  font-size: var(--font-xs);
}

/* ── Comment card ────────────────────────────────────────────────────────── */
.timeline-comment-row {
  position: relative;
}

/* Internal (default) comments read as grey "team note" bubbles — the
   couple never sees these. Shared comments (visibility=shared) flip to
   the plain white card so they read as outbound, client-visible. */
.timeline-comment {
  position: relative;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--color-surface-muted);
  border: 1px solid transparent;
  transition: border-color var(--transition-normal), box-shadow var(--transition-normal);
}

.timeline-comment-row:hover .timeline-comment,
.timeline-comment-row:focus-within .timeline-comment {
  border-color: var(--color-border);
}

.timeline-comment--shared {
  background: var(--color-surface);
  border-color: var(--color-border-subtle);
  box-shadow: var(--shadow-card);
}

.timeline-comment-row:hover .timeline-comment--shared,
.timeline-comment-row:focus-within .timeline-comment--shared {
  box-shadow: var(--shadow-card-hover);
}

/* Internal chat-row layout: avatar | content | time. The grey bubble
   lives on .comment-body, not the wrapper — so multiple bubbles can
   stack inside one column (root + replies) like a Front conversation. */
.timeline-comment-row--internal {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  column-gap: var(--space-3);
}

/* Row-level avatar is now redundant — the per-comment time divider above
   the row carries the avatar (image or initials) for non-grouped rows.
   Kept as ``display: none`` rather than removed from the template so the
   row markup stays shared across surfaces (overview, sidebar, modal). */
.timeline-comment-row--internal > .comment-avatar--row {
  display: none;
}

.timeline-comment--internal {
  background: transparent;
  border: none;
  box-shadow: none;
  padding: 0;
  min-width: 0;
}

.timeline-comment-row--internal:hover .timeline-comment--internal,
.timeline-comment-row--internal:focus-within .timeline-comment--internal {
  border-color: transparent;
  box-shadow: none;
}

.timeline-comment--internal .comment-header {
  padding-right: 0;
  margin-bottom: var(--space-1);
}

/* Bubble left-aligns under the divider's avatar (--space-4 hairline +
   --space-2 gap = 18px from the row's left edge), so the eye reads a clean
   column of avatar → bubble down the feed. Grouped bubbles inherit the
   same indent so they stack vertically with their non-grouped siblings. */
.timeline-comment--internal > .comment-body {
  background: color-mix(in srgb, var(--color-accent-hover) 14%, var(--color-surface));
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: var(--space-3) var(--space-4);
  margin-left: calc(var(--space-4) + var(--space-2));
  display: inline-block;
  max-width: 100%;
}

/* Right-column time is now redundant — the per-comment time divider above
   each non-grouped row carries the timestamp. Kept as `display: none` rather
   than removed from the template so the row markup stays shared across
   surfaces (overview, sidebar, modal). */
.timeline-comment-row--internal > .comment-row-time {
  display: none;
}

/* ── Author-grouped rows ─────────────────────────────────────────────────
   Slack-style burst grouping: a second (or later) consecutive root comment
   from the same author within 5 min drops its time divider above and stacks
   tight under the previous row. The first row in the burst still carries
   the divider with author + timestamp. Set via the
   ``timeline-comment-row--grouped`` modifier (see comment_thread.html).

   Negative margin-top cancels the feed's flex gap (--space-6) and adds a
   tight --space-1 back, so the bubble snugs up to its sibling without
   fighting the parent's gap. Per-row avatar and header are hidden globally
   (the divider owns that info), so the grouped modifier only needs to
   handle stacking. */
.timeline-comment-row--grouped {
  margin-top: calc(var(--space-1) - var(--space-6));
}

/* Replies inside an internal thread render as smaller stacked bubbles
   in the same column. Both the reply divider above and the reply body
   share the same left indent so the nested thread reads as one column
   offset from the root. Top margin on the divider gives the reply a
   bit of air from the root content (and from a previous reply) above. */
.timeline-comment--internal .timeline-reply,
.timeline-comment--internal .comment-time-divider--reply {
  margin-left: var(--space-6);
}

.timeline-comment--internal .comment-time-divider--reply {
  margin-top: var(--space-4);
}

.timeline-comment--internal .timeline-reply {
  border-top: none;
  padding-left: 0;
  padding-top: var(--space-2);
}

/* Reply bubble mirrors the root bubble's indent rule: the body sits under
   the divider's avatar, so the eye reads a clean second column of avatar
   → bubble inside the nested reply thread. The --space-6 from the wrapper
   (above) puts the column itself in; this margin lines up the bubble with
   the avatar inside that column. */
.timeline-comment--internal .timeline-reply .comment-body {
  background: color-mix(in srgb, var(--color-accent-hover) 14%, var(--color-surface));
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: var(--space-2) var(--space-3);
  margin-left: calc(var(--space-4) + var(--space-2));
  display: inline-block;
  max-width: 100%;
}

/* ── Sender block ────────────────────────────────────────────────────────── */
/* In-bubble header (avatar + author + inline time) is now redundant — the
   per-comment time divider above the row carries the same info for root
   comments, and replies sit visually inside a labelled card so their bubble
   alone is enough context. */
.comment-header {
  display: none;
}

.comment-avatar {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  flex-shrink: 0;
}

.comment-avatar--small {
  width: 1.375rem;
  height: 1.375rem;
  font-size: var(--font-xs);
}

.comment-author {
  font-weight: var(--weight-medium);
  color: var(--color-subtle);
  font-size: var(--font-xs);
  white-space: nowrap;
}

.comment-time {
  color: var(--color-muted);
  font-size: var(--font-xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Badges + context pill share the metadata row */
.comment-pin-badge,
.comment-shared-badge,
.comment-edited {
  font-size: var(--font-xs);
}

.comment-edited {
  color: var(--color-muted);
  font-style: italic;
}

.comment-pin-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-accent);
  font-weight: var(--weight-medium);
}
.comment-pin-badge svg {
  width: 0.75rem;
  height: 0.75rem;
}

.comment-shared-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.05rem var(--space-2);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-warning) 18%, transparent);
  color: var(--color-warning-text);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.comment-pin-badge { white-space: nowrap; }

.comment-context-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.05rem var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-surface-dim);
  color: var(--color-subtle);
  font-size: var(--font-xs);
  line-height: var(--line-height-normal);
  text-decoration: none;
  white-space: nowrap;
  border: 1px solid var(--color-border-subtle);
  transition: background var(--transition-normal), color var(--transition-normal), border-color var(--transition-normal);
}

a.comment-context-pill:hover {
  background: var(--color-surface-muted);
  border-color: var(--color-border);
  color: var(--color-text);
}

.comment-context-pill--inert {
  cursor: default;
}

/* ── Body ────────────────────────────────────────────────────────────────── */
.comment-body {
  font-size: var(--font-base);
  line-height: var(--line-height-relaxed);
  color: var(--color-text);
}

.comment-body p {
  margin: 0 0 var(--space-2) 0;
}

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

/* Mentions inside comment bodies pick up the accent (Front-style @handle). */
.comment-body a {
  color: var(--color-accent);
  text-decoration: none;
  font-weight: var(--weight-medium);
}
.comment-body a:hover {
  text-decoration: underline;
}

/* Markdown lists — indent so bullets/numbers sit inside the comment's text
   margin instead of hanging outside it (the global ul/ol reset zeroes
   padding). */
.comment-body ul,
.comment-body ol {
  margin: 0 0 var(--space-2) 0;
  padding-left: var(--space-5);
}

.comment-body ul:last-child,
.comment-body ol:last-child {
  margin-bottom: 0;
}

/* ── Threaded replies — soft hairline separators, nested + indented ──────── */
.timeline-reply {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  padding-left: var(--space-5);
  border-top: 1px solid var(--color-border-subtle);
  background: transparent;
  font-size: var(--font-sm);
}

.timeline-reply .comment-header {
  margin-bottom: var(--space-1);
  padding-right: 0;
}

.timeline-reply .comment-body {
  font-size: var(--font-sm);
}

/* ── Hover action chip (pin · reply · delete) ────────────────────────────── */
.comment-actions {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  display: flex;
  gap: var(--space-1);
  align-items: center;
  padding: var(--space-1);
  background: var(--color-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  opacity: 0;
  transform: translateY(-0.125rem);
  transition: opacity var(--transition-normal), transform var(--transition-normal);
  pointer-events: none;
}

.timeline-comment-row:hover .comment-actions,
.timeline-comment-row:focus-within .comment-actions {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.comment-action {
  background: none;
  border: none;
  width: 1.625rem;
  height: 1.625rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  cursor: pointer;
  padding: 0;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.comment-action svg {
  width: 1rem;
  height: 1rem;
  display: block;
}

.comment-action:hover {
  color: var(--color-text);
  background: var(--color-hover);
}

.comment-action--active {
  color: var(--color-accent);
}

.comment-action--danger:hover {
  color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 10%, transparent);
}

/* ── Inline comment editor (author/admin) ────────────────────────────────── */
.comment-edit-form {
  margin-top: var(--space-2);
}

.comment-edit-form[hidden] {
  display: none;
}

.comment-edit-textarea {
  width: 100%;
  resize: vertical;
  font: inherit;
  font-size: var(--font-sm);
  color: var(--color-text);
  background: var(--color-surface);
  line-height: 1.45;
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  outline: none;
}

.comment-edit-textarea:focus {
  border-color: var(--color-accent);
}

.comment-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* ── Reply form ──────────────────────────────────────────────────────────── */
.reply-form {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
}

.reply-input {
  width: 100%;
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-size: var(--font-sm);
  font-family: inherit;
  resize: none;
  outline: none;
  transition: border-color var(--transition-normal), box-shadow var(--transition-normal);
}

.reply-input:focus {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.reply-form-actions {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

/* ════════════════════════════════════════════════════════════════════════
   System activity — muted centred separators (Front "Snoozed" / "N events")
   ════════════════════════════════════════════════════════════════════════ */
/* Individual activity events use the same hairline-divider treatment as
   the comment time divider: a short ::before rule left of the content, the
   row contents (time + icon + text), then a long ::after rule filling out
   to the right edge. Matches the rhythm of comment dividers so the feed
   reads as one consistent stream. Grouped events keep their own block
   treatment (see .activity-group-divider further below). */
.timeline-activity {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-muted);
  line-height: var(--line-height-normal);
}

.timeline-activity::before {
  content: "";
  order: -2;
  flex: 0 0 var(--space-4);
  height: 1px;
  background: var(--color-border-subtle);
}

.timeline-activity::after {
  content: "";
  order: 1;
  flex: 1;
  height: 1px;
  background: var(--color-border-subtle);
}

.activity-text {
  flex: 0 1 auto;
  min-width: 0;
  text-align: left;
}

.activity-text strong {
  color: var(--color-subtle);
  font-weight: var(--weight-semibold);
}

/* Leading type icon — clarifies the action at a glance. */
.activity-icon {
  display: inline-flex;
  vertical-align: -0.15em;
  width: 0.875rem;
  height: 0.875rem;
  margin-right: var(--space-1);
  color: var(--color-muted);
}
.activity-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}
.timeline-activity--approve .activity-icon,
.activity-icon--approve { color: var(--color-success); }
.timeline-activity--move .activity-icon,
.activity-icon--move { color: var(--color-accent); }

.activity-time {
  order: -1;
  flex-shrink: 0;
  min-width: 5.5rem;
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* No leading "·" — left-aligned rows read clearly without it. */
.activity-text::before {
  content: none;
}

/* ── Grouped events: a centred divider with flanking hairlines ───────────── */
.timeline-activity-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Grouped events use the same left-aligned hairline rhythm as the comment
   time divider and individual activity rows: short ::before tucks under
   the content, ::after fills out to the right edge. */
.activity-group-divider {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.activity-group-divider::before {
  content: "";
  flex: 0 0 var(--space-4);
  height: 1px;
  background: var(--color-border-subtle);
}

.activity-group-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border-subtle);
}

/* Per-comment header divider — full-width centered "<author> · <time>" on a
   hairline, reusing the system-events divider visual so a comment burst
   reads as one labelled group. Negative top-margin on the following row
   cancels the feed gap so the bubble sits close under its label instead
   of floating mid-air between divider and previous block.

   Author reads slightly stronger than the time so the eye lands on "who"
   first when scanning the feed; time is muted + tabular-numbered for a
   stable right-of-name rhythm. */
.comment-time-divider {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* Short hairline before the avatar (just a wink of rule, so the avatar
   reads as anchored at --space-4 from the left edge), then the divider
   content (avatar · name · time), then the long hairline filling out
   to the right edge. Left-aligned variant of the centered system-events
   divider. */
.comment-time-divider::before {
  content: "";
  flex: 0 0 var(--space-4);
  height: 1px;
  background: var(--color-border-subtle);
}

.comment-time-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border-subtle);
}

/* Small circular avatar inline on the divider, slightly larger than the
   text so it reads as the row anchor. ``--image`` is the real photo; the
   ``--initials`` sibling is the onerror fallback (and the default for
   commenters without a profile picture). */
.comment-time-divider-avatar {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  flex-shrink: 0;
}

.comment-time-divider-avatar--image {
  object-fit: cover;
}

.comment-time-divider-avatar--initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-accent);
  color: var(--color-surface);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
}

/* When the real photo loads, hide its initials sibling. ``onerror`` on the
   image removes the <img>; the sibling selector then stops matching and
   the initials become visible automatically — no inline ``style=`` needed
   (template lint forbids it). */
.comment-time-divider-avatar--image + .comment-time-divider-avatar--initials {
  display: none;
}

.comment-time-divider-author {
  color: var(--color-subtle);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.comment-time-divider-sep {
  color: var(--color-muted);
  white-space: nowrap;
}

.comment-time-divider-time {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.comment-time-divider + .timeline-comment-row {
  margin-top: calc(var(--space-2) - var(--space-6));
}

.activity-group-toggle {
  background: none;
  border: none;
  font-size: var(--font-xs);
  color: var(--color-muted);
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: inherit;
  white-space: nowrap;
  transition: color var(--transition-fast);
}

.activity-group-toggle:hover {
  color: var(--color-text);
}

.activity-group-chev {
  font-size: var(--font-xs);
  transition: transform var(--transition-normal);
}

.timeline-activity-group.is-open .activity-group-chev {
  transform: rotate(180deg);
}

.activity-group-items {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* The explicit display above overrides the [hidden] attribute's UA
   display:none, which broke the collapse toggle — restore it so
   toggleActivityGroup() can hide the group again. */
.activity-group-items[hidden] {
  display: none;
}

.activity-group-items .timeline-activity {
  text-align: left;
}

/* ════════════════════════════════════════════════════════════════════════
   Composer — calm rounded card (shared base; case overrides below)
   ════════════════════════════════════════════════════════════════════════ */
.composer-wrap {
  /* border: 1px solid var(--color-border-subtle); */
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: border-color var(--transition-normal), box-shadow var(--transition-normal);
  background: var(--color-surface);
}

.composer-wrap:focus-within {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.composer-inline {
  display: flex;
  align-items: flex-end;
  gap: 0;
}

.composer-inline-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-3) 0;
  flex-shrink: 0;
}

.page-composer-scope-hint {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.page-composer-scope-hint[hidden] {
  display: none;
}

.composer-send {
  background: var(--color-accent);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  color: var(--color-surface);
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.composer-send:hover {
  background: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
}


/* ── Page-level timeline ── */

.page-timeline {
  display: flex;
  flex-direction: column;
}

.page-timeline .timeline-entries {
  flex: 1;
  overflow-y: auto;
}

.page-composer {
  border-top: 1px solid var(--color-border);
  padding: 0.5rem 0;
  flex-shrink: 0;
}

.page-composer-wrap {
  box-shadow: none;
  display: flex;
  flex-direction: column;
}

.page-composer-wrap .composer-editor-textarea {
  border-bottom: 1px solid var(--color-border);
  border-radius: 12px 12px 0 0;
}

.page-composer-wrap .composer-inline-actions {
  padding: 0.3rem 0.5rem;
  justify-content: flex-end;
}

.composer-editor-textarea {
  flex: 1;
  min-width: 0;
  padding: 0.55rem 0.75rem;
  font-size: 0.85rem;
  font-family: inherit;
  line-height: 1.5;
  border: none;
  outline: none;
  resize: none;
  min-height: 1.4em;
  background: transparent;
}

/* ── Passport checks ── */

.tool-ws-passport-checks {
  margin: 0.5rem 0;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-dim);
}

.tool-ws-passport-checks-title {
  font-size: 0.82rem;
  font-weight: var(--weight-semibold);
  cursor: pointer;
}

.passport-checks-form {
  margin-top: var(--space-3);
}

.passport-checks-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.passport-checks-field {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.passport-checks-hint {
  font-size: 0.72rem;
  color: var(--color-warning-text);
}

/* ── Files tab ── */

.files-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}

.files-table th {
  text-align: left;
  font-weight: var(--weight-medium);
  font-size: 0.75rem;
  color: var(--color-muted);
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}

.files-table td {
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
  vertical-align: middle;
}

.files-row:hover {
  background: var(--color-hover);
}

.files-row--highlight {
  background: color-mix(in srgb, var(--color-accent) 6%, transparent);
  animation: files-highlight-fade 3s ease-out forwards;
}

@keyframes files-highlight-fade {
  0% {
    background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  }

  100% {
    background: transparent;
  }
}

.files-filename {
  font-weight: var(--weight-medium);
  word-break: break-all;
}

.files-date,
.files-uploader,
.files-pages,
.files-size {
  white-space: nowrap;
  color: var(--color-muted);
}

.files-req-link {
  color: var(--color-accent);
  text-decoration: none;
  font-size: 0.8rem;
}

.files-req-link:hover {
  text-decoration: underline;
}

.source-file-link {
  color: var(--color-accent);
  text-decoration: none;
}

.source-file-link:hover {
  text-decoration: underline;
}

/* ══════════════════════════════════════════════════════════════════════════════
   COMPONENTS — primitives used by macros in core/templates/components/
   Keep this section tight; one class family per primitive, variants as modifiers.
   Added as part of #162 (design-system alignment).
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── Tag (canonical badge family for MVP) ──────────────────────────────────── */

.tag {
  display: inline-block;
  padding: 0.2rem 0.55rem;
  border-radius: var(--radius-pill);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  white-space: nowrap;
  line-height: 1.4;
}

.tag--sm {
  padding: 0.1rem 0.4rem;
  font-size: 0.68rem;
}

.tag--neutral {
  background: var(--color-surface-muted);
  color: var(--color-subtle);
}

.tag--success {
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
  color: var(--color-success);
}

.tag--warning {
  background: color-mix(in srgb, var(--color-warning-text) 12%, transparent);
  color: var(--color-warning-text);
}

.tag--danger {
  background: color-mix(in srgb, var(--color-danger) 12%, transparent);
  color: var(--color-danger);
}

.tag--info {
  background: color-mix(in srgb, var(--color-info) 12%, transparent);
  color: var(--color-info);
}

.tag--muted {
  background: transparent;
  color: var(--color-muted);
  border: 1px solid var(--color-border);
}

.tag--missing {
  background: transparent;
  color: var(--color-warning-text);
  border: 1px solid color-mix(in srgb, var(--color-warning) 45%, transparent);
}

/* Base-service palette (#1047) — the pill half.

   `.tag--service-{key}` is a tag TONE like the six above it, and follows their
   shape exactly: the token as text, a 12% mix of itself as the background. The
   difference is what it encodes — identity (which service was bought), not
   status (how the case is doing). That is why these are separate classes and
   not a remapping of tag--success / tag--danger onto services: painting
   MayDay `danger` would make a tier read as an alarm.

   Each key is bound ONCE, for both consumers at the same time: the pill class
   and the row class name the same token in one rule. That pairing is the point
   — binding `--lv-tint` on the pill alone leaves `.lv-row--tint-{key}` with an
   undefined property, and `color-mix()` against an undefined custom property
   resolves to *transparent*: the row gets the class, the class matches, the
   wash paints nothing. It looks exactly like the pref not working, and no
   class-presence assertion can see it — the tests asserted the classes and
   passed while the board rendered untinted. Only computed style catches it.
   The parity test now asserts this paired form, and it is the browser pass
   that found it. See the row-tint block further down. */
.tag--service-blue,
.lv-row--tint-blue { --lv-tint: var(--color-service-blue); }
.tag--service-teal,
.lv-row--tint-teal { --lv-tint: var(--color-service-teal); }
.tag--service-green,
.lv-row--tint-green { --lv-tint: var(--color-service-green); }
.tag--service-amber,
.lv-row--tint-amber { --lv-tint: var(--color-service-amber); }
.tag--service-rose,
.lv-row--tint-rose { --lv-tint: var(--color-service-rose); }
.tag--service-plum,
.lv-row--tint-plum { --lv-tint: var(--color-service-plum); }

.tag[class*="tag--service-"] {
  background: color-mix(in srgb, var(--lv-tint) 12%, transparent);
  color: var(--lv-tint);
}

/* tag--with-dot: prepends a tone-coloured leading dot. Reads as a status
   indicator without leaning on the pill background — useful when the tag
   sits in a dense row (file lists, submission file rows) where the tinted
   shape would feel busy. */
.tag--with-dot::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: var(--space-2);
  border-radius: 50%;
  background: currentColor;
  vertical-align: 0.05em;
}

/* ── Badge — notification indicator (count pill or bare dot). ──────────────────
   Overlay variant (.badge--pin) needs a position:relative host. Distinct from
   .tag, which is an inline label. */
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 var(--space-1);
  border-radius: var(--radius-pill);
  font-size: var(--font-xxs);
  font-weight: var(--weight-normal);
  line-height: 2;
  color: var(--color-surface);
  background: var(--color-accent);
}

.badge--dot {
  min-width: 8px;
  width: 8px;
  height: 8px;
  padding: 0;
}

.badge--pin {
  position: absolute;
  top: 0px;
  right: 0px;
}

.badge--pin.badge--dot {
  top: 5px;
  right: 5px;
}

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

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

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

/* ── Alert (tinted message banner — see #272) ──────────────────────────────── */

.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  font-size: var(--font-sm);
  line-height: 1.45;
}

.alert--sm {
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-xs);
  gap: var(--space-2);
}

.alert--info {
  background: color-mix(in srgb, var(--color-info) 10%, transparent);
  border-color: color-mix(in srgb, var(--color-info) 25%, transparent);
  color: var(--color-info);
}

.alert--success {
  background: color-mix(in srgb, var(--color-success) 10%, transparent);
  border-color: color-mix(in srgb, var(--color-success) 25%, transparent);
  color: var(--color-success);
}

.alert--warning {
  background: color-mix(in srgb, var(--color-warning-text) 10%, transparent);
  border-color: color-mix(in srgb, var(--color-warning-text) 25%, transparent);
  color: var(--color-warning-text);
}

.alert--danger {
  background: color-mix(in srgb, var(--color-danger) 10%, transparent);
  border-color: color-mix(in srgb, var(--color-danger) 25%, transparent);
  color: var(--color-danger);
}

.alert--muted {
  background: var(--color-surface-muted);
  border-color: var(--color-border);
  color: var(--color-subtle);
}

/* ── View-as banner (#1005 slice 7) ─────────────────────────────────────────
   The portal shell's impersonation disclosure. Sticky rather than fixed so it
   stays in flow and no layout below needs a top offset, and sticky rather than
   static because an admin who scrolls it away is exactly the admin who forgets
   whose session they are writing into. Colours come from `.alert--warning`;
   this rule is layout only. */

.view-as-banner {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  align-items: center;
  justify-content: space-between;
  border-radius: 0;
  border-left: none;
  border-right: none;
  border-top: none;
  /* `.alert--warning` mixes its tint into `transparent`, which is right for a
     banner in flow and wrong for a sticky one — page content would show
     through as it scrolls underneath. Same tint, mixed into the surface. */
  background: color-mix(in srgb, var(--color-warning-text) 10%, var(--color-surface));
  /* Clear the icon rail. It is `position: fixed` at `--z-dropdown`, so it sits
     over this banner whatever z-index the banner takes — the first browser
     pass had "Viewing" hidden behind it. Same arithmetic as the layout's own
     `margin-left` a few hundred lines below. */
  padding-left: calc(var(--rail-width) + var(--rail-inset) + var(--rail-inset) + var(--space-4));
}

.view-as-banner__text {
  flex: 1 1 auto;
}

.view-as-banner__stop {
  flex: 0 0 auto;
  margin-left: var(--space-4);
}

.alert-icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  margin-top: 1px;
}

.alert-icon svg {
  width: 100%;
  height: 100%;
}

.alert-message {
  flex: 1;
  min-width: 0;
}

.alert-close {
  background: none;
  border: 0;
  color: inherit;
  font-size: 1.1rem;
  line-height: 1;
  padding: 0 var(--space-1);
  cursor: pointer;
  opacity: 0.6;
  flex-shrink: 0;
}

.alert-close:hover {
  opacity: 1;
}

.alert-action {
  flex-shrink: 0;
}

/* Sign-in blocks — the stacked "why can't they sign in" list on a person
   profile (tools/people/templates/person_profile.html). Each row is a plain
   .alert; this only stacks them and spaces them off the actions below. */
.sign-in-blocks {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.sign-in-blocks__effect {
  display: block;
  opacity: 0.85;
}

/* ── Auth shell (used by base_auth.html and 404/500) ───────────────────────── */

.auth-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-8) var(--space-5);
  background: var(--color-bg);
}

.auth-shell .card {
  padding: 2.5rem;
  width: 100%;
  max-width: 380px;
  border-radius: var(--radius-lg);
}

.auth-shell .logo {
  margin-bottom: var(--space-3);
  text-align: center;
}

.auth-shell .logo img {
  height: 36px;
  width: auto;
}

.auth-shell .subtitle {
  text-align: center;
  font-size: 1.1rem;
  font-weight: var(--weight-semibold);
  color: var(--color-text);
  margin-bottom: var(--space-8);
}

/* ── Card header / actions (extensions to existing .card) ──────────────────── */

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

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-6);
  border-bottom: 1px solid var(--color-border);
}

.card--padded .card-header {
  padding: 0 0 var(--space-5) 0;
  margin-bottom: var(--space-5);
}

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

.card--active {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

/* Compact card — smaller padding + tighter, uppercase-muted title. Used
   by the page modal's stacked cards (Quality reasons, Passport addon,
   Renderings, Page info, Version history rows) where the default
   .card-title's inset is too much next to the surrounding
   tab/button hierarchy. Lays out the body as a flex column with a small
   gap so consecutive items (checkbox rows, date rows, inline notes)
   space themselves consistently. */
.card--compact {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.card--compact[hidden] {
  display: none;
}

.card--compact.card--padded {
  padding: var(--space-4);
}

.card--compact.card--padded .card-header {
  padding: 0;
  margin-bottom: 0;
  border-bottom: 0;
}

.card--compact .card-title {
  padding: 0;
  margin: 0;
}

/* Title meta — secondary inline meta beside the title (e.g. "2 of 4",
   "internal only"). Right-aligned via margin-left auto, normal-cased so
   it reads as data not as a heading. */
.card-title-meta {
  margin-left: auto;
  font-size: 11px;
  font-weight: var(--weight-normal);
  color: var(--color-subtle);
  font-variant-numeric: tabular-nums;
}

/* Warning variant — warning-tinted surface + warning-coloured title.
   Used by the Quality tab's "Reasons" card (appears when status =
   needs_replacement). */
.card--warning {
  border-color: color-mix(in srgb, var(--color-warning) 35%, var(--color-border));
  background: color-mix(in srgb, var(--color-warning) 4%, var(--color-surface));
}

.card--warning .card-title {
  color: var(--color-warning-text);
}

/* ── Form error + icon-glyph span ──────────────────────────────────────────── */

.form-group.has-error .form-control {
  border-color: var(--color-danger);
}

.form-error {
  font-size: var(--font-xs);
  color: var(--color-danger);
  margin-top: 0.2rem;
}

.btn-icon-glyph {
  display: inline-flex;
  align-items: center;
  line-height: 0;
}

/* ── Empty state extensions ──────────────────────────────────────────────
   Two icon styles supported:
   - Text/emoji glyph fall-through (legacy callers — leads, cases tables)
   - SVG icon scoped via :has(svg) to a 44px tile with surface-muted bg
     (used by Round 2 Files-tab empty states; matches the design pattern). */

.empty-state-icon {
  font-size: 2rem;
  margin-bottom: var(--space-3);
  opacity: 0.4;
}

.empty-state-icon:has(svg) {
  font-size: initial;
  opacity: 1;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  background: var(--color-surface-muted);
  color: var(--color-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--space-4);
}

.empty-state-title {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  margin-bottom: var(--space-2);
}

/* The message reads at the body style like every other sentence
   (/ux body-text-style, 2026-10-09); only its colour is the placeholder's. */
.empty-state-message {
  font-size: var(--font-xs);
  color: var(--color-muted);
  max-width: 44ch;
  margin: 0 auto var(--space-5);
  line-height: var(--line-height-normal);
}

.empty-state-message a {
  color: var(--color-accent);
  text-decoration: none;
}

.empty-state-message a:hover {
  text-decoration: underline;
}

.empty-state-action {
  display: inline-flex;
  gap: var(--space-3);
  align-items: center;
}

/* ── Stage bar (case detail header) ────────────────────────────────────────── */

.stage-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-6);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  overflow-x: auto;
}

/* Flat (#1369): no surface of its own, so it sits on its host's surface and
   its first dot starts at the host's gutter. Two classes so the narrow-screen
   padding below cannot reinstate the padding this removes. */
.stage-bar.stage-bar--flat {
  padding: 0;
  background: transparent;
  border-bottom: 0;
}

.stage-bar-step {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-muted);
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

.stage-bar-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-border);
  flex-shrink: 0;
}

.stage-bar-step.is-done {
  color: var(--color-success);
}

.stage-bar-step.is-done .stage-bar-dot {
  background: var(--color-success);
}

.stage-bar-step.is-active {
  color: var(--color-accent);
  font-weight: var(--weight-semibold);
}

.stage-bar-step.is-active .stage-bar-dot {
  background: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.stage-bar-connector {
  flex: 0 0 24px;
  height: 1px;
  background: var(--color-border);
}

.stage-bar-connector.is-done {
  background: var(--color-success);
}

/* ── Loading indicators ────────────────────────────────────────────────────── */

.loading-spinner {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: loading-spin 0.6s linear infinite;
  vertical-align: -2px;
}

@keyframes loading-spin {
  to {
    transform: rotate(360deg);
  }
}

.loading-indicator {
  display: none;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  color: var(--color-muted);
  font-size: var(--font-sm);
}

.loading-indicator.htmx-request,
.htmx-request .loading-indicator {
  display: inline-flex;
}

.loading-label {
  line-height: 1;
}

.loading-skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
}

.loading-skeleton-row {
  height: 12px;
  border-radius: var(--radius-xs);
  background: linear-gradient(90deg,
      var(--color-surface-muted) 0%,
      var(--color-border-subtle) 50%,
      var(--color-surface-muted) 100%);
  background-size: 200% 100%;
  animation: loading-shimmer 1.2s ease-in-out infinite;
}

@keyframes loading-shimmer {
  0% {
    background-position: 200% 0;
  }

  100% {
    background-position: -200% 0;
  }
}

/* ── Responsive breakpoints for primitives ─────────────────────────────────── */

@media (max-width: 480px) {
  .auth-shell {
    padding: var(--space-5) var(--space-3);
  }

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

  .card--padded {
    padding: var(--space-5);
  }

  .card-header {
    padding: var(--space-4) var(--space-5);
  }

  .stage-bar {
    padding: var(--space-3) var(--space-4);
  }

  .modal-dialog {
    width: 92vw !important;
  }
}

/* ── migrated: platform/core/templates/base_error.html ── */

.auth-shell .error-code {
  font-size: 3rem;
  font-weight: var(--weight-bold);
  color: var(--color-accent);
  text-align: center;
  margin-bottom: var(--space-3);
  letter-spacing: -0.02em;
}

/* ── end migrated: platform/core/templates/base_error.html ── */

/* ── migrated: platform/core/templates/base_portal.html ── */

body.portal-shell {
  font-family: var(--font);
  background: var(--color-surface-muted);
  color: var(--color-text);
  height: 100vh;
  overflow: hidden;
}


.portal-shell .container {
  max-width: 760px;
  margin: 0 auto;
}

/* The house card inset, as on the team's cards (PV-24 3b, #1404). */
.portal-shell .card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-5);
  margin-bottom: var(--space-5);
}

.portal-shell .btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius-sm);
  font-size: var(--font-base);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: background 0.15s, opacity 0.15s;
  border: none;
  text-decoration: none;
}

.portal-shell .btn-primary {
  background: var(--color-accent);
  color: #fff;
}

.portal-shell .btn-primary:hover {
  background: var(--color-accent-hover);
}

/* ── end migrated: platform/core/templates/base_portal.html ── */

/* ── migrated: platform/core/templates/base_team.html ── */

:root {
  --rail-width: 48px;
  --rail-inset: 10px;
  --rail-inset-y: 10px;
  --panel-width: 200px;
  --nav-width: calc(var(--rail-width) + var(--rail-inset) + var(--panel-width));
}

body.team-shell {
  font-family: var(--font);
  background: var(--color-surface-muted);
  color: var(--color-text);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.htmx-progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--color-accent);
  opacity: 0;
  transition: opacity 0.1s;
  z-index: 200;
}

.htmx-request .htmx-progress-bar,
.htmx-progress-bar.htmx-request {
  opacity: 1;
  animation: progress-pulse 1.2s ease-in-out infinite;
}

@keyframes progress-pulse {
  0% {
    transform: scaleX(0.3);
    transform-origin: left;
  }

  50% {
    transform: scaleX(0.8);
    transform-origin: left;
  }

  100% {
    transform: scaleX(0.3);
    transform-origin: left;
  }
}

.team-shell .layout,
.portal-shell .layout {
  display: flex;
  min-height: 100vh;
}

/* Lock html/body so the team .main can be a fixed-height windowed surface
   (rail insets on all sides). Without this, the document would scroll
   alongside .main-content. */
html:has(body.team-shell),
body.team-shell {
  overflow: hidden;
  height: 100%;
}

.team-shell .nav-rail,
.portal-shell .nav-rail {
  width: var(--rail-width);
  background: var(--color-rail);
  box-shadow: rgba(0, 0, 0, 0.03) 0px 2px 6px;
  position: fixed;
  top: var(--rail-inset-y);
  bottom: var(--rail-inset-y);
  left: var(--rail-inset);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.85rem 0;
  z-index: var(--z-dropdown);
}

.team-shell .nav-panel {
  transition: width 0.2s ease-in-out, border-color 0.2s ease-in-out;
  overflow: hidden;
}

.team-shell .layout.nav-collapsed .nav-panel,
.team-shell .layout.no-panel .nav-panel {
  width: 0;
  border-right-color: transparent;
}

.team-shell .layout.nav-collapsed .main,
.team-shell .layout.no-panel .main,
.portal-shell .layout.no-panel .main {
  margin-left: calc(var(--rail-width) + var(--rail-inset) + var(--rail-inset));
}

/* ── The secondary menu's top row (#1295, components/nav_menu.html) ──
   Its name, its own actions and the toggle that hides it; the rail keeps no
   toggle. The row sits outside .sb-body, so it stays put while the menu
   scrolls. Library's + and a case's back link ride in it unchanged. */
.nav-panel-top {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-height: calc(var(--control-h) + var(--space-3));
  padding: 0 var(--space-2) 0 var(--space-4);
}

.nav-panel-top__title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.nav-panel-top .nav-menu-toggle {
  margin-left: auto;
}

.nav-panel-top:hover .sb-group-add {
  opacity: 1;
}

.nav-panel-top .sb-back {
  margin: 0;
}

/* The page header's way back, shown only while the menu is closed. Its
   tooltip opens rightward: the card clips anything left of its edge. */
.nav-menu-reopen {
  display: none;
  margin-right: var(--space-2);
}

.team-shell .layout.nav-collapsed:not(.no-panel) .nav-menu-reopen {
  display: inline-flex;
}

.nav-menu-reopen::after {
  right: auto;
  left: 0;
}

/* On its own page the calendar's rail sits under a top row that already
   names it (base_team.html); inside the Team menu its heading still divides. */
.nav-panel-top + .sb--calendar > .sb-body > .sb-group-label:first-child {
  display: none;
}

.team-shell .main,
.portal-shell .main {
  transition: margin-left 0.2s ease-in-out;
}

.team-shell .nav-rail-sections,
.portal-shell .nav-rail-sections {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  flex: 1;
}

.team-shell .nav-rail-btn,
.portal-shell .nav-rail-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
  color: var(--color-muted);
  text-decoration: none;
  transition: background 0.2s ease, color 0.2s ease;
  position: relative;
}

.team-shell .nav-rail-btn:hover,
.portal-shell .nav-rail-btn:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--color-text);
}

.team-shell .nav-rail-btn.active,
.portal-shell .nav-rail-btn.active {
  background: var(--color-surface);
  color: var(--color-accent);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}

.team-shell .nav-rail-btn svg,
.portal-shell .nav-rail-btn svg {
  width: 18px;
  height: 18px;
}

.team-shell .nav-rail-btn::after,
.team-shell .nav-rail-help::after,
.portal-shell .nav-rail-btn::after,
.portal-shell .nav-rail-help::after {
  content: attr(data-tooltip);
  position: absolute;
  left: calc(100% + 4px);
  top: 50%;
  transform: translateY(-50%);
  background: var(--color-text);
  color: #fff;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  height: 34px;
  line-height: 34px;
  padding: 0 0.7rem;
  border-radius: var(--radius-md);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
  z-index: 200;
}

.team-shell .nav-rail-btn:hover::after,
.team-shell .nav-rail-help:hover::after,
.portal-shell .nav-rail-btn:hover::after,
.portal-shell .nav-rail-help:hover::after {
  opacity: 1;
}

.team-shell .nav-rail-divider,
.portal-shell .nav-rail-divider {
  width: 24px;
  height: 1px;
  background: #e9eaf1;
  margin: 0.25rem 0;
}

.team-shell .nav-rail-bottom,
.portal-shell .nav-rail-bottom {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}

.team-shell .nav-rail-help,
.portal-shell .nav-rail-help {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
  color: var(--color-muted);
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  transition: background 0.2s ease, color 0.2s ease;
  position: relative;
}

.team-shell .nav-rail-help:hover,
.portal-shell .nav-rail-help:hover {
  background: rgba(0, 0, 0, 0.07);
  color: var(--color-text);
}

/* Active utility — same raised-pill treatment as .nav-rail-btn.active: on the
   Transfers / Notifications page (route match) or while the Feedback panel is
   open (toggled by feedback.js). */
.team-shell .nav-rail-help.active,
.portal-shell .nav-rail-help.active {
  background: var(--color-surface);
  color: var(--color-accent);
  box-shadow: var(--shadow-sm);
}

/* A rail utility rendered as an <a> (e.g. Transfers, #781) — drop the link
   underline; .nav-rail-help already handles box, colour and hover. */
.nav-rail-link {
  text-decoration: none;
}

.team-shell .nav-rail-help svg,
.portal-shell .nav-rail-help svg {
  width: 18px;
  height: 18px;
}

.team-shell .nav-rail-help-icon,
.portal-shell .nav-rail-help-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  font-size: 0.7rem;
  font-weight: var(--weight-bold);
}

.team-shell .nav-rail-user-wrap,
.portal-shell .nav-rail-user-wrap {
  position: relative;
}

/* The team rail's sections (Rasmus, 2026-10-04): air inside a section, more
   air and a divider between sections. The portal rail keeps its own spacing. */
.team-shell .nav-rail-sections {
  gap: 0;
}

.team-shell .nav-rail-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
}

.team-shell .nav-rail-sections > .nav-rail-divider {
  margin: var(--space-4) 0;
}

.team-shell .nav-rail-bottom {
  gap: var(--space-4);
}

/* Folding rail — too short for every icon (fitRail, base_team.html): icons
   fold into the avatar menu one step at a time — Transfers, then the bell,
   then Librarian and Help. Each step's class hides its rail icon (`>`: the
   menu rows carry the same `data-fold` one level down) and shows its menu
   row. The bell is keyed by id — it self-replaces, so it carries no
   `data-fold` — and stays in the page, hidden, so it keeps refreshing; its
   count shows on the avatar and on the menu's Notifications row. */
.team-shell .nav-rail--fold-transfers .nav-rail-bottom > [data-fold="transfers"],
.team-shell .nav-rail--fold-bell #notification-bell,
.team-shell .nav-rail--fold-help .nav-rail-bottom > [data-fold="help"] {
  display: none;
}

/* `.nav-rail-menu` in every selector: the menu's own `a { display: flex }`
   outranks a bare attribute, and showed the row with the icon still in the
   rail. */
.team-shell .nav-rail-menu [data-fold],
.team-shell .nav-rail-avatar__unread {
  display: none;
}

.team-shell .nav-rail--fold-transfers .nav-rail-menu [data-fold="transfers"],
.team-shell .nav-rail--fold-bell .nav-rail-menu [data-fold="bell"],
.team-shell .nav-rail--fold-help .nav-rail-menu [data-fold="help"] {
  display: flex;
}

.team-shell .nav-rail--fold-bell .nav-rail-avatar__unread:not(.is-empty) {
  display: inline-flex;
}

.team-shell .nav-rail-menu__count {
  margin-left: auto;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-accent);
}

.team-shell .nav-rail-menu__count.is-empty {
  display: none;
}

/* Librarian and Help fold in as button rows, styled like the menu's links. */
.team-shell .nav-rail-menu button {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: 0.6rem 0.85rem;
  font-family: inherit;
  font-size: 0.82rem;
  color: var(--color-text);
  text-align: left;
  background: none;
  border: none;
  cursor: pointer;
  transition: background 0.1s;
  white-space: nowrap;
}

.team-shell .nav-rail-menu button:hover {
  background: var(--color-hover);
}

.team-shell .nav-rail-menu > button,
.team-shell .nav-rail-menu > button + a {
  border-top: 1px solid var(--color-border);
}

.team-shell .nav-rail-avatar,
.portal-shell .nav-rail-avatar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--avatar-border-radius);
  background: var(--avatar-bg);
  color: #fff;
  font-size: 11px;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
  cursor: pointer;
  border: none;
  font-family: inherit;
  transition: opacity 0.15s;
}

.team-shell .nav-rail-avatar:hover,
.portal-shell .nav-rail-avatar:hover {
  opacity: 0.85;
}

.team-shell .nav-rail-avatar--image,
.portal-shell .nav-rail-avatar--image {
  background: transparent;
  border-radius: 50%;
}

.team-shell .nav-rail-avatar::after,
.portal-shell .nav-rail-avatar::after {
  content: "";
  position: absolute;
  bottom: 1px;
  right: 1px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-success);
  border: 2px solid var(--color-surface);
}

.team-shell .nav-rail-menu,
.portal-shell .nav-rail-menu {
  display: none;
  position: absolute;
  bottom: -4px;
  left: calc(100% + 10px);
  min-width: 160px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  overflow: hidden;
  z-index: var(--z-modal);
}

.team-shell .nav-rail-user-wrap.open .nav-rail-menu,
.portal-shell .nav-rail-user-wrap.open .nav-rail-menu {
  display: block;
}

.team-shell .nav-rail-menu a,
.portal-shell .nav-rail-menu a {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0.6rem 0.85rem;
  font-size: 0.82rem;
  color: var(--color-text);
  text-decoration: none;
  transition: background 0.1s;
  white-space: nowrap;
}

.team-shell .nav-rail-menu a:hover,
.portal-shell .nav-rail-menu a:hover {
  background: var(--color-hover);
}

.team-shell .nav-rail-menu a+a,
.portal-shell .nav-rail-menu a+a {
  border-top: 1px solid var(--color-border);
}

.team-shell .nav-rail-menu-icon,
.portal-shell .nav-rail-menu-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--color-muted);
}

/* portal user menu — static name header above the sign-out link */
.portal-shell .nav-rail-menu-header {
  padding: 0.6rem 0.85rem;
  font-size: var(--font-xs);
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}

.team-shell .nav-panel,
.portal-shell .nav-panel {
  width: var(--panel-width);
  min-width: 0;
  background: none;
  position: fixed;
  top: 0;
  bottom: 0;
  left: calc(var(--rail-width) + var(--rail-inset));
  overflow-y: auto;
  z-index: 5;
  white-space: nowrap;
}

.team-shell .nav-panel-title,
.portal-shell .nav-panel-title {
  font-size: 0.95rem;
  font-weight: var(--weight-bold);
  padding: 1.5rem 1.1rem 1rem;
  color: var(--color-text);
}

.team-shell .nav-panel-section,
.portal-shell .nav-panel-section {
  padding: 0 0.6rem;
  margin-bottom: 0.5rem;
}

.team-shell .nav-panel-label,
.portal-shell .nav-panel-label {
  font-size: 0.72rem;
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  padding: 1rem 0.65rem 0.3rem;
  letter-spacing: 0.01em;
}

.team-shell .nav-panel-label:first-child,
.portal-shell .nav-panel-label:first-child {
  padding-top: 0.1rem;
}

/* The legacy panel link. Not the .sb sidebar's own rows, and not the calendar
   rail's mini-month days and arrows — a day cell sized like a nav link breaks
   the month's grid. */
.team-shell .nav-panel a:not(.sb-back):not(.sb-row):not(.sb-child):not(.cal-mini__day):not(.cal-icon-btn),
.portal-shell .nav-panel a:not(.sb-back):not(.sb-row):not(.sb-child):not(.cal-mini__day):not(.cal-icon-btn) {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0.55rem 0.65rem;
  border-radius: var(--radius-md);
  font-size: 0.9rem;
  color: var(--color-text);
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
}

.team-shell .nav-panel a:not(.sb-back):not(.sb-row):not(.sb-child):not(.cal-mini__day):not(.cal-icon-btn):hover,
.portal-shell .nav-panel a:not(.sb-back):not(.sb-row):not(.sb-child):not(.cal-mini__day):not(.cal-icon-btn):hover {
  background: var(--color-hover);
}

.team-shell .nav-panel a.active,
.portal-shell .nav-panel a.active {
  background: rgba(0, 0, 0, 0.07);
  font-weight: var(--weight-medium);
}

.team-shell .sidenav-icon,
.portal-shell .sidenav-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--color-muted);
}

.team-shell .sidenav-icon svg,
.portal-shell .sidenav-icon svg {
  width: 100%;
  height: 100%;
}

.team-shell .nav-badge {
  margin-left: auto;
  font-size: 0.6rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  color: var(--color-accent);
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-xs);
}

/* Team main — fixed-height window pinned to the rail insets. Content lives
   inside .main-detail / .main-header / .main-content; only .main-content
   scrolls. */
.team-shell .main {
  margin-left: var(--nav-width);
  flex: 1;
  min-width: 0;
  height: calc(100vh - 2 * var(--rail-inset-y));
  overflow: clip;
  padding: 0;
  background: var(--color-team-surface);
  border-radius: var(--radius-lg);
  margin-top: var(--rail-inset-y);
  margin-right: var(--rail-inset-y);
  margin-bottom: var(--rail-inset-y);
}

/* Inner flex column that fills .main and stacks header / content. */
.team-shell .main-detail,
.portal-shell .main-detail {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

/* Pinned region above .main-content. Stays put as content scrolls. */
.team-shell .main-header,
.portal-shell .main-header {
  flex: 0 0 auto;
}

/* The only scroll container inside .main. */
.team-shell .main-content,
.portal-shell .main-content {
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
}

.team-shell .main-content {
  padding: var(--space-ws-gutter);
  /* One line height for all body text in the content area; nothing set it
     before, so every unstyled run took the browser's "normal"
     (/ux body-text-style, 2026-10-09). */
  line-height: var(--line-height-normal);
  /* No padding transition here: the doc-switch is a root View Transition
     (htmx transition:true) that snapshots the whole frame — gutter included —
     and cross-fades old→new as ONE animation. A separate `transition: padding`
     ran a second, desynced tween on top (gutter eased while the content
     cross-faded), which read as "gutter out → switch → gutter in". Letting the
     crossfade own the gutter change keeps it all-at-once. */
}

/* Hairline workspace: close the top/bottom gutter (areas run full height) and
   the RIGHT gutter (detail panel flush to the edge), but keep the LEFT gutter
   so the filmstrip isn't jammed against the nav. Scoped via :has so the list
   tabs keep their full gutter. */
.team-shell .main-content:has(.tool-ws-unified) {
  padding-top: 0;
  padding-bottom: 0;
  padding-right: 0;
}

/* Opt-out for split-layout pages that paint their own chrome edge-to-edge
   (case detail's left case-nav + right column, etc.). Template adds
   `is-flush` to {% block body_class %}.

   Both shells, since #958: the couple's Drive is the same component as the
   team's and needs the same edge-to-edge, height-bounded region. Scoping this
   to `.team-shell` was true while every flush surface was team-side, and the
   portal drive is the first that is not. */
.team-shell.is-flush .main-content,
.portal-shell.is-flush .main-content {
  padding: 0;
}

/* ── Main Header — C2 design (page title + section picker)
   Drop into the .main-header slot; pairs with the main_header_nav macro
   in components/page.html. */

.mh {
  border-bottom: 1px solid var(--color-border-subtle);
  display: flex;
  align-items: center;
  gap: 0;
  padding: 0 var(--space-ws-gutter);
  min-height: 60px;
}

.mh-title {
  font-size: var(--font-lg);
  font-weight: var(--weight-bold);
  line-height: var(--line-height-tight);
  color: var(--color-text-strong);
  white-space: nowrap;
  flex-shrink: 0;
  margin: 0;
}

.mh-picker {
  position: relative;
  margin-left: auto;
}

.mh-picker--standalone {
  display: inline-block;
  margin-left: 0;
}

.mh-picker--standalone .mh-picker-menu {
  right: auto;
  left: 0;
}

.mh-picker > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: transparent;
  transition: background var(--transition-normal), border-color var(--transition-normal);
}

.mh-picker > summary::-webkit-details-marker { display: none; }

.mh-picker > summary:hover {
  border-color: var(--color-muted);
  background: var(--color-hover);
}

.mh-picker[open] > summary {
  border-color: var(--color-muted);
  background: var(--color-surface-muted);
}

/* Standalone (gallery / non-header) hover treatment — shared by bordered
   and borderless variants. Borderless swaps the border for an outline. */
.mh-picker--standalone > summary {
  transition:
    background var(--transition-slow),
    border-color var(--transition-slow),
    outline-color var(--transition-slow),
    color var(--transition-slow);
}

.mh-picker--standalone > summary:hover,
.mh-picker--standalone[open] > summary {
  background: var(--color-surface-dim);
  border-color: var(--color-border-subtle);
  color: var(--color-subtle);
}

.mh-picker--borderless > summary,
.mh-picker--borderless > summary:hover,
.mh-picker--borderless[open] > summary {
  border-color: transparent;
}

.mh-picker--borderless > summary {
  background: transparent;
  outline: 1px solid transparent;
}

.mh-picker--borderless > summary:hover,
.mh-picker--borderless[open] > summary {
  outline-color: var(--color-border-subtle);
}

.mh-chev {
  opacity: 0.6;
  transition: transform 0.5s cubic-bezier(0.71, -0.4, 0.15, 1.38);
}

.mh-picker[open] .mh-chev { transform: rotate(180deg); }

.mh-picker-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  left: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  min-width: 240px;
  padding: var(--space-1);
  z-index: var(--z-dropdown);
  display: flex;
  flex-direction: column;
  transform-origin: center;
}

/* Re-animate every open. CSS transitions don't fire on subsequent opens
   because <details> hides content via its slot, not by changing the
   menu's display — so the menu element never "transitions" on
   re-appearance. A keyframe animation re-triggers each time [open] is
   added, which is what we want. */
.mh-picker[open] > .mh-picker-menu {
  animation: mh-picker-menu-open 0.4s cubic-bezier(0.71, -0.4, 0.15, 1.38);
}

/* Close animation runs while [open] is still set; JS adds .is-closing,
   waits for animationend, then removes [open]. `forwards` holds the
   end-state so the menu stays faded out until the attribute flips. */
.mh-picker.is-closing > .mh-picker-menu {
  animation: mh-picker-menu-close 0.4s cubic-bezier(0.71, -0.4, 0.15, 1.38) forwards;
}

@keyframes mh-picker-menu-open {
  from {
    opacity: 0;
    transform: translateY(-20px);
  }
}

@keyframes mh-picker-menu-close {
  to {
    opacity: 0;
    transform: translateY(-20px);
  }
}

.mh-picker-group {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-normal);
  color: var(--color-muted);
  padding: var(--space-3) var(--space-3) var(--space-2);
}

.mh-picker-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-normal);
  color: var(--color-text);
  border-radius: var(--radius-sm);
  text-decoration: none;
}

/* When the item is a <button> (HTMX-driven menus, e.g. case assignee picker)
   rather than an anchor, neutralise the browser's default button chrome so it
   matches the <a> variant pixel-for-pixel. */
button.mh-picker-item {
  appearance: none;
  background: transparent;
  border: 0;
  font-family: inherit;
  width: 100%;
  text-align: left;
  cursor: pointer;
}

.mh-picker-item:hover { background: var(--color-hover); }

.mh-picker-item.is-active {
  background: color-mix(in srgb, var(--color-accent) 5%, transparent);
  color: var(--color-text-strong);
  font-weight: var(--weight-normal);
}

.mh-kbd {
  font-family: ui-monospace, monospace;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
  background: var(--color-bg);
  padding: 1px var(--space-2);
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-border-subtle);
}

/* Leading slot inside an .mh-picker-item — used by the More button for
   per-row icons. Wraps the label+icon as a flex unit so the trailing
   .mh-kbd badge still sits at the row's right edge. */
.mh-picker-item-main {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.mh-picker-item-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  color: var(--color-muted);
  flex-shrink: 0;
}

/* Section separator between More-button groups. */
.mh-picker-divider {
  height: 1px;
  background: var(--color-border-subtle);
  margin: var(--space-1) var(--space-2);
}

.mh-picker-item--disabled,
.mh-picker-item--disabled:hover {
  background: transparent;
  cursor: default;
  pointer-events: none;
  opacity: 0.55;
}

/* An item's second line (`hint` in more_button) — on a disabled item, why
   the action cannot run. Wraps at the menu's base width rather than
   widening it; body colour, since a disabled item is already dimmed. */
.mh-picker-item-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  text-align: left;
}

.mh-picker-item-hint {
  max-width: 240px;
  white-space: normal;
  color: var(--color-text);
}

/* More button: square 3-dot trigger. Replaces the labelled .mh-picker
   summary so the pill styling doesn't show through. Menu animation and
   positioning are inherited from .mh-picker / .mh-picker-menu. */
.mh-picker--more {
  position: relative;
  display: inline-block;
  margin-left: 0;
}

.mh-picker--more > summary {
  width: 30px;
  height: 30px;
  padding: 0;
  gap: 0;
  justify-content: center;
  color: var(--color-muted);
}

.mh-picker--more > summary:hover,
.mh-picker--more[open] > summary {
  color: var(--color-text);
}

/* More-menu escapes any ancestor `overflow: hidden` (e.g. .main-content's
   scroll container) by positioning against the viewport. JS sets
   top/bottom + left/right + max-height each time the picker opens. */
.mh-picker-menu--more {
  position: fixed;
  top: auto;
  right: auto;
  left: auto;
  bottom: auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  max-height: 80vh;
}

.more-btn-glyph { display: block; }

/* Pill-shaped on/off toggle for header-level mode switches (mobile
   preview, notifications, debug overlay). Uses aria-pressed for state. */
.mh-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font-family: inherit;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--transition-normal), color var(--transition-normal), border-color var(--transition-normal);
}

.mh-toggle:hover {
  color: var(--color-text);
  border-color: var(--color-muted);
}

.mh-toggle[aria-pressed="true"] {
  color: var(--color-text-strong);
  background: var(--color-surface-muted);
  border-color: var(--color-muted);
}

.mh-toggle[aria-pressed="true"]:hover {
  background: var(--color-border);
}

.mh-toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* Group container so multiple toggles share a consistent gap and sit
   together near the picker. margin-left: auto pushes the cluster right;
   the picker drops its own auto-margin when this group is present. */
.mh-toggles {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
}

.mh:has(.mh-toggles) .mh-picker {
  margin-left: var(--space-2);
}

/* Docs toolbar hosted in the case header — sits in the slot the assignee
   picker would occupy. Holds two `.tool-ws-header-actions` rows (normal +
   multi-select) that the existing workspace JS toggles between. */
.mh-toolbar {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.mh-toolbar .mh-picker {
  margin-left: 0;
}

/* Heading cluster — wraps .mh-title + an optional baseline-aligned note. */
.mh-heading {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

/* Metadata-scale annotation next to .mh-title. Configurable on four
   axes via data-attributes — tone, icon, style, and alignment. */
.mh-note {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-normal);
  min-width: 0;
}

.mh-note .mh-note-icon {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 12px;
  height: 12px;
}

.mh-note .mh-note-icon svg { width: 100%; height: 100%; display: block; }

/* Optical-centring offset — when the note sits inline beside the larger
   .mh-title, text and drawn-glyph indicators don't visually centre against
   the title at align-items: center; nudge them down 1px (text) / 3px
   (drawn icons + 1px transform lift). Right/center-aligned notes stand on
   their own and centre cleanly via flex without the nudge. */
.mh-note .mh-note-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mh-note[data-align="inline"] .mh-note-text { margin-top: 1px; }

/* Bottom variant — drops the note to the bottom of the heading row instead
   of being optically centred. Use when the note reads as a suffix to the
   title (e.g. a case code after a couple name) rather than a status badge. */
.mh-note[data-align="inline-bottom"] { align-self: flex-end; }
.mh-note[data-align="inline-bottom"] .mh-note-text { margin-top: 0; }

/* Below variant — the note on its own line under the title, both held in
   .mh-title-stack. Frees the row's width for a toolbar that shares it (the
   case header's docs toolbar). A title wider than the heading's line cuts off
   with an ellipsis rather than overflowing it. */
.mh-title-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
}
.mh-title-stack .mh-title {
  flex-shrink: 1;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mh-note[data-align="below"] { max-width: 100%; }

/* Subject suffix (#943) — who a container is about, when its type doesn't say.
   Reads as part of the note, one step darker so it's legible as a name rather
   than as more metadata. Its pencil stays invisible until the heading is
   hovered (or the button is focused), so an unnamed container carries no
   permanent chrome. */
/* Reads as one phrase with the type name beside it — "Child Birth Certificate
   — Emil" — so it takes the note's own size, weight and colour rather than
   setting its own. Inheriting is the point: it was a shade darker than the type
   name and read as a different kind of thing. */
.mh-note .mh-note-subject {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mh-note .mh-note-edit {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  padding: var(--space-1);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-fast), background-color var(--transition-fast),
    color var(--transition-fast);
}

.mh-heading:hover .mh-note-edit,
.mh-note .mh-note-edit:focus-visible { opacity: 1; }

.mh-note .mh-note-edit:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.mh-note[data-tone="muted"]   { color: var(--color-muted); }
.mh-note[data-tone="info"]    { color: var(--color-info); }
.mh-note[data-tone="success"] { color: var(--color-success); }
.mh-note[data-tone="warning"] { color: var(--color-warning); }
.mh-note[data-tone="danger"]  { color: var(--color-danger); }
.mh-note[data-tone="accent"]  { color: var(--color-accent); }

/* Style: chip — tinted background, pill shape. */
.mh-note[data-style="chip"] {
  padding: 2px var(--space-3);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, currentColor 10%, transparent);
  font-weight: var(--weight-medium);
}

.mh-note[data-style="chip"][data-tone="muted"] {
  background: var(--color-surface-muted);
  color: var(--color-subtle);
}

/* Style: dot — small filled circle leads the text.
   translateY(2px) is the optical-centering nudge for drawn glyphs against
   the larger title in the inline variant; right/center notes stand alone
   and don't need it. Applied via transform (not margin) so the rule
   passes lint_css raw-px-spacing without baselining. */
.mh-note[data-style="dot"] .mh-note-icon {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.mh-note[data-align="inline"][data-style="dot"] .mh-note-icon {
  transform: translateY(2px);
}
.mh-note[data-style="dot"] .mh-note-icon svg { display: none; }

/* Style: live — dot + pulsing halo. Reads as realtime / monitoring. */
.mh-note[data-style="live"] .mh-note-icon {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  position: relative;
}
.mh-note[data-align="inline"][data-style="live"] .mh-note-icon {
  transform: translateY(2px);
}
.mh-note[data-style="live"] .mh-note-icon::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  background: currentColor;
  opacity: var(--opacity-medium);
  animation: mh-note-pulse 1.6s ease-out infinite;
}
.mh-note[data-style="live"] .mh-note-icon svg { display: none; }
@keyframes mh-note-pulse {
  0%   { transform: scale(0.6); opacity: var(--opacity-medium); }
  100% { transform: scale(2);   opacity: 0; }
}

/* Style: loader — spinning ring. Reads as in-progress / fetching.
   The keyframes compose translateY (optical-centering offset) with the
   rotation so the spin doesn't reset the vertical nudge. */
.mh-note[data-style="loader"] .mh-note-icon {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  border-top-color: transparent;
  animation: mh-note-spin 0.8s linear infinite;
}
.mh-note[data-style="loader"] .mh-note-icon svg { display: none; }
@keyframes mh-note-spin {
  from { transform: translateY(3px) rotate(0deg); }
  to   { transform: translateY(3px) rotate(360deg); }
}

/* Alignment — inline is the default (note inside .mh-heading). For
   centred and right variants the note is rendered as a sibling of
   the heading + picker so it can position independently. */
.mh-note[data-align="center"] {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, calc(-50% - 0.5px));
}

.mh-note[data-align="right"] {
  margin-left: auto;
}

.mh:has(.mh-note[data-align="right"]) .mh-picker { margin-left: var(--space-3); }

/* The page header is a full-bleed sticky bar inside .main-content: it
   cancels .main-content's 2rem padding so the divider spans edge to edge,
   and pins to the top as the page content scrolls under it. Legacy pages
   that still render page_header inside {% block content %} get this for
   free; new pages should put page_header in {% block main_header %}. */
.team-shell .page-header {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  margin: calc(var(--space-8) * -1) calc(var(--space-8) * -1) var(--space-6);
  padding: var(--space-5) var(--space-8);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

/* portal main — floating detail-window card, mirrors team .main. Fixed to
   the rail's height; .main-content inside is the only scroll container.
   Default left-margin assumes a sub-nav panel; the .no-panel layout class
   (see line ~8419) overrides for rail-only pages. */
.portal-shell .main {
  flex: 1;
  min-width: 0;
  height: calc(100vh - 2 * var(--rail-inset-y));
  overflow: clip;
  background: var(--color-team-surface);
  border-radius: var(--radius-lg);
  margin-left: var(--nav-width);
  margin-top: var(--rail-inset-y);
  margin-right: var(--rail-inset-y);
  margin-bottom: var(--rail-inset-y);
}

.team-shell #toast-container {
  position: fixed;
  bottom: 1rem;
  left: 1rem;
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  pointer-events: none;
}

.team-shell .toast {
  background: #fdf0f0;
  color: var(--color-danger);
  border: 1px solid #f5c6c6;
  padding: 0.75rem 1rem;
  border-radius: var(--radius-sm);
  font-size: var(--font-base);
  max-width: 24rem;
  cursor: pointer;
  pointer-events: auto;
  animation: toast-in 0.25s ease-out;
}

.team-shell .toast.toast-out {
  animation: toast-out 0.2s ease-in forwards;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateX(-1rem);
  }

  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes toast-out {
  from {
    opacity: 1;
    transform: translateX(0);
  }

  to {
    opacity: 0;
    transform: translateX(-1rem);
  }
}

.team-shell .toast.toast--success {
  background: color-mix(in srgb, var(--color-success) 8%, var(--color-surface));
  color: var(--color-success);
  border: 1px solid color-mix(in srgb, var(--color-success) 30%, transparent);
}

.team-shell .help-sidebar {
  width: 0;
  overflow: hidden;
  flex-shrink: 0;
  transition: width 0.2s ease-in-out;
}

.team-shell .help-sidebar.open {
  width: 300px;
}

.team-shell .help-sidebar-inner {
  width: 300px;
  min-width: 300px;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border-left: 1px solid var(--color-border);
}

.team-shell .help-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--color-border);
}

.team-shell .help-panel-title {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
}

.team-shell .help-panel-close {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-muted);
  padding: 0.25rem;
  border-radius: var(--radius-xs);
  display: flex;
}

.team-shell .help-panel-close:hover {
  color: var(--color-text);
  background: var(--color-hover);
}

.team-shell .help-panel-body {
  flex: 1;
  overflow-y: auto;
  padding: 0;
}

/* ── Feedback panel — Direction A "Stack" (composer + activity + recording) ── */

.feedback-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 380px;
  max-width: 100vw;
  background: var(--color-surface);
  border-left: 1px solid var(--color-border);
  box-shadow: var(--shadow-md);
  transform: translateX(100%);
  transition: transform var(--transition-slow) ease;
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  color: var(--color-text);
}

.feedback-panel.open {
  transform: translateX(0);
}

.feedback-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 52px;
  padding: 0 var(--space-5);
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
}

.feedback-panel__title {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  margin: 0;
}

.feedback-panel__head-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

/* "See all feedback" — the board is the Library's Feedback page (#1325); the
   panel only composes. */
.feedback-panel__all {
  font-size: var(--font-sm);
  color: var(--color-accent);
  text-decoration: none;
  margin-right: var(--space-2);
}

.feedback-panel__all:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.feedback-panel__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 0;
  background: transparent;
  color: var(--color-muted);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.feedback-panel__close:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.feedback-panel__close:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.feedback-panel__close svg {
  width: 16px;
  height: 16px;
}

.feedback-panel__pane {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}

.feedback-panel__pane[hidden] {
  display: none;
}

.feedback-panel__pane--compose {
  padding: var(--space-5) var(--space-5) var(--space-3);
  display: flex;
  flex-direction: column;
}

.feedback-panel__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.feedback-panel__form[hidden] {
  display: none;
}

.feedback-panel__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.feedback-panel__label {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--font-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-muted);
  font-weight: var(--weight-semibold);
  margin: 0;
}

.feedback-panel__label-hint {
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--weight-normal);
  color: var(--color-muted);
}

.feedback-panel__type {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.feedback-panel__type-option {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 30px;
  padding: 0 var(--space-4) 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  cursor: pointer;
  user-select: none;
  transition: background var(--transition-fast), color var(--transition-fast),
    border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.feedback-panel__type-option:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.feedback-panel__type-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  border-radius: inherit;
}

.feedback-panel__type-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.feedback-panel__type-option:has(.feedback-panel__type-input:checked) {
  background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
  border-color: color-mix(in srgb, var(--color-accent) 40%, var(--color-border));
  color: var(--color-accent);
  font-weight: var(--weight-semibold);
}

.feedback-panel__type-option--friction:has(.feedback-panel__type-input:checked) {
  background: color-mix(in srgb, var(--color-info) 10%, var(--color-surface));
  border-color: color-mix(in srgb, var(--color-info) 40%, var(--color-border));
  color: var(--color-info);
}

.feedback-panel__type-option:has(.feedback-panel__type-input:focus-visible) {
  box-shadow: var(--shadow-focus);
}

.feedback-panel__type-option[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  background: var(--color-surface);
  color: var(--color-text-strong);
  border: 1px solid var(--color-border);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-xs);
  box-shadow: var(--shadow-md);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--transition-normal);
  z-index: var(--z-dropdown);
}

/* Anchor leftmost / rightmost tooltips to their pill edge so the
   bubble doesn't extend past the panel's overflow clip. */
.feedback-panel__type-option:first-child[data-tip]::after {
  left: 0;
  transform: none;
}

.feedback-panel__type-option:last-child[data-tip]::after {
  left: auto;
  right: 0;
  transform: none;
}

.feedback-panel__type-option:hover::after,
.feedback-panel__type-option:has(.feedback-panel__type-input:focus-visible)::after {
  opacity: 1;
  transition-delay: 250ms;
}

.feedback-panel__type:hover .feedback-panel__type-option:not(:hover)::after {
  opacity: 0;
  transition-delay: 0ms;
}

.feedback-panel .sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.feedback-panel__input,
.feedback-panel__textarea {
  width: 100%;
  font: inherit;
  font-size: var(--font-base);
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.feedback-panel__input::placeholder,
.feedback-panel__textarea::placeholder {
  color: var(--color-muted);
}

.feedback-panel__input:focus,
.feedback-panel__textarea:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.feedback-panel__textarea {
  resize: vertical;
  min-height: 92px;
  font-family: inherit;
}

.feedback-panel__attach {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  overflow: hidden;
}

.feedback-panel__attach-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  min-height: 44px;
  position: relative;
}

.feedback-panel__attach-row[hidden] {
  display: none;
}

/* General-sibling rule so separators land between every attach-row even
   when an attach-preview div sits in between (which `+ adjacent` would
   skip). The mic-row override below removes the separator above mic so
   Record clip + Microphone visually pair as one unit. */
.feedback-panel__attach-row~.feedback-panel__attach-row {
  border-top: 1px solid var(--color-border-subtle);
}

#feedback-record-mic-row {
  border-top: 0;
}

.feedback-panel__attach-row--filled {
  background: color-mix(in srgb, var(--color-accent) 4%, var(--color-surface));
}

.feedback-panel__attach-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--color-muted);
}

.feedback-panel__attach-row--filled .feedback-panel__attach-icon {
  color: var(--color-accent);
}

.feedback-panel__attach-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.feedback-panel__attach-name {
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--color-text);
}

.feedback-panel__attach-row--filled .feedback-panel__attach-name {
  color: var(--color-text-strong);
}

.feedback-panel__attach-hint {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.feedback-panel__attach-action {
  flex-shrink: 0;
}

.feedback-panel__attach-preview {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-dim);
}

.feedback-panel__attach-preview[hidden] {
  display: none;
}

.feedback-panel__attach-preview-thumb {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-border);
  flex-shrink: 0;
  object-fit: cover;
  background: var(--color-surface);
}

.feedback-panel__attach-preview-meta {
  flex: 1;
  min-width: 0;
  font-size: var(--font-xs);
  color: var(--color-subtle);
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
}

.feedback-panel__attach-preview-meta strong {
  color: var(--color-text-strong);
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.feedback-panel__attach-preview-remove {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: 0;
  background: transparent;
  color: var(--color-muted);
  border-radius: var(--radius-xs);
  cursor: pointer;
  font: inherit;
  font-size: var(--font-base);
  line-height: 1;
}

.feedback-panel__attach-preview-remove:hover {
  color: var(--color-danger);
  background: var(--color-hover);
}

.feedback-panel__attach-preview-remove:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.feedback-panel__attach-preview video {
  width: 100%;
  max-height: 220px;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-border);
}

.feedback-panel__switch {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  position: relative;
}

.feedback-panel__switch-input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.feedback-panel__switch-track {
  position: relative;
  width: 30px;
  height: 18px;
  background: color-mix(in srgb, var(--color-muted) 45%, var(--color-surface));
  border-radius: var(--radius-pill);
  transition: background var(--transition-normal);
}

.feedback-panel__switch-track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  background: var(--color-surface);
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition-normal);
}

.feedback-panel__switch-input:checked+.feedback-panel__switch-track {
  background: var(--color-accent);
}

.feedback-panel__switch-input:checked+.feedback-panel__switch-track::after {
  transform: translateX(12px);
}

.feedback-panel__switch-input:focus-visible+.feedback-panel__switch-track {
  box-shadow: var(--shadow-focus);
}

/* Website-target switch (#966) — switch with an inline text label. */
.feedback-panel__switch--labeled {
  gap: var(--space-2);
}

.feedback-panel__switch-text {
  font-size: var(--font-sm);
  color: var(--color-text);
}

#feedback-website-url-field {
  margin-top: var(--space-3);
}

/* Pointer to the website bug bookmarklet (#987), under the page field. */
.feedback-panel__field-note {
  margin: var(--space-2) 0 0;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.feedback-panel__field-note a {
  color: var(--color-accent);
}

/* Website bug bookmarklet (#987) — the page it is dragged from and the
   popup it opens. */
.feedback-website,
.feedback-bookmarklet {
  max-width: 36rem;
}

.feedback-website__lead,
.feedback-bookmarklet__lead {
  margin: 0 0 var(--space-2);
}

.feedback-website__page {
  margin: 0 0 var(--space-4);
  font-size: var(--font-sm);
  color: var(--color-text-strong);
  overflow-wrap: anywhere;
}

.feedback-website__hint,
.feedback-bookmarklet__hint {
  margin: 0 0 var(--space-4);
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.feedback-website__hint:last-child,
.feedback-bookmarklet__hint:last-child {
  margin-bottom: 0;
}

.feedback-bookmarklet__drag {
  margin: 0 0 var(--space-4);
}

.feedback-bookmarklet__drag .btn {
  cursor: grab;
}

.feedback-bookmarklet__steps {
  margin: 0 0 var(--space-4);
  padding-left: var(--space-6);
  font-size: var(--font-sm);
  line-height: var(--line-height-relaxed);
}

/* "Filed as #N" links to the created GitLab issue (#966) — keep the
   success-card and toast typography, just signal the affordance. */
.feedback-panel__success-title a,
.toast--success a {
  color: inherit;
  text-decoration: underline;
}

.feedback-panel__ghost {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 28px;
  padding: 0 var(--space-3);
  font: inherit;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-text);
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast);
}

.feedback-panel__ghost:hover {
  background: var(--color-hover);
}

.feedback-panel__ghost:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  border-color: var(--color-accent);
}

.feedback-panel__ghost--recording {
  color: var(--color-danger);
  border-color: var(--color-danger);
}

.feedback-panel__context {
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  background: var(--color-surface-dim);
}

.feedback-panel__context-summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
  list-style: none;
  font-size: var(--font-xs);
  color: var(--color-subtle);
  font-weight: var(--weight-medium);
}

.feedback-panel__context-summary::-webkit-details-marker {
  display: none;
}

.feedback-panel__context-summary::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid var(--color-muted);
  border-top: 3px solid transparent;
  border-bottom: 3px solid transparent;
  transition: transform var(--transition-fast);
}

.feedback-panel__context[open] .feedback-panel__context-summary::before {
  transform: rotate(90deg);
}

.feedback-panel__context-body {
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-xs);
  color: var(--color-subtle);
  line-height: var(--line-height-relaxed);
  border-top: 1px dashed var(--color-border);
}

.feedback-panel__context-body ul {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-5);
}

.feedback-panel__context-body code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-xs);
}

.feedback-panel__foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
  flex-shrink: 0;
}

.feedback-panel__foot[hidden] {
  display: none;
}

.feedback-panel__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 32px;
  padding: 0 var(--space-4);
  font: inherit;
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: 1px solid transparent;
  transition: background var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast);
}

.feedback-panel__btn--ghost {
  background: transparent;
  color: var(--color-text);
  border-color: var(--color-border);
}

.feedback-panel__btn--ghost:hover {
  background: var(--color-hover);
}

.feedback-panel__btn--primary {
  background: var(--color-accent);
  color: var(--color-surface);
  font-weight: var(--weight-semibold);
}

.feedback-panel__btn--primary:hover {
  background: var(--color-accent-hover);
}

.feedback-panel__btn:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.feedback-panel__btn[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

.feedback-panel__spinner {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, var(--color-surface) 50%, transparent);
  border-top-color: var(--color-surface);
  animation: feedback-panel-spin 0.7s linear infinite;
}

@keyframes feedback-panel-spin {
  to {
    transform: rotate(360deg);
  }
}

/* One feedback item's thread when it has nothing to show (#1325). The rows
   are the comment thread's rail rows and the composer the page composer
   (round H4), so nothing else here is the item's own. */
.feedback-note-thread__empty {
  font-size: var(--font-sm);
  color: var(--color-muted);
  font-style: italic;
  margin: 0;
}

.feedback-note-thread__error {
  font-size: var(--font-sm);
  color: var(--color-danger);
  margin: 0;
}

/* Success state */
.feedback-panel__success {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-8) var(--space-5);
  gap: var(--space-3);
}

.feedback-panel__success-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-success) 12%, var(--color-surface));
  color: var(--color-success);
  margin-bottom: var(--space-2);
}

.feedback-panel__success-mark svg {
  width: 28px;
  height: 28px;
}

.feedback-panel__success-title {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  margin: 0;
}

.feedback-panel__success-meta {
  font-size: var(--font-sm);
  color: var(--color-subtle);
  max-width: 280px;
  margin: 0;
  line-height: var(--line-height-normal);
}

.feedback-panel__success-meta a {
  color: var(--color-accent);
  text-decoration: none;
  font-weight: var(--weight-medium);
}

.feedback-panel__success-meta a:hover {
  text-decoration: underline;
}

.feedback-panel__success-actions {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

/* Floating "Stop recording" pill (rendered as a sibling of the panel) */
.feedback-rec-pill {
  position: fixed;
  bottom: 1.25rem;
  left: 4.25rem;
  z-index: var(--z-toast);
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-danger);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  font: inherit;
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--color-text-strong);
  transition: background var(--transition-fast);
}

.feedback-rec-pill[hidden] {
  display: none;
}

.feedback-rec-pill:hover {
  background: color-mix(in srgb, var(--color-danger) 8%, var(--color-surface));
}

.feedback-rec-pill__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--color-danger);
  animation: feedback-rec-pulse 1.4s ease-in-out infinite;
}

.feedback-rec-pill__time {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--color-muted);
  font-weight: var(--weight-normal);
}

@keyframes feedback-rec-pulse {

  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }

  50% {
    opacity: 0.45;
    transform: scale(0.85);
  }
}

/* "Recording isn't supported" hint inside an attach row */
.feedback-panel__attach-unsupported {
  margin: 0;
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-xs);
  color: var(--color-muted);
  background: var(--color-surface-dim);
  border-top: 1px solid var(--color-border-subtle);
}

/* ── end migrated: platform/core/templates/base_team.html ── */

/* ── Staff auth (#797) — shared layer ──────────────────────────────────────
   One quiet centered card per page, Inter-only, composed on design-system
   primitives (.card / .btn / .form-* / .alert / .checkbox). Replaces the
   retired editorial poster family and its sixteen --auth-* scoped tokens.
   Round record: rules/design-system/handoffs/staff-auth/RETURN/. */

body.auth-shell {
  font-family: var(--font);
  color: var(--color-text);
  font-size: var(--font-base);
  line-height: var(--line-height-normal);
}

.auth-shell a {
  color: var(--color-accent);
  text-decoration: none;
}

.auth-shell a:hover {
  color: var(--color-accent-hover);
  text-decoration: underline;
}

.auth-shell a.btn:hover {
  text-decoration: none;
}

/* `.auth-shell a` outranks `.btn-primary` (0,1,1 vs 0,1,0), so a filled
   button rendered as an <a> in the auth shell drew accent text on an accent
   fill — an unlabelled bar (reset_password's "Request a new link",
   account_2fa_setup's "Continue to dashboard", view_as_ended). Restore the
   button's own foreground. */
.auth-shell a.btn-primary,
.auth-shell a.btn-primary:hover {
  color: var(--color-surface);
}

/* Server-branch show/hide rides on the native hidden attribute; keep it
   winning over component display values (.alert / .auth-form are flex). */
.auth-shell [hidden] {
  display: none !important;
}

.card.auth-card {
  /* non-token: card measure */
  max-width: 400px;
  width: 100%;
  padding: var(--space-8);
  border-radius: var(--radius-auth-card);
  overflow: visible;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.card.auth-card--wide {
  /* non-token: setup-page measure */
  max-width: 480px;
}

.auth-logo img {
  /* non-token: logo size (36px shell default reads oversized on the quiet card) */
  height: 24px;
  width: auto;
  display: block;
}

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

.auth-head h1 {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  line-height: var(--line-height-tight);
  margin-top: var(--space-3);
}

.auth-sub {
  font-size: var(--font-sm);
  color: var(--color-subtle);
}

.auth-sub strong {
  font-weight: var(--weight-medium);
  color: var(--color-text);
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

/* Submits = Button primary, full width; padding-sized to match the
   .form-control height (--control-h is toolbar scale, too small beside
   a text input). */
.auth-form .btn,
.auth-actions .btn {
  width: 100%;
  justify-content: center;
  height: auto;
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-base);
  font-weight: var(--weight-medium);
}

/* Password eye toggle — adornment inside the field */
.auth-input-wrap {
  position: relative;
}

.auth-input-wrap .form-control {
  padding-right: var(--space-8);
}

.auth-pw-toggle {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--space-8);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: none;
  color: var(--color-muted);
  cursor: pointer;
  transition: color var(--transition-fast);
}

.auth-pw-toggle:hover {
  color: var(--color-text);
}

.auth-pw-toggle:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}

.auth-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.auth-row .checkbox,
.auth-link-sm {
  font-size: var(--font-xs);
}

.auth-alt-link {
  background: none;
  border: 0;
  padding: 0;
  font-family: inherit;
  font-size: var(--font-xs);
  color: var(--color-accent);
  cursor: pointer;
  text-align: center;
  align-self: center;
}

.auth-alt-link:hover {
  color: var(--color-accent-hover);
  text-decoration: underline;
}

.auth-foot {
  border-top: 1px solid var(--color-border-subtle);
  padding-top: var(--space-5);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.auth-card .alert-message strong {
  font-weight: var(--weight-semibold);
}

/* One-time-code cells (#cells / #digits): one family, styled as a
   .form-control split into six — same border / radius / focus tokens. */
.auth-cells {
  display: flex;
  gap: var(--space-3);
}

.auth-cells .auth-cell {
  flex: 1;
  min-width: 0;
  display: flex;
}

.auth-cells input {
  flex: 1;
  width: 100%;
  min-width: 0;
  height: var(--control-h-lg);
  text-align: center;
  font-size: var(--font-xl);
  font-weight: var(--weight-medium);
  font-family: inherit;
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast),
    background var(--transition-fast);
}

.auth-cells input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
  background: var(--color-surface);
}

.auth-cells .auth-cell--filled input,
.auth-cells input.is-filled {
  background: var(--color-surface);
  border-color: var(--color-slate-300);
}

.auth-cells.is-error input {
  border-color: var(--color-danger);
}

.auth-field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
}

.auth-field-hint {
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

/* Key / recovery-code text — Inter (mono retired with #797); spacing +
   tabular numerals carry the glyph clarity instead. */
.auth-keytext {
  font-family: var(--font);
  /* non-token: code legibility */
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
}

.auth-step-label {
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  text-transform: uppercase;
  /* non-token: matches .form-label */
  letter-spacing: 0.06em;
}

@media (max-width: 480px) {
  .auth-shell {
    padding: var(--space-5) var(--space-4);
  }

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

/* ── Staff auth — login 2FA ── */

/* Trust-device hint aligns under the checkbox caption (box + gap). */
.auth-check-hint {
  /* non-token: checkbox text alignment */
  padding-left: 1.625rem;
  margin-top: calc(-1 * var(--space-3));
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.auth-backup-hint.is-warn {
  color: var(--color-warning-text);
}

/* ── Staff auth — email-code 2FA ── */

/* Single code input (couples hit this page; one field pastes cleaner
   than cells on mobile mail apps). */
.auth-code-single {
  font-size: var(--font-lg);
  /* non-token: single-field code spacing */
  letter-spacing: 0.25em;
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-medium);
}

/* ── Staff auth — forgot password ── */

.auth-sent {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.auth-dispatch {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-5);
  font-size: var(--font-xs);
  margin: 0;
}

.auth-dispatch dt {
  color: var(--color-muted);
  font-weight: var(--weight-medium);
}

.auth-dispatch dd {
  color: var(--color-subtle);
  margin: 0;
}

.auth-sent .btn {
  width: 100%;
  justify-content: center;
}

/* The dev/staging reset link carries a full token URL — keep it inside the alert. */
.auth-sent .alert a {
  word-break: break-all;
}

/* ── Staff auth — reset password ── */

.auth-meter {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.auth-meter span {
  flex: 1;
  height: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--color-border-subtle);
  transition: background var(--transition-normal);
}

.auth-meter[data-score="1"] span:nth-child(1) {
  background: var(--color-danger);
}

.auth-meter[data-score="2"] span:nth-child(-n + 2) {
  background: var(--color-warning);
}

.auth-meter[data-score="3"] span:nth-child(-n + 3) {
  background: var(--color-success);
}

.auth-meter[data-score="4"] span {
  background: var(--color-success);
}

.auth-meter-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.auth-meter-row em {
  font-style: normal;
  font-weight: var(--weight-medium);
  color: var(--color-subtle);
}

/* Password checklist — components/password_rules.html, on every page that
   sets a password (reset, portal invite, team profile). Unmet rules stay
   grey while the first attempt is being typed and turn red once the user has
   moved on (.is-attempted), so the checklist reads as a requirement, not a tip. */
.password-rules {
  margin-top: var(--space-3);
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.password-rules__title {
  margin: 0 0 var(--space-2);
  font-weight: var(--weight-medium);
  color: var(--color-subtle);
}

.password-rules__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2) var(--space-4);
}

.password-rules__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  transition: color var(--transition-normal);
}

.password-rules__mark {
  display: inline-flex;
  /* non-token: check glyph */
  width: 12px;
  height: 12px;
  color: var(--color-border);
  flex-shrink: 0;
  transition: color var(--transition-normal);
}

.password-rules__mark svg {
  width: 100%;
  height: 100%;
}

.password-rules__icon-missing {
  display: none;
}

.password-rules__item.is-met {
  color: var(--color-subtle);
}

.password-rules__item.is-met .password-rules__mark {
  color: var(--color-success);
}

.password-rules.is-attempted .password-rules__item:not(.is-met) {
  color: var(--color-danger);
}

.password-rules.is-attempted .password-rules__item:not(.is-met) .password-rules__mark {
  color: var(--color-danger);
}

.password-rules.is-attempted .password-rules__item:not(.is-met) .password-rules__icon-met {
  display: none;
}

.password-rules.is-attempted .password-rules__item:not(.is-met) .password-rules__icon-missing {
  display: block;
}

.password-rules__missing {
  margin: var(--space-2) 0 0;
  font-size: var(--font-sm);
  color: var(--color-danger);
}

.auth-confirm-wrap.is-error .form-control {
  border-color: var(--color-danger);
}

.auth-confirm-wrap.is-match .form-control {
  border-color: var(--color-success);
}

.auth-match-hint.is-error {
  color: var(--color-danger);
}

.auth-match-hint.is-ok {
  color: var(--color-success);
}

/* ── Staff auth — 2FA setup ── */

.auth-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* QR pairing row — frame keeps a quiet border; server renders the
   qr_data_uri at 172px, scaled by the frame. */
.auth-qr {
  display: flex;
  gap: var(--space-5);
  align-items: flex-start;
}

.auth-qr-frame {
  flex-shrink: 0;
  /* non-token: QR frame measure */
  width: 132px;
  aspect-ratio: 1;
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.auth-qr-frame img {
  width: 100%;
  height: 100%;
  display: block;
}

.auth-qr-meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  font-size: var(--font-sm);
  color: var(--color-subtle);
  min-width: 0;
}

details.auth-manual summary {
  cursor: pointer;
  font-size: var(--font-xs);
  color: var(--color-accent);
}

details.auth-manual summary:hover {
  color: var(--color-accent-hover);
  text-decoration: underline;
}

details.auth-manual[open] summary {
  margin-bottom: var(--space-3);
}

.auth-key-row {
  display: flex;
  gap: var(--space-3);
  align-items: stretch;
}

.auth-key-row code {
  flex: 1;
  min-width: 0;
  font-size: var(--font-sm);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  word-break: break-all;
}

.auth-key-row .btn {
  flex-shrink: 0;
  height: auto;
}

.auth-codes {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--color-surface-dim);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.auth-codes-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2) var(--space-5);
  font-size: var(--font-sm);
  color: var(--color-text);
}

.auth-codes-actions {
  display: flex;
  gap: var(--space-3);
}

.auth-codes-actions .btn {
  flex: 1;
  justify-content: center;
}

@media (max-width: 480px) {
  .auth-qr {
    flex-direction: column;
  }

  .auth-codes-grid {
    grid-template-columns: 1fr;
  }
}

/* ── end Staff auth (#797) ── */

/* ── migrated: platform/tools/dev/templates/components.html ── */

.dev-gallery .gallery-header {
  margin-bottom: 2rem;
}

/* Animations page: flat responsive grid, one card per animation. Card =
   soft preview pane on top, then name + sm/md/lg toggle + copy actions
   (animations.html; the toggle/copy behaviour is the page's script block). */
.dev-gallery .gallery-body--anim-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
  gap: 1.5rem;
}

.anim-card {
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.anim-card__preview {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 15rem;
  background: var(--color-team-surface);
  border-bottom: 1px solid var(--color-border-subtle);
}

.anim-card__footer {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1rem 1.25rem 1.25rem;
}

.anim-card__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.anim-card__name {
  margin: 0;
  font-size: var(--font-lg);
  font-weight: 600;
  color: var(--color-text);
}

.anim-card__sizes {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.anim-card__size {
  border: 0;
  background: transparent;
  padding: 0.25rem 0.6rem;
  font-size: var(--font-xs);
  color: var(--color-muted);
  cursor: pointer;
}

.anim-card__size + .anim-card__size {
  border-left: 1px solid var(--color-border);
}

.anim-card__size.is-active {
  color: var(--color-accent-hover);
  background: var(--color-bg);
  font-weight: var(--weight-medium);
}

.anim-card__actions {
  display: flex;
  gap: 0.75rem;
}

.anim-card__actions .btn {
  flex: 1;
  justify-content: center;
}

.dev-gallery .gallery-intro {
  color: var(--color-muted);
  max-width: 48rem;
}

.dev-gallery .gallery-intro code {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  background: var(--color-bg);
  padding: 0.1rem 0.3rem;
  border-radius: var(--radius-xs);
}

.dev-gallery .gallery-category {
  margin-bottom: var(--space-5);
  scroll-margin-top: var(--space-5);
}

.dev-gallery .gallery-category-title {
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
  margin: 0 0 var(--space-5);
}

.dev-gallery .gallery-component {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.5rem;
}

.dev-gallery .gallery-component-header {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.25rem;
}

.dev-gallery .gallery-component-header h3 {
  margin: 0;
  font-size: var(--font-lg);
  font-weight: 600;
  color: var(--color-text);
}

.dev-gallery .gallery-path {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  color: var(--color-muted);
  background: var(--color-bg);
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-xs);
}

.dev-gallery .gallery-component-description {
  margin: 0 0 1rem;
  color: var(--color-muted);
  font-size: var(--font-sm);
  max-width: 60rem;
}

.dev-gallery .gallery-variant-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(24rem, 1fr));
  gap: 1.25rem;
}

@media (max-width: 640px) {
  .dev-gallery .gallery-variant-grid {
    grid-template-columns: 1fr;
  }
}

.dev-gallery .gallery-variant {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-bg);
  display: flex;
  flex-direction: column;
}

.dev-gallery .gallery-variant-label {
  padding: 0.4rem 0.75rem;
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
}

.dev-gallery .gallery-frame {
  padding: 1rem;
  background: var(--color-surface);
  overflow-x: auto;
  pointer-events: none;
}

.dev-gallery .gallery-frame>* {
  max-width: 100%;
}

.dev-gallery .gallery-snippet {
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
}

.dev-gallery .gallery-snippet>summary {
  padding: 0.5rem 0.75rem;
  font-size: var(--font-xs);
  color: var(--color-muted);
  cursor: pointer;
  user-select: none;
}

.dev-gallery .gallery-snippet>summary:hover {
  color: var(--color-text);
}

.dev-gallery .gallery-snippet pre {
  margin: 0;
  padding: 0.75rem;
  background: var(--color-bg);
  border-top: 1px solid var(--color-border);
  font-size: var(--font-sm);
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.dev-gallery .gallery-snippet code {
  background: none;
  padding: 0;
  color: var(--color-text);
}

/* Agent-handoff cells used on /design-system/components/button.
   .data-table provides the table chrome (header band, dividers); these
   two rules just nest the live preview and keep the handoff button
   from wrapping mid-label. */
.dev-gallery .gallery-handoff-preview {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
}

.dev-gallery .gallery-handoff-btn {
  white-space: nowrap;
}

/* Re-clip to the card's rounded corners. The global `.lv-host` rule
   sets `overflow: visible` so saved-views / columns-picker popovers
   can escape the card; the button page has none of those, so the
   table's thead background spills past the radius. Restoring
   `overflow: hidden` for this scope keeps the rounded corners crisp. */
.lv-host[data-lv-scope="button"],
.lv-host[data-lv-scope="icon"] {
  overflow: hidden;
}

/* Shared Lucide icon — rendered by the icon() macro
   (components/icon.html). Inherits text color so a parent's
   `color: var(--color-muted)` (etc.) propagates to the stroke. */
.icon {
  display: inline-block;
  vertical-align: middle;
  color: currentColor;
}

/* Aliases column on /design-system/icons — muted so the row reads
   as "name (with synonyms)" rather than two equal columns. */
.ds-icon-aliases {
  color: var(--color-muted);
}

/* "lucide.dev ↗" link in the icons-page header — same .btn-ghost-sm
   chrome as other header actions, but with a small gap so the
   external-link glyph sits cleanly after the label. */
.ds-icons-source-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* ── end migrated: platform/tools/dev/templates/components.html ── */

/* ── migrated: platform/tools/dev/templates/tokens.html ── */

.dev-gallery .tokens-header {
  margin-bottom: 2rem;
}

.dev-gallery .tokens-intro {
  color: var(--color-muted);
  max-width: 48rem;
}

.dev-gallery .tokens-intro code {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  background: var(--color-bg);
  padding: 0.1rem 0.3rem;
  border-radius: var(--radius-xs);
}

.dev-gallery .tokens-category {
  margin-bottom: var(--space-5);
  scroll-margin-top: var(--space-5);
}

.dev-gallery .tokens-category-title {
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
  margin: 0 0 var(--space-5);
}

.dev-gallery .tokens-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  gap: 1rem;
}

@media (max-width: 640px) {
  .dev-gallery .tokens-grid {
    grid-template-columns: 1fr;
  }
}

.dev-gallery .token-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-4) calc(var(--space-4) + 2px);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.dev-gallery .token-sample {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 72px;
  background: var(--color-bg);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

/* Sample bodies */
.dev-gallery .swatch {
  width: 100%;
  height: 100%;
}

.dev-gallery .radius-sample {
  width: 48px;
  height: 48px;
  background: var(--color-accent);
}

.dev-gallery .shadow-sample {
  width: 60%;
  height: 44px;
  background: var(--color-surface);
  border-radius: var(--radius-md);
}

.dev-gallery .space-sample {
  background: var(--color-accent);
  border-radius: var(--radius-xs);
}

/* Display specimen — oversized to fill the token-card preview. Not a UI
   size; do not promote to a token. */
.dev-gallery .type-sample {
  font-family: var(--font);
  font-size: 1.75rem;
  color: var(--color-text);
}

.dev-gallery .type-sample--lh {
  font-size: var(--font-base);
  line-height: inherit;
  text-align: center;
}

.dev-gallery .transition-sample {
  width: 28px;
  height: 28px;
  background: var(--color-accent);
  border-radius: 50%;
  animation: dev-gallery-token-transition-bounce 1.6s ease-in-out infinite alternate;
}

@keyframes dev-gallery-token-transition-bounce {
  from {
    transform: translateX(-60px);
  }

  to {
    transform: translateX(60px);
  }
}

/* Display specimen — large numeral preview of the z-index value. Not a
   UI size; do not promote to a token. */
.dev-gallery .z-sample {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: 1.5rem;
  font-weight: var(--weight-semibold);
  color: var(--color-accent);
}

.dev-gallery .opacity-sample {
  width: 60%;
  height: 100%;
}

/* Card meta — design-handoff bundle from /design/SiedqbpmbzjYL9aziEfJLg.
   Name + value share a baseline row; description below. All text at --font-xs
   normal with line-height: 1 so mono labels sit tight on their glyph height. */
.dev-gallery .token-meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.dev-gallery .token-meta__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.dev-gallery .token-name {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: 1;
  color: var(--color-text-strong);
}

.dev-gallery .token-value {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: 1;
  color: var(--color-muted);
  word-break: break-all;
}

.dev-gallery .token-description {
  margin: 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: 1;
  color: var(--color-muted);
}

/* Colour swatches: 2px nudge so they settle above the meta row. */
.dev-gallery .token-sample--color {
  margin-bottom: 2px;
}

/* Scale-family sub-heading inside the Colour scales category (Slate /
   Accent / Green / Amber) — same voice as the category title, one size in. */
.dev-gallery .tokens-subgroup-title {
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  line-height: var(--line-height-tight);
  color: var(--color-subtle);
  margin: var(--space-5) 0 var(--space-3);
}

.dev-gallery .tokens-subgroup-title:first-of-type {
  margin-top: 0;
}

/* Compact colour grid — smaller swatch cards so whole colour scales fit on
   one screen. Applied to the Colour + Colour scales categories. */
.dev-gallery .tokens-grid--compact {
  grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr));
  gap: var(--space-3);
}

.dev-gallery .tokens-grid--compact .token-card {
  gap: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-3);
}

.dev-gallery .tokens-grid--compact .token-sample {
  height: 44px;
}

.dev-gallery .tokens-grid--compact .token-meta {
  gap: var(--space-1);
}

/* Compact cards are narrow enough that descriptions wrap — give the wrapped
   lines breathing room (the wide-card default is line-height 1). */
.dev-gallery .tokens-grid--compact .token-description {
  line-height: var(--line-height-tight);
}

/* ── end migrated: platform/tools/dev/templates/tokens.html ── */

/* ── migrated: platform/tools/system/templates/design.html ── */

.dev-gallery .gallery-header {
  margin-bottom: 2rem;
}

.dev-gallery .gallery-intro {
  color: var(--color-muted);
  max-width: 52rem;
}

.dev-gallery .gallery-intro strong {
  color: var(--color-text);
}

.dev-gallery .gallery-intro code {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  background: var(--color-bg);
  padding: 0.1rem 0.3rem;
  border-radius: var(--radius-xs);
}

.dev-gallery .gallery-intro a {
  color: var(--color-accent);
  text-decoration: none;
}

.dev-gallery .gallery-intro a:hover {
  text-decoration: underline;
}

.dev-gallery .gallery-toolbar {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* Components page hides the notification overlays so they don't sit over
   the gallery — toggled back on by the gallery-notifications-toggle pill. */
.team-shell.gallery-notifications-hidden .flash-stack,
.team-shell.gallery-notifications-hidden #toast-container {
  display: none;
}

.dev-gallery .gallery-component {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.5rem;
}

.dev-gallery .gallery-component-header {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.25rem;
}

.dev-gallery .gallery-component-header h3 {
  margin: 0;
  font-size: var(--font-lg);
  font-weight: 600;
  color: var(--color-text);
}

.dev-gallery .gallery-path {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  color: var(--color-muted);
  background: var(--color-bg);
  padding: 0.15rem 0.45rem;
  border-radius: var(--radius-xs);
}

.dev-gallery .gallery-component-description {
  margin: 0 0 1rem;
  color: var(--color-muted);
  font-size: var(--font-sm);
  max-width: 60rem;
}

.dev-gallery .gallery-variant-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(24rem, 1fr));
  gap: 1.25rem;
}

@media (max-width: 640px) {
  .dev-gallery .gallery-variant-grid {
    grid-template-columns: 1fr;
  }
}

.dev-gallery .gallery-variant {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-bg);
  display: flex;
  flex-direction: column;
}

.dev-gallery .gallery-variant-label {
  padding: 0.4rem 0.75rem;
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
}

.dev-gallery .gallery-frame {
  padding: 1rem;
  background: var(--color-surface);
  overflow-x: auto;
  pointer-events: none;
}

.dev-gallery .gallery-frame>* {
  max-width: 100%;
}

.dev-gallery .gallery-snippet {
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
}

.dev-gallery .gallery-snippet>summary {
  padding: 0.5rem 0.75rem;
  font-size: var(--font-xs);
  color: var(--color-muted);
  cursor: pointer;
  user-select: none;
}

.dev-gallery .gallery-snippet>summary:hover {
  color: var(--color-text);
}

.dev-gallery .gallery-snippet pre {
  margin: 0;
  padding: 0.75rem;
  background: var(--color-bg);
  border-top: 1px solid var(--color-border);
  font-size: var(--font-sm);
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.dev-gallery .gallery-snippet code {
  background: none;
  padding: 0;
  color: var(--color-text);
}

/* Mobile preview — constrains each variant frame to 375px wide */
.dev-gallery .gallery-body.gallery--mobile .gallery-variant-grid {
  grid-template-columns: 375px;
}

.dev-gallery .gallery-body.gallery--mobile .gallery-frame {
  max-width: 375px;
}

.dev-gallery .gallery-frame:not([inert]) {
  pointer-events: auto;
  overflow: visible;
}
.dev-gallery .gallery-variant:has(.gallery-frame:not([inert])) { overflow: visible; }

/* ── end migrated: platform/tools/system/templates/design.html ── */

/* ── platform/tools/dev/templates/typography.html ── */

.dev-gallery.typography-page {
  max-width: 62rem;
}

/* Pill section nav — passive anchor links. Sticky kept for legacy callers
   that still render pillnav inside a scrolling .main-content body; pages
   that render it in {% block main_header %} (e.g. /dev/tokens) get the
   pinned-by-structure version for free. */
.dev-pillnav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  margin-bottom: 2.5rem;
  padding: 0.75rem 0;
  background: var(--color-team-surface);
}

.dev-pillnav a {
  padding: 0.35rem 0.85rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--color-subtle);
  text-decoration: none;
  transition: background var(--transition-fast) ease;
}

.dev-pillnav a:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.dev-gallery .typography-section {
  margin-bottom: var(--space-5);
  scroll-margin-top: var(--space-5);
}

.dev-gallery .typography-section-title {
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
  margin: 0 0 var(--space-5);
}

.dev-gallery .typography-subhead {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
  margin: 1.75rem 0 0.85rem;
}

.dev-gallery .typography-prose {
  font-size: var(--font-base);
  color: var(--color-subtle);
  line-height: var(--line-height-relaxed);
  max-width: 48rem;
  margin: 0 0 0.85rem;
}

.dev-gallery .typography-prose--note {
  font-size: var(--font-sm);
  color: var(--color-muted);
  border-left: 2px solid var(--color-border);
  padding-left: 0.75rem;
}

/* Fonts section */
.dev-gallery .typography-family-card {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  padding: 1.25rem;
  margin-bottom: 1rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

/* Display specimen — showcases the loaded font family. Not a UI size;
   do not promote to a token. */
.dev-gallery .typography-family-specimen {
  font-size: 1.85rem;
  line-height: var(--line-height-tight);
  color: var(--color-text);
}

.dev-gallery .typography-family-meta {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.dev-gallery .typography-family-head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.dev-gallery .typography-family-label {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}

.dev-gallery .typography-family-loaded {
  font-size: var(--font-xs);
  color: var(--color-muted);
  line-height: var(--line-height-normal);
  margin: 0.35rem 0 0;
}

/* Type scale section */
.dev-gallery .typography-spec-list {
  display: flex;
  flex-direction: column;
}

.dev-gallery .typography-spec-row {
  display: grid;
  grid-template-columns: 1fr 16rem;
  gap: 1.5rem;
  align-items: center;
  padding: 1rem 0;
  border-bottom: 1px solid var(--color-border-subtle);
}

.dev-gallery .typography-spec-row:last-child {
  border-bottom: 0;
}

.dev-gallery .typography-specimen {
  color: var(--color-text);
}

/* Display specimen — large weights/tracking preview. Not a UI size;
   do not promote to a token. */
.dev-gallery .typography-specimen--md {
  font-size: 1.5rem;
}

.dev-gallery .typography-specimen--lh {
  font-size: var(--font-base);
}

@media (max-width: 720px) {
  .dev-gallery .typography-spec-row {
    grid-template-columns: 1fr;
    gap: 0.5rem;
  }
}

/* Applying type section */
.dev-gallery .typography-role-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  gap: 1rem;
}

.dev-gallery .typography-role-card {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1.25rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.dev-gallery .typography-role-example {
  color: var(--color-text);
  padding: 0.85rem;
  background: var(--color-bg);
  border-radius: var(--radius-sm);
}

.dev-gallery .typography-role-name {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
  margin: 0;
}

.dev-gallery .typography-role-tokens {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.dev-gallery .typography-role-tokens li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

.dev-gallery .typography-role-tokens code {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  color: var(--color-text);
}

.dev-gallery .typography-role-tokens span {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* Editorial treatments section */
.dev-gallery .typography-note-card {
  padding: 1.1rem 1.25rem;
  margin-bottom: 0.85rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.dev-gallery .typography-note-title {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
  margin: 0 0 0.4rem;
}

.dev-gallery .typography-note-scope {
  font-size: var(--font-xs);
  color: var(--color-muted);
  margin: 0;
}

/* Agent handoff section */
.dev-gallery .typography-handoff {
  position: relative;
}

.dev-gallery .typography-copy-btn {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  padding: 0.3rem 0.7rem;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.dev-gallery .typography-copy-btn:hover {
  background: var(--color-hover);
}

.dev-gallery .typography-spec {
  margin: 0;
  padding: 1rem 1.1rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow-x: auto;
}

.dev-gallery .typography-spec code {
  font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
  font-size: 0.75rem;
  line-height: var(--line-height-relaxed);
  color: var(--color-text);
  white-space: pre;
}

/* ── end platform/tools/dev/templates/typography.html ── */

/* ── migrated: platform/tools/onboarding/templates/help_menu.html ── */
/* Rendered inside #help-panel-body (base_team.html); requires .help-panel-body ancestor. */

.help-panel-body .help-section {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--color-border);
}

.help-panel-body .help-section:last-child {
  border-bottom: none;
}

.help-panel-body .help-section-title {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-muted);
  margin-bottom: 0.5rem;
}

.help-panel-body .help-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0;
  font-size: 0.82rem;
  color: var(--color-text);
  text-decoration: none;
}

.help-panel-body .help-link:hover {
  color: var(--color-accent);
}

.help-panel-body .help-shortcut-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.25rem 0;
  font-size: 0.8rem;
}

.help-panel-body .help-shortcut-key {
  font-size: 0.7rem;
  font-family: monospace;
  background: var(--color-bg);
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-border);
  color: var(--color-muted);
}

/* ── end migrated: platform/tools/onboarding/templates/help_menu.html ── */

/* ── migrated: platform/tools/onboarding/templates/partials/whats_new.html ── */
/* HTMX-loaded fragment inside help_menu.html; requires .help-panel-body ancestor. */

.help-panel-body .onb-whatsnew {
  list-style: none;
  padding: 0;
  margin: 0;
}

.help-panel-body .onb-whatsnew-item {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--color-border);
}

.help-panel-body .onb-whatsnew-item:last-child {
  border-bottom: none;
}

.help-panel-body .onb-whatsnew-title {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--color-text);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.help-panel-body .onb-whatsnew-content {
  font-size: 0.78rem;
  color: var(--color-muted);
  margin-top: 0.2rem;
  line-height: 1.4;
}

.help-panel-body .onb-whatsnew-date {
  font-size: 0.7rem;
  color: var(--color-muted);
  margin-top: 0.15rem;
}

/* An update's "Read Help" (#1315). */
.help-panel-body .onb-whatsnew-date a {
  color: var(--color-accent);
  text-decoration: none;
}

.help-panel-body .onb-whatsnew-date a:hover {
  text-decoration: underline;
}

.help-panel-body .onb-whatsnew-empty {
  font-size: 0.8rem;
  color: var(--color-muted);
  font-style: italic;
}

/* ── end migrated: platform/tools/onboarding/templates/partials/whats_new.html ── */

/* ── migrated: platform/tools/onboarding/templates/partials/tips.html ── */
/* HTMX-loaded fragment inside help_menu.html; requires .help-panel-body ancestor. */

.help-panel-body .onb-tip {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.4rem 0;
  font-size: 0.82rem;
  color: var(--color-text);
}

.help-panel-body .onb-tip-icon {
  flex-shrink: 0;
  color: var(--color-accent);
  margin-top: 0.1rem;
}

.help-panel-body .onb-tip-body {
  flex: 1;
}

.help-panel-body .onb-tip-title {
  font-weight: 600;
  font-size: 0.82rem;
  margin-bottom: 0.15rem;
}

.help-panel-body .onb-tip-content {
  font-size: 0.8rem;
  color: var(--color-text);
}

.help-panel-body .onb-tip-dismiss {
  display: inline-block;
  margin-top: 0.35rem;
  padding: 0.2rem 0.5rem;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-accent) 20%, transparent);
  border-radius: var(--radius-xs);
  cursor: pointer;
  font-family: inherit;
  transition: background 0.15s;
}

.help-panel-body .onb-tip-dismiss:hover {
  background: color-mix(in srgb, var(--color-accent) 15%, transparent);
}

.help-panel-body .onb-tips-empty {
  font-size: 0.8rem;
  color: var(--color-muted);
  font-style: italic;
}

/* ── end migrated: platform/tools/onboarding/templates/partials/tips.html ── */

/* "Help for this page" — tools/library/templates/partials/library_help_pages.html,
   loaded into help_menu.html; requires .help-panel-body ancestor. Entries are .help-link. */

.help-panel-body .help-empty {
  font-size: var(--font-sm);
  color: var(--color-muted);
  font-style: italic;
}

/* *This page is wrong* under a Help page: quieter, and lined up with the
   page's title past its icon (14px icon + the row's gap). */
.help-panel-body .help-link--quiet {
  padding: 0 0 var(--space-1) calc(var(--space-5) + var(--space-2));
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* ── migrated: platform/tools/library/templates/library_librarian_queue.html ── */

.librarian-queue .ingest-tab.active {
  background: var(--color-primary, #4a6cf7);
  color: #fff;
}

/* ── end migrated: platform/tools/library/templates/library_librarian_queue.html ── */

/* ── migrated: platform/tools/system/templates/system_audit.html ── */

/* ── Email preview (partials/email_preview.html): one mail rendered with
   dummy data, inside the preview modal on /system/emails/templates or in a
   card on its own page. The frame sizes itself to the mail (onload in the
   partial); the plain-text part folds under it. */
.card > .email-preview {
  padding: var(--space-5) 0 0;
}

.email-preview__meta,
.email-preview__description {
  margin: 0;
  padding: 0 var(--space-5);
}

.card > .email-preview > .email-preview__meta,
.card > .email-preview > .email-preview__description {
  padding: 0 var(--space-5);
}

.modal-body .email-preview__meta,
.modal-body .email-preview__description {
  padding: 0;
}

.email-preview__description {
  margin-top: var(--space-1);
  margin-bottom: var(--space-4);
}

.email-preview__frame {
  display: block;
  width: 100%;
  border: 0;
  overflow: hidden;
  background: var(--color-surface);
}

.email-preview__plain {
  border-top: 1px solid var(--color-border);
}

.email-preview__plain > summary {
  cursor: pointer;
  color: var(--color-accent);
  padding: var(--space-4) var(--space-5);
}

.modal-body .email-preview__plain > summary {
  padding: var(--space-4) 0;
}

.email-preview__text {
  margin: 0;
  padding: 0 var(--space-5) var(--space-5);
  font-family: inherit;
  font-size: var(--font-xs);
  white-space: pre-wrap;
  overflow-x: auto;
}

.modal-body .email-preview__text {
  padding: 0 0 var(--space-4);
}

/* The settings modal's form: one field, its hint, the two buttons. */
.settings-form .form-actions {
  padding-top: var(--space-5);
}

/* ── /system/emails/templates (/ux pass, 2026-10-09) ─────────────────────
   The registry table groups its rows under a label row; the test-progress
   score is a tag the script retones; the Test Email modal is three
   numbered steps on the surface-muted ground. */
.data-table th.data-table__group {
  text-align: left;
  background: var(--color-bg);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-muted);
}

.send-step {
  padding: var(--space-5);
  background: var(--color-bg);
  border-radius: var(--radius-sm);
}

.send-step + .send-step,
.modal-body > .alert + .send-step,
.modal-body > p + .send-step {
  margin-top: var(--space-5);
}

.send-step__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.send-step__head > p {
  margin: 0;
}

/* The step number: a filled badge, sized to read as a marker. */
.badge.send-step__num {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  font-size: var(--font-xs);
  line-height: 1;
}

.send-step__link {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-accent);
  text-decoration: none;
}

/* The step's body indents under its number (24px marker + the head gap). */
.send-step__body {
  padding-left: calc(24px + var(--space-3));
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.send-step__body > p {
  margin: 0;
}

.send-step__body > .form-label {
  margin-top: var(--space-3);
}

.send-step__body > .btn {
  margin-top: var(--space-3);
  align-self: flex-start;
}

.send-step__body .form-group {
  margin-bottom: var(--space-3);
}

.modal-body > .modal-lead {
  margin: 0 0 var(--space-5);
}

/* ── /system/audit (/ux pass, 2026-10-09) ────────────────────────────────
   The filter toolbar opens the card, so it carries the card inset; the
   table below it is wrapped (htmx target) and the footer closes the card. */
.system-audit .toolbar {
  padding: var(--space-5);
}

.system-audit .export-dropdown {
  position: relative;
  display: inline-block;
}

.system-audit .export-menu {
  position: absolute;
  left: 0;
  top: calc(100% + var(--space-1));
  min-width: 180px; /* the longest item, "Bundle (.tar.gz)", on one line */
  padding: var(--space-1) 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-floating);
  z-index: var(--z-dropdown);
}

.system-audit .export-menu-item {
  display: block;
  width: 100%;
  padding: 8px 14px;
  border: none;
  background: none;
  text-align: left;
  font-size: 0.875rem;
  color: var(--color-text);
  cursor: pointer;
}

.system-audit .export-menu-item:hover {
  background: var(--color-border-subtle);
}

/* ── end migrated: platform/tools/system/templates/system_audit.html ── */

/* ── migrated: platform/tools/library/templates/library_librarian_review.html ── */

.librarian-review .ingest-review-panels {
  display: flex;
  height: 55vh;
  min-height: 300px;
  gap: 0;
}

.librarian-review .ingest-panel-left {
  flex: 1;
  min-width: 200px;
  overflow: hidden;
}

.librarian-review .ingest-panel-right {
  flex: 1;
  min-width: 200px;
  overflow: hidden;
}

/* The right panel's content sits in a form, a flex item between the card
   and the scrolling area. A flex item grows to fit what it holds unless told
   it may shrink, and then the scrolling area is as tall as the text and the
   panel clips it instead of scrolling. */
.librarian-review .ingest-panel-right form,
.librarian-review .ingest-panel-right form > div {
  min-height: 0;
}

.librarian-review .ingest-resize-handle {
  width: 6px;
  cursor: col-resize;
  background: var(--color-border, #e2e2e2);
  flex-shrink: 0;
  transition: background 0.15s;
}

.librarian-review .ingest-resize-handle:hover,
.librarian-review .ingest-resize-handle.dragging {
  background: var(--color-primary, #4a6cf7);
}

/* Rendered markdown */
.librarian-review .md-rendered h1 {
  font-size: 1.4rem;
  font-weight: 700;
  margin: 1rem 0 0.5rem;
}

.librarian-review .md-rendered h2 {
  font-size: 1.15rem;
  font-weight: 600;
  margin: 1rem 0 0.4rem;
}

.librarian-review .md-rendered h3 {
  font-size: 1rem;
  font-weight: 600;
  margin: 0.75rem 0 0.3rem;
}

.librarian-review .md-rendered p {
  margin: 0.4rem 0;
}

.librarian-review .md-rendered ul,
.librarian-review .md-rendered ol {
  margin: 0.4rem 0;
  padding-left: 1.5rem;
}

.librarian-review .md-rendered li {
  margin: 0.2rem 0;
}

.librarian-review .md-rendered code {
  background: rgba(0, 0, 0, 0.06);
  padding: 0.1rem 0.3rem;
  border-radius: var(--radius-xs);
  font-size: 0.85em;
}

.librarian-review .md-rendered pre {
  background: rgba(0, 0, 0, 0.04);
  padding: 0.75rem;
  border-radius: var(--radius-xs);
  overflow-x: auto;
}

.librarian-review .md-rendered pre code {
  background: none;
  padding: 0;
}

.librarian-review .md-rendered blockquote {
  border-left: 3px solid var(--color-border, #e2e2e2);
  padding-left: 0.75rem;
  color: var(--color-muted);
  margin: 0.5rem 0;
}

.librarian-review .md-rendered a {
  color: var(--color-primary, #4a6cf7);
}

.librarian-review .md-rendered table {
  border-collapse: collapse;
  margin: 0.5rem 0;
}

.librarian-review .md-rendered th,
.librarian-review .md-rendered td {
  border: 1px solid var(--color-border, #e2e2e2);
  padding: 0.3rem 0.6rem;
  font-size: 0.85rem;
}

/* Shimmer animation */
.librarian-review .ingest-shimmer {
  height: 8px;
  width: 60%;
  margin: 0 auto;
  border-radius: var(--radius-xs);
  background: linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s ease-in-out infinite;
}

/* Keyframe name kept global — unique across repo. */
@keyframes shimmer {
  0% {
    background-position: 200% 0;
  }

  100% {
    background-position: -200% 0;
  }
}

/* ── end migrated: platform/tools/library/templates/library_librarian_review.html ── */

/* ── Library menu (partials/library_nav.html, #1295) ─────────────────────────
   The tree is the sidebar's own parts — .sb-row for a top-level folder,
   .sb-child below it, .sb-children with its tie line for each level — at a
   compact indent: every level steps in one --space-5 (16px) so a page four
   folders deep still fits the menu (round H rejected the 28px case-rail
   indent for squeezing deep names). Folders read darker than pages. */

.sb--library .sb-row__count {
  font-size: var(--font-xxs);
}

/* New page on the heading — only while a page is open (one place at a time).
   A compact accent-outline button sized to the menu row (round H). */
.sb--library .lib-nav__new-page {
  flex-shrink: 0;
  width: auto;
  margin: 0;
  padding: 0 var(--space-3);
  border: 1px solid var(--color-accent-200);
  font-weight: var(--weight-medium);
  color: var(--color-accent);
}

.sb--library .lib-nav__new-page:hover {
  color: var(--color-accent-700);
  background: var(--color-accent-50);
}

/* A folder's children open when its row is expanded. The row sits in a
   .sb-row-host (for the hover +), so the group follows the host. */
.lib-nav__host:has(> [aria-expanded="true"]) + .lib-nav__group {
  display: block;
}

.lib-nav__group .lib-nav__group {
  margin: 1px 0 var(--space-1);
  padding-left: var(--space-5);
}

.lib-nav__group .lib-nav__group::before {
  left: var(--space-2);
}

/* Folders read darker than pages, at every level — `.sb a` mutes links. */
.sb--library .lib-nav__folder {
  color: var(--color-text);
}

.lib-nav__folder[aria-expanded="true"] > .sb-row__caret {
  transform: rotate(90deg);
}

.lib-nav__folder .sb-row__caret {
  margin-left: 0;
}

.lib-nav__folder .sb-row__count {
  margin-left: auto;
}

.sb--library .lib-nav__page {
  color: var(--color-muted);
}

.sb--library .lib-nav__page:hover,
.sb--library .lib-nav__page[aria-current="page"] {
  color: var(--color-text-strong);
}

/* The hover + sits just inside the caret, and the count makes way for it. */
.lib-nav__host .sb-row-add {
  right: calc(var(--sb-row-margin-x) + var(--sb-row-pad) + var(--space-5));
}

.lib-nav__host:hover .sb-row__count {
  visibility: hidden;
}

.lib-nav__empty {
  margin: var(--space-1) var(--sb-group-pad-x);
  font-size: var(--font-xs);
  color: var(--color-muted);
  line-height: var(--line-height-normal);
}

/* ── Library Home (library_home.html, round H2 Home.dc.html) ──────────────────
   A header bar across the content card with the side pane flush beneath it.
   The lib-* parts are bespoke by choice (DECISIONS.md §H2 · Home). */

.lib-split {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
}

.lib-split > .main-content {
  flex: 1 1 auto;
}

.lib-split > .lib-pane {
  flex: 0 0 340px;
  width: 340px;
}

.lib-split--pane-hidden > .lib-pane {
  display: none;
}

.lib-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
}

.lib-stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  border-left: 1px solid var(--color-border-subtle);
  min-width: 0;
  color: inherit;
  text-decoration: none;
}

.lib-stat:first-child {
  border-left: 0;
}

a.lib-stat:hover {
  background: var(--color-hover);
}

a.lib-stat:hover .lib-stat__l {
  color: var(--color-accent);
}

.lib-stat__l {
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

.lib-stat__v {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.lib-stat__n {
  font-size: var(--font-xl);
  font-weight: var(--weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-strong);
  font-variant-numeric: tabular-nums;
}

.lib-stat__s {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.lib-stat__s--up {
  color: var(--color-success);
}

.lib-teasers {
  display: flex;
  flex-direction: column;
}

.lib-teasers__empty {
  margin: 0;
  padding: var(--space-4) var(--space-5);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.lib-teaser {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--color-border-subtle);
  min-width: 0;
}

.lib-teaser:first-child {
  border-top: 0;
}

.lib-teaser__path {
  font-size: var(--font-xs);
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lib-teaser__title {
  margin: 0;
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
}

.lib-teaser__title a {
  color: var(--color-accent);
  text-decoration: none;
}

.lib-teaser__title a:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.lib-teaser__lede {
  margin: 0;
  font-size: var(--font-xs);
  line-height: var(--line-height-normal);
  color: var(--color-text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.lib-teaser__foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.lib-teaser__meta {
  margin-left: auto;
  font-size: var(--font-xs);
  color: var(--color-muted);
  white-space: nowrap;
}

.lib-desk {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  background: var(--color-accent-50);
  border: 1px solid var(--color-accent-200);
  border-radius: var(--radius-md);
}

.lib-desk__top {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.lib-desk__ico {
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
  background: var(--color-accent);
  color: var(--color-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.lib-desk__name {
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-accent-800);
}

.lib-desk__body {
  margin: 0;
  font-size: var(--font-xs);
  line-height: var(--line-height-normal);
  color: var(--color-text);
}

.lib-desk__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.lib-desk__list a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 26px;
  padding: 0 var(--space-2);
  margin: 0 calc(var(--space-2) * -1);
  border-radius: var(--radius-sm);
  font-size: var(--font-xs);
  color: var(--color-text);
  text-decoration: none;
}

.lib-desk__list a:hover {
  background: var(--color-surface);
  color: var(--color-accent-700);
}

.lib-desk__n {
  font-weight: var(--weight-semibold);
  color: var(--color-accent-700);
  font-variant-numeric: tabular-nums;
}

.lib-desk__acts {
  display: flex;
  gap: var(--space-3);
  margin-top: 2px;
}

.lib-feed {
  display: flex;
  flex-direction: column;
  padding: 0 var(--space-5) var(--space-5);
}

.lib-feed__row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  font-size: var(--font-xs);
  line-height: var(--line-height-normal);
  color: var(--color-text);
}

.lib-feed__tx {
  flex: 1;
  min-width: 0;
}

.lib-feed__tx a {
  color: var(--color-accent);
  text-decoration: none;
}

.lib-feed__tx a:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.lib-feed__tm {
  flex: 0 0 auto;
  color: var(--color-muted);
}

.lib-links {
  display: flex;
  flex-direction: column;
  padding: 0 var(--space-5) var(--space-4);
}

.lib-links a {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  font-size: var(--font-xs);
  color: var(--color-accent);
  text-decoration: none;
}

.lib-links a:hover .lib-links__t {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.lib-links svg {
  flex-shrink: 0;
  color: var(--color-muted);
}

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

.lib-links__m {
  margin-left: auto;
  color: var(--color-muted);
  white-space: nowrap;
}

/* ── Needs re-verifying (library_reverify.html, round H2 Reverify.dc.html) ──
   The section strip sits directly under the unchanged one-line header. */

.lib-strip {
  padding: 0 var(--space-ws-gutter);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border-subtle);
}

/* The strip's tabs are links here, where the relationship doc's are buttons. */
.lib-strip .section-tab {
  text-decoration: none;
}

/* ── Updates (#1315) — the read pane of the one Updates screen (round H4, 6c):
   what changed, as written, then its picture. */

.lib-updates__content {
  margin: 0;
  color: var(--color-text);
  white-space: pre-line;
}

/* The picture of the part of the screen an update changed (#1332): a framed
   thumbnail, opened full size in a new tab. */
.lib-updates__image {
  display: inline-block;
  margin-top: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  line-height: 0;
}

.lib-updates__image img {
  display: block;
  max-width: 100%;
  max-height: 240px;
}

/* ── Registers and the Queue (round H3: Rules.dc.html, Examples.dc.html,
   Intake.dc.html) — the rows' title cells, and "Add to queue". The registers'
   toolbar is the list view's since round H4. */

/* A row's title cell: an id tag, then the title, cut with an ellipsis. */
.lib-row-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.lib-row-title a {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The Queue's source line under an item's title. */
.lib-item-sub {
  margin-top: 2px;
  color: var(--color-muted);
}

/* The way back to the card an item came from — in the muted source line,
   so it reads as part of it until hovered. */
.lib-source-link {
  color: inherit;
  white-space: nowrap;
  text-decoration: none;
}

.lib-source-link:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.lib-reads {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* What Guru knew of a card, above the Review panels. */
.librarian-guru {
  margin-bottom: var(--space-4);
}

.lib-inline-form {
  display: inline;
  margin: 0;
}

/* Help runner (#1320): the texts kept out of a packet, and the problems that
   refused a bundle — a plain list inside a card. */
.help-runner-list {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-5);
}

.help-runner-list li + li {
  margin-top: var(--space-2);
}

.lib-intake .seg-toggle {
  margin-bottom: var(--space-4);
}

/* ── Everything about X (round H2 About.dc.html) — library_about.html and
   Home's way in. One .data-table with bucket rows, as the Docs activity table
   groups. */

/* A section title that is the whole row — Home's way into the About views. */
.page-grid .system-section-head.lib-head--wide > :first-child {
  flex-basis: 100%;
}

.lib-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-5) var(--space-5);
}

.lib-tags a.tag {
  text-decoration: none;
}

.lib-tags__n {
  margin-left: var(--space-1);
  font-variant-numeric: tabular-nums;
}

.lib-filter-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.lib-filter-row .seg-toggle {
  margin-left: auto;
}

.lib-filter-row .seg-toggle-opt {
  text-decoration: none;
}

.lib-page-link {
  font-weight: var(--weight-medium);
  text-decoration: none;
}

.lib-page-link:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.lib-cell-sub {
  margin-top: 2px;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lib-cell-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.lib-cell-tags a.tag {
  text-decoration: none;
}

.lib-grouped svg.lib-folder,
.lib-page-link svg.lib-folder {
  vertical-align: -2px;
  margin-right: var(--space-2);
  color: var(--color-muted);
}

/* Air above each folder group after the first (DECISIONS.md §H2 · About) —
   in one <tbody>, or one per group on the list view. */
.lib-grouped tbody .bucket-row:not(:first-child) td,
.lib-grouped .lv-group + .lv-group > .bucket-row td {
  border-top: var(--space-6) solid var(--color-surface);
}

.lib-rules {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.lib-rule {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-dim);
  text-decoration: none;
  color: var(--color-text);
}

.lib-rule:hover {
  border-color: var(--color-accent-200);
  color: var(--color-text);
}

.lib-rule__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.lib-rule__t {
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
}

.lib-rule__p {
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

.lib-pane .card-header a {
  font-size: var(--font-xs);
  color: var(--color-accent);
  text-decoration: none;
  white-space: nowrap;
}

.card--padded .lib-links,
.card--padded .lib-teasers__empty {
  padding: 0;
}

.lib-move-list {
  display: flex;
  flex-direction: column;
  max-height: 22rem;
  overflow-y: auto;
  margin: var(--space-3) 0 var(--space-4);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
}

.lib-move-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
  font-size: var(--font-xs);
  cursor: pointer;
}

.lib-move-item:first-child {
  border-top: 0;
}

.lib-move-item:hover {
  background: var(--color-hover);
}

.lib-move-item__t {
  flex: 1;
  min-width: 0;
  color: var(--color-text-strong);
}

.lib-move-item__p {
  color: var(--color-muted);
  white-space: nowrap;
}

/* ── Library pages (interim surface, #1292 — slice C replaces it) ── */

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

.librarian-filing {
  margin-top: var(--space-4);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.librarian-filing__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--space-3);
  align-items: end;
}

.librarian-filing__row .form-group {
  margin: 0;
}

.librarian-filing__row--end {
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)) auto auto;
}

.librarian-filing__fixed {
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--color-surface-muted);
}

/* ── Library page — reader and editor (library_page_detail.html,
   library_page_edit.html; round H Main.dc.html / Editor.dc.html) ─────────────
   A reading column centred in the content card, the side pane flush beside
   it (.lib-split from Home). A page reads in the app's own type — Inter and
   the existing size scale, no reading face (decisions.md, 2026-10-10). */

/* The page brings its own padding: the reading column, and in the editor a
   toolbar flush under the header. */
.team-shell .lib-split > .main-content.lib-page-body {
  padding: 0;
}

.lib-doc {
  padding: var(--space-6) var(--space-8) var(--space-8);
}

.lib-doc__inner {
  max-width: 700px;
  margin: 0 auto;
}

.lib-crumbs {
  margin-bottom: var(--space-6);
  font-size: var(--font-sm);
  color: var(--color-subtle);
}

.lib-crumbs a {
  color: inherit;
}

.lib-crumbs a:hover {
  color: var(--color-accent-hover);
}

.lib-doc__title {
  margin: 0 0 var(--space-5);
  font-size: var(--font-xl);
  font-weight: var(--weight-semibold);
  line-height: var(--line-height-tight);
  color: var(--color-text-strong);
}

/* The editor's title is an input wearing the headline. */
.lib-doc__title-input {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  font-family: inherit;
  outline: none;
}

.lib-doc__title-input:focus-visible {
  box-shadow: var(--shadow-focus);
  border-radius: var(--radius-xs);
}

/* Long-form reading: a larger body than the app's. The lede is the page's
   summary — a first paragraph marked "Summary:" (decisions.md 2026-10-12, which
   narrows 2026-10-10's "the first paragraph is the lede"). The reader draws it
   as .lib-lede; a page without one opens straight on its body. */
.lib-prose {
  font-size: var(--font-lg);
  line-height: var(--line-height-relaxed);
  color: var(--color-text);
  /* A bare URL is one unbreakable word; let it break rather than run into the pane. */
  overflow-wrap: anywhere;
}

.lib-prose h2,
.lib-prose h3 {
  margin: var(--space-6) 0 var(--space-4);
  font-size: var(--font-xl);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.lib-prose h3 {
  font-size: var(--font-lg);
}

.lib-prose .lib-lede {
  margin-bottom: var(--space-6);
  font-size: var(--font-xl);
  color: var(--color-subtle);
}

.lib-prose p {
  margin: 0 0 var(--space-5);
}

/* The global reset zeroes every list's indent and spacing; the reader puts
   them back — one step of indent per level, air between items. */
.lib-prose ul,
.lib-prose ol {
  margin: 0 0 var(--space-5);
  padding-left: var(--space-6);
}

.lib-prose ul {
  list-style: disc;
}

.lib-prose ol {
  list-style: decimal;
}

.lib-prose ul ul {
  list-style: circle;
}

.lib-prose li + li {
  margin-top: var(--space-2);
}

.lib-prose li > ul,
.lib-prose li > ol {
  margin: var(--space-2) 0 0;
}

/* An image keeps the width it was drawn at (``?w=`` on its URL), never wider
   than the column; two in one paragraph sit side by side, as they did. */
.lib-prose img {
  max-width: 100%;
  height: auto;
  margin: var(--space-2) var(--space-2) var(--space-2) 0;
  vertical-align: top;
  border-radius: var(--radius-lg);
}

/* Images set side by side in one paragraph stay side by side, wrapping as
   they did where they came from: a pair Guru drew in its slightly wider column
   sits two to a row here too, each image at most half the column and keeping
   its proportions; smaller ones still fit three or four to a row. */
.lib-prose p:has(> img + img) {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: flex-start;
}

.lib-prose p:has(> img + img) > img {
  max-width: calc(50% - var(--space-3) / 2);
  margin: 0;
}

.lib-prose blockquote {
  margin: 0 0 var(--space-5);
  padding-left: var(--space-4);
  border-left: 3px solid var(--color-border);
  color: var(--color-subtle);
}

.lib-prose table {
  margin: 0 0 var(--space-5);
  border-collapse: collapse;
}

.lib-prose th,
.lib-prose td {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  text-align: left;
  vertical-align: top;
}

.lib-prose th {
  font-weight: var(--weight-semibold);
}

/* Struck text is a passage that no longer holds — kept, and read as such. */
.lib-prose del {
  color: var(--color-muted);
}

.lib-note {
  margin: 0 0 var(--space-4);
  font-size: var(--font-xs);
  line-height: var(--line-height-relaxed);
  color: var(--color-subtle);
}

.lib-note--quiet {
  margin: var(--space-1) 0 0;
  color: var(--color-muted);
}

/* ── A rule reference: read-only, linking to the rule ───────────────────── */

.lib-rule {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg);
  font-family: var(--font);
  color: var(--color-text);
  text-decoration: none;
}

.lib-rule:hover {
  border-color: var(--color-accent-200);
  color: var(--color-text);
}

.lib-rule__top {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.lib-rule__id {
  flex: 0 0 auto;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--color-accent-100);
  font-family: ui-monospace, monospace;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  line-height: 1;
  color: var(--color-accent);
}

.lib-rule__title {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
}

.lib-rule__status {
  margin-left: auto;
  font-size: var(--font-xs);
  white-space: nowrap;
}

.lib-rule__status--on {
  color: var(--color-success);
}

.lib-rule__status--off {
  color: var(--color-muted);
}

.lib-rule__pred {
  font-family: ui-monospace, monospace;
  font-size: var(--font-sm);
  line-height: var(--line-height-relaxed);
  color: var(--color-subtle);
}

.lib-rule__foot {
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

/* The pane's mini card: the same block, smaller. */
.lib-rule-cards {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.lib-rule-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  color: var(--color-text);
  text-decoration: none;
}

.lib-rule-card:hover {
  border-color: var(--color-accent-200);
  color: var(--color-text);
}

.lib-rule-card__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.lib-rule-card__title {
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
}

.lib-rule-card .lib-rule__pred {
  font-size: var(--font-xs);
}

/* ── The side pane: the case-details voice, lighter ───────────────────────
   No eye toggles and no hairlines; spacing separates the sections. Mirrors
   the case pane's .fr-section-head / .control-meta values — those rules are
   scoped to the case pages, so the Library carries its own. */

.lib-pane .ws-detail-pane {
  padding: var(--space-5);
}

.lib-sec + .lib-sec {
  margin-top: var(--space-6);
}

/* A section htmx re-renders in place sits in a host that is empty when the
   section has nothing to say, so it spaces like a section only when it holds
   one — and the section after it spaces as if it were not there. */
.lib-sec-host:has(.lib-sec),
.lib-sec-host + .lib-sec {
  margin-top: var(--space-6);
}

.lib-sec__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--color-muted);
}

.lib-sec__n {
  font-weight: var(--weight-normal);
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

.lib-sec__act {
  margin-left: auto;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0;
  text-transform: none;
  color: var(--color-accent);
  text-decoration: none;
  cursor: pointer;
}

.lib-sec__act:hover {
  color: var(--color-accent-hover);
}

.lib-fact {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  min-width: 0;
}

.lib-fact__ico {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--color-muted);
}

.lib-fact__ico .checkbox {
  gap: 0;
}

.lib-fact__label {
  flex-shrink: 0;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
  white-space: nowrap;
  color: var(--color-muted);
}

.lib-fact__value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-size: var(--font-xs);
  line-height: var(--line-height-tight);
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-text);
}

/* A fact that is a link — Open <screen>, the update that shipped it — in the
   accent, as the pane's other links, not the browser's default blue. */
/* An amount a page quotes (#1423): the figure is the point, so it keeps its
   width and the label gives way to it. */
.lib-fact--amount .lib-fact__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.lib-fact--amount .lib-fact__value {
  flex: 0 0 auto;
}

.lib-fact__value a {
  color: var(--color-accent);
  text-decoration: none;
}

.lib-fact__value a:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.lib-fact__input {
  border: 0;
  background: transparent;
  font-family: inherit;
  font-size: var(--font-xs);
  text-align: right;
  color: var(--color-accent);
}

.lib-pill {
  text-decoration: none;
}

a.lib-pill:hover {
  background: var(--color-accent-200);
}

/* Inside the pane the links list has no card padding of its own. */
.lib-pane .lib-links {
  padding: 0;
}

.lib-links--nav a::after {
  content: "→";
  margin-left: auto;
  padding-left: var(--space-3);
  color: var(--color-muted);
}

.lib-links--nav a:hover::after {
  color: var(--color-accent-hover);
}

.lib-links__row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.lib-links__row > a {
  flex: 1 1 auto;
  min-width: 0;
}

.lib-links__remove {
  display: inline-flex;
  padding: var(--space-1);
  border: 0;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--color-muted);
  cursor: pointer;
}

.lib-links__remove:hover {
  background: var(--color-hover);
  color: var(--color-danger);
}

.lib-inline-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.lib-inline-form > .form-control {
  flex: 1 1 10rem;
  min-width: 0;
}

.lib-inline-form > .checkbox {
  flex-basis: 100%;
}

.lib-publish-note {
  margin-top: var(--space-2);
}

/* Correction requests: the reader's *This page is wrong* form, and the
   editor's list of requests a publish can answer. */
.lib-correct-form .lib-note--quiet {
  margin-bottom: var(--space-3);
}

.lib-corrections {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.lib-corrections .checkbox {
  align-items: flex-start;
}

.lib-corrections__text {
  display: block;
  font-size: var(--font-sm);
  color: var(--color-text);
}

.lib-corrections__meta {
  display: block;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* What in the draft may be personal data (#1301), under its warning. */
.lib-personal-data {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--font-sm);
  color: var(--color-text);
  overflow-wrap: anywhere;
}

/* Content audit (#1301): the allowed-address list under the Personal data
   tab, and the one-button forms in its rows. */
.lib-audit__heading {
  margin: var(--space-8) 0 var(--space-2);
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
}

.lib-audit__form {
  display: inline;
  margin: 0;
}

/* A finding and its *Allow*, on one line; the action keeps to the right
   edge as the section head's does. */
.lib-personal-data__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

/* ── Versions (round H Versions.dc.html): the timeline in the side pane,
   the viewing bar over a version, and the marks of a diff ──────────────── */

.lib-timeline {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
}

/* The guide line the knots sit on. */
.lib-timeline::before {
  content: "";
  position: absolute;
  top: var(--space-4);
  bottom: var(--space-4);
  left: 5px;
  width: 1px;
  background: var(--color-border);
}

.lib-timeline__row {
  position: relative;
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-3) var(--space-3) 0;
  border-radius: var(--radius-sm);
  color: var(--color-text);
  text-decoration: none;
}

.lib-timeline__row:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.lib-timeline__row[aria-current="true"] {
  background: var(--color-active);
}

.lib-timeline__knot {
  position: relative;
  z-index: var(--z-base);
  flex: 0 0 11px;
  height: 11px;
  margin-top: var(--space-1);
  border: 2px solid var(--color-slate-300);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
}

.lib-timeline__knot--live {
  border-color: var(--color-success);
  background: var(--color-success);
}

.lib-timeline__knot--draft {
  border-color: var(--color-amber-500);
  background: var(--color-amber-100);
}

.lib-timeline__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.lib-timeline__when {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
}

.lib-timeline__who {
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

.lib-timeline__note {
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

/* The bar over a version: what it is, Version · Changes, Restore. Sits where
   the editor's toolbar sits. */
.lib-viewing {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4) var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--color-border-subtle);
  background: var(--color-bg);
  font-size: var(--font-sm);
}

.lib-viewing__ico {
  display: inline-flex;
  color: var(--color-subtle);
}

.lib-viewing__what strong {
  font-weight: var(--weight-semibold);
}

.lib-viewing__who {
  color: var(--color-subtle);
}

.lib-viewing__switch .seg-toggle-opt {
  text-decoration: none;
}

.lib-viewing__restore {
  margin-left: auto;
}

/* A diff: added words green, removed words struck — and whole blocks the same. */
.lib-ins {
  padding: 0 2px;
  border-radius: var(--radius-xs);
  background: var(--color-green-100);
  color: var(--color-green-700);
  text-decoration: none;
}

.lib-del {
  padding: 0 2px;
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--color-danger) 15%, transparent);
  color: var(--color-danger);
}

.lib-diff {
  margin: 0 0 var(--space-5);
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid;
  border-radius: var(--radius-xs);
}

.lib-diff > :last-child {
  margin-bottom: 0;
}

.lib-diff--ins {
  border-color: var(--color-success);
  background: var(--color-green-100);
}

.lib-diff--del {
  border-color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 8%, transparent);
  text-decoration: line-through;
}

/* ── The editor: a sticky, labelled toolbar across the editing area ─────── */

.lib-editing {
  position: relative;
}

.lib-toolbar {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--color-border-subtle);
  background: var(--color-surface);
}

.lib-toolbar .page-composer-toolbar-btn {
  width: auto;
  min-width: var(--control-h);
  height: var(--control-h);
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
}

.pm-tb-btn--labelled {
  gap: var(--space-2);
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--color-subtle);
}

.pm-tb-btn__icon {
  display: inline-flex;
}

.pm-tb-select {
  height: var(--control-h);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  font-family: inherit;
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
  color: var(--color-subtle);
  cursor: pointer;
}

.lib-doc .alert {
  margin-bottom: var(--space-6);
}

/* On a page the editor is the page: no frame around it. */
.lib-editor.pm-editor,
.lib-editor.pm-editor:focus-within {
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

/* The page is the editing surface: no inner padding, no minimum box. */
.lib-editor .pm-editor-surface [contenteditable="true"] {
  min-height: 16rem;
  padding: 0;
  line-height: var(--line-height-relaxed);
}

.lib-editor .pm-editor-surface [contenteditable="true"] h2 {
  font-size: var(--font-xl);
  font-weight: var(--weight-semibold);
}

.lib-editor .pm-editor-surface [contenteditable="true"] h3 {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
}

/* Saved sits in the header here, not under the editor: no footer rule. */
.lib-save-status .pm-editor-status {
  min-height: 0;
  padding: 0;
  border-top: 0;
}

.lib-save-status .pm-editor-status[data-state="saved"]::before {
  content: "● ";
}

/* A rule reference in the editor: the rule's link alone in its paragraph,
   drawn as the read-only block the reader renders (round H, Editor.dc.html).
   CSS sees element children only, so text typed beside the link still shows
   the block here; the reader's stricter test leaves that one a plain link. */
.lib-editor [contenteditable="true"] p:has(> a[href^="/library/rules/"]:only-child) {
  position: relative;
  margin: var(--space-5) 0;
  padding: var(--space-5) var(--space-5) var(--space-4);
  border: 2px solid var(--color-accent);
  border-radius: var(--radius-lg);
  background: var(--color-bg);
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
}

.lib-editor [contenteditable="true"] p:has(> a[href^="/library/rules/"]:only-child)::before {
  content: "RULE REFERENCE · READ-ONLY";
  position: absolute;
  top: calc(-1 * var(--space-4));
  left: var(--space-4);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-xs);
  background: var(--color-accent);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.05em;
  color: var(--color-surface);
}

.lib-editor [contenteditable="true"] p:has(> a[href^="/library/rules/"]:only-child) > a {
  color: var(--color-text);
  text-decoration: none;
}

/* An About pill in the editor: the label and a × that takes it off. */
.lib-pill--removable {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.lib-pill--removable button {
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: var(--font-xs);
  line-height: 1;
  color: var(--color-accent);
  cursor: pointer;
}

.lib-pill--removable button:hover {
  color: var(--color-danger);
}

/* ── The picker (components/picker.html): a find field over a list of
   options, in the design system's modal. Drawn for the Library editor (round
   H2 Picker.dc.html) and shared with the search box (#1355) ─────────────── */

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

.picker-kinds {
  align-self: flex-start;
}

.picker-find {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--control-h);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-muted);
}

.picker-find:focus-within {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.picker-find input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  font-family: inherit;
  font-size: var(--font-sm);
  color: var(--color-text);
}

.picker-find input::placeholder {
  color: var(--color-text-lavender-grey);
}

/* The search box's field — larger, so it holds the focus. */
.picker-find--lg {
  height: var(--control-h-lg);
  padding: 0 var(--space-4);
  border-radius: var(--radius-lg);
}

.picker-find--lg input {
  font-size: var(--font-lg);
}

/* Esc closes the box, so the browser's own clear button has no job there. */
.picker-find--lg input::-webkit-search-cancel-button {
  appearance: none;
}

.picker-sug {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

.picker-sug svg {
  color: var(--color-accent);
}

.picker-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  border: 0;
  font-family: inherit;
  cursor: pointer;
}

.picker-chip button {
  display: inline-flex;
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-accent);
  cursor: pointer;
}

.picker-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
  max-height: 280px;
  margin: 0 calc(-1 * var(--space-3));
  overflow: auto;
}

.picker-opt {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--control-h);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--font-sm);
  color: var(--color-text);
  cursor: pointer;
}

.picker-opt:hover {
  background: var(--color-hover);
}

.picker-opt:has(input:checked:not(:disabled)) {
  background: var(--color-accent-50);
}

.picker-opt input {
  flex: 0 0 auto;
  margin: 0;
  accent-color: var(--color-accent);
}

.picker-opt--held {
  color: var(--color-muted);
  cursor: default;
}

.picker-opt--held:hover {
  background: transparent;
}

.picker-opt__t {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.picker-opt__two {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.picker-opt__p {
  overflow: hidden;
  font-size: var(--font-xs);
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-muted);
}

.picker-opt__m {
  flex: 0 0 auto;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* An option the icon leads (the Move tree, a search result): the radio stays
   for the keyboard and is hidden. */
.picker-opt--icon input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.picker-opt--icon svg {
  flex: 0 0 auto;
  color: var(--color-muted);
}

.picker-opt--icon:has(input:checked) {
  font-weight: var(--weight-medium);
  color: var(--color-accent-700);
}

.picker-opt--icon:has(input:checked) svg {
  color: var(--color-accent);
}

.picker-opt--icon:has(input:focus-visible) {
  box-shadow: var(--shadow-focus);
}

.picker-depth-1 {
  padding-left: calc(var(--space-3) + var(--space-5));
}

.picker-depth-2 {
  padding-left: calc(var(--space-3) + 2 * var(--space-5));
}

.picker-depth-3 {
  padding-left: calc(var(--space-3) + 3 * var(--space-5));
}

.picker-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.picker-label {
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
}

.picker-quiet {
  margin: 0;
  padding: var(--space-3);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* A group's name over its options — the Amount picker's Named amounts,
   Packages, Prices and Fees (#1423). */
.picker-group {
  margin: 0;
  padding: var(--space-3) var(--space-3) var(--space-1);
}

.picker-note {
  min-width: 0;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.picker-acts {
  display: flex;
  flex: 0 0 auto;
  gap: var(--space-3);
}

/* A link option (a search result): opened, never picked, so the keyboard's
   place in the list is a class, not a checked radio. */
.picker-opt[href] {
  text-decoration: none;
}

.picker-opt--active {
  background: var(--color-accent-50);
  font-weight: var(--weight-medium);
  color: var(--color-accent-700);
}

.picker-opt--active svg {
  color: var(--color-accent);
}

/* ── The search box (#1355, handoffs/search/RETURN): the picker in the
   modal, opened from the rail or with "/" ──────────────────────────────── */

/* Held near the top of the window, so the list grows downward. */
.search-overlay {
  align-items: flex-start;
  padding-top: calc(3 * var(--space-8));
}

.search-overlay .modal-dialog {
  position: relative;
  overflow: visible;
}

/* The kind filter, the facets and the kind keys come and go with a query;
   their own display would otherwise win over `hidden`. */
.search-overlay [hidden] {
  display: none;
}

.search-overlay .modal-body {
  max-height: 60vh;
  overflow-y: auto;
}

/* The close button on the popup's corner, half outside the frame. */
.search-close {
  position: absolute;
  top: calc(-1 * var(--space-4));
  right: calc(-1 * var(--space-4));
  z-index: var(--z-base);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-h);
  height: var(--control-h);
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-subtle);
  box-shadow: var(--shadow-md);
  cursor: pointer;
}

.search-close:hover {
  background: var(--color-surface-dim);
  color: var(--color-text-strong);
}

/* The rail's one button among links: dressed as they are. */
.search-rail-btn {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.search-rail-btn[aria-expanded="true"] {
  background: var(--color-active);
  color: var(--color-text-strong);
}

.search-overlay .seg-toggle-opt .tab-count {
  margin-left: var(--space-1);
}

.search-overlay .seg-toggle-opt--active .tab-count {
  color: inherit;
}

/* A group's header: a soft grey band, pinned while the list scrolls. */
.search-group {
  position: sticky;
  top: 0;
  z-index: var(--z-base);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-2) calc(-1 * var(--space-3)) 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
}

.search-group__more {
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: var(--font-xs);
  color: var(--color-accent);
  cursor: pointer;
}

.search-group__more:hover {
  color: var(--color-accent-hover);
}

.search-results .picker-list {
  max-height: none;
  overflow: visible;
}

/* The matched word, as the source wrote it. */
.search-mark {
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}

.picker-opt--active .search-mark {
  color: inherit;
}

/* "↵" on the row the keyboard is on. */
.search-enter {
  display: none;
  flex: 0 0 auto;
}

.picker-opt--active .search-enter {
  display: inline-flex;
}

.search-keys {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

.search-keys span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.search-overlay .empty-state {
  padding: var(--space-6) var(--space-5) var(--space-5);
}

/* ── migrated: platform/core/templates/dashboard.html ── */

.dashboard .dashboard-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
}

.dashboard .dashboard-grid .welcome-banner {
  grid-column: 1 / -1;
}

.dashboard .welcome-banner {
  padding: 1.5rem;
}

.dashboard .welcome-banner h1 {
  font-size: 1.35rem;
  font-weight: 600;
  margin-bottom: 0.25rem;
}

.dashboard .welcome-banner p {
  color: var(--color-muted);
  font-size: 0.875rem;
}

.dashboard .widget-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem 0.5rem;
}

.dashboard .widget-title {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-muted);
  margin: 0;
}

.dashboard .widget-link {
  font-size: 0.75rem;
  color: var(--color-accent);
  text-decoration: none;
}

.dashboard .widget-link:hover {
  text-decoration: underline;
}

.dashboard .widget-empty {
  padding: 1.5rem 1.25rem;
  text-align: center;
  color: var(--color-muted);
  font-size: 0.85rem;
}

/* My cases widget */
.dashboard .case-stage-group {
  padding: 0 1.25rem;
  margin-bottom: 0.75rem;
}

.dashboard .case-stage-group:last-child {
  margin-bottom: 0.75rem;
}

.dashboard .case-stage-label {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-muted);
  margin-bottom: 0.35rem;
}

.dashboard .case-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0;
  font-size: 0.85rem;
}

.dashboard .case-row a {
  color: var(--color-text);
  text-decoration: none;
  font-weight: 500;
}

.dashboard .case-row a:hover {
  color: var(--color-accent);
}

.dashboard .case-meta {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--color-muted);
  white-space: nowrap;
}

.dashboard .case-service {
  font-size: 0.7rem;
  color: var(--color-muted);
  background: var(--color-bg);
  padding: 0.1rem 0.4rem;
  border-radius: var(--radius-xs);
}

/* Activity widget */
.dashboard .activity-list {
  padding: 0 1.25rem 0.75rem;
  list-style: none;
}

.dashboard .activity-item {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.4rem 0;
  font-size: 0.82rem;
  border-bottom: 1px solid var(--color-border);
}

.dashboard .activity-item:last-child {
  border-bottom: none;
}

.dashboard .activity-action {
  color: var(--color-text);
  font-weight: 500;
}

.dashboard .activity-case {
  color: var(--color-accent);
  text-decoration: none;
  font-size: 0.78rem;
}

.dashboard .activity-case:hover {
  text-decoration: underline;
}

.dashboard .activity-who {
  color: var(--color-muted);
  font-size: 0.75rem;
}

.dashboard .activity-when {
  margin-left: auto;
  color: var(--color-muted);
  font-size: 0.72rem;
  white-space: nowrap;
}

/* Team online widget */
.dashboard .online-list {
  padding: 0 1.25rem 0.75rem;
  list-style: none;
}

.dashboard .online-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0;
  font-size: 0.85rem;
}

.dashboard .online-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #34c759;
  flex-shrink: 0;
}

.dashboard .online-name {
  font-weight: 500;
  color: var(--color-text);
}

.dashboard .online-context {
  font-size: 0.75rem;
  color: var(--color-muted);
}

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

/* ── end migrated: platform/core/templates/dashboard.html ── */

/* ── migrated: platform/tools/cases/templates/case_detail.html ── */

/* Layout for case_detail is provided by the team-shell .main / .main-detail
   primitives — no page-specific overrides needed. Page-only styling
   (presence indicator, assignee picker, fc-badge, etc.) lives below. */

/* Assignee picker now uses the shared .mh-picker classes
   (see "Main header picker" section above). No page-specific rules. */

/* ── Front conversations badge + dropdown ── */
.case-detail-page .fc-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin-left: 0.65rem;
}

.case-detail-page .fc-badge-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.55rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: 0.72rem;
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
}

.case-detail-page .fc-badge-trigger:hover {
  color: var(--color-text);
  border-color: var(--color-text);
}

.case-detail-page .fc-badge-icon {
  width: 0.9rem;
  height: 0.9rem;
}

.case-detail-page .fc-badge-dropdown {
  position: absolute;
  top: calc(100% + 0.3rem);
  left: 0;
  z-index: var(--z-dropdown);
  min-width: 280px;
  max-width: 360px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  padding: 0.25rem 0;
}

.case-detail-page .fc-badge-dropdown[hidden] {
  display: none;
}

/* ── Front conversations — rows shared by badge dropdown + sidebar panel ── */
/* ── Front conversations rows (shared: header-badge dropdown + details panel) ── */
.case-detail-page .fc-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.8rem;
  border-bottom: 1px solid var(--color-border);
}

.case-detail-page .fc-row:last-child {
  border-bottom: none;
}

.case-detail-page .fc-row:hover {
  background: color-mix(in srgb, var(--color-accent) 5%, transparent);
}

.case-detail-page .fc-icon {
  width: 0.9rem;
  height: 0.9rem;
  flex-shrink: 0;
  color: var(--color-muted);
}

.case-detail-page .fc-subject {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.case-detail-page .fc-time {
  flex-shrink: 0;
  font-size: 0.7rem;
  color: var(--color-muted);
}

/* ── Front conversations details panel ──
   Mirrors the Front plugin sidebar: an .fr-section-head header (uppercase +
   count + hairline) over bordered, rounded rows (channel icon · subject ·
   date). Overrides the shared row styling above for the panel only; the
   header-badge dropdown keeps its divider look. */
.case-detail-page .fc-panel {
  margin-bottom: var(--space-4);
}

.case-detail-page .fc-panel-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* Flat rows matching the field-block lines below the panel: borderless,
   flush, min-height instead of padding, muted 14px icon, accent-on-hover. */
.case-detail-page .fc-panel-list .fc-row {
  gap: var(--space-2);
  min-height: var(--control-h);
  padding: 0;
  border: none;
  border-radius: 0;
  font-size: var(--font-xs);
}

.case-detail-page .fc-panel-list .fc-row:hover {
  background: transparent;
}

.case-detail-page .fc-panel-list .fc-icon {
  width: 14px;
  height: 14px;
  color: var(--color-muted);
  transition: color var(--transition-fast);
}

.case-detail-page .fc-panel-list .fc-subject {
  transition: color var(--transition-fast);
}

.case-detail-page .fc-panel-list .fc-time {
  font-size: var(--font-xs);
  font-variant-numeric: tabular-nums;
  transition: color var(--transition-fast);
}

.case-detail-page .fc-panel-list .fc-row:hover .fc-icon,
.case-detail-page .fc-panel-list .fc-row:hover .fc-subject,
.case-detail-page .fc-panel-list .fc-row:hover .fc-time {
  color: var(--color-accent-hover);
}

/* ── Origin details panel ──
   Where the case came from: channel · form · entry page · first touch.
   Same flat key/value rhythm as the field-block lines around it. */
.case-detail-page .origin-panel {
  margin-bottom: var(--space-4);
}

.case-detail-page .origin-list {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.case-detail-page .origin-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  font-size: var(--font-xs);
}

.case-detail-page .origin-row dt {
  color: var(--color-muted);
  flex: 0 0 90px;
}

.case-detail-page .origin-row dd {
  margin: 0;
  min-width: 0;
}

.case-detail-page .origin-row .origin-path {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* WhatsApp reference row (#1148) — the one editable row on the panel: an
   input + Save on one line, the refusal message on its own line under them.
   Baseline, not centre: the label stays level with the input when the
   message grows the row. */
.case-detail-page .origin-row--ref {
  align-items: baseline;
}

.case-detail-page .origin-row--ref dd {
  flex: 1 1 auto;
}

.case-detail-page .origin-ref-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.case-detail-page .origin-ref-input {
  flex: 1 1 12ch;
  min-width: 0;
  max-width: 20ch;
  font-variant-numeric: tabular-nums;
}

.case-detail-page .origin-ref-form .form-error {
  flex-basis: 100%;
  margin-top: 0;
}

.case-detail-page .origin-ref-value {
  font-variant-numeric: tabular-nums;
}

/* WhatsApp consent row (#1061) — the consent tag, then the couple's numbers
   on the same line, wrapping under it when there are several. */
.case-detail-page .origin-row--whatsapp dd {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.case-detail-page .origin-number {
  font-variant-numeric: tabular-nums;
}

.case-detail-page .origin-number__note {
  color: var(--color-muted);
}

/* The withdraw action (#1061): a text link after the numbers, wrapping under
   them in a narrow panel, with the label kept level with the first line. */
.case-detail-page .origin-row--whatsapp {
  align-items: baseline;
}

.case-detail-page .origin-consent-form {
  display: inline;
}

/* Case detail uses the team-shell .main-detail / .main-content primitives
   (dual-classed in case_detail.html). #case-main is kept as a JS hook for
   HTMX swaps. */

.case-detail-page .case-main-padded {
  max-width: 900px;
}

/* Data-dense surfaces (Files tab) drop the readability max-width so the
   table and submissions log can use the full available width. */
.case-detail-page .case-main-padded--wide {
  max-width: none;
}

/* ── Stage / Service / Assignee controls (case-overview redesign) ─────────
   Stacked rows that match the field rows below: a bare leading icon, the
   label on the left, and the editable select masquerading as plain
   right-aligned text (no border, no chevron) on the right. The assignee row
   carries a small avatar next to the name. */
:is(.case-detail-page, .case-pane-details) .detail-controls {
  margin-bottom: var(--space-4);
}

:is(.case-detail-page, .case-pane-details) .control-block {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  cursor: pointer;
}

/* Leading icon on the control rows — aligns them with the icon-led field
   rows below. */
:is(.case-detail-page, .case-pane-details) .detail-field-ico {
  flex-shrink: 0;
  display: inline-flex;
  width: 14px;
  height: 14px;
  color: var(--color-muted);
  cursor: pointer;
  transition: color var(--transition-fast);
}

:is(.case-detail-page, .case-pane-details) .detail-field-ico svg {
  width: 14px;
  height: 14px;
}

:is(.case-detail-page, .case-pane-details) .control-meta {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-width: 0;
}

:is(.case-detail-page, .case-pane-details) .control-meta .detail-field-label {
  flex-shrink: 0;
  min-width: 0;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  letter-spacing: 0.02em;
  line-height: 16px;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--transition-fast);
}

:is(.case-detail-page, .case-pane-details) .control-select {
  appearance: none;
  -webkit-appearance: none;
  /* Size to the selected value, not the widest option, so the control sits
     flush right (and the assignee name stays next to its avatar). */
  width: auto;
  field-sizing: content;
  max-width: 100%;
  cursor: pointer;
  border: 0;
  padding: 1px 2px;
  border-radius: var(--radius-xs);
  font-family: inherit;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-tight);
  color: var(--color-text);
  background-color: transparent;
  background-image: none;
  text-align: right;
  text-align-last: right;
  transition: color var(--transition-fast);
}

/* Hover the whole row — icon, label and value all turn accent together, no
   background fill. Pointing anywhere on the row (including the label) reads as
   one interactive control. */
:is(.case-detail-page, .case-pane-details) .control-block:hover .detail-field-ico,
:is(.case-detail-page, .case-pane-details) .control-block:hover .detail-field-label,
:is(.case-detail-page, .case-pane-details) .control-block:hover .control-select {
  color: var(--color-accent-hover);
}

:is(.case-detail-page, .case-pane-details) .control-select:focus-visible {
  outline: none;
  background-color: var(--color-surface-muted);
  box-shadow: var(--shadow-focus);
}

:is(.case-detail-page, .case-pane-details) .control-assignee-value {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

:is(.case-detail-page, .case-pane-details) .control-assignee-value .control-select {
  text-align: left;
  text-align-last: left;
}

:is(.case-detail-page, .case-pane-details) .control-assignee-value .assignee-avatar {
  width: 18px;
  height: 18px;
  font-size: 0.55rem;
}

/* One assignee per phase: the owner's avatar on top, whoever held the case in
   its other phases stacked behind it, muted — history, not a second owner.
   The kebab beside the stack names each by phase. */
:is(.case-detail-page, .case-pane-details) .assignee-stack {
  display: inline-flex;
  align-items: center;
}

:is(.case-detail-page, .case-pane-details) .assignee-stack .assignee-avatar {
  border: 1px solid var(--color-surface);
  box-sizing: content-box;
}

/* Earlier holders first in the DOM, the owner last: each overlaps the one
   before it, and the owner — positioned — paints in front. */
:is(.case-detail-page, .case-pane-details) .assignee-stack .assignee-avatar + .assignee-avatar {
  margin-left: calc(var(--space-2) * -1);
}

:is(.case-detail-page, .case-pane-details) .assignee-stack .assignee-avatar:not(.assignee-avatar--earlier) {
  position: relative;
}

:is(.case-detail-page, .case-pane-details) .assignee-avatar--earlier {
  background: var(--color-surface-muted);
  color: var(--color-muted);
}

:is(.case-detail-page, .case-pane-details) .assignee-avatar--earlier img {
  filter: grayscale(1);
}

:is(.case-detail-page, .case-pane-details) .assignee-history__menu {
  padding: var(--space-2);
  min-width: 200px;
}

:is(.case-detail-page, .case-pane-details) .assignee-history__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
}

:is(.case-detail-page, .case-pane-details) .assignee-history__row .assignee-avatar {
  width: 22px;
  height: 22px;
  font-size: 0.62rem;
}

:is(.case-detail-page, .case-pane-details) .assignee-history__who {
  display: flex;
  flex-direction: column;
  min-width: 0;
  text-align: left;
}

:is(.case-detail-page, .case-pane-details) .assignee-history__phase {
  font-size: var(--font-xxs);
  color: var(--color-muted);
}

:is(.case-detail-page, .case-pane-details) .assignee-history__name {
  font-size: var(--font-xs);
  color: var(--color-text);
}

/* A locked control (closed case, single-phase type, tier-less type) — the
   select is still the value display, but the row stops reading as a control:
   no pointer, no hover accent. */
:is(.case-detail-page, .case-pane-details) .control-select:disabled {
  cursor: default;
  color: var(--color-muted);
}

:is(.case-detail-page, .case-pane-details) .control-block:has(.control-select:disabled),
:is(.case-detail-page, .case-pane-details) .control-block:has(.control-select:disabled) .detail-field-ico,
:is(.case-detail-page, .case-pane-details) .control-block:has(.control-select:disabled) .detail-field-label {
  cursor: default;
}

:is(.case-detail-page, .case-pane-details) .control-block:has(.control-select:disabled):hover .detail-field-ico,
:is(.case-detail-page, .case-pane-details) .control-block:has(.control-select:disabled):hover .detail-field-label,
:is(.case-detail-page, .case-pane-details) .control-block:has(.control-select:disabled):hover .control-select {
  color: var(--color-muted);
}

/* ── Status control (the lifecycle axis) ────────────────────────────────────
   Not a select: state text on the right with the actions kebab beside it. The
   row itself is inert — only the kebab is interactive — so it drops the
   pointer and the row-wide hover accent the select rows have. */
.case-detail-page .lc-block,
.case-detail-page .lc-block .detail-field-ico,
.case-detail-page .lc-block .detail-field-label {
  cursor: default;
}

.case-detail-page .lc-block:hover .detail-field-ico,
.case-detail-page .lc-block:hover .detail-field-label {
  color: var(--color-muted);
}

.case-detail-page .lc-value {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}

/* "End this lead…" launcher (#1180) — the standard ghost button stretched to
   the column at the foot of the controls, the same shape and place as the
   People section's "+ Add person" row (.ps-add-row) and the Front sidebar's
   .fp-close-case. A lead's one exit; a tiered case keeps its exit in the
   Status kebab. */
.case-detail-page .lc-end-row {
  margin-top: var(--space-3);
}

.case-detail-page .lc-end-row .btn {
  width: 100%;
  justify-content: center;
}

.case-detail-page .lc-state {
  font-size: var(--font-xs);
  line-height: var(--line-height-tight);
  color: var(--color-text);
  text-align: right;
  padding: 1px 2px;
}

/* Closed lanes — the ended episodes, listed read-only under the Status row
   (#1041). Indented past the control row's leading icon so the list hangs off
   the Status label rather than the panel edge; 14px is .detail-field-ico's
   width above, not a spacing step. */
.case-detail-page .lc-closed {
  margin: 0 0 var(--space-3);
  padding-left: calc(14px + var(--space-2));
}

.case-detail-page .lc-closed-head {
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  letter-spacing: 0.02em;
  line-height: 16px;
}

.case-detail-page .lc-closed-list {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
}

.case-detail-page .lc-closed-row + .lc-closed-row {
  margin-top: var(--space-3);
}

.case-detail-page .lc-closed-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.case-detail-page .lc-closed-arrow {
  font-size: var(--font-xs);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
}

.case-detail-page .lc-closed-dates {
  margin-left: auto;
  font-size: var(--font-xxs);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
  white-space: nowrap;
}

.case-detail-page .lc-closed-why {
  margin-top: var(--space-1);
  font-size: var(--font-xs);
  line-height: var(--line-height-tight);
  color: var(--color-subtle);
  overflow-wrap: anywhere;
}

/* Lifecycle dialogs — footer buttons right-aligned, error under the form. */
.lc-form-actions {
  justify-content: flex-end;
}

.lc-form .form-error {
  margin: 0 var(--space-4) var(--space-2);
}

/* ── Sectioned fields panel (case-overview redesign) ──────────────────────
   Schema fields grouped into named sections, each with a filled/total count
   and a trailing hairline; single-column rows lead with a line icon, label
   left / value right, empties kept visible but dimmed. Click-to-edit is
   unchanged — the value span is the editField target. */
:is(.case-detail-page, .case-pane-details) .fr-section + .fr-section {
  margin-top: var(--space-4);
}

:is(.case-detail-page, .case-pane-details) .fr-section-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-muted);
  white-space: nowrap;
}

:is(.case-detail-page, .case-pane-details) .fr-section-count {
  order: 2;
  font-weight: var(--weight-normal);
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

:is(.case-detail-page, .case-pane-details) .fr-section-head::after {
  content: "";
  order: 3;
  flex: 1 1 auto;
  min-width: var(--space-5);
  height: 1px;
  background: var(--color-border);
}

/* Eye toggle: folds the section body in/out, remembered per section in
   localStorage (tab_details.html script). Mirrors the workspace ws-rev-eye
   pattern — open eye by default, eye-off when collapsed. Sits at the far right
   (order 4) after the trailing hairline. */
:is(.case-detail-page, .case-pane-details) .fr-section.is-collapsed > *:not(.fr-section-head) {
  display: none;
}

:is(.case-detail-page, .case-pane-details) .fr-section-eye {
  order: 4;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  padding: 0;
  background: none;
  border: 0;
  color: var(--color-accent);
  cursor: pointer;
  transition: color var(--transition-normal);
}

:is(.case-detail-page, .case-pane-details) .fr-section-eye:hover {
  color: var(--color-accent-hover);
}

:is(.case-detail-page, .case-pane-details) .fr-section-eye[aria-pressed="true"] {
  color: var(--color-muted);
}

:is(.case-detail-page, .case-pane-details) .fr-section-eye .fr-section-ic {
  width: 12px;
  height: 12px;
}

:is(.case-detail-page, .case-pane-details) .fr-section-eye .fr-section-ic:nth-of-type(2) {
  display: none;
}

:is(.case-detail-page, .case-pane-details) .fr-section-eye[aria-pressed="true"] .fr-section-ic:nth-of-type(1) {
  display: none;
}

:is(.case-detail-page, .case-pane-details) .fr-section-eye[aria-pressed="true"] .fr-section-ic:nth-of-type(2) {
  display: inline-block;
}

:is(.case-detail-page, .case-pane-details) .fr-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}

:is(.case-detail-page, .case-pane-details) .field-block {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  /* Let the row shrink below its content (it's a 1fr grid item) so a long
     value's overflow:hidden clip engages instead of widening the row and
     pushing the value to the panel edge. */
  min-width: 0;
  cursor: pointer;
}

:is(.case-detail-page, .case-pane-details) .field-block-ico {
  flex-shrink: 0;
  display: inline-flex;
  width: 14px;
  height: 14px;
  color: var(--color-muted);
  cursor: pointer;
  transition: color var(--transition-fast);
}

:is(.case-detail-page, .case-pane-details) .field-block-ico svg {
  width: 14px;
  height: 14px;
}

:is(.case-detail-page, .case-pane-details) .field-block-meta {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-width: 0;
}

:is(.case-detail-page, .case-pane-details) .field-block-label {
  flex-shrink: 0;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  letter-spacing: 0.02em;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--transition-fast);
}

:is(.case-detail-page, .case-pane-details) .field-block-value {
  min-width: 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-tight);
  color: var(--color-text);
  text-align: right;
  overflow: hidden;
  transition: color var(--transition-fast);
}

/* The value text clips to one line with an ellipsis when it outgrows the
   track (e.g. a long Zoom link), with the full string in a title tooltip —
   matching the Front conversation rows. The inner span stays inline-block so
   short values keep hugging the right edge of the row (text-align: right on
   the parent); only long ones fill the track and truncate. */
:is(.case-detail-page, .case-pane-details) .field-block-value-text {
  display: inline-block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

/* While the row is being edited the value cell hosts the input — drop the
   clip so its focus ring isn't sliced. */
:is(.case-detail-page, .case-pane-details) .field-block-value:has(input),
:is(.case-detail-page, .case-pane-details) .field-block-value:has(select) {
  overflow: visible;
}

/* The month picker (#1248) fills the track beside the label rather than
   sizing to its content, which pushed it past the panel edge. */
:is(.case-detail-page, .case-pane-details) .field-block-value:has(.month-picker) {
  flex: 1 1 auto;
  overflow: visible;
}

:is(.case-detail-page, .case-pane-details) .field-block.is-empty .field-block-value {
  color: var(--color-muted);
}

/* Bool fields render their value as a checkbox glyph: accent when checked,
   muted (empty box) when not. Declared before the row-hover rule so hover still
   lifts the whole row to accent-hover. */
:is(.case-detail-page, .case-pane-details) .field-block-value.field-block-bool {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  color: var(--color-muted);
}

:is(.case-detail-page, .case-pane-details) .field-block-value.field-block-bool.is-checked {
  color: var(--color-accent);
}

/* Hover the whole row — icon, label and value all turn accent together, like
   the stage/service/assignee control rows above. Each row is click-to-edit. */
:is(.case-detail-page, .case-pane-details) .field-block:hover .field-block-ico,
:is(.case-detail-page, .case-pane-details) .field-block:hover .field-block-label,
:is(.case-detail-page, .case-pane-details) .field-block:hover .field-block-value {
  color: var(--color-accent-hover);
}

.case-detail-page .overview-layout {
  display: flex;
  gap: 0;
  height: 100%;
}

/* Fixed-width fields sidebar — 360px, no resize. The timeline column flexes to
   fill the rest. (Mobile drops to a stacked, full-width column below.) */
.case-detail-page .overview-fields {
  flex: 0 0 360px;
  border-left: 1px solid var(--color-border-subtle);
  overflow-y: auto;
  padding: var(--space-ws-gutter);
}

.case-detail-page .overview-timeline {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  position: relative;
  overflow: hidden;
}

.case-detail-page .overview-timeline-scroll {
  flex: 1;
  overflow-y: auto;
  padding: 0 0 70px 0;
  /* space so last entries aren't hidden behind floating composer */
  min-height: 0;
}

.case-detail-page .overview-timeline-scroll.timeline-fading {
  opacity: 0;
}

.case-detail-page .overview-timeline-scroll.timeline-fade-in {
  animation: timelineFadeIn 0.28s ease-out;
}

@keyframes timelineFadeIn {
  from {
    opacity: 0;
    transform: translateY(6px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ── Case-detail-page overrides (timeline + composer + floating-toolbar) ── */
.case-detail-page .timeline {
  padding: var(--space-ws-gutter);
}

.case-detail-page .composer {
  position: absolute;
  bottom: 18px;
  left: 18px;
  right: 18px;
  padding:0;
  z-index: var(--z-sticky);
  box-shadow: var(--shadow-card);
}

.case-detail-page .composer .composer-wrap {
  /* background: var(--color-surface); */
  /* box-shadow: var(--shadow-md); */
}

.case-detail-page .composer--shared .composer-wrap {
  border-color: var(--color-warning);
}

.case-detail-page .composer--shared .composer-wrap:focus-within {
  border-color: var(--color-warning);
  box-shadow: var(--shadow-focus-warning);
}

.case-detail-page .composer-editor {
  padding: var(--space-3) var(--space-4);
  font-size: var(--font-base);
  font-family: inherit;
  line-height: var(--line-height-relaxed);
  outline: none;
  min-height: 1.4em;
  max-height: 18.75rem;
  overflow-y: auto;
  color: var(--color-text);
}

.case-detail-page .composer-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--color-muted);
  pointer-events: none;
}

.case-detail-page .composer-editor--expanded {
  min-height: 7.5rem;
}

.case-detail-page .composer-inline .composer-editor {
  flex: 1;
  min-height: 1.4em;
  padding: var(--space-3) var(--space-4);
}

.case-detail-page .composer-expand-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-muted);
  font-size: var(--font-sm);
  padding: var(--space-1);
  line-height: 1;
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast), color var(--transition-fast);
}

.case-detail-page .composer-expand-btn:hover {
  color: var(--color-text);
  background: var(--color-hover);
}

/* ── Floating toolbar (text selection) — token-pure dark surface ─────────── */
.case-detail-page .floating-toolbar {
  display: none;
  position: absolute;
  z-index: var(--z-modal);
  background: var(--color-text-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  gap: var(--space-1);
  box-shadow: var(--shadow-floating);
}

.case-detail-page .floating-toolbar.visible {
  display: flex;
}

.case-detail-page .floating-toolbar .toolbar-btn {
  width: 1.75rem;
  height: 1.625rem;
  border: none;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--color-surface);
  font-size: var(--font-xs);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.case-detail-page .floating-toolbar .toolbar-btn:hover {
  background: color-mix(in srgb, var(--color-surface) 15%, transparent);
}


@media (max-width: 768px) {
  .case-detail-page .overview-layout {
    flex-direction: column;
    height: auto;
  }

  .case-detail-page .overview-fields {
    flex: none;
    border-left: none;
    border-top: 1px solid var(--color-border);
  }
}

/* ════════════════════════════════════════════════════════════════════════
   Case-overview timeline — Direction C "Timeline Rail".
   Markup is emitted by the rail partials (_rail_comment.html, _rail_activity.html,
   _rail_activity_group.html) — the only comment_thread.html render path, shared
   by the case-overview timeline and the docs workspace panel Activity rail.
   Reference: timeline/project/timeline/styles.css §"DIRECTION C" + §"PINNED".
   ════════════════════════════════════════════════════════════════════════ */

/* ── Audience filter pill (header) ──────────────────────────────────────
   Mirrors the .page-composer-scope pill so the overview tab reads as one
   design language across composer + timeline. */
.case-timeline-seg {
  display: inline-flex;
  background: var(--color-surface-muted);
  border-radius: var(--radius-sm);
  padding: 1px;
  font-size: var(--font-xs);
}

.case-timeline-seg-btn {
  appearance: none;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  padding: var(--space-1) var(--space-3);
  height: 28px;
  border-radius: var(--radius-xs);
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  transition: background var(--transition-fast), color var(--transition-fast), box-shadow var(--transition-fast);
}

.case-timeline-seg-btn:hover:not([aria-pressed="true"]) {
  color: var(--color-text);
}

.case-timeline-seg-btn[aria-pressed="true"] {
  background: var(--color-surface);
  color: var(--color-text-strong);
  box-shadow: var(--shadow-sm);
}

.case-timeline-seg-count {
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  font-weight: var(--weight-normal);
}

/* ── Filter hide rules ──────────────────────────────────────────────────
   Each filter hides the .rail-row entries that don't match. Pinned section
   stays visible so the filter narrows the feed without redrawing it. */
.timeline--rail.timeline--filter-team .rail-row--comment:not(:has(.rail-card--internal)),
.timeline--rail.timeline--filter-team .rail-row--activity,
.timeline--rail.timeline--filter-team .rail-row--submission,
.timeline--rail.timeline--filter-team .rail-row--conversation-message {
  display: none;
}

/* A combined-redo member is a comment too, but it renders as a stack line, not
   as a .rail-row--comment — so every filter that reasons about comment rows has
   to name it as well, or an internal redo leaks into the Shared view. */
.timeline--rail.timeline--filter-shared .rail-row--comment:has(.rail-card--internal),
.timeline--rail.timeline--filter-shared .rail-redo-stack__item--internal,
.timeline--rail.timeline--filter-shared .rail-row--activity,
.timeline--rail.timeline--filter-shared .rail-row--submission,
.timeline--rail.timeline--filter-shared .rail-row--conversation-message {
  display: none;
}

/* The group card carries the batch's text now, so hiding every line inside an
   all-internal group is not enough — the wrapper would still print the redo
   under a filter that means "what the couple sees". */
.timeline--rail.timeline--filter-shared .rail-redo-group:not(:has(.rail-redo-stack__item--shared)) {
  display: none;
}

.timeline--rail.timeline--filter-system .rail-row--comment,
.timeline--rail.timeline--filter-system .rail-redo-group {
  display: none;
}

/* Redos (#640): show every redo card (open + resolved, struck) and drop notes +
   activity. Grouped batches stay: a group renders as .rail-redo-group holding
   .rail-redo-stack__item lines, none of which these selectors match. */
.timeline--rail.timeline--filter-redos .rail-row--comment:not(:has(.rail-card--redo)),
.timeline--rail.timeline--filter-redos .rail-row--activity,
.timeline--rail.timeline--filter-redos .rail-row--submission,
.timeline--rail.timeline--filter-redos .rail-row--conversation-message,
.timeline--rail.timeline--filter-redos .timeline-activity-group {
  display: none;
}

/* Messages (#1026) — the complement of Redos on the couple's panel: ordinary
   conversation with the redo requests taken out. A redo group card is a wrapper
   around redo members, so it goes with them. */
.timeline--rail.timeline--filter-messages .rail-row--comment:has(.rail-card--redo),
.timeline--rail.timeline--filter-messages .rail-redo-group,
.timeline--rail.timeline--filter-messages .rail-row--activity,
.timeline--rail.timeline--filter-messages .rail-row--submission,
.timeline--rail.timeline--filter-messages .rail-row--conversation-message,
.timeline--rail.timeline--filter-messages .timeline-activity-group {
  display: none;
}

/* ── Feed scroll region ─────────────────────────────────────────────────
   Re-zero padding around the rail column. The rail-row grid carries its
   own column for the node, so the feed itself doesn't need a left inset. */
.timeline--rail .timeline-entries {
  padding-left: 0;
  display: flex;
  flex-direction: column;
}

/* ── Rail row grid ──────────────────────────────────────────────────────
   Two-column grid: 1.75rem node + 1fr body, --space-4 gap.
   Padding-bottom = row gap so the rail line stays continuous between rows. */
.rail-row {
  position: relative;
  display: grid;
  grid-template-columns: 1.75rem minmax(0, 1fr);
  column-gap: var(--space-4);
  padding-bottom: var(--space-6);
}

.rail-row:last-child {
  padding-bottom: 0;
}

/* ── Rail line ──────────────────────────────────────────────────────────
   Drawn on each row's node column so the line spans the full row height
   (continues even past tall cards). First/last rows trim the line so it
   doesn't bleed above the first node or below the last. */
.rail-row > .rail-row-node {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: 2px;
}

.rail-row > .rail-row-node::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: calc(0px - var(--space-6));
  left: 50%;
  width: 2px;
  margin-left: -1px;
  background: var(--color-border-subtle);
  pointer-events: none;
}

.rail-row:first-child > .rail-row-node::before {
  top: 0.85rem;
}

.rail-row:last-child > .rail-row-node::before {
  bottom: auto;
  height: 0.85rem;
}

/* ── Avatar (rail node for comments) ────────────────────────────────────
   The avatar IS the node. Surface-coloured halo masks the rail line so it
   appears to pass behind the circle. */
.rail-avatar {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-surface);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.01em;
  user-select: none;
}

.rail-avatar--image {
  object-fit: cover;
}

.rail-avatar--image + .rail-avatar--initials {
  display: none;
}

.rail-avatar--sm {
  width: 1.25rem;
  height: 1.25rem;
  font-size: var(--font-xxs);
}

/* ── Live-presence faces (rail rows) ───────────────────────────────────────
   Small avatar pips showing who is viewing a row right now. Painted
   client-side from the location-scoped presence broadcast (base_team.html);
   initials only — the presence payload carries no avatar id. The slots sit
   right-aligned after the row label (which is flex:1) and stay hidden while
   empty. */
.sb-row-presence,
.doc-faces {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.sb-row-presence[hidden],
.doc-faces[hidden] {
  display: none;
}

.face {
  position: relative;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 18px;
  height: 18px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  color: var(--color-surface);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  line-height: 1;
  letter-spacing: 0.01em;
  user-select: none;
}

.doc-faces .face {
  width: 17px;
  height: 17px;
}

.face--image {
  object-fit: cover;
  background: none;
}

.face--more {
  background: var(--color-surface-muted);
  color: var(--color-subtle);
}

/* Presence-face tooltip. The faces live inside the case-nav rail, which clips
   its overflow (`.sb` hidden / `.sb-body` auto), so a CSS `::after` would be
   trapped by the rail and read as "behind the nav". It's rendered in the top
   layer by app.js (`.floating-tip`) instead — a fixed node on <body>,
   positioned to the face and clamped to the viewport, so it is never clipped
   or stacked behind anything. */
.floating-tip {
  position: fixed;
  z-index: var(--z-dropdown);
  pointer-events: none;
  white-space: nowrap;
  background: var(--color-text-strong);
  color: var(--color-surface);
  font-size: var(--font-xxs);
  font-weight: var(--weight-medium);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-xs);
  box-shadow: var(--shadow-md);
}

.floating-tip[hidden] {
  display: none;
}

/* The card shape of the same tip — a title, a few rows — for an element that
   carries its content as a hidden `.tip-content` child (the insights chart's
   columns). Same node, same positioning; only the type and wrap change. */
.floating-tip--rich {
  white-space: normal;
  text-align: left;
  min-width: 11rem;
  max-width: 18rem;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-normal);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
}

.floating-tip__title {
  display: block;
  font-weight: var(--weight-semibold);
  margin-bottom: var(--space-1);
}

.floating-tip__row {
  display: block;
}

.floating-tip__muted {
  color: var(--color-slate-300);
}

.rail-row > .rail-row-node > .rail-avatar {
  position: relative;
  z-index: var(--z-base);
  box-shadow: var(--shadow-rail-node);
}

/* ── Activity node (smaller circle with the action icon inside) ────────
   Dashed border for grouped events; tinted ring for approve / move. */
/* Activity icon as rail node. Reset the global ``.activity-icon`` margin /
   vertical-align / inline-flex so the box is a clean centred square; flex
   centering otherwise factors the right-margin into the centre point and
   the inline-flex baseline inflates the height past 1.15rem (= oval).

   Same surface-color halo as ``.rail-avatar`` so the rail line is masked
   in the gap between the row's top and the icon's top (otherwise the line
   sticks up past the circle). */
.rail-row-node--activity > .activity-icon {
  position: relative;
  z-index: var(--z-base);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0.15rem;
  margin: 0;
  background: var(--color-surface-dim);
  border: 2px solid var(--color-border);
  border-radius: 50%;
  box-sizing: content-box;
  color: var(--color-muted);
  line-height: 1;
  vertical-align: baseline;
  box-shadow: var(--shadow-rail-node);
}

.rail-row-node--activity > .activity-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

.rail-row-node--approve > .activity-icon {
  border-color: color-mix(in srgb, var(--color-success) 55%, var(--color-border));
  color: var(--color-success);
}

.rail-row-node--move > .activity-icon {
  border-color: color-mix(in srgb, var(--color-accent) 45%, var(--color-border));
  color: var(--color-accent);
}

.rail-row-node--group > .rail-node-dot {
  position: relative;
  z-index: var(--z-base);
  width: 1.15rem;
  height: 1.15rem;
  background: var(--color-surface-dim);
  border: 2px dashed var(--color-border);
  border-radius: 50%;
  box-sizing: content-box;
  display: block;
  box-shadow: var(--shadow-rail-node);
}

/* ── Body column + card ────────────────────────────────────────────────
   The card holds meta + body + replies. Internal = tinted, shared = white. */
.rail-row-body-col {
  min-width: 0;
}

.rail-card {
  position: relative;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border-subtle);
  background: var(--color-surface);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-slow) ease-out;
}

.rail-card--internal {
  background: color-mix(in srgb, var(--color-accent-hover) 9%, var(--color-surface));
  border-color: var(--color-border);
}

.rail-card--shared {
  border-color: var(--color-border-subtle);
}

/* ── Meta line (name · time · pill / chip) ─────────────────────────────
   Sits at the top of every card. Matches design ``.meta``. */
.rail-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: var(--font-xs);
  color: var(--color-muted);
  margin-bottom: var(--space-1);
}

.rail-meta-author {
  color: var(--color-subtle);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.rail-meta-sep {
  color: var(--color-muted);
}

.rail-meta-time {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rail-meta-edited {
  font-style: italic;
  color: var(--color-muted);
}

/* Context pill (subject-bound, deep-link) — matches design ``.context-pill``. */
.rail-meta-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.05rem var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-surface-dim);
  color: var(--color-subtle);
  font-size: var(--font-xs);
  text-decoration: none;
  white-space: nowrap;
  border: 1px solid var(--color-border-subtle);
  transition: background var(--transition-normal), border-color var(--transition-normal);
}

.rail-meta-pill:hover {
  background: var(--color-surface-muted);
  border-color: var(--color-border);
}

/* Audience chip (fallback when there's no context pill) — same look as the
   pill but non-interactive. */
.rail-meta-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.05rem var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-surface-dim);
  color: var(--color-subtle);
  font-size: var(--font-xs);
  border: 1px solid var(--color-border-subtle);
  white-space: nowrap;
}

/* Redo discriminator (#639) — marks a kind='redo' comment / the bulk group. */
.rail-meta-chip--redo {
  background: color-mix(in srgb, var(--color-warning) 14%, var(--color-surface));
  color: var(--color-warning-text);
  border-color: color-mix(in srgb, var(--color-warning) 40%, transparent);
  font-weight: var(--weight-semibold);
}

/* Cross-alternative source tag (#640) — "on Alternative 2" on a comment that
   lives on a sibling page of the slot. An accent tint sets it apart from the
   neutral audience chip without competing with the warning redo chip. */
.rail-meta-chip--alt {
  background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
  color: var(--color-accent);
  border-color: color-mix(in srgb, var(--color-accent) 30%, transparent);
}

/* A redo card gets a warning-tinted left edge so it reads apart from notes. */
.rail-card--redo {
  border-left: var(--space-1) solid var(--color-warning);
}

/* A resolved redo (#640 follow-up): its page was re-approved/cancelled, so the
   request is closed. Swap the amber edge for a success-green one and mute the
   body so open redos stand out as the actionable ones. */
.rail-card--redo-resolved {
  border-left-color: var(--color-success);
  opacity: 0.7;
}

.rail-card--redo-resolved .rail-body {
  text-decoration: line-through;
  text-decoration-color: var(--color-subtle);
}

/* Resolved chip — replaces the amber "Redo request" chip once a redo closes. */
.rail-meta-chip--resolved {
  background: color-mix(in srgb, var(--color-success) 14%, var(--color-surface));
  color: var(--color-success);
  border-color: color-mix(in srgb, var(--color-success) 40%, transparent);
  font-weight: var(--weight-semibold);
}

/* Bulk-redo group — display-only wrapper. Members render as stacked lines and
   the batch's redo text is hoisted to __body, printed once for the group. */
.rail-redo-group {
  border: 1px solid color-mix(in srgb, var(--color-warning) 30%, var(--color-border-subtle));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-warning) 5%, var(--color-surface));
  padding: var(--space-2) var(--space-3);
}

.rail-redo-group__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.rail-redo-group__count {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.rail-redo-group__members {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.rail-redo-group__body {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid color-mix(in srgb, var(--color-warning) 25%, var(--color-border-subtle));
}

/* One stacked member. It is a `.rail-card` cut down to a line: the same radius,
   border, surface and amber left edge, so a stacked batch still reads as a run
   of redo cards rather than as one block with rules through it. Hairline
   separators inside a shared panel were the block — the card edge is what
   carries "these are the same kind of thing as the ungrouped redo above". */
.rail-redo-stack__item {
  border: 1px solid var(--color-border);
  border-left: var(--space-1) solid var(--color-warning);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
}

.rail-redo-stack__item--internal {
  background: color-mix(in srgb, var(--color-accent-hover) 9%, var(--color-surface));
}

/* Mirrors `.rail-card--redo-resolved`: a closed redo trades the amber edge for
   green and steps back, so open ones stay the actionable-looking rows. */
.rail-redo-stack__item--resolved {
  border-left-color: var(--color-success);
  opacity: 0.7;
}

.rail-redo-stack__line {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  transition: background var(--transition-fast);
}

/* Only a line that has something to open is a target. A line whose page carries
   no preview renders no chevron and no handler, so it must not look clickable. */
.rail-redo-stack__line--expandable {
  cursor: pointer;
}

.rail-redo-stack__line:hover {
  background: var(--color-hover);
}

.rail-redo-stack__avatar {
  display: inline-flex;
  flex-shrink: 0;
}

/* One page per row is the whole point of the stack, so the meta line truncates
   rather than wrapping — and the pill is what survives the squeeze. */
.rail-redo-stack__line .rail-meta {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  flex-wrap: nowrap;
  overflow: hidden;
}

.rail-redo-stack__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: var(--space-5);
  height: var(--space-5);
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-muted);
  cursor: pointer;
  transition: transform var(--transition-fast), color var(--transition-fast);
}

.rail-redo-stack__toggle:hover {
  color: var(--color-text);
}

.rail-redo-stack__toggle svg {
  width: var(--space-4);
  height: var(--space-4);
}

.rail-redo-stack__item.is-open .rail-redo-stack__toggle {
  transform: rotate(180deg);
}

/* The preview opens inside its card, on a click, and displaces the lines below —
   which is correct now that opening is deliberate. It overlaid while it also
   opened on hover: inline displacement measured 136px per open, so a pointer
   crossing the stack landed on whichever line had just moved under it and opened
   that one too. Dropping hover removed the cause rather than damping it, and the
   overlay went with it — a popover is the shape of a transient preview, not of a
   card the reader chose to expand. */
/* The expanded half of a card: the page, and why it was sent back, side by side.
   Both children hide independently, so a collapsed line with a shared
   requirement leaves this row empty and zero-height — the indent lives here so
   the pair stays aligned whichever of them is showing. */
.rail-redo-stack__expand {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding-left: var(--space-6);
}

.rail-redo-stack__preview {
  width: fit-content;
  flex-shrink: 0;
  margin: 0 0 var(--space-3);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.rail-redo-stack__preview[hidden] {
  display: none;
}

/* Hidden while the group's hoisted list speaks for this line; shown when the
   card is opened, and shown while collapsed for a member whose requirement the
   group cannot speak for. */
.rail-redo-stack__reasons {
  display: none;
  min-width: 0;
  padding: 0 var(--space-3) var(--space-3) 0;
}

.rail-redo-stack__item.is-open .rail-redo-stack__reasons,
.rail-redo-stack__item--body-on-line .rail-redo-stack__reasons {
  display: block;
}

/* The thumb is a link to the page, so it takes the affordance the pill has. */
.rail-redo-stack__thumb-link {
  display: block;
  border-radius: var(--radius-sm);
}

.rail-redo-stack__thumb-link:hover .rail-redo-stack__thumb {
  border-color: var(--color-accent);
}

/* Big enough to recognise the page, small enough that opening two at once still
   reads as a stack. Not a spacing token — the scale tops out at 2rem. */
.rail-redo-stack__thumb {
  display: block;
  max-width: 11rem;
  max-height: 11rem;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}

/* Replies indent to the line they answer — they are never hidden, because the
   couple replying per page is why the group stays display-only. */
.rail-redo-stack__item .rail-replies {
  padding-left: var(--space-6);
}

/* ── Body text ─────────────────────────────────────────────────────────── */
.rail-body {
  color: var(--color-text);
  line-height: var(--line-height-relaxed);
  word-break: break-word;
}

.rail-body p {
  margin: 0 0 var(--space-2);
}

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

.rail-body strong {
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

/* ── Replies inside a card ──────────────────────────────────────────────
   Each reply gets a small avatar + meta + body, separated from the root
   body by a hairline. */
.rail-replies {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* The container is rendered unconditionally so the fragments insert path has a
   stable beforeend anchor (#comment-replies-{id}); with no replies yet it must
   carry no chrome. The :has() check restores spacing/border the moment a reply
   is inserted — no class-flip on the insert. */
.rail-replies:not(:has(.rail-reply)) {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.rail-reply {
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr);
  column-gap: var(--space-3);
  align-items: start;
}

.rail-reply-node {
  display: flex;
  justify-content: center;
  padding-top: 2px;
}

.rail-reply-content {
  min-width: 0;
}

.rail-reply-content .rail-meta {
  margin-bottom: 2px;
}

.rail-reply-content .rail-body {
  font-size: var(--font-sm);
  line-height: var(--line-height-normal);
}

/* ── Activity row body (icon + verb + sep + time) ───────────────────────
   The inline icon repeats the action symbol from the node so the verb
   still scans at a glance when the rail node is small. */
.rail-activity-text {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: var(--font-xs);
  color: var(--color-muted);
  padding-top: 0.15rem;
}

.rail-activity-text > .activity-icon {
  width: 0.95rem;
  height: 0.95rem;
}

.rail-activity-verb {
  min-width: 0;
}

.rail-activity-verb strong {
  color: var(--color-subtle);
  font-weight: var(--weight-semibold);
}

/* ── Website enquiry row (#1063) ────────────────────────────────────────
   What the couple wrote, rendered live from website_form_submissions — no
   comment row behind it, so no card chrome, no actions, no reply target.
   The header line reuses .rail-activity-text; the answers get a quiet block
   below it so a long message reads as prose rather than as a metadata line. */
.submission-answers {
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--color-border);
  background: var(--color-surface-dim);
  border-radius: var(--radius-xs);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.submission-answer dt {
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-weight: var(--weight-medium);
  margin-bottom: var(--space-1);
}

.submission-answer dd {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-text);
  /* A message field has no length cap on the website (services.WEBSITE_FORMS
     gives it max_len 10000), so the value must wrap on its own newlines and
     break rather than widen the feed. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.submission-anonymised,
.submission-attachment,
.submission-released {
  margin: var(--space-3) 0 0;
  font-size: var(--font-xs);
}

/* ── Conversation message row (#1088) ───────────────────────────────────
   One WhatsApp message on the case, read live from conversation_messages —
   the enquiry row's sibling, so the same quiet block under the header line.
   Our side (outbound) carries the accent edge, the couple's the plain one, so
   a run reads as an exchange at a glance without chat bubbles in the rail. */
.conversation-message-body {
  margin: var(--space-2) 0 0;
  padding: var(--space-2) var(--space-4);
  max-width: 64ch;
  border-left: 2px solid var(--color-border);
  background: var(--color-surface-dim);
  border-radius: var(--radius-xs);
  font-size: var(--font-sm);
  color: var(--color-text);
  /* A message is the couple's own prose, up to the channel's limit: keep its
     line breaks and break a long token rather than widen the feed. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.rail-row--outbound .conversation-message-body {
  border-left-color: var(--color-accent);
}

.conversation-message-none {
  margin: var(--space-2) 0 0;
  font-size: var(--font-xs);
  font-style: italic;
}

/* Group toggle button — bare, just text + chev. */
.rail-group-toggle {
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--font-xs);
  color: var(--color-muted);
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  white-space: nowrap;
  transition: color var(--transition-fast);
}

.rail-group-toggle:hover {
  color: var(--color-text);
}

/* Expanded group items — nested activity rows offset under the group node. */
.rail-activity-group .activity-group-items {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-left: calc(1.75rem + var(--space-4));
  padding-top: var(--space-3);
}

.rail-activity-group .activity-group-items[hidden] {
  display: none;
}

.rail-activity-group .activity-group-items .rail-row {
  padding-bottom: 0;
}

.rail-activity-group .activity-group-items .rail-row > .rail-row-node::before {
  display: none;
}

/* ── Hover action chips ────────────────────────────────────────────────
   Floating top-right of the card. Reuses the global .comment-actions
   look + opacity flow, only repositioned for the rail card. */
.rail-row--comment {
  position: relative;
}

.rail-row--comment .comment-actions,
.rail-pinned-item .comment-actions {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
}

/* Pinned cards carry the same hover chips (unpin · reply · edit · delete)
   as feed rows — without their own reveal they stay permanently invisible. */
.rail-pinned-item:hover .comment-actions,
.rail-pinned-item:focus-within .comment-actions {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.rail-row--comment:hover .comment-actions,
.rail-row--comment:focus-within .comment-actions {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* A stack line is one row tall, so floating the chips over it would land them on
   the pill — the one thing on the line that has to stay readable. They ride the
   end of the flex row instead, and only reserve width once revealed. */
.rail-redo-stack__line .comment-actions {
  position: static;
  flex-shrink: 0;
  margin-left: auto;
}

.rail-redo-stack__line:hover .comment-actions,
.rail-redo-stack__line:focus-within .comment-actions {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* Reply rows carry edit · delete chips, revealed on hover like the cards. The
   reply is a 2-col grid; the chips ride absolute top-right so they don't
   disturb it. */
.rail-reply {
  position: relative;
}

.rail-reply-actions {
  top: var(--space-1);
  right: var(--space-1);
}

.rail-reply:hover .comment-actions,
.rail-reply:focus-within .comment-actions {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* ════════════════════════════════════════════════════════════════════════
   Pinned section — each pinned item is a single card with the avatar
   inline inside the meta line. No rail / no node — the section sits
   above the feed.
   Reference: timeline/project/timeline/styles.css §"PINNED".
   ════════════════════════════════════════════════════════════════════════ */
.timeline--rail .page-timeline-pinned {
  background: color-mix(in srgb, var(--color-warning) 5%, var(--color-surface));
  border: 1px solid color-mix(in srgb, var(--color-warning) 35%, var(--color-border));
  border-radius: var(--radius-md);
  padding: 0;
  gap: 0;
  margin-bottom: var(--space-4);
  display: flex;
  flex-direction: column;
}

.timeline--rail .page-timeline-pinned-head {
  padding: var(--space-2) var(--space-3);
  margin-bottom: 0;
}

.timeline--rail .page-timeline-pinned-head .tag {
  background: transparent;
  padding: 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-warning-text);
}

.timeline--rail .page-timeline-pinned-head .tag--with-dot::before {
  background: var(--color-warning);
  width: 7px;
  height: 7px;
}

.rail-pinned-item {
  position: relative;
  background: var(--color-surface);
  border: 1px solid color-mix(in srgb, var(--color-warning) 22%, var(--color-border-subtle));
  border-radius: var(--radius-sm);
  padding: var(--space-4) var(--space-5);
  margin: 0 var(--space-3) var(--space-3);
}

.rail-pinned-item:first-of-type {
  margin-top: 0;
}

/* Pinned meta line carries the avatar inline (design's MetaLine with
   avatar prop). The avatar sits flush at the start, then name · time · pill. */
.rail-meta--with-avatar {
  margin-bottom: var(--space-2);
}

.rail-avatar-wrap {
  display: inline-flex;
  align-items: center;
}

.rail-meta--with-avatar .rail-avatar {
  box-shadow: none;
}

/* Pinned references (#570): compact, read-only jump-to chips on the panel
   rail. The full card stays inline in the feed; these are shortcuts, so they
   reuse the warning-tinted pinned container and lay out as a stacked chip list. */
.page-timeline-pinned--refs[hidden] {
  display: none;
}

.rail-pins-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: 0 var(--space-3) var(--space-3);
}

.rail-pin-ref {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface);
  border: 1px solid color-mix(in srgb, var(--color-warning) 22%, var(--color-border-subtle));
  border-radius: var(--radius-sm);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.rail-pin-ref:hover {
  background: color-mix(in srgb, var(--color-warning) 6%, var(--color-surface));
  border-color: color-mix(in srgb, var(--color-warning) 40%, var(--color-border));
}

.rail-pin-ref-avatar {
  width: 1.25rem;
  height: 1.25rem;
}

.rail-pin-ref-author {
  flex-shrink: 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}

.rail-pin-ref-snippet {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* Pinned strip (item 4): on the case overview the refs section sticks to the
   top of the scroll container so pinned comments stay reachable without
   scrolling, and collapses to just the "Pinned · N" head (default collapsed,
   JS persists the open state). The docs panel keeps the static refs above.

   Full-bleed band: the overview timeline content carries an 18px gutter
   (`.case-detail-page .timeline`), so negative margins cancel it to stretch the
   warning band edge-to-edge and flush to the scroll top; the head + cards
   re-inset to the gutter so they stay aligned with the feed below. Square
   corners + a single bottom hairline read it as a strip, not a card. */
.overview-timeline-scroll .page-timeline-pinned--strip {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  margin: calc(-1 * var(--space-ws-gutter)) calc(-1 * var(--space-ws-gutter)) var(--space-4);
  border: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--color-warning) 30%, var(--color-border));
  border-radius: 0;
  background: color-mix(in srgb, var(--color-warning) 7%, var(--color-surface));
}

.overview-timeline-scroll .page-timeline-pinned--strip .page-timeline-pinned-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-3) var(--space-ws-gutter);
  appearance: none;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-warning-text);
  text-align: left;
  cursor: pointer;
  user-select: none;
}

.overview-timeline-scroll .page-timeline-pinned--strip .rail-pins-list {
  padding: 0 var(--space-ws-gutter) var(--space-ws-gutter);
}

.overview-timeline-scroll .page-timeline-pinned-ico {
  display: inline-flex;
  color: var(--color-warning-text);
}

.overview-timeline-scroll .page-timeline-pinned-title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.overview-timeline-scroll .page-timeline-pinned-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 var(--space-1);
  box-sizing: border-box;
  background: var(--color-warning);
  color: var(--color-warning-text);
  border-radius: var(--radius-pill);
  font-size: var(--font-xxs);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.overview-timeline-scroll .page-timeline-pinned-toggle {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--font-xxs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-warning-text);
  opacity: 0.85;
}

.overview-timeline-scroll .page-timeline-pinned-head:hover .page-timeline-pinned-toggle {
  opacity: 1;
}

.overview-timeline-scroll .page-timeline-pinned-chev {
  display: inline-flex;
  transition: transform var(--transition-normal);
}

/* Open state lives on the scroll container (``pins-open``), not the strip — the
   strip is rebuilt on every pin/unpin, so a class on it wouldn't survive. */
.overview-timeline-scroll.pins-open .page-timeline-pinned-chev {
  transform: rotate(180deg);
}

.overview-timeline-scroll:not(.pins-open) .page-timeline-pinned--strip .rail-pins-list {
  display: none;
}

/* Show / Hide label: both render; the container's open state picks one. */
.overview-timeline-scroll .page-timeline-pinned-toggle-label--hide {
  display: none;
}

.overview-timeline-scroll.pins-open .page-timeline-pinned-toggle-label--show {
  display: none;
}

.overview-timeline-scroll.pins-open .page-timeline-pinned-toggle-label--hide {
  display: inline;
}

/* Flash a feed row when a pinned reference jumps to it (#570). The ring rides
   on top of the card's resting shadow and fades out via the base transition
   when the flash class is removed (~1.4s later, in JS). */
.rail-row--flash .rail-card {
  box-shadow: var(--shadow-sm), var(--shadow-focus-warning);
}

/* ── end migrated: platform/tools/cases/templates/case_detail.html ── */

/* ── migrated: platform/tools/cases/templates/partials/add_person_modal.html ── */

/* The Add-person modal hosts an `.mh-picker` for Role selection. The
   menu is absolute-positioned, so the default `.modal-body { overflow-y: auto }`
   clips it. The form is short — no scrollable content — so dropping
   overflow here is safe. */
#add-person-modal .modal-body {
  overflow: visible;
}

/* ── end migrated: platform/tools/cases/templates/partials/add_person_modal.html ── */

/* ── migrated: platform/tools/cases/templates/case_new.html ── */

.case-new-page .doc-requirements {
  margin-top: 1rem;
}

.case-new-page .doc-requirements h4 {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0 0 0.5rem;
}

.case-new-page .doc-check-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0;
  font-size: 0.9rem;
  cursor: pointer;
}

.case-new-page .doc-check-label input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.case-new-page .doc-info-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  background: var(--color-border, #ddd);
  color: var(--color-muted, #888);
  font-size: 0.7rem;
  font-style: italic;
  font-weight: 600;
  cursor: help;
  flex-shrink: 0;
  margin-left: auto;
}

.case-new-page .child-cert-count {
  margin-top: 0.75rem;
  padding-left: 1.5rem;
}

/* ── end migrated: platform/tools/cases/templates/case_new.html ── */

/* ── migrated: platform/tools/people/templates/person_profile.html ── */

.person-profile-page .profile-header {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}

.person-profile-page .profile-info {
  flex: 1;
  min-width: 0;
}

.person-profile-page .avatar-edit {
  position: relative;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  overflow: hidden;
  cursor: pointer;
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0;
}

.person-profile-page .avatar-edit__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  opacity: 0;
  transition: opacity 0.12s ease-in-out;
  border-radius: 50%;
}

.person-profile-page .avatar-edit:hover .avatar-edit__overlay,
.person-profile-page .avatar-edit:focus-visible .avatar-edit__overlay {
  opacity: 1;
}

.person-profile-page .avatar-dropzone {
  display: block;
  border: 1.5px dashed var(--color-border);
  border-radius: var(--radius-md);
  padding: 2rem 1.5rem;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}

.person-profile-page .avatar-dropzone.is-dragover {
  border-color: var(--color-accent);
  background: var(--color-bg);
}

.person-profile-page .avatar-dropzone__preview {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: var(--color-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 0.75rem;
  overflow: hidden;
  color: var(--color-muted);
  font-size: 2rem;
}

.person-profile-page .avatar-dropzone__preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.person-profile-page .avatar-dropzone__hint {
  color: var(--color-muted);
  font-size: 0.85rem;
  margin: 0;
}

.person-profile-page .avatar-dropzone__choose {
  display: inline-block;
  margin-top: 0.4rem;
  color: var(--color-accent);
  font-weight: 600;
  cursor: pointer;
}

.person-profile-page .avatar-dropzone__choose input {
  display: none;
}

.person-profile-page .profile-info h1 {
  font-size: 1.35rem;
  font-weight: 600;
  margin: 0 0 0.25rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.person-profile-page .profile-meta {
  font-size: 0.85rem;
  color: var(--color-muted);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  margin-top: 0.25rem;
}

.person-profile-page .profile-meta span {
  white-space: nowrap;
}

.person-profile-page .profile-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
}

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

.person-profile-page .profile-section {
  min-width: 0;
}

.person-profile-page .profile-section-full {
  grid-column: 1 / -1;
}

.person-profile-page .section-title {
  font-size: 0.9rem;
  font-weight: 600;
  margin-bottom: 0.75rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--color-border);
}

.person-profile-page .detail-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.4rem 0;
  font-size: 0.85rem;
}

.person-profile-page .detail-label {
  color: var(--color-muted);
  flex-shrink: 0;
  margin-right: 1rem;
}

.person-profile-page .detail-value {
  text-align: right;
}

.person-profile-page .action-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

/* Portal account card (#1005 slice 6) — replaces the single muted
   "Portal access active." line on an external person's profile. Built from
   layout only; the badge inside brings its own .status-badge styling. */
.person-profile-page .portal-account-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.person-profile-page .portal-account-card__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.person-profile-page .portal-account-card__email {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.person-profile-page .portal-account-card__meta {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.person-profile-page .portal-account-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.person-profile-page .profile-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.person-profile-page .form-row-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

@media (max-width: 600px) {
  .person-profile-page .form-row-2 {
    grid-template-columns: 1fr;
  }
}

.person-profile-page .access-grid {
  display: grid;
  grid-template-columns: auto repeat(3, 1fr);
  gap: 0;
  font-size: 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.person-profile-page .access-grid-header {
  background: var(--color-bg);
  font-weight: 600;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--color-border);
}

.person-profile-page .access-grid-cell {
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--color-border);
}

.person-profile-page .access-grid-cell:last-child,
.person-profile-page .access-grid-header:last-child {
  border-bottom: none;
}

.person-profile-page .access-grid-module {
  font-weight: 500;
  background: var(--color-bg);
}

/* ── end migrated: platform/tools/people/templates/person_profile.html ── */

/* ── Engine Room pages (/ux pass, 2026-10-09) ─────────────────────────────
   Shared vocabulary for /engine and its five children: a feed of events in
   a card, a grid of tool/model tiles, a chip row, a filter row and a pager.
   Everything reads at the body style; status words are tags. */

/* A feed of events: one row per event, a hairline between rows, the card
   inset on the sides and the bottom. Rows sit at the line height plus one
   small step; the first and last row carry no extra padding so the card
   insets stay exact (/ux card-inset-top, card-inset-bottom). */
.card > .engine-feed {
  padding: 0 var(--space-5) var(--space-5);
}

/* Every row opens with a tag, whose own 0.2rem of padding is chrome, not
   air: the top inset is measured to the tag's text, so the feed gives
   that much back when it opens the card. */
.card > .engine-feed:first-child {
  padding-top: calc(var(--space-5) - 0.2rem);
}

/* Cards stacked inside a wrapper (an htmx target) get the standard gap, as
   stacked cards directly in a section do (/ux card-gap). */
.card-stack > .card + .card {
  margin-top: var(--space-5);
}

.engine-feed__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border-subtle);
}

.engine-feed__row:first-child {
  padding-top: 0;
}

.engine-feed__row:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

/* The status word at the start of a row is a tag of fixed width, so the
   text column lines up down the feed. */
.engine-feed__row > .tag {
  flex-shrink: 0;
  min-width: 3.5rem;
  text-align: center;
}

.engine-feed__text {
  flex: 1;
  min-width: 0;
}

.engine-feed__meta {
  white-space: nowrap;
}

/* Links in a feed, a card list or a status line take the accent; there is
   no platform-wide anchor rule (/ux text-color-token). */
.engine-feed a,
.card > .card-list a,
.card-title a {
  color: var(--color-accent);
  text-decoration: none;
}

.card-title a {
  color: inherit;
}

/* Tiles: a grid of small cards (one per tool, model or provider) under a
   section head. Cards in the grid keep their own .card-title. */
.engine-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--space-5);
}

/* A row of tags: filters (today's domains on /engine/operations), the
   addons in use on /system/document-types. */
.engine-chips,
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.engine-chips > a,
.chip-row > a {
  text-decoration: none;
}

/* A chip row that opens a card: the tags' own 0.2rem of padding is chrome,
   so the row gives that much back to keep the inset exact (/ux card-inset-top). */
.card > .chip-row:first-child {
  padding-top: calc(var(--space-5) - 0.2rem);
}

/* The slug beside a page-type label in the edit row. */
.dt-edit__slug {
  min-width: 100px;
}

/* A filter row with labels inside a card: wraps when the row is narrow. */
.card > .inline-form.engine-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  row-gap: var(--space-3);
}

.engine-filters > label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* Buttons in a table's last column, right-aligned with the cells. */
.engine-row-actions,
.row-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* "page 1 of 3" and the Previous / Next pair at the foot of a feed. */
.engine-pager {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
}

.engine-pager > span {
  display: inline-flex;
  gap: var(--space-1);
}

/* ── /system/status dashboard ── */
/* Rows sit at the line height, no row gap: a definition list reads with the
   same line pitch as a paragraph (/ux body-line-pitch). */
.status-dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0 var(--space-4);
  margin: 0;
}

.status-dl dt {
  color: var(--color-text);
  font-size: var(--font-xs);
}

.status-dl dd {
  margin: 0;
  font-size: var(--font-xs);
}

/* Sits directly inside a .card under .card-title; the card itself has
   no padding, so the paragraph carries the standard inset (/ux card-inset-x). */
/* Body text in a card reads at the table-value size (/ux, 2026-10-09). */
.status-line {
  margin: 0;
  padding: 0 var(--space-5);
  font-weight: var(--weight-normal);
  font-size: var(--font-xs);
}

.status-hint {
  margin: var(--space-1) 0 0;
  color: var(--color-muted);
  font-size: var(--font-xs);
}

.status-modal-dl {
  margin-bottom: var(--space-5);
}

.status-modal-subhead {
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--font-sm);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
}

.status-modal-list {
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 16rem;
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.status-modal-list li {
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-sm);
  font-variant-numeric: tabular-nums;
  border-bottom: 1px solid var(--color-border-subtle);
}

.status-modal-list li:last-child {
  border-bottom: none;
}

/* ── end /system/status ── */

/* ══════════════════════════════════════════════════════════════════════════════
   DOCUMENTS VIEW (#174) — activity-led document list at /cases/docs.
   Activity row, date-bucket grouping, filter chips, popover menu, pagination.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── Couple avatar tints (activity rows + grouped headers) ─────────────────── */
/* Per-party tints — bespoke palette, not part of the canonical token set.
   Kept as literals because they're semantically tied to "party A/B avatar". */

.couple-avatar--a {
  background: var(--couple-a-bg, #dde1f3);
  color: var(--couple-a-fg, #3b4790);
}

.couple-avatar--b {
  background: var(--couple-b-bg, #f3ddea);
  color: var(--couple-b-fg, #903b71);
}

.couple-avatar--c {
  background: var(--couple-c-bg, #ddf3e7);
  color: var(--couple-c-fg, #1a8c42);
}

.couple-avatar--d {
  background: var(--couple-d-bg, #f3ecdd);
  color: var(--couple-d-fg, #905a3b);
}

/* ── Activity-led row ──────────────────────────────────────────────────────── */

.activity {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  min-width: 0;
}

.activity-verb {
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  font-size: var(--font-base);
  white-space: nowrap;
}

/* Activity row layout — actor name leads, verb tag follows inline.
   Filename / quote drops to a second line below. The bucket header
   already names the document, so we don't repeat the requirement
   label here — what's informative per row is who did it and what
   specifically. */
.activity-row-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.activity-actor {
  color: var(--color-text);
  font-weight: var(--weight-normal);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Indent docs row content so the verb-tag/actor line aligns with the
   bucket header's requirement label above it (which sits past the
   collapse caret). Without this the rows hug the column's left edge
   while the header indents — a small visual jolt every time a bucket
   opens. Also tightens vertical padding to match the bucket-row rhythm
   so the open bucket reads as one block. */
.docs-row td {
  padding: var(--space-3) var(--space-5);
}

.docs-row td:first-child {
  padding-left: calc(var(--space-5) + 14px + var(--space-2));
}

.activity-verb--upload {
  color: var(--color-info);
}

.activity-verb--replace {
  color: var(--color-info);
}

.activity-verb--comment {
  color: var(--color-accent);
}

.activity-verb--message {
  color: var(--color-accent);
}

.activity-verb--flag {
  color: var(--color-danger);
}

.activity-verb--approve {
  color: var(--color-success);
}

.activity-time,
.row-time {
  color: var(--color-muted);
  font-size: var(--font-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.activity-sep {
  color: var(--color-border);
}

.activity-what {
  color: var(--color-subtle);
  font-weight: var(--weight-normal);
  font-size: var(--font-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 2px;
}

.activity-quote {
  display: block;
  margin-top: 2px;
  color: var(--color-subtle);
  font-size: var(--font-sm);
  font-style: italic;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* ── List-view layout manager (column widths + resize + sideways scroll) ─────
   When a list-view table opts in via data-lv-layout + table[data-lv-resizable]
   + <colgroup> with data-col-key/min-width, the layout JS sets per-column
   widths. A set that does not fit is never trimmed (#1441): the table grows
   past its wrap and the shell body scrolls sideways, with the pinned
   columns held on the left. These rules cover:
     - table-layout: fixed for stable column widths
     - resize handle look + drag affordance
     - the pinned columns (§"Pinned columns" below)
*/

.lv-host[data-lv-layout] table[data-lv-resizable] {
  table-layout: fixed;
}

/* ── List-view live-updates pill (#268) ─────────────────────────────────────
   Floating overlay anchored top-center inside .table-wrap. Mounted by
   static/js/list_view_realtime.js on pages whose host carries
   data-lv-realtime-scope. position:absolute means appearing/disappearing
   never reflows the table — the pill announces itself without nudging
   the data the user is reading. Accent-coloured so the eye actually
   catches it; one-shot pulse on first appearance to sell the moment.
   Click → re-fetch the current URL via the framework's refresh path. */

.lv-host[data-lv-realtime-scope] .table-wrap {
  /* Anchor for the pill's position: absolute. Scoped to realtime-opt-in
     hosts so unrelated tables aren't affected. */
  position: relative;
}

.lv-realtime-pill {
  position: absolute;
  top: var(--space-3);
  left: 50%;
  /* --z-dropdown sits above the table's sticky <th>s (which use
     --z-sticky); without this, ths painted later in DOM order would
     occlude the pill at the same coordinate. */
  z-index: var(--z-dropdown, 50);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  background: var(--color-accent);
  /* Mirrors .btn-primary's white-on-accent text token. */
  color: var(--color-surface);
  border: 0;
  border-radius: var(--radius-pill);
  font-size: var(--font-sm);
  font-weight: 500;
  line-height: 1.4;
  cursor: pointer;
  box-shadow: var(--shadow-md);
  white-space: nowrap;
  /* Resting hidden state: nudged up + invisible. .--in mounts the pill
     into its rest position with a slight overshoot via the spring-y
     cubic-bezier — gives the entrance a sense of physicality. */
  transform: translate(-50%, -0.75rem);
  opacity: 0;
  transition:
    transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1),
    opacity 0.18s ease-out;
}

.lv-realtime-pill[hidden] {
  display: none;
}

.lv-realtime-pill--in {
  transform: translate(-50%, 0);
  opacity: 1;
}

/* One-shot ring pulse on first appearance (count crosses 0 → 1). Draws
   the eye to the announcement, then settles. JS strips the class after
   the keyframe so the next 1 → 2 increment doesn't re-trigger it (only
   the first emergence is the surprising moment worth animating). */
.lv-realtime-pill--pulse {
  animation: lv-realtime-pulse 0.6s ease-out;
}

@keyframes lv-realtime-pulse {
  0% {
    box-shadow:
      var(--shadow-md),
      0 0 0 0 color-mix(in srgb, var(--color-accent) 55%, transparent);
  }

  100% {
    box-shadow:
      var(--shadow-md),
      0 0 0 14px color-mix(in srgb, var(--color-accent) 0%, transparent);
  }
}

.lv-realtime-pill-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.lv-realtime-pill:hover {
  background: var(--color-accent-hover);
}

.lv-realtime-pill:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.lv-realtime-pill--warning {
  background: color-mix(in srgb, var(--color-warning) 14%, var(--color-surface));
  color: var(--color-warning-text);
}

.lv-realtime-pill--warning:hover {
  background: color-mix(in srgb, var(--color-warning) 24%, var(--color-surface));
}

/* Surgical row update (#662 follow-on). When a live update replaces a single
   row in place (core/fragments), a subtle accent-tinted background blooms in
   then fades out over the cells so the change reads as a controlled update. The
   .lv-row--updated class is baked into the server-rendered row fragment (the
   single-row endpoint sets lv_row_updated) so it rides whichever node the OOB
   swap lands on; the full-table + append renders leave it off. Mirrors the
   files-table row-pulse pattern.
   NB: background only — an earlier opacity ease-in dipped the whole row to 50%
   on every update, which read as a flash; removed. The tint EASES IN (the
   `6%` bloom below) rather than appearing at full strength on frame 0: an
   instant 0→16% pop landed on the same frame as the OOB content swap and read
   as a harsh flick (frame-captured on #688). The bloom masks the content cut
   instead of slamming alongside it. */
.data-table tr.lv-row--updated > td {
  animation: lv-row-updated-pulse 1.6s ease-out;
}

/* Structured filter/sort swap (#688 follow-on). On a warm filter/sort change the
   table shell (colgroup + thead) stays mounted and only the rows region swaps —
   so it fades to partial opacity while the fetch is in flight, then fills, the
   same calm loading motion the case-detail pane uses (arch §"Loading, updating
   & motion") instead of a whole-table repaint. The dim is deferred ~140ms by
   list_view.js so a fast swap never flickers. Opacity only (compositor-friendly,
   honours reduced-motion via the token). */
tbody[id^="lv-rows-"] {
  transition: opacity var(--transition-normal);
}

.lv-rows--loading {
  opacity: 0.5;
}

@media (prefers-reduced-motion: reduce) {
  tbody[id^="lv-rows-"] {
    transition: none;
  }
}

@keyframes lv-row-updated-pulse {
  /* Bloom in over the first ~6% (~95ms) so the tint never appears in a single
     frame; ease-in for a soft onset, ease-out for a lingering fade. */
  0% {
    background: transparent;
    animation-timing-function: ease-in;
  }

  6% {
    background: color-mix(in srgb, var(--color-accent) 16%, transparent);
    animation-timing-function: ease-out;
  }

  100% {
    background: transparent;
  }
}

/* Surgical per-cell update (#688). When a live update replaces a single cell in
   place (core/fragments, ?cols=…), the same accent-tinted pulse blooms in then
   fades out over just that <td> so the change reads as a controlled update
   without repainting the rest of the row. The .lv-cell--updated class is baked
   into the server-rendered cell fragment (the per-cell endpoint sets
   lv_cell_updated); the full-table + append + whole-row renders leave it off.
   Reuses the row pulse keyframe — same eased-in bloom, single cell. */
.data-table td.lv-cell--updated {
  animation: lv-row-updated-pulse 1.6s ease-out;
}

/* The platform-wide `@media (max-width: 900px) { .data-table { display:
   block; overflow-x: auto } }` rule converts every data table into a
   horizontally-scrollable block on small screens. A layout-managed table
   already scrolls sideways — in the shell body, the one box that also owns
   vertical scroll — so a second scroller on the table itself would split
   the two axes again (and take the sticky header and pinned columns with
   it). Override for layout-managed tables only. */
@media (max-width: 900px) {
  .lv-host[data-lv-layout] table[data-lv-resizable].data-table {
    display: table;
    overflow: visible;
  }
}

.lv-host[data-lv-layout] table[data-lv-resizable] th,
.lv-host[data-lv-layout] table[data-lv-resizable] td {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Resize handle — sits at the right edge of a <th>, captures drags. */
.lv-host[data-lv-layout] table[data-lv-resizable] thead th {
  position: relative;
}

/* The hit zone is intentionally wide (~14px) so the user does not have
   to hit the exact pixel. The visible tick is rendered as a 1px
   pseudo-element pinned to the column boundary so the affordance stays
   thin and unobtrusive — only the invisible parent grows. */
.lv-col-resizer {
  position: absolute;
  top: 0;
  right: 0;
  width: 14px;
  height: 100%;
  cursor: col-resize;
  user-select: none;
  z-index: var(--z-sticky);
}

/* Subtle always-on tick at the column edge so the user can find the
   drag zone. Half-height vertical line at low contrast — reads as a
   column separator. On hover it grows to full height and brightens
   to the accent colour so the affordance is unmistakable. */
.lv-col-resizer::after {
  content: "";
  position: absolute;
  top: 30%;
  bottom: 30%;
  right: 0;
  width: 1px;
  background: var(--color-border);
  transition: background var(--transition-fast),
    top var(--transition-fast),
    bottom var(--transition-fast),
    width var(--transition-fast);
}

.lv-col-resizer:hover::after,
.lv-col-resizer.is-dragging::after {
  background: var(--color-accent);
  top: 0;
  bottom: 0;
  width: 2px;
}

/* Suppress hover affordances on the rest of the page during a drag so
   the cursor stays col-resize even over rows / popovers. */
body.lv-resizing,
body.lv-resizing * {
  cursor: col-resize !important;
  user-select: none !important;
}

/* Inside the layout-managed Case column, the responsive_text renderer
   needs to know the *available* width for the couple name. By default
   `.case-ref` and `.couple-row` are inline-flex and content-sized —
   they grow to fit whatever the renderer writes in, defeating the
   measurement. Force them to fill the cell and let the inner
   couple-row span flex-shrink to 0; the renderer then reads a
   well-defined width and picks the right tier. */
.lv-host[data-lv-layout] td[data-col-key="case"] .case-ref {
  display: flex;
  width: 100%;
  min-width: 0;
  align-items: center;
}

.lv-host[data-lv-layout] td[data-col-key="case"] .couple-row {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
}

.lv-host[data-lv-layout] td[data-col-key="case"] .couple-row>span {
  display: block;
  min-width: 0;
}

/* Scoped to layout-managed hosts so non-list-view tables (audit_table
   etc.) don't gain an unexpected positioning context. `position: relative`
   anchors the live-updates pill (§"List-view live-updates pill").

   `overflow: visible` on BOTH axes, and the one-axis history is why.

   This rule said `overflow-x: hidden` once, and that silently made the wrap
   the vertical scroller: a computed `visible` beside a non-`visible`
   sibling is promoted to `auto` (CSS Overflow 3 §3.1), so the wrap — a flex
   item whose `min-height: auto` then resolves to 0 — shrank and scrolled
   itself, while `#lv-shell-body-{scope}`, which every comment called the
   scroll authority, never scrolled (#1073). It then said `overflow-x: clip`,
   the only non-`visible` value that leaves the other axis alone — correct
   while the layout manager hid every column that did not fit, so nothing
   was ever wider than the wrap.

   Since #1441 nothing is hidden: a set wider than the host makes the table
   wider than the wrap, and `clip` would cut those columns off. Making the
   wrap the sideways scroller is not an option either — any scrolling value
   on x promotes y, which is #1073 again, and a sticky header anchors to its
   nearest scroller, so it would stop sticking to the body. So the wrap does
   not scroll at all: the table overflows it, and the shell body scrolls on
   both axes (§"Internal scroll shell"). The sticky header (`top`) and the
   pinned columns (`left`) then resolve against one box, and the sideways
   scrollbar sits at the bottom of the card rather than under the last row.

   `!important` wins against the base `.table-wrap { overflow-x: auto }`
   that otherwise leaks through under pre-existing per-page rules. */
.lv-host[data-lv-layout] .table-wrap {
  position: relative;
  overflow: visible !important;
}

/* Layout-managed host stays `overflow: visible` (inherited from the
   base `.lv-host` rule) so absolutely-positioned popovers in the
   toolbar — Saved Views, plus any future popover-style controls —
   can extend below the toolbar without being clipped. The shell
   body inside is the scroller on both axes; the host itself doesn't
   need any clipping rules. */

/* ── Pinned columns (#1441) ──────────────────────────────────────────────────
   A column config marked `pinned` (Couple + ID on the case boards) sticks to
   the left edge while the shell body scrolls sideways, so a row is always
   readable as whose row it is.

   Positional on purpose. list_view_layout.js moves the pinned columns to the
   front of the row and writes their count to `data-lv-pins` on the host; the
   rules below stick the first N cells of every row. Rows that arrive later —
   an infinite-scroll page, a surgical single-row or single-cell update
   (core/fragments) — are pinned by the same selector with no JS touching
   them. The host sits outside every swap target, so the count and the
   offsets (`--lv-pin-left-2/3`, the widths of the pinned cells before) ride
   through all of them. `[colspan]` cells (the infinite-scroll sentinel, a
   group's bucket row) span the whole row and are left alone.

   Stacking: a pinned body cell sits above the cells scrolling under it
   (`--z-base`) and below the header (`--z-sticky`), so rows still pass under
   the sticky header; a pinned header cell sits one above the rest of the
   header, which slides under it sideways. */
.lv-host[data-lv-pins="1"] tr > :nth-child(-n+1):not([colspan]),
.lv-host[data-lv-pins="2"] tr > :nth-child(-n+2):not([colspan]),
.lv-host[data-lv-pins="3"] tr > :nth-child(-n+3):not([colspan]) {
  position: sticky;
  left: 0;
  z-index: var(--z-base);
}

.lv-host[data-lv-pins="2"] tr > :nth-child(2):not([colspan]),
.lv-host[data-lv-pins="3"] tr > :nth-child(2):not([colspan]) {
  left: var(--lv-pin-left-2, 0);
}

.lv-host[data-lv-pins="3"] tr > :nth-child(3):not([colspan]) {
  left: var(--lv-pin-left-3, 0);
}

/* `:not([colspan])` here too, though no header cell spans: it keeps this
   rule one type selector above the one before, so it wins on z-index.

   `top: auto` because going sticky would otherwise pick up `.data-table th`'s
   `top: 0` and pin these header cells vertically too — on a board whose
   other header cells are `position: relative` and scroll away (only the
   `is-flush` list pins its header, §"Pin the table header", whose `top: 0`
   out-ranks this). Measured on /cases before this line: half a header,
   Couple + ID, left floating over the rows 6,000px down. */
.lv-host[data-lv-pins="1"] thead tr > :nth-child(-n+1):not([colspan]),
.lv-host[data-lv-pins="2"] thead tr > :nth-child(-n+2):not([colspan]),
.lv-host[data-lv-pins="3"] thead tr > :nth-child(-n+3):not([colspan]) {
  top: auto;
  z-index: calc(var(--z-sticky) + var(--z-base));
}

/* A pinned body cell must be opaque, or the columns scrolling under it show
   through. Row backgrounds live on the <tr>, and several are translucent
   (the service tint is a 9% wash), so the cell cannot just inherit one.
   Each row state that paints a background therefore also declares its
   OPAQUE twin as `--lv-pin-bg`, beside its own background, and the pinned
   cell paints that: hover, the service tint, the selected row, a group's
   bucket row. A row state added later without a twin pins to plain
   surface — visibly flat under the pointer, never see-through.
   `background-color` only, so the tint's leading-edge stroke
   (`background-image`) survives on the first cell. */
.lv-host[data-lv-pins="1"] tbody tr > td:nth-child(-n+1):not([colspan]),
.lv-host[data-lv-pins="2"] tbody tr > td:nth-child(-n+2):not([colspan]),
.lv-host[data-lv-pins="3"] tbody tr > td:nth-child(-n+3):not([colspan]) {
  background-color: var(--lv-pin-bg, var(--color-surface));
}

/* The surgical-update pulse animates td backgrounds from transparent, which
   on a pinned cell would turn it see-through for the 1.6s of every live
   update. Same bloom, mixed onto the cell's own opaque ground. */
.lv-host[data-lv-pins="1"] tbody tr.lv-row--updated > td:nth-child(-n+1):not([colspan]),
.lv-host[data-lv-pins="2"] tbody tr.lv-row--updated > td:nth-child(-n+2):not([colspan]),
.lv-host[data-lv-pins="3"] tbody tr.lv-row--updated > td:nth-child(-n+3):not([colspan]) {
  animation-name: lv-pinned-updated-pulse;
}

@keyframes lv-pinned-updated-pulse {
  0% {
    background-color: var(--lv-pin-bg, var(--color-surface));
    animation-timing-function: ease-in;
  }

  6% {
    background-color: color-mix(in srgb, var(--color-accent) 16%, var(--lv-pin-bg, var(--color-surface)));
    animation-timing-function: ease-out;
  }

  100% {
    background-color: var(--lv-pin-bg, var(--color-surface));
  }
}

/* The divider on the last pinned column, only once something has scrolled
   under it — at rest the pinned cells are just the first columns and get no
   line the others lack. A background stroke rather than a border: under
   `border-collapse` a cell's border is painted by the table and would scroll
   away from the sticky cell (see the flush header's `::after` divider). */
.lv-host--scrolled-x[data-lv-pins="1"] tr > :nth-child(1):not([colspan]),
.lv-host--scrolled-x[data-lv-pins="2"] tr > :nth-child(2):not([colspan]),
.lv-host--scrolled-x[data-lv-pins="3"] tr > :nth-child(3):not([colspan]) {
  background-image: linear-gradient(to left, var(--color-border) 1px, transparent 1px);
}

/* ── Case-ref cell (case id chip + couple) ─────────────────────────────────── */

.case-ref {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

/* ── Date-bucket header (when grouped) ─────────────────────────────────────── */

/* The bucket row paints its cells, not itself; the custom property is for a
   pinned cell, whose own rule would otherwise out-rank this one
   (§"Pinned columns"). */
.bucket-row {
  --lv-pin-bg: var(--color-surface-dim);
}

.bucket-row td {
  background: var(--color-surface-dim);
  padding: var(--space-3) var(--space-5);
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-subtle);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.bucket-row--toggle {
  cursor: pointer;
  user-select: none;
}

.bucket-row--toggle:hover {
  --lv-pin-bg: color-mix(in srgb, var(--color-accent) 6%, var(--color-surface-dim));
}

.bucket-row--toggle:hover td {
  background: color-mix(in srgb, var(--color-accent) 6%, var(--color-surface-dim));
  color: var(--color-text-strong);
}

.bucket-row td .bucket-count {
  margin-left: var(--space-2);
  color: var(--color-muted);
  font-weight: var(--weight-medium);
  text-transform: none;
  letter-spacing: 0;
}

.bucket-row td .bucket-latest {
  margin-left: var(--space-2);
  color: var(--color-subtle);
  font-weight: var(--weight-medium);
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--font-xs);
  font-variant-numeric: tabular-nums;
}

/* Document bucket header — per-column cells aligned with rows below.
   The activity cell keeps the uppercase section-divider treatment;
   other cells render in normal row style so the data is readable. */
.bucket-row--document td {
  text-transform: none;
  letter-spacing: 0;
  font-weight: var(--weight-medium);
  color: var(--color-text);
  font-size: var(--font-sm);
}

.bucket-row--document td:first-child {
  font-weight: var(--weight-semibold);
  color: var(--color-subtle);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: var(--font-xs);
}

.bucket-caret {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-right: var(--space-2);
  transition: transform var(--transition-normal);
  color: var(--color-muted);
}

.bucket-caret--closed {
  transform: rotate(-90deg);
}

/* A grouped list view (list_table `groups=`): one <tbody> per group, its
   bucket row first. A folded group keeps only its bucket row. */
.lv-group.is-folded > tr:not(.bucket-row) {
  display: none;
}

.bucket-collapse-all {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  color: var(--color-muted);
  transition: color var(--transition-normal);
}

.bucket-collapse-all:hover {
  color: var(--color-text);
}

/* ── Filter chip row (active filters under the toolbar) ────────────────────── */

.filter-chips {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  background: var(--color-surface-dim);
  border-bottom: 1px solid var(--color-border-subtle);
  flex-wrap: wrap;
  font-size: var(--font-sm);
}

/* Keep the active-filter chips in view while the list scrolls.

   They sit inside `#lv-shell-body-{scope}` alongside the table, and until
   #1073 that box did not scroll — `.table-wrap` did — so the chips were
   frozen for free. Moving the scroll up to the body would have carried them
   off-screen with the rows, which is the one user-visible regression that
   change could have caused: scrolling a long board would silently lose the
   record of what is filtering it.

   `.filter-chips` already paints an opaque `--color-surface-dim`, so rows
   pass underneath cleanly with no extra background needed.

   The sticky goes on the REGION WRAPPER (`#lv-chips-{scope}`), not on
   `.filter-chips` inside it. A sticky element is confined to its own parent's
   box, and the wrapper is exactly chip-row height — so sticking the inner
   element gives it nowhere to travel and it scrolls away with its parent,
   looking like the rule did nothing. The wrapper is a direct child of the
   scrolling body, which is the box the offset has to resolve against.
   Measured: inner → 300px of drift, wrapper → 0.

   z-index is deliberately BELOW the sticky `<thead>`'s `--z-sticky`. The two
   only collide on a surface that is both `.is-flush` (where the thead is
   sticky — main.css §"Pin the table header") and renders a chip row, which no
   surface does today. If one ever does, the header wins and the chips slide
   under it: losing the chips costs the reader a filter summary they can still
   see in the toolbar, losing the column headers costs them the table. */
/* `left: 0` since #1441: the body also scrolls sideways now, and the chips
   are the width of the body, not of the table — without it they would ride
   off to the left with the first columns. */
.lv-host[data-lv-layout] [id^="lv-chips-"] {
  position: sticky;
  top: 0;
  left: 0;
  z-index: var(--z-base);
}

/* The footer summary has the same story and the same fix, one edge down.

   `.lv-footer` has carried `position: sticky; bottom: 0` since the framework
   shipped, and it never did anything: the body did not scroll, so the footer
   simply sat at the bottom of a static box and was always in view. #1073 makes
   the body scroll, at which point the declaration finally matters — and is
   confined to `#lv-footer-{scope}`, a wrapper exactly its own height, so it
   cannot travel. Measured before this rule: the footer sat at y=5916 against a
   body bottom of y=741, i.e. the row count vanished until you scrolled to the
   end of the list.

   So the wrapper sticks and `.lv-footer`'s own declaration becomes the no-op
   it has always effectively been. Left in place rather than removed — it is
   the visual home for the footer and harmless there, and deleting it would
   read as a behaviour change when it is not. */
.lv-host[data-lv-layout] [id^="lv-footer-"] {
  position: sticky;
  bottom: 0;
  /* Same sideways story as the chips above. */
  left: 0;
  z-index: var(--z-base);
}

.filter-chips-label {
  color: var(--color-muted);
  margin-right: var(--space-1);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-4);
  font-size: var(--font-xs);
  color: var(--color-text);
  font-weight: var(--weight-medium);
}

.chip--view {
  background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface));
  border-color: color-mix(in srgb, var(--color-accent) 35%, var(--color-border));
}

.chip-key {
  color: var(--color-muted);
  font-weight: var(--weight-normal);
}

.chip-close {
  width: 16px;
  height: 16px;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
  display: grid;
  place-items: center;
  font-size: 12px;
  line-height: 1;
  padding: 0;
}

.chip-close:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.chips-clear {
  margin-left: auto;
  background: var(--color-surface-dim);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  font-family: inherit;
  transition: background var(--transition-fast),
    border-color var(--transition-fast),
    color var(--transition-fast);
}

.chips-clear:hover {
  background: var(--color-bg);
  border-color: var(--color-muted);
  color: var(--color-text-strong);
}

/* ── Saved-views popover menu ──────────────────────────────────────────────── */

.menu-host {
  position: relative;
  display: inline-block;
}

.menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  min-width: 240px;
  z-index: var(--z-dropdown);
  padding: var(--space-2);
}

.menu-item--active {
  background: color-mix(in srgb, var(--color-accent) 8%, transparent);
  color: var(--color-accent);
  font-weight: var(--weight-medium);
}

.menu-count {
  margin-left: auto;
  color: var(--color-muted);
  font-size: var(--font-xs);
  font-variant-numeric: tabular-nums;
}

/* ── Page context menu — the docs-workspace right-click menu. Wears the DS kebab
   (mh-picker / more_button) vocabulary so it matches the People-section kebab
   pixel-for-pixel; page-context-menu.js builds .mh-picker-item rows and sets the
   cursor coordinates on .mh-picker-menu--more (already position: fixed). The one
   override: lift it above the reader/panel — the kebab's z-dropdown sits below
   the full-bleed reader a right-click can open over. */
.page-ctx-menu {
  z-index: var(--z-modal);
}

/* ── "Add pages" affordances (#788) — the discoverable end-of-grid tile and the
   reader-rail "+", both opening the add menu. The panel-free replacement for
   "+ Add pages". Sized to sit as the trailing cell: the grid is a flex-wrap of
   fixed-height (--ws-card-h) cards, the rail a 104px column. */
.tool-ws-unified .tool-ws-grid .tool-ws-add-tile {
  flex: 0 0 auto;
  /* Match the row's cards in full — thumb + info footer — without hardcoding the
     footer height: stretch to the flex line's height (the tallest card in the
     row). min-height keeps it card-sized when it's alone on a row. */
  align-self: stretch;
  min-height: var(--ws-card-h);
  width: calc(var(--ws-card-h) * 0.75);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-muted);
  font-family: inherit;
  font-size: var(--font-sm);
  cursor: pointer;
  transition: border-color 160ms ease, color 160ms ease, background 160ms ease;
}

/* Landscape-anchored grids: match the missing-card fallback aspect (4:3). */
.tool-ws-unified .tool-ws-grid--landscape .tool-ws-add-tile {
  width: calc(var(--ws-card-h) * 1.3333);
}

/* ── Uploading tile (#1310) — one per file still on its way, where its card
   will land (upload.js). Card-sized like the add tile, so the grid does not
   jump when the real card replaces it; the shimmer is the one Processing cards
   wear, so a file reads uploading → processing → ready as one tile. */
.tool-ws-unified .tool-ws-grid .tool-ws-upload-tile {
  flex: 0 0 auto;
  align-self: stretch;
  min-height: var(--ws-card-h);
  width: calc(var(--ws-card-h) * 0.75);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: var(--space-3);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  background: linear-gradient(
    90deg,
    var(--color-surface-dim) 25%,
    var(--color-surface-muted) 50%,
    var(--color-surface-dim) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s ease-in-out infinite;
  color: var(--color-muted);
  font-size: var(--font-xs);
}

.tool-ws-unified .tool-ws-grid--landscape .tool-ws-upload-tile {
  width: calc(var(--ws-card-h) * 1.3333);
}

.tool-ws-upload-tile__label {
  font-weight: var(--weight-medium);
}

.tool-ws-upload-tile__name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-subtle);
}

/* The Processing card takes the same shimmer, filling its thumb. */
.page-card-thumb .page-card-processing {
  align-self: stretch;
  justify-content: center;
  width: 100%;
  background: linear-gradient(
    90deg,
    var(--color-surface-dim) 25%,
    var(--color-surface-muted) 50%,
    var(--color-surface-dim) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .tool-ws-unified .tool-ws-grid .tool-ws-upload-tile,
  .page-card-thumb .page-card-processing {
    animation: none;
  }
}

.tool-ws-add-tile:hover,
/* The section tile is a file-drop target too (section-tabs.js `_bindTileDrop`,
   which marks a hover with this class whatever shape the affordance is). Same
   lit state as the pointer hover — one visual answer to "this will take it". */
.tool-ws-add-tile.tool-ws-section-add--drop {
  border-color: var(--color-accent);
  color: var(--color-accent);
  background: var(--color-hover);
}

/* Section variant (#1099): a real label under the plus, where the flat grid's
   tile says the same two words on every requirement. "Add to Communications"
   does not fit a 0.75·h card on one line, so it wraps and centres rather than
   widening the tile off the card grid. */
.tool-ws-unified .tool-ws-grid .tool-ws-add-tile--section {
  padding: var(--space-3);
  text-align: center;
  line-height: var(--line-height-tight);
}

.tool-ws-reader-rail-add {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
  transition: border-color 160ms ease, color 160ms ease, background 160ms ease;
}

.tool-ws-reader-rail-add:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  background: var(--color-hover);
}

/* ── Pagination footer (extends existing .table-footer) ────────────────────── */

.table-footer-nav {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.page-btn {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  height: 28px;
  min-width: 28px;
  padding: 0 var(--space-3);
  font-size: var(--font-sm);
  border-radius: var(--radius-xs);
  cursor: pointer;
  color: var(--color-text);
  font-family: inherit;
  font-variant-numeric: tabular-nums;
}

.page-btn[disabled] {
  color: var(--color-muted);
  cursor: not-allowed;
}

.page-btn--active {
  background: var(--color-accent);
  color: var(--color-surface);
  border-color: var(--color-accent);
}

.page-btn--active[disabled] {
  color: var(--color-surface);
}

/* ── Toolbar narrow mode — search collapse ───────────────────────────────────

   Below 1400px the documents toolbar can't fit a 220px search alongside
   all filter dropdowns. Collapse to a 32px icon that re-expands on
   hover, focus, or while typing — keeps the toolbar single-row without
   losing the affordance.

   Filter selects intentionally do NOT get a forced compression rule.
   The existing `.toolbar--no-wrap .filter-select { max-width: 11rem }`
   plus default flex-shrink lets each filter shrink to its content's
   min-width and truncate the label with ellipsis ("Updated" → "Upda").
   That gracefully degrades from full label → truncated label as the
   toolbar tightens, without the abrupt chevron-only flip we tried
   earlier (which felt aggressive even when there was still room).
*/
@media (max-width: 1400px) {
  .toolbar .search-input--collapsible {
    width: var(--space-8);
    /* Override `.search-input { min-width: 200px }` — without this the
       width:32px collapse never takes effect, the field stays at the
       200px floor and just hides its text. */
    min-width: var(--space-8);
    padding: 0 0 0 var(--space-8);
    cursor: pointer;
    color: transparent;
  }

  .toolbar .search-input--collapsible::placeholder {
    color: transparent;
  }

  .toolbar .search-input--collapsible:hover,
  .toolbar .search-input--collapsible:focus,
  .toolbar .search-input--collapsible:not(:placeholder-shown) {
    width: 220px;
    min-width: 220px;
    padding: 0 var(--space-4) 0 var(--space-8);
    cursor: text;
    color: var(--color-text);
  }

  .toolbar .search-input--collapsible:hover::placeholder,
  .toolbar .search-input--collapsible:focus::placeholder {
    color: var(--color-muted);
  }
}

/* ── end Documents view (#174) ── */

/* ── List-view framework (#266) ──────────────────────────────────────────────
   Components are namespaced .lv-*. Consumers compose their own page shell
   (see /cases/docs in cases_docs.html for the canonical layout).
*/

/* List-view host wrapper. Override .card's overflow:hidden so absolutely-
   positioned popovers (saved-views, columns picker) inside the toolbar
   can escape the card's bounds. The body div handles its own scroll. */
.lv-host {
  overflow: visible;
}

/* Internal scroll shell — bounds the card to the viewport, lets the
   inner body div flex-grow and scroll vertically. With this in place
   the table's `position: sticky` thead anchors to the body container
   instead of the viewport, so headers stay put as rows scroll. The
   sticky footer-summary anchors the same way, sitting at the body's
   bottom. The toolbar sits above the body (flex: 0 0 auto) so it never
   scrolls.

   `12rem` is the rough chrome above the card on the documents view —
   app header + page title + spacing. If the viewport is shorter than
   that, max-height fails open (card can grow taller; page falls back
   to ordinary scroll). */
.lv-host[data-lv-layout] {
  display: flex;
  flex-direction: column;
  /* Explicit height (not max-height): with `max-height` and body's
     `flex-basis: 0`, the container's natural height collapses to just
     the toolbar (body contributes zero), so the body never gets any
     allocated space and the rows vanish. Setting `height` directly
     gives the flex container a definite size and the body fills the
     remaining axis. `min-height` is the fallback for very short
     viewports (page scrolls naturally rather than the card going to
     a negative size).

     The `calc()` is a conservative first-paint fallback before JS
     measures — after the layout manager runs `setHostHeight()` (in
     list_view_layout.js) the host gets an exact pixel `height` set
     inline, measured from the bottom of its scroll container's
     content box (`.main-content`) rather than from the viewport. The
     CSS undershoot here is intentional: a slight gap below the card
     for the first frame is invisible, but a slight overshoot triggers
     a scrollbar to flash before JS shrinks the host. */
  height: calc(100vh - 11rem);
  min-height: 400px;
}

.lv-host[data-lv-layout]>form.toolbar,
.lv-host[data-lv-layout]>.toolbar {
  flex: 0 0 auto;
}

/* The scroller on both axes: vertical for the rows, sideways for a column
   set wider than the host (#1441). One box for both, so the sticky header,
   the pinned columns, the chips and the footer all resolve against it. */
.lv-host[data-lv-layout] [id^="lv-shell-body-"] {
  flex: 1 1 0;
  min-height: 0;
  overflow: auto;
  /* Flex-column inside the body so the footer can be pushed to the
     bottom via `margin-top: auto` when content is short. Sticky still
     handles the scroll case (footer rides at the bottom of the
     scroll viewport while the body scrolls). */
  display: flex;
  flex-direction: column;
}

/* Two selectors because the footer is nested two different ways, and the
   direct-child combinator is what makes that matter.

   `leads` / `people` / `team` include `footer_summary.html` straight into the
   shell body, so `.lv-footer` is a direct child. `cases` / `weddings` wrap it
   in `<div id="lv-footer-{scope}" class="lv-region">` — the stable region id
   the #688 surgical swap needs — which makes `.lv-footer` a grandchild and
   drops it out of a rule written before that wrapper existed. The result was
   visible on staging and impossible to spot in the CSS: the rule is valid,
   still applies, and matches nothing on half the boards, so weddings and cases
   rendered their count directly under the last row while leads pinned it to
   the bottom of the card.

   The wrapper is the flex item on those scopes, so the auto has to go on
   whichever of the two is the direct child. Sticky (`bottom: 0`, on the same
   wrapper) covers the opposite case — content taller than the body — and the
   two never fight: the auto only does work when there is spare flex space, and
   when there is spare space nothing is scrolling for sticky to react to. */
.lv-host[data-lv-layout] [id^="lv-shell-body-"]>.lv-footer,
.lv-host[data-lv-layout] [id^="lv-shell-body-"]>[id^="lv-footer-"] {
  /* `!important` because the body uses flex; without it some baseline
     `.lv-footer` margin or the framework's component CSS can overrule
     the auto. The auto only does work when there's spare flex space
     above the footer (i.e. content shorter than body); when content
     is taller, it's a no-op and the footer flows naturally before
     `position: sticky` takes over for scroll. */
  margin-top: auto !important;
}

/* Filter builder modal — built on the design-system .modal-* primitives
   (see "Modal" section). The width override here is the only bespoke
   shell rule: condition rows (field × op × value × remove) need more
   than --lg's 720px to breathe. The body's flex / overflow / padding
   come from .modal-body. */

.lv-filter-builder-panel {
  width: min(880px, 95vw);
}

/* Filter section — uppercase tracked label, condition rows below,
   then a "+ Add condition" button. Two sections per modal: All (AND)
   and Any (OR). */

.lv-filter-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.lv-filter-section+.lv-filter-section {
  margin-top: var(--space-4);
}

.lv-filter-section-title {
  margin: 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.lv-filter-section-title strong {
  color: var(--color-text-strong);
  font-weight: var(--weight-semibold);
}

.lv-filter-section-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.lv-filter-section-rows:empty {
  display: none;
}

.lv-filter-add {
  align-self: flex-start;
}

/* Save row — name input + Shared toggle. Sits flush left in the footer;
   actions (Preview/Save) anchor right. */

.lv-filter-save-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1 1 auto;
}

.lv-filter-name-input {
  flex: 1 1 auto;
  height: var(--control-h);
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  font: inherit;
  font-size: var(--font-sm);
  color: var(--color-text);
  min-width: 0;
}

.lv-filter-name-input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

/* Visible "circle → filled" toggle for the Shared checkbox. The native
   input is positioned over the circle so click reaches it but the
   visual is the styled circle. */
.lv-filter-shared-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-sm);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

.lv-filter-shared-toggle input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.lv-filter-shared-circle {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  /* 1px hairline border — `1px` is exempt-as-structural per the
     design-system rules. */
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  transition: background var(--transition-fast),
    border-color var(--transition-fast);
}

.lv-filter-shared-toggle:has(input:checked) .lv-filter-shared-circle {
  background: var(--color-accent);
  border-color: var(--color-accent);
}

.lv-filter-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* Condition row — field × op × value × remove. */

.lv-filter-row {
  display: grid;
  grid-template-columns: minmax(140px, 1fr) minmax(140px, 1fr) minmax(180px, 2fr) auto;
  gap: var(--space-2);
  align-items: center;
}

.lv-filter-row-remove {
  background: transparent;
  border: 0;
  color: var(--color-muted);
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-lg);
  line-height: 1;
  border-radius: var(--radius-sm);
}

.lv-filter-row-remove:hover {
  color: var(--color-text);
  background: var(--color-surface-dim);
}

/* Columns modal — built on the design-system .modal-* primitives. The
   panel sizes to the natural width of its content (pill row), so a
   5-column list view stays compact and a 12-column one expands up to
   the viewport cap. `fit-content` resolves to max-content of the
   children, clamped by max-width. min-width keeps the chrome readable
   when there are very few columns. */

.lv-columns-modal-panel {
  width: fit-content;
  /* 880, not 95vw. `fit-content` resolves to the widest child, and once the
     pills were bucketed the widest child became a group row — /cases's "Lead &
     intake" is eight long labels, which stretched the panel to 1350px on a
     wide screen and turned a list of headings into a banner. Capped, the rows
     wrap and each group reads as a block. Below the cap nothing changed: a
     five-column picker still sizes to its content. */
  max-width: min(880px, 95vw);
  min-width: min(420px, 95vw);
}

/* The picker form inside the modal panel drops its own width
   constraints — modal-body provides padding/scroll, the panel's
   fit-content sizing absorbs the pill row. */
.lv-columns-modal-panel .lv-columns-picker {
  min-width: 0;
  max-width: none;
  padding: 0;
}

/* Single Done button right-aligns instead of taking the modal-footer
   default justify-content: space-between's leftward placement. */
.lv-columns-modal-panel .modal-footer {
  justify-content: flex-end;
}

/* Saved-views popup */

.lv-saved-views {
  display: flex;
  flex-direction: column;
  min-width: 280px;
}

.lv-saved-views-error,
.lv-filter-error {
  background: color-mix(in srgb, var(--color-danger) 10%, var(--color-surface));
  color: var(--color-danger);
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-xs);
}

/* The filter modal's footer copy of the popover error, plus the Update
   button that only shows while editing a view. `.btn`'s display outranks
   the UA `[hidden]` rule, so re-assert it. */
.lv-filter-error {
  width: 100%;
  border-radius: var(--radius-sm);
}

.lv-filter-error[hidden],
.lv-filter-actions .btn[hidden] {
  display: none;
}

.lv-saved-views-section {
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border-subtle);
}

.lv-saved-views-section:last-of-type {
  border-bottom: 0;
}

.lv-saved-views-heading {
  margin: 0;
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.lv-saved-views-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.lv-saved-views-list li {
  display: flex;
  align-items: stretch;
}

.lv-saved-views-row {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  background: transparent;
  border: 0;
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  font-size: var(--font-base);
  color: var(--color-text);
}

.lv-saved-views-row:hover {
  background: var(--color-hover);
}

.lv-saved-views-label {
  font-weight: var(--weight-medium);
}

.lv-saved-views-shared-tag {
  display: inline-block;
  margin-left: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-weight: var(--weight-normal);
}

.lv-saved-views-desc {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.lv-saved-views-delete,
.lv-saved-views-edit {
  align-self: stretch;
  width: 32px;
  background: transparent;
  border: 0;
  color: var(--color-muted);
  cursor: pointer;
  font-size: var(--font-base);
  line-height: 1;
}

.lv-saved-views-delete:hover {
  background: color-mix(in srgb, var(--color-danger) 8%, transparent);
  color: var(--color-danger);
}

.lv-saved-views-edit:hover {
  background: var(--color-accent-50);
  color: var(--color-accent);
}

.lv-saved-views-empty {
  padding: var(--space-3) var(--space-4);
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

/* Columns picker — pill-row variant.

   Renders configured columns as draggable pills inside the popover.
   Drag a pill (from the grip) to reorder; click the toggle on the
   right to flip visibility. Order persists in
   `localStorage["lv-order-{scope}"]`; visibility persists in the
   existing `{scope}_columns` cookie via the form's hx-post. */

.lv-columns-picker {
  min-width: 360px;
  max-width: 520px;
  padding: var(--space-3) var(--space-4);
}

.lv-columns-picker-pills {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.lv-col-pill {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-surface-dim);
  border: 1px solid var(--color-border);
  font-size: var(--font-sm);
  color: var(--color-text);
  cursor: grab;
  user-select: none;
  transition: background var(--transition-fast),
    border-color var(--transition-fast),
    opacity var(--transition-fast);
}

.lv-col-pill:hover {
  border-color: var(--color-muted);
}

/* The two halves of the picker (Shown · Add a column). A pill carries the
   markup for both roles and the parent list decides which controls show — see
   the `column_pill` macro in columns_picker.html for why it is done this way
   (the pill is MOVED between the lists at runtime, never re-rendered). */

/* Three levels of surface, because there are three levels of structure and the
   first cut carried none of them: the two section labels were muted uppercase
   micro-text while the group headings under them were darker and larger, so the
   eye read the groups as the top level and the sections as their captions. The
   hierarchy was upside down, and no amount of heading weight fixes that on its
   own — the levels have to be separable without reading the words.

   white modal body  >  the Shown strip (muted, solid — the table as it stands)
                     >  each group card (dim, recessed — a drawer to pick from)  */

.lv-columns-section-title {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0 0 var(--space-3) 0;
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.lv-columns-section-hint {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  text-transform: none;
  letter-spacing: normal;
  color: var(--color-muted);
}

/* Shown reads as the table it stands for: a bounded strip, so the sequence
   inside it looks like a sequence rather than like more tags. */
.lv-columns-picker-pills--shown {
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-muted);
}

.lv-columns-available {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
}

/* A label gutter, not a heading above its pills: with eight groups every
   heading lands on the same x and the eye runs down one column instead of
   zig-zagging heading → pills → heading. It is also ~15% shorter, which is what
   brings the Display toggle back above the fold — that is a control, not a
   footer, and stacked headings pushed it off screen. */
.lv-columns-group {
  display: grid;
  grid-template-columns: 150px 1fr;
  align-items: start;
  column-gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface-dim);
}

.lv-columns-group + .lv-columns-group {
  margin-top: var(--space-2);
}

.lv-columns-group-title {
  /* Nudged down to sit on the first pill row's centre: the pill carries
     `var(--space-2)` of top padding and the label does not, so without this the
     heading rides above the row it names. */
  padding-top: var(--space-2);
  margin: 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-subtle);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Under the label rather than beside it — in the gutter the count is a second
   line of the same block, and inline it would push long group names to wrap. */
.lv-columns-group-count {
  display: block;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  letter-spacing: normal;
  color: var(--color-muted);
}

/* Below the gutter's own width the two columns stop being a layout: 150px of
   label against what is left of a phone screen wraps every pill to its own
   line. Stack them, and the heading goes back above its pills. */
@media (max-width: 640px) {
  .lv-columns-group {
    grid-template-columns: 1fr;
  }

  /* The flex row is also what puts the count back beside the label: a flex item
     is blockified, so `display: inline` on `.lv-columns-group-count` here would
     compute to `block` and land on nothing — measured, not assumed
     (`getComputedStyle(...).display` under a 420px viewport). Nothing to
     declare; the parent already does it. */
  .lv-columns-group-title {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    padding-top: 0;
    margin-bottom: var(--space-2);
  }
}

/* A group whose last pill just moved to Shown, and the all-shown case. Both
   are states the client reaches by moving nodes, so CSS decides them from the
   DOM rather than JS toggling a class the server would also have to render. */
.lv-columns-group:not(:has(.lv-col-pill)) {
  display: none;
}

.lv-columns-available-empty {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-subtle);
}

.lv-columns-available:has(.lv-col-pill) .lv-columns-available-empty {
  display: none;
}

.lv-col-pill--available {
  cursor: pointer;
  background: transparent;
  border-style: dashed;
}

.lv-col-pill--available:hover {
  background: var(--color-surface-dim);
  border-style: solid;
}

/* Role-specific controls: the grip and ✕ belong to a pill in Shown, the + to
   one in Available. */
.lv-col-pill--available .lv-col-pill-grip,
.lv-col-pill--available .lv-col-pill-remove,
.lv-col-pill--shown .lv-col-pill-add {
  display: none;
}

.lv-col-pill--dragging {
  opacity: 0.4;
  cursor: grabbing;
}

/* Drop indicator — a 2px accent-coloured bar on the leading or
   trailing edge of the hovered pill. Layered via ::before / ::after
   so we don't disturb the pill's own padding/border. */
.lv-col-pill--drop-before::before,
.lv-col-pill--drop-after::after {
  content: "";
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 2px;
  background: var(--color-accent);
  border-radius: var(--radius-pill);
  pointer-events: none;
}

.lv-col-pill--drop-before::before {
  left: -5px;
}

.lv-col-pill--drop-after::after {
  right: -5px;
}

.lv-col-pill-grip {
  display: inline-flex;
  align-items: center;
  color: var(--color-muted);
  cursor: grab;
}

.lv-col-pill-label {
  font-weight: var(--weight-medium);
}

/* One input per pill, visually hidden: the + and the ✕ are both <label for>
   pointing at it, so the column's state survives the pill moving lists. */
.lv-col-pill-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}

.lv-col-pill-add,
.lv-col-pill-remove,
.lv-col-pill-locked {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-muted);
}

.lv-col-pill-add,
.lv-col-pill-remove {
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: color var(--transition-fast), background var(--transition-fast);
}

.lv-col-pill-remove:hover {
  color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 12%, transparent);
}

.lv-col-pill--available:hover .lv-col-pill-add {
  color: var(--color-accent);
}

/* The grip is the drag target, so it keeps the grab cursor while a forced
   pill's lock explains why that pill has no ✕. */
.lv-col-pill-locked {
  cursor: default;
}

/* Display prefs (#1047) — the settings half of the columns modal.

   Deliberately quieter than the pills above it: a hairline rule, muted
   section label and normal-weight rows. The pills are what people open this
   modal for; prefs are the drawer underneath, and making them equally loud
   would change what the modal reads as. Renders only when the scope declares
   a pref, so most lists still show pills and nothing else. */
.lv-display-prefs {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
}

.lv-display-prefs-title {
  margin: 0 0 var(--space-2) 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.lv-display-pref {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  cursor: pointer;
  font-size: var(--font-sm);
  color: var(--color-text);
}

.lv-display-pref-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.lv-display-pref-help {
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

/* Why a pref does nothing right now (DisplayPref.unavailable_fn). Warning-toned
   so it reads as a condition to fix rather than as more help text; the link
   goes to where it is fixed. */
.lv-display-pref-unavailable {
  font-size: var(--font-xs);
  color: var(--color-warning-text);
}

.lv-display-pref-unavailable a {
  color: inherit;
  text-decoration: underline;
}

/* Legacy `.lv-columns-picker-row` selector retained for any consumer
   still rendering the checkbox-list variant; safe no-op when the new
   pill template is in use. */
.lv-columns-picker-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  cursor: pointer;
  font-size: var(--font-sm);
}

/* Chip — advanced (custom filter) variant */

.chip--advanced {
  border-style: dashed;
}

.chip-key--button {
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

/* Group-by control */

/* Sort header */

.lv-sort-th-btn {
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  transition: color var(--transition-normal);
}

.lv-sort-th-btn:hover {
  color: var(--color-text);
}

.lv-sort-th--active .lv-sort-th-btn {
  color: var(--color-text-strong);
}

.lv-sort-th-glyph {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* Search input */

.lv-search-input {
  background: var(--color-surface-dim);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--font-sm);
  min-width: 220px;
}

.lv-search-input:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: -1px;
}

/* Footer summary */

.lv-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: 12px var(--space-ws-gutter);
  border-top: 1px solid var(
  --color-border-subtle);
  background: var(
  --color-surface-dim);
  font-size: var(
  --font-xs);
  color: var(
  --color-muted);
  position: sticky;
  bottom: 0;
  /* The shell body scrolls sideways too (#1441); the footer stays put. */
  left: 0;
  z-index: var(
  --z-base);
  /* Sticky child can escape the parent .card overflow clip in Chrome/Safari
     when promoted to its own compositing layer — match the card radius so
     the bottom corners stay rounded. */
  border-bottom-left-radius: var(
  --radius-md);
  border-bottom-right-radius: var(
  --radius-md);
}

.lv-footer-count {
  color: var(--color-muted);
  font-weight: var(--weight-medium);
}

.lv-footer-num {
  color: var(--color-text-strong);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.lv-footer-sep {
  margin: 0 var(--space-1);
  color: var(--color-subtle);
}

.lv-footer-totals {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: var(--space-4);
}

.lv-footer-total-label {
  color: var(--color-muted);
}

.lv-footer-total-value {
  color: var(--color-text);
  font-weight: var(--weight-medium);
}

/* Infinite-scroll sentinel */

.lv-sentinel td {
  text-align: center;
  padding: var(--space-4);
  color: var(--color-muted);
  font-size: var(--font-xs);
}

.lv-sentinel--terminal td {
  color: var(--color-subtle);
  font-style: italic;
}

/* Popover container (saved-views, columns picker triggers) */

[data-lv-popover-host] {
  position: relative;
  display: inline-block;
}

.lv-popover {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  display: none;
  z-index: var(--z-dropdown);
  min-width: 240px;
  max-height: 60vh;
  overflow-y: auto;
}

.lv-popover.open {
  display: block;
}

/* Filter-builder row select/input styling (rows are JS-injected via
   list_view.js's renderFilterRow; the grid layout itself lives near
   the rest of the filter-builder scaffolding above). */

.lv-filter-row select,
.lv-filter-row input {
  height: var(--control-h);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-3);
  font: inherit;
  font-size: var(--font-sm);
  color: var(--color-text);
  min-width: 0;
}

.lv-filter-row select:focus,
.lv-filter-row input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

/* Field-picker type headings (#1107). The field <select> buckets its options
   into <optgroup>s by field type ("Dates", "Choices", …) so the dates in a
   forty-option list sit together. Only the field picker gets them — the op
   and value selects are flat — so the selector stays on that one control. */

.lv-filter-row select[data-lv-row-field] optgroup {
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
}

.lv-filter-row select[data-lv-row-field] optgroup option {
  font-size: var(--font-sm);
  font-weight: var(--weight-normal);
  color: var(--color-text);
}

/* ── end List-view framework (#266) ── */

/* ── Customer portal — upload + status MVP (#452) ── */
.req-group {
  margin-bottom: var(--space-6);
}

.req-group h2 {
  font-size: 1rem;
  color: var(--color-text-strong);
  margin-bottom: var(--space-3);
}

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

.req-list__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  text-decoration: none;
  color: var(--color-text);
}

.req-list__link:hover {
  border-color: var(--color-accent);
}

.req-list__label {
  font-weight: var(--weight-medium);
}

.req-list__empty {
  color: var(--color-muted);
}

.req-status {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-4) 0;
}

.req-status__pages {
  font-size: 0.8rem;
  color: var(--color-muted);
}

.portal-upload,
.portal-submit {
  margin: var(--space-4) 0;
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
}

.portal-upload__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: 0.85rem;
  color: var(--color-muted);
}

/* Chunked upload (#613) — the gesture's progress / outcome line, owned by
   static/js/portal-upload.js. A chunked form stacks (dropzone above, progress
   below) instead of the legacy inline row. */
.portal-upload[data-upload-chunked] {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2);
}

.portal-upload__progress {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

/* `display: flex` above beats the idle line's [hidden], which left its spinner
   turning beside an empty document's verdict line (#1371). */
.portal-upload__progress[hidden] {
  display: none;
}

.portal-upload__progress--error {
  color: var(--color-danger);
}

.portal-upload__progress--done {
  color: var(--color-text);
}

/* Terminal states carry no spinner (.loading-spinner's display beats [hidden]). */
.portal-upload__progress--error .loading-spinner,
.portal-upload__progress--done .loading-spinner {
  display: none;
}

/* Same read as .btn[disabled] — the picker is inert while a gesture is in flight. */
.portal-upload--busy .dropzone,
.portal-upload--busy .upload-inline {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

.loading-spinner--sm {
  width: 0.75rem;
  height: 0.75rem;
  border-width: 1.5px;
}

.back-link {
  display: inline-block;
  margin-bottom: var(--space-2);
  font-size: var(--font-sm);
  color: var(--color-muted);
  text-decoration: none;
}

.back-link:hover {
  color: var(--color-accent);
}

/* ── end Customer portal MVP (#452) ── */

/* ── Portal account auth (#454) ── */
.portal-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: 360px;
}

.portal-form__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: 0.85rem;
  color: var(--color-muted);
}

.portal-form__field input {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 1rem;
}

.portal-form__hint {
  font-size: 0.8rem;
  color: var(--color-muted);
  margin: 0;
}

/* ── end Portal account auth (#454) ── */

/* ── Portal pre-engagement view (#457) ──
   /portal/welcome — the couple's first screen. A waiting room turned into
   step 1 of a journey: five-step timeline card, optional collapsible intro
   video. Composed from existing tokens only. The topbar title + status pill
   and "Talk to us" link carry reassurance (see base_portal). */

/* journey card — the anchor of the page */
.portal-welcome__journey {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-auth-card);
  /* The house card inset, like every portal card (PV-24 3b, #1404). */
  padding: var(--space-5);
  margin-top: var(--space-5);
}

.portal-welcome__journey-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.portal-welcome__journey-head h2 {
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

/* One unit: on a phone it wraps under the title whole, never word by word. */
.portal-welcome__journey-meta {
  font-size: var(--font-xs);
  color: var(--color-text);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* timeline: marker column + content column */
.portal-welcome__steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.portal-welcome__step {
  display: grid;
  grid-template-columns: 32px 1fr;
  gap: var(--space-4);
  padding: var(--space-3) 0 var(--space-5);
  position: relative;
}

.portal-welcome__step:last-child {
  padding-bottom: 0;
}

/* vertical rule connecting markers */
.portal-welcome__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 15px;
  top: 36px;
  bottom: 0;
  width: 1.5px;
  background: var(--color-border);
}

.portal-welcome__step-marker {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--color-surface);
  border: 1.5px solid var(--color-border);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  /* sits above the connector rule; the rule starts below the marker (top:36px)
     so tree order already keeps the marker on top — position alone suffices */
  position: relative;
}

.portal-welcome__step-body {
  padding-top: var(--space-1);
  min-width: 0;
}

.portal-welcome__step-title {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  line-height: 1.35;
}

/* Body text, whatever the step's state: the marker and the title carry the
   state, the line reads the same on every step (PV-24 1b, #1404). */
.portal-welcome__step-sub {
  font-size: var(--font-xs);
  color: var(--color-text);
  /* The house gap under a heading (/ux heading-gap): the title is one. */
  margin-top: var(--space-3);
  line-height: var(--line-height-normal);
}

/* A tag (the `.tag` class beside it); it wraps rather than running a long
   line off a phone, which `.tag` alone would not. */
.portal-welcome__step-tag {
  display: inline-block;
  white-space: normal;
  margin-top: var(--space-3);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  background: var(--color-surface-muted);
  padding: 2px var(--space-3);
  border-radius: var(--radius-pill);
}

/* DONE — filled success marker with check, number hidden */
.portal-welcome__step--done .portal-welcome__step-marker {
  background: var(--color-success);
  border-color: var(--color-success);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><polyline points='3.5 8.2 6.7 11.4 12.5 5.3'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
  /* number hidden — the check glyph stands in */
  font-size: 0;
}

.portal-welcome__step--done .portal-welcome__step-title {
  color: var(--color-text);
}

/* highlight the connector leaving a completed step */
.portal-welcome__step--done::before {
  background: linear-gradient(to bottom, var(--color-success), var(--color-accent));
}

/* ACTIVE — current step, accent ring + pulsing chip */
.portal-welcome__step--active .portal-welcome__step-marker {
  background: var(--color-surface);
  border: 1.5px solid var(--color-accent);
  color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.portal-welcome__step--active .portal-welcome__step-tag {
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  color: var(--color-accent);
  font-weight: var(--weight-semibold);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.portal-welcome__step--active .portal-welcome__step-tag::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-accent);
  animation: portal-welcome-pulse 1.8s ease-in-out infinite;
}

@keyframes portal-welcome-pulse {

  0%,
  100% {
    opacity: 0.4;
    transform: scale(0.85);
  }

  50% {
    opacity: 1;
    transform: scale(1.1);
  }
}

/* ENDED — the journey stopped here (refunded / withdrawn): neutral ring, no
   pulse, the Ended pill (#1022, rules/architecture/case-lifecycle.md). */
.portal-welcome__step--ended .portal-welcome__step-marker {
  background: var(--color-surface);
  border: 1.5px solid var(--color-border);
  color: var(--color-muted);
}

.portal-welcome__step--ended .portal-welcome__step-tag {
  background: var(--color-surface-muted);
  color: var(--color-muted);
  font-weight: var(--weight-semibold);
}

/* PAUSED — on hold: warning ring, the On hold pill, no pulse. */
.portal-welcome__step--paused .portal-welcome__step-marker {
  background: var(--color-surface);
  border: 1.5px solid var(--color-warning);
  color: var(--color-warning-text);
}

.portal-welcome__step--paused .portal-welcome__step-tag {
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  color: var(--color-warning-text);
  font-weight: var(--weight-semibold);
}

/* UPCOMING — fully muted */
.portal-welcome__step--upcoming .portal-welcome__step-title {
  color: var(--color-muted);
  font-weight: var(--weight-medium);
}

.portal-welcome__step--upcoming .portal-welcome__step-marker {
  color: var(--color-muted);
}

/* FINAL — the destination: dashed marker + star */
.portal-welcome__step--final .portal-welcome__step-marker {
  border-style: dashed;
  background: var(--color-surface-dim);
}

.portal-welcome__step--final .portal-welcome__step-title {
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}

/* intro video — collapsible disclosure; optional, so it stays out of the way */
.portal-welcome__video {
  margin-top: var(--space-5);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-auth-card);
  overflow: hidden;
}

.portal-welcome__video[open] .portal-welcome__video-head {
  border-bottom: 1px solid var(--color-border-subtle);
}

.portal-welcome__video-head {
  padding: var(--space-4) var(--space-5);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.portal-welcome__video-head::-webkit-details-marker {
  display: none;
}

.portal-welcome__video-head:hover {
  background: var(--color-hover);
}

.portal-welcome__video-head:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.portal-welcome__video-play {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  color: var(--color-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.portal-welcome__video-play svg {
  width: 12px;
  height: 12px;
  margin-left: 1px;
}

.portal-welcome__video-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.portal-welcome__video-title {
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.portal-welcome__video-label {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.portal-welcome__video-chevron {
  width: 18px;
  height: 18px;
  color: var(--color-muted);
  transition: transform var(--transition-normal);
  flex-shrink: 0;
}

.portal-welcome__video[open] .portal-welcome__video-chevron {
  transform: rotate(180deg);
}

.portal-welcome__video-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--color-surface-muted);
}

.portal-welcome__video-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .portal-welcome__step--active .portal-welcome__step-tag::before {
    animation: none;
  }
}

/* ── end Portal pre-engagement view (#457) ── */

/* ── Docs workspace page controls — Single|Spread + combobox + pills (#456) ── */

.seg-toggle {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  overflow: hidden;
}

.seg-toggle-opt {
  appearance: none;
  border: 0;
  background: transparent;
  padding: 0.3rem 0.7rem;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  cursor: pointer;
  transition: background var(--transition-normal), color var(--transition-normal);
}

.seg-toggle-opt+.seg-toggle-opt {
  border-left: 1px solid var(--color-border);
}

.seg-toggle-opt:hover {
  color: var(--color-text);
}

.seg-toggle-opt--active {
  background: var(--color-accent);
  color: var(--color-surface);
}

.seg-toggle-opt--active:hover {
  color: var(--color-surface);
}

/* A segmented quick filter on a list view (the Library's About, round H4):
   a radio per option inside the toolbar form, so a change rides the list's
   swap; the checked one reads as --active. The input is visually hidden,
   the label is the button. */
.seg-toggle-opt:has(> input:checked) {
  background: var(--color-accent);
  color: var(--color-surface);
}

.seg-toggle-opt:has(> input:focus-visible) {
  box-shadow: var(--shadow-focus);
}

.seg-toggle-opt > input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.seg-toggle--bulk {
  height: 2rem;
}

.seg-toggle--bulk .seg-toggle-opt {
  padding: 0 var(--space-3);
}

.page-modal-faces {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

.page-modal-faces--spread {
  flex-direction: row;
  gap: 0.6rem;
}

.page-modal-face {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  flex: 1;
  min-width: 0;
}

.page-modal-faces--spread .page-modal-face {
  flex: 1 1 50%;
}

.page-modal-face-tag {
  position: absolute;
  top: 0.15rem;
  left: -0.95rem;
  font-size: 0.65rem;
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  letter-spacing: 0.04em;
}

.page-modal-face-pills {
  display: flex;
  gap: 0.3rem;
  flex-wrap: wrap;
}

.pill-toggle {
  appearance: none;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  padding: 0.15rem 0.55rem;
  font-size: 0.7rem;
  font-weight: var(--weight-medium);
  color: var(--color-muted);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--transition-normal), color var(--transition-normal),
    border-color var(--transition-normal);
}

.pill-toggle:hover:not(.pill-toggle--disabled) {
  color: var(--color-text);
  border-color: var(--color-muted);
}

.pill-toggle--on {
  background: var(--color-accent);
  color: var(--color-surface);
  border-color: var(--color-accent);
}

.pill-toggle--on:hover {
  color: var(--color-surface);
}

.pill-toggle--disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Mixed — some but not all targets carry the label (#653, bulk selection).
   Reads as "partly on": the accent colour of --on, but as an outline so it
   never gets mistaken for the full-on state at a glance. */
.pill-toggle--mixed {
  background: var(--color-surface);
  color: var(--color-accent);
  border-color: var(--color-accent);
  border-style: dashed;
}

.pill-toggle--mixed:hover {
  color: var(--color-accent);
  border-color: var(--color-accent);
}

/* ── Combobox (filterable type/label input) ── */

.combobox {
  position: relative;
}

.combobox-input {
  width: 100%;
}

.combobox-list {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  max-height: 14rem;
  overflow-y: auto;
  margin: 0;
  padding: 0.2rem 0;
  list-style: none;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  z-index: var(--z-dropdown);
}

.combobox-opt {
  padding: 0.3rem 0.55rem;
  font-size: 0.8rem;
  color: var(--color-text);
  cursor: pointer;
}

.combobox-opt:hover,
.combobox-opt--active {
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}

/* ── Page card chips (stamp / visa indicators on the grid) ── */

.page-card-chips {
  position: absolute;
  top: 0.35rem;
  left: 0.35rem;
  display: flex;
  gap: 0.25rem;
  pointer-events: none;
}

/* Undated tag anchors to the thumb's bottom-left (#802), apart from the
   top-left status chips. */
.page-card-chips--bottom {
  top: auto;
  bottom: 0.35rem;
}

/* A dated card keeps the Undated chip in its markup, `hidden`, so a date save
   flips it in place (#1097) — and `display: flex` above outranks the UA's
   `[hidden]`. */
.page-card-chips[hidden] {
  display: none;
}

.page-card-chip {
  font-size: 0.62rem;
  font-weight: var(--weight-medium);
  padding: 0.1rem 0.4rem;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-text) 55%, transparent);
  color: var(--color-surface);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.page-card-chip--stamp {
  background: color-mix(in srgb, var(--color-accent) 85%, transparent);
}

.page-card-chip--visa {
  background: color-mix(in srgb, var(--color-danger) 80%, transparent);
}

.page-card-chip--excluded {
  background: color-mix(in srgb, var(--color-danger) 75%, transparent);
}

.page-card-chip--undated {
  background: color-mix(in srgb, var(--color-warning-fg, #9a3412) 75%, transparent);
}

/* ── File block chrome (#994) ──────────────────────────────────────────────
   "These pages arrived as one upload" — a 4-page ticket PDF reads as one
   booking rather than four scattered pages.

   Per-card, never a wrapper, and never a full-width band. In the unified shell
   the grid is `display: flex; flex-wrap: wrap` (the rule at the top of this
   file), which beats the `display: grid` rule beneath it — so `grid-column:
   1 / -1` is inert there, and a wrapper would become one flex item that can
   never share a row with its neighbours. It would also break `_applyGridSort`,
   which walks `grid.children` assuming cards are direct children.

   So the grouping rides the cards: a tinted surface + accented border on every
   member of a fragment (which still reads as one block when the row wraps),
   and one marker on the fragment's first card. A fragment of one carries the
   marker and not the frame — a tab shows only its own pages, so a lone card is
   a true statement about this tab, and framing it would imply a group of one. */
.page-card[data-file-framed] {
  /* The border is the signal, not the fill: once real thumbnails load they
     cover the card, so a background tint only ever shows in the info bar. */
  border-color: color-mix(in srgb, var(--color-accent) 70%, var(--color-border));
  background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface));
}

/* The marker IS a page-card chip — same pill, same background formula — with
   two differences it cannot inherit: it sits bottom-right (the other three
   corners are taken: expand button top-right, status chips + alternatives
   count top-left, "Undated" bottom-left), and it is clickable, so it cannot
   ride the `.page-card-chips` wrapper, which sets `pointer-events: none`. */
.page-card-chip--file {
  position: absolute;
  right: var(--space-2);
  bottom: var(--space-2);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  border: none;
  text-transform: none;
  line-height: 1;
  cursor: pointer;
  transition: background var(--transition-fast);
}

.page-card-chip--file:hover,
.page-card-chip--file:focus-visible {
  background: var(--color-accent);
}

.page-card-chip--file svg {
  flex: 0 0 auto;
}

/* ── end Docs workspace page controls (#456) ── */


/* ──────────────────────────────────────────────────────────────────────────
   Page modal — Round 3 redesign
   Stage padding, top-of-side toolbar with grip + position chip, identity
   status row inline, quality-checks renderer, activity tab refresh,
   keyboard shortcut affordances. Composes on existing tokens — no new ones.
   ────────────────────────────────────────────────────────────────────────── */

/* Stage padding around the modal — 20px on every edge so the modal feels
   like a contained panel rather than a full-page takeover. */
.page-modal-dialog {
  padding: var(--space-5);
  background: transparent;
}

/* The modal fills the stage minus padding. Side-panel width is driven by
   a CSS custom property set inline from JS so the resize grip can update
   it live. */
.page-modal-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--page-modal-side-width, 360px);
  /* Single row that fills the parent — keeps the side panel anchored to
     viewport height so its tab-content can overflow internally. Without
     this, the row defaults to auto and the side panel grows to fit its
     content (and the activity tab never scrolls). */
  grid-template-rows: minmax(0, 1fr);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  position: relative;
}

/* Reset the side flex tokens — width is now controlled by the parent grid.
   min-height: 0 lets the inner tab-content shrink so its overflow rule
   actually takes effect. */
.page-modal-side {
  flex: initial;
  min-width: 0;
  min-height: 0;
  max-width: none;
  position: relative;
  overflow: hidden;
}

/* Image area uses the grid track — no flex sizing */
.page-modal-image-area {
  flex: initial;
  min-width: 0;
}

/* ── Top-of-side toolbar (refresh) ────────────────────────────────────── */
.page-modal-controls {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border-subtle);
  background: var(--color-surface);
  flex-shrink: 0;
  justify-content: flex-start;
  flex-wrap: wrap;
}

.page-modal-controls .btn.btn-sm.btn-ghost {
  padding: 2px var(--space-2);
  min-width: 26px;
  height: 24px;
  font-size: var(--font-sm);
}

.page-modal-controls-pos {
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  padding: 0 var(--space-2) 0 2px;
  white-space: nowrap;
  flex-shrink: 0;
}

.page-modal-controls-sep {
  width: 1px;
  height: 14px;
  background: var(--color-border);
  margin: 0 var(--space-1);
  flex-shrink: 0;
}

.page-modal-controls-spacer {
  flex: 1 1 0;
}

/* Crop variant — accent-tinted background while in crop mode. The regular
   image-tool icons hide; the aspect segmented + Cancel/Apply buttons take
   their place inline. Grip + position chip stay (operator still needs to
   know which page they're on). flex-wrap is off so a single row holds the
   whole crop toolbar; without that override the regular toolbar's wrap
   rule sends actions to a second line that overlays the tab content. */
.page-modal-controls--crop {
  background: color-mix(in srgb, var(--color-accent) 5%, var(--color-surface));
  border-bottom-color: color-mix(in srgb, var(--color-accent) 25%, var(--color-border));
  flex-wrap: nowrap;
}

/* Hide every direct child the regular toolbar contains except the grip and
   the position chip, and except the crop-mode elements themselves. */
.page-modal-controls--crop>.btn,
.page-modal-controls--crop>.page-modal-controls-sep,
.page-modal-controls--crop>.page-modal-controls-spacer {
  display: none;
}

/* When in crop mode, .crop-aspect-bar and .crop-actions are the rightmost
   inline groups. Push the actions to the right edge. */
.page-modal-controls--crop>.crop-aspect-bar {
  /* Override the legacy `.crop-aspect-bar { position: absolute }` that
     pinned the bar to the top-centre of the image area in pre-Round-3. */
  position: static;
  transform: none;
  top: auto;
  left: auto;
  background: transparent;
  box-shadow: none;
  padding: 0;
  display: flex !important;
  /* JS sets inline display flex; class also wins */
  align-items: center;
  gap: var(--space-1);
  margin-left: var(--space-3);
}

.page-modal-controls--crop>.crop-actions {
  /* Override the legacy `.crop-actions { position: absolute; bottom: 1rem }`
     that anchored the cancel/apply float to the image-area bottom. In
     crop-redesign these buttons live inline at the right of the toolbar. */
  position: static;
  transform: none;
  left: auto;
  right: auto;
  bottom: auto;
  top: auto;
  background: transparent;
  box-shadow: none;
  padding: 0;
  display: flex !important;
  align-items: center;
  gap: var(--space-1);
  margin-left: auto;
}

/* When NOT in crop mode, the elements are present in the DOM with inline
   `style="display: none"`. That's already correct — these rules only fire
   in the --crop variant so the regular toolbar layout is unaffected. */

/* In-toolbar segmented for the crop aspect — match the existing seg-toggle
   look from the identity block (rounded white pill on active). */
.page-modal-controls--crop .crop-aspect-segment {
  display: inline-flex;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 1px;
  height: 24px;
}

.page-modal-controls--crop .crop-aspect-option {
  border: 0;
  background: transparent;
  padding: 0 var(--space-3);
  border-radius: var(--radius-xs);
  color: var(--color-subtle);
  cursor: pointer;
  font: inherit;
  font-size: 11px;
  font-weight: var(--weight-medium);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
}

.page-modal-controls--crop .crop-aspect-option:hover {
  color: var(--color-text);
}

.page-modal-controls--crop .crop-aspect-option--active {
  background: var(--color-accent);
  color: var(--color-surface);
}

/* In-toolbar crop action buttons — sized to match the toolbar icon-btn
   height so the row reads as one continuous toolbar. */
.page-modal-controls--crop .crop-actions .btn-sm {
  height: 24px;
  padding: 2px var(--space-3);
  font-size: 11px;
}

/* ── Crop selection refresh — tight handles + close × at NW corner ──── */
/* Border switches from white-dashed to solid accent — reads as a
   deliberate selection rather than a marching-ants placeholder. The dim
   mask via box-shadow stays (baseline). */
.crop-selection {
  border: 1px solid var(--color-accent);
}

/* Close × chip — subtler, sits OUTSIDE the NW corner with enough offset
   to not collide with the NW corner handle (handle is at -4/-4, so we
   push the chip well clear). Prototype reserves the bottom-right area
   for the apply/cancel toolbar. */
.crop-selection .crop-selection-close {
  top: -28px;
  left: -28px;
  right: auto;
  width: 20px;
  height: 20px;
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  font-size: 14px;
  box-shadow: var(--shadow-sm);
}

.crop-selection .crop-selection-close:hover {
  background: var(--color-danger);
  color: var(--color-surface);
  border-color: var(--color-danger);
  filter: none;
}

/* Corner handles — small 8×8 surface squares with accent border. */
.crop-selection .crop-handle--nw,
.crop-selection .crop-handle--ne,
.crop-selection .crop-handle--sw,
.crop-selection .crop-handle--se {
  width: 8px;
  height: 8px;
  background: var(--color-surface);
  border: 1.5px solid var(--color-accent);
}

.crop-selection .crop-handle--nw {
  top: -4px;
  left: -4px;
}

.crop-selection .crop-handle--ne {
  top: -4px;
  right: -4px;
}

.crop-selection .crop-handle--sw {
  bottom: -4px;
  left: -4px;
}

.crop-selection .crop-handle--se {
  bottom: -4px;
  right: -4px;
}

/* Edge bars — solid accent strips, 16×2 / 2×16 centred on each edge.
   Using transform: translate instead of negative margin to dodge the
   raw-px-spacing lint while keeping the same visual result. */
.crop-selection .crop-handle--n,
.crop-selection .crop-handle--s {
  width: 16px;
  height: 2px;
  left: 50%;
  margin-left: 0;
  transform: translateX(-50%);
  background: var(--color-accent);
  border: 0;
}

.crop-selection .crop-handle--n {
  top: -1px;
}

.crop-selection .crop-handle--s {
  bottom: -1px;
}

.crop-selection .crop-handle--e,
.crop-selection .crop-handle--w {
  width: 2px;
  height: 16px;
  top: 50%;
  margin-top: 0;
  transform: translateY(-50%);
  background: var(--color-accent);
  border: 0;
}

.crop-selection .crop-handle--e {
  right: -1px;
}

.crop-selection .crop-handle--w {
  left: -1px;
}

/* ── Resize grip + accent line + readout + snap guides ─────────────────── */
/* Grip button — leftmost item in the top toolbar. Drag = resize sidebar.
   Double-click = reset to default width. */
.page-modal-resize-grip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  flex-direction: column;
  width: 16px;
  height: 24px;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: col-resize;
  border-radius: var(--radius-xs);
  flex-shrink: 0;
  transition: background var(--transition-fast);
}

.page-modal-resize-grip:hover {
  background: var(--color-hover);
}

.page-modal-resize-grip span {
  width: 2px;
  height: 2px;
  background: var(--color-muted);
  border-radius: var(--radius-pill);
  pointer-events: none;
  transition: background var(--transition-fast);
}

.page-modal-resize-grip:hover span,
body.page-modal-resizing .page-modal-resize-grip span {
  background: var(--color-accent);
}

/* Vertical accent line at the side panel's left edge — invisible by
   default, lit on grip hover or during drag. */
.page-modal-resize-line {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -1px;
  width: 2px;
  background: var(--color-accent);
  opacity: 0;
  pointer-events: none;
  z-index: var(--z-base);
  transition: opacity var(--transition-normal);
}

.page-modal-side:has(.page-modal-resize-grip:hover) .page-modal-resize-line,
body.page-modal-resizing .page-modal-resize-line {
  opacity: 1;
}

/* Width readout — floating chip below the grip during drag */
.page-modal-resize-readout {
  position: absolute;
  top: 36px;
  left: var(--space-2);
  z-index: var(--z-dropdown);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-3);
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
  box-shadow: var(--shadow-md);
  pointer-events: none;
  white-space: nowrap;
}

.page-modal-resize-readout--snapped {
  background: var(--color-accent);
  color: var(--color-surface);
  border-color: var(--color-accent);
}

/* Snap-point guides — ghost lines inside .page-modal-body at each snap
   position, visible only during drag. The line within snap range brightens
   to solid accent (no shadow — token rules). */
.page-modal-resize-snap-guide {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: color-mix(in srgb, var(--color-accent) 35%, transparent);
  pointer-events: none;
  z-index: var(--z-base);
  transition: background var(--transition-fast), width var(--transition-fast);
}

.page-modal-resize-snap-guide--near {
  background: var(--color-accent);
  width: 2px;
}

.page-modal-resize-snap-label {
  position: absolute;
  top: var(--space-3);
  left: var(--space-1);
  font-size: 10px;
  font-weight: var(--weight-semibold);
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-surface) 92%, transparent);
  padding: 1px var(--space-2);
  border-radius: var(--radius-xs);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.page-modal-resize-snap-guide--near .page-modal-resize-snap-label {
  background: var(--color-accent);
  color: var(--color-surface);
}

body.page-modal-resizing {
  cursor: col-resize !important;
  user-select: none;
}

/* ── Inline status row in the identity block ──────────────────────────── */
.page-modal-status-row {
  display: grid;
  grid-template-columns: auto auto 1fr;
  gap: var(--space-1);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
  margin-top: var(--space-3);
}

.page-modal-status-row .btn {
  white-space: nowrap;
  min-width: 0;
}

.page-modal-status-btn {
  text-align: center;
  font-weight: var(--weight-medium);
}

/* Approve gets the rightmost slot and grows to fill */
.page-modal-status-btn--approve {
  justify-content: center;
}

/* Active states — committed tone-tinted look. The base button class
   provides the colour; the --active modifier deepens it. */
.page-modal-status-btn--active.page-modal-status-btn--approve {
  background: color-mix(in srgb, var(--color-success) 14%, transparent);
  color: var(--color-success);
  border-color: var(--color-success);
  font-weight: var(--weight-semibold);
}

.page-modal-status-btn--active.page-modal-status-btn--approve:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-success) 20%, transparent);
  border-color: var(--color-success);
  color: var(--color-success);
}

.page-modal-status-btn--active.page-modal-status-btn--redo {
  background: color-mix(in srgb, var(--color-warning) 14%, transparent);
  color: var(--color-warning-text);
  border-color: var(--color-warning);
  font-weight: var(--weight-semibold);
}

.page-modal-status-btn--active.page-modal-status-btn--reject {
  background: color-mix(in srgb, var(--color-danger) 14%, transparent);
  color: var(--color-danger);
  border-color: var(--color-danger);
  font-weight: var(--weight-semibold);
}

/* ── Quality tab — checks renderer ────────────────────────────────────── */
.quality-checks {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* Card container + title styles for the modal's compact cards now live
   on the shared `card--compact` / `card--warning` variants (see the
   `card` macro and §"Card header / actions" above). Inner element rules
   (.quality-checks-item, -box, -date-row, -empty) remain below. */

.quality-checks-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  cursor: pointer;
  user-select: none;
  font-size: var(--font-sm);
  color: var(--color-text);
}

.quality-checks-item input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.quality-checks-box {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--color-border);
  background: var(--color-surface);
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.quality-checks-item:hover .quality-checks-box {
  border-color: var(--color-accent);
}

/* Ticked = the design-system checkmark (same SVG as .checkbox), not a
   font-dependent "✓" glyph. background-color (not the shorthand) so the
   image survives the warn variant's colour override below. */
.quality-checks-item--on .quality-checks-box {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='3.5 8.5 6.5 11.5 12.5 5'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 0.75rem 0.75rem;
}

/* Warning-tinted row — a ticked check that flags a risk rather than
   confirming a pass (e.g. passport "4 or fewer blank pages"). Only the
   ticked state carries the warning colour; unticked reads as neutral. */
.quality-checks-item--warn.quality-checks-item--on {
  color: var(--color-warning-text);
}

.quality-checks-item--warn.quality-checks-item--on .quality-checks-box {
  background-color: var(--color-warning);
  border-color: var(--color-warning);
}

/* Free-text detail for a needs_description reason (#638). */
.quality-checks-desc {
  width: 100%;
  margin: var(--space-1) 0 var(--space-2);
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-sm);
  line-height: var(--line-height-normal);
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.quality-checks-date-row {
  display: grid;
  grid-template-columns: 88px 1fr;
  align-items: center;
  gap: var(--space-3);
  padding: 2px 0;
  font-size: var(--font-sm);
}

.quality-checks-date-row label {
  color: var(--color-subtle);
  font-size: var(--font-xs);
}

.quality-checks-date-row input[type="date"] {
  font-variant-numeric: tabular-nums;
}

/* A select in a checks row is a field beside its row's inputs, not a toolbar
   control: their colour and surface, as `.form-group > select.form-control`
   gives a select in a form group. Size and padding stay the row's. */
.quality-checks-date-row > select.form-control {
  color: var(--color-text);
  background-color: var(--color-bg);
}

/* "From the ID page" — the checks a doc type groups with what its ID page
   prints (`page: "id_page"`, passport_checks/_rows.html). A sibling group on
   the rows' own surface, so a hairline and a quiet caption set it apart; it
   is not a nesting level and takes no surface of its own. */
.quality-checks-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border-subtle);
}

.quality-checks-group:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.quality-checks-group__title {
  margin: 0;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
}

/* "Add comment with these issues →" button at the end of the reasons card */
.quality-checks-comment-btn {
  align-self: flex-start;
  margin-top: var(--space-2);
}

/* Relationship-doc Assembly checklist — reviewer's lens. Read-only
   guidance card inside the Quality tab; no save state in v1. Issue
   #482 §4. */
.quality-assembly-list {
  list-style: disc;
  padding-left: var(--space-5);
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  color: var(--color-subtle);
  font-size: var(--font-sm);
}

/* Empty state — shown when no visible quality groups (photo doc, not redo) */
.quality-checks-empty {
  background: var(--color-surface);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-5);
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.quality-checks-empty-title {
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  font-size: var(--font-sm);
}

.quality-checks-empty-msg {
  color: var(--color-subtle);
  font-size: var(--font-xs);
}

/* Save indicator — pip next to the title that flashes on save */
.quality-checks-save-pip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-success);
  font-weight: var(--weight-semibold);
  opacity: 0;
  transition: opacity var(--transition-normal);
}

.quality-checks-save-pip--visible {
  opacity: 1;
}

.quality-checks-save-pip::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: currentColor;
}

/* ── Activity tab — composer + pinned + grouped activity ───────────────── */
/* Activity tab body uses flex column with composer pinned to the bottom */
.page-modal-tab-content>.page-timeline {
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-height: 0;
}

/* Pinned section at top — warm-tinted container; inner comment shares the
   same tint so they read as one merged surface (matches the prototype). */
.page-timeline-pinned {
  flex-shrink: 0;
  background: color-mix(in srgb, var(--color-warning) 6%, var(--color-surface));
  border: 1px solid color-mix(in srgb, var(--color-warning) 30%, var(--color-border));
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.page-timeline-pinned-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.page-timeline-pinned-soft-cap {
  font-size: 10px;
  color: var(--color-warning-text);
}

/* Feed scrolls independently so composer stays pinned */
.page-timeline-feed {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Reply rows are NOT indented anymore — they're flat top-level rows with
   a quote-in-reply preamble showing who/what they're replying to. */
.timeline-reply-quote {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 11px;
  color: var(--color-muted);
  padding-bottom: var(--space-1);
  margin-bottom: var(--space-1);
  border-bottom: 1px dashed var(--color-border-subtle);
  min-width: 0;
}

.timeline-reply-quote-icon {
  color: var(--color-muted);
  font-size: 12px;
  flex-shrink: 0;
  line-height: 1;
}

.timeline-reply-quote-meta {
  flex-shrink: 0;
}

.timeline-reply-quote-meta strong {
  color: var(--color-text);
  font-weight: var(--weight-semibold);
}

.timeline-reply-quote-snippet {
  font-style: italic;
  color: var(--color-subtle);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}

/* ── Composer refresh ─────────────────────────────────────────────────── */
/* The outer .page-composer owns the card surface (border + radius + audience
   wash). The inner .composer-wrap (legacy layout div) is neutralised in this
   composer's scope so we don't get a card-within-a-card; the textarea and
   action bar own their own inner padding, edge-to-edge inside the border. */
.page-composer {
  flex-shrink: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  cursor: text;
  transition: border-color var(--transition-normal), background var(--transition-normal);
  margin-top: auto;
}

.page-composer .composer-wrap {
  background: transparent;
  border-radius: 0;
  overflow: visible;
}

.page-composer .composer-wrap:focus-within {
  border-color: transparent;
  box-shadow: none;
}

/* Team audience wash — only when the composer is expanded so resting cards
   stay neutral. Couple (shared) keeps the default white surface; the
   segmented selector makes the active side legible without leaning on a
   background tint. */
.page-composer--internal.page-composer--expanded {
  background: color-mix(in srgb, var(--color-accent-hover) 8%, var(--color-surface));
  /* Local deep-blue companion to --color-accent-hover. Scoped to the Team
     wash so the rest of the platform keeps reading the accent-hover tone at
     full strength; here we want a slightly darker shade for chrome (toolbar
     glyphs, audience label, inactive chip text). */
  --composer-team-deep: color-mix(in srgb, var(--color-accent-hover) 62%, black);
}

/* Team-mode chrome: toolbar buttons, audience label, scope-hint and the
   "Markdown supported" hint pick up the deep-blue tone so the bar reads as
   one tinted surface instead of grey chrome floating over a blue wash. */
.page-composer--internal.page-composer--expanded .page-composer-toolbar-btn {
  color: var(--composer-team-deep);
}

.page-composer--internal.page-composer--expanded .page-composer-toolbar-hint,
.page-composer--internal.page-composer--expanded .page-composer-audience-label,
.page-composer--internal.page-composer--expanded .page-composer-scope-hint {
  color: var(--composer-team-deep);
  opacity: 0.6;
}

.page-composer-textarea {
  width: 100%;
  border: 0;
  resize: none;
  font: inherit;
  font-size: var(--font-base);
  color: var(--color-text);
  background: transparent;
  outline: none;
  line-height: var(--line-height-relaxed);
  padding: var(--space-4) var(--space-5);
  min-height: 1.5rem;
  box-sizing: border-box;
}

/* When the composer is open, the textarea reserves a comfortable height so
   an empty Team draft doesn't feel cramped before the user has typed. */
.page-composer--expanded .page-composer-textarea {
  padding-bottom: var(--space-3);
}

.page-composer-textarea::placeholder {
  color: var(--color-muted);
}

.page-composer-toolbar {
  display: flex;
  align-items: center;
  gap: 2px;
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border-subtle);
}

.page-composer-toolbar[hidden] {
  display: none;
}

.page-composer-toolbar-btn {
  width: var(--control-h);
  height: var(--control-h);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  color: var(--color-subtle);
  font: inherit;
  font-size: var(--font-base);
  cursor: pointer;
  border-radius: var(--radius-xs);
  transition: background var(--transition-fast), color var(--transition-fast);
}

.page-composer-toolbar-btn:hover {
  background: var(--color-hover);
  color: var(--color-text-strong);
}

.page-composer-toolbar-btn svg {
  width: 16px;
  height: 16px;
}

.page-composer-toolbar-hint {
  margin-left: auto;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* Live "X is typing…" row (#579) — a quiet, centered chat-style line at the
   bottom of the comments feed (kept last by case_view_realtime.js), with the
   typist's avatar in the same style as a comment row. */
.timeline-typing {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2);
  font-size: var(--font-xs);
  font-style: italic;
  color: var(--color-muted);
}

/* `display: flex` outranks the `[hidden]` UA rule, so without this an idle
   typing row keeps its padding as ~20px of dead space in the feed. */
.timeline-typing[hidden] {
  display: none;
}

.timeline-typing-avatar {
  display: inline-flex;
  flex-shrink: 0;
}

.page-composer-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding-top: var(--space-1);
  border-top: 1px solid var(--color-border-subtle);
}

.page-composer-foot[hidden] {
  display: none;
}

.page-composer-audience-label {
  font-size: var(--font-xs);
  color: var(--color-muted);
  white-space: nowrap;
}

.page-composer-scope {
  display: inline-flex;
  background: var(--color-surface-muted);
  border-radius: var(--radius-sm);
  padding: 1px;
  font-size: var(--font-xs);
}

.page-composer-scope button {
  border: 0;
  background: transparent;
  padding: var(--space-1) var(--space-3);
  height: 30px;
  border-radius: var(--radius-xs);
  color: var(--color-muted);
  cursor: pointer;
  font: inherit;
  font-weight: var(--weight-normal);
  transition: background var(--transition-fast), color var(--transition-fast), box-shadow var(--transition-fast);
}

.page-composer-scope button:hover:not(.is-active):not(:disabled) {
  color: var(--color-text);
}

.page-composer-scope button.is-active {
  background: var(--color-surface);
  color: var(--color-text-strong);
  box-shadow: var(--shadow-sm);
}

/* Team active chip: solid brand blue with white text, so "Visible to Team"
   reads at a glance even with the rest of the footer present. */
.page-composer--internal .page-composer-scope button[data-scope="internal"].is-active {
  background: var(--color-accent-hover);
  color: var(--color-surface);
  border: 1px solid color-mix(in srgb, var(--color-accent-hover) 24%, transparent);
}

/* Team-mode segmented track: light-blue wash so the selector reads as part
   of the tinted surface rather than a neutral pill floating on top. The
   inactive Couple chip stays visible but recedes (deep blue + opacity). */
.page-composer--internal.page-composer--expanded .page-composer-scope {
  background: color-mix(in srgb, var(--color-accent-hover) 14%, var(--color-surface));
}

.page-composer--internal.page-composer--expanded .page-composer-scope button:not(.is-active) {
  color: var(--composer-team-deep);
  opacity: 0.6;
}

/* Locked while replying to an internal parent (#495): the Couple chip can't
   be selected — a reply may never be more visible than what it answers. */
.page-composer-scope button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* `font-family: inherit` because a button does not inherit one — without it the
   UA default wins and this is the one control in the composer not rendering in
   Inter, beside a toolbar and a scope selector that both set it. */
.page-composer-send {
  height: 30px;
  padding: 0 var(--space-4);
  font-family: inherit;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
}

/* Couple send button is a ghost — the accent-bold default ships the message
   to the customer, so the design quiets it to a deliberate, second-glance
   action. Team Send (below) stays strong because the team-side ship is the
   everyday case. Scoped to .page-composer so the canonical .composer-send
   strong styling elsewhere is untouched. */
.page-composer .page-composer-send {
  background: var(--color-surface);
  border-color: var(--color-border-subtle);
  color: var(--color-muted);
}

.page-composer .page-composer-send:hover:not(:disabled) {
  background: var(--color-surface-muted);
  border-color: var(--color-border);
  color: var(--color-text);
}

/* Send & archive (#1081) — the second submit beside Send, quieter still: no
   border until hovered, so Send stays the one the eye lands on. */
.page-composer .page-composer-send--secondary {
  border-color: transparent;
  margin-right: var(--space-1);
}

/* Team send button: solid brand blue with white text — the strong commit
   colour for the everyday "ship to team" action. */
.page-composer--internal .page-composer-send {
  background: var(--color-accent-hover);
  border-color: color-mix(in srgb, var(--composer-team-deep) 33%, transparent);
  color: var(--color-surface);
}

.page-composer--internal .page-composer-send:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-accent-hover) 86%, black);
  border-color: color-mix(in srgb, var(--color-accent-hover) 86%, black);
  color: var(--color-surface);
}

/* ── Empty state for Activity tab ─────────────────────────────────────── */
.page-timeline-empty {
  flex: 1;
  min-height: 0;
  align-items: center;
  justify-content: center;
  background: var(--color-surface);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-5);
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.page-timeline-empty-title {
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  font-size: var(--font-sm);
}

.page-timeline-empty-msg {
  color: var(--color-subtle);
  font-size: var(--font-xs);
}

/* ── Version history — overflow menu (per-row) ────────────────────────── */
.version-history-overflow {
  position: relative;
}

.version-history-overflow-list {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: var(--space-1);
  min-width: 160px;
  z-index: var(--z-dropdown);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Flipped variant — set by versionOverflowToggle() when there isn't
   enough room below the trigger inside the scrolling tab container
   (typically: the menu is on the bottom-most card; opening downward
   would land it under the tab-content's overflow boundary). */
.version-history-overflow-list--up {
  top: auto;
  bottom: calc(100% + 4px);
}

.version-history-overflow-list[hidden] {
  display: none;
}

.version-history-overflow-item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-xs);
  font: inherit;
  font-size: var(--font-sm);
  color: var(--color-text);
  cursor: pointer;
}

.version-history-overflow-item:hover {
  background: var(--color-hover);
}

/* ── Combobox refinement — ellipsis + drop L/R face tag ───────────────── */
.combobox-input {
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}

/* L/R face tag dropped — position conveys orientation. */
.page-modal-face-tag {
  display: none;
}

/* ── Keyboard shortcuts ─────────────────────────────────────────────────
   A small reusable set: the keycap glyph, the trigger chip that opens the
   cheat-sheet, and the cheat-sheet body itself (placed inside the platform
   `modal` macro, which supplies backdrop + header + close). Markup lives in
   core/templates/components/shortcuts.html. Replaces the legacy
   .page-modal-kb-* rules from the v1 docs-workspace modal. */

/* Raised key glyph */
.keycap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 var(--space-2);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-bottom-width: 2px;
  border-radius: var(--radius-xs);
  line-height: 1;
}

/* Smaller variant — keeps the raised surface + border from .keycap (so it
   stays legible on tinted surfaces like the nav rail), just more compact. */
.keycap--sm {
  min-width: 18px;
  height: 18px;
  padding: 0 var(--space-1);
}

/* Trigger chip — opens the shortcuts sheet. Inline by default; consumers
   that want it pinned (e.g. the bottom-right of a panel) add positioning in
   their own page scope. */
.shortcuts-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: transparent;
  border: 1px solid transparent;
  padding: var(--space-1) var(--space-3);
  font: inherit;
  font-size: var(--font-xs);
  color: var(--color-muted);
  cursor: pointer;
  border-radius: var(--radius-xs);
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.shortcuts-btn:hover {
  background: var(--color-hover);
  border-color: var(--color-border-subtle);
  color: var(--color-subtle);
}

/* A combo of keycaps, side by side */
.shortcuts-keys {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

/* Cheat-sheet body — two-column grid of shortcut groups */
.shortcuts-sheet {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4) var(--space-6);
}

.shortcuts-sheet__group-title {
  font-size: var(--font-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border-subtle);
}

.shortcuts-sheet__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  font-size: var(--font-sm);
}

.shortcuts-sheet__label {
  color: var(--color-text);
}

.shortcuts-sheet__keys {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.shortcuts-sheet__sep {
  font-size: var(--font-xs);
  color: var(--color-muted);
  margin: 0 var(--space-1);
}

.shortcuts-sheet__foot {
  margin: var(--space-4) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

/* ── Adjust .page-modal-tab-content to enable a flex column for Activity ── */
/* The Activity tab needs its child to fill the height — Quality/History/Info
   keep their flow layout. We apply via :has() so old tabs aren't affected.
   We also turn OFF the outer overflow-y on tab-content for the activity
   case — the .page-timeline-feed inside handles its own scrolling, and
   double overflow containers fight over height calculation. */
.page-modal-tab-content:has(> .page-timeline) {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}

.page-modal-tab-content:has(> .page-timeline)>.page-timeline {
  flex: 1;
  min-height: 0;
  padding: var(--space-4);
  height: auto;
}

/* ── Visual gap pass — match the prototype screenshots ─────────────────
   These rules layer on top of the Round 3 base above to close the gaps
   between the live implementation and the design's screenshots. */

/* Tighten the identity block — the prototype packs Single/Spread, faces,
   action row and status row into a compact stack. Halve the gap and trim
   the outer padding so the side panel has more room for the tab body
   below. Shared between modal (`.page-modal-side`) and filmstrip review
   (`.review-panel-inner`). */
:is(.page-modal-side, .review-panel-inner) .page-modal-page-controls {
  padding: var(--space-3) var(--space-4);
  gap: var(--space-3);
}

:is(.page-modal-side, .review-panel-inner) .page-modal-faces {
  gap: var(--space-2);
}

:is(.page-modal-side, .review-panel-inner) .page-modal-face {
  gap: var(--space-1);
}

:is(.page-modal-side, .review-panel-inner) .page-modal-control-actions {
  gap: var(--space-1);
}

/* Status row inline padding — was over-indented vs. the rest of the
   identity block. */
.page-modal-status-row {
  padding-top: var(--space-2);
  margin-top: 0;
}

/* Tab body sits on the dim surface so the white quality cards have
   something to read against. */
:is(.page-modal-side, .review-panel-inner) .page-modal-tab-content {
  background: var(--color-surface-dim);
}

/* Toolbar separators are too prominent at 14px solid borders; reduce to
   subtle hairlines that read as breathing room only. */
.page-modal-controls-sep {
  background: var(--color-border-subtle);
  height: 12px;
  margin: 0 2px;
}

/* Quality cards: subtle border so the card reads as a contained block
   without competing with the dim surface. */
:is(.page-modal-side, .review-panel-inner) .quality-checks-card {
  border-color: var(--color-border-subtle);
}

/* Pill on-state — scoped to the modal's face pills only. Outlined-tinted
   per the design (accent border + accent text + 12% tinted bg); does NOT
   override the global filled-accent pill used on page-card chips, column
   toggles, etc. */
.page-modal-face-pills .pill-toggle.pill-toggle--on,
.page-modal-face-pills .pill-toggle.pill-toggle--on:hover {
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  color: var(--color-accent);
  border-color: color-mix(in srgb, var(--color-accent) 35%, transparent);
}

/* Combobox chevron — small ▾ on the right edge of the input. Pure CSS so
   it survives modal re-renders without re-init. */
.combobox {
  position: relative;
}

.combobox::after {
  content: "▾";
  position: absolute;
  right: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  font-size: 10px;
  color: var(--color-muted);
  pointer-events: none;
  transition: transform var(--transition-fast);
}

.combobox:has(.combobox-list:not([hidden]))::after {
  transform: translateY(-50%) rotate(180deg);
  color: var(--color-accent);
}

.combobox .combobox-input {
  padding-right: var(--space-5);
  /* room for the chevron */
}

/* Side-panel comboboxes — the page-type chooser sits next to small
   labels and buttons, so it reads heavier than its neighbours with the
   default form-control--sm sizing. Dial down the size + weight so it
   matches the surrounding tab/button hierarchy. Shared between the
   modal (`.page-modal-side`) and the filmstrip review panel
   (`.review-panel-inner`) — both render the same partials. */
:is(.page-modal-side, .review-panel-inner) .combobox-input {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
}

:is(.page-modal-side, .review-panel-inner) .combobox-opt {
  font-size: var(--font-xs);
}

/* Same treatment for the passport card's Expiry / Issue date inputs +
   the Page info dl — they were rendering heavier than neighbouring text
   in either side panel context. */
:is(.page-modal-side, .review-panel-inner) .quality-checks-date-row {
  font-size: var(--font-xs);
}

:is(.page-modal-side, .review-panel-inner) .quality-checks-date-row input[type="date"] {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
}

:is(.page-modal-side, .review-panel-inner) .page-modal-info-list {
  font-size: var(--font-xs);
}

:is(.page-modal-side, .review-panel-inner) .page-modal-info-list dt {
  font-weight: var(--weight-normal);
}

:is(.page-modal-side, .review-panel-inner) .page-modal-info-list dd {
  color: var(--color-subtle);
  font-weight: var(--weight-normal);
}

/* Action row alignment — Delete (text link) sits next to Include/Exclude
   on the right edge. The whole row right-aligns with margin-left auto on
   the first child. */
:is(.page-modal-side, .review-panel-inner) .page-modal-control-actions {
  justify-content: flex-end;
  align-items: center;
  gap: var(--space-3);
}

/* Delete is rendered as a text link (no border, no background) until
   hover — keeps the destructive action quiet at rest, surfaces it when
   the operator deliberately points at it. */
.page-modal-delete-link {
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  font-size: var(--font-xs);
  color: var(--color-danger);
  cursor: pointer;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--transition-fast);
}

.page-modal-delete-link:hover {
  border-bottom-color: var(--color-danger);
}

.page-modal-delete-link:focus-visible {
  outline: none;
  border-bottom-color: var(--color-danger);
}

/* Status row buttons — all three share the same visual grammar:
   inactive = white bg, tone text + tone outline; active = tone-tinted bg,
   tone text + tone outline. Approve picks up a ✓ check via the template.
   Scoped to .page-modal-status-row so the global .btn-danger/.btn-primary
   filled variants stay correct elsewhere. */
.page-modal-status-row .page-modal-status-btn--reject:not(.page-modal-status-btn--active),
.page-modal-status-row .page-modal-status-btn--redo:not(.page-modal-status-btn--active),
.page-modal-status-row .page-modal-status-btn--approve:not(.page-modal-status-btn--active) {
  background: var(--color-surface);
}

.page-modal-status-row .page-modal-status-btn--reject:not(.page-modal-status-btn--active) {
  color: var(--color-danger);
  border-color: color-mix(in srgb, var(--color-danger) 30%, transparent);
}

.page-modal-status-row .page-modal-status-btn--reject:not(.page-modal-status-btn--active):hover {
  background: color-mix(in srgb, var(--color-danger) 8%, transparent);
  border-color: var(--color-danger);
  color: var(--color-danger);
}

.page-modal-status-row .page-modal-status-btn--redo:not(.page-modal-status-btn--active) {
  color: var(--color-warning-text);
  border-color: color-mix(in srgb, var(--color-warning) 30%, transparent);
}

.page-modal-status-row .page-modal-status-btn--redo:not(.page-modal-status-btn--active):hover {
  background: color-mix(in srgb, var(--color-warning) 8%, transparent);
  border-color: var(--color-warning);
  color: var(--color-warning-text);
}

.page-modal-status-row .page-modal-status-btn--approve:not(.page-modal-status-btn--active) {
  color: var(--color-success);
  border-color: color-mix(in srgb, var(--color-success) 30%, transparent);
}

.page-modal-status-row .page-modal-status-btn--approve:not(.page-modal-status-btn--active):hover {
  background: color-mix(in srgb, var(--color-success) 8%, transparent);
  border-color: var(--color-success);
  color: var(--color-success);
}

/* Status row sizing — tighter padding than the default .btn-sm so the
   three buttons fit comfortably side-by-side. */
.page-modal-status-row .page-modal-status-btn {
  padding: var(--space-1) var(--space-3);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

/* Toolbar icon buttons — strip the .btn-ghost border so they read as
   plain text-icons, not boxy buttons. The hover state stays (background
   tint via .btn-ghost:hover). */
.page-modal-controls .btn.btn-sm.btn-ghost {
  border: 0;
  background: transparent;
  color: var(--color-subtle);
  padding: 2px var(--space-2);
  min-width: 26px;
  height: 24px;
  font-size: var(--font-sm);
}

.page-modal-controls .btn.btn-sm.btn-ghost:hover:not(:disabled) {
  background: var(--color-hover);
  color: var(--color-text-strong);
}

.page-modal-controls .btn.btn-sm.btn-ghost:disabled {
  opacity: 0.4;
}

/* Renderings card — note text is a short caption, not body copy */
.page-modal-info-note {
  font-size: var(--font-xs);
  color: var(--color-subtle);
  line-height: var(--line-height-normal);
  margin: 0;
}

/* Drop the legacy `.quality-form { padding-top: 0.5rem }` in modal context
   so the Quality tab's first card aligns with the top of the tab body —
   the Info tab has no equivalent padding so they were offset. */
:is(.page-modal-side, .review-panel-inner) .quality-form,
:is(.page-modal-side, .review-panel-inner) .quality-form--checks,
:is(.page-modal-side, .review-panel-inner) .passport-checks-form {
  padding-top: 0;
  gap: var(--space-4);
}

/* Tab count badge — round chip after the tab label */
.page-modal-tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: var(--space-1);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  background: var(--color-surface-muted);
  padding: 1px var(--space-2);
  border-radius: var(--radius-pill);
  font-weight: var(--weight-medium);
}

.page-modal-tab--active .page-modal-tab-count {
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
}

/* Version history card — modal idiom (active variant supplied by the
   card macro as `.card--active`; relative positioning so the overflow
   menu pops above neighbouring cards). The :has() lifts the row's
   stacking context when its menu is open so the menu isn't clipped by
   the next card's background. */
.version-history-card {
  position: relative;
}

/* Specificity bump (0,2,0) so the tighter per-row padding wins over the
   macro's `.card--compact.card--padded { padding: var(--space-4) }`. */
.card--padded.version-history-card {
  padding: var(--space-3);
}

.version-history-card:has(.version-history-overflow-list:not([hidden])) {
  z-index: var(--z-dropdown);
  /* `.card { overflow: hidden }` clips children that escape the card's
     rounded box — including the overflow menu when it pops outside the
     card on the flip-up path. Override only while the menu is open so
     the rest of the card chrome (focus shadow on `--active`, etc.)
     keeps its visual containment in the resting state. */
  overflow: visible;
}

.version-history-card .version-history-row {
  padding: 0;
}

/* Source meta text — tighter font in either side-panel context. */
:is(.page-modal-side, .review-panel-inner) .version-history-meta-source {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* Overflow dropdown: above sibling cards. */
.version-history-overflow-list {
  z-index: var(--z-modal);
}

/* Reply banner — sits above the composer textarea when the operator
   clicks Reply on a comment. Stores the parent comment id, shows who is
   being replied to + a short quoted snippet, and offers a × to cancel. */
.page-composer-reply-banner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: color-mix(in srgb, var(--color-accent) 5%, var(--color-surface));
  border: 1px solid color-mix(in srgb, var(--color-accent) 25%, transparent);
  border-radius: var(--radius-xs);
  margin-bottom: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-text);
}

.page-composer-reply-banner[hidden] {
  display: none;
}

.page-composer-reply-icon {
  color: var(--color-accent);
  flex-shrink: 0;
}

.page-composer-reply-meta {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page-composer-reply-snippet {
  color: var(--color-muted);
  font-style: italic;
}

.page-composer-reply-cancel {
  background: transparent;
  border: 0;
  color: var(--color-subtle);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 0 var(--space-1);
  flex-shrink: 0;
}

.page-composer-reply-cancel:hover {
  color: var(--color-text);
}

/* Composer foot — visibility scope + Send button, revealed when the
   textarea is focused (or in reply mode). */
.page-composer-foot {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.page-composer-foot[hidden] {
  display: none;
}

/* Push the Send button to the right edge of the bar; the segmented Couple/Team
   selector sits inline before it. The base .page-composer-scope styling lives
   in the canonical block above. */
.page-composer-send {
  margin-left: auto;
}

/* Composer toolbar — markdown shortcuts. Revealed alongside the foot
   on focus. */
.page-composer-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border-subtle);
  margin-bottom: var(--space-2);
}

.page-composer-toolbar[hidden] {
  display: none;
}

.page-composer-toolbar-btn {
  background: transparent;
  border: 0;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-xs);
  color: var(--color-subtle);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.page-composer-toolbar-btn:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.page-composer-toolbar-hint {
  margin-left: auto;
  font-size: 10px;
  color: var(--color-muted);
}

/* Single action bar: markdown toolbar (left) + visibility toggle and send
   (right) share one row beneath the textarea, replacing the split where the
   toolbar and foot sat at different heights and the actions floated to the
   right of the textarea. Shared by all three composer surfaces (overview,
   page modal, section sidebar). Descendant selectors (0,2,0) override the
   base .page-composer-toolbar / .page-composer-foot rules regardless of
   source order. */
.page-composer-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  row-gap: var(--space-2);
  padding: var(--space-3) var(--space-4) var(--space-3) var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
}

.page-composer-bar[hidden] {
  display: none;
}

/* Bar divider picks up the warm tone when the Team wash is on, so the tint
   reads as a single surface instead of a band of plain divider over blue. */
.page-composer--internal.page-composer--expanded .page-composer-bar {
  border-top-color: color-mix(in srgb, var(--color-accent-hover) 22%, var(--color-border-subtle));
}

/* Lock hint (#495) — its own full-width line above the action row when
   replying to an internal comment, so it never crowds toolbar + send. */
.page-composer-bar .page-composer-scope-hint {
  flex-basis: 100%;
  margin: 0;
}

.page-composer-bar .page-composer-toolbar {
  margin: 0;
  padding: 0;
  border-bottom: 0;
  flex: 0 1 auto;
  min-width: 0;
}

.page-composer-bar .page-composer-toolbar-hint {
  margin-left: var(--space-2);
}

.page-composer-bar .page-composer-foot {
  margin: 0 0 0 auto;
  padding: 0;
  border-top: 0;
  justify-content: flex-end;
  flex-shrink: 0;
}

/* ── end Page modal Round 3 ──────────────────────────────────────────── */
/* ════════════════════════════════════════════════════════════════════════
   Review sidebar (#505) — page Overview pane redesign
   "Review Sidebar.html" Claude Design handoff. Labelled PAGE / INFO / HISTORY
   sections down the ~360px detail panel. Tokens only — no new tokens; the
   warning-gold reason states derive from --color-warning via color-mix.
   Behavioural hooks (data-page-controls, data-panel-quality, wsPanel*
   onclicks) are unchanged; this is composition + skin.
   ════════════════════════════════════════════════════════════════════════ */
.ws-rev {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.ws-rev-ic {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* ── Section: eyebrow header + collapsible body ── */
.ws-rev-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.ws-rev-section-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.ws-rev-section--collapsed .ws-rev-section-body {
  display: none;
}

.ws-rev-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 20px;
}

.ws-rev-eyebrow {
  margin: 0;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--color-muted);
}

.ws-rev-head-controls {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* ── Eye show/hide toggle ── */
.ws-rev-eye {
  display: inline-flex;
  align-items: center;
  padding: 0;
  background: none;
  border: 0;
  color: var(--color-accent);
  cursor: pointer;
  transition: color var(--transition-normal);
}

.ws-rev-eye:hover {
  color: var(--color-accent-hover);
}

.ws-rev-eye[aria-pressed="true"] {
  color: var(--color-muted);
}

.ws-rev-eye .ws-rev-ic {
  width: 12px;
  height: 12px;
}

.ws-rev-eye .ws-rev-ic:nth-of-type(2) {
  display: none;
}

.ws-rev-eye[aria-pressed="true"] .ws-rev-ic:nth-of-type(1) {
  display: none;
}

.ws-rev-eye[aria-pressed="true"] .ws-rev-ic:nth-of-type(2) {
  display: inline-block;
}

/* ── Spread / single slash toggle ── */
.ws-rev-spread {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.ws-rev-spread__opt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0;
  background: none;
  border: 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  cursor: pointer;
  transition: color var(--transition-normal);
}

.ws-rev-spread__opt:hover {
  color: var(--color-text);
}

.ws-rev-spread__opt.is-active,
.ws-rev-spread__opt.is-active:hover {
  color: var(--color-accent);
}

.ws-rev-spread__opt .ws-rev-ic {
  width: 13px;
  height: 13px;
}

.ws-rev-spread__sep {
  color: var(--color-border);
  font-size: var(--font-xs);
}

/* The toggle has nothing to act on while the section body is hidden — fade
   it out but keep the head row's height stable (design: PageCard visible). */
.ws-rev-section--collapsed .ws-rev-spread {
  opacity: 0;
  pointer-events: none;
}

/* ── State badge in the PAGE header ── */
.ws-rev-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 1px var(--space-2) 1px var(--space-1);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  white-space: nowrap;
}

.ws-rev-badge .ws-rev-ic {
  width: 13px;
  height: 13px;
}

.ws-rev-badge--excluded {
  color: var(--color-subtle);
  background: var(--color-surface-muted);
}

.ws-rev-badge--approved {
  color: var(--color-success);
  background: color-mix(in srgb, var(--color-success) 12%, white);
  border-color: color-mix(in srgb, var(--color-success) 35%, var(--color-border));
}

.ws-rev-badge--redo {
  color: var(--color-warning-text);
  background: color-mix(in srgb, var(--color-warning) 22%, white);
  border-color: color-mix(in srgb, var(--color-warning) 45%, var(--color-border));
}

/* ── Page box: faces + decision/reasons ── */
.ws-rev-box {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  /* overflow: hidden; */
}

.ws-rev-box--approved {
  background: color-mix(in srgb, var(--color-success) 5%, var(--color-surface));
  border-color: color-mix(in srgb, var(--color-success) 35%, var(--color-border));
}

.ws-rev-box--redo {
  border-color: color-mix(in srgb, var(--color-warning) 40%, var(--color-border));
}

/* Excluded — neutral "set aside": dimmed greyscale faces under a faint
   diagonal hatch. Awaiting (pending) mirrors it in the warm redo tone. */
.ws-rev-box--excluded {
  position: relative;
  background: var(--color-surface-dim);
}

.ws-rev-box--excluded::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: repeating-linear-gradient(
    135deg,
    transparent 0 9px,
    color-mix(in srgb, var(--color-text) 2%, transparent) 9px 18px
  );
  pointer-events: none;
}

.ws-rev-box--pending {
  position: relative;
  background: color-mix(in srgb, var(--color-warning) 6%, var(--color-surface));
  border-color: color-mix(in srgb, var(--color-warning) 40%, var(--color-border));
}

.ws-rev-box--pending::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: repeating-linear-gradient(
    135deg,
    transparent 0 9px,
    color-mix(in srgb, var(--color-warning) 9%, transparent) 9px 18px
  );
  pointer-events: none;
}

/* ── Faces (combobox + pills) inside the box ── */
.ws-rev-faces {
  padding: var(--space-4);
}

.ws-rev-faces.page-modal-faces,
.ws-rev-faces.page-modal-faces--spread {
  gap: var(--space-3);
}

.ws-rev-box--excluded .ws-rev-faces {
  opacity: 0.45;
  filter: grayscale(1);
  pointer-events: none;
  user-select: none;
}

.ws-rev-box--pending .ws-rev-faces {
  opacity: 0.45;
  filter: grayscale(0.5);
  pointer-events: none;
  user-select: none;
}

.ws-rev-face__label {
  display: none;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  letter-spacing: 0.01em;
  color: var(--color-subtle);
}

.ws-rev .page-modal-faces--spread .ws-rev-face__label {
  display: inline-flex;
}

.ws-rev-face__label .ws-rev-ic {
  width: 13px;
  height: 13px;
  color: var(--color-muted);
}

/* Per-face vertical rhythm: label → combobox → chips. */
.ws-rev .page-modal-face {
  gap: var(--space-2);
}

/* Number-or-type combo — the design's 30px control (sb-combo) with a real
   svg chevron toggle button; the global glyph ::after steps aside. */
.ws-rev .combobox-input {
  height: var(--control-h);
  padding: 0 var(--space-6) 0 var(--space-3);
  font-size: var(--font-sm);
  border-radius: var(--radius-sm);
}

.ws-rev .combobox::after {
  content: none;
}

.ws-rev .combobox-chev {
  position: absolute;
  top: 0;
  right: 0;
  width: 28px;
  height: var(--control-h);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: none;
  color: var(--color-muted);
  cursor: pointer;
}

.ws-rev .combobox-chev .ws-rev-ic {
  width: 15px;
  height: 15px;
}

.ws-rev .combobox-opt {
  font-size: var(--font-sm);
}

/* Stamp / Visa as full-width bordered chips (not tiny pills), success-toned
   when on — matches the design chips. */
.ws-rev .page-modal-face-pills {
  gap: var(--space-2);
}

.ws-rev .pill-toggle {
  flex: 1;
  min-width: 0;
  height: var(--control-h);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: 0 var(--space-2);
  border-radius: var(--radius-xs);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
}

/* Three classes deep to out-rank the accent-tinted page-modal on-state
   (.page-modal-face-pills .pill-toggle.pill-toggle--on) — in the review
   sidebar the on-state is success-toned per the design. */
.ws-rev .page-modal-face-pills .pill-toggle--on,
.ws-rev .page-modal-face-pills .pill-toggle--on:hover {
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
  color: var(--color-success);
  border-color: color-mix(in srgb, var(--color-success) 35%, transparent);
}

.ws-rev .pill-toggle .ws-rev-ic {
  width: 12px;
  height: 12px;
}

/* ── Decision foot (kebab + Needs redo / Approve), state foots ── */
.ws-rev-foot {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
}

/* The decision foot (kebab + Needs redo / Approve) packs tighter than the
   state foots — design sb-faces-foot gap 6px vs 8px. */
.ws-rev-foot:has(.ws-rev-decision) {
  gap: var(--space-2);
}

.ws-rev-foot__icon {
  display: inline-flex;
  flex-shrink: 0;
}

.ws-rev-foot__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.ws-rev-foot__title {
  font-size: var(--font-sm);
  font-weight: var(--weight-medium);
}

.ws-rev-foot__meta {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* "What's missing" (#1098) — sits under the missing foot inside the same box,
   so it needs the gap the foot's own row does not provide. */
.ws-rev-missing-desc {
  margin-top: var(--space-2);
  margin-bottom: 0;
}

/* The disclosure, not decoration: it says the couple reads the field, so it
   carries the warning text colour rather than the muted one every other hint
   uses — a hint the operator skims past is how quality_notes leaked (#955). */
.ws-rev-missing-desc__hint {
  color: var(--color-warning-text);
}

/* Resting-state foots sit above the box's hatch overlay. */
.ws-rev-foot--excluded,
.ws-rev-foot--pending,
.ws-rev-foot--approved {
  position: relative;
  z-index: var(--z-base);
}

.ws-rev-foot--excluded .ws-rev-foot__icon,
.ws-rev-foot--excluded .ws-rev-foot__title {
  color: var(--color-subtle);
}

/* The resting titles read as quiet captions (design tweak: 12px regular). */
.ws-rev-foot--excluded .ws-rev-foot__title,
.ws-rev-foot--approved .ws-rev-foot__title {
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
}

.ws-rev-foot--approved {
  border-top-color: color-mix(in srgb, var(--color-success) 25%, var(--color-border-subtle));
}

.ws-rev-foot--approved .ws-rev-foot__icon,
.ws-rev-foot--approved .ws-rev-foot__title {
  color: var(--color-success);
}

.ws-rev-foot--approved .ws-rev-foot__meta {
  color: var(--color-subtle);
}

/* ── Awaiting-new-version foot: meta row + reasons recap + cancel ── */
.ws-rev-foot--pending {
  flex-direction: column;
  align-items: stretch;
  border-top-color: color-mix(in srgb, var(--color-warning) 35%, var(--color-border-subtle));
}

.ws-rev-foot__row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.ws-rev-foot--pending .ws-rev-foot__icon {
  margin-top: 1px;
  color: var(--color-warning-text);
}

.ws-rev-foot--pending .ws-rev-foot__title {
  color: var(--color-warning-text);
}

.ws-rev-foot--pending .ws-rev-foot__meta {
  color: var(--color-subtle);
}

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

.ws-rev-foot__reasons li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

.ws-rev-foot__reasons li::before {
  content: "";
  width: 4px;
  height: 4px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-warning-text) 55%, transparent);
}

.ws-rev-foot__actions {
  display: flex;
  gap: var(--space-3);
}

.ws-rev-foot__actions > * {
  flex: 1;
  min-width: 0;
}

.ws-rev-cancel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  height: var(--control-h);
  border: 1px solid color-mix(in srgb, var(--color-warning) 45%, var(--color-border));
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-warning-text);
  font-size: var(--font-sm);
  font-weight: var(--weight-normal);
  cursor: pointer;
  transition: background var(--transition-normal), border-color var(--transition-normal);
}

.ws-rev-cancel:hover {
  background: color-mix(in srgb, var(--color-warning) 14%, transparent);
  border-color: color-mix(in srgb, var(--color-warning) 60%, var(--color-border));
}

.ws-rev-decision {
  flex: 1;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--space-2);
}

.ws-rev-decision__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  height: var(--control-h);
  padding: 0 var(--space-2);
  border: 1px solid;
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  line-height: var(--line-height-tight);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--transition-normal), border-color var(--transition-normal), color var(--transition-normal);
}

.ws-rev-decision__btn--redo {
  color: var(--color-warning-text);
  border-color: color-mix(in srgb, var(--color-warning) 35%, transparent);
}

.ws-rev-decision__btn--redo:hover {
  background: color-mix(in srgb, var(--color-warning) 10%, transparent);
  border-color: var(--color-warning);
}

.ws-rev-decision__btn--redo.is-active {
  background: color-mix(in srgb, var(--color-warning) 18%, transparent);
  border-color: var(--color-warning);
}

.ws-rev-decision__btn--approve {
  color: var(--color-success);
  border-color: color-mix(in srgb, var(--color-success) 30%, transparent);
}

.ws-rev-decision__btn--approve:hover {
  background: color-mix(in srgb, var(--color-success) 8%, transparent);
  border-color: var(--color-success);
}

.ws-rev-decision__btn--approve[aria-pressed="true"] {
  background: color-mix(in srgb, var(--color-success) 14%, transparent);
  border-color: var(--color-success);
}

/* ── Kebab overflow ── */
.ws-rev-kebab {
  position: relative;
  flex-shrink: 0;
}

.ws-rev-kebab__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-h);
  height: var(--control-h);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-subtle);
  cursor: pointer;
  transition: background var(--transition-normal), border-color var(--transition-normal), color var(--transition-normal);
}

.ws-rev-kebab__btn:hover {
  background: var(--color-hover);
  border-color: var(--color-muted);
  color: var(--color-text);
}

.ws-rev-kebab__btn .ws-rev-ic {
  width: 16px;
  height: 16px;
}

.ws-rev-kebab__menu {
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 0;
  z-index: var(--z-dropdown);
  min-width: 184px;
  padding: var(--space-1);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  display: none;
}

.ws-rev-kebab--open .ws-rev-kebab__menu {
  display: block;
}

.ws-rev-kebab__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: none;
  border-radius: var(--radius-xs);
  font-size: var(--font-sm);
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
}

.ws-rev-kebab__item:hover {
  background: var(--color-hover);
}

.ws-rev-kebab__item .ws-rev-ic {
  color: var(--color-muted);
}

/* Re-include / Undo trailing buttons */
.ws-rev-relink,
.ws-rev-undo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
  height: var(--control-h);
  padding: 0 var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-subtle);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  cursor: pointer;
  transition: background var(--transition-normal), border-color var(--transition-normal), color var(--transition-normal);
}

.ws-rev-relink:hover,
.ws-rev-undo:hover {
  background: var(--color-hover);
  border-color: var(--color-muted);
  color: var(--color-text);
}

/* ── Form wraps the box + the reasons/addon checklist; both flow as a
   spaced column so the cards sit BELOW the box, not nested inside it. ── */
/* The global .quality-form adds padding-top: 0.5rem and
   .passport-checks-form adds margin-top; the old surfaces
   (.page-modal-side / .review-panel-inner) zero the padding, but this
   panel isn't under those wrappers — without this, head-to-box spacing
   doubles (the section gap already provides the design's 8px). */
.ws-rev .quality-form {
  padding-top: 0;
  margin-top: 0;
}

.ws-rev-form,
.ws-rev-checklist {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* A foot with no faces above it (section-tabbed docs) shouldn't carry a
   top divider against the box edge. */
.ws-rev-box > .ws-rev-foot:first-child {
  border-top: 0;
}

/* The standalone checklist cards below the box (Passport, Renderings,
   Assembly) take the same dim surface as the Info / History boxes, so the
   panel reads as one consistent stack of bordered sections. The warning
   "Reasons" card keeps its own tinted surface. */
.ws-rev .card:not(.card--warning) {
  background: var(--color-surface-dim);
}

/* ── Reasons-for-redo composer (design "Needs redo open · D — checklist +
   preview"): eyebrow head with ticked-count pill + × cancel, then a warm
   card — checkbox rows with hairline dividers, an add-another free-text
   row, the generated message preview (dashed) and the To/Send foot. The
   olive/cream accents are the design's bespoke redo palette — scoped here,
   not global tokens. ── */
.ws-rev-redo {
  --redo-on: #8d7d36;        /* checked fill, count pill, active text */
  --redo-cream: #fefbec;     /* card + preview fill, checkmark stroke */
  --redo-sand: #d8c98a;      /* card border, unchecked checkbox border */
  --redo-send-fill: #fde79d; /* send button face */
  --redo-send-border: #e9daa7;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* The bulk redo composer (#631) toggles on the `hidden` attribute, but the
   class above sets `display:flex`, which beats the UA `[hidden]` rule (equal
   specificity, author wins). Restore `hidden` so the selection-panel composer
   stays collapsed until "Needs redo N" opens it. The single-page composer is
   server-gated and never sets `hidden`, so this is inert there. */
.ws-rev-redo[hidden] {
  display: none;
}

/* Redo-set summary (#634) — the collapsed, not-yet-sent redo state. panel.js
   toggles it against the composer via the `hidden` attribute, so the same
   author-vs-UA `[hidden]` override applies. */
.ws-rev-redo-summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.ws-rev-redo-summary[hidden] {
  display: none;
}

.ws-rev-redo-summary__reasons {
  margin: 0;
}

.ws-rev-redo-summary__empty {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.ws-rev-redo-summary__link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--color-accent);
  text-decoration: underline;
  cursor: pointer;
}

.ws-rev-redo-summary__actions {
  margin-top: var(--space-1);
}

/* Sent variant — the "Awaiting new version" card. Keeps the old pending-foot
   treatment (padding + hairline + sit above the box hatch) so the sent and
   not-yet-sent redo summaries read as one component. */
.ws-rev-redo-summary--sent {
  position: relative;
  z-index: var(--z-base);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
}

.ws-rev-redo-summary__sent-head {
  align-items: flex-start;
  justify-content: flex-start;
}

.ws-rev-redo__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.ws-rev-redo__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--redo-on);
  color: var(--redo-cream);
  font-size: 11px;
  font-weight: var(--weight-semibold);
}

.ws-rev-redo__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--color-subtle);
  cursor: pointer;
  transition: background var(--transition-normal), color var(--transition-normal);
}

.ws-rev-redo__close:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.ws-rev-redo__close svg {
  width: 12px;
  height: 12px;
}

.ws-rev-redo__box {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--redo-sand);
  border-radius: var(--radius-md);
  background: var(--redo-cream);
}

.ws-rev-redo__list {
  display: flex;
  flex-direction: column;
}

.ws-rev-redo__list > * + * {
  border-top: 1px solid color-mix(in srgb, var(--color-warning) 24%, transparent);
}

.ws-rev-redo__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-1) 0;
  font-size: var(--font-xs);
  line-height: var(--line-height-normal);
  color: var(--color-text);
  cursor: pointer;
  user-select: none;
}

.ws-rev-redo__row input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* Free-text detail for a needs_description reason (#638) — sits under its
   row, so it must not pick up the list's inter-row divider. */
.ws-rev-redo__desc {
  width: 100%;
  margin: var(--space-1) 0 var(--space-2);
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-xs);
  line-height: var(--line-height-normal);
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--redo-sand);
  border-radius: var(--radius-xs);
}

.ws-rev-redo__list > .ws-rev-redo__desc {
  border-top: none;
}

.ws-rev-redo__check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  border: 1px solid var(--redo-sand);
  border-radius: var(--radius-xs);
  background: var(--color-surface);
  color: var(--redo-cream);
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.ws-rev-redo__check svg {
  width: 10px;
  height: 10px;
  opacity: 0;
}

.ws-rev-redo__row:hover .ws-rev-redo__check {
  border-color: var(--redo-on);
}

.ws-rev-redo__row input:checked + .ws-rev-redo__check {
  background: var(--redo-on);
  border-color: var(--redo-on);
}

.ws-rev-redo__row input:checked + .ws-rev-redo__check svg {
  opacity: 1;
}

.ws-rev-redo__row input:focus-visible + .ws-rev-redo__check {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* Partner selector (#737) — narrows a shared-requirement redo to one partner.
   A segmented radio set in the warm redo palette; sits above the reasons. */
.ws-rev-redo__partner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.ws-rev-redo__partner-lead {
  font-size: 11px;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-warning-text);
}

.ws-rev-redo__partner-opt {
  position: relative;
  display: inline-flex;
  cursor: pointer;
}

.ws-rev-redo__partner-opt input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.ws-rev-redo__partner-pill {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--redo-sand);
  border-radius: var(--radius-pill);
  background: var(--redo-cream);
  font-size: var(--font-xs);
  color: var(--redo-on);
  transition:
    background var(--transition-fast),
    border-color var(--transition-fast);
}

.ws-rev-redo__partner-opt:hover .ws-rev-redo__partner-pill {
  border-color: var(--redo-on);
}

.ws-rev-redo__partner-opt input:checked + .ws-rev-redo__partner-pill {
  background: var(--redo-on);
  border-color: var(--redo-on);
  color: var(--color-surface);
}

.ws-rev-redo__partner-opt input:focus-visible + .ws-rev-redo__partner-pill {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.ws-rev-redo__msg {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.ws-rev-redo__msg-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.ws-rev-redo__msg-label {
  font-size: 11px;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-warning-text);
}

.ws-rev-redo__edit {
  padding: 0;
  border: 0;
  background: none;
  font-size: var(--font-xs);
  color: var(--color-accent);
  cursor: pointer;
}

.ws-rev-redo__edit:hover {
  text-decoration: underline;
}

.ws-rev-redo__preview {
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--redo-sand);
  border-radius: var(--radius-sm);
  background: var(--redo-cream);
  font-size: var(--font-xs);
  line-height: var(--line-height-relaxed);
  color: var(--redo-on);
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.ws-rev-redo__foot {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.ws-rev-redo__to {
  position: relative;
  flex-shrink: 0;
}

.ws-rev-redo__to-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: var(--control-h);
  padding: 0 var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  font-size: var(--font-xs);
  color: var(--color-warning-text);
  cursor: pointer;
  transition: background var(--transition-fast);
}

.ws-rev-redo__to-btn:hover {
  background: color-mix(in srgb, var(--color-warning) 14%, transparent);
}

.ws-rev-redo__to-chev {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  opacity: 0.8;
}

.ws-rev-redo__to-menu {
  position: absolute;
  bottom: calc(100% + var(--space-1));
  left: 0;
  z-index: var(--z-dropdown);
  min-width: 160px;
  padding: var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  box-shadow: var(--shadow-md);
  display: none;
}

.ws-rev-redo__to--open .ws-rev-redo__to-menu {
  display: block;
}

.ws-rev-redo__to-opt {
  display: block;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-xs);
  background: none;
  font-size: var(--font-sm);
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
}

.ws-rev-redo__to-opt:hover {
  background: var(--color-hover);
}

.ws-rev-redo__to-opt.is-selected {
  font-weight: var(--weight-medium);
}

.ws-rev-redo__send {
  flex: 1;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--control-h);
  border: 1px solid var(--redo-send-border);
  border-radius: var(--radius-sm);
  background: var(--redo-send-fill);
  color: var(--color-warning-text);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  cursor: pointer;
  transition: background var(--transition-normal), border-color var(--transition-normal);
}

.ws-rev-redo__send:hover:not(:disabled) {
  background: color-mix(in srgb, var(--color-warning) 38%, var(--color-surface));
  border-color: color-mix(in srgb, var(--color-warning) 55%, var(--color-surface));
}

.ws-rev-redo__send:disabled {
  opacity: 0.55;
  cursor: default;
}

.ws-rev-redo__send svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

/* ── Info foldout ── */
.ws-rev-fold {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-dim);
}

.ws-rev-fold__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px dashed var(--color-border);
}

.ws-rev-fold__title {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  font-size: var(--font-xs);
  color: var(--color-text-strong);
  white-space: nowrap;
  overflow: hidden;
}

/* Finder-style middle truncation (split in page_info_fold.html): the head
   yields with an ellipsis, the tail (extension) never shrinks. */
.ws-rev-fold__title-head {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ws-rev-fold__title-tail {
  flex: none;
}

/* Header filename as a quiet source-file link (Drive preview): inherits the
   title's strong text colour and only reads as a link on hover. Flex so the
   middle-truncation head/tail spans keep working inside the link. */
.ws-rev-fold__title-link {
  display: flex;
  align-items: baseline;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}

.ws-rev-fold__title-link:hover {
  color: var(--color-accent);
  text-decoration: underline;
}

/* Show more / Show less — quiet accent link in the fold head. The two
   labels swap on the fold's open class (panel.js toggles via data-ws-fold). */
.ws-rev-fold__toggle {
  margin-left: auto;
  flex-shrink: 0;
  padding: 0;
  background: none;
  border: 0;
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  color: var(--color-accent);
  cursor: pointer;
}

.ws-rev-fold__toggle:hover {
  text-decoration: underline;
}

.ws-rev-fold__toggle-less {
  display: none;
}

.ws-rev-fold--open .ws-rev-fold__toggle-less {
  display: inline;
}

.ws-rev-fold--open .ws-rev-fold__toggle-more {
  display: none;
}

/* Closed fold keeps the three orientation rows; the rest fold away. The
   separator set recomputes against the *visible* rows so the first shown
   row never carries a stray top border. */
.ws-rev-fold:not(.ws-rev-fold--open) .ws-rev-info-row--extra {
  display: none;
}

.ws-rev-fold:not(.ws-rev-fold--open) .ws-rev-info-row {
  border-top: 0;
}

.ws-rev-fold:not(.ws-rev-fold--open)
  .ws-rev-info-row:not(.ws-rev-info-row--extra)
  ~ .ws-rev-info-row:not(.ws-rev-info-row--extra) {
  border-top: 1px solid var(--color-border-subtle);
}

.ws-rev-info {
  margin: 0;
  padding: 2px var(--space-4) 2px;
  display: flex;
  flex-direction: column;
}

.ws-rev-info-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-2) 0;
  border-top: 1px solid var(--color-border-subtle);
}

.ws-rev-info-row:first-child {
  border-top: 0;
}

.ws-rev-info-row dt {
  margin: 0;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
  color: var(--color-muted);
  white-space: nowrap;
}

/* One line, clipped with an ellipsis — the row's space-between keeps short
   values hugging the right edge, and a title tooltip (page_overview.html) carries
   the full string for the long ones (lens model, software). */
.ws-rev-info-row dd {
  min-width: 0;
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-text);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}

.ws-rev-link {
  color: var(--color-accent);
  text-decoration: none;
}

.ws-rev-link:hover {
  text-decoration: underline;
}

/* Passport addon as its own ws-rev section (#505 panel) — a dim bordered
   card matching INFO, with the save pip floated top-right (invisible until
   it flashes on save). */
.ws-rev-passport {
  position: relative;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-dim);
}

.ws-rev-passport__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
}

.ws-rev-passport__pip {
  position: absolute;
  top: var(--space-3);
  right: var(--space-4);
}

/* Design typography: check rows 12px (prow titles), date values 13px
   (sb-date) — the global quality-checks/form-control sizes differ. */
.ws-rev-passport .quality-checks-item {
  font-size: var(--font-xs);
}

.ws-rev-passport .form-control--sm {
  font-size: var(--font-sm);
}

/* Rule flags (#1038) — the non-pass core/rules verdicts under the check rows,
   one sentence + the cited rule ID (a .tag chip). A fail takes its severity's
   colour; unknown reads muted — "not set", never a pass. Same list rhythm as
   the digest flags above; rendered inside both inline.html variants. */
.rule-flags {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: var(--space-2) 0 0;
  border-top: 1px solid var(--color-border-subtle);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rule-flag {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--font-xs);
  line-height: 1.4;
}

.rule-flag__text {
  color: var(--color-text);
}

.rule-flag--blocking .rule-flag__text {
  color: var(--color-danger);
}

.rule-flag--warning .rule-flag__text {
  color: var(--color-warning-text);
}

.rule-flag--unknown .rule-flag__text {
  color: var(--color-muted);
}

.rule-flag__id {
  flex-shrink: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ── AI suggestion (#1274) — components/ai_suggestion.html ──────────────────
   One value a model proposed, beside the field it would fill. Marked as AI
   output (rules/security.md §"AI Act") by the sparkles label in the info tone;
   the dashed frame says "not a fact yet". Approve arms after a dwell
   (ai-suggestion.js): unarmed it reads muted, arming fills in, armed takes the
   success tone. The --static variant is the reader card's read-only listing. */
.ai-suggestion {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  margin-top: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border: 1px dashed color-mix(in srgb, var(--color-info) 45%, transparent);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--color-info) 5%, var(--color-surface));
  font-size: var(--font-xs);
  line-height: var(--line-height-normal);
}

.ai-suggestion.is-busy {
  opacity: var(--opacity-medium);
  pointer-events: none;
}

.ai-suggestion--static {
  margin-top: 0;
  border-style: solid;
  border-color: var(--color-border-subtle);
  background: var(--color-surface);
}

.ai-suggestion__label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-info);
  font-weight: var(--weight-semibold);
  cursor: help;
}

.ai-suggestion__value {
  color: var(--color-text-strong);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.ai-suggestion__sure,
.ai-suggestion__prior {
  color: var(--color-muted);
}

.ai-suggestion--unsure .ai-suggestion__sure {
  color: var(--color-warning-text);
}

/* The tooltip's words for a keyboard and a screen reader: the approve and
   reject buttons' aria-describedby. Hidden on screen (the `title` carries it
   for the mouse) — the .insights-chart__caption recipe, as there is no global
   visually-hidden utility. */
.ai-suggestion__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.ai-suggestion__actions {
  display: inline-flex;
  gap: var(--space-1);
  margin-left: auto;
}

.ai-suggestion__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0 var(--space-2);
  min-height: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  cursor: pointer;
  transition:
    background var(--transition-fast),
    border-color var(--transition-fast),
    color var(--transition-fast);
}

.ai-suggestion__btn:hover {
  background: var(--color-hover);
}

.ai-suggestion__btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* ``aria-disabled`` is the keyboard's arming state only — a click approves at
   once — so the unarmed button reads as a normal button, not a disabled one,
   and a pointer on it shows it ready at once (``is-pointer-ready``). */
.ai-suggestion__btn--approve.is-arming {
  border-color: color-mix(in srgb, var(--color-success) 35%, var(--color-border));
  transition: border-color var(--transition-slower);
}

.ai-suggestion__btn--approve[aria-disabled="false"],
.ai-suggestion__btn--approve.is-pointer-ready {
  color: var(--color-success);
  border-color: color-mix(in srgb, var(--color-success) 55%, var(--color-border));
}

.ai-suggestion__btn--approve.is-unarmed {
  border-color: var(--color-warning);
}

.ai-suggestion__btn--reject:hover {
  color: var(--color-danger);
}

/* The check chip sits on its own line under the check row it fills. */
.quality-checks-suggestion {
  margin: 0 0 var(--space-1);
}

/* Reject dialog — the appConfirm shell (dialogs.js) around the chip's
   <template>: reasons, an optional right value, an optional note. */
.ai-reject {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.ai-reject__what {
  margin: 0;
  font-size: var(--font-sm);
}

.ai-reject__reasons {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  border: 0;
}

.ai-reject__reason {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-sm);
  cursor: pointer;
}

.ai-reject__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
}

.ai-reject__optional {
  color: var(--color-muted);
  font-weight: var(--weight-normal);
}

.ai-reject__count {
  margin: 0;
  text-align: right;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.ai-reject__error {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-danger);
}

.ai-reject__foot {
  justify-content: flex-end;
}

/* ── Passport reader card (#1274) — addons/passport_reader/card.html ────────
   The requirement pane's summary of the reader, for its audience only: the
   run state, the pending values (read-only chips), Read passport / Read again
   and the rule flags. Loaded lazily into its slot. */
.passport-reader-slot {
  margin-top: var(--space-3);
}

/* The state line is the panel's secondary copy (.ws-panel-hint's colour):
   the tag above says the state, the sparkle and the fold headings below carry
   the emphasis — dark body text here outshouted both (Rasmus, 2026-10-12). */
.passport-reader__state {
  margin: 0 0 var(--space-2);
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.passport-reader__code {
  color: var(--color-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.passport-reader__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  padding: 0;
  list-style: none;
}

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

.passport-reader__place {
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

.passport-reader__hint {
  margin: 0 0 var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.passport-reader__read {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

/* The AI sparkle beside the card's state line. While the passport is in line
   for the reader, and while it is read, it breathes (ai-breathe, below — the
   presence dot's rhythm, .sb-presence) so a passport never reads as forgotten;
   still under reduced motion. The tray's follow-up line breathes with the same
   keyframes. */
.passport-reader__sparkle {
  display: inline-flex;
  margin-right: var(--space-1);
  color: var(--color-accent);
  vertical-align: -2px;
}

.passport-reader__sparkle--moving {
  animation: ai-breathe 1.6s ease-in-out infinite;
}

@keyframes ai-breathe {
  0%, 100% { transform: scale(1);    opacity: 1; }
  50%      { transform: scale(1.25); opacity: 0.55; }
}

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

/* The document's work left, beside its title in the case header
   (partials/ws_overview_chip.html, 2026-10-12): the platform's sparkle, the
   counts, and the AI label only beside what a model produced. A pill button
   at control height; while a read runs it takes the accent tint and the
   sparkle breathes with the reader card's rhythm (ai-breathe, above). With
   no work left it rests in the muted text colour — it is the way back to the
   overview, so it never goes, and the sparkle stays as its identity
   (2026-10-13). The resting tick is muted, not green: "nothing to review"
   is not a pass. */
.ws-overview-chip-slot {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: var(--space-2);
}

.ws-overview-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--control-h);
  padding: 0 var(--space-3) 0 var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-size: var(--font-sm);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--transition-normal), border-color var(--transition-normal),
    color var(--transition-normal);
}

.ws-overview-chip:hover {
  background: var(--color-hover);
}

.ws-overview-chip:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.ws-overview-chip__mark {
  display: inline-flex;
  color: var(--color-accent);
}

.ws-overview-chip__sep {
  color: var(--color-muted);
}

.ws-overview-chip--working {
  border-color: var(--color-accent-200);
  background: var(--color-accent-50);
}

.ws-overview-chip--working .ws-overview-chip__mark {
  animation: ai-breathe 1.6s ease-in-out infinite;
}

.ws-overview-chip--resting {
  color: var(--color-muted);
}

.ws-overview-chip--resting:hover {
  color: var(--color-text);
}

.ws-overview-chip__tick {
  display: inline-flex;
  color: var(--color-muted);
}

@media (prefers-reduced-motion: reduce) {
  .ws-overview-chip--working .ws-overview-chip__mark {
    animation: none;
  }
}

/* The card's two lists — the MRZ's reads, then the page numbers — each fold
   behind the panel's Show all (.ws-rev-fold), its box and dashed rule taken
   off here. The heading is the summary: the AI sparkle, the list's name in
   the fold title's strong text, and its count quieter beside it. */
.passport-reader__fold {
  margin: 0 0 var(--space-2);
}

.passport-reader__fold.ws-rev-fold {
  border: 0;
  background: none;
}

.passport-reader__fold .ws-rev-fold__head {
  padding: 0 0 var(--space-1);
  border-bottom: 0;
}

.passport-reader__fold-title {
  align-items: center;
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
}

.passport-reader__group-count {
  margin-left: var(--space-1);
  font-weight: var(--weight-normal);
  color: var(--color-muted);
}

.passport-reader__fold:not(.ws-rev-fold--open) .passport-reader__fold-body {
  display: none;
}

/* The passport's own reads, under the read buttons (2026-10-15): one entry
   per run — when and what kind, then what came of it, quieter — the entries
   spaced wider than their two lines, so each run reads as one; a failure's
   result in the danger colour. */
.passport-reader__history {
  margin-top: var(--space-3);
}

.passport-reader__history .passport-reader__list {
  gap: var(--space-3);
}

.passport-reader__read-line {
  display: flex;
  flex-direction: column;
  font-size: var(--font-xs);
}

.passport-reader__read-when {
  color: var(--color-text);
}

.passport-reader__read-result {
  color: var(--color-muted);
}

.passport-reader__read-line--failed .passport-reader__read-result {
  color: var(--color-danger);
}

/* The admin's way to the review of every read (Engine Room). */
.passport-reader__review {
  display: inline-block;
  margin-top: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-accent);
}

/* Read passport / Read again beside Read ID page. */
.passport-reader__reads {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* A read in flight (_poll.html): which read, which pass, the page, and a thin
   bar — the transfers tray's bar, the same tokens, a sibling of its own
   because the tray's is BEM-scoped to the tray. */
.passport-reader__poll:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0 0 var(--space-2);
}

.passport-reader__progress {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.passport-reader__bar {
  display: block;
  height: 3px;
  background: var(--color-hover);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.passport-reader__bar-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--color-accent);
  transition: width var(--transition-normal) linear;
}

/* ── History: render the version chain as flush rows inside one box (like
   INFO), instead of the stacked standalone cards used in the modal. ── */
.ws-rev-history .version-history {
  gap: 0;
  margin: 0;
  padding: 0;
}

.ws-rev-history .card.version-history-card {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: none;
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
}

.ws-rev-history .card.version-history-card:first-child {
  border-top: 0;
}

.ws-rev-history .card.version-history-card.card--active {
  background: color-mix(in srgb, var(--color-accent) 5%, transparent);
}

.ws-rev-history .version-history-expand {
  margin: var(--space-3) var(--space-4) 0;
}

/* Design typography: history-fold badges read at 12px/400 (sb-fold__body
   .sb-badge) — the global .tag--sm (0.68rem) is too small here. */
.ws-rev-history .tag {
  font-size: var(--font-xs);
}
/* ── end Review sidebar (#505) ───────────────────────────────────────── */

/* ── core/fragments — loading + dirty states ─────────────────────────────
   The runtime (static/js/fragments.js) toggles these:
   - .fragment-pending on the enclosing [data-fragment-surface], or on a
     closer [data-fragment-pending-scope] when the mutation opts into one
     (#667), while a Platform.fragments.request() mutation is in flight —
     armed on an indicator delay (~300 ms), so fast mutations never flash it
   - [data-fragment-dirty] on a region the active-edit guard skipped —
     it re-applies on focusout, so the cue is deliberately quiet */

[data-fragment-surface].fragment-pending,
[data-fragment-pending-scope].fragment-pending {
  opacity: 0.7;
  transition: opacity var(--transition-normal) ease;
}

[data-fragment-dirty] {
  outline: 1px dashed var(--color-border);
  outline-offset: 2px;
}

/* ── Credential register (Engine Room → /engine/credentials) ──────────────── */

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

.credential-register__intro {
  margin: 0 0 var(--space-5);
  max-width: 60ch;
}

.credential-register__summary {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.cred-summary-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.cred-summary-card__count {
  font-size: var(--font-lg);
  font-weight: var(--weight-bold);
  color: var(--color-text-strong);
}

.cred-summary-card__label {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.cred-summary-card--overdue {
  border-color: color-mix(in srgb, var(--color-danger) 40%, transparent);
  background: color-mix(in srgb, var(--color-danger) 8%, var(--color-surface));
}

.cred-summary-card--overdue .cred-summary-card__count {
  color: var(--color-danger);
}

.cred-summary-card--soon {
  border-color: color-mix(in srgb, var(--color-warning) 45%, transparent);
  background: color-mix(in srgb, var(--color-warning) 12%, var(--color-surface));
}

.cred-summary-card--soon .cred-summary-card__count {
  color: var(--color-warning-text);
}

.cred-summary-card--ok .cred-summary-card__count {
  color: var(--color-success);
}

.credential-register__create {
  margin-top: var(--space-5);
}

.cred-pill {
  display: inline-block;
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  padding: 0.2rem 0.5rem;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

.cred-pill--ok {
  background: color-mix(in srgb, var(--color-success) 15%, transparent);
  color: var(--color-success);
}

.cred-pill--soon {
  background: color-mix(in srgb, var(--color-warning) 18%, transparent);
  color: var(--color-warning-text);
}

.cred-pill--overdue {
  background: color-mix(in srgb, var(--color-danger) 15%, transparent);
  color: var(--color-danger);
}

.cred-pill--none,
.cred-pill--inactive {
  background: color-mix(in srgb, var(--color-muted) 14%, transparent);
  color: var(--color-muted);
}

.cred-name {
  font-weight: var(--weight-semibold);
}

.cred-slug {
  display: block;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.cred-row--archived {
  opacity: 0.55;
}

.cred-actions > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
}

.cred-actions > summary::-webkit-details-marker {
  display: none;
}

.cred-actions__panel {
  margin-top: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  background: var(--color-surface-dim);
  text-align: left;
}

.cred-form {
  display: grid;
  gap: var(--space-3);
}

.cred-form__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}

.cred-actions__secondary {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
}

@media (max-width: 700px) {
  .credential-register__summary {
    grid-template-columns: repeat(2, 1fr);
  }

  .cred-form__grid {
    grid-template-columns: 1fr;
  }
}

/* ── People section — case-overview sidebar (.ps-*) ─────────────────────────
   Ported from the Claude Design export (case-people-sidebar): one line per
   person `role / name` + portal dot + ⋯, a rich kebab menu (contact block,
   Edit / Change-role submenu / Remove), a styled remove confirm, empty-party
   slots, the "+ Add person" launcher, and the 320px Add/Edit dialogs. Raw
   export values are mapped to existing tokens — no new tokens. Kebab and
   role-select mechanics reuse the platform mh-picker <details> pattern; only
   the content styling is the design's. See partials/people_sidebar.html. */
.ps-gap .fr-section-count {
  color: var(--color-warning-text);
  font-weight: var(--weight-semibold);
}

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

/* one-line row: role / name · linked dot · ⋯ */
.ps-idline {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
}

.ps-idline[hidden] {
  display: none;
}

/* leading user icon — mirrors the control rows' .detail-field-ico */
.ps-person-ico {
  flex-shrink: 0;
  display: inline-flex;
  width: 14px;
  height: 14px;
  color: var(--color-muted);
}

/* role + name share the control rows' .detail-field-label type exactly */
.ps-role-tag,
.ps-name {
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  letter-spacing: 0.02em;
  line-height: 16px;
  white-space: nowrap;
}

.ps-role-tag {
  flex-shrink: 0;
}

.ps-dot-sep {
  flex-shrink: 0;
  color: var(--color-muted);
  opacity: 0.55;
}

.ps-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The name became the route to the person's profile. It keeps .ps-name's
   colour and weight so the rail reads the same as before — the affordance is
   the hover, not a permanently blue link in a dense list.
   Deliberately no `color` here: the anchor carries both classes, this rule is
   later in the file, so declaring one (even `inherit`) would win on source
   order and drop .ps-name's muted colour. Author rules already beat the UA
   link colour without it. */
.ps-name-link {
  text-decoration: none;
  cursor: pointer;
}

.ps-name-link:hover,
.ps-name-link:focus-visible {
  color: var(--color-text);
  text-decoration: underline;
}

.ps-idline-right {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

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

/* kebab trigger — quiet until the row is hovered or the menu is open */
.ps-kebab > summary {
  width: 20px;
  height: 20px;
  padding: 0;
  gap: 0;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--color-muted);
  opacity: 0.5;
  transition: opacity var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}

.ps-person:hover .ps-kebab > summary,
.ps-kebab[open] > summary {
  opacity: 1;
}

.ps-kebab > summary:hover {
  background: var(--color-surface-muted);
  color: var(--color-text);
  border-color: transparent;
}

.ps-kebab > summary svg {
  width: 15px;
  height: 15px;
}

/* kebab menu content — contact block, then actions */
.ps-menu {
  min-width: 216px;
  padding: var(--space-1);
}

.ps-menu-person {
  padding: var(--space-3) var(--space-3) var(--space-2);
}

.ps-menu-person-name {
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
  margin-bottom: var(--space-2);
}

.ps-menu-info {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.ps-menu-line {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-subtle);
  min-width: 0;
}

.ps-menu-line svg {
  width: 12px;
  height: 12px;
  color: var(--color-muted);
  flex-shrink: 0;
}

.ps-menu-line span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ps-menu-line--linked,
.ps-menu-line--linked svg {
  color: var(--color-success);
}

.ps-menu-line--muted,
.ps-menu-line--muted svg {
  color: var(--color-muted);
}

.ps-menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font-family: inherit;
  font-size: var(--font-xs);
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
  /* The menu holds one <a> ("Open profile") among the buttons — without this
     it alone would carry the browser's default underline. */
  text-decoration: none;
}

.ps-menu-item:hover {
  background: var(--color-hover);
}

.ps-menu-item .mi-ico {
  width: 14px;
  height: 14px;
  color: var(--color-muted);
  display: inline-flex;
  flex-shrink: 0;
}

.ps-menu-item .mi-ico svg {
  width: 14px;
  height: 14px;
}

.ps-menu-item .mi-check {
  margin-left: auto;
  width: 13px;
  height: 13px;
  color: var(--color-accent);
  display: none;
}

.ps-menu-item .mi-check svg {
  width: 13px;
  height: 13px;
}

.ps-menu-item.is-active {
  color: var(--color-text-strong);
  background: color-mix(in srgb, var(--color-accent) 5%, transparent);
}

.ps-menu-item.is-active .mi-check {
  display: inline-flex;
}

.ps-menu-item.is-danger {
  color: var(--color-danger);
}

.ps-menu-item.is-danger .mi-ico {
  color: var(--color-danger);
}

.ps-menu-item.is-danger:hover {
  background: color-mix(in srgb, var(--color-danger) 9%, transparent);
}

.ps-menu-divider {
  height: 1px;
  background: var(--color-border-subtle);
  margin: var(--space-1) var(--space-2);
}

.ps-menu-group {
  font-size: var(--font-xxs);
  color: var(--color-muted);
  padding: var(--space-2) var(--space-3) var(--space-1);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* inline remove confirm — replaces the row line */
.ps-confirm {
  margin-top: var(--space-2);
  margin-bottom: var(--space-2);
  padding: var(--space-3);
  border: 1px solid color-mix(in srgb, var(--color-danger) 30%, var(--color-border));
  background: color-mix(in srgb, var(--color-danger) 4%, var(--color-surface));
  border-radius: var(--radius-sm);
}

.ps-confirm[hidden] {
  display: none;
}

.ps-confirm-text {
  font-size: var(--font-xs);
  color: var(--color-subtle);
  margin-bottom: var(--space-3);
}

.ps-confirm-text strong {
  color: var(--color-text-strong);
  font-weight: var(--weight-semibold);
}

.ps-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* add / edit form (inside the 320px dialog) */
.ps-dialog {
  width: 320px;
  overflow: visible;
}

/* Let the role-select dropdown escape the dialog — the platform modal-body
   scrolls (overflow-y: auto), which would clip the open menu. Mirrors the
   design's .ps-modal/.ps-modal-body overflow: visible. Scoped to .ps-dialog
   so long-content modals elsewhere keep scrolling. */
.ps-dialog .modal-body {
  overflow: visible;
}

.ps-form {
  padding: 0;
}

.ps-field-row {
  position: relative;
  margin-bottom: var(--space-2);
}

.ps-field {
  width: 100%;
  height: 28px;
  font-size: var(--font-xs);
  font-family: inherit;
  padding: 0 var(--space-3);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  transition: border-color var(--transition-normal), box-shadow var(--transition-normal);
}

.ps-field::placeholder {
  color: var(--color-muted);
}

.ps-field:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.ps-field[readonly] {
  background: var(--color-surface-dim);
  color: var(--color-subtle);
}

.ps-field--search {
  padding-left: 2rem;
}

.ps-field-row .ps-search-ico {
  position: absolute;
  left: 0.55rem;
  top: 7px;
  width: 14px;
  height: 14px;
  color: var(--color-muted);
  pointer-events: none;
}

.ps-field-row .ps-search-ico svg {
  width: 14px;
  height: 14px;
}

/* role select — mh-picker <details> with the design's bordered trigger */
.ps-form-role {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-2) 0;
}

.ps-form-role-label {
  font-size: var(--font-xs);
  color: var(--color-muted);
  flex-shrink: 0;
}

.ps-role-select {
  position: relative;
  flex: 1;
  min-width: 0;
  display: block;
}

.ps-role-select > summary.ps-role-trigger {
  width: 100%;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 0 var(--space-3);
  font-family: inherit;
  font-size: var(--font-xs);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  cursor: pointer;
}

.ps-role-select > summary.ps-role-trigger.is-placeholder {
  color: var(--color-muted);
}

.ps-role-select > summary.ps-role-trigger:hover {
  border-color: var(--color-muted);
  background: var(--color-surface);
}

.ps-role-trigger .chev {
  width: 12px;
  height: 12px;
  opacity: 0.6;
  flex-shrink: 0;
  display: inline-flex;
}

.ps-role-trigger .chev svg {
  width: 12px;
  height: 12px;
}

.ps-select-menu {
  display: none;
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 0;
  right: 0;
  z-index: var(--z-dropdown);
  flex-direction: column;
  max-height: 220px;
  overflow-y: auto;
  background: var(--color-surface);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--space-1);
}

.ps-role-select[open] > .ps-select-menu {
  display: flex;
}

.ps-form-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* selected-existing-person confirmation strip */
.ps-selected {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  font-size: var(--font-xs);
  color: var(--color-subtle);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
  background: color-mix(in srgb, var(--color-accent) 5%, transparent);
  border-radius: var(--radius-sm);
}

.ps-selected strong {
  color: var(--color-text-strong);
  font-weight: var(--weight-semibold);
}

.ps-selected .ps-selected-clear {
  margin-left: auto;
  border: 0;
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* typeahead results */
.ps-results {
  list-style: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  margin: var(--space-2) 0;
  padding: 0;
  overflow: hidden;
}

.ps-result {
  border-bottom: 1px solid var(--color-border-subtle);
}

.ps-result:last-child {
  border-bottom: 0;
}

.ps-result-btn {
  display: flex;
  width: 100%;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: transparent;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

.ps-result-btn:hover {
  background: color-mix(in srgb, var(--color-accent) 5%, transparent);
}

.ps-result-name {
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-text);
}

.ps-result-email {
  font-size: var(--font-xxs);
  color: var(--color-muted);
}

.ps-result-linked {
  margin-left: auto;
}

.ps-results-empty {
  font-size: var(--font-xs);
  color: var(--color-muted);
  padding: var(--space-2) 0 0;
}

/* empty-party slot + "+ Add person" launcher */
.ps-person--empty .ps-empty-add {
  border: 0;
  background: transparent;
  font-family: inherit;
  cursor: pointer;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-accent);
  padding: 0;
}

.ps-person--empty .ps-empty-add:hover {
  color: var(--color-accent-hover);
}

/* "+ Add person" launcher — the standard ghost button, stretched to the
   column so it still reads as the section's add row. */
.ps-add-row {
  margin-top: var(--space-3);
}

.ps-add-row .btn {
  width: 100%;
  justify-content: center;
}

/* ==========================================================================
   Empty-state animations — the anim_<slug> component family
   Soft indigo icons for surfaces with nothing to show yet, imported from
   Claude Design via /anim-import (rules/design-system.md § Animated SVGs).
   Shared anatomy: a rounded container tile (--color-anim-surface) holding
   idle shapes (--color-anim-shape); the accent (--color-accent) lights one
   shape at a time (no positional motion — colour only). All colour lives here
   as tokens — the SVG markup carries no colour attributes (svg-raw-color
   lint). Each animation is self-contained: its own classes, keyframes and
   reduced-motion freeze, so deleting one deletes cleanly.
   Sizing: width 100% capped by max-width per size; the SVG scales via
   viewBox. Markup: components/anim_<slug>.html.
   ========================================================================== */

/* ── Empty folder — 2×2 grid, accent circles the cells ──────────────────── */
.anim-empty-folder {
  display: block;
  width: 100%;
  max-width: 76px;
  height: auto;
  margin: 0 auto;
  opacity: var(--opacity-anim);
}

.anim-empty-folder--sm {
  max-width: 56px;
}

.anim-empty-folder--lg {
  max-width: 110px;
}

.anim-empty-folder__container {
  fill: var(--color-anim-surface);
}

.anim-empty-folder__cell {
  fill: var(--color-anim-shape);
  animation: anim-empty-folder-orbit 8s ease-in-out infinite;
}

.anim-empty-folder__cell--tl {
  animation-delay: 0s;
}

.anim-empty-folder__cell--tr {
  animation-delay: 2s;
}

.anim-empty-folder__cell--br {
  animation-delay: 4s;
}

.anim-empty-folder__cell--bl {
  animation-delay: 6s;
}

@keyframes anim-empty-folder-orbit {
  0%,
  30%,
  100% {
    fill: var(--color-anim-shape);
  }
  12% {
    fill: var(--color-accent);
  }
}

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

/* ── Empty preview — document page, accent runs down the lines ──────────── */
.anim-empty-preview {
  display: block;
  width: 100%;
  max-width: 76px;
  height: auto;
  margin: 0 auto;
  opacity: var(--opacity-anim);
}

.anim-empty-preview--sm {
  max-width: 56px;
}

.anim-empty-preview--lg {
  max-width: 110px;
}

.anim-empty-preview__container {
  fill: var(--color-anim-surface);
}

.anim-empty-preview__page {
  fill: var(--color-anim-paper);
}

.anim-empty-preview__line {
  fill: var(--color-anim-shape);
  animation: anim-empty-preview-orbit 6s ease-in-out infinite;
}

.anim-empty-preview__line--1 {
  animation-delay: 0s;
}

.anim-empty-preview__line--2 {
  animation-delay: 0.6s;
}

.anim-empty-preview__line--3 {
  animation-delay: 1.2s;
}

@keyframes anim-empty-preview-orbit {
  0%,
  30%,
  100% {
    fill: var(--color-anim-shape);
  }
  12% {
    fill: var(--color-accent);
  }
}

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

/* ── Empty list — three rows, accent lights each dot in turn ────────────── */
.anim-empty-list {
  display: block;
  width: 100%;
  max-width: 76px;
  height: auto;
  margin: 0 auto;
  opacity: var(--opacity-anim);
}

.anim-empty-list--sm {
  max-width: 56px;
}

.anim-empty-list--lg {
  max-width: 110px;
}

.anim-empty-list__container {
  fill: var(--color-anim-surface);
}

.anim-empty-list__bar {
  fill: var(--color-anim-shape);
}

.anim-empty-list__dot {
  fill: var(--color-anim-shape);
  animation: anim-empty-list-orbit 6s ease-in-out infinite;
}

.anim-empty-list__dot--1 {
  animation-delay: 0s;
}

.anim-empty-list__dot--2 {
  animation-delay: 0.6s;
}

.anim-empty-list__dot--3 {
  animation-delay: 1.2s;
}

@keyframes anim-empty-list-orbit {
  0%,
  30%,
  100% {
    fill: var(--color-anim-shape);
  }
  12% {
    fill: var(--color-accent);
  }
}

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

/* ── Empty queue — fanned page stack, accent lights each page ───────────── */
.anim-empty-queue {
  display: block;
  width: 100%;
  max-width: 76px;
  height: auto;
  margin: 0 auto;
  opacity: var(--opacity-anim);
}

.anim-empty-queue--sm {
  max-width: 56px;
}

.anim-empty-queue--lg {
  max-width: 110px;
}

.anim-empty-queue__container {
  fill: var(--color-anim-surface);
}

/* Pages are outlined in the container tint so the fan reads as separate
   sheets against it. */
.anim-empty-queue__page {
  fill: var(--color-anim-shape);
  stroke: var(--color-anim-surface);
  animation: anim-empty-queue-orbit 6s ease-in-out infinite;
}

.anim-empty-queue__page--1 {
  animation-delay: 0s;
}

.anim-empty-queue__page--2 {
  animation-delay: 0.6s;
}

.anim-empty-queue__page--3 {
  animation-delay: 1.2s;
}

@keyframes anim-empty-queue-orbit {
  0%,
  30%,
  100% {
    fill: var(--color-anim-shape);
  }
  12% {
    fill: var(--color-accent);
  }
}

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

/* ── All done — checkmark, accent sweeps the stroke ─────────────────────── */
.anim-all-done {
  display: block;
  width: 100%;
  max-width: 76px;
  height: auto;
  margin: 0 auto;
  opacity: var(--opacity-anim);
}

.anim-all-done--sm {
  max-width: 56px;
}

.anim-all-done--lg {
  max-width: 110px;
}

.anim-all-done__container {
  fill: var(--color-anim-surface);
}

.anim-all-done__check {
  stroke: var(--color-anim-shape);
  animation: anim-all-done-orbit 6s ease-in-out infinite;
}

@keyframes anim-all-done-orbit {
  0%,
  30%,
  100% {
    stroke: var(--color-anim-shape);
  }
  12% {
    stroke: var(--color-accent);
  }
}

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

/* ── core/editor — inline-WYSIWYG markdown editor (#835) ────────────────────
   Thin chrome around a vendored ProseMirror surface. Toolbar reuses the
   page-composer-toolbar-btn base with width relaxed from the square composer
   default — a holdover from the text labels #839 replaced with icons; the
   editor's design round decides whether the buttons go back to square. */
.pm-editor {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.pm-editor:focus-within {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.pm-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border-subtle);
}

.pm-tb-btn {
  width: auto;
  min-width: var(--control-h);
  padding: 0 var(--space-2);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
}

.pm-tb-btn.is-active {
  background: var(--color-accent-50);
  color: var(--color-accent);
}

.pm-tb-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.pm-tb-sep {
  align-self: stretch;
  width: 1px;
  margin: var(--space-1) var(--space-1);
  background: var(--color-border-subtle);
}

.pm-editor-surface {
  flex: 1 1 auto;
}

.pm-editor-surface [contenteditable="true"] {
  min-height: 12rem;
  padding: var(--space-4);
  outline: none;
  color: var(--color-text);
  line-height: var(--line-height-relaxed);
}

.pm-editor-surface [contenteditable="true"] > * + * {
  margin-top: var(--space-3);
}

.pm-editor-surface [contenteditable="true"] h2 {
  font-size: var(--font-lg);
  font-weight: var(--weight-bold);
}

.pm-editor-surface [contenteditable="true"] h3 {
  font-size: var(--font-base);
  font-weight: var(--weight-bold);
}

.pm-editor-surface [contenteditable="true"] ul,
.pm-editor-surface [contenteditable="true"] ol {
  padding-left: var(--space-6);
}

.pm-editor-surface [contenteditable="true"] li {
  margin-top: var(--space-1);
}

.pm-editor-surface [contenteditable="true"] blockquote {
  padding-left: var(--space-4);
  border-left: 3px solid var(--color-border);
  color: var(--color-subtle);
}

.pm-editor-surface [contenteditable="true"] code {
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--color-surface-muted);
  font-family: monospace;
  font-size: 0.9em;
}

.pm-editor-surface [contenteditable="true"] pre {
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--color-surface-muted);
  overflow-x: auto;
  font-family: monospace;
}

.pm-editor-surface [contenteditable="true"] pre code {
  padding: 0;
  background: none;
}

.pm-editor-surface [contenteditable="true"] a {
  color: var(--color-accent);
  text-decoration: underline;
}

/* Inline images (#838): the stored display copy is ≤1600px, so cap to the
   column and keep the aspect. Block-level so an image paragraph reads as a
   figure, not a glyph in a line. */
.pm-editor-surface [contenteditable="true"] img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-sm);
}

/* The upload placeholder — a blob preview held at the insertion point until the
   stored URL comes back; dimmed so it reads as "not there yet". */
.pm-image-uploading {
  display: block;
  opacity: 0.5;
}

.pm-image-uploading img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-sm);
}

/* ProseMirror renders the placeholder as a widget decoration, which means
   `contenteditable="false"` on the span AND its trailing <br> appended anyway —
   a widget is not text, so the block still counts as empty. Both facts are load-
   bearing here. The false-value span is why the surface rules above had to say
   `[contenteditable="true"]`: a bare `[contenteditable]` matches any value, so
   `.pm-editor-surface [contenteditable]` (0,2,0) outranked this rule (0,1,0) and
   painted the hint in --color-text, and handed it the editor's own padding and
   min-height. And the <br> is why the placeholder is out of flow: in flow the two
   share the paragraph and the resting composer stands two lines tall. Absolute
   against the paragraph (not the editor) so a long hint wraps inside the box. */
.pm-editor-surface [contenteditable="true"] p:has(> .pm-placeholder) {
  position: relative;
}

.pm-placeholder {
  position: absolute;
  top: 0;
  left: 0;
  color: var(--color-text-lavender-grey);
  pointer-events: none;
}

/* The hint has done its job once the operator is in the field — it goes on focus
   rather than waiting for the first keystroke. */
.pm-editor-surface [contenteditable="true"]:focus .pm-placeholder {
  display: none;
}

.pm-editor-status {
  min-height: 1.5em;
  padding: var(--space-2) var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
  color: var(--color-muted);
  font-size: var(--font-xs);
}

.pm-editor-status[data-state="saved"] {
  color: var(--color-success);
}

.pm-editor-status[data-state="unsaved"] {
  color: var(--color-warning-text);
}

.pm-editor-status[data-state="error"] {
  color: var(--color-danger);
}

.pm-link-wrap {
  position: relative;
  display: inline-flex;
}

.pm-link-popover {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  z-index: var(--z-dropdown);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  box-shadow: var(--shadow-md);
}

.pm-link-popover[hidden] {
  display: none;
}

.pm-link-input {
  width: 16rem;
}

.pm-link-apply,
.pm-link-remove {
  width: auto;
  padding: 0 var(--space-3);
  font-size: var(--font-xs);
}

/* ── core/editor Phase B (#836): consumer sizing + tables ────────────────── */

/* Comment composer: the editor blends into the composer input area — no card
   chrome, small resting height (the 12rem Library default would be huge here).
   Its toolbar renders into .page-composer-toolbar, so the host has no .pm-toolbar. */
.page-composer-editor.pm-editor {
  border: none;
  border-radius: 0;
  background: transparent;
}

.page-composer-editor.pm-editor:focus-within {
  box-shadow: none;
}

/* The editor replaced .page-composer-textarea as the visible input (#836), so
   it has to carry that textarea's box itself: nothing between it and the
   composer border supplies an inset (.page-composer and .composer-wrap are
   both padding-0), and with no font-size it fell back to the inherited body
   12px — so the surface rendered flush on the border, a size down from the
   textarea it replaced. Mirrors .page-composer-textarea, expanded
   padding-bottom included. */
.page-composer-editor .pm-editor-surface [contenteditable="true"] {
  min-height: 1.5rem;
  padding: var(--space-4) var(--space-5);
  font-size: var(--font-base);
  line-height: var(--line-height-relaxed);
}

.page-composer--expanded .page-composer-editor .pm-editor-surface [contenteditable="true"] {
  padding-bottom: var(--space-3);
}

/* Attachment page: standalone editor in a card — modest resting height. */
.attachment-page-editor .pm-editor-surface [contenteditable="true"] {
  min-height: 4rem;
}

/* Tables (prosemirror-tables). Element + kebab selectors only — the library's
   own camelCase classes (.selectedCell/.tableWrapper) are left to defaults so
   the kebab-case CSS lint stays clean. */
.pm-editor-surface [contenteditable="true"] table {
  width: auto;
  margin: var(--space-2) 0;
  border-collapse: collapse;
}

.pm-editor-surface [contenteditable="true"] td,
.pm-editor-surface [contenteditable="true"] th {
  min-width: 3rem;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  vertical-align: top;
}

.pm-editor-surface [contenteditable="true"] th {
  background: var(--color-surface-muted);
  font-weight: var(--weight-semibold);
  text-align: left;
}

.pm-editor-surface [contenteditable="true"] .column-resize-handle {
  position: absolute;
  top: 0;
  right: -2px;
  bottom: 0;
  width: 3px;
  background: var(--color-accent);
  pointer-events: none;
}


/* ── Inbox (#1079) — three panes: nav panel · thread list · thread ───────── */

/* The list is a plain list-view host; the pane sits beside it. The host's
   height is the framework's (calc fallback, then JS-measured), and the pane
   takes the same box so the two columns scroll independently. */
/* Second consumer (#1127): the phase boards' case pane — `.board-layout` /
   `.board-pane`, same grid, same collapse, wired by static/js/board_pane.js. */
.inbox-layout,
.board-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
  align-items: stretch;
}

.inbox-layout--open,
.board-layout--open {
  grid-template-columns: minmax(0, 5fr) minmax(360px, 4fr);
}

.inbox-pane,
.board-pane {
  display: none;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  /* The pane takes the list's box, not its own guess: the grid row is the
     list host's JS-measured height (list_view_layout.js setHostHeight), and
     the pane's content is taken out of flow below, so a long thread can
     never grow the row and the pane always ends where the list ends. */
  position: relative;
  height: auto;
  overflow: hidden;
}

.inbox-layout--open .inbox-pane,
.board-layout--open .board-pane {
  display: flex;
}

/* Pane toggle (#1081) — the docs detail-panel collapse on the third pane:
   the list reflows to the full width, the selection stays. A row click
   reveals (inbox.html clears the stored collapse). */
.inbox-layout--open.inbox-layout--pane-collapsed {
  grid-template-columns: minmax(0, 1fr);
}

.inbox-layout--pane-collapsed .inbox-pane {
  display: none;
}

/* Archived (#1081): the head's tag says so; the archive button sits with the
   other pane tools and swaps the pane on success. */
.inbox-pane-archived {
  white-space: nowrap;
}

.inbox-pane-body {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
}

.inbox-pane-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--color-border-subtle);
  flex: 0 0 auto;
  /* A narrow pane wraps the tools under the title rather than breaking the
     case ref across lines. */
  flex-wrap: wrap;
}

.inbox-pane-title {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  flex-wrap: wrap;
}

/* Filter + open-case beside the title: the overview's segmented filter on
   the same mechanism (comments.js), minus System. */
.inbox-pane-tools {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 0 0 auto;
}

.inbox-pane-case {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  color: var(--color-text);
  text-decoration: none;
  font-weight: var(--weight-semibold);
  min-width: 0;
}

.inbox-pane-case:hover .inbox-pane-couple {
  text-decoration: underline;
}

.inbox-pane-couple {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inbox-pane-case .case-ref {
  white-space: nowrap;
}

/* The thread scrolls inside the pane; the composer rides at the bottom of
   the same scroll box, as it does on the case overview. */
.inbox-pane-thread {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  /* No padding on the scroll box itself: the pinned strip inside is sticky,
     and a sticky element pins to the scroll container's padding edge, so any
     padding here is air above the strip that no negative margin can cancel.
     The gutter lives on the timeline instead, exactly as the case overview
     does it (`.case-detail-page .timeline`), and the strip's negative margins
     cancel that. */
  padding: 0;
}

.inbox-pane-thread .timeline {
  padding: var(--space-ws-gutter);
}

/* The composer is the pane's footer: pinned under the scrolling thread, with
   the same gutter the thread has, and a hairline to say where the feed ends. */
.inbox-pane-composer {
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-ws-gutter) var(--space-ws-gutter);
  border-top: 1px solid var(--color-border-subtle);
  background: var(--color-surface);
}

.inbox-pane-empty,
.board-pane-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 100%;
  padding: var(--space-8);
  color: var(--color-muted);
  text-align: center;
}

.inbox-pane-empty-title,
.board-pane-empty-title {
  font-weight: var(--weight-medium);
  color: var(--color-subtle);
}

.inbox-pane-empty-msg,
.board-pane-empty-msg {
  font-size: var(--font-xs);
  max-width: 32ch;
}

/* Rows: an unanswered thread reads as waiting; the open one as selected. */
.inbox-row--unanswered td[data-col-key="preview"],
.inbox-row--unanswered td[data-col-key="case"] {
  font-weight: var(--weight-medium);
}

.lv-host[data-lv-layout] .data-table tr.inbox-row.is-active,
.board-layout .lv-host[data-lv-layout] .data-table tr.clickable-row.is-active {
  background: var(--color-surface-muted);
  /* Opaque twin for a pinned cell (§"Pinned columns"). */
  --lv-pin-bg: var(--color-surface-muted);
}

.inbox-author {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
}

.inbox-author-kind {
  font-size: var(--font-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-muted);
}

.inbox-author--couple .inbox-author-kind {
  color: var(--color-accent);
}

.inbox-author-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inbox-preview {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-subtle);
}

.inbox-age {
  color: var(--color-muted);
  white-space: nowrap;
}

/* ── The No case lane (#1086): WhatsApp conversations no case has claimed.
   The row names the sender (profile name over the number); the pane lists
   the run read-only, with the attach picker as its footer. ─────────────── */
.inbox-sender {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
}

.inbox-sender-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inbox-sender-handle {
  font-size: var(--font-xs);
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  white-space: nowrap;
}

.inbox-preview--kind {
  font-style: italic;
  color: var(--color-muted);
}

.inbox-count {
  font-variant-numeric: tabular-nums;
  color: var(--color-subtle);
}

.no-case-messages {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
  padding: var(--space-ws-gutter);
  list-style: none;
}

.no-case-message {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-width: 64ch;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-lg);
  background: var(--color-surface-muted);
}

.no-case-message__meta {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.no-case-message__body {
  font-size: var(--font-sm);
  color: var(--color-text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.no-case-message__body--none {
  font-style: italic;
  color: var(--color-muted);
}

.no-case-done {
  display: flex;
  align-items: center;
  justify-content: center;
}

.no-case-attach {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.no-case-attach__label {
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}

.no-case-attach .picker-list:empty {
  display: none;
}

.no-case-attach__none {
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.no-case-attach__actions {
  display: flex;
  justify-content: flex-end;
}

/* An answered intake form whose email already belongs to a case (#1090):
   the match named under the answers, with the one-click attach beside it. */
.no-case-answer-match {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.no-case-answer-match__text {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-text);
}

/* Create lead under the attach (WhatsApp task 9): email and name side by
   side, the button on their baseline; wraps to a column in a narrow pane. */
.no-case-lead__hint {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.no-case-lead__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-3);
}

.no-case-lead__fields > .form-group {
  flex: 1 1 12rem;
  min-width: 0;
}

.no-case-lead__submit {
  display: flex;
  flex: 0 0 auto;
  align-self: flex-end;
}

/* The case's Front import state (#1092 item 5): pending until Front returns a
   conversation id, failed once the ladder is exhausted. Reads from the
   attempts table, so it can show before any link exists. */
.inbox-front-badge {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 0 var(--space-2);
  max-width: 36ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  border-radius: var(--radius-pill);
}

.inbox-front-badge--pending {
  color: var(--color-warning-text);
  background: color-mix(in srgb, var(--color-warning) 18%, transparent);
}

.inbox-front-badge--failed {
  color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 10%, transparent);
}

/* Engine room → Front → Lead imports: the two numbers that need a person. */
.front-imports-summary {
  padding: 0 var(--space-5) var(--space-4);
}

.front-imports-count--stalled {
  color: var(--color-warning-text);
  font-weight: var(--weight-semibold);
}

.front-imports-count--failed {
  color: var(--color-danger);
  font-weight: var(--weight-semibold);
}

.front-imports-failed .front-imports-error {
  max-width: 40ch;
  overflow-wrap: anywhere;
  font-size: var(--font-xs);
  color: var(--color-subtle);
}

/* Engine room → Front → Stage moves: one collapsible group per case type ×
   phase × tier, each holding a stage → inbox picker. Collapsed by default so
   forty-eight stages do not bury the rest of the page; a group that already
   carries a mapping opens itself. */
.stage-inbox-group {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  margin: 0 var(--space-5) var(--space-2);
}

.stage-inbox-group__summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  font-weight: var(--weight-semibold);
}

.stage-inbox-table {
  margin: 0;
}

.stage-inbox-table__stage {
  width: 40%;
}

/* Nav panel counts — the lane's live number, right-aligned like a badge. */
.sb-row__count {
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  padding-right: 2px;
}

.sb-row__count--attention {
  color: var(--color-accent);
  font-weight: var(--weight-semibold);
}

@media (max-width: 1100px) {
  .inbox-layout--open,
  .board-layout--open {
    grid-template-columns: minmax(0, 1fr);
  }

  .inbox-layout--open .inbox-list,
  .board-layout--open .board-list {
    display: none;
  }

  /* With the list hidden nothing sizes the row, and the pane's content is
     out of flow — give it the list's own fallback height. */
  .inbox-layout--open .inbox-pane,
  .board-layout--open .board-pane {
    height: calc(100vh - 11rem);
  }
}

/* ── Phase-board case pane (#1127) — the case inside the board's third column ──
   The column itself (.board-layout / .board-pane / the collapse) shares the
   inbox's rules above. What is here is the pane's content: a head with the
   three facts a row shows, a tab strip, and either the overview's timeline
   column or its rail read-only. The rail's own rules are scoped
   `:is(.case-detail-page, .case-pane-details)` so the pane renders the
   overview's partials the way the page does. */
.case-pane-body {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
}

.case-pane-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--color-border-subtle);
  flex: 0 0 auto;
}

.case-pane-title {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  flex-wrap: wrap;
}

.case-pane-open {
  margin-left: auto;
  white-space: nowrap;
}

.case-pane-case {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  color: var(--color-text);
  text-decoration: none;
  font-weight: var(--weight-semibold);
  min-width: 0;
}

.case-pane-case:hover .case-pane-couple {
  text-decoration: underline;
}

.case-pane-couple {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.case-pane-case .case-ref {
  white-space: nowrap;
}

/* The three facts — stage · assignee · base service — as a compact
   definition list, the row's cells in prose. */
.case-pane-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  margin: 0;
  font-size: var(--font-xs);
}

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

.case-pane-fact dt {
  color: var(--color-muted);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.case-pane-fact dd {
  margin: 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The tab strip + the open tab's body are one region (#board-pane-tab): the
   strip pins, the body takes the rest and scrolls inside. */
.case-pane-tab {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.case-pane-tabs {
  flex: 0 0 auto;
  margin-bottom: 0;
  padding: 0 var(--space-5);
}

/* The strip's tabs are buttons here (a tab click is a fetch, not a
   navigation), so undo the button chrome the anchor never had. */
.case-pane-tabs .tab {
  background: none;
  border-top: 0;
  border-left: 0;
  border-right: 0;
  cursor: pointer;
  font-family: inherit;
}

/* Timeline: the overview's left column, pinned composer under a scrolling
   feed — the page's `.case-detail-page .overview-timeline*` rules restated
   for this host, plus the inbox pane's gutter on the timeline itself. */
.case-pane-timeline {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.case-pane-timeline .overview-timeline {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  position: relative;
  overflow: hidden;
}

.case-pane-timeline .overview-timeline-scroll {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  padding: 0;
}

.case-pane-timeline .timeline {
  padding: var(--space-ws-gutter);
}

/* The timeline column's placeholder while the first GET is in flight —
   shared by the page and the pane (overview_timeline_column.html). */
.overview-timeline-loading {
  padding: var(--space-4);
}

.case-pane-timeline .page-composer {
  flex: 0 0 auto;
  padding: var(--space-3) var(--space-ws-gutter) var(--space-ws-gutter);
  border-top: 1px solid var(--color-border-subtle);
  background: var(--color-surface);
}

/* Details: the rail, scrolling inside the pane. */
.case-pane-details {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-4) var(--space-5) var(--space-ws-gutter);
}

.case-pane-details .field-block--readonly {
  cursor: default;
}

.case-pane-no-fields {
  padding: var(--space-4) 0;
}

/* "Edit on the case page" — the sections the pane shows read-only or not at
   all, each a link into the overview (`?edit=<section>`) or the Drive. */
.case-pane-edit-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
  font-size: var(--font-xs);
}

.case-pane-edit-links-label {
  color: var(--color-muted);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  flex-basis: 100%;
}

.case-pane-edit-links a {
  color: var(--color-text);
}

/* A rail section head's "Edit" link (fr_section_head action_href). */
.fr-section-action {
  margin-left: auto;
  font-size: var(--font-xxs);
  font-weight: var(--weight-medium);
  text-transform: none;
  letter-spacing: 0;
  color: var(--color-muted);
  text-decoration: none;
}

.fr-section-action:hover {
  color: var(--color-text);
  text-decoration: underline;
}

/* A read-only empty party slot reads as a note, not a button. */
.ps-empty-note {
  font-size: var(--font-xs);
}

/* The GD-id cell is a link now (the row opens the pane): same look as the
   span it replaced, underlined on hover so the page click is discoverable. */
a.case-id {
  color: inherit;
  text-decoration: none;
}

a.case-id:hover {
  text-decoration: underline;
}

/* The filter builder's pane hint, beside the name and Shared. */
.lv-filter-pane {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-muted);
  white-space: nowrap;
}

/* ── Add-ons control (#1140) ────────────────────────────────────────────────
   A picker where the other control rows have a select, right-aligned like the
   select's value: the trigger is the value (the carried add-ons as tags), the
   menu is the platform's .mh-picker-menu--more (fixed, so the pane's scroll
   container cannot clip it; more_button.js places it). The row opens it from
   anywhere, as the select rows do (openControl). A retired add-on the case
   still carries reads struck through: removable, never re-addable. */
:is(.case-detail-page, .case-pane-details) .add-ons-picker {
  display: flex;
  justify-content: flex-end;
  min-width: 0;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker > .add-ons-picker__trigger,
:is(.case-detail-page, .case-pane-details) .add-ons-picker > .add-ons-picker__trigger:hover,
:is(.case-detail-page, .case-pane-details) .add-ons-picker[open] > .add-ons-picker__trigger {
  width: auto;
  height: auto;
  min-width: 0;
  max-width: 100%;
  gap: var(--space-1);
  padding: 1px 2px;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--color-text);
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker > .add-ons-picker__trigger:focus-visible {
  outline: none;
  background-color: var(--color-surface-muted);
  box-shadow: var(--shadow-focus);
}

:is(.case-detail-page, .case-pane-details) .control-add-ons:hover .mh-chev {
  color: var(--color-accent-hover);
}

/* Nothing on offer at all: a value, not a control — no pointer, no accent. */
:is(.case-detail-page, .case-pane-details) .control-add-ons--inert,
:is(.case-detail-page, .case-pane-details) .control-add-ons--inert .detail-field-ico,
:is(.case-detail-page, .case-pane-details) .control-add-ons--inert .detail-field-label {
  cursor: default;
}

:is(.case-detail-page, .case-pane-details) .control-add-ons--inert:hover .detail-field-ico,
:is(.case-detail-page, .case-pane-details) .control-add-ons--inert:hover .detail-field-label {
  color: var(--color-muted);
}

/* One line, never wrapped: the tags that do not fit are hidden by
   case_surface.js and counted in "+N" beside them. Packed from the start, not
   the end — the trigger is already right-aligned, and an end-packed overflow
   spills off the start edge, where scrollWidth cannot see it to fold it. */
:is(.case-detail-page, .case-pane-details) .add-ons-picker__tags {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-1);
  min-width: 0;
  overflow: hidden;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__tag {
  display: inline-flex;
  flex-shrink: 0;
}

/* The display above beats the UA's [hidden] rule — restate it. */
:is(.case-detail-page, .case-pane-details) .add-ons-picker__tag[hidden],
:is(.case-detail-page, .case-pane-details) .add-ons-picker__more[hidden],
:is(.case-detail-page, .case-pane-details) .add-ons-picker__empty[hidden],
:is(.case-detail-page, .case-pane-details) .add-ons-picker__tag--folded {
  display: none;
}

/* The menu a region swap carried open (case_surface.js) is the same menu —
   no second entrance. */
:is(.case-detail-page, .case-pane-details) .add-ons-picker--kept > .mh-picker-menu {
  animation: none;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__empty {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__more {
  flex-shrink: 0;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
  white-space: nowrap;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__menu {
  min-width: 220px;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__item {
  justify-content: flex-start;
  gap: var(--space-2);
  cursor: pointer;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__item input {
  margin: 0;
  cursor: pointer;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__item--locked,
:is(.case-detail-page, .case-pane-details) .add-ons-picker__item--locked input {
  cursor: default;
  color: var(--color-muted);
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__item--locked:hover {
  background: transparent;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__item--retired span {
  text-decoration: line-through;
  color: var(--color-muted);
}

/* A child-kind entry (#1144) — an anchor, not a form control: the box is
   derived from the child case and the click navigates (to the child, or to
   the new-case form with this case as parent). The box is decoration on the
   anchor; without pointer-events: none a click on it runs the checkbox's own
   activation (toggle) instead of the link's. */
:is(.case-detail-page, .case-pane-details) .add-ons-picker__item--case input {
  pointer-events: none;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__go {
  margin-left: auto;
  padding-left: var(--space-3);
  font-size: var(--font-xxs);
  color: var(--color-muted);
  white-space: nowrap;
}

:is(.case-detail-page, .case-pane-details) .add-ons-picker__hint {
  margin: 0;
  padding: var(--space-1) var(--space-3) var(--space-2);
  font-size: var(--font-xxs);
  color: var(--color-muted);
}

/* ── /system/services section chrome (#1140) ──────────────────────────────
   The Add-ons section is the first block on that page written without inline
   styles; the classes name what the older sections do with style attributes. */
.system-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: var(--space-8);
  margin-bottom: var(--space-3);
}

/* The first section on a page sits where the header title sits: the content
   gutter alone, no section margin above it (/ux gutter, top). */
.main-content > .system-section-head:first-child,
/* Inside a page grid the section gap is the grid's row gap, so a head that
   opens a section carries no margin of its own: two sections sharing a row
   start level, and a hidden section (a create form behind a button) leaves
   no gap behind. */
.page-grid > .page-section > .system-section-head:first-child,
.page-grid > div > .page-section > .system-section-head:first-child {
  margin-top: 0;
}

/* A cell that must not wrap (a timestamp). */
.cell-nowrap {
  white-space: nowrap;
}

/* A section that is only a head (explanation, no card) ends where its hint
   ends, so the next section's own top margin is the whole gap. */
.page-section > .system-section-head:last-child {
  margin-bottom: 0;
}

/* ── Page grid ──────────────────────────────────────────────────────────────
   Twelve columns inside .main-content, opt-in per page: wrap the content in
   .page-grid and give each block a .col-N. A block with no .col-N spans the
   row. Sections (heading + card) are .page-section so their internal spacing
   stays what the stacked layout had. (/ux grid-columns) */
.page-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--space-5);
  /* One section gap between rows (/ux section-gap); the heads carry none. */
  row-gap: var(--space-8);
  align-items: start;
  /* The page ends with the same air a section gap has: the content gutter
     plus this brings the last block to --space-8 from the bottom edge
     (/ux page-bottom-gap). */
  padding-bottom: calc(var(--space-8) - var(--space-ws-gutter));
}

.page-grid > * {
  grid-column: span 12;
  min-width: 0;
}

/* A wrapper with nothing in it yet (an htmx target whose partial returned
   nothing) takes no row, so it adds no gap. */
.page-grid > div:not(:has(*)) {
  display: none;
}

/* A row of cards inside a section, on the same twelve columns. Cards in a
   row are grid cards and keep their own .card-title. Stacked blocks in a
   section get the standard gap between them. */
.page-row {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--space-5);
  row-gap: var(--space-5);
  align-items: stretch;
}

.page-row > * {
  grid-column: span 12;
  min-width: 0;
}

.page-section > .page-row + .card,
.page-section > .card:not(.hidden) + .card,
.page-section > .card + .page-row,
.page-section > .page-row + .page-row {
  margin-top: var(--space-5);
}

.col-1 { grid-column: span 1; }
.col-2 { grid-column: span 2; }
.col-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; }
.col-5 { grid-column: span 5; }
.col-6 { grid-column: span 6; }
.col-7 { grid-column: span 7; }
.col-8 { grid-column: span 8; }
.col-9 { grid-column: span 9; }
.col-10 { grid-column: span 10; }
.col-11 { grid-column: span 11; }
.col-12 { grid-column: span 12; }

/* Table columns on the same twelve: a .tcol-N on a th gives the column
   N twelfths of the table. Column count across a row should total 12.
   (/ux table-columns) */
/* A table whose headers sit on the twelve keeps those widths whatever the
   cells hold: fixed layout, and a value too long for its column wraps
   instead of pushing the column wider (/ux table-columns). */
.data-table:has(> thead th[class*="tcol-"]) {
  table-layout: fixed;
}

.data-table:has(> thead th[class*="tcol-"]) td {
  overflow-wrap: anywhere;
}

.tcol-1 { width: calc(100% * 1 / 12); }
.tcol-2 { width: calc(100% * 2 / 12); }
.tcol-3 { width: calc(100% * 3 / 12); }
.tcol-4 { width: calc(100% * 4 / 12); }
.tcol-5 { width: calc(100% * 5 / 12); }
.tcol-6 { width: calc(100% * 6 / 12); }
.tcol-7 { width: calc(100% * 7 / 12); }
.tcol-8 { width: calc(100% * 8 / 12); }
.tcol-9 { width: calc(100% * 9 / 12); }
.tcol-10 { width: calc(100% * 10 / 12); }
.tcol-11 { width: calc(100% * 11 / 12); }
.tcol-12 { width: 100%; }

.system-section-title {
  margin: 0;
  font-size: var(--font-lg);
}

.system-section-hint {
  margin: var(--space-1) 0 0;
  font-size: var(--font-xs);
}

/* Links inside section hints and status lines take the accent; there is no
   platform-wide anchor rule, so an unclassed link would fall back to the
   browser blue (/ux text-color-token). */
.system-section-hint a,
.card > .status-line a,
.data-table a:not(.btn) {
  color: var(--color-accent);
}

/* The title block of a section head (title + hint) takes six of the twelve
   columns: half the row minus half a column gap (/ux hint-columns). The
   actions on the right keep the rest. */
.page-grid .system-section-head > :first-child {
  flex: 0 1 calc(50% - var(--space-5) / 2);
}

/* In a section of six columns or fewer the title block is the whole
   section: six of twelve would be three of six. */
.page-section:is(.col-1, .col-2, .col-3, .col-4, .col-5, .col-6) > .system-section-head > :first-child {
  flex-basis: 100%;
}

/* Section head actions sit on the right, level with the title. */
.system-section-head > .inline-form {
  align-self: flex-start;
}

.system-slug {
  font-size: var(--font-xs);
}

/* In a right-aligned cell the action buttons line up on the right too. */
.data-table td.col-right .system-row-actions {
  justify-content: flex-end;
}

.system-row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

/* One action under the other, so a row's buttons read as a short list and the
   column stays narrow (/system/services). */
.system-row-actions--stack {
  flex-direction: column;
  align-items: flex-start;
}

.system-inline-form {
  display: inline;
}

.system-edit-cell {
  background: var(--color-bg);
  padding: var(--space-4);
}

.system-edit-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
}

/* A field that takes the whole row of the edit form (a source, a list of
   pass-through boxes): the price register's long inputs (#1393). */
.system-edit-form > .form-group--full {
  flex: 1 1 100%;
}

.system-empty {
  padding: var(--space-8);
  text-align: center;
}

/* The poller's note under the card title on /engine/integrations/frontdesksuite —
   aligned with .card-title's inset rather than the card edge. */
.booking-calendars-hint {
  padding: 0 var(--space-5) var(--space-3);
}

/* A declared integration's page (/engine/integrations/<key>): notes and the
   secret forms sit on .card-title's inset, like the poller's above. */
.integration-page__hint {
  padding: 0 var(--space-5) var(--space-3);
}

.integration-page__form {
  padding: 0 var(--space-5) var(--space-5);
}

/* The add-a-booking-address form on the same page — the card's inset, and the
   address field takes the row. */
.booking-calendars-add {
  padding: 0 var(--space-5) var(--space-5);
}

.booking-calendars-add .form-control {
  flex: 1;
}

/* A calendar's name — ours, set by a person — heads its row. */
.booking-calendar-name {
  flex-wrap: nowrap;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}

.booking-calendar-name .form-control {
  flex: 1;
  font-weight: var(--weight-semibold);
}

/* The kommune's own intro prose under a calendar's label, one line — the
   full text is in the title. Raw, never parsed (rules/ontology.md). */
.booking-calendar-prose {
  max-width: 48ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Passport reader review (/engine/ai/passport-reader, #1275) ─────────────── */

/* The note under each card title — the card title's inset, as on the
   booking-calendars page. */
/* The read the mistakes list is filtered to (?read=). */
.passport-review tr.is-selected {
  background: var(--color-surface-dim);
}

.passport-review__hint {
  padding: 0 var(--space-5) var(--space-3);
}

.passport-review__nowrap {
  white-space: nowrap;
}

/* A rate, and under it the counts and the interval behind it. */
.passport-review__rate {
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

/* The reader's value and the correct one, digits aligned for comparison. */
.passport-review__value {
  font-variant-numeric: tabular-nums;
}

/* A secondary line inside a cell: counts, the typed value, a note. */
.passport-review__sub {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.passport-review__triage {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--space-1);
}

.passport-review__version {
  width: 4.5rem;
}

/* A create form that opens above a /system catalogue (/system/resources). */
.system-create-card {
  margin-bottom: var(--space-6);
  padding: var(--space-5);
}

/* Inside a page section the stacking rules own the gaps, so the create
   card's own bottom margin is dropped (/ux page-bottom-gap, section-gap). */
.page-section > .system-create-card {
  margin-bottom: 0;
}

/* "Works in" — the phases a team (user type) or a person works. Inline in
   the profile's Account row, a block on the user-type form. */
.works-in {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.works-in__label {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.works-in--block {
  display: block;
  margin-bottom: var(--space-5);
}

.works-in__options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

/* Who a feature flag is on for (#1260): a summary, and a disclosure with one
   tick per team member on the /system/flags row. */
.flag-audience {
  font-size: var(--font-xs);
}

.flag-audience__summary {
  display: block;
  margin-bottom: var(--space-1);
}

/* The picker opens from a link, not a button, so the Actions column stays
   the only one with buttons (/ux table-actions-right). */
.flag-audience__picker > summary {
  list-style: none;
  cursor: pointer;
  color: var(--color-accent);
}

.flag-audience__picker > summary::-webkit-details-marker {
  display: none;
}

.flag-audience__form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  margin: var(--space-2) 0;
}

.flag-audience__hint {
  margin: 0;
  max-width: 32ch;
  font-size: var(--font-xs);
}

.flag-audience__choices {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* "Share fields from" on a case type's Fields tab: a picker, then the
   source's fields as ticks (the ones already here locked). */
.system-share-fields {
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}

.system-share-fields__pick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.system-share-fields__pick .form-control {
  width: auto;
}

.system-share-fields__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.system-share-fields__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* The Section input on the Fields tab (add form and edit row): a short
   free-text box with the known names as a datalist. */
.system-field-section {
  width: 12.5rem;
}

/* The Fields tab's one-line reminder that its groups and drag order are what
   the case page, board pane and Front sidebar show. */
.system-fields-hint {
  margin-left: auto;
  margin-right: var(--space-4);
  font-size: var(--font-xs);
}

/* Rename a section from its group header on the Fields tab: a button, then
   an inline form that opens beside it. */
.system-rename-section {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
}

.system-rename-section__form {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.system-rename-section__form.hidden {
  display: none;
}

/* ── New-case setup fields (#1144 follow-up) ────────────────────────────────
   One block per case type, holding what that type asks for at creation (its
   field_schema `setup` flags). Hidden until its type is the selected one —
   a class, not an inline style, so `lint_templates` stays green. The block
   is a .form-section, which sets no display of its own. */
.setup-fields {
  display: none;
}

.setup-fields--selected {
  display: block;
}

/* A document checklist on /cases/new belongs to a case type that collects the
   couple's documents; a type that does not (the apostille) hides all three
   sections. A class, not an inline style — `lint_templates` stays green. */
.doc-section--hidden {
  display: none;
}

/* Hidden until the toggle says the selected type collects no documents. Its
   own class, not the sections' — this element is display:none on its own, so
   removing `doc-section--hidden` from it would reveal nothing. */
.no-docs-hint {
  display: none;
}

.no-docs-hint--shown {
  display: block;
}

/* ── Name-this-document dialog (#1159) ───────────────────────────────────
   One editor for the two name parts an operator owns — details and subject.
   The preview strip renders the composed name as it is typed; it is the only
   thing carrying the "Type (details) - Name" convention to the person, since
   no validator can enforce prose. */
/* `.form-group` carries no outer margin — it only spaces its own children —
   so stacked groups in this dialog run the hint of one into the label of the
   next. The form owns the rhythm between them. */
.doc-name-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.doc-name-form__type {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-text);
}

.doc-name-form__preview {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-dim);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
}

.doc-name-form__preview-label {
  font-size: var(--font-xs);
  color: var(--color-muted);
  text-transform: uppercase;
  /* Raw, like every other uppercase micro-label in this file (3001, 3228,
     4306): there is no letter-spacing token, and one label does not make a
     new semantic role worth minting one for. */
  letter-spacing: 0.04em;
}

.doc-name-form__preview-value {
  font-size: var(--font-sm);
  color: var(--color-text-strong);
}

.doc-name-form__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
}

/* ── Quarantine review: what was submitted (#1166 follow-up) ─────────────────
   A disclosure per trapped row, mirroring `.feedback-panel__context` — the
   platform's existing "show me the payload" idiom. Collapsed by default: the
   table is for scanning and the panel is for judging, and the reviewer decides
   which row deserves the second. */
.quarantine-row__detail-row > td {
  padding-top: 0;
  padding-bottom: var(--space-3);
}

.quarantine-detail {
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  background: var(--color-surface-dim);
}

.quarantine-detail__summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
  list-style: none;
  font-size: var(--font-xs);
  color: var(--color-subtle);
  font-weight: var(--weight-medium);
}

.quarantine-detail__summary::-webkit-details-marker {
  display: none;
}

.quarantine-detail__summary::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid var(--color-muted);
  border-top: 3px solid transparent;
  border-bottom: 3px solid transparent;
  transition: transform var(--transition-fast);
}

.quarantine-detail[open] .quarantine-detail__summary::before {
  transform: rotate(90deg);
}

.quarantine-detail__body {
  padding: var(--space-3) var(--space-4);
  border-top: 1px dashed var(--color-border);
}

/* Label above, value below — a long message is the point of this panel, so it
   gets the full width rather than a narrow value column beside a label. */
.quarantine-detail__list {
  margin: 0;
  display: grid;
  gap: var(--space-1);
}

.quarantine-detail__list + .quarantine-detail__list {
  margin-top: var(--space-3);
}

.quarantine-detail__term {
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-weight: var(--weight-medium);
}

.quarantine-detail__value {
  margin: 0 0 var(--space-2);
  font-size: var(--font-sm);
  color: var(--color-text);
  line-height: var(--line-height-relaxed);
  /* Attacker-chosen text: wrap it rather than let one long token stretch the
     table, and keep the sender's own line breaks — they are evidence too. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* No letter-spacing here on purpose: the design system mints no token for it
   (the two uppercase labels elsewhere in this file carry raw em values), and a
   one-off decorative tracking is not worth a platform-wide token. A `var()`
   naming a token that does not exist computes to the guaranteed-invalid value
   and the declaration vanishes silently — the `--shadow-row-edge` failure in
   rules/ontology.md §Platform structure. */
.quarantine-detail__extra-label {
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-muted);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
}

.quarantine-detail__list--raw .quarantine-detail__term code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--font-xs);
}

/* ── Quarantine review: decisions and groups (#1204) ─────────────────────────
   A row carries two decisions now, Release and Dismiss, side by side at the
   right edge. A group is one browser's burst folded into one line; its rows
   open underneath in the same disclosure the per-row panel uses. */
.quarantine-row__actions {
  text-align: right;
  white-space: nowrap;
}

.quarantine-row__actions > * + * {
  margin-left: var(--space-2);
}

.quarantine-row__form {
  display: inline;
}

.quarantine-group > td {
  font-weight: var(--weight-medium);
}

.quarantine-group__rows {
  padding: var(--space-2) var(--space-3) var(--space-3);
  border-top: 1px dashed var(--color-border);
}

/* ── Insights (leads + conversions) ─────────────────────────────────────── */
/* Bars are a styled list, not SVG — see tools/insights/templates/partials/
   insights_chart.html for why. The only inline value is --bar-height. */

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

.insights__controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  align-items: center;
}

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

.insights__control-label {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.insights__segmented {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.insights__segment {
  padding: var(--space-2) var(--space-4);
  font-size: var(--font-sm);
  color: var(--color-text);
  text-decoration: none;
  background: var(--color-surface);
}

.insights__segment:hover {
  background: var(--color-surface-muted);
}

.insights__segment--on {
  background: var(--color-accent);
  color: var(--color-surface);
}

/* The card carries no body padding of its own (the title pads itself), so the
   hint, the chart and the empty line take the gutter here. The data table
   stays full-bleed, as data tables in cards do. */
.insights .card > .status-hint,
.insights .card > .insights-chart,
.insights .card > .insights-chart__empty {
  padding-inline: var(--space-5);
}

.insights .card > .insights-chart__empty {
  padding-bottom: var(--space-5);
}

/* The scroller around a chart: two arrows and an edge fade on whichever side
   still has buckets out of view. insights.js opens the chart at its latest
   bucket and toggles the --more-* classes from the chart's scroll position,
   so the fade and the arrows can never disagree with what is off-screen. The
   fades are gradients to the card surface, so they read as "the chart goes
   on" rather than as a border. */
.insights-scroller {
  position: relative;
}

.insights-scroller::before,
.insights-scroller::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: var(--space-8);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--transition-normal);
}

.insights-scroller::before {
  left: 0;
  background: linear-gradient(to right, var(--color-surface), transparent);
}

.insights-scroller::after {
  right: 0;
  background: linear-gradient(to left, var(--color-surface), transparent);
}

.insights-scroller--more-earlier::before,
.insights-scroller--more-later::after {
  opacity: 1;
}

.insights-scroller__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: var(--z-base);
  width: var(--control-h);
  height: var(--control-h);
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-subtle);
  box-shadow: var(--shadow-sm);
}

.insights-scroller__nav:hover {
  color: var(--color-text);
  border-color: var(--color-accent);
}

/* .btn-icon sets display, and an author display beats the browser's own
   [hidden] rule — so the hidden attribute alone left both arrows painted. */
.insights-scroller__nav[hidden] {
  display: none;
}

.insights-scroller__nav--earlier {
  left: var(--space-2);
}

.insights-scroller__nav--later {
  right: var(--space-2);
}

/* The chart itself. align-items:flex-end is what makes the bars share a
   baseline; the column stretches and the bar grows upward from it. */
.insights-chart {
  overflow-x: auto;
  padding-top: var(--space-5);
  padding-bottom: var(--space-3);
}

.insights-chart__bars {
  display: flex;
  align-items: flex-end;
  gap: var(--space-1);
  min-height: 220px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Columns share the width but are capped: with one bucket in the window (the
   first month after the cutover) an uncapped column filled the card edge to
   edge and read as a block, not a bar. Twelve months sit left-aligned under
   the cap; a year of days still shrinks to the floor and scrolls. */
.insights-chart__col {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  min-width: 28px;
  max-width: 56px;
  height: 220px;
}

.insights-chart__value {
  font-size: var(--font-xs);
  color: var(--color-muted);
  margin-bottom: var(--space-1);
}

/* The bar is a column of segments rather than a filled box, so a bucket both
   systems answered stacks its two shares instead of rendering as two bars.
   `background` moves to the segments; the bar keeps the rounding and clips. */
.insights-chart__bar {
  display: flex;
  flex-direction: column-reverse;
  width: 100%;
  height: var(--bar-height);
  min-height: 2px;
  overflow: hidden;
  border-radius: var(--radius-xs) var(--radius-xs) 0 0;
}

.insights-chart__seg {
  display: block;
  width: 100%;
  height: var(--seg-share);
}

/* The imported Pipedrive share, deliberately a different weight: the seam is a
   fact about the data, so the chart shows it rather than blending it away. */
.insights-chart__seg--imported {
  background: var(--color-accent-200);
}

.insights-chart__seg--native {
  background: var(--color-accent);
}

/* The lead mix: the bar split by what became of each lead — paid, or the lead
   stage it last held (core/journey_facts.py, tools/insights/services.lead_mix).
   Stages are shaded by rank in the ladder with ONE hue, light → dark, because
   a ladder is an order and not a set of identities; paid takes the success
   role and an unrecorded stage a grey. The rank tones are the accent scale's
   200/300/400/500/700/900 — six steps whose adjacent pairs read apart with the
   surface gap below; a seventh stage would wrap onto the last tone. Identity
   never rests on the colour alone: the legend, the hover card and the table
   all name the segment. */
.insights-chart__seg--mix {
  box-sizing: border-box;
}

/* A surface-coloured gap between touching segments, drawn inside the
   segment's own height so the stack still sums to the bar. */
.insights-chart__seg--mix + .insights-chart__seg--mix {
  border-top: 2px solid var(--color-surface);
}

/* insights.js hides a segment when its legend entry is off; display:block
   above would otherwise beat the attribute (the scroller nav has the same
   note). */
.insights-chart__seg[hidden] {
  display: none;
}

.insights-chart__seg--tone-paid {
  background: var(--color-success);
}

.insights-chart__seg--tone-stage-1 {
  background: var(--color-accent-200);
}

.insights-chart__seg--tone-stage-2 {
  background: var(--color-accent-300);
}

.insights-chart__seg--tone-stage-3 {
  background: var(--color-accent-400);
}

.insights-chart__seg--tone-stage-4 {
  background: var(--color-accent-500);
}

.insights-chart__seg--tone-stage-5 {
  background: var(--color-accent-700);
}

.insights-chart__seg--tone-stage-6 {
  background: var(--color-accent-900);
}

/* A stage the catalogue no longer carries — kept visible, in a neutral. */
.insights-chart__seg--tone-other {
  background: var(--color-muted);
}

.insights-chart__seg--tone-unknown {
  background: var(--color-border);
}

/* The Channels page: a channel is a category, not a rank, so it takes the
   service palette rather than the stage ramp. Legend and table carry the
   name — identity never rests on the colour. */
.insights-chart__seg--tone-channel-paid {
  background: var(--color-service-amber);
}

.insights-chart__seg--tone-channel-organic {
  background: var(--color-service-green);
}

.insights-chart__seg--tone-channel-ai {
  background: var(--color-service-plum);
}

.insights-chart__seg--tone-channel-referral {
  background: var(--color-service-teal);
}

.insights-chart__seg--tone-channel-direct {
  background: var(--color-service-blue);
}

/* With a segment switched off the bars are re-stacked to the rest, and last
   year's outline (a whole-bar figure) would compare against the wrong thing. */
.insights-chart--filtered .insights-chart__ghost {
  display: none;
}

/* The legend is also the filter. Pressed = shown; an unpressed entry reads
   struck through with its swatch drained to the border grey. */
.insights-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.insights .card > .insights-legend {
  padding-inline: var(--space-5);
}

.insights-legend__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font-size: var(--font-xs);
  color: var(--color-subtle);
  cursor: pointer;
}

.insights-legend__item:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

.insights-legend__item:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.insights-legend__item[aria-pressed="false"] {
  color: var(--color-muted);
  text-decoration: line-through;
}

.insights-legend__item[aria-pressed="false"] .insights-legend__swatch {
  background: var(--color-border);
}

.insights-legend__swatch {
  display: inline-block;
  flex: none;
  width: var(--space-3);
  height: var(--space-3);
  border-radius: var(--radius-xs);
}

/* The rate chart's "read with care" bar: a cohort too small for its rate to
   mean much, or too young to have paid yet. The pale/solid pair on the two
   count charts means Pipedrive/Casebox; this chart has no source split, so
   pale is free to mean this, and the hover card and table say which. */
.insights-chart__bar--rate {
  background: var(--color-accent);
}

.insights-chart__bar--caution {
  background: var(--color-accent-100);
}

/* Last year's bar for the same bucket, as an outline behind this year's. The
   column is the positioning box and the bar's own height is a share of it,
   so the ghost uses the same share and sits on the same baseline: the label
   row's height up from the bottom. Taller than this year's, the outline
   shows above the bar; shorter, the bar covers it and the hover card says. */
.insights-chart__col {
  position: relative;
}

.insights-chart__ghost {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--font-xs) * var(--line-height-normal) + var(--space-2));
  height: var(--ghost-height);
  min-height: 2px;
  border: 1px dashed var(--color-accent-300);
  border-bottom: 0;
  border-radius: var(--radius-xs) var(--radius-xs) 0 0;
  pointer-events: none;
}

.insights-chart__bar {
  position: relative;
}

/* The one bucket both systems answered. Marked because it is the only
   discontinuity a reader of this chart can see — the day Pipedrive stopped
   taking deals is invisible here, the series never read it after the seam. */
.insights-chart__col--seam {
  position: relative;
}

.insights-chart__marker {
  position: absolute;
  top: calc(-1 * var(--space-5));
  font-size: var(--font-xs);
  color: var(--color-muted);
  white-space: nowrap;
  border-bottom: 1px dashed var(--color-border);
  padding-bottom: var(--space-1);
}

.insights-chart__label {
  font-size: var(--font-xs);
  color: var(--color-text-lavender-grey);
  margin-top: var(--space-2);
  white-space: nowrap;
}

.insights-chart__empty {
  color: var(--color-muted);
  font-size: var(--font-sm);
}

/* The same figures as a table, for a screen reader and for copying a number
   out. Folded by default so it does not double the card's height — a native
   <details>, styled like the feedback report's disclosure. The summary row
   takes the card gutter; the table inside stays full-bleed. */
.insights-chart__details {
  margin-top: var(--space-4);
  border-top: 1px solid var(--color-border-subtle);
}

.insights-chart__summary {
  padding: var(--space-3) var(--space-5);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-subtle);
  cursor: pointer;
}

.insights-chart__summary:hover {
  color: var(--color-text);
}

.insights-chart__summary:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
  border-radius: var(--radius-sm);
}

/* The table's caption is for a screen reader only — the visible heading is the
   card title above it. There is no global visually-hidden utility in this
   stylesheet (only a .feedback-panel-scoped .sr-only), so the recipe is spelled
   here rather than assumed. */
.insights-chart__caption {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.insights__footnote {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* Rate rows (tools/insights/templates/partials/insights_chart.html, rate_rows):
   one horizontal bar per group over the window, best first. The row is the
   table — label, bar, figure and note are laid out as columns of one grid so
   the figures align down the card, and the bar is the only thing that is not
   text. The fill's only inline value is --bar-width, the same shape as the
   column charts' --bar-height. */
.insights .card > .insights-rates {
  padding-inline: var(--space-5);
  padding-bottom: var(--space-5);
}

.insights-rates {
  display: grid;
  grid-template-columns: minmax(6rem, max-content) minmax(8rem, 1fr) max-content max-content minmax(0, 1fr);
  column-gap: var(--space-4);
  row-gap: var(--space-3);
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
}

.insights-rates__row {
  display: contents;
}

.insights-rates__label {
  font-size: var(--font-sm);
  color: var(--color-text);
}

.insights-rates__row--unknown .insights-rates__label {
  color: var(--color-muted);
}

.insights-rates__track {
  display: block;
  height: var(--space-4);
  border-radius: var(--radius-xs);
  background: var(--color-surface-muted);
  overflow: hidden;
}

.insights-rates__fill {
  display: block;
  width: var(--bar-width);
  min-width: 2px;
  height: 100%;
  border-radius: var(--radius-xs);
  background: var(--color-accent);
}

.insights-rates__fill--caution {
  background: var(--color-accent-100);
}

.insights-rates__row--unknown .insights-rates__fill {
  background: var(--color-border);
}

.insights-rates__value {
  font-size: var(--font-sm);
  color: var(--color-text);
  text-align: right;
}

.insights-rates__cohort {
  font-size: var(--font-xs);
  color: var(--color-muted);
  white-space: nowrap;
}

.insights-rates__note {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

/* The maturity table (insights_chart.html, maturity_table): cohorts down,
   ages across, each cell the share paid by that age. Shaded on the accent
   ramp light → dark by share of the table's peak — four steps, the figure
   printed in every cell, so the shade only guides the eye down a column and
   never carries the value alone. The ramp stops at 300 so body-text ink keeps
   its contrast on the darkest step. A pending cell is a dash in muted ink: an
   age the cohort has not reached, which is not a zero. */
.insights-maturity-wrap {
  overflow-x: auto;
}

.insights-maturity th,
.insights-maturity td {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.insights-maturity th.insights-maturity__bucket,
.insights-maturity td.insights-maturity__bucket {
  text-align: left;
}

.insights-maturity__cell--tone-1,
.insights-funnel__cell--tone-1 {
  background: var(--color-accent-50);
}

.insights-maturity__cell--tone-2,
.insights-funnel__cell--tone-2 {
  background: var(--color-accent-100);
}

.insights-maturity__cell--tone-3,
.insights-funnel__cell--tone-3 {
  background: var(--color-accent-200);
}

.insights-maturity__cell--tone-4,
.insights-funnel__cell--tone-4 {
  background: var(--color-accent-300);
}

.insights-maturity__cell--pending {
  color: var(--color-muted);
}

.insights-maturity__cell--total {
  font-weight: var(--weight-semibold);
}

.insights-maturity__row--small td {
  color: var(--color-muted);
}

.insights-maturity__cell:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* The funnel table (insights_chart.html, funnel_table): groups down, lead
   stages across, each cell a share over its count. It shares the maturity
   table's accent ramp (the tone rules above) and its tabular figures; the
   group column stays put while a wide ladder scrolls under it. Off-ladder
   columns sit after a rule in muted ink — beside the funnel, not in it. */
.insights-funnel-wrap {
  overflow-x: auto;
}

.insights-funnel th,
.insights-funnel td {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.insights-funnel .insights-funnel__group {
  position: sticky;
  left: 0;
  text-align: left;
  background: var(--color-surface);
}

.insights-funnel__share {
  display: block;
  font-size: var(--font-sm);
  color: var(--color-text);
}

.insights-funnel__count {
  display: block;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.insights-funnel__cell--paid .insights-funnel__share {
  font-weight: var(--weight-semibold);
}

.insights-funnel__aside,
.insights-funnel__cell--aside {
  border-left: 1px solid var(--color-border);
}

.insights-funnel__cell--aside .insights-funnel__share,
.insights-funnel__row--small .insights-funnel__share,
.insights-funnel__row--small .insights-funnel__group,
.insights-funnel__row--unknown .insights-funnel__group {
  color: var(--color-muted);
}

.insights-funnel__row--total th,
.insights-funnel__row--total td {
  border-top: 1px solid var(--color-border);
  font-weight: var(--weight-semibold);
}

/* The Channels table: the funnel's conventions — muted for a small cohort
   or the Unknown row, a ruled semibold total — on a plain figures table. */
.insights-channels__label {
  text-align: left;
  white-space: nowrap;
}

.insights-channels__row--small td,
.insights-channels__row--unknown .insights-channels__label {
  color: var(--color-muted);
}

.insights-channels__note {
  color: var(--color-muted);
  font-size: var(--font-xs);
  white-space: nowrap;
}

.insights-channels__row--total th,
.insights-channels__row--total td {
  border-top: 1px solid var(--color-border);
  font-weight: var(--weight-semibold);
}

@media (max-width: 720px) {
  .insights-rates {
    grid-template-columns: minmax(5rem, max-content) 1fr max-content;
  }

  .insights-rates__cohort,
  .insights-rates__note {
    grid-column: 2 / -1;
  }
}

/* ── Calendar (core/calendar, #1223) ────────────────────────────────────────
   One grid sized by its container: `.cal` is the container, so a full page, a
   pane and a strip share the markup with no mode flag. On the page the rail is
   the shell's nav panel (`.sb--calendar` in `.nav-panel`), as on the case and
   design-system pages — the grid sits on the main card. Layout reference:
   rules/design-system/handoffs/calendar/REFERENCE (Front's week view) —
   Front's arrangement, our tokens.

   Layer colour is a palette key (`.cal-tone--{key}`), never a value: each key
   binds `--cal-tone` to a service-palette token, and every tinted surface
   derives from it by color-mix, as the board's row tint does. */

.cal {
  --cal-hour-h: 3rem;
  --cal-gutter-w: 3.5rem;
  --cal-line: var(--color-border-subtle);
  /* Weekends read as quieter days, not as disabled ones: a half-strength wash,
     so the hour lines stay visible through it. */
  --cal-weekend: color-mix(in srgb, var(--color-surface-muted) 50%, transparent);
  /* Today, on every grain: one accent wash — and, on a month's cell, a
     hairline in the accent — so the day reads at a glance in a month, a
     week, a day and the rows. */
  --cal-today: color-mix(in srgb, var(--color-accent) 12%, transparent);
  --cal-today-edge: color-mix(in srgb, var(--color-accent) 45%, transparent);
  container-type: inline-size;
  container-name: cal;
  height: 100%;
  min-height: 0;
  background: var(--color-team-surface);
}

/* The hatch is declared on the rail too: on the page the rail is the shell's
   nav panel, outside `.cal`, and its legend draws the hatched swatches. */
.cal,
.cal-rail {
  --cal-hatch: repeating-linear-gradient(
    135deg,
    var(--color-surface-dim) 0 4px,
    transparent 4px 8px
  );
}

.cal-tone--blue { --cal-tone: var(--color-service-blue); }
.cal-tone--teal { --cal-tone: var(--color-service-teal); }
.cal-tone--green { --cal-tone: var(--color-service-green); }
.cal-tone--amber { --cal-tone: var(--color-service-amber); }
.cal-tone--rose { --cal-tone: var(--color-service-rose); }
.cal-tone--plum { --cal-tone: var(--color-service-plum); }
.cal-tone--slate { --cal-tone: var(--color-slate-500); }

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

/* Rail — the page's nav panel, on the .sb sidebar (group labels, rows). A
   block that is not a row (the mini-month, the find box, the legend) takes
   the group label's inset, so its left edge lines up with the labels'. */

.sb--calendar .sb-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-top: var(--space-6);
}

.cal-rail .cal-rail__block {
  padding: 0 var(--sb-group-pad-x);
}

/* `.sb a` mutes every link in a panel; the mini-month's days and arrows are
   the calendar's text, so they take it back (the outside-month, heat and
   anchor states below still win — they are more specific and later). */
.cal-rail .cal-mini__day,
.cal-rail .cal-icon-btn { color: var(--color-text); }

.cal-rail .cal-layers,
.cal-rail .cal-legend {
  margin-top: var(--space-3);
}

.cal-mini__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-2);
}

.cal-mini__label {
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.cal-mini__grid {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: var(--font-xs);
}

.cal-mini__grid th {
  padding-bottom: var(--space-1);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
}

.cal-mini__grid td {
  padding: 1px;
  text-align: center;
}

.cal-mini__day {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  border-radius: var(--radius-pill);
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  transition: background var(--transition-fast);
}

.cal-mini__day:hover { background: var(--color-hover); }
.cal-mini__day.is-outside { color: var(--color-subtle); }

/* The heat map: where the openings are this month, relative to the busiest
   day shown — four steps of the slots layer's own colour. */
.cal-mini__day.cal-heat--1 { background: color-mix(in srgb, var(--color-service-teal) 14%, transparent); }
.cal-mini__day.cal-heat--2 { background: color-mix(in srgb, var(--color-service-teal) 28%, transparent); }
.cal-mini__day.cal-heat--3 { background: color-mix(in srgb, var(--color-service-teal) 46%, transparent); }
.cal-mini__day.cal-heat--4 {
  background: color-mix(in srgb, var(--color-service-teal) 70%, transparent);
  color: var(--color-surface);
}

.cal-mini__day.is-today {
  outline: 1px solid var(--color-accent);
  outline-offset: -1px;
}

.cal-mini__day.is-anchor {
  background: var(--color-accent);
  color: var(--color-surface);
  font-weight: var(--weight-semibold);
}

.cal-find { position: relative; }

.cal-find__icon {
  position: absolute;
  top: 50%;
  left: calc(var(--sb-group-pad-x) + var(--space-3));
  display: flex;
  color: var(--color-muted);
  transform: translateY(-50%);
}

.cal-find__input {
  width: 100%;
  height: var(--control-h);
  padding: 0 var(--space-3) 0 var(--space-8);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  background: var(--color-hover);
  color: var(--color-text);
  font-size: var(--font-sm);
}

/* No dropdown arrow for the datalist: the suggestions open as you type, and
   the arrow's reserved width cut the placeholder off in a 200px panel. */
.cal-find__input::-webkit-calendar-picker-indicator { display: none !important; }

.cal-find__input:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus);
}

.cal-find__message {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-warning-text);
}

/* A heading in the panel reads as the panel's group label, not a title. */
.cal-layers__title {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
}

.cal-layers__list,
.cal-legend__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A layer is a panel row (.sb-row) carrying its colour's checkbox. */
.cal-layer { gap: var(--space-2); }
.sb .cal-layer.is-on { color: var(--color-text); }

.cal-layer__box {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: var(--sb-icon);
  height: var(--sb-icon);
  border: 2px solid var(--cal-tone);
  border-radius: var(--radius-xs);
  color: var(--color-surface);
}

.cal-layer.is-on .cal-layer__box { background: var(--cal-tone); }
.cal-layer:not(.is-on) .cal-layer__label { color: var(--color-muted); }

.cal-legend__entry {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-xs);
  color: var(--color-text);
}

.cal-legend__horizon {
  width: 1rem;
  border-top: 2px dashed var(--color-warning);
}

/* A layer's own key (Layer.swatch): how one of its items is drawn, in its
   tone — solid and hatched as the Team calendar's Away and Requested bars
   (.cal-item--leave below). */
.cal-legend__swatch {
  width: 1rem;
  height: 0.75rem;
  border-left: 2px solid var(--cal-tone);
  border-radius: var(--radius-xs);
}

.cal-legend__swatch--solid { background: color-mix(in srgb, var(--cal-tone) 45%, transparent); }

.cal-legend__swatch--hatched {
  border-left-style: dashed;
  background: var(--cal-hatch), color-mix(in srgb, var(--cal-tone) 14%, transparent);
}

/* Top bar */

/* The host's one column: top bar over the grid, as tall as the host, so the
   grid (flex: 1, overflow: auto) is the box that scrolls — the hour rows
   scroll within the calendar and open on the working day (calendar.js). The
   `.cal-frame` grid that held the rail beside it used to give this height;
   since the rail moved to the shell's nav panel (!2272) the column states it
   itself, or the hour rows grow to their full 24 hours and the page scrolls
   instead. */
.cal-main {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-width: 0;
  min-height: 0;
}

.cal-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--cal-line);
}

.cal-top__period {
  margin: 0 var(--space-2);
  font-size: var(--font-lg);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.cal-top__subtitle {
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.cal-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-h);
  height: var(--control-h);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  transition: background var(--transition-fast);
}

.cal-icon-btn:hover { background: var(--color-hover); }

.cal-grains {
  display: inline-flex;
  margin-left: auto;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

/* The Shortcuts button closes the top bar: pushed right on its own, and beside
   the grain switcher when there is one (the switcher already took the push). */
.cal-top__shortcuts { margin-left: auto; }
.cal-grains + .cal-top__shortcuts { margin-left: 0; }

.cal-grains__btn {
  display: inline-flex;
  align-items: center;
  height: var(--control-h);
  padding: 0 var(--space-3);
  color: var(--color-text);
  font-size: var(--font-sm);
  text-decoration: none;
  transition: background var(--transition-fast);
}

.cal-grains__btn + .cal-grains__btn { border-left: 1px solid var(--color-border); }
.cal-grains__btn:hover { background: var(--color-hover); }

.cal-grains__btn.is-active {
  background: var(--color-accent);
  color: var(--color-surface);
}

/* The row-order control and its span (CalendarConfig.row_orders) wear the
   grain switcher's look and sit before it: the first of them takes the push
   right, the ones after sit beside it. */
.cal-order + .cal-order,
.cal-order + .cal-grains { margin-left: var(--space-2); }

.cal-grid {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: auto;
}

/* Marks — the shared cell vocabulary */

.cal-mark {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
}

.cal-mark--open { color: var(--color-success); }
.cal-mark--full { color: var(--color-danger); }
.cal-mark--not-offered { color: var(--color-service-blue); }
.cal-mark--closed { color: var(--color-service-rose); }
.cal-mark--unreleased { color: var(--color-muted); }

/* Items */

.cal-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--space-1);
  min-width: 0;
  padding: 1px var(--space-2);
  overflow: hidden;
  border-left: 2px solid var(--cal-tone, var(--color-slate-500));
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--cal-tone, var(--color-slate-500)) 14%, transparent);
  color: var(--color-text-strong);
  font-size: var(--font-xs);
  line-height: var(--line-height-tight);
  text-decoration: none;
  white-space: nowrap;
}

a.cal-item:hover {
  background: color-mix(in srgb, var(--cal-tone, var(--color-slate-500)) 26%, transparent);
}

.cal-item__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-item__time,
.cal-item__count { font-variant-numeric: tabular-nums; }
.cal-item__time { color: var(--color-muted); }

.cal-item__count {
  margin-left: auto;
  font-weight: var(--weight-semibold);
}

.cal-item__seen,
.cal-item__tag {
  font-size: var(--font-xxs);
  color: var(--color-muted);
}

.cal-item__seen {
  flex-basis: 100%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-item--unreleased {
  border-left-color: var(--color-border);
  background: var(--cal-hatch);
  color: var(--color-muted);
}

.cal-item--compact {
  justify-content: center;
  padding: 1px var(--space-1);
}

/* A slot's history (core/calendar SLOT_STATE_LABELS, #1226). Both gone states
   are inferences, so they recede rather than shout: *probably booked* greys
   and strikes through (taken, as bookings take slots), *probably withdrawn*
   is hatched (taken back, the way an unreleased day is drawn), and a slot
   added after the day was listed carries a small "new". */
.cal-item--gone-booked {
  border-left-color: var(--color-border);
  background: var(--color-surface-muted);
  color: var(--color-muted);
}

.cal-item--gone-booked .cal-item__time,
.cal-item--gone-booked .cal-item__label { text-decoration: line-through; }

.cal-item--gone-withdrawn {
  border-left-color: var(--color-border);
  background: var(--cal-hatch);
  color: var(--color-muted);
}

.cal-item__new {
  padding: 0 var(--space-1);
  border-radius: var(--radius-xs);
  background: var(--cal-tone, var(--color-slate-500));
  color: var(--color-surface);
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  line-height: var(--line-height-tight);
}

.cal-legend__slot {
  width: 1rem;
  height: 0.75rem;
  border-radius: var(--radius-xs);
}

.cal-legend__slot--booked { background: var(--color-surface-muted); outline: 1px solid var(--color-border); }
.cal-legend__slot--withdrawn { background: var(--cal-hatch); outline: 1px solid var(--color-border); }
.cal-legend__slot--added { background: var(--color-service-teal); }

/* Availability levels (CalendarItem.level, #1231): a reading's chip in how
   much of what was released is still free — the status family, so red, amber
   and green mean what they mean everywhere. A typed "open" has no count to
   divide and is drawn as an outline in the free colour. The legend's chip is
   the item's own shape. */
.cal-item--level-plenty,
.cal-legend__level--plenty { --cal-tone: var(--color-success); }
.cal-item--level-few,
.cal-legend__level--few { --cal-tone: var(--color-warning); }
.cal-item--level-none,
.cal-legend__level--none { --cal-tone: var(--color-danger); }
.cal-item--level-typed,
.cal-legend__level--typed { --cal-tone: var(--color-success); }

.cal-item--level-few .cal-item__count { color: var(--color-warning-text); }

/* Which of a hall's calendars a reading came from (CalendarItem.tag). */
.cal-item__tag {
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 50%;
}

/* A followed calendar's reading (CalendarItem.reference): there to watch,
   never to book — muted grey, dashed, so it cannot pass for an opening. */
.cal-item--reference {
  --cal-tone: var(--color-muted);
  color: var(--color-muted);
  outline: 1px dashed var(--color-border);
  outline-offset: -1px;
}

.cal-item--reference .cal-mark { color: var(--color-muted); }

.booking-calendar-followed { white-space: nowrap; }

.cal-item--level-typed,
.cal-legend__level--typed {
  outline: 1px solid var(--cal-tone);
  outline-offset: -1px;
  background: transparent;
}

.cal-legend__level {
  width: 1rem;
  height: 0.75rem;
  border-left: 2px solid var(--cal-tone);
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--cal-tone) 14%, transparent);
}

/* Our couples on a Month or rows day, counted (#1231): the names are the
   Day's. The heart and the number read in a rows cell; the word joins them
   where there is room. */
.cal-item--weddings .cal-item__count { margin-left: 0; }
.cal-item--weddings .icon { flex: 0 0 auto; color: var(--cal-tone); }

/* A day notice (#1399): the glyph is what gets it seen without a click — alone
   in a rows cell, before the start of the text in a month cell, beside the
   whole text on one hall's Day. A writer's notice is a button opening it. */
.cal-item--notice {
  flex-wrap: nowrap;
  width: 100%;
  border-top: 0;
  border-right: 0;
  border-bottom: 0;
  font: inherit;
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  text-align: left;
}

.cal-item--notice .icon { flex: 0 0 auto; color: var(--cal-tone); }
button.cal-item--notice { cursor: pointer; }
button.cal-item--notice:hover { background: color-mix(in srgb, var(--cal-tone) 26%, transparent); }
.cal-item--notice.cal-item--compact { width: auto; }

.cal-item--notice-full { align-items: flex-start; white-space: normal; }
.cal-item--notice-full .icon { margin-top: 1px; }
/* In a rows cell the number or the glyph is the reading; the word is the
   tooltip's. */
.cal-item--compact:is(.cal-item--open, .cal-item--full, .cal-item--not-offered, .cal-item--closed, .cal-item--unreleased) .cal-item__label,
.cal-item--compact:has(.cal-item__count) .cal-item__label { display: none; }
.cal-item--compact .cal-item__count { margin-left: 0; }

/* Month */

.cal-month {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 36rem;
}

.cal-month__head,
.cal-month__week {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  border-bottom: 1px solid var(--cal-line);
}

.cal-month__dow {
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-xs);
  font-weight: var(--weight-medium);
  color: var(--color-muted);
}

.cal-month__week {
  flex: 1;
  min-height: 6rem;
}

.cal-day {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  min-height: 0;
  padding: var(--space-1);
  border-right: 1px solid var(--cal-line);
}

.cal-day:last-child { border-right: 0; }
.cal-day.is-weekend { background: var(--cal-weekend); }
.cal-day.is-outside .cal-day__num { color: var(--color-subtle); }
.cal-day.is-unreleased { background: var(--cal-hatch); }

.cal-day__num {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  height: 1.5rem;
  padding: 0 var(--space-1);
  border-radius: var(--radius-pill);
  color: var(--color-text);
  font-size: var(--font-xs);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}

.cal-day__num:hover { background: var(--color-hover); }

.cal-day.is-today {
  background: var(--cal-today);
  outline: 1px solid var(--cal-today-edge);
  outline-offset: -1px;
}

.cal-day.is-today .cal-day__num {
  background: var(--color-accent);
  color: var(--color-surface);
  font-weight: var(--weight-semibold);
}

.cal-day__items {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 0;
  overflow: hidden;
}

.cal-day__more {
  padding: 0 var(--space-2);
  font-size: var(--font-xxs);
  color: var(--color-muted);
  text-decoration: none;
}

.cal-day__more:hover { color: var(--color-text); }

/* The horizon: the line between the last day the resource has released and
   the first it has not — down the last released day's right edge in every
   arrangement (a week's whole column, head to the last hour), and in a month
   along the bottom of the last seven released days too, so it steps across
   the week rows as one line (view._horizon_edges, #1230). Drawn over the
   cell's own border, never as it, so no cell changes width. */
.cal-day.is-horizon::after,
.cal-day.is-horizon-below::before,
.cal-time__dayhead.is-horizon::after,
.cal-time__allcell.is-horizon::after,
.cal-time__col.is-horizon::after,
.cal-rows__cell.is-horizon::after {
  position: absolute;
  pointer-events: none;
  content: "";
}

.cal-day.is-horizon::after,
.cal-time__dayhead.is-horizon::after,
.cal-time__allcell.is-horizon::after,
.cal-time__col.is-horizon::after,
.cal-rows__cell.is-horizon::after {
  top: 0;
  right: -1px;
  bottom: 0;
  border-right: 2px dashed var(--color-warning);
}

/* Past the cell's own bottom border, into the week row's: -1px. */
.cal-day.is-horizon-below::before {
  right: -1px;
  bottom: -1px;
  left: 0;
  border-bottom: 2px dashed var(--color-warning);
}

/* A whole day is the click target (DayCell.opens_day, calendar.js): its
   empty space opens the day. */
.cal-day[data-cal-open],
.cal-rows__cell[data-cal-open] { cursor: pointer; }

.cal-day[data-cal-open]:hover,
.cal-rows__cell[data-cal-open]:hover { background-color: var(--color-hover); }

/* Week and day: the all-day row over the hour rows */

.cal-time {
  /* --cal-zoom grows the hour rows when a week holds blocks too short to read
     at the default height (core/calendar/view.py _zoom). */
  --cal-hour-h: calc(3rem * var(--cal-zoom, 1));
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.cal-time__head,
.cal-time__allday,
.cal-time__body {
  display: grid;
  grid-template-columns: var(--cal-gutter-w) repeat(var(--cal-cols), minmax(0, 1fr));
}

.cal-time__head,
.cal-time__allday { border-bottom: 1px solid var(--cal-line); }

.cal-time__dayhead {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--space-2) var(--space-3);
}

.cal-time__dow {
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.cal-time__date {
  font-size: var(--font-lg);
  font-weight: var(--weight-medium);
  color: var(--color-text-strong);
  text-decoration: none;
}

.cal-time__dayhead.is-today .cal-time__dow,
.cal-time__dayhead.is-today .cal-time__date { color: var(--color-accent); }

.cal-time__gutter--label {
  padding: var(--space-2) var(--space-1);
  font-size: var(--font-xxs);
  color: var(--color-muted);
  text-align: right;
}

.cal-time__allcell {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  min-height: 2.5rem;
  padding: var(--space-1);
  border-left: 1px solid var(--cal-line);
}

.cal-time__allcell.is-unreleased { background: var(--cal-hatch); }

.cal-time__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.cal-time__body { position: relative; }

.cal-time__hour {
  position: relative;
  height: var(--cal-hour-h);
  padding-right: var(--space-2);
  font-size: var(--font-xxs);
  color: var(--color-muted);
  text-align: right;
}

.cal-time__hour span {
  position: relative;
  top: -0.5em;
}

.cal-time__col {
  position: relative;
  height: calc(24 * var(--cal-hour-h));
  border-left: 1px solid var(--cal-line);
  background-image: repeating-linear-gradient(
    to bottom,
    var(--cal-line) 0 1px,
    transparent 1px var(--cal-hour-h)
  );
}

.cal-time__col.is-weekend { background-color: var(--cal-weekend); }

/* Today's column in a week or a day: the all-day cell, the hours and the
   head, one wash. */
.cal-time__col.is-today,
.cal-time__allcell.is-today,
.cal-time__dayhead.is-today {
  background-color: var(--cal-today);
}

.cal-block {
  position: absolute;
  top: calc(var(--cal-at) * var(--cal-hour-h));
  left: calc(var(--cal-lane) / var(--cal-lanes) * 100% + 2px);
  display: flex;
  width: calc(100% / var(--cal-lanes) - 4px);
  height: calc(var(--cal-len) * var(--cal-hour-h));
}

.cal-block .cal-item {
  flex: 1;
  align-items: flex-start;
  align-content: flex-start;
  padding: var(--space-1) var(--space-2);
  background: color-mix(in srgb, var(--cal-tone, var(--color-slate-500)) 22%, var(--color-surface));
  white-space: normal;
}

.cal-now {
  position: absolute;
  top: calc(var(--cal-at) * var(--cal-hour-h));
  right: 0;
  left: 0;
  z-index: var(--z-base);
  border-top: 2px solid var(--color-danger);
  pointer-events: none;
}

.cal-now::before {
  position: absolute;
  top: -6px;
  left: -5px;
  width: 10px;
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--color-danger);
  content: "";
}

/* Rows: one row per thing, the period's days across */

.cal-rows {
  height: 100%;
  overflow: auto;
}

.cal-rows__table {
  width: 100%;
  min-width: calc(12rem + var(--cal-cols) * 2.25rem);
  border-collapse: separate;
  border-spacing: 0;
  table-layout: fixed;
  font-size: var(--font-xs);
}

/* Rows whose cells hold a bar (CalendarConfig.rows_fit — teammates): the
   period's days narrow to fit the grid rather than scroll sideways out of
   sight, down to a floor where a bar still reads; the label column narrows
   first (#1230). */
.cal-rows--fit .cal-rows__table { min-width: calc(9rem + var(--cal-cols) * 1rem); }

@container cal (max-width: 64rem) {
  .cal-rows--fit .cal-rows__corner,
  .cal-rows--fit .cal-rows__label { width: 9rem; }
}

.cal-rows__table thead th {
  position: sticky;
  top: 0;
  z-index: var(--z-base);
  border-bottom: 1px solid var(--cal-line);
  background: var(--color-team-surface);
}

.cal-rows__corner,
.cal-rows__label {
  position: sticky;
  left: 0;
  width: 12rem;
  background: var(--color-team-surface);
  text-align: left;
}

.cal-rows__label { z-index: var(--z-base); }
.cal-rows__table thead .cal-rows__corner { z-index: var(--z-sticky); }

.cal-rows__dayhead {
  padding: var(--space-1) 0;
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  text-align: center;
}

.cal-rows__dayhead span { display: block; }

.cal-rows__date {
  font-weight: var(--weight-medium);
  color: var(--color-text-strong);
  font-variant-numeric: tabular-nums;
}

.cal-rows__dayhead.is-today .cal-rows__date { color: var(--color-accent); }

.cal-rows__tally {
  font-size: var(--font-xxs);
  color: var(--color-subtle);
  font-variant-numeric: tabular-nums;
}

.cal-rows__tally.has-open {
  font-weight: var(--weight-semibold);
  color: var(--color-success);
}

.cal-rows__label {
  padding: var(--space-2) var(--space-3);
  overflow: hidden;
  border-right: 1px solid var(--cal-line);
  border-bottom: 1px solid var(--cal-line);
  font-weight: var(--weight-medium);
  color: var(--color-text-strong);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cal-rows__label a {
  color: inherit;
  text-decoration: none;
}

.cal-rows__label a:hover { text-decoration: underline; }

.cal-rows__cell {
  position: relative;
  height: 2.25rem;
  padding: 2px;
  border-right: 1px solid var(--cal-line);
  border-bottom: 1px solid var(--cal-line);
  text-align: center;
  vertical-align: middle;
}

.cal-rows__cell .cal-item + .cal-item { margin-top: 2px; }

.cal-rows__cell.is-weekend,
.cal-rows__dayhead.is-weekend { background: var(--cal-weekend); }

/* Today's column, header to last row — after the weekend rule, so a
   weekend today still reads as today. */
.cal-rows__cell.is-today,
.cal-rows__dayhead.is-today {
  background: var(--cal-today);
}
.cal-rows__dayhead.is-today .cal-rows__date { font-weight: var(--weight-semibold); }
.cal-rows__cell.is-unreleased { background: var(--cal-hatch); }

.cal-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 100%;
  min-height: 16rem;
  padding: var(--space-6);
  color: var(--color-muted);
  text-align: center;
}

.cal-empty p { margin: 0; }

/* Very narrow: a month cell keeps its glyphs and counts, the tooltip keeps
   the words and the provenance. */
@container cal (max-width: 36rem) {
  .cal-day .cal-item__label,
  .cal-day .cal-item__seen,
  .cal-top__subtitle { display: none; }

  .cal-month__dow { padding: var(--space-1); }
}

/* The way back from one resource to all of them (CalendarConfig.all_resources_label). */
.cal-top__all {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--font-sm);
  color: var(--color-accent);
  text-decoration: none;
}

.cal-top__all:hover { text-decoration: underline; }

/* ── Calendar hosts and actions (#1224) ─────────────────────────────────── */

/* A row's own horizon beside its name — the answer to "how far out has this
   hall released" in every month, not only the one the line is drawn in. */
.cal-rows__released {
  display: block;
  overflow: hidden;
  font-size: var(--font-xxs);
  font-weight: var(--weight-normal);
  color: var(--color-muted);
  text-overflow: ellipsis;
}

/* Why the row sits where it does in the order on show (CalendarRow.note) —
   "38 couples", "next free 14 Nov" — so a reordering is never unexplained. */
.cal-rows__note {
  display: block;
  overflow: hidden;
  font-size: var(--font-xxs);
  font-weight: var(--weight-medium);
  color: var(--color-text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The cell action (CalendarConfig.cell_actions_fn): quiet until the cell is
   hovered or the button focused, so a month of "Mark" does not shout. */
.cal-day__action {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 0 var(--space-1);
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--color-muted);
  font-size: var(--font-xxs);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-fast), background var(--transition-fast);
}

.cal-day__actions { display: inline-flex; gap: 2px; }

.cal-day > .cal-day__actions {
  position: absolute;
  top: var(--space-1);
  right: var(--space-1);
}

/* Several actions on one day (a town hall's reading and its notice, #1399):
   glyphs only in the cell, each saying its words on hover — the top bar
   carries them labelled for today, so none is hover-only. */
.cal-day__actions:has(> .cal-day__action + .cal-day__action) .cal-day__action-label { display: none; }

.cal-day:hover .cal-day__action,
.cal-time__allcell:hover .cal-day__action,
.cal-day__action:focus-visible { opacity: 1; }
.cal-day__action:hover { background: var(--color-hover); color: var(--color-text); }

@media (hover: none) {
  .cal-day__action { opacity: 1; }
}


/* The pane: the grid minus the rail, at Week, beside a board's list. */
.cal--pane { height: 100%; }
.cal--pane .cal-top { padding: var(--space-2) var(--space-3); }
.cal--pane .cal-top__period { font-size: var(--font-base); }

.cal-top__open {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-left: auto;
  font-size: var(--font-sm);
  color: var(--color-accent);
  text-decoration: none;
}

.cal-top__open:hover { text-decoration: underline; }

/* The top bar's link out (CalendarConfig.top_links_fn, #1231) — the venue's
   booking system on one hall's Day. */
.cal-top__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--font-sm);
  color: var(--color-accent);
  text-decoration: none;
}

.cal-top__link:hover { text-decoration: underline; }

/* The strip: the all-day row alone, one month, on the case page. */
.cal--strip {
  height: auto;
  margin-top: var(--space-3);
  padding: var(--space-2);
  border: 1px solid var(--cal-line);
  border-radius: var(--radius-md);
}

/* One line at the Details column's width (323px, #1231): the month and the
   way out keep their words, the read stamp gives way with an ellipsis, and
   the hall's name is left out — the strip sits under the Town hall field
   that names it. */
.cal-strip__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  min-width: 0;
  margin-bottom: var(--space-2);
  font-size: var(--font-xs);
  white-space: nowrap;
}

.cal-strip__month { flex: 0 0 auto; font-weight: var(--weight-semibold); color: var(--color-text-strong); }
.cal-strip__subtitle { display: none; }

/* A block, not the stamp's own inline-flex: an ellipsis needs the text in a
   block, and a flex item's text is an anonymous box it cannot cut. */
.cal-strip__head .cal-read {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-strip__open {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  margin-left: auto;
  color: var(--color-accent);
  text-decoration: none;
}

.cal-strip__open:hover { text-decoration: underline; }

.cal-strip__days {
  /* The offset parent the strip's scroll-to-their-day measures from. */
  position: relative;
  display: grid;
  grid-auto-columns: minmax(1.75rem, 1fr);
  grid-auto-flow: column;
  margin: 0;
  padding: 0;
  overflow-x: auto;
  list-style: none;
}

.cal-strip__day {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
  padding: var(--space-1) 1px;
  border-right: 1px solid var(--cal-line);
  font-size: var(--font-xxs);
}

.cal-strip__day:last-child { border-right: 0; }
.cal-strip__day.is-weekend { background: var(--cal-weekend); }
.cal-strip__day.is-unreleased { background: var(--cal-hatch); }
.cal-strip__day.is-horizon { border-right: 2px dashed var(--color-warning); }
.cal-strip__dow { color: var(--color-muted); }
.cal-strip__date { font-weight: var(--weight-medium); font-variant-numeric: tabular-nums; }
.cal-strip__day.is-today .cal-strip__date { color: var(--color-accent); }

.cal-strip__items {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
}

/* Their day: the one cell the strip exists to show. */
.cal-strip__day.is-marked {
  border-radius: var(--radius-xs);
  outline: 2px solid var(--color-service-blue);
  outline-offset: -2px;
}

.cal-strip__legend {
  margin: var(--space-2) 0 0;
  font-size: var(--font-xxs);
  color: var(--color-muted);
}

/* Week-grain slot blocks: a venue's ten-minute slots as small blocks in the
   hour rows; the time is the block, the source and as-of its tooltip. */
.cal-block .cal-item--slot {
  align-items: center;
  padding: 0 var(--space-1);
  line-height: 1;
  background: color-mix(in srgb, var(--cal-tone) 14%, var(--color-surface));
  font-size: var(--font-xxs);
}

/* A gone slot in the hour rows keeps its block — same place, same size — so
   the day's shape stays readable; only its fill says what became of it. */
.cal-block .cal-item--slot.cal-item--gone-booked { background: var(--color-surface-muted); }
.cal-block .cal-item--slot.cal-item--gone-withdrawn { background: var(--cal-hatch), var(--color-surface); }

/* Our booking among them: solid, so it reads as ours at a glance. */
.cal-block .cal-item--booking {
  flex-wrap: nowrap;
  align-items: center;
  padding: 0 var(--space-1);
  background: var(--cal-tone);
  color: var(--color-surface);
  font-size: var(--font-xxs);
  line-height: 1;
  white-space: nowrap;
}

.cal-block .cal-item--booking .cal-item__time { color: inherit; }

/* The Mark form in a calendar host's action slot (#1224). */
.cal-mark-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 32rem;
}

.cal-mark-form__range {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2);
}

.cal-mark-form__title {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-text);
}

.cal-mark-form__error {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-danger);
}

.cal-mark-form__actions {
  display: flex;
  gap: var(--space-2);
}

/* Picking many days at once (CalendarConfig.selection_fn, #1250): the page
   header's menu that turns it on, the strip saying it is on, every rows cell as a
   target, and the bar under the grid — the picks beside the consumer's form. */
.cal-actions {
  display: inline-flex;
  align-items: center;
}

.cal-selecting {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid color-mix(in srgb, var(--color-accent) 20%, transparent);
  background: color-mix(in srgb, var(--color-accent) 8%, transparent);
  color: var(--color-accent);
  font-size: var(--font-sm);
}

.cal-selecting__text {
  flex: 1;
  min-width: 0;
  color: var(--color-text);
}

.cal-selecting__text strong {
  color: var(--color-accent);
  font-weight: var(--weight-semibold);
}

.cal-select-hit {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: cell;
}

.cal-select-hit:disabled { cursor: not-allowed; }

.cal-select-hit:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
}

.cal-rows__cell.is-picked {
  background: color-mix(in srgb, var(--color-accent) 16%, transparent);
  outline: 1.5px solid var(--color-accent);
  outline-offset: -2px;
}

/* A day nobody may pick (a booking system's hall, a public holiday) recedes. */
.cal-rows__cell.is-refused > .cal-item { opacity: 0.5; }

.cal-select-col,
.cal-select-row {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.cal-select-col {
  display: block;
  width: 100%;
  border-radius: var(--radius-xs);
}

.cal-select-col:hover:not(:disabled) { background: var(--color-hover); }
.cal-select-col:disabled { cursor: default; }
.cal-select-row { text-align: left; }
.cal-select-row:hover { text-decoration: underline; }

.cal-select-col:focus-visible,
.cal-select-row:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.cal-select-bar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--space-5);
  align-items: end;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
  box-shadow: var(--shadow-floating);
}

.cal-select-bar__summary { min-width: 0; }

.cal-select-bar__count {
  margin: 0 0 var(--space-1);
  font-size: var(--font-base);
  font-weight: var(--weight-semibold);
  color: var(--color-text-strong);
}

.cal-select-bar__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 5.25rem;
  margin: 0;
  padding: 0;
  overflow: auto;
  list-style: none;
  font-size: var(--font-xs);
}

.cal-select-bar__list li {
  display: grid;
  grid-template-columns: 9rem minmax(0, 1fr);
  gap: var(--space-2);
}

.cal-select-bar__row {
  overflow: hidden;
  font-weight: var(--weight-medium);
  color: var(--color-text-strong);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cal-select-bar__days { color: var(--color-muted); }

.cal-select-bar__hint {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.cal-select-bar:not(.is-empty) .cal-select-bar__hint,
.cal-select-bar.is-empty .cal-select-bar__list { display: none; }

/* The town hall form in the selection bar (#1250): the reading, the note and
   the actions on one line under the picks. */
.cal-mark-many {
  display: grid;
  grid-template-columns: minmax(10rem, auto) minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: end;
}

.cal-mark-many .form-group { margin: 0; }

.cal-mark-many__error {
  grid-column: 1 / -1;
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-danger);
}

.cal-mark-many__actions {
  display: flex;
  gap: var(--space-2);
}

/* Saved views (#1229) — the rail's Views group: panel rows (.sb-row), the
   author's name trailing a colleague's view, and a row's edit and delete
   showing on hover or focus, the way a panel row's own add button does
   (.sb-row-add). The group's Save is always shown — it is the group's one
   action, not a row's. */
.cal-rail .cal-views { margin-top: var(--space-3); }
.cal-views .cal-views__save { opacity: 1; }

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

.cal-view .sb-row { gap: var(--space-2); }
.cal-view--editable .sb-row { padding-right: calc(var(--sb-row-pad) + var(--space-8) + var(--space-2)); }

.cal-view__by {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.cal-view__actions {
  position: absolute;
  top: 50%;
  right: calc(var(--sb-row-margin-x) + var(--space-1));
  display: inline-flex;
  gap: 2px;
  transform: translateY(-50%);
  opacity: 0;
  transition: opacity var(--transition-fast);
}

.cal-view:hover .cal-view__actions,
.cal-view:focus-within .cal-view__actions { opacity: 1; }

.cal-view__action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--color-muted);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.cal-view__action:hover {
  background: var(--color-hover);
  color: var(--color-text);
}

/* The form in the view dialog. */
.cal-view-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.cal-view-form .form-group { margin: 0; }

.cal-view-form__summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-text);
}

.cal-view-form__label { color: var(--color-muted); }

.cal-view-form__replace {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.cal-view-form__replace .cal-view-form__summary {
  padding-left: var(--space-6);
  color: var(--color-muted);
}

.cal-view-form__error {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-danger);
}

.cal-view-form__actions {
  display: flex;
  gap: var(--space-2);
}

/* The Team calendar's panel (#1227): the Team rows at their own height, the
   calendar's rail beneath taking the rest and scrolling inside it — each
   `.sb` is otherwise the panel's full height, which hid the rail below it. */
.nav-panel:has(> .sb--team + .cal-rail) {
  display: flex;
  flex-direction: column;
}

.sb--team:has(+ .cal-rail) {
  flex: 0 0 auto;
  height: auto;
  padding-bottom: 0;
}

.sb--team + .cal-rail {
  flex: 1 1 auto;
  min-height: 0;
  height: auto;
}

/* A row's cell action (a teammate's own row on the Team calendar, #1227):
   the glyph alone — the cell is a day wide — quiet until the cell is hovered. */
.cal-rows__cell .cal-day__action-label { display: none; }
/* No padding round the glyph: a fitted row's day can be narrower than a
   padded button, which pushed the grid a scroll wider than its card. */
.cal-rows__cell .cal-day__action { padding: 0; }
.cal-rows__cell:hover .cal-day__action { opacity: 1; }

/* The Team calendar (#1227): a teammate's leave in their row. Away is solid,
   Requested hatched over the same tone; neither carries a word in a row cell —
   the word is the tooltip's, the detail a click away. */
.cal-item--leave {
  /* The bar is its own size container: its word shows where the bar is wide
     enough to carry it (a week's day) and is the tooltip's and a screen
     reader's where it is not (a month's). */
  container: leave / inline-size;
  justify-content: center;
  width: 100%;
  min-height: 1.25rem;
  font-family: inherit;
}

@container leave (max-width: 3.5rem) {
  .cal-item--leave .cal-item__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

button.cal-item--leave {
  border-width: 0 0 0 2px;
  border-style: solid;
  border-color: var(--cal-tone);
  cursor: pointer;
}

.cal-item--leave:not(.cal-item--requested) {
  background: color-mix(in srgb, var(--cal-tone) 45%, transparent);
}

button.cal-item--leave:not(.cal-item--requested):hover {
  background: color-mix(in srgb, var(--cal-tone) 60%, transparent);
}

.cal-item--requested {
  border-left-style: dashed;
  background: var(--cal-hatch), color-mix(in srgb, var(--cal-tone) 14%, transparent);
}

/* A half-day (#1236): the fill — solid when approved, hatched when requested —
   covers only its half of the bar, the morning the left and the afternoon the
   right, over the faint tone the rest of the day keeps. A month's bar carries
   no word, so the half is what says which. */
.cal-item--part-morning { --leave-half: left; }
.cal-item--part-afternoon { --leave-half: right; }

.cal-item--leave:is(.cal-item--part-morning, .cal-item--part-afternoon) {
  --leave-fill: color-mix(in srgb, var(--cal-tone) 45%, transparent);
  background:
    linear-gradient(var(--leave-fill), var(--leave-fill)) var(--leave-half) / 50% 100% no-repeat,
    color-mix(in srgb, var(--cal-tone) 14%, transparent);
}

button.cal-item--leave:is(.cal-item--part-morning, .cal-item--part-afternoon):not(.cal-item--requested):hover {
  --leave-fill: color-mix(in srgb, var(--cal-tone) 60%, transparent);
  background:
    linear-gradient(var(--leave-fill), var(--leave-fill)) var(--leave-half) / 50% 100% no-repeat,
    color-mix(in srgb, var(--cal-tone) 14%, transparent);
}

.cal-item--requested:is(.cal-item--part-morning, .cal-item--part-afternoon) {
  background:
    var(--cal-hatch) var(--leave-half) / 50% 100% no-repeat,
    color-mix(in srgb, var(--cal-tone) 14%, transparent);
}

/* A teammate's row label (CalendarConfig.row_template): a green dot when they
   have the team app open, the name truncating beside it. */
.team-row-label {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}

.team-row-label__dot {
  flex: 0 0 auto;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--color-success);
}

.team-row-label__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The office's row: its weekly hours under its name (CalendarRow.note). */
.team-row-label--stacked {
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
}

.team-row-label--stacked > * {
  max-width: 100%;
}

/* The request form and one teammate's leave, in the action slot (#1227). */
.leave-form,
.leave-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 40rem;
}

.leave-detail__title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-text);
}

.leave-form__hint {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-muted);
}

.leave-form__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--space-3);
}

.leave-form__actions {
  display: flex;
  gap: var(--space-2);
}

.leave-detail__facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-1) var(--space-3);
  margin: 0;
  font-size: var(--font-xs);
}

.leave-detail__facts dt { color: var(--color-muted); }
.leave-detail__facts dd { margin: 0; }

.leave-error {
  margin: 0;
  font-size: var(--font-xs);
  color: var(--color-danger);
}

/* The strip on the case page (#1224), under the town-hall section's fields. */
.case-wedding-strip { margin-top: var(--space-2); }

.case-wedding-strip__warning {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  color: var(--color-warning-text);
  font-size: var(--font-xs);
}

/* A day notice on the couple's day (#1399) — the calendar's own amber, so it
   reads as the same thing the grid draws. */
.case-wedding-strip__notice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-left: 2px solid var(--color-service-amber);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-service-amber) 14%, transparent);
  color: var(--color-text-strong);
  font-size: var(--font-xs);
}

.case-wedding-strip__notice .icon { flex: 0 0 auto; margin-top: 1px; color: var(--color-service-amber); }

/* Expiry warnings on the case overview (#1259) — the strip's warning row,
   one per document failing an expiry rule, at the top of the details panel.
   Danger-tinted when a blocking rule (DK-3) fails. */
.case-expiry-warnings {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
}

.case-expiry-warning {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-warning) 12%, transparent);
  color: var(--color-warning-text);
  font-size: var(--font-xs);
}

.case-expiry-warning--blocking {
  background: color-mix(in srgb, var(--color-danger) 8%, transparent);
  color: var(--color-danger);
}

.case-expiry-warning__body {
  flex: 1;
  min-width: 0;
}

.case-expiry-warning__head {
  margin: 0;
  font-weight: var(--weight-semibold);
}

.case-expiry-warning__head a {
  color: inherit;
}

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

.case-expiry-warning__rule {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

/* The case's Payments page (#1426, Main.dc.html): the sales column beside the
   case's right-hand pane — the overview's two-column shape, with a plain
   scrolling column and the house gutter where the overview has its timeline. */
.case-payments {
  flex: 1 1 auto;
  min-width: 0;
  overflow-y: auto;
  padding: var(--space-ws-gutter);
}

.case-payments > .system-section-head:first-child {
  margin-top: 0;
}

/* The picked sale's row, outside a list view: the fill a list view gives its
   picked row. */
.case-payments .data-table tr.clickable-row.is-active {
  background: var(--color-surface-muted);
}

/* The sale row's actions menu sits in a table cell, so it opens under its
   own trigger instead of the header's. */
.case-payments td .mh-picker--more {
  position: relative;
  display: inline-block;
}

.case-payments td .mh-picker-menu--more {
  position: absolute;
  top: calc(100% + var(--space-1));
  right: 0;
  left: auto;
  z-index: var(--z-dropdown);
}

/* A refund on the case's Payments page (#1431, RefundRaise.dc.html,
   PaymentsRefunded.dc.html): its own section under the receipts, the card's
   inset holding the steps, the facts, the notes and the actions — mixed
   content, so it pads itself instead of a table's cells. */
.refund-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-5);
}

/* The four facts: the platform's one label voice over the values, four across. */
.refund-facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-5);
}

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

/* The refund's note box: the comment box's frame, collapsed to one line
   with its button beside it, in flow under the notes. */
.refund-note-form {
  flex-direction: row;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  padding-right: var(--space-2);
}

.refund-note-form .composer-wrap {
  flex: 1 1 auto;
  min-width: 0;
}

/* What a choice in the Raise a refund dialog hides — the bank fields for a
   card refund, the amount box until "a different amount" is picked — is
   hidden: a form group's display would otherwise win over [hidden]. */
.system-dialog-form [hidden] {
  display: none;
}

.case-payments .card + .system-section-head {
  margin-top: var(--space-6);
}

/* Failed card payments on the case's Payments page (#1425): one row per attempt,
   who declined it as a tag, the plain-language why, how they paid, and the
   Library page on what to tell the couple. */
.payment-attempt-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.payment-attempt {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  font-size: var(--font-xs);
}

.payment-attempt__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.payment-attempt__when,
.payment-attempt__how {
  color: var(--color-muted);
}

.payment-attempt__what,
.payment-attempt__how {
  margin: 0;
}

.payment-attempt__tell {
  font-weight: var(--weight-medium);
  color: var(--color-text);
  text-decoration: none;
}

.payment-attempt__tell:hover {
  text-decoration: underline;
}

/* The case pane's Calendar tab (#1224): the wedding calendar's pane host,
   filling the tab the way the timeline does. */
.case-pane-calendar,
.case-pane-calendar__host {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  min-height: 0;
}

.case-pane-calendar__host > .cal { flex: 1 1 0; }

.case-pane-calendar__empty {
  margin: 0;
  padding: var(--space-6) var(--space-4);
  color: var(--color-muted);
  font-size: var(--font-sm);
  text-align: center;
}

/* The town-hall cell on a phase board opens the hall's week in the pane. */
.board-layout--open [id^="lv-rows-"] td[data-col-key="town_hall"] { cursor: pointer; }
.board-layout--open [id^="lv-rows-"] td[data-col-key="town_hall"]:hover { text-decoration: underline; }

/* When the readings on show were last read (Freshness, #1224): quiet when
   fresh, amber when old enough to distrust — every host renders it. */
.cal-read {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: var(--font-xs);
  color: var(--color-muted);
  white-space: nowrap;
}

.cal-read.is-stale {
  padding: 0 var(--space-1);
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--color-warning) 14%, transparent);
  color: var(--color-warning-text);
  font-weight: var(--weight-medium);
}

.cal-read--row {
  display: flex;
  width: fit-content;
  font-size: var(--font-xxs);
}

/* ── What's New editor: the shipped-feedback picker (#1316) — an entry's
   linked feedback ticked, then every closed feedback issue no other update
   links, each with its GitLab labels; "Link by #iid" under the list. */

.wn-picker {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.wn-picker__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-height: 16rem;
  overflow-y: auto;
}

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

.wn-picker__labels {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.wn-picker__empty {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-muted);
}

.wn-picker__error,
.wn-picker__refusal {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-danger);
}

.wn-picker__lookup {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.wn-picker__lookup .form-control {
  width: 8rem;
}

/* ── Library › Feedback (#1325) — the board in the Help page's grouped table,
   and one item on a page of its own. */

.lib-feedback__num {
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}

.lib-feedback__update {
  font-size: var(--font-sm);
  color: var(--color-accent);
  text-decoration: none;
}

.lib-feedback__update:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.lib-feedback-item {
  max-width: 48rem;
}

.lib-feedback__error {
  margin: 0;
  font-size: var(--font-sm);
  color: var(--color-danger);
}

/* ── /system/whatsapp (2026-10-17) ────────────────────────────────────────
   A milestone's stage and template editor folds away under its read-only
   facts: the summary is the ghost button that opens it, and the stage
   checkboxes sit in one group per case type. */
.whatsapp-edit {
  padding: 0 var(--space-5) var(--space-5);
}

.whatsapp-edit > summary {
  list-style: none;
  display: inline-flex;
  margin-top: var(--space-4);
}

.whatsapp-edit > summary::-webkit-details-marker {
  display: none;
}

.whatsapp-edit[open] > summary {
  margin-bottom: var(--space-4);
}

.whatsapp-stage-group {
  margin-top: var(--space-4);
}

.whatsapp-settings .card > .status-dl + .data-table {
  margin-top: var(--space-4);
}

/* ── /system/information-needs (2026-10-17) ─────────────────────────────────
   A row per information need, a column per channel. Each cell lists the channel's
   wording per language; a language the channel asks with the default reads
   muted beside a Default tag. The editor folds away under the words, like a
   milestone's on /system/whatsapp. */
.information-needs__table th,
.information-needs__table td {
  vertical-align: top;
}

.information-needs__reader,
.information-needs__meta {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--font-xs);
  font-weight: var(--weight-normal);
  color: var(--color-muted);
}

.information-needs__number {
  display: block;
  font-weight: var(--weight-semibold);
}

.information-needs__key {
  display: block;
  color: var(--color-text-strong);
}

.need-wording + .need-wording {
  margin-top: var(--space-3);
}

.need-wording__lang {
  display: block;
  font-size: var(--font-xxs);
  font-weight: var(--weight-semibold);
  color: var(--color-muted);
}

.need-wording__text--default {
  color: var(--color-muted);
}

.need-wording-edit > summary {
  list-style: none;
  display: inline-flex;
  margin-top: var(--space-3);
}

.need-wording-edit > summary::-webkit-details-marker {
  display: none;
}

.need-wording-edit[open] > summary {
  margin-bottom: var(--space-3);
}

/* ── Stage merge (#1182) ─────────────────────────────────────────────────
   The stages tab's "Merge into…" panel and a merging stage's status row.
   The row spans the stage table; the block inside is a plain stack. */

.stage-merge-row > td {
  background: var(--color-surface-dim);
  padding: var(--space-4) var(--space-5);
}

.stage-merge {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  font-size: var(--font-sm);
}

.stage-merge__line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0;
}

.stage-merge__note {
  margin: 0;
  color: var(--color-subtle);
}

.stage-merge__failure-list {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-5);
}

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

.stage-merge__actions .form-label {
  margin: 0;
}

.stage-merge__select {
  width: auto;
  min-width: 16rem;
  max-width: 100%;
}

/* The edit dialogs on /system/services: a form in two columns, grouped by
   fieldset with a heading in the subheading voice (labels keep the one label
   voice), and the footer bar carrying the row's other actions on the left
   and Cancel + Save on the right. */
.system-dialog-form {
  display: grid;
  gap: var(--space-5);
}

.system-fieldset {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4) var(--space-5);
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.system-fieldset + .system-fieldset {
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}

.system-fieldset__legend {
  grid-column: 1 / -1;
  padding: 0;
  margin-bottom: var(--space-1);
  font-size: var(--font-sm);
  font-weight: var(--weight-semibold);
}

.system-fieldset > .form-group {
  margin: 0;
  min-width: 0;
}

.system-fieldset > .form-group--full {
  grid-column: 1 / -1;
}

.system-dialog-side {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* A figure and the lines it was reached from, stacked inside a table cell on
   /system/services: the total, then one row per line with the amounts in their
   own right-aligned column so the arithmetic reads down, not across. Same size
   and colour as every other cell (/ux body-text-style, table-cell-font). */
.price-breakdown {
  display: grid;
  justify-items: start;
  gap: var(--space-1);
  text-align: left;
}

/* The total and the amounts flush with the cell's right edge, labels and
   notes on the left. */
.price-breakdown__total {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  justify-self: stretch;
}

/* Ruled like a sum: a subtotal has one line above, the result one above and
   two below. */
.price-breakdown__total--sub,
.price-breakdown__total--sum {
  padding: var(--space-1) 0;
  border-top: 1px solid var(--color-border);
}

.price-breakdown__total--sum {
  border-bottom: 3px double var(--color-border);
}

.price-breakdown__amount {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.price-breakdown__lines {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--space-3);
  justify-self: stretch;
  margin: 0;
}

/* The price on its own line above the deductions, ruled off from them. */
.price-breakdown__lines--price {
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border);
}

.price-breakdown__lines dt {
  text-align: left;
  font-weight: var(--weight-normal);
}

.price-breakdown__lines dd {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* A column of checkboxes, one choice per line: the package editor's
   sellables on /finance, where each label carries a price and a status. */
.check-row--stack {
  flex-direction: column;
  align-items: flex-start;
}

/* The package editor's derived sum and the warning under it, one fieldset
   row apart, swapped together as sellables are picked. */
.package-derived {
  display: grid;
  gap: var(--space-4);
}

/* Mark / unmark offerable under a package's offerable state on /finance ›
   Packages: the same gap as the reasons above it. */
.package-offer-form {
  margin-top: var(--space-1);
}
