/* ============================================================
   THEME CONTRACT (#183 — follow system light/dark)
   ------------------------------------------------------------
   All colors are driven by the semantic tokens below. Each token pairs a
   light + dark value with light-dark(); the :root color-scheme decides which
   resolves ("light dark" follows the OS — #183; the #221 toggle forces it via
   :root[data-theme]). Any stylesheet or page inline <style> should reference
   these tokens instead of hardcoding hex so it flips automatically. Roles:
     --bg          page / app / scroll-area background
     --surface     panels, table body, inputs, pills, cards, buttons
     --surface-2   inset / subtle fills (input bg, note bg)
     --surface-3   hover fills / interactive-row tint
     --elevated    modals, popovers, mega-menu (floats above surface)
     --ink         primary text            --muted   secondary text
     --heading     strong heading text (was the text role of --vcg-d)
     --line / --line-dk   borders / dividers
     --vcg-dd / --vcg-d   near-black / dark brand FILLS (stay dark in
                          both themes: app bar, primary button, corner)
     --accent      copper accent (readable on both)
   Elements that are intentionally dark in LIGHT mode (app bar, footer,
   primary button, corner cell, toast) stay dark in dark mode — leave
   their white text alone. @media print re-pins every token to light
   so printouts are always on white regardless of system theme.
   ============================================================ */
:root {
  /* #183/#221: each token pairs its light + dark value via light-dark(); the
     color-scheme property below decides which resolves. "light dark" = follow
     the OS (the #183 default). The #221 toggle forces it per user choice via
     :root[data-theme] (right below). No @media query needed — light-dark() +
     color-scheme cover follow-system, manual override, AND print (forces light).
     VOREA monochrome palette — light value first, dark value second. */
  color-scheme: light dark;
  --vcg: light-dark(#555E68, #8b96a3);        /* neutral slate — focus/accents */
  --vcg-d: light-dark(#333333, #2f353d);      /* dark FILL role (primary btn/corner/neutral toast) */
  --vcg-dd: light-dark(#1A1A1A, #0f1215);     /* near-black bars stay near-black */
  --ink: light-dark(#111111, #e6e9ec);
  --muted: light-dark(#6B7280, #9aa5b0);
  --heading: light-dark(#333333, #d3dae1);
  --tint: light-dark(#F5F5F5, #262c34);
  --tint2: light-dark(#EBEBEB, #2b313a);
  --line: light-dark(#E0E0E0, #333a43);
  --line-dk: light-dark(#C8C8C8, #454d57);
  --hdr: light-dark(#F5F5F5, #242a31);
  --row-hdr: light-dark(#FFFFFF, #1e2229);
  --subtotal: light-dark(#E8E8E8, #2b313a);
  --grand: light-dark(#D4D4D4, #363d47);
  /* #F13: the light value was #B86820, which is only 4.17:1 on --surface — it
     is used for a lot of small 11-12px UI text ("Open →", "Refresh access",
     section links), all of which failed AA. #AC601B is a hair deeper copper at
     4.73:1 on white / 4.56:1 on --surface-2. If brand guidelines require the
     exact original copper, revert this one value and instead raise those call
     sites to 14px+ (large-text AA is 3:1, which #B86820 already meets). */
  --accent: light-dark(#AC601B, #d98a3d);     /* copper / amber TEXT on a light/dark surface */
  /* #F8: --accent is tuned for text, not for a solid fill behind white text
     (4.17:1 light / 2.74:1 dark — both fail AA). These deeper copper values are
     the fill role for primary buttons: 5.23:1 light, 4.91:1 dark. */
  --accent-solid: light-dark(#A25A18, #A85E1A);
  --accent-solid-hover: light-dark(#8A4B14, #8F4E15);
  /* The copper CHIP pair (beta pill, lookahead chip, help search highlight).
     --accent-ink is a step deeper than --accent, not an alias of it: --accent is
     tuned to 4.73:1 on WHITE, which leaves nothing in hand for a tinted fill
     under it (3.97:1 on the fill below). This copper clears AA on both. */
  --accent-ink: light-dark(#8F4E15, #e5a35c);   /* 6.42:1 on --surface / 7.39:1 */
  --accent-fill: light-dark(#F7E9DA, #3d2c19);  /* --accent-ink on it: 5.39:1 / 6.18:1 */
  /* Same problem, green: --ok-ink is the text-on-pale-fill role and would put
     white on #7fd6a6 (1.9:1) in dark mode. --ok-solid is the FILL role behind
     white text and stays dark in both themes (6.49:1 / 7.99:1). */
  --ok-solid: light-dark(#1B6B45, #175C3A);
  --ok-solid-hover: light-dark(#145235, #10462B);
  /* #F6: link text needs 4.5:1 on --surface; --accent is only 4.17:1 in light. */
  --link: light-dark(#9C5518, #d98a3d);
  --priority: light-dark(#fff3c4, #4a3f1a);   /* flagged row highlight */
  --priority-ink: light-dark(#5a3c00, #f0d488);
  --priority-hover: light-dark(#ffe94d, #5c4d1f);
  --chip: light-dark(#E8E8E8, #333a43);
  /* The :focus-visible ring colour, named so a page can draw its own ring. */
  --focus: var(--vcg);
  /* semantic surfaces / elevation */
  --bg: light-dark(#ffffff, #15181c);
  --surface: light-dark(#ffffff, #1e2229);
  --surface-2: light-dark(#fafbfc, #23282f);
  --surface-3: light-dark(#f0f4f7, #2b313a);
  --elevated: light-dark(#ffffff, #232932);
  /* light-dark() takes colors only, so pair the shadow COLOR, not the whole value */
  --shadow-color: light-dark(rgba(0,0,0,.12), rgba(0,0,0,.5));
  --shadow-color-lg: light-dark(rgba(0,0,0,.2), rgba(0,0,0,.65));
  --shadow: 0 1px 4px var(--shadow-color);
  --shadow-lg: 0 8px 30px var(--shadow-color-lg);

  /* ---- status roles (#F9) -------------------------------------------------
     One definition for success / warning / danger / info / beta. Each role
     pairs a text ink, a soft fill, and a border so a badge, pill or banner can
     be built from tokens alone. All light-dark() so they flip with the theme —
     this replaces the pale hardcoded pairs that used to be re-declared per
     page. Inks are chosen to clear 4.5:1 on their own fill in BOTH themes. */
  --ok-ink:    light-dark(#1B6B45, #7fd6a6);
  --ok-fill:   light-dark(#EAF6EF, #16321f);
  --ok-line:   light-dark(#9FCDB3, #2c5c40);
  --warn-ink:  light-dark(#8A5A00, #f0c26a);
  --warn-fill: light-dark(#FBF1E7, #3a2c12);
  --warn-line: light-dark(#EAD4BC, #5c4a20);
  --danger-ink:  light-dark(#A4262C, #f09a9a);
  --danger-fill: light-dark(#FDF3F2, #3a1c1c);
  --danger-line: light-dark(#E9C4C2, #6b2f2f);
  --info-ink:  light-dark(#2A5C8A, #8fc4ee);
  --info-fill: light-dark(#EAF2FB, #16283a);
  --info-line: light-dark(#CFE0F2, #2d4a68);

  /* ---- type scale (#F10) --------------------------------------------------
     The app previously used 41 distinct font-size values. These 8 steps cover
     every real need; --fs-md (12px) is the default control/table size and
     --fs-base (13px) is body copy. Use a token, not a literal px. */
  --fs-2xs: 10px;   /* micro labels, badges  */
  --fs-xs:  11px;   /* dense table meta      */
  --fs-sm:  11.5px; /* secondary UI text     */
  --fs-md:  12px;   /* controls, table cells */
  --fs-base:13px;   /* body copy             */
  --fs-lg:  15px;   /* section / card titles */
  --fs-xl:  17px;   /* page titles           */
  --fs-2xl: 22px;   /* hero titles           */

  /* ---- spacing scale (#F10) — 4px grid ---------------------------------- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px; --sp-12: 48px;

  /* ---- radius scale (#F10) ---------------------------------------------
     Was 25 distinct values. --radius is the control/default, --radius-lg the
     panel/card, --radius-pill for fully-round chips. (dashboard.css used to
     define its own --radius/--radius-lg privately; it now inherits these.) */
  --radius-sm: 4px;
  --radius: 6px;
  --radius-lg: 10px;
  --radius-pill: 999px;

  /* ---- layout (#F11) -----------------------------------------------------
     Two content widths for TEXT: --content-w for standard reading/form pages and
     --content-w-wide for data-dense pages. Always pair with `margin: 0 auto`.
     --content-w-photos (#402) is a third, and deliberately not a reading width at
     all: a grid of image tiles is scanned, not read, so the only thing capping it
     is where a row stops being takeable in at a glance. Use it for tile walls,
     never for prose. */
  --content-w: 880px;
  --content-w-wide: 1180px;
  --content-w-photos: 2000px;
  --page-gutter: var(--sp-8);

  /* ---- breakpoints (#F17) -------------------------------------------------
     Was 8 ad-hoc values (640/720/820/860/880/900/1100/1200). Media queries
     cannot read custom properties, so these are documented here and used as
     literals — but there are now only four, and each has one meaning:

       640px   phone            stack everything, drop optional chrome
       720px   small tablet     collapse the top nav + app-bar status text
       900px   large tablet     multi-column grids -> single column
      1200px   narrow desktop   wide grids -> fewer columns

     1920px (min-width) stays a one-off for the TV/kiosk page.
     720px is deliberately NOT merged upward into 900px: it is the shared
     nav-collapse breakpoint, and moving it would collapse the top nav on an
     iPad in portrait (768px), which is a behaviour change, not a cleanup. */

  /* ---- z-index ladder (#F19) ---------------------------------------------
     25 magic numbers between 40 and 9999 used to decide what covered what, so
     "does this drawer sit above the mega-menu?" could only be answered by
     grepping. These names are the answer. Values preserve the ORDER that was
     already shipped — this is a renaming, not a re-layering.

     Values below 40 are deliberately NOT tokenised: 1-10 are local layering
     inside a single component (sticky table cells, resize handles, a bar on a
     tile) where a global scale adds nothing and the literal is clearer. */
  --z-raised: 10;        /* lifted within its own component            */
  --z-actionbar: 50;     /* pinned action bar inside a page           */
  --z-pop: 60;           /* popover anchored to a control             */
  --z-banner: 70;        /* app-wide update banner                    */
  --z-drawer: 80;        /* slide-in panel                            */
  --z-page-modal: 90;    /* page-local modal                          */
  --z-tooltip: 99;       /* follows the cursor, above page chrome     */
  --z-sticky: 100;       /* sticky headers / toolbars                 */
  --z-docked-panel: 110; /* full-bleed panel that keeps the nav USABLE */
  --z-nav: 120;          /* mega-menu (must clear sticky headers)     */
  --z-modal: 130;        /* shared modal                              */
  --z-toast: 140;        /* transient message, above modals           */
  --z-overlay: 200;      /* full-screen backdrop                      */
  --z-overlay-top: 210;  /* the panel that backdrop belongs to        */
  --z-palette: 250;      /* command palette, above everything in-app  */
  --z-lavish: 260;       /* markup overlay: must sit over whatever it annotates */
  --z-authgate: 9999;    /* blocks the whole app until sign-in        */

  font-size: 13px;
}

/* #221: the theme toggle (nav.js) writes data-theme; no attribute = "auto"
   (follow the OS via color-scheme: light dark above). Forcing the scheme makes
   every light-dark() token — here and in reports.css — resolve to that theme. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font-family: "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
  color: var(--ink); background: var(--bg);
  /* stylelint-disable-next-line declaration-block-no-duplicate-properties -- 100vh is the fallback for the 100dvh below */
  display: flex; flex-direction: column; height: 100vh; overflow: hidden;
  /* The app shell is a fixed-height flex column that never scrolls as a
     document, so its height IS the usable area. On a phone `100vh` is the
     tallest the viewport ever gets — it ignores the URL bar — which pushed
     60-90px of the shell (the footer, and part of the scrolling pane above it)
     permanently under the browser chrome. `100dvh` tracks the visible area.
     The 100vh line above stays as the fallback for anything without dvh. */
  height: 100dvh;
}

/* ---------- app bar ---------- */
.appbar {
  display: flex; align-items: center; gap: 16px; height: 54px; padding: 0 18px;
  background: linear-gradient(180deg, var(--vcg-dd), #111111); color: #fff;
  box-shadow: var(--shadow); z-index: 10; flex: 0 0 auto;
}
.appbar-brand { display: flex; align-items: center; gap: 12px; }
.logo-img { height: 22px; display: block; }
.appbar-sub {
  font-size: var(--fs-base); letter-spacing: .5px; color: #cdd9e0; font-weight: 500;
  padding-left: 12px; border-left: 1px solid rgba(255,255,255,.28);
}
.appbar-spacer { flex: 1; }
.appbar-status { font-size: var(--fs-sm); color: #d7e1e7; display: flex; align-items: center; gap: 8px; }
.appbar-status .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.dot.live { background: #7CFC9A; } .dot.snapshot { background: #ffd166; }
.dot.empty { background: #ff8a8a; } .dot.busy { background: #7ec8ff; animation: pulse 1s infinite; }
@keyframes pulse { 50% { opacity: .3; } }

/* #a11y: the skip link nav.js injects ahead of the top nav on every page.
   Off-screen by transform rather than display:none, because a link that is
   not rendered is not focusable and could never be reached. :focus, not
   :focus-visible — this control exists only for the keyboard, so there is
   no mouse case to keep clean. */
.skip-link {
  position: fixed; top: var(--sp-2); left: var(--sp-2); z-index: var(--z-palette);
  transform: translateY(-250%); transition: transform .12s ease-out;
  background: var(--vcg-dd); color: #fff; text-decoration: none;
  font-size: var(--fs-base); font-weight: 500;
  padding: var(--sp-2) var(--sp-4); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.skip-link:focus { transform: translateY(0); outline: 2px solid #fff; outline-offset: 2px; }
/* Where the skip link lands. It is a container, not a control, so a ring drawn
   around the whole content area would say nothing — the page having scrolled to
   the content IS the feedback, and the next Tab lands on a real control that
   gets the house ring. Scoped to the element nav.js marks, so no other
   tabindex="-1" target loses its own. */
.skip-target:focus { outline: none; }
@media (prefers-reduced-motion: reduce) { .skip-link { transition: none; } }

/* ---------- app footer ---------- */
.appfooter {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
  height: auto; min-height: 30px; padding: 0 16px; background: var(--vcg-dd); color: #cdd9e0;
  font-size: var(--fs-xs); border-top: 1px solid rgba(0,0,0,.25); z-index: 6;
  /* Mobile foundation: env(safe-area-inset-*) clears the notch / home
     indicator and is a no-op (0) on any device without insets. */
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.appfooter .ft-left { display: flex; align-items: center; gap: 8px; letter-spacing: .3px; }
.appfooter .ft-logo { height: 13px; opacity: .95; }
.appfooter .ft-right { color: #b9c6cd; display: flex; gap: 12px; align-items: center; }
.appfooter .ft-right .dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }

/* ---------- control ribbon ---------- */
.ribbon {
  display: flex; align-items: center; gap: 12px; padding: 7px 16px; flex: 0 0 auto;
  background: var(--surface); border-bottom: 1px solid var(--line); box-shadow: 0 1px 2px rgba(0,0,0,.04);
  flex-wrap: wrap;
}
.rb-group { display: flex; align-items: center; gap: 6px; font-size: var(--fs-md); }
.rb-group > label { color: var(--muted); }
.rb-group.toggles { gap: 12px; }
.rb-group.toggles label, .priority-group label { display: flex; align-items: center; gap: 4px; cursor: pointer; color: var(--ink); }
/* The action buttons' wrapper (#audit-2026-09). 12px, not .rb-group's 6px, so
   wrapping them changed nothing about how the desktop ribbon lays out. */
.rb-actions { gap: 12px; flex-wrap: wrap; }
/* ⊞ / ⊟ stay icon-only in the ribbon, where the glyph is unambiguous beside
   its neighbours and the words would widen the bar. In the phone sheet they
   are tiles in a grid of labelled tiles, so there they get their words. */
.btn-tx-wide { display: none; }
.rb-sep { width: 1px; height: 22px; background: var(--line); }
.rb-spacer { flex: 1; }
.ribbon select {
  background: var(--surface); border: 1px solid var(--line-dk); border-radius: 5px;
  padding: 4px 7px; font-size: var(--fs-md); color: var(--ink); min-width: 150px;
}
.ribbon select:focus { outline: 2px solid var(--vcg); border-color: var(--vcg); }

.btn {
  border-radius: var(--radius); padding: 6px 11px; font-size: var(--fs-md); cursor: pointer; white-space: nowrap;
  border: 1px solid var(--line-dk); background: var(--surface); color: var(--ink);
}
.btn:hover { background: var(--tint); }
.btn-light { background: rgba(255,255,255,.16); color: #fff; border-color: rgba(255,255,255,.5); font-weight: 600; }
.btn-light:hover { background: rgba(255,255,255,.3); }
.btn-ghost { background: transparent; border-color: var(--line); color: var(--heading); }
.btn-ghost:hover { background: var(--tint); }
.btn:disabled { opacity: .55; cursor: default; }

/* #F5: no button rule defined a focus ring anywhere in the app — keyboard users
   had no visible position. :focus-visible only, so mouse clicks stay clean.
   Applies to every button/link-button, not just .btn, so the per-page button
   families inherit it too.
   No border-radius: an outline already follows the element's own corners, so a
   radius here rounds the ELEMENT instead of the ring. It was squaring off the
   7px photo tiles and shrinking rounder controls for as long as they held
   focus, and any element clipping content to its radius re-clipped it. */
button:focus-visible,
a:focus-visible,
[role="button"]:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--vcg);
  outline-offset: 2px;
}
/* on the dark app bar the slate ring is too low-contrast — use white there */
.appbar button:focus-visible, .appbar a:focus-visible,
.appfooter a:focus-visible, .btn-light:focus-visible {
  outline-color: #fff;
}

/* ---------- #F8: the page button families ----------------------------------
   signature-portal, subcontract, meetings, swppp, onsite-metrics, dev-admin and
   insurance each carried their own near-identical copy of the same button (7
   copies, 32 rules, all in page-level <style> blocks). They are collapsed here
   into ONE definition with seven names, so the markup on those pages did not
   have to change but there is now a single source of truth.

   This also fixes the contrast bug they all shared: `background: var(--accent);
   color: #fff` is 4.17:1 in light and 2.74:1 in dark — the *primary action* on
   each of those pages failed AA in both themes. --accent-solid is a deeper
   copper that carries white text at 5.2:1 / 4.9:1. --accent itself is unchanged
   and still correct for TEXT on a light surface. */
.sg-btn, .sc-btn, .mt-btn, .sw-btn, .om-btn, .da-btn, .ins-btn, .uu-btn {
  font-size: var(--fs-md); font-weight: 600; padding: 7px 14px;
  border-radius: var(--radius); border: 1px solid var(--line-dk);
  background: var(--surface); color: var(--ink); cursor: pointer; text-decoration: none;
}
.sg-btn:hover, .sc-btn:hover, .mt-btn:hover, .sw-btn:hover,
.om-btn:hover, .da-btn:hover, .ins-btn:hover, .uu-btn:hover { border-color: var(--accent); color: var(--accent); }
.sg-btn:disabled, .sc-btn:disabled, .mt-btn:disabled, .sw-btn:disabled,
.om-btn:disabled, .ins-btn:disabled, .uu-btn:disabled, .da-btn[disabled] { opacity: .5; cursor: default; }

.sg-btn-primary, .sc-btn-primary, .mt-btn-primary, .sw-btn-primary,
.om-btn-primary, .da-btn-primary, .ins-btn-primary {
  background: var(--accent-solid); border-color: var(--accent-solid); color: #fff;
}
.sg-btn-primary:hover, .sc-btn-primary:hover, .mt-btn-primary:hover, .sw-btn-primary:hover,
.om-btn-primary:hover, .da-btn-primary:hover, .ins-btn-primary:hover {
  background: var(--accent-solid-hover); border-color: var(--accent-solid-hover); color: #fff;
}

.sg-btn-sm, .mt-btn-sm, .ins-btn-sm { padding: var(--sp-1) var(--sp-2); font-size: var(--fs-sm); }

.sg-btn-danger:hover, .ins-btn-danger:hover { border-color: var(--danger-ink); color: var(--danger-ink); }
.da-btn-danger { background: var(--danger-fill); border-color: var(--danger-line); color: var(--danger-ink); }
.da-btn-danger:hover { background: var(--danger-fill); border-color: var(--danger-ink); color: var(--danger-ink); }

/* ---------- #F9: one badge / pill component --------------------------------
   The "Beta" tag alone existed five times (.dl-beta-tag, .sm-beta-tag,
   .tl-beta-tag, .beta-pill, .hs-badge-beta) in five files, at 9-10px, all with
   hardcoded pale pairs that failed AA (3.74:1). One `.badge` base plus status
   modifiers replaces them; every colour comes from the --ok/--warn/--danger/
   --info status tokens, so they flip with the theme and clear 4.5:1.

   Usage: <span class="badge badge-warn">Beta</span>
   The legacy class names are kept as aliases so existing markup keeps working. */
.badge,
.dl-beta-tag, .sm-beta-tag, .tl-beta-tag, .beta-pill, .hs-badge {
  display: inline-block; font-size: var(--fs-2xs); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
  border-radius: var(--radius-sm); padding: 2px 7px;
  border: 1px solid var(--line); background: var(--surface-2); color: var(--muted);
}
.badge-ok,     .hs-badge-done  { color: var(--ok-ink);     background: var(--ok-fill);     border-color: var(--ok-line); }
.badge-warn,   .hs-badge-rfi   { color: var(--warn-ink);   background: var(--warn-fill);   border-color: var(--warn-line); }
.badge-danger, .hs-badge-alert { color: var(--danger-ink); background: var(--danger-fill); border-color: var(--danger-line); }
.badge-info,   .hs-badge-submittal { color: var(--info-ink); background: var(--info-fill); border-color: var(--info-line); }
/* the Beta tag is a warning-toned badge — all five legacy names now resolve here */
.badge-beta, .dl-beta-tag, .sm-beta-tag, .tl-beta-tag, .beta-pill, .hs-badge-beta {
  color: var(--warn-ink); background: var(--warn-fill); border-color: var(--warn-line);
}
.dl-beta-tag, .sm-beta-tag, .tl-beta-tag { margin-left: var(--sp-2); }
/* signature/purple keeps a distinct hue but is now theme-aware */
.hs-badge-sign { color: light-dark(#6B3FA0, #c4a6ea); background: light-dark(#F1EBFA, #2a1f3a); border-color: light-dark(#DCCBF0, #4a3a66); }

/* #F6: there was no base anchor rule in any stylesheet, so unclassed links fell
   back to browser-default blue/purple. Inherit the app's accent instead. */
a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }
/* links that are really UI chrome (nav items, cards, buttons) opt out */
a.btn, a.navitem, a.topnav-link, a.tool-card, a.mm-item { color: inherit; text-decoration: none; }
a.btn:hover, a.navitem:hover, a.topnav-link:hover, a.tool-card:hover, a.mm-item:hover { text-decoration: none; }

/* ---------- layout ---------- */
.layout { flex: 1; display: flex; min-height: 0; }
.pivot-pane { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.fields-pane { flex: 0 0 auto; width: var(--fields-pane-w, 326px); background: var(--surface); border-left: 1px solid var(--line); display: flex; flex-direction: column; min-height: 0; position: relative; }
.fields-pane-resizer { position: absolute; left: 0; top: 0; bottom: 0; width: 5px; cursor: col-resize; z-index: 10; }
.fields-pane-resizer:hover { background: var(--vcg); opacity: .35; }
body.fields-resizing .fields-pane-resizer { background: var(--vcg); opacity: .5; }
body.fields-resizing { cursor: col-resize !important; user-select: none; }

/* ---------- pivot table ---------- */
.pivot-scroll { flex: 1; overflow: auto; background: var(--bg); padding: 0; }
.pivot-inner { padding: 16px 20px 28px; min-width: max-content; }
.pivot-inner > table.pivot { box-shadow: 0 1px 6px rgba(40,60,75,.12); border-radius: var(--radius-sm); overflow: hidden; }
table.pivot { border-collapse: collapse; font-size: var(--fs-md); min-width: 100%; }
table.pivot th, table.pivot td { border: 1px solid var(--line); padding: 3px 9px; white-space: nowrap; text-align: left; background-clip: padding-box; }
table.pivot thead th { position: sticky; top: 0; z-index: 3; background: var(--hdr); color: var(--heading); font-weight: 600; border-bottom: 2px solid var(--line-dk); text-align: center; }
/* Excel-style column resize: grab strip on a header cell's right edge */
.col-resize { position: absolute; top: 0; right: -4px; width: 8px; height: 100%; cursor: col-resize; z-index: 6; }
.col-resize:hover { background: var(--vcg); opacity: .45; }
body.col-resizing { cursor: col-resize; user-select: none; }
body.col-resizing * { cursor: col-resize !important; }
/* once any width is set, fixed layout honors the per-column widths (both ways) */
table.pivot.colw { table-layout: fixed; }
table.pivot.colw th, table.pivot.colw td { overflow: hidden; text-overflow: ellipsis; }
table.pivot thead th.corner { text-align: left; background: var(--vcg-dd); color: #fff; z-index: 4; left: 0; position: sticky; font-weight: 600; }
table.pivot thead th.measure-hdr { text-align: right; }
/* FR-233: value headers are click-to-sort. Cursor + a subtle hover cue, and a
   ▲/▼ indicator on the active measure. */
table.pivot thead th.measure-hdr.sortable { cursor: pointer; }
table.pivot thead th.measure-hdr.sortable:hover { background: var(--surface-3); }
table.pivot thead th.measure-hdr.sorted { color: var(--vcg); }
.sort-ind { margin-left: 4px; font-size: var(--fs-2xs); opacity: .9; }
table.pivot td.val, table.pivot th.val { text-align: right; font-variant-numeric: tabular-nums; }
/* #698: the row label WRAPS at its cap rather than truncating. The blanket
   `white-space: nowrap` above plus `text-overflow: ellipsis` cut a long
   submittal title off at 520px - and the 📎 attachment link that labelHtml()
   appends AFTER the title went with it, so the one control on the row was
   unreachable on exactly the items whose titles are longest. Wrapping grows the
   row instead; `overflow-wrap` is for a single unbroken token (a spec number or
   a URL) that is itself wider than the cap. The ellipsis declarations are gone
   rather than kept: `text-overflow` needs `nowrap` to do anything, so leaving
   them would only suggest a truncation that no longer happens. */
table.pivot td.rowlabel { position: sticky; left: 0; z-index: 2; background: var(--row-hdr); font-weight: 500; max-width: 520px; white-space: normal; overflow-wrap: break-word; }
/* Placeholder shown in the pivot while a not-yet-cached dataset loads (first visit
   to a dataset) so the previous dataset's table isn't left under the new title. */
table.pivot td.pivot-loading-cell { text-align: center; color: var(--muted); padding: 44px 24px; font-size: var(--fs-base); border: none; background: var(--surface); }
tr.subtotal td { background: var(--subtotal); font-weight: 600; }
tr.grand td { background: var(--grand); font-weight: 700; border-top: 2px solid var(--line-dk); }
tr.datarow:hover td { background: var(--surface-3); }
tr.datarow:hover td.rowlabel { background: var(--surface-3); }

/* priority — highlight the whole row yellow, including the group/title column.
   Cells with an inline background (level fill) keep their fill because inline styles
   beat selector rules; only cells with no inline background turn yellow. */
tr.priority td, tr.priority td.rowlabel { background: var(--priority); color: var(--priority-ink); }
tr.priority:hover td { background: var(--priority-hover); }

.hcol-btn { display: inline-block; margin-left: 5px; padding: 0 4px; border-radius: 3px; cursor: pointer; font-size: var(--fs-2xs); line-height: 1.4; color: #dbe5ea; background: rgba(255,255,255,.16); font-weight: 700; }
.hcol-btn:hover { background: rgba(255,255,255,.36); color: #fff; }
.hcol-sep { margin: 0 4px; opacity: .55; font-weight: 400; }
/* indented group column: each level on its own line, banded, at group start */
td.rowlabel-group { vertical-align: top; }
.ind-lvl { display: block; padding: 1px 4px 1px 0; margin: -1px 0; }
/* standalone grouping-header rows (visual separators between groups) */
tr.grouphdr td { font-weight: 600; border-top: 1.5px solid var(--line-dk); }
tr.grouphdr td.rowlabel-group { letter-spacing: .2px; }
.toggle { display: inline-block; width: 14px; text-align: center; cursor: pointer; color: var(--muted); user-select: none; font-size: var(--fs-xs); }
.toggle:hover { color: var(--accent); }
.leaf-spacer { display: inline-block; width: 14px; }
.star { display: inline-block; width: 16px; text-align: center; cursor: pointer; color: #B89060; user-select: none; }
.star:hover { color: var(--accent); }
.star.on { color: var(--accent); }
td.rowlabel a { color: var(--ink); text-decoration: none; }
td.rowlabel a:hover { text-decoration: underline; }
.empty-pivot { padding: 44px; text-align: center; color: var(--muted); }

/* ---------- field list ---------- */
.fields-header { padding: 10px 14px; font-weight: 600; color: var(--heading); border-bottom: 1px solid var(--line); background: var(--surface); }
.field-search { margin: 9px; padding: 6px 9px; border: 1px solid var(--line-dk); border-radius: var(--radius); }
.field-list { flex: 1; overflow: auto; padding: 0 7px 8px; min-height: 80px; }
.field-group-title { font-size: var(--fs-xs); font-weight: 700; color: var(--muted); text-transform: uppercase; padding: 9px 6px 2px; letter-spacing: .5px; }
.field-item { display: flex; align-items: center; gap: 7px; padding: 4px 7px; border-radius: var(--radius); cursor: grab; font-size: var(--fs-md); }
.field-item:hover { background: var(--tint); }
.field-item.dragging { opacity: .4; }
.field-item .ico { font-size: var(--fs-2xs); color: var(--muted); width: 14px; text-align: center; }
.field-item.measure .ico { color: var(--vcg); font-weight: 700; }

/* ---------- zones ---------- */
/* #394: the drag handle was a bare 5px bar — a 5px target with no grip, and its
   :hover paint was the only feedback that it was interactive. It now reserves
   11px of row (so the pointer finds it without pixel-hunting) and carries the
   same "···" grip as .zones-hsplit, drawn INSIDE its own box: the earlier bar
   sat flush against the zones grid, so the highlight looked like it belonged to
   the ▽ Filters cell below and read as that cell being clipped. */
.zones-top-resizer { flex: 0 0 11px; cursor: n-resize; background: var(--line-dk); display: flex; align-items: center; justify-content: center; user-select: none; }
.zones-top-resizer::after { content: "···"; font-size: var(--fs-xs); line-height: 1; color: var(--muted); pointer-events: none; }
.zones-top-resizer:hover { background: var(--vcg); }
.zones-top-resizer:hover::after { color: #fff; }
body.zones-top-resizing * { cursor: n-resize !important; user-select: none !important; }
/* Default height is half the viewport (#394 — 300px opened too low to use), and
   the drag still overrides it by setting --zones-total-h in px. */
.zones { display: grid; grid-template-columns: var(--zones-left-px, 1fr) 5px 1fr; grid-template-rows: var(--zones-top-px, 1fr) 5px 1fr; height: var(--zones-total-h, 50vh); min-height: 180px; position: relative; background: var(--line); }
.zone { background: var(--surface); padding: 7px; min-height: 0; display: flex; flex-direction: column; overflow: auto; }
.zone-tl { grid-column: 1; grid-row: 1; }
.zone-tr { grid-column: 3; grid-row: 1; }
.zone-bl { grid-column: 1; grid-row: 3; }
.zone-br { grid-column: 3; grid-row: 3; }
.zones-vsplit { grid-column: 2; grid-row: 1 / 4; cursor: col-resize; background: var(--line-dk); display: flex; align-items: center; justify-content: center; user-select: none; }
.zones-vsplit::after { content: "⋮"; font-size: var(--fs-xs); color: var(--muted); pointer-events: none; }
.zones-hsplit { grid-column: 1 / 4; grid-row: 2; cursor: row-resize; background: var(--line-dk); display: flex; align-items: center; justify-content: center; user-select: none; }
.zones-hsplit::after { content: "···"; font-size: var(--fs-xs); color: var(--muted); pointer-events: none; }
.zones-vsplit:hover, .zones-hsplit:hover { background: var(--vcg); }
body.zones-v-resizing * { cursor: col-resize !important; user-select: none !important; }
body.zones-h-resizing * { cursor: row-resize !important; user-select: none !important; }
.zone-title { font-size: var(--fs-xs); font-weight: 700; color: var(--heading); margin-bottom: 5px; }
.zone-drop { flex: 1; display: flex; flex-direction: column; gap: 4px; border: 1px dashed transparent; border-radius: var(--radius); padding: 2px; min-height: 44px; }
.zone-drop.dragover { border-color: var(--vcg); background: var(--surface-3); }
.pill { position: relative; display: flex; align-items: center; gap: 6px; background: var(--surface); border: 1px solid var(--line-dk); border-radius: var(--radius); padding: 4px 7px; font-size: var(--fs-md); cursor: grab; }
/* #a11y: a pill is reorderable from the keyboard (Arrow / Alt+Arrow), so it is
   tabbable and needs the house ring — the global :focus-visible rule only
   reaches buttons, links and [role=button]. */
.pill:focus-visible { outline: 2px solid var(--vcg); outline-offset: 2px; }
/* .agg / .cfg / .rm are real <button>s now; as spans no keyboard could reach
   them at all. Strip the browser button chrome back to what the spans
   rendered as, and floor the hit box at WCAG 2.5.8's 24px. The pill is 26px
   tall, so the negative margin lets a 24px target sit inside it without
   growing the row — the same trick the coarse-pointer block below uses.
   Declared BEFORE the per-control rules so their fill and size still win. */
.pill .agg, .pill .cfg, .pill .rm {
  font: inherit; border: 0; background: none; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 24px; min-height: 24px; margin-top: -4px; margin-bottom: -4px;
}
.pill .cfg { color: var(--heading); cursor: pointer; font-size: var(--fs-xs); padding: 0 2px; }
.pill .cfg:hover { color: var(--accent); }
.pill .level-dot { width: 11px; height: 11px; border-radius: 3px; border: 1px solid rgba(0,0,0,.25); flex: 0 0 auto; }
.pill.drop-before { box-shadow: 0 -2px 0 var(--vcg); }
.pill.drop-after { box-shadow: 0 2px 0 var(--vcg); }
.pill .grip { color: #b7c5ce; cursor: grab; }
.pill .lbl { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.pill .agg { color: var(--heading); font-size: 10.5px; cursor: pointer; background: var(--chip); padding: 1px 5px; border-radius: var(--radius-sm); font-weight: 600; }
.pill .agg:hover { background: var(--surface-3); }
.pill .rm { color: #b0535a; cursor: pointer; font-weight: 700; }
.pill.dragging { opacity: .4; }

/* #a11y: several tables make the whole <tr> clickable and it is the ONLY way
   into the detail view — fine with a mouse, unreachable by keyboard, and
   unannounced. The operable control is a real button in the row’s primary
   cell (role="button" on the <tr> would have hidden every other cell from a
   screen reader). It inherits the cell’s own type so the row looks exactly
   as it did, and carries no handler: Enter/Space fire a click that bubbles to
   the row listener that was already there. The row’s hover tint is the
   affordance, so this deliberately adds no underline of its own. */
.cell-action {
  font: inherit; color: inherit; text-align: inherit; letter-spacing: inherit;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  display: inline; width: auto;
}

/* ---------- modal ---------- */
.modal { position: fixed; inset: 0; background: rgba(30,45,60,.4); display: flex; align-items: center; justify-content: center; z-index: var(--z-modal); }
.modal.hidden { display: none; }
.modal-box { background: var(--elevated); border-radius: var(--radius-lg); width: 580px; max-width: 92vw; max-height: 86vh; display: flex; flex-direction: column; box-shadow: var(--shadow-lg); }
.modal-box.modal-wide { width: 860px; }
.chart-controls { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; font-size: var(--fs-md); }
.chart-controls select { padding: 4px 7px; border: 1px solid var(--line-dk); border-radius: 5px; }
.chart-totals { color: var(--muted); }
.chart-canvas { width: 100%; }
.chart-note { margin-top: 10px; font-size: var(--fs-xs); color: var(--muted); }
.modal-head { padding: 13px 18px; font-weight: 600; color: var(--heading); border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; }
.modal-head .x { border: none; background: none; font-size: var(--fs-2xl); cursor: pointer; color: var(--muted); }
.modal-body { padding: 15px 18px; overflow: auto; }

/* ---------- 📎 attachment preview (attachment-preview.js) ----------
   Near-fullscreen: a drawing sheet needs the room. #747: the full width of the
   page, with no fixed cap - a 1280px ceiling left a wide monitor's sheet in a
   column with dead scrim either side. The 12px gutter and 92vh leave just
   enough scrim that "this is a layer over the page" stays legible, and keep
   Expand meaningful. Expand drops the scrim entirely - either through the
   Fullscreen API (the whole monitor) or, where that is refused, by filling the
   window. Both states are the same geometry, so they are declared together. */
.modal-box.modal-viewer { width: min(1280px, 96vw); height: 92vh; max-height: 92vh; }
/* Scoped to .attach-preview on purpose: Home's close & distribute box shares
   .modal-viewer and keeps the 1280px cap - #747 widened only the preview. */
.attach-preview .modal-box.modal-viewer { width: calc(100vw - 24px); max-width: none; }
.attach-preview.is-maximized, .attach-preview:fullscreen { background: var(--surface-2); }
.attach-preview.is-maximized .modal-box.modal-viewer,
.attach-preview:fullscreen .modal-box.modal-viewer {
  width: 100vw; height: 100dvh; max-width: none; max-height: none; border-radius: 0;
}
.attach-preview .modal-head { gap: 10px; }
.ap-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ap-count { flex: none; font-weight: 400; color: var(--muted); font-size: var(--fs-md); }
.ap-actions { flex: none; display: flex; align-items: center; gap: 8px; }
.ap-btn { text-decoration: none; }
.ap-body { display: flex; flex: 1; min-height: 0; }
.ap-list { width: 230px; flex: none; overflow: auto; border-right: 1px solid var(--line); padding: 8px; display: flex; flex-direction: column; gap: 4px; }
.ap-file { display: block; width: 100%; text-align: left; background: none; border: 1px solid transparent; border-radius: var(--radius); padding: 7px 9px; font-size: var(--fs-md); color: var(--ink); cursor: pointer; overflow-wrap: anywhere; }
.ap-file:hover { background: var(--surface-3); }
.ap-file.is-active { background: var(--surface-2); border-color: var(--line-dk); font-weight: 600; }
.ap-pane { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* A failed download reports HERE, above the pages, instead of through the
   status panel that hides them - flex:none so it takes its own height and the
   render below keeps the rest (and its scroll position). */
.ap-alert { flex: none; display: flex; align-items: center; gap: 10px; padding: 8px 12px; font-size: var(--fs-md); color: var(--danger-ink); background: var(--danger-fill); border-bottom: 1px solid var(--danger-line); }
.ap-alert[hidden] { display: none; }
.ap-alert-msg { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.ap-alert .ap-alert-x { flex: none; border: none; background: none; font-size: var(--fs-xl); line-height: 1; cursor: pointer; color: inherit; }
.ap-status { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 20px; text-align: center; color: var(--muted); }
.ap-status-msg { margin: 0; max-width: 46em; overflow-wrap: anywhere; }
.ap-status-actions { margin: 8px 0 0; display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.ap-pages { position: relative; flex: 1; overflow: auto; background: var(--surface-2); }
/* The sheets sit in a strip inside the scroller rather than in the scroller
   itself. A zoomed page is WIDER than the column, and an overflowing child
   centred directly in a scroll container has its left edge clipped out of
   reach (`margin: 0 auto` + overflow is the classic version of that bug);
   `width: max-content; min-width: 100%` gives the scroller something to scroll
   and still centres a page narrower than the column. The gutter lives here too,
   so horizontal scrolling does not eat the right-hand padding. */
.ap-strip { width: max-content; min-width: 100%; padding: 14px; display: flex; flex-direction: column; align-items: center; gap: 14px; }
/* "One page" means one page: every sheet is sized to the column, so snapping is
   what keeps a scroll from parking the reader across a seam. */
.ap-pages.is-fit-page { scroll-snap-type: y mandatory; }
.ap-pages.is-fit-page .ap-page { scroll-snap-align: center; }
/* The zoom bar. flex: none - it is part of the column's chrome, and the pages
   below it are measured against what it leaves. */
.ap-tools { flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 6px 12px; border-bottom: 1px solid var(--line); background: var(--surface); }
.ap-tools[hidden] { display: none; }
.ap-zoom-btn { min-width: 34px; font-size: var(--fs-lg); line-height: 1; padding: 4px 9px; }
.ap-zoom-select { padding: 4px 7px; border: 1px solid var(--line-dk); border-radius: 5px; background: var(--elevated); color: var(--ink); font-size: var(--fs-md); }
.ap-zoom-now { color: var(--muted); font-size: var(--fs-md); }
/* #846: find + markup share the bar, which wraps (.ap-tools above) rather
   than squeezing the find box to nothing; the markup row always takes a line
   of its own. */
.ap-find { display: inline-flex; align-items: center; gap: 4px; margin-left: auto; min-width: 0; }
.ap-find-input { width: 15em; min-width: 7em; flex: 1 1 auto; padding: 4px 8px; border: 1px solid var(--line-dk); border-radius: 5px; background: var(--elevated); color: var(--ink); font-size: var(--fs-md); }
.ap-find-input.is-miss { border-color: var(--danger-line); background: var(--danger-fill); }
.ap-find-count { min-width: 5.5em; color: var(--muted); font-size: var(--fs-md); white-space: nowrap; }
.ap-mark-toggle[aria-pressed="true"] { background: var(--surface-2); border-color: var(--line-dk); font-weight: 600; }
.ap-markbar { flex-basis: 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; padding-top: 6px; border-top: 1px solid var(--line); }
.ap-markbar[hidden] { display: none; }
.ap-mark-group { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.ap-mark-tool[aria-pressed="true"] { background: var(--surface-2); border-color: var(--ink); font-weight: 600; }
.ap-mark-color { width: 22px; height: 22px; padding: 0; border: 2px solid var(--surface); border-radius: 50%; box-shadow: 0 0 0 1px var(--line-dk); cursor: pointer; }
.ap-mark-color[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--ink); }
.ap-mark-note { color: var(--muted); font-size: var(--fs-sm); }
/* The overlay sits over the canvas, sized with the page. It takes no pointer
   input unless a markup tool is armed, so reading, scrolling and selecting
   behave exactly as before; armed, it owns touch too (a finger draws instead
   of panning - the same explicit-off-state rule as drawings-markup.js). The
   overlay is positioned against .ap-page, which is relative for it. */
.ap-overlay { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
.ap-pages.is-marking .ap-overlay { pointer-events: auto; touch-action: none; cursor: crosshair; }
.ap-pages.is-marking[data-tool="erase"] .ap-overlay { cursor: not-allowed; }
.ap-hit { fill: rgb(255, 208, 0); fill-opacity: .45; mix-blend-mode: multiply; }
.ap-hit.is-current { fill: rgb(255, 128, 0); fill-opacity: .6; }
.ap-note-edit { position: absolute; z-index: 1; box-sizing: border-box; padding: 3px 4px; border: 1.5px solid; border-radius: 0; background: #fff; font: 13px/1.25 Helvetica, Arial, sans-serif; resize: both; }
/* Both panels above declare `display: flex`, which outranks the UA stylesheet's
   `[hidden] { display: none }` — the same trap `.megamenu[hidden]` and
   `.support-pop[hidden]` guard against. Without these, the "Loading …" status
   never leaves the screen and a single-file posting keeps an empty file column. */
.ap-list[hidden], .ap-status[hidden] { display: none; }
/* Width AND height are set per page by the viewer (sizePage), which is what
   lets a deflated stub canvas keep the space its sheet will take. No
   `max-width: 100%` here on purpose: in the fit modes the computed width
   already fits the column, and at an explicit zoom a clamp would silently
   cancel the zoom instead of scrolling. */
.ap-page { position: relative; flex: none; background: #fff; box-shadow: var(--shadow-lg); }
.ap-page canvas { display: block; width: 100%; height: 100%; }
@media (max-width: 820px) {
  /* The .attach-preview selector matches the desktop rule's specificity so
     the phone geometry still wins over it. */
  .modal-box.modal-viewer, .attach-preview .modal-box.modal-viewer { width: 100vw; height: 100dvh; max-width: none; max-height: none; border-radius: 0; }
  .ap-body { flex-direction: column; }
  /* The base `.ap-pane` only declares `min-width: 0`, which is the constraint
     that matters in the desktop ROW direction. Turned to a column here, the
     axis flips and the pane's default `min-height: auto` (it is not itself a
     scroll container, so the automatic minimum is content-based) held it at
     full document height - `.ap-pages` then had nothing to clip and a
     multi-page PDF could not be scrolled past sheet 1 on a phone. */
  .ap-pane { min-height: 0; }
  .ap-list { width: auto; flex: none; flex-direction: row; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--line); }
  .ap-file { width: auto; white-space: nowrap; overflow-wrap: normal; }
  .ap-strip { padding: 8px; gap: 8px; }
  .ap-tools { padding: 6px 8px; }
  /* #846: on a phone the find box takes a row of its own under the zoom and
     Markup controls, rather than being squeezed between them. */
  .ap-find { order: 2; flex: 1 1 100%; margin-left: 0; }
  .ap-find-input { width: auto; }
  .ap-markbar { order: 3; }
  /* Expand is a no-op here - under 820px the dialog is ALREADY the whole
     viewport - and the header it would sit in is the one place with no room
     to spare, so it goes rather than pushing the filename into an ellipsis. */
  .ap-expand { display: none; }
}
.fmt-section { margin-bottom: 18px; }
.fmt-section h4 { margin: 0 0 8px; font-size: var(--fs-base); color: var(--ink); }
.fmt-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; font-size: var(--fs-md); }
.fmt-row select, .fmt-row input[type=number] { padding: 4px 7px; border: 1px solid var(--line-dk); border-radius: 5px; }
.swatch { width: 16px; height: 16px; border-radius: var(--radius-sm); border: 1px solid rgba(0,0,0,.2); display: inline-block; cursor: pointer; }
.fmt-legend { display: flex; flex-wrap: wrap; gap: 6px; }
.fmt-legend .item { display: flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: var(--radius-lg); font-size: var(--fs-xs); }

.btn-primary { background: var(--vcg-d); border-color: var(--heading); color: #fff; }
.btn-primary:hover { background: var(--vcg-dd); }
/* #432: the "Open design review" button (fr-announcements.js, feature-requests.html)
   carries the review feature's own blue identity — the same blue as .fr-review-badge —
   instead of the generic dark --vcg-d primary, which is charcoal in BOTH themes and
   read as a dark slab dropped into the light UI. This pair is theme-aware (deep blue
   on light, brighter blue on dark), white text AA in both (6.4:1 / 4.8:1). */
.btn-review { background: light-dark(#0a58ca, #2f6fd6); border-color: light-dark(#0a58ca, #2f6fd6); color: #fff; }
.btn-review:hover { background: light-dark(#0847a6, #245ec0); border-color: light-dark(#0847a6, #245ec0); }
.btn-sm { padding: 3px 9px; font-size: var(--fs-xs); }

/* ---------- snapshot report PAGE (/snapshots.html) ---------- */
.snap-page { display: flex; flex-direction: column; min-height: 100vh; }
.snap-page .appbar-status { color: #cdd9e0; font-size: var(--fs-md); }
.print-only { display: none; }

/* ---------- call / item notes ---------- */
.note-btn {
  display: inline-block; width: 15px; text-align: center; cursor: pointer;
  color: transparent; user-select: none; font-size: var(--fs-xs); line-height: inherit;
  transition: color .1s;
}
tr:hover .note-btn { color: #aab4bc; }
.note-btn.on { color: var(--accent) !important; }
.note-btn:hover { color: var(--accent) !important; }

/* ✎ Notes column (opts.notesCol): each item's note text as its own column —
   the report-ready replacement for hand-marking the printed PDF. */
table.pivot th.notes-hdr { text-align: left; }
table.pivot td.note-cell { text-align: left; white-space: pre-wrap; font-size: var(--fs-xs); min-width: 160px; max-width: 340px; }
/* The ✎ Notes column hosts the #314 comment feed when live (opts.notesEditable →
   td.note-cell-edit); read-only report/print cells stay plain text. */
table.pivot td.note-cell.note-cell-edit { cursor: text; }

/* #314: the Notes column as an append-only comment FEED. Each comment is one
   line "{M/D} - {Author}: {text}" (date_change comments drop the colon); the
   feed scrolls within the cell and an "add a comment" input sits beneath it. */
table.pivot td.note-cell .cmt-feed { display: flex; flex-direction: column; gap: 2px; max-height: 160px; overflow-y: auto; }
table.pivot td.note-cell .cmt {
  display: flex; align-items: baseline; gap: 4px;
  white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.35;
}
table.pivot td.note-cell .cmt-line { flex: 1 1 auto; }
table.pivot td.note-cell .cmt-head { color: var(--muted); font-variant-numeric: tabular-nums; }
table.pivot td.note-cell .cmt-date .cmt-line { color: var(--muted); font-style: italic; }
/* Delete × — elevated users only; revealed on comment hover so it stays quiet. */
.cmt-del {
  flex: 0 0 auto; border: none; background: none; cursor: pointer; padding: 0 2px;
  color: transparent; font-size: var(--fs-md); line-height: 1; transition: color .1s;
}
.cmt:hover .cmt-del { color: #aab4bc; }
.cmt-del:hover { color: var(--danger-ink) !important; }
/* The append input — a borderless contenteditable that reads as plain text; a
   faint "add a comment…" prompt shows when empty. */
.cmt-add {
  display: block; margin-top: 3px; padding: 1px 3px; min-height: 1.3em;
  border-radius: 3px; outline: none; cursor: text; color: var(--ink);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.cmt-add:empty::before { content: "add a comment…"; color: var(--muted); }
.cmt-add:hover { background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--line); }
.cmt-add:focus { background: var(--surface); box-shadow: inset 0 0 0 1px var(--accent); }
/* "Show notes" (opts.notesRows): the item's ✎ note as a line under its row. */
table.pivot tr.note-row td.note-row-cell {
  text-align: left; white-space: pre-wrap; font-size: var(--fs-xs); color: var(--muted);
  background: var(--tint); border-left: 3px solid var(--accent);
  padding: 3px 8px 4px 26px;
}
/* #218 "Show responses" (opts.responsesRows): the submittal's returned-approver
   trail (Responses To Date) as its OWN read-only column, just left of ✎ Notes —
   each returned approver on its own line (render.js splits the "; "-joined value). */
table.pivot th.responses-hdr { text-align: left; }
table.pivot td.response-cell {
  text-align: left; font-size: var(--fs-xs); color: var(--muted); vertical-align: top;
  line-height: 1.45; min-width: 150px; max-width: 300px;
}

/* #191: inline confirmed-return-date picker on each leaf item row (OAC review). */
.commit-date {
  margin-left: 6px; font-size: var(--fs-xs); line-height: 1.2; padding: 0 2px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);
  color: var(--muted); cursor: pointer; vertical-align: baseline; max-width: 130px;
}
.commit-date.on { color: var(--ink); border-color: var(--accent); font-weight: 600; }
.commit-date:hover { border-color: var(--accent); }
@media print { .commit-date { border: none; padding: 0; color: var(--ink); -webkit-appearance: none; appearance: none; } }

/* The ⓘ beside the picker: opens that item's committed-date history. Kept in the
   layout at all times and merely made transparent (the .note-btn trick) — a
   display/visibility toggle would shift the whole row's width on hover, which in
   a dense grid reads as the table twitching under the cursor. */
.commit-wrap { white-space: nowrap; }
.commit-hist {
  border: none; background: none; padding: 0 1px; margin: 0 0 0 2px;
  font: inherit; font-size: var(--fs-xs); line-height: inherit; vertical-align: baseline;
  color: transparent; cursor: pointer; user-select: none; transition: color .1s;
}
tr:hover .commit-hist, .commit-wrap:hover .commit-hist { color: var(--muted); }
.commit-hist:hover, .commit-hist:focus-visible { color: var(--accent) !important; }
/* Touch has no hover to reveal it with (mobile foundation: gate ergonomics on
   input modality, not on screen width). */
@media (hover: none) { .commit-hist { color: var(--muted); } }
@media print { .commit-hist { display: none !important; } }


.modal-note-box { width: 520px; }
.note-modal-title { font-weight: 600; color: var(--heading); font-size: 14px; }
.note-modal-sub {
  font-size: var(--fs-sm); color: var(--muted); margin-top: 3px;
  max-width: 440px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.note-textarea {
  width: 100%; padding: 10px 12px; border: 1px solid var(--line-dk); border-radius: var(--radius);
  font-size: var(--fs-base); font-family: inherit; line-height: 1.6; resize: vertical; min-height: 148px;
  color: var(--ink); background: var(--surface-2); box-sizing: border-box;
}
.note-textarea:focus { outline: 2px solid var(--vcg); border-color: var(--vcg); background: var(--surface); }
.note-meta { font-size: var(--fs-xs); color: var(--muted); margin-top: 7px; min-height: 15px; }
.note-tip { font-size: var(--fs-xs); color: var(--muted); margin-top: 4px; }
.note-footer {
  display: flex; align-items: center; gap: 8px; padding: 12px 18px;
  border-top: 1px solid var(--line); background: var(--surface-2); border-radius: 0 0 10px 10px;
}
.note-delete-btn { margin-left: auto; color: var(--danger-ink); border-color: var(--line); }
.note-delete-btn:hover { background: var(--danger-fill); }
.modal-rename-box { width: 420px; }
.rename-hdr-field { font-size: var(--fs-sm); color: var(--muted); margin-bottom: 10px; }
.rename-hdr-input {
  width: 100%; padding: 9px 12px; border: 1px solid var(--line-dk); border-radius: var(--radius);
  font-size: var(--fs-base); font-family: inherit; color: var(--ink); background: var(--surface-2); box-sizing: border-box;
}
.rename-hdr-input:focus { outline: 2px solid var(--vcg); border-color: var(--vcg); background: var(--surface); }

/* ---------- New / Edit view dialog (#384) ---------- */
/* Reuses .modal-rename-box + .rename-hdr-input; adds the field labels and the
   company/project scope picker under them. */
.vm-lbl {
  display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--muted);
  margin: 0 0 5px; letter-spacing: 0.02em;
}
.vm-lbl + .rename-hdr-input, .vm-lbl + .vm-select { margin-bottom: 12px; }
.vm-select {
  width: 100%; padding: 9px 10px; border: 1px solid var(--line-dk); border-radius: var(--radius);
  font-size: var(--fs-base); font-family: inherit; color: var(--ink); background: var(--surface-2);
  box-sizing: border-box;
}
.vm-select:focus { outline: 2px solid var(--vcg); border-color: var(--vcg); background: var(--surface); }

/* ---------- priority journal (#140) ---------- */
.prio-count { color: var(--muted); font-size: var(--fs-md); font-weight: 600; min-width: 14px; text-align: center; }
.prio-log-msg { padding: 24px 4px; text-align: center; color: var(--muted); font-size: var(--fs-base); }
.prio-log-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.prio-log-table th, .prio-log-table td { padding: 6px 10px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
.prio-log-table thead th { position: sticky; top: 0; background: var(--hdr); color: var(--heading); font-weight: 600; border-bottom: 2px solid var(--line-dk); }
.prio-log-when, .prio-log-who { color: var(--muted); white-space: nowrap; }
.prio-log-act { font-weight: 600; white-space: nowrap; }
.prio-log-act.add { color: var(--ok-ink); }
.prio-log-act.rem { color: var(--danger-ink); }

/* ---------- committed-date history modal (#commitHistModal) ----------
   ONE item's promised return dates, opened by the ⓘ on its date picker. Must stay
   BELOW .prio-log-table: .chist-table reuses that journal look and overrides its
   cell `vertical-align`, and both selectors carry the same specificity, so source
   order is what decides. */
.modal-chist-box { width: 700px; }
.chist-head-text { min-width: 0; }
.chist-title { font-weight: 600; color: var(--heading); font-size: 14px; }
.chist-sub {
  font-size: var(--fs-sm); color: var(--muted); margin-top: 3px; font-weight: 400;
  max-width: 520px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The latest promise, called out above the log — the number people came for. */
.chist-current {
  border: 1px solid var(--ok-line); background: var(--ok-fill);
  border-radius: 7px; padding: 10px 13px; margin-bottom: 14px;
}
.chist-current.moved { border-color: var(--warn-line); background: var(--warn-fill); }
.chist-current.none { border-color: var(--line); background: var(--surface-2); }
.chist-current-lbl {
  font-size: 10.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted);
}
.chist-current-date { font-size: 19px; font-weight: 600; color: var(--ink); margin-top: 2px; }
.chist-current-meta { font-size: var(--fs-sm); color: var(--muted); margin-top: 3px; }
.chist-moved {
  font-size: var(--fs-sm); color: var(--warn-ink); margin-top: 7px; padding-top: 7px;
  border-top: 1px solid var(--warn-line); line-height: 1.5;
}
.chist-log-lbl {
  font-size: 10.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 6px;
}
/* Every header on one line: "Ball in court" wrapped to three lines when the
   nowrap data columns squeezed its column down to the word width. */
.chist-table th { white-space: nowrap; }
.chist-table td { vertical-align: middle; }
.chist-table tr.chist-live td { background: var(--tint); }
.chist-date { white-space: nowrap; font-weight: 600; color: var(--ink); }
.chist-badge {
  font-size: 9.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ok-ink); background: var(--ok-fill); border: 1px solid var(--ok-line);
  border-radius: 9px; padding: 1px 6px; margin-left: 5px; vertical-align: 1px;
}
.chist-badge.moved { color: var(--warn-ink); background: var(--warn-fill); border-color: var(--warn-line); }
.chist-cleared { color: var(--muted); font-style: italic; font-weight: 400; }
.chist-slip { white-space: nowrap; color: var(--muted); font-variant-numeric: tabular-nums; }
.chist-slip.late { color: var(--danger-ink); font-weight: 600; }
.chist-slip.early { color: var(--ok-ink); font-weight: 600; }
.chist-when, .chist-who, .chist-bic { color: var(--muted); }
.chist-when { white-space: nowrap; }
.chist-bic { min-width: 96px; max-width: 190px; overflow-wrap: break-word; }
.chist-act { width: 34px; text-align: right; }
.chist-del {
  border: 1px solid transparent; background: none; border-radius: 5px;
  padding: 2px 5px; font-size: var(--fs-md); line-height: 1; cursor: pointer; color: var(--muted);
}
.chist-del:hover:not(:disabled) { color: var(--danger-ink); background: var(--danger-fill); border-color: var(--danger-line); }
.chist-del:disabled { cursor: default; opacity: .5; }
.chist-tip { flex: 1; font-size: var(--fs-xs); color: var(--muted); line-height: 1.4; }
/* Six columns do not fit a phone at the desktop cell padding; the modal body
   scrolls, but tightening the cells is what keeps it from needing to. */
@media (max-width: 640px) {
  .chist-table th, .chist-table td { padding: 5px 6px; }
  .chist-current-date { font-size: var(--fs-xl); }
  .chist-sub { max-width: 60vw; }
}

/* ---------- toast ---------- */
/* #F1: the default toast used `background: var(--ink)` with `color:#fff`. --ink
   flips to near-white in dark mode, so every default toast rendered white-on-
   white (1.22:1 — invisible). --vcg-d is the "stays dark in both themes" FILL
   role, which is what a dark toast actually wants. */
/* `pre-line` so a caller may pass several lines as one toast. The element is a
   singleton per page: two toast() calls in a row overwrite each other in the
   same frame, so anything that has more than one thing to say has to say it in
   one message or lose all but the last (#386). */
.toast { position: fixed; bottom: calc(20px + env(safe-area-inset-bottom, 0px)); left: 50%; transform: translateX(-50%); background: var(--vcg-d); color: #fff; padding: 10px 18px; border-radius: var(--radius-lg); font-size: 12.5px; line-height: 1.45; white-space: pre-line; box-shadow: var(--shadow-lg); z-index: var(--z-toast); max-width: 70vw; }
/* #a11y: the toast is a live region now, and a live region inside
   display:none is not in the accessibility tree at all. All eighteen toast
   helpers set the text FIRST and clear .hidden second, so a display:none
   toast announced nothing — a screen-reader user had no way to learn that
   a save succeeded or failed. Clipping it keeps the node live and announcing
   while leaving it exactly as invisible and as unclickable as it was. It is
   position:fixed, so this costs no layout either way. */
.toast.hidden { opacity: 0; pointer-events: none; clip-path: inset(50%); }
/* the toast is a "stays dark in both themes" surface like the app bar, so the
   error fill must stay a DARK red — --danger-ink is the light text-on-fill role
   and would put white text on a pale red (2.14:1). */
.toast.err { background: light-dark(#a4262c, #8c2027); }
/* #386: `.ok` was an ALIAS of the neutral fill, so every "Saved." / "Created" /
   "Imported N rows" toast in the app was indistinguishable from a plain notice —
   which left `err` as the only fill that said anything, and a page reporting a
   SUCCESSFUL report-card upload through it announced the success in error red.
   Same rule as `.err`: the toast is not a themed surface, so both fills stay
   dark in BOTH themes and keep the white text — a dark green (6.5:1 light /
   8.0:1 dark) and a dark amber (5.9:1 / 8.2:1). Deliberately NOT the
   --ok-fill / --warn-fill badge role, which is a near-white chip meant to carry
   --ok-ink text and would render white-on-white here, the #F1 bug over again.
   `.warn` is "it worked, but check this" — the caveats a report-card upload
   reports back — so a caveat stops claiming the upload failed. */
.toast.ok   { background: var(--ok-solid); }
.toast.warn { background: light-dark(#8A5A00, #6D4703); }
.hidden { display: none; }

/* ---------- software update banner (#vorea-update-banner) ---------- */
#vorea-update-banner {
  position: fixed; bottom: 20px; right: 20px; z-index: var(--z-banner);
  display: flex; align-items: center; gap: 10px;
  max-width: 540px; padding: 11px 14px;
  background: var(--vcg-dd); color: #e8edf0;
  border-radius: 9px; box-shadow: var(--shadow-lg);
  font-size: 12.5px; line-height: 1.4;
  border-left: 3px solid var(--accent);
  animation: vu-fadein .25s ease;
}
#vorea-update-banner.hidden { display: none; }
#vorea-update-banner.vorea-update-banner-updating {
  border-left-color: #7ec8ff;
}
#vorea-update-banner.vorea-update-banner-error {
  border-left-color: #e07070;
}

@keyframes vu-fadein {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.vu-icon { font-size: var(--fs-lg); flex-shrink: 0; }
.vu-text { flex: 1; min-width: 0; }
.vu-text strong { color: #fff; }
.vu-sha {
  font-family: ui-monospace, monospace; font-size: var(--fs-xs);
  background: rgba(255,255,255,.12); padding: 1px 5px; border-radius: var(--radius-sm);
  color: #cdd9e0;
}
.vu-subject {
  display: block; font-size: var(--fs-sm); color: #9aacb8; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 380px;
}
.vu-launcher-note { display: block; font-size: var(--fs-sm); color: #9aacb8; margin-top: 2px; }

.vu-update-btn {
  /* #F8: white text on --accent is 4.17:1 — --accent-solid is the FILL role
     for exactly this and clears AA in both themes (5.23:1 / 4.91:1). */
  flex-shrink: 0; background: var(--accent-solid); border-color: var(--accent-solid);
  color: #fff; font-weight: 700; white-space: nowrap;
}
.vu-update-btn:hover:not(:disabled) { background: var(--accent-solid-hover); border-color: var(--accent-solid-hover); }
.vu-update-btn:disabled { opacity: .55; cursor: default; }

.vu-dismiss {
  flex-shrink: 0; background: none; border: none; cursor: pointer;
  color: #8a9aaa; font-size: var(--fs-xl); line-height: 1; padding: 0 2px;
}
.vu-dismiss:hover { color: #cdd9e0; }

/* spinner used in the "updating" state */
.vu-spinner {
  display: inline-block; flex-shrink: 0;
  width: 15px; height: 15px;
  border: 2px solid rgba(255,255,255,.25);
  border-top-color: #7ec8ff;
  border-radius: 50%;
  animation: vu-spin .75s linear infinite;
}
@keyframes vu-spin { to { transform: rotate(360deg); } }

/* ---------- admin update section styles ---------- */
.upd-grid { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.upd-cell { flex: 1 1 150px; }
.upd-label { font-size: var(--fs-xs); font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 3px; }
.upd-value { font-size: 13.5px; font-weight: 600; color: var(--heading); }
.upd-value code { font-family: ui-monospace, monospace; font-size: var(--fs-md); background: var(--tint); padding: 2px 6px; border-radius: var(--radius-sm); color: var(--heading); }
.upd-subject { font-size: var(--fs-md); color: var(--muted); margin-top: 4px; }
.upd-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 4px; }
.upd-status { font-size: var(--fs-md); color: var(--muted); flex: 1; min-width: 0; }
.upd-status.upd-ok { color: var(--ok-ink); }
.upd-status.upd-err { color: var(--danger-ink); }
.upd-spinner-inline {
  display: inline-block; width: 12px; height: 12px; margin-right: 5px; vertical-align: middle;
  border: 2px solid rgba(0,0,0,.15); border-top-color: var(--heading);
  border-radius: 50%; animation: vu-spin .75s linear infinite;
}

/* ---------- data search ---------- */
.data-search { padding: 5px 9px; border: 1px solid var(--line-dk); border-radius: 16px; font-size: var(--fs-md); min-width: 190px; }
.data-search:focus { outline: 2px solid var(--vcg); border-color: var(--vcg); }

/* ---------- collapsible field panel ---------- */
body.panel-hidden .fields-pane { display: none; }
#togglePanelBtn { font-weight: 600; }

/* ---------- link icon ---------- */
.link-ico { text-decoration: none; font-size: var(--fs-base); opacity: .85; }
.link-ico:hover { opacity: 1; }
/* "Review" beside a submittal's 📎 when it's in your court and the sub has
   returned it (review-buttons.js). A screen control, never ink on a report. */
.rv-btn {
  font: inherit; font-size: var(--fs-xs); font-weight: 700; line-height: 1.3; cursor: pointer;
  margin-left: 4px; padding: 1px 7px; vertical-align: baseline;
  color: var(--accent); background: var(--surface); border: 1px solid var(--accent);
  border-radius: var(--radius-pill); white-space: nowrap;
}
.rv-btn:hover, .rv-btn:focus-visible { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
@media print { .rv-btn { display: none !important; } }
.procore-link { color: inherit; text-decoration: underline; text-decoration-color: rgba(0,0,0,.3); text-underline-offset: 2px; }
.procore-link:hover { text-decoration-color: currentColor; }

/* ---------- per-level config popover ---------- */
.cfg-pop { position: absolute; top: 110%; right: 0; z-index: var(--z-pop); background: var(--elevated); border: 1px solid var(--line-dk); border-radius: 8px; box-shadow: var(--shadow-lg); width: 220px; padding: 10px; }
.cfg-title { font-size: var(--fs-xs); font-weight: 700; color: var(--heading); text-transform: uppercase; letter-spacing: .4px; margin-bottom: 4px; }
.cfg-opt { display: flex; gap: 6px; align-items: center; font-size: var(--fs-md); padding: 2px 0; cursor: pointer; }
.cfg-swatches { display: flex; gap: 6px; flex-wrap: wrap; }
.cfg-sw { width: 22px; height: 22px; border-radius: 5px; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: var(--fs-md); }
.cfg-sw.sel { outline: 2px solid var(--accent); outline-offset: 1px; }
.cfg-note { font-size: 10.5px; color: var(--muted); margin-top: 8px; }

/* ---------- print header / footer (screen-hidden) ---------- */
.print-header { display: none; }
table.pivot tfoot { display: none; }   /* footer row only appears in print */
tr.print-only { display: none; }       /* branded banner row only appears in print */

/* ============================================================
   REDUCED MOTION
   ------------------------------------------------------------
   photos.css and project-home.css each answer prefers-reduced-motion for their
   own decorative motion; the other stylesheets and the inline page blocks did
   not, so most of the suite animated regardless of the OS setting.

   This is deliberately NOT the usual `animation: 0.01ms !important` kill. That
   pattern also destroys the transitions that CARRY MEANING here — the row tint
   confirming a filter applied, the focus ring, a button's pressed state — and
   on a data tool losing those is its own accessibility problem. Instead:
   looping and travelling animation stops, and transitions collapse to a short
   120ms fade that still reports the state change without moving anything
   across the screen.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 120ms !important;
  }
  html { scroll-behavior: auto !important; }
}

/* ============================================================
   PRINT / PDF  — clean branded report (Excel-like), room to breathe
   ============================================================ */
@media print {
  /* Printouts always go on white paper: re-pin every theme token to its light
     value so a dark-mode viewer still prints a clean light report (#183). */
  /* #221: force LIGHT in print regardless of the toggle or OS — every
     light-dark() token (here and in reports.css) then resolves to its light
     value, so printouts stay on white paper. !important beats the data-theme
     color-scheme rule; :root[data-theme] covers a forced theme, :root the
     "auto" (unset) case. */
  :root, :root[data-theme] { color-scheme: light !important; }
  /* Margins: LEFT/RIGHT come from .pivot-inner padding (below) so they survive the
     print dialog's "Margins: None" setting; TOP/BOTTOM stay on @page so they repeat
     on every page and leave room for the bottom-center page numbers. */
  @page {
    size: landscape; margin: 0.5in 0 0.55in 0;
    @bottom-center { content: "Page " counter(page) " of " counter(pages); font-size: 8pt; color: #555; }
  }

  html, body, .appshell, .appmain { display: block !important; height: auto; overflow: visible; background: #fff; }
  * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
  .sidenav, .topnav, .megamenu, .appbar, .ribbon, .fields-pane, .filters-bar, .appfooter, .toast, .modal, .cfg-pop, .slicer-pop, .support-pop, .print-header, .reports-pane { display: none !important; }
  .layout, .pivot-pane { display: block; height: auto; overflow: visible; }
  .pivot-scroll { overflow: visible; height: auto; padding: 0; background: #fff; }
  .pivot-inner { padding: 0 0.7in; box-sizing: border-box; width: 100%; min-width: 0; }
  .pivot-inner > table.pivot { box-shadow: none; border-radius: 0; overflow: visible; }

  /* hide interactive glyphs so the printout reads like the Excel report */
  .toggle, .star, .leaf-spacer, .hcol-btn, .col-resize, .sort-ind { display: none !important; }
  .hcol-sep { margin: 0 4px; }

  /* Fit the whole table inside the page width so margins are always maintained:
     fixed layout + wrapping prevents the table from overflowing the printable area. */
  table.pivot { font-size: 8px !important; width: 100% !important; border-collapse: collapse; table-layout: fixed; overflow: visible; }
  table.pivot th, table.pivot td {
    border: 1px solid var(--line) !important; padding: 2px 4px;
    white-space: normal !important; overflow-wrap: anywhere; word-break: break-word; vertical-align: top;
  }
  /* Issue #37: column/value headers may wrap to at most two lines — clip beyond two
     so a long header never pushes the rows tall. The branded banner and the corner
     cells (row-label headers, which legitimately span multiple header rows / stack
     several level names) are exempt. */
  table.pivot thead th:not(.print-banner):not(.corner) { line-height: 1.15; max-height: 2.5em; overflow: hidden; }
  /* type-based column widths so the table fits the page and matches Excel proportions */
  table.pivot col.pcol-xl { width: 22%; }
  table.pivot col.pcol-l  { width: 13%; }
  table.pivot col.pcol-m  { width: 8%; }
  table.pivot col.pcol-s  { width: 7%; }
  table.pivot col.pcol-v  { width: 5%; }
  table.pivot col.pcol-notes { width: 16%; }
  table.pivot col.pcol-resp  { width: 18%; }
  table.pivot td.val, table.pivot th.val { text-align: right; }
  /* ✎ Notes column prints its text; keep the typed line breaks. The pencil
     button is redundant on paper once the note text itself prints. */
  table.pivot td.note-cell { white-space: pre-wrap !important; text-align: left; }
  table.pivot.notes-col .note-btn { display: none !important; }
  /* #314: print the comment feed as text; drop the add-input and delete buttons. */
  table.pivot td.note-cell .cmt-feed { max-height: none !important; overflow: visible !important; }
  table.pivot td.note-cell .cmt-add, table.pivot td.note-cell .cmt-del { display: none !important; }
  table.pivot tr.note-row td.note-row-cell { white-space: pre-wrap !important; text-align: left;
    background: var(--tint) !important; color: var(--vcg-d) !important; }
  /* #218 Responses column prints with the table (read-only; keeps its per-reviewer line breaks). */
  table.pivot td.response-cell { text-align: left; color: var(--vcg-d) !important; vertical-align: top; }
  table.pivot thead th { position: static; background: var(--hdr) !important; color: var(--vcg-d) !important; border-bottom: 1.5px solid var(--line-dk) !important; }
  table.pivot thead th.corner { position: static; background: var(--vcg-dd) !important; color: #fff !important; }
  table.pivot td.rowlabel { position: static; max-width: none; }
  tr.priority td { background: var(--priority); color: var(--priority-ink); }
  tr { page-break-inside: avoid; }

  /* repeat banner + column headers at top, branded footer at bottom, on EVERY page */
  thead { display: table-header-group; }
  tfoot { display: table-footer-group; }
  tr.print-only { display: table-row !important; }

  /* branded banner (mirrors the Excel print header).
     Uses display:table (NOT flex) — a flex colspan cell containing a replaced
     image collapses to ~1 column under table-layout:fixed in Chromium, which made
     the header wrap one character per line. Table-cells fill the full colspan. */
  th.print-banner { background: #fff !important; border: none !important; border-bottom: 2px solid var(--vcg) !important; padding: 0 0 7px !important; }
  .pb { display: table; width: 100%; table-layout: fixed; }
  .pb-l, .pb-c, .pb-r { display: table-cell; vertical-align: top; }
  .pb-l { width: 30%; font-size: 9px; color: #222; text-align: left; line-height: 1.3; font-weight: 400; }
  .pb-l .pb-co { display: block; font-weight: 700; }
  .pb-c { width: 40%; text-align: center; }
  .pb-c .pb-proj { font-size: 15px; font-weight: 700; color: var(--vcg-dd); letter-spacing: .3px; }
  .pb-c .pb-title { font-size: 10px; color: #444; margin-top: 2px; }
  .pb-r { width: 30%; text-align: right; }
  .pb-r img, .pb-logo { height: 26px; max-width: 100%; }
  .pb-em { font-size: 15px; font-weight: 700; color: var(--vcg-dd); display: block; }

  /* branded footer — same table-cell technique for full-width reliability */
  table.pivot tfoot .print-foot-cell { border: none !important; border-top: 1px solid var(--line-dk) !important; padding: 5px 2px 0 !important; background: #fff !important; font-weight: 400; }
  .pf { display: table; width: 100%; table-layout: fixed; font-size: 8px; color: #555; }
  .pf .pf-l, .pf .pf-c, .pf .pf-r { display: table-cell; vertical-align: top; }
  .pf .pf-l { width: 34%; text-align: left; line-height: 1.3; }
  .pf .pf-c { width: 33%; text-align: center; }
  .pf .pf-r { width: 33%; text-align: right; }
}

/* ============================================================
   LEFT NAV RAIL + REPORTS BUILDER (screen)
   ============================================================ */
.appshell { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.appmain { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; }

/* THE SHARED SCROLLING CONTENT PANE (#610 "Enable scroll on page").
   `body` is a fixed-height flex column with `overflow: hidden` — the shell's
   height IS the usable area and the document itself never scrolls — so the
   pane between the app bar and the footer has to carry its own overflow. Every
   page written before September declares that on a private class of its own
   (`.pm-page`, `.vb-page`, `.ins-page` … nineteen near-identical copies of the
   same four declarations). Priority Circulation and Inspections were written
   against `class="page"` on the reasonable assumption that the shared one
   already existed. It did not: the class matched no rule anywhere, so both
   pages had no overflow of their own and simply CLIPPED everything past the
   fold — no scrollbar, no wheel, nothing under the first screenful reachable.
   This is the rule they were written for.
   `margin-inline: auto` because both pages set their own `max-width` on this
   same element rather than on an inner wrapper; without it a capped page sits
   flush left instead of centred. */
.page {
  flex: 1; overflow: auto; padding: 24px 32px 64px;
  background: var(--bg); width: 100%; margin-inline: auto;
}
@media (max-width: 640px) { .page { padding: 12px 12px 44px; } }
/* #F14: visually-hidden but announced — for pages whose design has no room for
   a visible page title (the dashboard opens straight into its own nav bar). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* #F14: this is now an <h1> on all 22 app pages (it was a <div>, so most pages
   had no h1 at all and screen readers got no page title). Reset the UA h1
   margin/size so the app bar looks exactly as before. */
.appbar-title { font-size: var(--fs-lg); font-weight: 600; color: #fff; letter-spacing: .3px; margin: 0;
  /* #F16: flex so the SVG page icon sits inline with the title text. */
  display: flex; align-items: center; gap: var(--sp-2); }

/* ---- Top navigation bar + categorized mega-menu ----
   Replaces the vertical rail (#67): a one-row bar that no longer clips
   top-to-bottom on short laptops, with the tools grouped into a dropdown
   panel the way Procore's tool menu is organized. */
/* nav.js REPLACES this placeholder with the real .topnav, and it does so from a
   module at the foot of the page - i.e. after the browser's first paint. The
   placeholder is an empty div, so until then the bar occupies no height and
   everything under it renders 50px too high, then drops when the nav lands.
   That was invisible while Home opened on empty skeletons; it stopped being
   invisible the moment Home started painting its warm cache in the first frame
   (home-cache.js), because the reader is now looking at real content when the
   page jumps. Reserve the bar's height here so nothing moves - the number has
   to track .topnav's `height` below. Every page carries this placeholder, so
   every page gets the fix. */
#nav-placeholder:empty { display: block; height: 50px; }
.topnav {
  flex: 0 0 auto; display: flex; align-items: center; gap: 4px;
  background: var(--surface); color: var(--ink); height: 50px; padding: 0 12px;
  border-bottom: 1px solid var(--line); box-shadow: var(--shadow);
  position: relative; /* no z-index, so existing in-content popups/modals keep
                         their stacking. Not an offset parent any more — see the
                         CAUTION below for why .megamenu had to leave. */
  /* #F17: the nav's children are all `white-space: nowrap`, so below ~800px
     they could not shrink and pushed the DOCUMENT wider than the viewport —
     every page scrolled sideways on a phone (51px over at 390px, more with a
     project selector). min-width:0 lets the flex items actually shrink, and
     the scroll container makes the nav itself absorb anything left over
     instead of the page. The scrollbar is hidden because the row is 50px tall
     and a visible bar would eat it.
     CAUTION: this makes .topnav a scroll container, so anything anchored to it
     that needs to paint OUTSIDE the 50px row must be `position: fixed`, not
     `absolute` — absolute children get clipped to the row and disappear. That is
     what happened to .megamenu; both it and .support-pop are fixed for exactly
     this reason, and the `clipped-nav-popover` rule in
     scripts/check_formatting_invariants.mjs enforces it. */
  min-width: 0; overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -ms-overflow-style: none;
}
.topnav::-webkit-scrollbar { display: none; }
.topnav > * { flex-shrink: 0; }
.topnav-brand { display: flex; align-items: center; height: 100%; padding: 0 12px 0 4px; margin-right: 2px; }
.topnav-brand .logo-img { height: 22px; }
.topnav-spacer { flex: 1; }

.topnav-trigger {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: var(--radius);
  color: var(--ink); font-size: var(--fs-base); font-weight: 600; padding: 8px 12px;
  transition: background .12s, border-color .12s;
}
.topnav-trigger:hover { background: var(--surface-3); }
.topnav-trigger[aria-expanded="true"] { background: var(--surface-3); border-color: var(--line); }
.topnav-trigger .tt-ic { font-size: 16px; line-height: 1; }
.topnav-trigger .tt-caret { font-size: var(--fs-2xs); color: var(--muted); }

/* right-side bar actions (Requests, Feature Request) */
.topnav-link {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer; text-decoration: none;
  background: transparent; border: 1px solid transparent; border-radius: var(--radius);
  color: var(--ink); font-size: 12.5px; font-weight: 500; padding: 8px 11px;
  transition: background .12s, color .12s;
}
.topnav-link:hover { background: var(--surface-3); }
.topnav-link .nav-ic { font-size: var(--fs-lg); line-height: 1; }
.topnav-link.fr-btn { background: rgba(115,146,167,.14); font-weight: 600; }
.topnav-link.fr-btn:hover { background: rgba(115,146,167,.26); }

/* the dropdown panel — categorized columns, like the Procore tool menu */
.megamenu {
  /* FIXED, not absolute, and this is load-bearing: #F17 above made .topnav a
     scroll container, and a scroll container clips its own absolutely-positioned
     children. The menu still opened with the right size and z-index — it was
     simply clipped away to nothing inside a 50px-tall box, so it read as "hidden
     behind the UI" and took every tool icon in the app with it. A fixed element
     is positioned against the viewport and is not clipped by an ancestor's
     overflow, which is also how .support-pop (the other top-bar popover) already
     escapes the same box. `top` is measured off the bar when the menu opens
     (feature-request.js initMegaMenu); the value here is the static fallback —
     the 50px row plus its 1px border. */
  position: fixed; top: 51px; left: 8px; z-index: var(--z-nav); /* #190: sit above the dashboard sticky bar (z-index 100), below shared modal/toast (130/140) and drawer/modal overlays (200/210) */
  /* Wrap rather than force one row: five 168px columns need ~990px, so between
     721px (where they stack) and ~990px the last column used to sit outside the
     panel's own `overflow: auto` box and had to be scrolled to. Wrapping keeps
     every tool on screen at every width. */
  display: flex; flex-wrap: wrap; gap: 26px; padding: 18px 22px 20px;
  background: var(--elevated); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: 0 10px 34px rgba(20,30,40,.18);
  max-width: calc(100vw - 16px); max-height: calc(100vh - 70px); overflow: auto;
  /* Mobile foundation: clear the home indicator; no-op (0) without insets. */
  padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
}
.megamenu[hidden] { display: none; }
/* #101 follow-up — no flash of tools a user can't see. The mega-menu's tool
   columns stay hidden until tool-access.js has resolved the signed-in user's
   access and revealed the allowed items (it adds `ta-ready` on <html>). Without
   this, a gated link — e.g. Subcontractor Ratings for an external subcontractor
   — ships in the static markup and is briefly visible AND clickable before the
   async gate removes it. JS always adds the class once the access call settles
   (success, error, or the local build), so the menu is never left blank. */
html:not(.ta-ready) .megamenu .mm-col { display: none !important; }
/* Pin strip (phase 2, pins.js): chips are CLONES of real menu rows - same
   ta-ready gate as the columns above, and belt-and-braces on top of it, since
   pins.js re-invokes applyToolAccess() after every render (a chip carries the
   same real href as its row, so the TOOL_HREFS removal loop above strips a
   just-revoked pin for free, no separate gating logic needed here). */
html:not(.ta-ready) .topnav-pin { display: none !important; }
.mm-col { min-width: 168px; display: flex; flex-direction: column; }
.mm-head {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); padding: 0 8px 6px; margin-bottom: 4px; border-bottom: 1px solid var(--line);
}
/* nav entries render as left-aligned rows inside the menu */
.navitem {
  background: transparent; border: none; cursor: pointer; text-decoration: none;
  display: flex; flex-direction: row; align-items: center; gap: 10px; width: 100%;
  padding: 8px 8px; border-radius: var(--radius); color: var(--ink);
  font-size: var(--fs-base); font-weight: 500; text-align: left; transition: background .12s, color .12s;
}
.navitem .nav-ic { font-size: 16px; line-height: 1; width: 20px; text-align: center; }
.navitem:hover { color: var(--ink); background: var(--surface-3); }
.navitem.on { color: var(--ink); background: rgba(115,146,167,.18); font-weight: 600; }
.sidenav-divider { display: none; }

/* Menu head blocks (phase 4a, pins.js): the scope header, Pinned and Recent
   blocks injected at the TOP of .megamenu, before the first .mm-col.
   DELIBERATELY not .mm-col - hideEmptyMenuColumns() (tool-access.js) and
   collectTools() (command-palette.js) both iterate ".megamenu .mm-col" and
   must never see it, same gate as the columns until access resolves. */
html:not(.ta-ready) .mm-pins { display: none !important; }
.mm-pins {
  display: flex; flex-direction: column; gap: 10px; width: 100%;
  padding-bottom: 12px; margin-bottom: 8px; border-bottom: 1px solid var(--line);
}
.mm-scope { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: var(--fs-md); }
.mm-scope-label { font-weight: 700; color: var(--ink); margin-right: auto; }
.mm-scope-link { color: var(--link); text-decoration: none; font-weight: 600; }
.mm-scope-link:hover { text-decoration: underline; }
.mm-scope-cfg {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: var(--radius); color: var(--muted); text-decoration: none;
}
.mm-scope-cfg:hover { background: var(--surface-3); color: var(--ink); }

.mm-pins-head {
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 4px;
  /* Row+wrap below needs the head on its OWN line, ahead of the chips it
     labels rather than beside the first one. Harmless on the phone override
     (column direction) below - a column child is already full-width via the
     default align-items:stretch, with or without this. */
  width: 100%;
}
/* Desktop default: compact chips that wrap in a row, like the top-bar strip
   - NOT the full-width stacked rows every other menu row uses, which is
   correct only for the phone sheet's single dense column and is restored
   there by the max-width:720px override further down. (#578 removed the
   menu's Pinned block - the pinned surface inside the menu is each catalog
   row's own ★ toggle, in place.) */
.mm-recent { display: flex; flex-direction: row; flex-wrap: wrap; gap: 6px; }

/* Recent: dashed-border chip style, plain links (no star) - visually
   distinct from a catalog row so the two never read as the same kind of
   control. Sized to content, same reason as .topnav-pin. */
.mm-recent-chip {
  display: inline-flex; width: auto;
  border: 1px dashed var(--line-dk); border-radius: var(--radius); margin-bottom: 2px;
}

/* Row-hover star (phase 4a): every .mm-col row gets a pin toggle, injected
   by pins.js as the row's LAST child. Hidden until hover/focus so the dense
   catalog columns don't grow a permanent extra glyph on every line; shown on
   :focus-within too so a keyboard user tabbing through the menu can still
   reach it without a mouse. */
.row-star {
  flex: none; margin-left: auto; background: transparent; border: none; cursor: pointer;
  color: var(--muted); font-size: var(--fs-base); line-height: 1; opacity: 0;
  width: 22px; height: 22px; border-radius: var(--radius-sm);
}
.navitem:hover .row-star, .navitem:focus-within .row-star, .row-star:focus { opacity: 1; }
.row-star:hover { background: var(--surface-3); color: var(--ink); }
/* Touch has no hover state to reveal this FROM. It used to be dropped here
   (the menu's Pinned block was the touch surface) - but #578 removed that
   block, so on touch the row star is now the ONLY in-place pin control:
   always visible, grown toward the WCAG 2.5.5 tap-target floor. */
@media (hover: none) { .row-star { opacity: 1; width: 40px; height: 40px; } }

/* Procore-style active-project selector in the top bar (#69) */
.topnav-project {
  height: 34px; max-width: 240px; margin-left: 2px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
  font-size: var(--fs-base); font-weight: 600; padding: 0 10px;
  cursor: pointer; transition: background .12s, border-color .12s;
}
.topnav-project:hover { background: var(--surface-3); }
.topnav-project:focus { outline: none; border-color: var(--vcg); box-shadow: 0 0 0 2px rgba(115,146,167,.22); }
.topnav-project:disabled { color: var(--muted); cursor: default; }
/* #F17: the project selector is the one nav item that should give up space
   first — it has a title attribute, so a truncated label is still recoverable. */
@media (max-width: 720px) { .topnav-project { max-width: 148px; flex-shrink: 1; min-width: 90px; } }
@media (max-width: 640px) { .topnav-project { max-width: 110px; } }

/* Pin strip (phase 2, pins.js): up to 6 chips inserted right after
   .topnav-trigger, each a CLONE of a real menu row (copied href/label/icon -
   the #F16 rule on icon markup applies here too). STATIC positioning only -
   NEVER absolute: .topnav is a scroll container (#F17) and clips anything
   absolutely positioned inside it, same lesson .megamenu already paid for.
   flex-shrink: 0 comes from `.topnav > *` above; no extra rule needed here. */
.topnav-pin {
  position: static;
  /* width: auto beats .navitem's width: 100% - a chip is a flex item in the
     .topnav row (flex-shrink: 0), so inheriting 100% makes each chip
     viewport-wide and shoves every control after it off-screen. */
  width: auto;
  height: 34px; padding: 0 10px; gap: 6px;
  display: inline-flex; align-items: center;
  border: 1px solid transparent; border-radius: var(--radius);
  font-size: var(--fs-base); font-weight: 600;
}
.topnav-pin .nav-ic { font-size: var(--fs-lg); line-height: 1; }
.topnav-pin:hover { background: var(--surface-3); border-color: var(--line); }
/* Phone: the strip's equivalent is the menu's own Pinned block (phase 4), not
   a row of chips squeezed beside a search button. */
@media (max-width: 899px) { .topnav-pin { display: none; } }
/* Narrow desktop/tablet: keep the 4 most relevant of up to 6, not all of
   them - the rest stay one tap into the menu. `topnav-pin-overflow` is set at
   RENDER time by pins.js (on the 5th/6th chip actually painted, so a pin
   whose row doesn't exist never burns one of the 4 visible slots) rather than
   measured here with a resize observer - simpler, and it can't fight a
   reflow mid-resize the way a JS measurement can. */
@media (min-width: 900px) and (max-width: 1200px) {
  .topnav-pin.topnav-pin-overflow { display: none; }
}

/* #130: "Refresh from Procore" pill — pairs with the project selector on the
   report pages (Ball in Court, Snapshots, Dashboard) that lack their own. */
.topnav-refresh {
  height: 34px; margin-left: 6px;
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
  font-size: 12.5px; font-weight: 600; padding: 0 12px;
  cursor: pointer; transition: background .12s, border-color .12s;
}
.topnav-refresh:hover { background: var(--surface-3); border-color: var(--vcg); }
.topnav-refresh:disabled { color: var(--muted); cursor: default; }
.topnav-refresh .nav-ic { font-size: 14px; line-height: 1; }
@media (max-width: 720px) { .topnav-refresh .nav-tx { display: none; } }

/* Support Center dropdown (top-right help button + panel, see feature-request.js) */
/* #F13: this button sits on its own copper tint, which lifted the effective
   background and dropped --accent text to 4.09:1 and the muted caret to 4.18:1
   — on all 27 pages that carry the nav. The caret now inherits the button ink,
   and the ink is the deeper --accent-solid. */
.support-btn .tt-caret { font-size: var(--fs-2xs); color: inherit; opacity: .8; }
/* literal pair, not var(--accent-solid): that token is itself a light-dark()
   value and light-dark() cannot be nested. Light needs a deeper ink on the
   copper tint; dark already passed with the brighter amber. */
.topnav-link.support-btn { background: rgba(184,104,32,.12); font-weight: 600; color: light-dark(#A25A18, #d98a3d); }
.topnav-link.support-btn:hover,
.topnav-link.support-btn[aria-expanded="true"] { background: rgba(184,104,32,.22); }
.support-pop {
  position: fixed; z-index: var(--z-pop); width: 304px;
  background: var(--elevated); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: 0 10px 34px rgba(20,30,40,.20);
  padding: 8px; display: flex; flex-direction: column; gap: 2px;
}
.support-pop[hidden] { display: none; }
.support-head {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); padding: 6px 10px 8px; margin-bottom: 4px; border-bottom: 1px solid var(--line);
}
.support-item {
  display: flex; align-items: flex-start; gap: 11px; width: 100%; text-align: left;
  background: transparent; border: none; cursor: pointer; text-decoration: none;
  padding: 9px 10px; border-radius: 7px; color: var(--ink); font: inherit;
}
.support-item:hover { background: var(--surface-3); }
.support-ic { font-size: var(--fs-xl); line-height: 1.25; width: 22px; text-align: center; flex: none; }
.support-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.support-tx b { font-size: var(--fs-base); font-weight: 600; }
.support-tx small { font-size: var(--fs-xs); color: var(--muted); line-height: 1.35; }

/* #378: Announcement popup — what changed on YOUR feature requests. Global
   because frontend/js/fr-announcements.js rides feature-request.js onto every
   page; the request queue's own badge styles are page-local, so the few tokens
   this needs are re-stated here rather than shared. */
.ann-box { width: 520px; }
.ann-intro { font-size: 12.5px; color: var(--muted); margin-bottom: 12px; }
.ann-card {
  border: 1px solid var(--line); border-left: 3px solid var(--line-dk);
  border-radius: 8px; padding: 11px 13px; margin-bottom: 10px; background: var(--surface-2);
}
/* The one rung that WAITS on the reader — flagged so it reads as a request, not
   an FYI (Implemented - Feature Testing needs their confirm or rejection). */
.ann-card.ann-needs-you { border-left-color: var(--accent); background: rgba(184,104,32,.07); }
.ann-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.ann-headline { font-weight: 600; font-size: 13.5px; color: var(--heading); }
.ann-ref { font-size: var(--fs-sm); font-weight: 600; color: var(--accent); text-decoration: none; flex: none; }
.ann-ref:hover { text-decoration: underline; }
.ann-summary { font-size: 12.5px; color: var(--ink); margin-top: 3px; }
.ann-move {
  display: flex; align-items: center; gap: 7px; margin-top: 7px;
  font-size: var(--fs-sm); color: var(--muted);
}
.ann-move b { color: var(--heading); font-weight: 600; }
.ann-arrow { opacity: .7; }
.ann-detail { font-size: var(--fs-md); color: var(--muted); line-height: 1.45; margin-top: 7px; }
.ann-prs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.ann-pr {
  font-size: var(--fs-xs); font-weight: 600; text-decoration: none; padding: 1px 7px;
  border: 1px solid var(--line); border-radius: var(--radius-pill); color: var(--muted); background: var(--surface);
}
.ann-pr:hover { color: var(--accent); border-color: var(--accent); }
.ann-more { font-size: var(--fs-sm); color: var(--muted); margin-top: 4px; }
/* #432: one-click into a design review waiting on the reader, right on its card. */
.ann-actions { margin-top: 10px; }
.ann-open-review { font-size: var(--fs-md); padding: 5px 12px; text-decoration: none; }
/* The action row reuses .note-footer — the same footer the request modal's
   Submit/Cancel sits in, so the two dialogs read as the same app. */

/* Quick Find trigger in the top bar (opens the ⌘K command palette) */
.topnav-search {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  height: 34px; padding: 0 10px 0 11px; margin-right: 4px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 7px;
  color: var(--muted); font: inherit; font-size: 12.5px; font-weight: 500;
  transition: background .12s, border-color .12s, color .12s;
}
.topnav-search:hover { background: var(--surface-3); border-color: var(--line-dk); color: var(--ink); }
.topnav-search .nav-ic { font-size: 14px; line-height: 1; }
.topnav-search-kbd {
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 10.5px; font-weight: 600;
  color: var(--muted); background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 1px 5px; line-height: 1.4;
}
/* #221: theme toggle — matches the Search control's shape so they read as a set. */
.topnav-theme {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  height: 34px; padding: 0 11px; margin-right: 4px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 7px;
  color: var(--muted); font: inherit; font-size: 12.5px; font-weight: 500;
  transition: background .12s, border-color .12s, color .12s;
}
.topnav-theme:hover { background: var(--surface-3); border-color: var(--line-dk); color: var(--ink); }
.topnav-theme .nav-ic { font-size: 14px; line-height: 1; }
@media (max-width: 720px) {
  .topnav-search .nav-tx, .topnav-search-kbd { display: none; }
  .topnav-search { padding: 0 9px; }
  .topnav-theme .nav-tx { display: none; }
  .topnav-theme { padding: 0 9px; }
  .topnav-tasks .nav-tx { display: none; }
}
/* My Tasks · Notes (tool `tasks`): steel tint, so it reads apart from Support
   Center's copper beside it. Rendered hidden until access is confirmed. */
.topnav-link.topnav-tasks { background: rgba(115,146,167,.14); font-weight: 600; }
.topnav-link.topnav-tasks:hover, .topnav-link.topnav-tasks.on { background: rgba(115,146,167,.26); }
.topnav-link.topnav-tasks[hidden], .mm-tasks[hidden] { display: none; }
.topnav-tasks-badge { font-size: 11px; font-weight: 700; line-height: 16px; padding: 0 6px; border-radius: 999px; background: var(--accent-solid); color: #fff; }
.topnav-tasks-badge[hidden] { display: none; }
.mm-tasks .topnav-tasks-badge { margin-left: auto; }

/* ---------- command palette (Quick Find, ⌘K) ---------- */
.cmdk {
  position: fixed; inset: 0; z-index: var(--z-palette); /* above the FR modal (130) and drawers (210) */
  display: flex; align-items: flex-start; justify-content: center;
  padding: 12vh 16px 16px;
}
.cmdk.hidden { display: none; }
.cmdk-backdrop { position: absolute; inset: 0; background: rgba(20,30,40,.44); }
.cmdk-panel {
  position: relative; width: 100%; max-width: 560px; max-height: 62vh;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--elevated); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 18px 54px rgba(15,22,30,.34);
}
.cmdk-search {
  display: flex; align-items: center; gap: 10px; padding: 12px 14px;
  border-bottom: 1px solid var(--line);
}
.cmdk-search-ic { font-size: var(--fs-lg); line-height: 1; color: var(--muted); flex: none; }
.cmdk-input {
  flex: 1; min-width: 0; border: none; background: transparent; outline: none;
  color: var(--ink); font: inherit; font-size: var(--fs-lg);
}
.cmdk-input::placeholder { color: var(--muted); }
.cmdk-kbd {
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 10.5px; font-weight: 600;
  color: var(--muted); background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 1px 5px; line-height: 1.5;
}
.cmdk-list { overflow: auto; padding: 6px; flex: 1; }
.cmdk-group {
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); padding: 10px 10px 5px;
}
.cmdk-opt {
  display: flex; align-items: center; gap: 11px; padding: 9px 10px; border-radius: 8px;
  cursor: pointer; color: var(--ink); scroll-margin: 6px;
}
.cmdk-opt.active { background: var(--surface-3); }
.cmdk-opt-ic { font-size: var(--fs-lg); line-height: 1; width: 22px; text-align: center; flex: none; }
.cmdk-opt-label { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdk-opt-cat {
  flex: none; font-size: 10.5px; font-weight: 600; color: var(--muted);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 5px; padding: 1px 7px;
}
.cmdk-opt.active .cmdk-opt-cat { border-color: var(--line-dk); }
.cmdk-empty { padding: 26px 16px; text-align: center; color: var(--muted); font-size: var(--fs-base); }
.cmdk-foot {
  display: flex; gap: 16px; padding: 8px 14px; border-top: 1px solid var(--line);
  background: var(--surface-2); font-size: var(--fs-xs); color: var(--muted);
}
.cmdk-foot span { display: inline-flex; align-items: center; gap: 5px; }
@media (max-width: 720px) {
  .cmdk { padding-top: 8vh; }
  .cmdk-foot { display: none; }
}

@media (max-width: 720px) {
  /* nowrap is required, not cosmetic: the base rule wraps so the last column
     can't fall outside the panel between 721px and ~990px, but here the main
     axis is VERTICAL, so wrapping spills the overflow into a second column to
     the RIGHT instead of scrolling down. Measured at 390x667 and 390x570: the
     panel's scrollWidth went 27px past its clientWidth, turning the phone menu
     into a cramped two-column grid that scrolls sideways — the exact failure
     #F17 removed from the document, reintroduced inside the panel. Column
     direction wants one column and a vertical scroll. */
  .megamenu { flex-direction: column; flex-wrap: nowrap; gap: 10px; left: 4px; right: 4px; }
  .topnav-link .nav-tx { display: none; }
  /* Phase 4b phone reorder: the one-column stack above puts every .megamenu
     child in DOM order by default, which buries the personal layer (Pinned/
     Recent/scope) under five columns of catalog. flex `order` only - DESKTOP
     LAYOUT IS UNTOUCHED (no order rule outside this max-width:720px block).
     Field/doc/fin/records keep their existing relative order; core (the
     "who can reach what" administrative column) and .mm-actions (theme/
     support/profile) sink to the bottom since they are reached rarely. */
  .mm-pins { order: 1; }
  .mm-col-field { order: 2; }
  .mm-col-doc { order: 3; }
  .mm-col-fin { order: 4; }
  .mm-col-records { order: 5; }
  .mm-col-beta { order: 6; }
  .mm-col-core { order: 7; }
  /* Developer sinks below Core for the same reason Core sinks below the
     catalog: it is reached rarely, by few people, and never from a phone in
     the field. */
  .mm-col-dev { order: 8; }
  .mm-actions { order: 9; }
  /* The desktop row+wrap pill chips above (Recent) revert to the ORIGINAL
     stacked full-width rows here - every other row in this single-column
     sheet is full-width, so a bordered pill sized to content would be the
     one inconsistent control on the page. Approved as-is; this restores it
     exactly rather than changing it. */
  .mm-recent { flex-direction: column; flex-wrap: nowrap; gap: 2px; }
  .mm-recent-chip { display: flex; width: 100%; }
}

/* ---------- feature request modal ---------- */
.fr-field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 14px; }
.fr-field:last-child { margin-bottom: 0; }
.fr-field label { font-size: var(--fs-md); font-weight: 600; color: var(--heading); }
.fr-field input, .fr-field select, .fr-field textarea {
  padding: 7px 10px; border: 1px solid var(--line-dk); border-radius: var(--radius);
  font-size: var(--fs-base); font-family: inherit; background: var(--surface-2); color: var(--ink);
}
.fr-field input:focus, .fr-field select:focus, .fr-field textarea:focus {
  outline: 2px solid var(--vcg); border-color: var(--vcg); background: var(--surface);
}
/* #324: restrict the details box to vertical resizing only. Left/right dragging
   made the handle hard to grab and, worse, a drag that ended over the modal
   backdrop fired a click that force-closed the whole window (the backdrop guard
   in feature-request.js is the other half of that fix). */
.fr-field textarea { resize: vertical; min-height: 84px; }
.fr-req { color: var(--danger-ink); }
/* #100: name/email/company locked to the signed-in user — read-only, quietly greyed. */
.fr-field input.fr-locked { background: var(--surface-2); color: var(--muted); cursor: not-allowed; }
.fr-field input.fr-locked:focus { outline-color: var(--line-dk); border-color: var(--line-dk); background: var(--surface-2); }
.fr-status { font-size: var(--fs-md); margin-left: auto; color: var(--muted); max-width: 280px; text-align: right; line-height: 1.4; }
.fr-priority-list { border: 1px solid var(--line-dk); border-radius: var(--radius); overflow: hidden; }
.fr-priority-opt {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px; cursor: pointer;
  background: var(--surface); border-bottom: 1px solid var(--line); transition: background .1s;
}
.fr-priority-opt:last-child { border-bottom: none; }
.fr-priority-opt:hover { background: var(--surface-3); }
.fr-priority-opt input[type="radio"] { display: none; }
.fr-priority-opt:has(input:checked) { background: var(--surface-3); }
.fr-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.fr-dot-critical { background: #e53e3e; }
.fr-dot-high     { background: #ed8936; }
.fr-dot-medium   { background: #4299e1; }
.fr-dot-low      { background: #48bb78; }
.fr-priority-label { font-weight: 600; font-size: 12.5px; color: var(--heading); min-width: 60px; }
.fr-priority-desc  { font-size: var(--fs-md); color: var(--muted); }

/* #166: new-feature vs functionality-adjustment picker (mirrors the priority list) */
.fr-type-list { border: 1px solid var(--line-dk); border-radius: var(--radius); overflow: hidden; }
.fr-type-opt {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px; cursor: pointer;
  background: var(--surface); border-bottom: 1px solid var(--line); transition: background .1s;
}
.fr-type-opt:last-child { border-bottom: none; }
.fr-type-opt:hover { background: var(--surface-3); }
.fr-type-opt input[type="radio"] { display: none; }
.fr-type-opt:has(input:checked) { background: var(--surface-3); }
.fr-type-label { font-weight: 600; font-size: 12.5px; color: var(--heading); min-width: 150px; }
.fr-type-desc  { font-size: var(--fs-md); color: var(--muted); }

/* ---------- feature-request attachments (#62) ---------- */
.fr-attach {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 14px 12px; border: 1.5px dashed var(--line-dk); border-radius: var(--radius);
  background: var(--surface-2); color: var(--muted); font-size: var(--fs-md); text-align: center;
  cursor: pointer; transition: background .1s, border-color .1s;
}
.fr-attach:hover, .fr-attach:focus { background: var(--surface-3); border-color: var(--vcg); outline: none; }
.fr-attach.drag { background: var(--surface-3); border-color: var(--vcg); color: var(--heading); }
.fr-attach-ic { font-size: 16px; }
.fr-attach-link { color: var(--vcg); font-weight: 600; text-decoration: underline; }
.fr-attach-hint { font-size: var(--fs-xs); color: var(--muted); margin-top: 5px; }
.fr-attach-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.fr-attach-list:empty { margin-top: 0; }
.fr-attach-item {
  display: flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 4px 6px 4px 4px; border: 1px solid var(--line-dk); border-radius: var(--radius);
  background: var(--surface); font-size: var(--fs-sm);
}
.fr-attach-thumb { width: 30px; height: 30px; border-radius: var(--radius-sm); object-fit: cover; flex-shrink: 0; }
.fr-attach-file { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.fr-attach-name { max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.fr-attach-rm {
  border: none; background: none; cursor: pointer; color: var(--muted);
  font-size: 16px; line-height: 1; padding: 0 2px; flex-shrink: 0;
}
.fr-attach-rm:hover { color: #c0392b; }

/* ============================================================
   COMBINED REPORT (Submittals + RFIs in one PDF)
   ============================================================ */
.combined-print-area { display: none; }

@media print {
  .combined-print-area { display: block; }
  .combined-hidden { display: none !important; }
  /* Force the RFIs section onto a new page */
  .combined-rfi-section { break-before: page; page-break-before: always; }
}

/* ----- Priority summary cover page (page 1 of the combined report) ----- */
.prio-cover-hd { margin-bottom: 10px; }
.prio-cover-title { font-size: var(--fs-2xl); font-weight: 700; color: var(--ink); margin: 16px 0 4px; }
.prio-cover-sub { font-size: var(--fs-md); color: var(--muted); margin-bottom: 18px; }
.prio-cover-cards { margin-bottom: 22px; }
.prio-cover-h3 { font-size: 14px; color: var(--ink); margin: 0 0 8px; }
.prio-cover-courts { width: 100%; }

@media print {
  /* Cover prints as page 1; the open log starts on the next page. */
  .prio-cover { break-after: page; page-break-after: always; }
  .prio-cover .rep-card { box-shadow: none; }
  .prio-cover-courts { font-size: 9pt; }
}

/* ---------- #F12: shared page furniture --------------------------------
   These rule bodies were byte-identical across many pages, each under its own
   2-3 letter prefix. They are defined ONCE here, grouped by every prefix that
   used them, so the pages' markup is unchanged but there is one definition to
   edit. Pages whose body genuinely differed keep their own rule (their page
   <style> still loads after this file and wins). ---------------------------- */
/* page scroll container */
.da-page, .dir-page, .fr-page, .ll-page, .mt-page, .om-page, .perm-page, .pf-page, .sc-page, .sw-page, .uu-page {
  background: var(--bg);
  flex: 1;
  overflow: auto;
  padding: 24px 32px 48px;
}
/* inline note box */
.admin-note, .beta-note, .da-note, .mt-note, .om-note, .uu-note {
  background: var(--tint);
  border-radius: var(--radius);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: var(--fs-sm);
  margin-top: 12px;
  padding: 9px 12px;
}
/* content panel */
.da-section, .mt-section, .om-section, .sc-section, .sw-section, .uu-section {
  background: var(--surface);
  border-radius: 11px;
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  margin-bottom: 24px;
  margin-left: auto;
  margin-right: auto;
  max-width: var(--content-w-wide);
  padding: 20px 24px;
}
/* empty state */
.dir-empty, .fr-empty, .ll-empty, .perm-empty {
  background: var(--surface);
  border-radius: 12px;
  border: 1px dashed var(--line-dk);
  color: var(--muted);
  font-size: 13.5px;
  padding: 52px 24px;
  text-align: center;
}
/* access gate */
.mt-gate, .om-gate, .pc-gate, .sc-gate, .sw-gate {
  color: var(--muted);
  font-size: 14px;
  margin: 60px auto;
  max-width: 520px;
  text-align: center;
}
/* section description */
.mt-desc, .om-desc, .sc-desc, .sw-desc {
  color: var(--muted);
  font-size: var(--fs-md);
  margin-bottom: 16px;
}
/* toolbar row */
.dir-toolbar, .perm-toolbar, .sm-toolbar {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 16px;
}
/* empty state */
.mt-empty, .om-empty, .uu-empty {
  color: var(--muted);
  padding: 16px 9px;
}

/* ---------- #F12: utility classes -------------------------------------------
   A handful of presentational inline style="" values repeated dozens of times
   across pages and JS template literals. These replace the most common ones so
   the value lives in CSS, not scattered through markup. Dynamic inline styles
   (computed widths, data-driven colours) legitimately stay inline. */
.u-muted    { color: var(--muted); }
.u-nowrap   { white-space: nowrap; }
.u-num      { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.u-right    { text-align: right; }
.u-flex1    { flex: 1; }
.u-full     { width: 100%; }
/* the standard "no rows" / "failed to load" cell inside a table body */
.cell-empty { color: var(--muted);      padding: var(--sp-4) 9px; }
.cell-error { color: var(--danger-ink); padding: var(--sp-4) 9px; }

/* ---------- #F18: shared error / empty state ------------------------------
   Error text used to be rendered inline with whatever colour each page picked,
   and often carried a raw parser or HTTP string. humanError() in js/errors.js
   supplies the words; these supply the shape. role="alert" is set in the
   markup so screen readers announce a failure that appears after load. */
.state-error {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
  padding: var(--sp-5) var(--sp-4); text-align: center;
  color: var(--danger-ink); background: var(--danger-fill);
  border: 1px solid var(--danger-line); border-radius: var(--radius-lg);
  font-size: var(--fs-base); line-height: 1.5;
}
.state-error-msg { max-width: 44ch; }
.state-empty {
  padding: var(--sp-8) var(--sp-4); text-align: center;
  color: var(--muted); font-size: var(--fs-base);
}

/* ---------- #F16: SVG icons ------------------------------------------------
   `stroke: currentColor` in js/icons.js means an icon inherits the text colour
   of whatever it sits in, so it flips with the theme automatically — which is
   the property emoji did NOT have (#F3: two landing tiles went invisible in
   dark mode because the glyph inherited --ink but the tile behind it did not
   flip). Sizing here keeps icons optically centred against their label. */
.ico { display: inline-block; vertical-align: -0.15em; flex-shrink: 0; }
.nav-ic { display: inline-flex; align-items: center; justify-content: center; }
.nav-ic .ico { vertical-align: 0; }
.appbar-title [data-icon] { display: inline-flex; }

/* ============================================================
   MOBILE FOUNDATION — shared phone/touch layer
   ------------------------------------------------------------
   One central pass that corrects mobile behaviour across every page that
   loads this sheet, so per-page phases build on it instead of each re-solving
   the same problems. The gating is deliberate and split two ways:

     • LAYOUT that only a small SCREEN needs (gutters, ribbon stacking,
       nav decluttering) gates on `@media (max-width: 640px)` — the phone
       breakpoint documented at the top of this file. A dense desktop UI in a
       narrow window keeps its layout.
     • ERGONOMICS that a TOUCH input needs (44px targets, 16px inputs that
       stop iOS focus-zoom, always-visible hover affordances) gate on input
       MODALITY — `@media (pointer: coarse)` / `@media (hover: none)`. A touch
       tablet at 900px needs these too; a small mouse-driven desktop window
       does not.

   Do NOT add `user-scalable=no` / `maximum-scale` anywhere — pinch-zoom is an
   accessibility requirement and the app is currently clean.
   ============================================================ */

/* ---- (6) dense-table safety net -----------------------------------------
   `.table-scroll` is the default wrapper for any wide table: it scrolls inside
   its own box so no table can push the DOCUMENT sideways on a phone. It is the
   horizontal-scroll half of the hybrid strategy; the other half is the
   `data-th` stacked-card pattern (documented in domains/frontend-core.md) that
   roster/entity tables use instead. Later phases pick one per table. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%; }

/* ---- (5) safe-area: fixed furniture clears notches / the home indicator ----
   env(safe-area-inset-*) resolves to 0 on any device without insets, so these
   rules are no-ops on desktop and only take effect on notched phones. They live
   with their base rules (repo lint forbids re-declaring a selector): see the
   safe-area padding on `.appfooter`, `.toast` and `.megamenu` above. */

/* ---- mega-menu mobile actions block (nav.js appends it) -------------------
   The secondary top-bar controls (theme, support, profile) live in the top bar
   on desktop; at phone width they move here so the bar reads brand·Menu·
   project·search. Hidden on desktop; revealed by the <=640px rule below. These
   are `.mm-action`, not `.navitem`, on purpose: nav.js closes the menu on any
   `.navitem` click (correct for a tool link, wrong for a toggle/popover). */
.mm-actions {
  display: none; flex-direction: column; width: 100%;
  border-top: 1px solid var(--line); margin-top: 4px; padding-top: 8px;
}
.mm-action {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: transparent; border: none; cursor: pointer; text-decoration: none;
  padding: 8px 8px; border-radius: var(--radius); color: var(--ink);
  font: inherit; font-size: var(--fs-base); font-weight: 500; text-align: left;
}
.mm-action:hover { background: var(--surface-3); }
.mm-action .nav-ic { font-size: 16px; line-height: 1; width: 20px; text-align: center; }

/* ---- (2) touch targets + iOS input-zoom guard (modality, not width) ------- */
@media (pointer: coarse) {
  /* 44px is the WCAG 2.5.5 / platform minimum tap target. inline-flex centres
     the label inside the taller box (a bare min-height would top-align it); the
     display override is scoped to visible buttons so it never un-hides one that
     is attribute- or class-hidden. */
  .btn, .btn-sm { min-height: 44px; }
  .btn:not([hidden]):not(.hidden), .btn-sm:not([hidden]):not(.hidden) {
    display: inline-flex; align-items: center; justify-content: center;
  }
  .ribbon select, .data-search,
  .topnav-trigger, .topnav-link, .topnav-search, .topnav-theme,
  .topnav-project, .topnav-refresh { min-height: 44px; }
  .navitem, .mm-action, .support-item { min-height: 44px; }
  /* home: the tool-shelf rows and the two section-head text actions are the
     page's navigation, so they carry the same tap-target floor. */
  .row-link, .sec-quiet, .sec-link { min-height: 44px; }
  .sec-quiet, .sec-link { display: inline-flex; align-items: center; }
  .modal-head .x { padding: 6px 10px; line-height: 1; }
  /* iOS zooms the page when a control with font-size < 16px gains focus. The
     element rule covers page-level inputs; the shared controls below set their
     own font-size with a class (higher specificity) so they need naming.

     The element rule is specificity 0,0,1, so it is a FLOOR and not a winner:
     any page rule that qualifies a control with a class or an attribute
     (`.dr-t input[type="number"]`, `.gt-field select`, `.sg-sort select`) beats
     it and Safari zooms anyway. Such a page therefore owes itself a matching
     `@media (pointer: coarse)` override naming its own selectors -- see
     daily-log.html, guard-tour.html, onsite-metrics.html, permits.html,
     signature-portal.html and swppp.html. Deliberately NOT solved here with
     `!important`: that would also shrink the controls a page sizes ABOVE 16px
     on purpose, and it would hide the per-page defect instead of naming it. */
  input, select, textarea { font-size: 16px; }
  .ribbon select, .data-search, .topnav-project, .cmdk-input, .proj-filter,
  .commit-date { font-size: 16px; }
}

/* ---- (3) hover-only affordances become visible where there is no hover ---- */
@media (hover: none) {
  /* The note pencil is transparent until row hover — on touch there is no
     hover, so reveal it at the same weight hover uses. */
  .note-btn { color: #aab4bc; }
}

/* ---- (1)(4)(6)(7) small-screen layout — phone breakpoint (<=640px) -------- */
@media (max-width: 640px) {
  /* (1) tighter page gutters: 24px 32px wastes a phone's width. */
  .da-page, .dir-page, .fr-page, .gt-page, .ll-page, .mt-page, .om-page,
  .perm-page, .pf-page, .sc-page, .sw-page, .uu-page { padding: 16px 14px 40px; }
  .da-section, .mt-section, .om-section,
  .sc-section, .sw-section, .uu-section { padding: 16px 14px; }

  /* (4) ribbon controls stack full-width instead of a ragged multi-row wrap.
     The max-height is a HARD FLOOR, not styling: on the pivot page this stack
     measured 967px, which is taller than any phone viewport, and because the app
     shell is `height:100dvh; overflow:hidden` the sibling holding the log was
     squeezed to a computed height of ZERO with no page scroll able to reach it.
     Capping the ribbon and letting it scroll inside itself means the worst case
     is a cramped page rather than an empty one. mobile-pivot.js replaces this
     with the command bar + sheet when it runs; this is what is left if it does
     not (script error, an old cached bundle), and it must still be usable. */
  .ribbon > .rb-group, .ribbon > select,
  .ribbon > .data-search, .ribbon > .btn { flex: 1 1 100%; min-width: 0; }
  .ribbon .rb-group { flex-wrap: wrap; }
  .ribbon select, .ribbon .data-search { min-width: 0; width: 100%; }
  .ribbon .rb-sep, .ribbon .rb-spacer { display: none; }
  .ribbon { max-height: 40dvh; overflow-y: auto; -webkit-overflow-scrolling: touch; }

  /* (6) a pivot dropped into the generic scroll wrapper: let it take its
     natural width and scroll horizontally inside the wrapper, rather than
     fight the sticky headers it only needs inside its own .pivot-scroll host.
     Scoped to `.table-scroll >` so the real pivot pages are untouched. */
  .table-scroll > table.pivot { min-width: max-content; }
  .table-scroll > table.pivot thead th,
  .table-scroll > table.pivot thead th.corner,
  .table-scroll > table.pivot td.rowlabel { position: static; }

  /* (7) declutter the phone top bar; the moved controls appear in the menu. */
  .topnav > .topnav-theme,
  .topnav > .support-btn,
  .topnav > .topnav-tasks,
  .topnav > .topnav-profile { display: none; }
  .mm-actions { display: flex; }
}

/* ============================================================
   THE PIVOT ON A PHONE  (index.html — Submittals / RFIs)
   ------------------------------------------------------------
   The MOBILE FOUNDATION above is the shared layer. This is the per-page phase
   it deferred: the pivot pages were explicitly left alone there ("the real
   pivot pages live in .pivot-scroll and are untouched"), and left alone they
   were unusable. The shell is a fixed-height flex column that never scrolls as
   a document, so when the foundation's <=640px rule stacked all eighteen ribbon
   controls to full width the ribbon measured 967px, ate the whole viewport, and
   .layout — the element holding the log — computed to a height of ZERO. Not
   cramped: absent, with no page scroll able to reach it.

   The fix is not to hide controls. It is to stop the ribbon being a stack:

     .mobbar     a ~52px command bar (open view · search · ⋯) that is the only
                 chrome the log competes with.
     #mobSheet   a bottom sheet that the REAL .ribbon is moved into at phone
                 width (mobile-pivot.js relocates the live node, so every
                 control keeps its existing listeners and nothing is rebuilt).
     .filters-bar  becomes a horizontally scrolling chip rail, not a wrap.
     .pivot-cards  the log itself, as cards (pivot-cards.js).

   Nothing is removed and nothing is phone-only functionality: the sheet holds
   the same Save view / Formatting / PDF / CSV / Fields controls a desktop has.
   ============================================================ */

/* The phone command bar and sheet exist in the DOM on every viewport so
   mobile-pivot.js has stable hosts to move controls into; they are inert until
   body.pivot-compact is set. */
.mobbar, .mobsheet, .mobsheet-scrim { display: none; }
/* Phone-only controls are absent everywhere else — not merely faded — so they
   never reach the keyboard order or a screen reader on a desktop. */
.phone-only { display: none; }
body.pivot-compact .mobsheet .phone-only { display: inline-flex; }

/* ---- the card list ------------------------------------------------------- */
/* Deliberately hairline-separated list rows rather than floating cards: a
   submittal log reads as one continuous list, and a deck of shadowed boxes
   would add 582 shadows to a phone's paint for no information. */
.pivot-cards { display: none; }
.pc-empty { padding: 48px 24px; text-align: center; color: var(--muted); font-size: var(--fs-base); }

.pc-card {
  position: relative;
  padding: 11px 14px 12px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  /* Off-screen cards skip layout and paint entirely. A full log is ~580 cards /
     ~25k nodes; without this the browser lays all of it out on every rebuild,
     which is the difference between a scroll that tracks the finger and one
     that does not. The intrinsic size is a measured median card so the
     scrollbar does not jump as cards realise. */
  content-visibility: auto;
  contain-intrinsic-size: auto 168px;
}
.pc-card:last-child { border-bottom: none; }
/* Same rule the grid uses: a flagged row is yellow, ink and all. */
.pc-card.pc-prio { background: var(--priority); color: var(--priority-ink); }
.pc-card.pc-prio .pc-ttl a, .pc-card.pc-prio dd { color: var(--priority-ink); }
.pc-card.pc-prio dt { color: var(--priority-ink); opacity: .8; }

.pc-hd { display: flex; align-items: flex-start; gap: 10px; }
.pc-ttl {
  flex: 1; min-width: 0; margin: 0;
  font-size: var(--fs-base); font-weight: 600; line-height: 1.35; color: var(--ink);
  overflow-wrap: anywhere;
}
.pc-ttl a.procore-link { color: inherit; text-decoration: none; }
.pc-ttl a.procore-link:active { text-decoration: underline; }
.pc-ttl .link-ico { margin-left: 4px; }
/* The ★ is the card's primary write action, so it gets a real target rather
   than the grid's 16px glyph. Pulled into the padding so the box is 44px
   without pushing the title down. */
.pc-card > .pc-hd > .star {
  flex: 0 0 auto; width: 44px; height: 44px; margin: -11px -12px 0 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 19px; line-height: 1;
}

.pc-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.pc-tag {
  font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .03em;
  padding: 2px 7px; border-radius: var(--radius-sm); border: 1px solid;
}
/* Every tag carries its own words, so colour is reinforcement and never the
   only signal — "8d overdue" reads as overdue in monochrome. */
.pc-tag-neutral { color: var(--heading); background: var(--chip); border-color: var(--line-dk); }
.pc-tag-ok      { color: var(--ok-ink);     background: var(--ok-fill);     border-color: var(--ok-line); }
.pc-tag-warn    { color: var(--warn-ink);   background: var(--warn-fill);   border-color: var(--warn-line); }
.pc-tag-danger  { color: var(--danger-ink); background: var(--danger-fill); border-color: var(--danger-line); }

.pc-meta { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 14px; margin: 9px 0 0; }
.pc-meta > div { min-width: 0; }
.pc-meta dt {
  margin: 0; font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.pc-meta dd { margin: 1px 0 0; font-size: var(--fs-md); color: var(--ink); overflow-wrap: anywhere; }
.pc-meta-all { grid-template-columns: minmax(0, 1fr); }

/* The disclosures and the date picker are one region, separated from the card's
   content by a single hairline rather than one rule each. Three stacked bands
   with their own borders is what made the first build's cards 570px tall. */
.pc-more, .pc-notes, .pc-commit { border-top: 1px solid var(--line); }
.pc-card > .pc-more + .pc-commit,
.pc-card > .pc-more + .pc-notes,
.pc-card > .pc-commit + .pc-notes { border-top-color: transparent; }
.pc-more > summary, .pc-notes > summary {
  min-height: 40px; display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-md); color: var(--muted); cursor: pointer;
  list-style: none;
}
.pc-notes > summary { color: var(--heading); }
.pc-notes-empty > summary { color: var(--muted); }
.pc-more > summary::-webkit-details-marker, .pc-notes > summary::-webkit-details-marker { display: none; }
.pc-more > summary::before, .pc-notes > summary::before {
  content: "▸"; color: var(--muted); font-size: var(--fs-xs); transition: transform .15s ease-out;
}
.pc-more[open] > summary::before, .pc-notes[open] > summary::before { transform: rotate(90deg); }
.pc-more[open] > .pc-meta, .pc-notes > .cmt-feed { padding-bottom: 8px; }
.pc-n {
  min-width: 18px; padding: 0 5px; border-radius: var(--radius-pill);
  background: var(--chip); color: var(--heading); font-size: var(--fs-2xs); font-weight: 700; text-align: center;
}

/* The committed return date. It is not in the summary grid above (a deliberate
   call — see FACE_FIELDS in pivot-cards.js) because it is a thing you SET, so
   it gets its own labelled row where the picker itself shows the current value. */
.pc-commit { display: flex; align-items: center; gap: 10px; min-height: 44px; }
.pc-commit-lbl {
  flex: 0 0 auto; font-size: var(--fs-2xs); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.pc-commit .commit-wrap { flex: 0 1 auto; min-width: 0; display: flex; align-items: center; gap: 2px; }
/* The base rule caps every picker at 130px for the dense grid; on a card it is
   a primary action at a touch font size, so it gets room to show a full date. */
.pc-commit .commit-date { flex: 0 1 auto; width: 170px; max-width: 170px; min-width: 0; margin-left: 0; padding: 0 9px; }

/* Group bands. The two outermost levels echo the pivot's own levelStyle fills
   (near-black reviewer over grey subcontractor) so the phone and the grid group
   the log the same way. Sticky so the reviewer whose items you are scrolling
   through stays named. */
.pc-group {
  /* Indent per group level. Declared here rather than relying on a var()
     fallback so the token a stylesheet reads is a token it defines;
     pivot-cards.js overrides it inline per row. */
  --pc-depth: 0;
  position: sticky; z-index: 2;
  display: flex; align-items: center; gap: 8px;
  padding: 7px 14px 7px calc(14px + var(--pc-depth) * 12px);
  font-size: var(--fs-sm); font-weight: 650;
}
.pc-group-0 { top: 0;    background: var(--vcg-dd); color: #fff; }
.pc-group-1 { top: 32px; background: var(--surface-3); color: var(--heading); border-bottom: 1px solid var(--line-dk); }
.pc-group-2 { top: 64px; background: var(--surface-2); color: var(--heading); border-bottom: 1px solid var(--line); }
.pc-group-lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-group-n { flex: 0 0 auto; font-weight: 500; opacity: .75; font-variant-numeric: tabular-nums; }
.pc-group > .toggle {
  flex: 0 0 auto; width: 44px; height: 44px; margin: -12px -6px -12px -14px;
  display: inline-flex; align-items: center; justify-content: center; color: inherit;
}
.pc-group-0 > .toggle { color: #cdd9e0; }

/* ---- the compact shell -------------------------------------------------
   Body-class gated, not width-gated: mobile-pivot.js owns the two thresholds
   (see breakpoints.js) and sets .pivot-compact / .pivot-cards-on, so the query
   is written once, in JS, and the stylesheet cannot drift from it. The
   MOBILE FOUNDATION rule at <=640px is the CSS-only floor for when this
   script never runs. */
body.pivot-compact .mobbar {
  display: flex; align-items: center; gap: 7px; flex: 0 0 auto;
  padding: 7px 10px; background: var(--surface); border-bottom: 1px solid var(--line);
  padding-left: max(10px, env(safe-area-inset-left));
  padding-right: max(10px, env(safe-area-inset-right));
}
/* The open view is the single most useful thing to see and change here, so it
   is a labelled button rather than a select: a native select at 44px with a
   16px font is most of the bar, and its label would have nowhere to live. */
.mob-view {
  flex: 1; min-width: 0; min-height: 44px;
  display: flex; align-items: center; gap: 6px;
  padding: 0 10px; border: 1px solid var(--line-dk); border-radius: var(--radius);
  background: var(--surface-2); color: var(--heading); font: inherit; font-size: var(--fs-base);
  text-align: left; cursor: pointer;
}
.mob-view .mv-cap { flex: 0 0 auto; color: var(--muted); font-size: var(--fs-md); }
.mob-view .mv-name { flex: 1; min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mob-view .mv-car { flex: 0 0 auto; color: var(--muted); }

.mob-ib {
  position: relative; flex: 0 0 auto; width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-dk); border-radius: var(--radius);
  background: var(--surface-2); color: var(--heading); font: inherit; font-size: var(--fs-lg); cursor: pointer;
}
.mob-ib .mob-badge {
  position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; padding: 0 3px;
  border-radius: var(--radius-pill); background: var(--accent-solid); color: #fff;
  font-size: var(--fs-2xs); font-weight: 700; display: grid; place-items: center;
}
.mob-ib .mob-badge:empty, .mob-ib .mob-badge[hidden] { display: none; }

/* The search field is the live #dataSearch, moved here by mobile-pivot.js —
   not a copy. It shares the bar with the view button, so it takes the row
   over when it is open. */
body.pivot-compact.mob-searching .mob-view { display: none; }
body.pivot-compact .mobbar .data-search { flex: 1; min-width: 0; min-height: 44px; width: auto; }
body.pivot-compact:not(.mob-searching) .mobbar .data-search { display: none; }

/* ---- the sheet: the real ribbon, in a drawer ---------------------------- */
body.pivot-compact.sheet-open .mobsheet-scrim {
  display: block; position: fixed; inset: 0; z-index: var(--z-modal);
  background: rgba(20, 28, 36, .5);
}
body.pivot-compact.sheet-open .mobsheet {
  display: flex; flex-direction: column;
  position: fixed; left: 0; right: 0; bottom: 0; z-index: calc(var(--z-modal) + 1);
  max-height: 86dvh;
  background: var(--elevated);
  border-top: 1px solid var(--line-dk);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-lg);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.mobsheet-hd {
  flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; border-bottom: 1px solid var(--line);
}
.mobsheet-hd h2 { flex: 1; margin: 0; font-size: var(--fs-lg); font-weight: 600; color: var(--heading); }
.mobsheet-x {
  flex: 0 0 auto; width: 44px; height: 44px; margin: -8px -10px -8px 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: none; color: var(--muted); font-size: var(--fs-2xl); cursor: pointer;
}
.mobsheet-body { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }

/* Inside the sheet the ribbon finally gets to be a stack — which is what it
   wanted to be all along, just not in 967px of the page's only viewport. */
body.pivot-compact .mobsheet .ribbon {
  display: flex; flex-direction: column; align-items: stretch; gap: 10px;
  padding: 14px; border-bottom: none; box-shadow: none; background: transparent;
}
body.pivot-compact .mobsheet .ribbon > .rb-group,
body.pivot-compact .mobsheet .ribbon > .rb-actions { width: 100%; }
body.pivot-compact .mobsheet .ribbon .rb-sep,
body.pivot-compact .mobsheet .ribbon .rb-spacer { display: none; }
body.pivot-compact .mobsheet .ribbon select { width: 100%; min-width: 0; }
body.pivot-compact .mobsheet .ribbon .rb-group.toggles,
body.pivot-compact .mobsheet .ribbon .rb-group.priority-group { flex-wrap: wrap; row-gap: 10px; }
body.pivot-compact .mobsheet .ribbon .rb-group.toggles label,
body.pivot-compact .mobsheet .ribbon .rb-group.priority-group label { min-height: 44px; }
body.pivot-compact .mobsheet .rb-actions {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
  border-top: 1px solid var(--line); padding-top: 12px;
}
body.pivot-compact .mobsheet .rb-actions .btn { width: 100%; }
body.pivot-compact .mobsheet .btn-tx-wide { display: inline; }
/* The two captioned groups (View, Layout): caption on its own line, then the
   select taking the row with its ⚙ beside it rather than orphaned beneath a
   full-width control. Scoped away from .toggles / .priority-group, whose
   <label>s wrap CHECKBOXES rather than captioning a field — giving those
   `flex: 1 0 100%` put every checkbox on its own line and made the sheet twice
   as long as it needed to be. */
body.pivot-compact .mobsheet .ribbon > .rb-group { flex-wrap: wrap; row-gap: 6px; }
body.pivot-compact .mobsheet .ribbon > .rb-group:not(.toggles):not(.priority-group) > label { flex: 1 0 100%; }
body.pivot-compact .mobsheet .ribbon > .rb-group > select { flex: 1; width: auto; }
body.pivot-compact .mobsheet .ribbon > .rb-group > .btn { flex: 0 0 auto; width: auto; }
/* Two checkboxes to a row: one per row wastes the sheet, three is too tight
   for a 44px label to hold its words. */
body.pivot-compact .mobsheet .ribbon > .rb-group.toggles > label,
body.pivot-compact .mobsheet .ribbon > .rb-group.priority-group > label {
  flex: 1 1 calc(50% - 6px); min-width: 140px;
}
body.pivot-compact .mobsheet .ribbon > .rb-group.priority-group > label:has(select) { flex: 1 1 100%; }
body.pivot-compact .mobsheet .ribbon > .rb-group.priority-group > label > select { flex: 1; min-width: 0; }
/* The ★ count trails its own checkbox rather than taking a line of its own. */
body.pivot-compact .mobsheet .prio-count { flex: 0 0 auto; align-self: center; }
/* Three bands, so the sheet reads as view / filters / actions rather than one
   long column. .rb-actions already carries the second rule. */
body.pivot-compact .mobsheet .ribbon > .rb-group.toggles {
  border-top: 1px solid var(--line); padding-top: 12px;
}

/* ---- the log ----------------------------------------------------------- */
/* The ribbon is gone from the flow, so .layout gets the height it never had.
   .pivot-inner's `min-width: max-content` is what let the grid push the pane
   sideways; the card list has no such need and simply fills the width. */
body.pivot-compact .ribbon { display: none; }
body.pivot-compact .fields-pane { display: none; }
body.pivot-compact .pivot-inner { padding: 0; }
body.pivot-cards-on .pivot-inner { display: none; }
body.pivot-cards-on .pivot-cards { display: block; }
/* The escape hatch: the real grid, scrolling inside its own pane with the
   row-label column pinned. This is the .table-scroll treatment the mobile
   foundation already blesses, applied in place. */
body.pivot-compact:not(.pivot-cards-on) .pivot-inner { padding: 0 0 24px; }
body.pivot-compact:not(.pivot-cards-on) table.pivot { font-size: var(--fs-xs); }
body.pivot-compact:not(.pivot-cards-on) table.pivot th,
body.pivot-compact:not(.pivot-cards-on) table.pivot td { padding: 6px 7px; }

/* Filter slicers: a chip rail you swipe, not a block that wraps to 114px. */
body.pivot-compact .filters-bar {
  flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
  padding: 7px 10px; gap: 7px;
}
body.pivot-compact .filters-bar::-webkit-scrollbar { display: none; }
body.pivot-compact .filters-bar .slicer { flex: 0 0 auto; }
/* The popover is anchored to a chip inside a horizontal scroller, so it is
   positioned against the viewport instead — otherwise it scrolls away from
   its own trigger, or is clipped by the rail. */
body.pivot-compact .slicer-pop {
  position: fixed; left: 10px; right: 10px; top: auto; width: auto;
  max-height: min(60dvh, 420px);
}

/* Print always wants the grid, whatever the screen was showing. The ribbon, the
   filter bar and the fields pane are already hidden by the main print block
   above; the only new job here is un-hiding .pivot-inner, which card mode
   hides on screen, and dropping the phone shell's own chrome. */
@media print {
  .pivot-cards, .mobbar, .mobsheet, .mobsheet-scrim { display: none !important; }
  body.pivot-compact .pivot-inner { display: block; }
}

/* ---- touch ergonomics INSIDE the pivot (modality, not width) --------------
   The MOBILE FOUNDATION's `pointer: coarse` block raises .btn, nav items and
   ribbon selects, but it never reached inside the table — which is where 810 of
   the page's sub-44px targets live. These are the pivot's own row controls, so
   a touch tablet at 900px gets them too, and a narrow mouse-driven desktop
   window keeps the dense grid. Negative margins keep the bigger hit box from
   changing the row's height. */
@media (pointer: coarse) {
  table.pivot .toggle, table.pivot .star, table.pivot .leaf-spacer {
    width: 34px; height: 34px; margin: -8px -4px; display: inline-flex;
    align-items: center; justify-content: center; vertical-align: middle;
  }
  /* These three ride on a ROW, so they are scoped to the scroll pane rather than
     to the table: on a phone the same controls are inside cards, and a rule
     that only named table.pivot left a 16x16 clip and a 13x14 ⓘ on every card.
     34px clears WCAG 2.5.8's 24px floor with room; the card's primary actions
     (★, the date picker, the note input) get the full 44 separately. */
  .pivot-scroll .link-ico { display: inline-flex; align-items: center; justify-content: center;
    min-width: 34px; min-height: 34px; margin: -8px -4px; vertical-align: middle; }
  .pivot-scroll .commit-date { min-height: 34px; }
  /* The submittal title is a link in a 13px-tall table cell. Padding it out to a
     29px box with a matching negative margin gives a thumb something to hit
     without adding a pixel to the row's height. */
  table.pivot td.rowlabel a.procore-link {
    display: inline-block; padding: 8px 0; margin: -8px 0;
  }
  /* On a card the date picker is a primary action, not a cell control. */
  .pc-commit .commit-date { min-height: 44px; }
  /* Hover-revealed on a desktop; on touch there is no hover, so it is always
     legible and always big enough to hit. */
  .pivot-scroll .commit-hist { color: var(--muted); min-width: 34px; min-height: 34px;
    display: inline-flex; align-items: center; justify-content: center; }
  /* The elevated-only × on a comment was an 11x12 target inside a scrolling feed. */
  .cmt-del { min-width: 32px; min-height: 32px; display: inline-flex;
    align-items: center; justify-content: center; }
  .slicer-btn { min-height: 44px; }
  .slicer-pop label { min-height: 44px; padding: 4px 11px; }
  .slicer-pop .opt-only { opacity: .85; }
  .slicer-pop .foot button { min-height: 44px; }
  /* A 13px checkbox is not a target. The label around it already reaches 44px
     in the sheet; this makes the box itself hittable everywhere. */
  .ribbon input[type="checkbox"] { width: 20px; height: 20px; }
  .pc-more > summary, .pc-notes > summary { min-height: 44px; }
  .cmt-add { min-height: 34px; display: block; }
  /* The field pills’ three controls. Drag is unavailable to a screen reader
     and awkward under a thumb, so on touch these ARE the interaction: 32px,
     with the negative margin trimmed so the pill grows by 4px, not by 8. */
  .pill .agg, .pill .cfg, .pill .rm { min-width: 32px; min-height: 32px; margin-top: -6px; margin-bottom: -6px; }
}

/* ---- narrow but not a phone: tablets and small windows -------------------- */
/* The ribbon is 205px here — a fifth of an iPad's height spent on controls that
   wrap into three ragged rows. Tightening it is not a phone concern, so it
   gates on width alone and stops short of the sheet. */
@media (min-width: 641px) and (max-width: 900px) {
  .ribbon { gap: 8px 10px; padding: 6px 12px; }
  .ribbon select { min-width: 118px; }
  .ribbon .rb-group.toggles { gap: 10px; }
  .rb-actions { gap: 8px; }
  /* Icon-only where the icon already says it, so the action row fits one line.
     The accessible name lives on the button's aria-label, not the glyph. */
  .rb-actions .btn .btn-tx { display: none; }
}

/* ---------- Lavish markup tool (lavish-markup.js, `lavish-markup` grant) ----
   A fixed toggle below the top bar switches the page into markup mode: hover
   ring, click-to-note popover, and a bottom-right queue drawer that files the
   batch as one feature request. Everything carries [data-lavish] so the tool's
   own chrome is never captured. All at --z-lavish: the tool annotates ANY page
   surface, so it must clear modals, drawers and sticky headers alike. */
body.lv-active { cursor: crosshair; user-select: none; }

/* #401 aligns this beside the top bar's Search control and #398 lets it be
   dragged; both are driven from JS (placeButton / moveTo), so `left`/`top` are
   set inline and `right` is only the no-top-bar fallback. It stays `fixed` at
   --z-lavish rather than becoming a child of the bar on purpose — see the note
   in lavish-markup.js: parented into .topnav it would sit behind the first modal
   backdrop, on exactly the surfaces someone wants to mark up. */
.lv-toggle {
  position: fixed; right: 12px; z-index: var(--z-lavish); touch-action: none;
  display: inline-flex; align-items: center; gap: 6px;
  font: inherit; font-size: 12.5px; font-weight: 600;
  padding: 6px 13px; border-radius: var(--radius-pill); cursor: pointer;
  border: 1px solid var(--accent-solid);
  background: var(--surface); color: var(--accent);
  box-shadow: 0 2px 8px rgba(0,0,0,.14);
}
.lv-toggle:hover { background: var(--surface-2); }
.lv-toggle.on { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
.lv-toggle.on:hover { background: var(--accent-solid-hover); }
.lv-count {
  display: inline-grid; place-items: center; min-width: 18px; height: 18px;
  padding: 0 4px; border-radius: var(--radius-pill); font-size: var(--fs-xs);
  background: var(--accent-solid); color: #fff;
}
.lv-toggle.on .lv-count { background: rgba(255,255,255,.25); }

.lv-banner {
  position: fixed; top: 10px; left: 50%; transform: translateX(-50%);
  z-index: var(--z-lavish); pointer-events: none;
  padding: 5px 16px; border-radius: var(--radius-pill); font-size: var(--fs-md); font-weight: 600;
  background: var(--vcg-d); color: #fff; box-shadow: 0 2px 10px rgba(0,0,0,.22);
}

.lv-ring {
  position: fixed; z-index: calc(var(--z-lavish) - 1); pointer-events: none;
  border-radius: 3px; outline: 2px solid var(--accent);
  background: light-dark(rgba(172,96,27,.07), rgba(217,138,61,.12));
}

.lv-pop {
  position: fixed; z-index: calc(var(--z-lavish) + 2); width: 320px;
  border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--elevated);
  box-shadow: 0 10px 34px rgba(0,0,0,.24); padding: 12px; cursor: auto; user-select: text;
}
.lv-pop-what { margin: 0; font-size: var(--fs-md); font-weight: 600; color: var(--ink); }
.lv-pop-what code { background: var(--tint); border-radius: var(--radius-sm); padding: 1px 5px; font-size: var(--fs-xs); }
.lv-pop-tx { color: var(--muted); font-weight: 400; }
.lv-note-input {
  margin-top: 8px; width: 100%; min-height: 72px; resize: vertical; box-sizing: border-box;
  border: 1px solid var(--line); border-radius: 8px; padding: 8px;
  font: inherit; font-size: var(--fs-base); background: var(--surface); color: var(--ink);
}
.lv-note-input:focus { outline: none; border-color: var(--accent); }
.lv-pop-actions { margin-top: 8px; display: flex; justify-content: flex-end; gap: 8px; }

.lv-drawer {
  position: fixed; right: 12px; bottom: 12px; z-index: calc(var(--z-lavish) + 1);
  width: 320px; border: 1px solid var(--line); border-radius: var(--radius-lg);
  background: var(--elevated); box-shadow: 0 10px 34px rgba(0,0,0,.24);
  cursor: auto; user-select: text; font-size: 12.5px;
}
.lv-drawer-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 9px 12px; border-bottom: 1px solid var(--line);
}
.lv-drawer-head p { margin: 0; font-weight: 600; color: var(--ink); }
.lv-muted { color: var(--muted); font-weight: 400; }
.lv-clear { border: none; background: none; font: inherit; font-size: var(--fs-sm); color: var(--muted); cursor: pointer; }
.lv-clear:hover { color: var(--danger-ink); }
.lv-done { margin: 0; padding: 10px 12px; color: var(--ok-ink, #2e7d32); }
.lv-list { list-style: none; margin: 0; padding: 0; max-height: 42vh; overflow-y: auto; }
.lv-list li { display: flex; gap: 8px; padding: 9px 12px; border-bottom: 1px solid var(--line); }
.lv-num {
  flex-shrink: 0; display: grid; place-items: center; width: 20px; height: 20px; margin-top: 1px;
  border-radius: var(--radius-pill); background: var(--tint); color: var(--muted); font-size: var(--fs-xs); font-weight: 600;
}
.lv-row-body { min-width: 0; flex: 1; }
.lv-row-el { margin: 0; font-weight: 500; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lv-row-el code { font-size: var(--fs-xs); color: var(--muted); }
.lv-row-change { margin: 2px 0 0; color: var(--muted); }
.lv-remove { flex-shrink: 0; align-self: flex-start; }
.lv-submit { padding: 10px 12px; border-top: 1px solid var(--line); display: grid; gap: 8px; }
.lv-submit label { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); display: grid; gap: 4px; }
.lv-submit input[type="text"] {
  width: 100%; box-sizing: border-box; border: 1px solid var(--line); border-radius: 8px;
  padding: 7px 8px; font: inherit; font-size: var(--fs-base); background: var(--surface); color: var(--ink);
}
.lv-submit input[type="text"]:focus { outline: none; border-color: var(--accent); }
.lv-prio { display: flex; flex-wrap: wrap; gap: 6px; }
.lv-prio-opt {
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 3px 10px;
  font-size: var(--fs-md); font-weight: 500; color: var(--muted);
}
.lv-prio-opt:has(input:checked) { border-color: var(--accent); color: var(--ink); background: var(--tint); }
.lv-msg { margin: 0; padding: 7px 9px; border-radius: 8px; font-size: var(--fs-md);
  background: light-dark(#fdecea, #4a2320); color: light-dark(#b3261e, #f2b8b5); }
.lv-wide { width: 100%; justify-content: center; }

/* ---- Demo deployment branding (nav.js applyDemoBranding) ------------------ */
.topnav-brand .brand-word { font-weight: 700; font-size: var(--fs-lg); letter-spacing: .02em;
  color: var(--ink, inherit); }
.demo-badge { margin-left: 7px; padding: 2px 6px; border-radius: 5px; font-size: var(--fs-2xs);
  font-weight: 700; letter-spacing: .08em; background: light-dark(#fef3c7, #4a3a12);
  color: light-dark(#92400e, #fbbf24); border: 1px solid light-dark(#fcd34d, #78560f); }
body.demo-mode .ft-logo { display: none; }
/* Any Vorea-branded image asset (wordmarks, letterheads) disappears in demo,
   and so do the Help page's baked screenshots — those show real project data. */
body.demo-mode img[src*="vorea" i] { display: none; }
body.demo-mode img[src*="/assets/help/"] { display: none; }

/* --- View As banner (developer-admin preview, frontend/js/view-as.js) --------
   Sticky rather than fixed: it must stay visible while scrolling a long page,
   but it must not overlay content — a preview is a mode you read, not a toast
   you dismiss, and the pages it rides include tall tables whose first row would
   otherwise sit under it. Sticky at the top of <body> keeps it above the nav in
   both source and paint order without any page needing to know it exists.

   The warn palette, not the accent: this is a "you are not seeing what you
   normally see" state, and it has to lose a scanning contest with nothing. */
.view-as-bar {
  position: sticky;
  top: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  background: var(--warn-fill);
  color: var(--warn-ink);
  border-bottom: 1px solid var(--warn-line);
  font-size: var(--fs-base);
  line-height: 1.35;
}
.view-as-eye { font-size: var(--fs-lg); flex: 0 0 auto; }
.view-as-text { flex: 1 1 auto; min-width: 0; }
.view-as-note { color: var(--warn-ink); opacity: .85; }
.view-as-caveat { margin-top: 2px; opacity: .85; }
.view-as-exit {
  flex: 0 0 auto;
  padding: 5px 12px;
  border: 1px solid var(--warn-line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}
.view-as-exit:hover { background: var(--surface-3); }

/* Narrow screens tighten the bar and let it wrap; nothing is dropped. The note
   is not decoration that a phone can do without — "Intranet tool permissions
   only, Procore data is still yours" is the limit most likely to be forgotten
   precisely while being relied on, and "what does a sub see on their phone" is
   a reason to run a preview at this width, not a reason to stop saying it.
   Subject, limit and the way out all survive here. */
@media (max-width: 640px) {
  .view-as-bar { align-items: flex-start; padding: 7px 10px; font-size: var(--fs-md); }
}

/* ============================================================
   The on-page CHECK PANE for a shipped feature request
   (frontend/js/fr-check.js, backend/fr_check.py).
   "Confirm it works" opens the page the change lives on; this pane rides its
   right edge with what was asked, the marked screenshot, what shipped, what
   to check, and the verdict. --z-docked-panel: above sticky toolbars (100),
   below the mega-menu (120) and modals (130) — the nav and any dialog the
   page opens stay usable over it. ≤820px it is a bottom sheet at 45dvh.
   ============================================================ */
.frc-pane {
  position: fixed; top: 0; right: 0; bottom: 0; width: 340px; max-width: 100vw;
  z-index: var(--z-docked-panel);
  display: flex; flex-direction: column;
  background: var(--elevated); color: var(--ink); border-left: 1px solid var(--line-dk);
  box-shadow: var(--shadow-lg); font-size: 13px; line-height: 1.45;
  animation: vu-fadein .18s ease-out;
}
.frc-head {
  display: flex; align-items: center; gap: 8px; padding: 10px 12px;
  border-bottom: 1px solid var(--line); flex: 0 0 auto;
}
.frc-title { font-weight: 650; color: var(--heading); white-space: nowrap; }
.frc-sp { flex: 1; }
.frc-ic {
  border: none; background: none; color: var(--muted); font: inherit; font-size: 16px;
  width: 24px; height: 24px; line-height: 1; cursor: pointer; border-radius: var(--radius); padding: 0;
}
.frc-ic:hover { background: var(--tint); color: var(--ink); }
.frc-badge {
  font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; white-space: nowrap;
  background: light-dark(#ccfbf1, #0d3b38); color: light-dark(#115e59, #7fd8cf);
}
.frc-badge-off { background: var(--chip); color: var(--muted); }
.frc-body { flex: 1 1 auto; overflow: auto; padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 12px; }
.frc-sec { min-width: 0; }
.frc-lab { font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 650; margin-bottom: 4px; }
.frc-summ { font-weight: 600; color: var(--heading); }
.frc-quote { margin: 4px 0; padding: 2px 10px; border-left: 3px solid var(--line-dk); }
.frc-details p, .frc-shipped p, .frc-check p { margin: 4px 0; }
.frc-details ul, .frc-shipped ul, .frc-check ul { margin: 4px 0; padding-left: 16px; }
.frc-muted { color: var(--muted); }
.frc-note { background: var(--warn-fill); color: var(--warn-ink); border: 1px solid var(--warn-line); border-radius: var(--radius); padding: 7px 10px; }
.frc-shots { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.frc-shot {
  border: 1px solid var(--line-dk); border-radius: 6px; padding: 0; background: var(--surface-2);
  cursor: zoom-in; overflow: hidden; width: 100%; max-height: 180px; display: block;
}
.frc-shot[hidden] { display: none; }
.frc-shot img { display: block; width: 100%; height: auto; }
.frc-steps { display: grid; gap: 6px; }
.frc-step { display: grid; grid-template-columns: 20px minmax(0, 1fr) auto; gap: 6px; align-items: center; min-width: 0; }
.frc-step a { color: var(--link); }
.frc-n {
  width: 20px; height: 20px; border-radius: 999px; background: var(--chip); color: var(--heading);
  font-size: 11px; font-weight: 650; display: grid; place-items: center;
}
.frc-step-here .frc-n { background: var(--accent-solid); color: #fff; }
.frc-sels { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
.frc-sels code { font-size: 11px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 4px; padding: 0 4px; }
.frc-link { border: none; background: none; color: var(--link); font: inherit; font-size: 12px; cursor: pointer; padding: 0; }
.frc-link:disabled { color: var(--muted); cursor: default; }
.frc-shipped { background: var(--surface-2); border: 1px solid var(--line); border-radius: 6px; padding: 8px 10px; }
.frc-who { font-size: 11.5px; color: var(--muted); margin-bottom: 4px; }
.frc-who a { color: var(--link); }
.frc-check { background: var(--accent-fill); border-left: 3px solid var(--accent); border-radius: 6px; padding: 8px 10px; }
.frc-check .frc-lab { color: var(--accent-ink); }
.frc-ta {
  width: 100%; border: 1px solid var(--line-dk); border-radius: 6px; background: var(--surface-2);
  color: var(--ink); padding: 7px 9px; font: inherit; font-size: 12.5px; resize: vertical; min-height: 54px;
}
.frc-ta:focus { outline: 2px solid var(--focus); }
.frc-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.frc-ok { background: var(--ok-fill); color: var(--ok-ink); border-color: var(--ok-line); font-weight: 600; }
.frc-ok:hover { background: var(--ok-line); }
.frc-status { margin-top: 6px; font-size: 12.5px; min-height: 1.2em; color: var(--muted); }
.frc-status[data-tone="ok"] { color: var(--ok-ink); }
.frc-status[data-tone="warn"] { color: var(--warn-ink); }
.frc-foot { border-top: 1px solid var(--line); padding: 8px 12px; font-size: 12px; color: var(--muted); flex: 0 0 auto; }
.frc-foot a { color: var(--link); }

/* Collapsed: the header alone, as a tab on the right edge; the page gets its
   width back. The whole tab re-expands on click (fr-check.js). */
.frc-pane.frc-collapsed {
  top: auto; bottom: 96px; width: auto; max-width: 70vw; height: auto;
  border-radius: var(--radius-lg) 0 0 var(--radius-lg); border-left: 1px solid var(--line-dk); cursor: pointer;
}
.frc-pane.frc-collapsed .frc-head { border-bottom: 0; }
.frc-pane.frc-collapsed .frc-body, .frc-pane.frc-collapsed .frc-foot { display: none; }

/* The ring on the marked element — a class on the element itself, so it
   scrolls with it (the Lavish ring is a fixed overlay because it tracks the
   cursor; this one marks a spot and stays put). */
.frc-ring {
  outline: 3px solid var(--accent) !important; outline-offset: 3px;
  box-shadow: 0 0 0 8px light-dark(rgba(172,96,27,.14), rgba(217,138,61,.18)) !important;
  transition: box-shadow .3s;
}

.frc-shotview-body { padding: 0; overflow: auto; }
.frc-shotview-body img { display: block; max-width: 100%; height: auto; }

@media (max-width: 820px) {
  .frc-pane {
    top: auto; left: 0; right: 0; bottom: 0; width: auto; height: 45dvh;
    border-left: 0; border-top: 1px solid var(--line-dk); border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }
  .frc-pane.frc-collapsed { left: auto; bottom: 0; height: auto; border-radius: var(--radius-lg) 0 0 0; }
}
@media print { .frc-pane { display: none; } }
