/* ═══════════════════════════════════════════════════════════════════════════
   PHNTM // PRIMITIVES  (P43.2)

   Eight components, built once, in the shared stylesheet. NOT APPLIED TO ANY
   MODULE in this dispatch: that is stage 5 and it is sequenced per module. The
   regression surface is /dev/primitives.

   Why: 32 chip implementations, 22 button, 16 row, 15 panel, 10 field, 8 card,
   578 unique class names, and 264KB of inline CSS against 55KB shared. Every
   one of those was written because there was nothing to reuse.

   PAINTED SIZE AND HIT SIZE ARE DIFFERENT NUMBERS throughout. Material: "If
   required, the target can extend beyond the visible container of the chip."
   A chip paints at 32dp and hit-tests at 48dp. That is what lets the system be
   dense and still pass WCAG 2.5.8.

   LINE HEIGHT IS NEVER LOCKED. WCAG 1.4.12 requires layouts to survive
   line-height at 1.5x, so every value here is a starting point, not a cage.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. CHIP ─────────────────────────────────────────────────────────────
   32dp painted, 48dp target via a pseudo-element that may overhang. Labels
   under 20 characters. Wraps by default; a single-row rail may scroll. */
.p-chip {
  position: relative;
  display: inline-flex; align-items: center; gap: var(--space100);
  height: var(--chip-painted);
  padding: 0 var(--space200);
  border-radius: var(--radius-chip);
  border: 1px solid var(--outline-variant);
  background: transparent;
  color: var(--on-surface);
  font-family: inherit;
  font-size: max(var(--label-medium-size), var(--fs-floor, 0px));
  letter-spacing: var(--label-medium-track);
  white-space: nowrap;
  cursor: pointer;
  box-sizing: border-box;
}
.p-chip:has(.p-chip-icon) { padding: 0 var(--space100); }
.p-chip-icon { width: var(--icon-size); height: var(--icon-size); flex: 0 0 auto; }
/* The hit target, allowed to extend past the painted box. pointer-events stays
   on so it actually catches the tap, unlike the P37.2 decorative overlays. */
.p-chip::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 100%; min-width: var(--target-min);
  height: var(--target-min);
}
.p-chip--assist     { border-color: var(--outline-variant); }
.p-chip--filter     { border-color: var(--outline-variant); }
.p-chip--filter[aria-pressed="true"],
.p-chip--filter.is-selected {
  background: var(--surface-high); border-color: var(--outline); color: var(--on-surface);
}
.p-chip--input      { border-color: var(--outline); }
.p-chip--suggestion { border-style: dashed; color: var(--on-surface-variant); }
.p-chip[disabled], .p-chip[aria-disabled="true"] { opacity: 0.38; cursor: default; }
.p-chip-rail { display: flex; flex-wrap: wrap; gap: var(--space100); }
/* P46.4: the full P33.7 treatment, on the primitive so there is ONE
   implementation. P33.7 fixed .cqp-rail on CORTEX; the dashboard has its own
   .cortex-rail which never received it, so the third chip rendered as
   "Open actio…" cut at the viewport edge. That is the census problem, not a
   regression: two rails, one fix. Both legacy selectors are aliased below
   until stage 5 replaces their markup with the primitive class. */
.p-chip-rail--scroll,
.cqp-rail, .cortex-rail {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  scrollbar-width: none;              /* Firefox */
  -ms-overflow-style: none;           /* old Edge */
  /* Trailing padding so the last chip can scroll fully clear of the edge
     instead of stopping against it. */
  padding-right: var(--space400);
  /* Fade so it is visible that more exists. Removed at the end, or the last
     chip is dimmed by a hint about content already on screen. */
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--space400)), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - var(--space400)), transparent 100%);
}
.p-chip-rail--scroll::-webkit-scrollbar,
.cqp-rail::-webkit-scrollbar, .cortex-rail::-webkit-scrollbar { display: none; }
.p-chip-rail--scroll > *, .cqp-rail > *, .cortex-rail > * { scroll-snap-align: start; flex: 0 0 auto; }
.p-chip-rail--scroll.at-end, .cqp-rail.at-end, .cortex-rail.at-end {
  -webkit-mask-image: none; mask-image: none;
}

/* ── 2. BADGE ────────────────────────────────────────────────────────────
   A badge is NOT a chip. It is a status marker anchored to an icon's upper
   trailing edge, never interactive, never wider than four characters. PHNTM
   currently confuses the two, which is why 32 chip implementations exist. */
/* Padding equal to the badge overhang. Without it every anchor reports as
   self-clipping by 2px, which is a real measurement rather than a false
   positive: the badge genuinely sits outside its parent's box. */
.p-badge-anchor { position: relative; display: inline-flex; padding: var(--badge-inset) var(--badge-inset) 0 0; }
/* Anchored INSIDE the padding rather than overhanging it. The first attempt
   kept top/right at -2px, so the padding moved the anchor without containing
   the badge and every anchor still self-clipped by exactly the overhang. */
.p-badge-anchor > .p-badge { top: 0; right: 0; }
.p-badge {
  position: absolute; top: -2px; right: -2px;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--badge-size); height: var(--badge-size); padding: 0 var(--space50);
  border-radius: var(--radius-card);
  background: var(--azul); color: var(--on-accent);
  font-size: max(var(--label-small-size), var(--fs-floor, 0px));
  letter-spacing: var(--label-small-track);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.p-badge--dot { min-width: var(--badge-dot); width: var(--badge-dot);
                height: var(--badge-dot); padding: 0; border-radius: 50%; }

/* ── 3. BUTTON ───────────────────────────────────────────────────────────
   Exactly three emphasis levels. A BUTTON GROUP WRAPS: it never scrolls
   horizontally and never clips a label, which is what DISPATCH's five-across
   action row does today. */
.p-btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space100);
  min-height: var(--target-min);
  min-width: var(--target-min);
  padding: 0 var(--space300);
  border-radius: var(--radius-chip);
  font-family: inherit;
  font-size: max(var(--label-large-size), var(--fs-floor, 0px));
  letter-spacing: var(--label-large-track);
  cursor: pointer;
  box-sizing: border-box;
  text-decoration: none;
  border: 1px solid transparent;
}
.p-btn--primary   { background: var(--azul); color: var(--on-accent); font-weight: 600; }
.p-btn--secondary { background: transparent; color: var(--azul); border-color: var(--outline); }
.p-btn--tertiary  { background: transparent; color: var(--azul); border-color: transparent; padding: 0 var(--space150); }
.p-btn[disabled], .p-btn[aria-disabled="true"] { opacity: 0.38; cursor: default; }
.p-btn--icon { padding: 0; width: var(--target-min); }
.p-btn--icon svg { width: var(--icon-btn-glyph); height: var(--icon-btn-glyph); }
.p-btn-group { display: flex; flex-wrap: wrap; gap: var(--space100); }

/* ── 4. FIELD ────────────────────────────────────────────────────────────
   Label ABOVE, never beside: a side label does not survive Compact. One
   implementation for search, text and select. */
.p-field { display: flex; flex-direction: column; gap: var(--space50); }
.p-field-label {
  font-size: max(var(--label-medium-size), var(--fs-floor, 0px));
  letter-spacing: var(--label-medium-track);
  color: var(--on-surface-variant);
}
.p-field-control {
  min-height: var(--target-min);
  padding: 0 var(--space150);
  border-radius: var(--radius-chip);
  border: 1px solid var(--outline-variant);
  background: var(--surface-low);
  color: var(--on-surface);
  font-family: inherit;
  font-size: max(var(--body-medium-size), var(--fs-floor, 0px));
  box-sizing: border-box; width: 100%;
}
.p-field-control:focus-visible { outline: var(--focus-ring) solid var(--azul); outline-offset: 1px; }
textarea.p-field-control { padding: var(--space150); resize: vertical; }
.p-field-help {
  font-size: max(var(--body-small-size), var(--fs-floor, 0px));
  color: var(--on-surface-variant);
}

/* ── 5. LIST ROW ─────────────────────────────────────────────────────────
   56 / 72 / 88dp. Content top-aligns at 88 and above, because centring three
   lines against a leading icon looks broken. */
.p-row {
  display: flex; align-items: center; gap: var(--space200);
  min-height: var(--list-row-height);
  padding: var(--space100) var(--space300) var(--space100) var(--space200);
  box-sizing: border-box;
  text-decoration: none; color: var(--on-surface);
}
.p-row--two   { min-height: var(--row-h-two-line); }
.p-row--three { min-height: var(--row-h-three-line); align-items: flex-start; padding-top: var(--space200); }
.p-row-body   { flex: 1 1 auto; min-width: 0; }
.p-row-title  { font-size: max(var(--body-large-size), var(--fs-floor, 0px)); }
.p-row-support {
  font-size: max(var(--body-medium-size), var(--fs-floor, 0px));
  color: var(--on-surface-variant);
}
.p-row-trailing { flex: 0 0 auto; display: flex; align-items: center; gap: var(--space100); }
/* Inset between items, full width between sections. */
.p-row + .p-row { border-top: 1px solid var(--outline-variant); }
.p-list--inset .p-row + .p-row { margin-left: var(--space200); }
.p-divider { height: 1px; background: var(--outline-variant); border: 0; margin: 0; }

/* ── 6. CARD ─────────────────────────────────────────────────────────────
   No shadow unless it sits on a busy background. Tone does the separating. */
.p-card {
  background: var(--surface-low);
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-card);
  padding: var(--space200);
  box-sizing: border-box;
}
.p-card--raised { background: var(--surface-high); }

/* ── 7. SECTION HEADER ───────────────────────────────────────────────────
   PHNTM's existing treatment, formalised. 11px is legitimate here and only
   here, because it is ALL CAPS and tracked. */
.p-section-header {
  font-size: max(var(--label-small-size), var(--fs-floor, 0px));
  line-height: var(--label-small-lh);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
  margin: var(--space300) 0 var(--space100);
}

/* ── 8. APP BAR ──────────────────────────────────────────────────────────
   RESERVES ITS OWN LANE for any fixed control rather than letting one float
   over page content. This generalises the P42.3 drawer fix instead of leaving
   it as a per-page special case. */
.p-appbar {
  display: flex; align-items: center; gap: var(--space200);
  min-height: var(--nav-item-height);
  padding: 0 var(--space200);
  padding-left: var(--p-appbar-lane, var(--space200));
  background: var(--surface-container);
  box-sizing: border-box;
}
.p-appbar-title {
  font-size: max(var(--title-large-size), var(--fs-floor, 0px));
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.p-appbar-actions { margin-left: auto; display: flex; gap: var(--space100); }

/* ── Cross-cutting: measure ──────────────────────────────────────────────
   Rule 6 of the P43.3 audit fails any prose line over 60 characters, and it
   caught this stylesheet's own demo copy at 140ch. A cap belongs in the
   system, not in each page that happens to remember. */
.p-prose, .p-row-support, .p-field-help { max-width: 60ch; }

/* ── Cross-cutting: middle truncation for identifiers ────────────────────
   Apple: "an ellipsis in the middle of text can make an item easier to
   distinguish because it preserves both the beginning and the end." Meeting
   titles are exactly this case and currently lose the date, which is the part
   that tells two standups apart. CSS cannot truncate mid-string, so the JS in
   /static/js/middle-truncate.js does it and this is the styling contract. */
.p-truncate-middle { display: inline-block; max-width: 100%; white-space: nowrap; overflow: hidden; }
.p-truncate-end    { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.p-truncate-2 {
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
}

/* ── Cross-cutting: glass on chrome only, never on content ───────────────
   Always behind a support query with an opaque fallback, so a browser without
   backdrop-filter gets a readable surface rather than a transparent one. */
.p-glass { background: var(--surface-container); }
@supports (backdrop-filter: blur(1px)) {
  .p-glass {
    background: color-mix(in srgb, var(--surface-container) 82%, transparent);
    backdrop-filter: blur(var(--scrim-blur)) saturate(140%);
    -webkit-backdrop-filter: blur(var(--scrim-blur)) saturate(140%);
  }
}

/* ── Responsive margins, Material's tiers (P43.4) ────────────────────────
   Compact 16dp, everything above 24dp. */
.p-pane { padding-inline: var(--space200); }
@media (min-width: 600px) { .p-pane { padding-inline: var(--space300); } }

/* ── App bar / page header fit (P44.4) ───────────────────────────────────
   These moved here when the drawer partial was deleted. They are NOT the
   P42.3 lane rule, which is gone with the floating button that caused it:
   there is no padding-left and no !important. What remains is the part that
   was always about headers fitting their own content, and removing it
   regressed /action-items at 360 by 4 elements the moment the partial went.

   The app bar reserves a lane for controls INSIDE it, which is the primitive's
   own --p-appbar-lane, rather than for anything floating over it. */
header, .page-header {
  flex-wrap: wrap;
  min-width: 0;
  box-sizing: border-box;
}
header > *, .page-header > *,
header .title-block, .page-header .title-block {
  min-width: 0;
  flex-shrink: 1;
}
header .title-block > *, .page-header .title-block > * {
  min-width: 0;
  overflow-wrap: anywhere;
}
header h1, .page-header h1 { min-width: 0; overflow-wrap: anywhere; }
header img, .page-header img { max-width: 100%; height: auto; }


/* ── Shell bottom clearance (P46.3) ──────────────────────────────────────
   The FAB floats over content, which is correct, but it was sitting on the
   NEXT ON CALENDAR card and covering the PREP READY chip, which is not: a FAB
   may obscure text, never an interactive control. The scrolling container
   reserves the bar, the FAB, 16dp of clearance and the safe-area inset, so
   the last element on any page can always be scrolled clear of both.

   Lives here rather than in each page, because "every page needs bottom
   room" is a shell property and the alternative is 40 places to forget. */
:root { --fab-size: 56px; }
body.has-nav {
  padding-bottom: calc(
    var(--nav-bar-height) + var(--fab-size) + var(--space200)
    + env(safe-area-inset-bottom, 0px));
}
@media (min-width: 600px) {
  /* No bar at this width, but the FAB is still there. */
  body.has-nav { padding-bottom: calc(var(--fab-size) + var(--space200)); }
}


/* ── P48.1: the painted box and the pointer target are different things ────
   P43 and P46 raised the interactive floor by setting min-height, which grows
   the painted box. Material 3 and Apple HIG both treat container size and
   target size as independent: a chip is painted at 32dp and the 48dp target
   is a transparent expansion around it. Conflating the two is what made
   everything bulky.

   Measured before this change, at 360x780 across five pages, only two classes
   actually had their label pinned to the top edge rather than centred:
   span.flag-badge (764 instances, text centre 13.1px above the box centre)
   and summary (1 instance, 14.5px). Native <button> centres its content via
   UA internal layout even at display: block and align-items: normal, so the
   blanket display change would have been risk without benefit. */
.target-expand, .flag-badge.clickable { position: relative; }
.target-expand::after, .flag-badge.clickable::after {
  content: ''; position: absolute;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  min-width: var(--target-min); min-height: var(--target-min);
  width: 100%; height: 100%;
}

/* ── P52.2: [hidden] is a contract, not a style preference ────────────────
   The UA says [hidden] { display: none } at an origin any author rule beats.
   So the moment a selector sets display to anything else, the attribute stops
   working for everything it matches and the element renders while the code
   believes it is hidden. That has now been found and fixed five separate
   times: cmdk, shortcuts, hovercard and selmenu each got their own guard, and
   .sb-item surfaced by accident in P48.7 as the 7px en-dash stub the operator
   measured on his phone.

   The audit built for this counts 638 selectors that set display and 238 with
   no guard of equal specificity. Guarding those one at a time is how you get a
   239th. !important is the right tool exactly here and almost nowhere else,
   because this is a semantic contract rather than a preference, and because
   specificity is the thing that failed. scribe.css already did this locally at
   line 453; this promotes it to the layer every page loads.

   Checked before applying: no rule anywhere in this codebase deliberately
   displays a [hidden] element, so nothing depends on the attribute being
   overridable. */
[hidden] { display: none !important; }

/* ── P52.1: min-width: 0 as the default, not the exception ────────────────
   A flex or grid item defaults to min-width: auto, which is its CONTENT size,
   so it refuses to shrink. Text then cannot ellipsis and a fixed trailing
   control gets pushed off the right edge of its own row. Rule 10 measured that
   directly: 95 hits on /action-items at 360dp, one per record, all 95 inside a
   flex parent with min-width: auto. At 320dp it doubles to 191, which is the
   text starting to escape alongside the control.

   Applied through :where() so it carries zero specificity and any deliberate
   rule beats it without an !important. It is safe to apply this widely because
   min-width: auto only resolves to a content size for flex and grid ITEMS;
   for everything else it already behaves as 0. So this changes exactly the
   elements that were broken and nothing else. */
:where(div, span, a, p, li, ul, ol, section, article, header, footer, main,
       aside, nav, label, form, figure, h1, h2, h3, h4, h5, h6) {
  min-width: 0;
}

/* A control cluster wraps rather than running off the edge. .actions measured
   338px inside a 312px right-col, which is what pushed its buttons out. */
:where(.actions, .right-col, [class$="-actions"], [class$="-controls"]) {
  flex-wrap: wrap;
  min-width: 0;
}

/* Fixed trailing furniture holds its painted width instead of being the thing
   that gets squeezed. Timestamps also stop their digits jittering. */
:where(button, .p-badge, .flag-badge, [class$="-btn"], [class*="-btn "]) {
  flex-shrink: 0;
}
:where(time, [class$="-time"], [class$="-date"], [class$="-timestamp"]) {
  flex-shrink: 0; font-variant-numeric: tabular-nums;
  margin-inline-start: var(--space100);
}

/* A summary is display: list-item, so centring cannot come from flex without
   losing the disclosure marker. P48.1 measured -14.5px of drift and left it;
   this closes it with block-layout centring instead. */
summary { align-content: center; }

/* ══════════════════════════════════════════════════════════════════════════
   P53.1  —  THE ROW PRIMITIVE

   One definition, used by every list from here on. Not a CAPTURE class: the
   whole point of putting it here is that P54, P55 and P56 adopt it rather than
   growing three more variants of it.

   TWO HEIGHTS, CHOSEN BY THE DATA. The operator: "if there's no status, I
   don't need a line just for the grey dot, we can just remove that grey dot,
   and shrink the height to one line." So a reviewed row emits no status node
   at all, not a hidden one and not an empty one, or the gap and the floor are
   both wrong.

   min-height AND REAL PADDING, NEVER height. His Android font scaling is not
   known yet. A row that grows is correct at any scale; a row fixed at 48px is
   wrong the moment his text renders larger.
   ══════════════════════════════════════════════════════════════════════════ */
/* Matched to the element and an attribute so this is (0,2,1), a genuine tie
   with theme.css's a[class]:not(.inline-link), which pins min-height to
   --tap-floor. The corrected load order from P46 settles the tie in favour of
   the primitive. Without this the row measured a 44px floor instead of 48. */
a.row[data-state], .row {
  display: flex;
  align-items: center;
  gap: var(--space150);
  padding: var(--space175) var(--space200);
  min-height: var(--row-h-one);
  border-bottom: 1px solid var(--outline-variant);
  position: relative;
  box-sizing: border-box;
  /* P54.0. The row is an <a>, so without this every title and timestamp
     carries the UA underline and the visited colour. The old .recording-card
     carried this reset itself and it went out with the card in P53; the
     primitive did not reproduce it. Worth stating precisely, because the
     suspicion fell on a[class]:not(.inline-link): that selector never carried
     a text-decoration reset, only display, align-items and min-height, so it
     is not implicated here and the P53 specificity work did not cause this. */
  text-decoration: none;
  color: inherit;
}
/* :visited must not repaint a list the operator has already opened half of. */
a.row:visited, a.row:visited .row__title, a.row:visited .row__date { color: inherit; }
a.row[data-state].row--status, .row--status {
  padding-block: var(--space125);
  min-height: var(--row-h-two);
}
.row__body {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  gap: var(--space25); justify-content: center;
}
.row__status {
  display: flex; align-items: center; gap: var(--space75);
  min-height: var(--line-micro);
}
.row__dot {
  width: var(--dot); height: var(--dot); border-radius: 50%;
  flex-shrink: 0; background: var(--state-waiting);
}
.row__state {
  font-size: var(--label-small-size); line-height: var(--label-small-lh);
  letter-spacing: var(--label-small-track);
  text-transform: uppercase; white-space: nowrap;
  color: var(--on-surface-variant);
}
.row__titleline { display: flex; align-items: flex-start; gap: var(--space100); }
.row__swatch {
  width: var(--swatch); height: var(--swatch);
  border-radius: var(--radius-xs); flex-shrink: 0;
  margin-top: var(--swatch-offset);
  background: var(--co-unknown);
}
/* min-width: 0 here and flex-shrink: 0 on the date are not optional. They are
   the P52 fix expressed in the primitive, and they are why this row cannot
   reintroduce the escaped-content defect rule 10 was built to catch. */
.row__title {
  flex: 1; min-width: 0;
  font-size: var(--text-row-size); line-height: var(--text-row-lh);
  letter-spacing: var(--text-row-track);
  color: var(--on-surface);
  display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
}
.row__date {
  font-size: var(--label-small-size); line-height: var(--label-small-lh);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums; flex-shrink: 0;
}

/* Company swatch by data attribute. No inline colours, no literals, and an
   explicit unknown fallback rather than an absent background. */
.row__swatch[data-co="ashling-partners"] { background: var(--co-ashling); }
.row__swatch[data-co="boeing"]           { background: var(--co-boeing); }
.row__swatch[data-co="unknown"]          { background: var(--co-unknown); }

/* States. The dot colour and the label carry the state together, so greyscale
   still separates them: WCAG 1.4.1 is not satisfied by colour alone. */
.row[data-state="undefined"]   .row__dot { background: var(--state-waiting); }
.row[data-state="unreviewed"]  .row__dot { background: var(--state-waiting); }
.row[data-state="failed"]      .row__dot { background: var(--state-failed); }
/* "now" joins the active group rather than getting rules of its own: a meeting
   in progress is the same class of thing as a recording in progress, and the
   operator reads the blinking dot as "this is happening". P65 rail. */
.row[data-state="transcribing"] .row__dot,
.row[data-state="identifying"]  .row__dot,
.row[data-state="summarising"]  .row__dot,
.row[data-state="now"]          .row__dot { background: var(--state-active); }

/* Blue blinks because something is actively changing. Amber and red do not. */
@keyframes row-dot-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
.row[data-state="transcribing"] .row__dot,
.row[data-state="identifying"]  .row__dot,
.row[data-state="summarising"]  .row__dot,
.row[data-state="now"]          .row__dot { animation: row-dot-blink 1.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .row[data-state] .row__dot { animation: none; }
}

/* Undefined is a question addressed to the operator, not a queue position:
   "it usually sits if it's unknown, meaning it can't determine from my
   calendar. once i confirm it or give it a name then it starts." So the title
   slot carries the duration instead, and the action sits on the row. */
.row[data-state="undefined"] .row__title {
  color: var(--text-dim); font-style: italic; font-weight: 400;
}
.row__action {
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--chip-painted);
  padding: 0 var(--space150);
  border: 1px solid var(--outline-variant); border-radius: var(--radius-chip);
  background: transparent; color: var(--on-surface);
  font-size: var(--label-small-size); white-space: nowrap;
}
/* 28dp painted, 48dp target, via the P48.1 expansion rather than by growing
   the painted box. */
.row__action { position: relative; }
.row__action::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  min-width: var(--target-min); min-height: var(--target-min);
  width: 100%; height: 100%;
}

/* ══════════════════════════════════════════════════════════════════════════
   P53.3  —  THE APP BAR PRIMITIVE

   P52 found that there is no shared app bar and that .p-appbar was used by
   zero templates. That is why the overflow trigger had to be position: fixed,
   and it is the root of the header inconsistency running back several
   dispatches. So the app bar is built here as a primitive, in normal document
   flow, and CAPTURE adopts it. P54, P55 and P56 migrate the rest as each
   module is rebuilt.
   ══════════════════════════════════════════════════════════════════════════ */
.appbar {
  display: flex; align-items: center; gap: var(--space150);
  min-height: var(--space600);                /* 52dp floor, grows with scale */
  padding: var(--space100) var(--space200);
  background: var(--surface);
  box-sizing: border-box;
}
.appbar__titles { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.appbar__title {
  font-size: var(--title-small-size); line-height: var(--title-small-lh);
  letter-spacing: var(--title-small-track);
  color: var(--on-surface);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.appbar__subtitle {
  font-size: var(--label-small-size); line-height: var(--label-small-lh);
  color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.appbar__actions { display: flex; align-items: center; gap: var(--space50); flex-shrink: 0; }
/* The overflow trigger becomes a member of this slot rather than an overlay.
   The P47.1 target and aria-controls wiring are unchanged. */
.appbar__actions .nav-more { position: static; background: transparent; }

/* Day group header. Sits on the canvas, never inside a container. */
.row-group {
  font-size: var(--label-small-size); line-height: var(--label-small-lh);
  letter-spacing: var(--label-small-track);
  text-transform: uppercase; color: var(--text-muted);
  padding: var(--space175) var(--space200) var(--space75);
}

/* Scroll clearance. Nav bar plus a 16dp margin, plus the safe area. Derived
   rather than guessed, because neither Material nor Apple publishes a number
   for a bar plus a list, and the derivation is written down so it survives
   review. */
.row-list { padding-bottom: calc(var(--nav-bar-height) + var(--space200)
            + env(safe-area-inset-bottom, 0px)); }

/* ── P54.1: the chip rail ──────────────────────────────────────────────────
   One row, horizontally scrollable, and the only filter surface on the page.
   Chips are painted at 32dp and take their 48dp target from the P48.1
   expansion rather than by growing, which is the same separation the row uses.
   The rail is a scroll container by design, so rule 10 correctly exempts what
   scrolls out of it. */
.chip-rail {
  display: flex; gap: var(--space100);
  padding: var(--space100) var(--space200) var(--space150);
  overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.chip-rail::-webkit-scrollbar { display: none; }
.chip-rail .p-chip {
  flex-shrink: 0; position: relative;
  display: inline-flex; align-items: center; gap: var(--space75);
  min-height: var(--chip-painted);
  padding: 0 var(--space150);
  border: 1px solid var(--outline-variant); border-radius: var(--radius-chip);
  background: transparent; color: var(--on-surface-variant);
  font-size: var(--label-small-size); letter-spacing: var(--label-small-track);
  white-space: nowrap;
  /* letter-spacing adds a trailing advance after the last glyph, which pushed
     scrollWidth 3px past clientWidth on the shortest chip and tripped rule 9.
     Sizing to content rather than to the flex base absorbs it. */
  width: max-content;
}
.chip-rail .p-chip::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  min-width: var(--target-min); min-height: var(--target-min);
  width: 100%; height: 100%;
}
.chip-rail .p-chip.is-on {
  background: var(--surface-high); color: var(--on-surface);
  border-color: var(--outline);
}
/* The company chip carries the same swatch as the rows beneath it, so the
   connection reads without a legend. */
.chip-swatch {
  width: var(--swatch); height: var(--swatch); border-radius: var(--radius-xs);
  flex-shrink: 0; background: var(--co-unknown);
}
.chip-swatch[data-co="ashling-partners"] { background: var(--co-ashling); }
.chip-swatch[data-co="boeing"]           { background: var(--co-boeing); }

/* Sort and refresh, set rarely, so they cost a menu rather than a permanent
   row. Opened from the app bar's action slot. */
.capture-sheet {
  display: flex; flex-direction: column;
  margin: 0 var(--space200) var(--space150);
  border: 1px solid var(--outline-variant); border-radius: var(--radius-card);
  background: var(--surface-container);
}
.capture-sheet__row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space150); min-height: var(--target-min);
  padding: 0 var(--space200);
  background: transparent; border: 0; color: var(--on-surface);
  font-size: var(--label-small-size); letter-spacing: var(--label-small-track);
  text-transform: uppercase; text-align: left;
}
.capture-sheet__row + .capture-sheet__row { border-top: 1px solid var(--outline-variant); }
.capture-sheet__row select {
  min-height: var(--target-min); background: transparent;
  color: var(--on-surface); border: 0;
  font-size: var(--label-small-size); max-width: 60%;
}

/* ── P55.2 / P55.3: the tools sheet ────────────────────────────────────────
   Upload, stats and search, closed by default and opened from the app bar.
   Not deleted: the upload path is unchanged and drag-and-drop still lands on
   the list surface, which is a larger target than the 147px zone ever was. */
.capture-tools {
  border: 1px solid var(--outline-variant); border-radius: var(--radius-card);
  margin: 0 var(--space200) var(--space150); padding: var(--space200);
  background: var(--surface-container);
}
.capture-tools__links {
  display: flex; gap: var(--space200); flex-wrap: wrap;
  margin-bottom: var(--space200);
}
.capture-tools__links a {
  min-height: var(--target-min); display: inline-flex; align-items: center;
  color: var(--text-muted); text-decoration: none;
  font-size: var(--label-small-size); letter-spacing: var(--label-small-track);
}
/* The JS-injected cross-vertical switcher duplicates what the nav bar already
   does, and cost 81px above the fold. Hidden on CAPTURE only; other surfaces
   that mount it are untouched. */
body.module-capture .vertical-switcher { display: none; }
/* The app bar supplies the separation this padding used to. !important is
   required rather than chosen: theme.css line 146 sets `main { padding:
   0.75rem !important }` inside a touch-tier query, and an important shorthand
   beats a normal longhand at any specificity. Matching it is the only way to
   override it, and it is scoped to this module so nothing else moves. */
body.module-capture main { padding-top: 0 !important; }

/* ── P56.2: HOME is three lists, not six cards ─────────────────────────────
   The card wrapper, its inner nesting and the section-head band are what put
   containers between the scroll root and the first row. The group label sits
   on the canvas exactly as it does on CAPTURE, and the list follows it. */
body.module-home .card {
  background: none; border: 0; border-radius: 0;
  padding: 0; margin: 0 0 var(--space150);
  box-shadow: none;
}
body.module-home .section-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space150);
  padding: var(--space175) var(--space200) var(--space75);
  min-height: 0;
}
body.module-home .section-head h2 {
  font-family: var(--font-mono);
  font-size: var(--label-small-size); line-height: var(--label-small-lh);
  letter-spacing: var(--label-small-track);
  text-transform: uppercase; color: var(--text-muted);
  margin: 0; font-weight: 400;
}
body.module-home main { padding-top: 0 !important; }
/* One stat row, not a card with an inner block. */
body.module-home .backlog-peek { display: flex; align-items: baseline; gap: var(--space150); min-width: 0; }
body.module-home .backlog-peek .num {
  font-size: var(--title-large-size); line-height: var(--title-large-lh);
  color: var(--azul); font-variant-numeric: tabular-nums; flex-shrink: 0;
}
body.module-home .backlog-peek .breakdown {
  font-size: var(--label-small-size); color: var(--text-muted); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── P56.3: nav labels must fit the box they were told to fit ──────────────
   They ellipsed 6px past their box at 320 and 3px at 360 with 1.3x scaling.
   The ellipsis was applied to an unconstrained box, so it never fired: a flex
   child defaults to min-width: auto, which is its content width. Constraining
   the item and the label is what lets text-overflow do its job. The font is
   NOT reduced below the type scale, which is the density trapdoor. */
.nav-bar__item, .nav-rail__item { overflow: hidden; }
.nav-bar__label, .nav-rail__label {
  min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Below this, a truncated label renders fewer than four characters and carries
   no meaning, so the icon carries the destination alone. The accessible name
   lives on the item's aria-label, so nothing is lost to a screen reader. */
@media (max-width: 359px) {
  .nav-bar__label { display: none; }
}
/* The section head is a label on the canvas, not a band. Its trailing link was
   inheriting the 44px tap floor and pushing the head to 64px. */
body.module-home .section-head .see-all {
  min-height: 0; font-size: var(--label-small-size);
  position: relative;
}
body.module-home .section-head .see-all::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  min-width: var(--target-min); min-height: var(--target-min);
  width: 100%; height: 100%;
}

/* PHNTM:company-colours generated from companies.color, do not hand edit */
[data-co="ashling-partners"]{background:var(--co-ashling-partners) !important}
[data-co="boeing"]{background:var(--co-boeing) !important}
[data-co="citizens"]{background:var(--co-citizens) !important}
[data-co="cvs"]{background:var(--co-cvs) !important}
[data-co="mayo-clinic"]{background:var(--co-mayo-clinic) !important}
[data-co="personal"]{background:var(--co-personal) !important}
[data-co="robotica-pro"]{background:var(--co-robotica-pro) !important}
/* PHNTM:company-colours end */

/* ── Segmented control, everywhere that is not a phone ──────────────────
   The segmented control was only ever styled inside hig.css, whose whole
   sheet sits behind (max-width: 767px). On a laptop the three LIBRARY tabs
   - Comics, Islamic, Books - therefore fell through to the browser's own
   button, which is a white pill with a grey bevel: three of them, in a dark
   app. Nothing was overriding it, because nothing outside the phone block
   ever named .seg.

   This is that missing base. It is the same control, drawn from the shared
   tokens rather than the phone sheet's local fills, and hig.css keeps
   winning on the phone because every rule there carries !important.
   ARKHM | 2026-09-22 */
.seg, .view-toggle {
  display: inline-flex; align-items: stretch; gap: 0;
  padding: var(--space25);
  background: var(--overlay-wash);
  border: 1px solid var(--border-divider);
  border-radius: var(--r-md);
  overflow: hidden;
}
.seg button, .view-toggle button {
  flex: 1 1 0; min-height: var(--control-h);
  padding: 0 var(--space150); margin: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--r-sm);
  background: transparent; color: var(--text-dim);
  font-family: var(--font-sans); font-size: var(--label-medium-size);
  font-weight: 500; letter-spacing: 0; text-transform: none;
  text-align: center; text-decoration: none; cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out-quart),
              color var(--motion-fast) var(--ease-out-quart);
}
.seg button:hover, .view-toggle button:hover { color: var(--on-surface); }
.seg button.on, .view-toggle button.active {
  background: var(--surface-high); color: var(--on-surface);
  box-shadow: inset 0 0 0 1px var(--border-divider);
}
/* Light mode inverts which of the two is the raised one: there the track is
   the tinted thing and the selected segment is the paper. --surface-high is
   darker than the wash in light, so reusing it put the selection in a hole. */
html[data-theme="light"] .seg button.on,
html[data-theme="light"] .view-toggle button.active { background: var(--surface-lowest); }
.seg button:focus-visible, .view-toggle button:focus-visible {
  outline: var(--focus-ring) solid var(--azul-pure); outline-offset: 1px;
}
