/* ============================================================
   Arbah — appearance layer: DARK repairs + the BLACK theme
   ============================================================
   Loaded LAST so it can correct token sets declared earlier without any
   `!important`. Two jobs, kept strictly separate:

   1. DARK REPAIRS. `src/design/tokens.css` declares the app's original
      token set on a bare `:root` with LIGHT values and no dark
      counterpart. The shared modal chrome (.modal-panel / -header /
      -footer / .modal-title), the form primitives (.input / .select /
      .textarea / .btn-*) and every dropdown built on them read those
      tokens — so they stayed white while `.av3` switched its own text to
      near-white via --arbah-ink. That is the "typed text invisible,
      labels half-visible, Cancel greyed out" family of bugs: not a set of
      unrelated popup faults, one missing dark token set feeding all of
      them. Giving `:root` a dark counterpart fixes them together.

   2. BLACK. A second dark appearance, layered ON TOP of dark rather than
      beside it: appearance.js resolves black to
      data-color-scheme="dark", so everything Dark already covers keeps
      working and Black only re-points surfaces to true black plus the
      brand accents. Dark itself is not touched by anything in this file's
      black section. No orange: --accent / --warning resolve to Arbah gold.

   Palettes mirror the v3 dark ramp (arbah-v3-tokens.css) so the legacy
   and v3 token sets cannot drift apart on the same screen.
   ============================================================ */

/* ============================================================
   1. DARK — legacy token set (drives every shared modal + control)
   ============================================================ */
html[data-color-scheme="dark"] {
  --ink:            #F1F5F9;
  --ink-muted:      #CBD5E1;
  --ink-subtle:     #94A3B8;

  --surface:        #111C2A;
  --surface-sunken: #0B1420;
  --surface-raised: #1A293A;

  --border:         #2B3D50;
  --border-strong:  #3C5269;

  /* Overlays go neutral in the dark themes — a navy wash over a dark page
     reads as a second, wrong theme (#26). */
  --scrim-rgb: 0 0 0;

  /* A dark chip on a dark page is not a tooltip, it is a smudge. */
  --tooltip-bg:     #1A293A;
  --tooltip-ink:    #F1F5F9;
  --tooltip-border: #3C5269;

  --brand:          #2C4A6E;
  --brand-hover:    #37597F;
  --brand-soft:     #1C2E45;

  --accent:         #F59E0B;
  --accent-hover:   #FBBF24;
  --accent-soft:    #46330F;

  --success:        #34D399;  --success-soft: #123D35;
  --warning:        #FBBF24;  --warning-soft: #473717;
  --danger:         #F87171;  --danger-soft:  #4A2025;
  --info:           #93B4DC;  --info-soft:    #1C315A;

  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.45);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.50);
}

/* The scrim has to darken too, or a dark panel sits on a pale wash. */
html[data-color-scheme="dark"] .modal-backdrop { background: rgba(0, 0, 0, 0.66); }

/* Form controls: these are the fields whose typed text went invisible.
   `color-scheme` also repaints the native caret, selection and the
   date/time picker indicators, which a background/color pair cannot. */
html[data-color-scheme="dark"] input,
html[data-color-scheme="dark"] select,
html[data-color-scheme="dark"] textarea {
  color-scheme: dark;
}

/* ============================================================
   1a. DARK — Tailwind neutral utilities
   ============================================================
   The bulk of the app's modals (Safety Logs among them) are built from
   Tailwind utility classes in markup rather than from tokens: `bg-white`,
   `text-gray-700`, `border-gray-300`. Those are literal colours compiled
   into tailwind.css — no token ever reaches them, so they stayed light
   while everything around them went dark. That is the whole "Log toolbox
   talk" failure in one line: the labels are `text-gray-700` (dark grey on
   a dark panel → half-invisible), the fields carry no background utility
   at all (so the UA paints them white while `.av3` supplies near-white
   inherited text → typed text invisible), and Cancel is
   `border-gray-300 text-gray-700` → the "greyed out" look.

   Re-pointing the neutral ramp at the theme tokens fixes every popup
   built this way at once, and — because the tokens themselves differ per
   appearance — Black inherits these same rules with no second copy.
   Coloured utilities (bg-blue-600, text-red-600 …) are deliberately left
   alone: they carry meaning and already read on a dark ground.
   ============================================================ */

/* Text: the grey ramp inverts — the dark greys were "strong text". */
html[data-color-scheme="dark"] .text-gray-900,
html[data-color-scheme="dark"] .text-gray-800,
html[data-color-scheme="dark"] .text-gray-700,
html[data-color-scheme="dark"] .text-slate-900,
html[data-color-scheme="dark"] .text-slate-700 { color: var(--ink); }

html[data-color-scheme="dark"] .text-gray-600,
html[data-color-scheme="dark"] .text-gray-500,
html[data-color-scheme="dark"] .text-slate-600,
html[data-color-scheme="dark"] .text-slate-500 { color: var(--ink-muted); }

html[data-color-scheme="dark"] .text-gray-400,
html[data-color-scheme="dark"] .text-gray-300,
html[data-color-scheme="dark"] .text-gray-200 { color: var(--ink-subtle); }

html[data-color-scheme="dark"] .hover\:text-gray-800:hover,
html[data-color-scheme="dark"] .hover\:text-gray-700:hover,
html[data-color-scheme="dark"] .hover\:text-gray-600:hover { color: var(--ink); }

html[data-color-scheme="dark"] .disabled\:text-gray-700:disabled,
html[data-color-scheme="dark"] .disabled\:text-gray-400:disabled,
html[data-color-scheme="dark"] .disabled\:text-gray-300:disabled { color: var(--ink-subtle); }

/* Surfaces. */
html[data-color-scheme="dark"] .bg-white,
html[data-color-scheme="dark"] .focus\:bg-white:focus { background-color: var(--surface); }

html[data-color-scheme="dark"] .bg-gray-50,
html[data-color-scheme="dark"] .bg-gray-100,
html[data-color-scheme="dark"] .bg-slate-50,
html[data-color-scheme="dark"] .bg-slate-100 { background-color: var(--surface-raised); }

html[data-color-scheme="dark"] .bg-gray-200,
html[data-color-scheme="dark"] .bg-gray-300,
html[data-color-scheme="dark"] .bg-gray-400 { background-color: var(--border); }

html[data-color-scheme="dark"] .hover\:bg-gray-50:hover,
html[data-color-scheme="dark"] .hover\:bg-gray-100:hover,
html[data-color-scheme="dark"] .hover\:bg-gray-200:hover,
html[data-color-scheme="dark"] .hover\:bg-gray-300:hover,
html[data-color-scheme="dark"] .hover\:bg-white:hover { background-color: var(--surface-raised); }

html[data-color-scheme="dark"] .disabled\:bg-white:disabled,
html[data-color-scheme="dark"] .disabled\:bg-gray-50:disabled,
html[data-color-scheme="dark"] .disabled\:bg-gray-100:disabled { background-color: var(--surface-sunken); }

/* Borders + dividers. */
html[data-color-scheme="dark"] .border-gray-50,
html[data-color-scheme="dark"] .border-gray-100,
html[data-color-scheme="dark"] .border-gray-200,
html[data-color-scheme="dark"] .border-gray-300,
html[data-color-scheme="dark"] .border-slate-100,
html[data-color-scheme="dark"] .hover\:border-gray-300:hover { border-color: var(--border); }

html[data-color-scheme="dark"] .border-gray-800,
html[data-color-scheme="dark"] .border-gray-900,
html[data-color-scheme="dark"] .border-slate-800 { border-color: var(--border-strong); }

html[data-color-scheme="dark"] .divide-gray-50 > * + *,
html[data-color-scheme="dark"] .divide-gray-100 > * + *,
html[data-color-scheme="dark"] .divide-gray-200 > * + * { border-color: var(--border); }

/* Fields that carry only a border utility: give them a real ground so
   inherited near-white text is never painted onto white.

   Wrapped in :where() ON PURPOSE — that keeps the whole rule at zero
   specificity, so it outranks only the user-agent default and loses to
   every authored rule. Without it, `html[…] input` (0,1,2) would outrank
   `.dsrw-input` (0,1,0) and this generic ground would flatten the
   wizard's own field palette — and any other component that styles its
   inputs properly. This is a floor, not an override. */
:where(html[data-color-scheme="dark"]) :where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select, textarea) {
  background-color: var(--surface-raised);
  color: var(--ink);
}
:where(html[data-color-scheme="dark"]) :where(input, textarea)::placeholder { color: var(--ink-subtle); }
/* A <select>'s drop-down list is painted by the OS from the option
   elements, which do not inherit the control's colours in every engine. */
:where(html[data-color-scheme="dark"]) :where(option) { background-color: var(--surface-raised); color: var(--ink); }

/* ============================================================
   1c. DARK — status chips, pills and figures
   ============================================================
   arbah-v3-tokens.css darkens the four `*-soft` chip BACKGROUNDS but
   never the strong foregrounds that sit on them, so a status chip in dark
   painted #047857 on #123D35 (2.19:1) — legible in light, unreadable in
   dark. Same for --arbah-bronze-700, which colours money figures and was
   left at #7A4F1F on a near-black card (2.61:1). Found by sweeping all 22
   routes; it hit chips, pills, trends and every SAR figure.

   The strong tokens are dual-role — text on ~70 rules, fill on ~46 — so
   lightening them alone would flip the fills to light-on-white. Only 7 of
   those fills carry text, and they are given a dark ink just below. */
html[data-color-scheme="dark"] .arbah-v3,
html[data-color-scheme="dark"].arbah-v3,
html[data-color-scheme="dark"] .av3,
html[data-color-scheme="dark"] .av3-shell {
  --arbah-success:#34D399;
  --arbah-warning:#FBBF24;
  --arbah-danger:#F87171;
  --arbah-info:#93B4DC;
  --arbah-bronze-700:#D9C28A;
  /* the bronze chip's own background — the one *-100 tint the dark block
     also forgot, which left gold text on cream (1.44:1) */
  --arbah-bronze-100:#2E2617;
}

/* The 7 rules that FILL with a strong semantic token and print text on it.
   Their text is hard-coded #fff, which stops working the moment the fill
   is a light tint, so it is restated as a dark ink here. */
html[data-color-scheme="dark"] .av3 .triage-head .badge,
html[data-color-scheme="dark"] .av3 .tb--photo,
html[data-color-scheme="dark"] .av3 .tb--risk,
html[data-color-scheme="dark"] .av3 .btn-danger,
html[data-color-scheme="dark"] .av3 .swatch .chk,
html[data-color-scheme="dark"] .av3 .tl-step.done .tl-dot,
/* Avatars take their circle colour from a semantic token chosen per user,
   so they inherit the same lightening and need the same dark ink. */
html[data-color-scheme="dark"] .av3 .avatar-sm,
html[data-color-scheme="dark"] .av3 .avatar-md,
/* the sidebar avatar is a bronze gradient whose far stop is bronze-700,
   so lightening that token lightened half the circle under white text */
html[data-color-scheme="dark"] .av3-shell .user-avatar { color: #14110A; }

/* The same dual-role trap, one pair further down the ramp. The *-soft
   fixes above cover the semantic chips, but `.id-chip` is painted from
   --arbah-navy-500 on --arbah-navy-100, and navy-500 is the one strong
   token the dark block deliberately does NOT lighten (it is also a solid
   button ground that has to keep carrying white text). So the project-ID
   chip stayed #6F91B8 on #263A50 — 3.56:1. Restating the chip's own ink
   fixes it without touching the token, so no button ground moves. */
html[data-color-scheme="dark"] .av3 .id-chip { color: var(--arbah-ink-muted); }

/* The appearance switch's SELECTED pill fills with --arbah-navy-500 and
   hard-codes #fff. In dark and black that token is the lifted accent
   #6F91B8, so the label of the option you are actually on sat at 3.27:1 —
   on every route, in both dark appearances. Same remedy as the seven
   fills above: a dark ink on the light fill (5.6:1) rather than lifting
   a token that also has to carry white text elsewhere. */
html[data-color-scheme="dark"] .av3-shell .appearance-option.active { color: #0B1420; }

/* The daily-report list's status pill is written as an INLINE style, so no
   rule here could reach it and it stayed a light chip on a dark table (the
   route sweep only sees it when a report of that status exists — with an
   empty list it renders nothing at all, which is how it was missed).
   DsrV16Panel now reads these variables and falls back to its original
   hexes, so light is unchanged and only dark/black re-point. */
html[data-color-scheme="dark"] {
  --dsr-chip-draft-bg: #253141;  --dsr-chip-draft-ink: #CBD5E1;
  --dsr-chip-final-bg: #123D35;  --dsr-chip-final-ink: #34D399;
}
html[data-appearance="black"] {
  --dsr-chip-draft-bg: #1C1C22;  --dsr-chip-draft-ink: #B4B4BE;
  --dsr-chip-final-bg: #10241D;  --dsr-chip-final-ink: #34D399;
}

/* ============================================================
   1b. DARK — the daily-report wizard
   ============================================================
   The wizard owns a --dsrw-* token set (declared on .dsrw-backdrop in
   arbah-v3-dsrwizard.css) whose light values are its original hexes.
   Re-pointing the names here is all it takes for the modal, its inputs,
   chips, dropdowns, review screen and done screen to follow the theme. */
/* `.dsrw-ta-list` rides alongside on every one of these: the suggestion list is
   portalled to <body>, so it is not a descendant of .dsrw-backdrop and would
   otherwise inherit none of the tokens. See the long note in
   arbah-v3-dsrwizard.css. */
html[data-color-scheme="dark"] .dsrw-backdrop,
html[data-color-scheme="dark"] .dsrw-ta-list {
  --dsrw-brand:#2C4A6E; --dsrw-brand-deep:#37597F;
  --dsrw-brand-text:#8FB3DB; --dsrw-on-brand:#FFFFFF;
  --dsrw-gold:#D9C28A; --dsrw-gold-ink:#E1CB95; --dsrw-gold-ink-2:#D9C28A;
  --dsrw-gold-border:#5B4A22; --dsrw-gold-soft:#3A2F14;
  --dsrw-gold-wash:#241D0F; --dsrw-gold-wash-2:#241D0F; --dsrw-gold-wash-3:#2A2213;
  --dsrw-surface:#111C2A; --dsrw-surface-2:#0F1A27; --dsrw-card:#16222F;
  --dsrw-sunken:#0D1825; --dsrw-sunken-2:#0F1A28; --dsrw-raised:#1A293A;
  --dsrw-track:#22344A;
  --dsrw-border-soft:#223243; --dsrw-border-soft-2:#26374A; --dsrw-border-hover:#35485E;
  --dsrw-border-2:#2B3D50; --dsrw-border:#2B3D50; --dsrw-border-dash:#3C5269;
  --dsrw-ink:#F1F5F9; --dsrw-ink-strong:#E2E8F0; --dsrw-ink-muted:#CBD5E1;
  --dsrw-ink-soft:#A9B6C6; --dsrw-ink-subtle:#94A3B8;
  --dsrw-ok:#34D399; --dsrw-ok-soft:#123D35;
  --dsrw-danger:#F87171; --dsrw-danger-2:#FCA5A5; --dsrw-danger-soft:#4A2025;
  --dsrw-scrim:rgba(0, 0, 0, 0.66);
  --dsrw-card-shadow:0 28px 70px rgba(0, 0, 0, 0.62), 0 3px 10px rgba(0, 0, 0, 0.44);
  --dsrw-focus-ring:rgba(143, 179, 219, 0.22);
}
/* The wizard renders its own inputs, so it needs the native-widget hint too. */
html[data-color-scheme="dark"] .dsrw-backdrop input,
html[data-color-scheme="dark"] .dsrw-backdrop select,
html[data-color-scheme="dark"] .dsrw-backdrop textarea { color-scheme: dark; }

/* The on-screen v16 document is a white A4 sheet by design (it is the
   printed page). Keep it white in every appearance — only its surround
   changes — so what you read on screen is what prints. */
html[data-color-scheme="dark"] .dsrw-doc { background: #FFFFFF; }

/* ============================================================
   2. BLACK — true black, Arbah blue + gold, no orange
   ============================================================
   Backgrounds are true/near black; ink is a soft off-white rather than
   #FFF, which is what keeps a black theme comfortable at night. Accents
   are LIFTED brand tints: navy #1e3a5f is invisible on black, so blue
   arrives as #8FB3DB and gold as #D4B36A. Solid brand fills stay deep
   enough for white text.
   ============================================================ */
html[data-appearance="black"] {
  --ink:            #EDEDF0;
  --ink-muted:      #B4B4BE;
  --ink-subtle:     #83838F;

  /* ELEVATION (Batch 10). The page is the only true-black thing on screen.
     Every surface that sits ON it is a distinctly lighter gray with a faint
     edge, so cards, popups and controls read as furniture standing on a
     black stage rather than dissolving into it. The previous #0C0C0E card
     on a #000 page was a 0.6% luminance step — invisible. */
  --surface:        #1B1B20;
  --surface-sunken: #0E0E11;
  --surface-raised: #26262C;

  --border:         #303038;
  --border-strong:  #3E3E48;

  /* True black wants a true-black scrim, not slate (#26). */
  --scrim-rgb: 0 0 0;

  --tooltip-bg:     #26262C;
  --tooltip-ink:    #EDEDF0;
  --tooltip-border: #3E3E48;

  --brand:          #1F4470;
  --brand-hover:    #28558A;
  --brand-soft:     #101A26;

  /* Gold, not amber — the app accent must carry no orange in Black. */
  --accent:         #D4B36A;
  --accent-hover:   #E2C489;
  --accent-soft:    #2A2314;

  --success:        #34D399;  --success-soft: #0C2A22;
  --warning:        #D4B36A;  --warning-soft: #2A2314;
  --danger:         #F87171;  --danger-soft:  #2E1416;
  --info:           #8FB3DB;  --info-soft:    #111E2E;

  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.80);
  --shadow-md: 0 10px 30px rgba(0, 0, 0, 0.86);
}

/* v3 token set — the sidebar, every module surface and all v3 chrome. */
html[data-appearance="black"],
html[data-appearance="black"] .arbah-v3,
html[data-appearance="black"].arbah-v3,
html[data-appearance="black"] .av3,
html[data-appearance="black"] .av3-shell {
  /* navy-700 is dual-role: the sidebar GROUND and the fill behind every
     primary button, segmented "on" state, KPI rail, progress bar and
     timeline dot. Black pinned it to #0A0A0C for the sidebar, which made
     all of those fills black-on-black — a primary button you could only
     find by its label. The sidebar now sets its own ground explicitly
     (below), which frees this token to be what the fills need: elevated
     gray, white text at 10.6:1.
     Same shape of bug as navy-500 in Batch 9, one step up the ramp. */
  --arbah-navy-900:#3A3A44;   /* primary-button hover — a step lighter */
  --arbah-navy-700:#2E2E36;
  /* Arbah blue, lifted just far enough to read on black (5.9:1) while
     still carrying white text where it is used as a fill (3.2:1) — it is
     both an accent colour and a button ground, so it has to clear both.
     Lifting it further (e.g. #8FB3DB) reads beautifully as text and then
     fails every white-on-blue chip, which the route sweep caught. */
  --arbah-navy-500:#6F91B8;
  --arbah-navy-100:#1E1E24;

  --arbah-bronze-700:#B9974F;
  --arbah-bronze-500:#D4B36A; /* app accent → gold, never bronze-orange */
  --arbah-bronze-200:#E2CC97;
  --arbah-bronze-100:#2A2314;

  --arbah-ink:#EDEDF0;
  --arbah-ink-muted:#B4B4BE;
  --arbah-ink-subtle:#83838F;

  /* Same elevation ramp as the legacy set above — the two must not drift,
     because both feed the same screen. */
  --arbah-surface:#1B1B20;
  --arbah-surface-sunken:#0E0E11;
  --arbah-surface-raised:#26262C;

  --arbah-border:#303038;
  --arbah-border-strong:#3E3E48;

  --arbah-success:#34D399;  --arbah-success-soft:#0C2A22;
  --arbah-warning:#D4B36A;  --arbah-warning-soft:#2A2314;
  --arbah-danger:#F87171;   --arbah-danger-soft:#2E1416;
  --arbah-info:#8FB3DB;     --arbah-info-soft:#111E2E;

  --page-bg:#000000;
  --app-bg:#000000;
  --shadow-sm:0 1px 3px rgba(0, 0, 0, 0.80);
  --shadow-md:0 10px 30px rgba(0, 0, 0, 0.86);
  --shadow-card:0 2px 10px rgba(0, 0, 0, 0.72);
  --ring:0 0 0 3px rgba(143, 179, 219, 0.34);
  --ring-danger:0 0 0 3px rgba(248, 113, 113, 0.32);
}

html[data-appearance="black"] body { background: #000000; color: #EDEDF0; }
html[data-appearance="black"] .modal-backdrop { background: rgba(0, 0, 0, 0.78); }

/* Gold is the app accent in Black, and gold is light — so the two fills
   that print text on it need a dark ink rather than the hard-coded #fff. */
html[data-appearance="black"] .av3 .btn-bronze,
/* the mobile bottom-tab count badge fills with bronze-500 — gold in Black —
   and hard-codes #fff, which lands at 2.0:1 */
html[data-appearance="black"] .av3-shell .bt-badge,
html[data-appearance="black"] .av3-shell .user-avatar { color: #14110A; }

/* The v3 shell paints the sidebar from a navy gradient; on black that
   gradient is the one place a blue cast survives, so flatten it. The
   sidebar and topbar are the stage rather than furniture, so they stay
   near-black — but they get a visible edge and their ITEMS are lifted
   (below), which is what stops the shell reading as one black slab. */
html[data-appearance="black"] .av3-shell .sidebar {
  background: #0A0A0C;
  border-inline-end: 1px solid #2A2A32;
}
html[data-appearance="black"] .av3-shell .topbar {
  background: #131318;
  border-bottom: 1px solid #2A2A32;
}

/* Under 1024px the sidebar stops being a column beside the content and
   becomes a fixed drawer that slides OVER the page. At the desktop ground
   (#0A0A0C) that drawer is invisible against the black behind it — you
   cannot see where the panel ends. As furniture it gets the surface
   treatment plus a hard edge. */
@media (max-width: 1023px) {
  html[data-appearance="black"] .av3-shell .sidebar {
    background: #1B1B20;
    border-inline-end: 1px solid #3A3A44;
    box-shadow: 0 0 60px rgba(0, 0, 0, .85);
  }
}
/* The mobile bottom bar sits on the page edge for the same reason. */
html[data-appearance="black"] .av3-shell .bottom-tabs {
  background: #131318;
  border-top: 1px solid #2A2A32;
}

/* ============================================================
   2b. BLACK — elevation, applied where tokens cannot reach
   ============================================================
   Raising --arbah-surface / -raised / --border lifts every surface that
   reads a token, which is most of the app. What is left is the handful
   of places that hard-code a near-black value or paint themselves from
   --arbah-navy-700 (which in Black IS true black). Those are here.
   Rule of the batch: nothing pure-black may sit on the pure-black page.
   ============================================================ */

/* Sidebar furniture: nav rows, the section headers, the appearance well
   and the sign-out button all sit ON the near-black sidebar. */
html[data-appearance="black"] .av3-shell .nav-item:hover,
html[data-appearance="black"] .av3-shell .more-toggle:hover {
  background: #202026;
}
html[data-appearance="black"] .av3-shell .nav-item.active {
  background: #26262C;
  box-shadow: inset 0 0 0 1px #3A3A44;
}
html[data-appearance="black"] .av3-shell .appearance-options {
  background: #131318;
  border-color: #2E2E36;
}
html[data-appearance="black"] .av3-shell .signout-btn {
  background: #1B1B20;
  border-color: #33333B;
}

/* The SELECTED appearance pill. Blue is right in Light and in the Blue
   theme; in Black the highlight is elevation, not hue — a lighter gray
   with a brighter edge. This also un-does the dark-ink rule above, which
   only makes sense on the blue fill. */
html[data-appearance="black"] .av3-shell .appearance-option.active {
  background: #2E2E36;
  color: #EDEDF0;
  box-shadow: 0 1px 5px rgba(0, 0, 0, .5), inset 0 0 0 1px #4A4A56;
}
html[data-appearance="black"] .av3-shell .appearance-option.active .appearance-state {
  background: #EDEDF0;
  box-shadow: 0 0 0 2px rgba(237, 237, 240, .14);
}

/* The Arbah Assistant. Its FAB, header and user bubbles paint from
   --arbah-navy-700, which Black sets to #0A0A0C — a black circle on a
   black page, which is exactly how the floating button disappeared. */
/* Buttons: the faint edge is what makes an elevated gray read as a
   pressable surface rather than a lighter patch of background. */
html[data-appearance="black"] .av3 .btn-primary {
  box-shadow: 0 1px 2px rgba(0, 0, 0, .6), inset 0 0 0 1px #4A4A56;
}
html[data-appearance="black"] .av3 .btn-secondary,
html[data-appearance="black"] .av3 .btn-ghost {
  background: #1F1F25;
  border-color: #3E3E48;
}

html[data-appearance="black"] .aa-fab {
  background: #26262C;
  color: #EDEDF0;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .86), inset 0 0 0 1px #3E3E48;
}
html[data-appearance="black"] .aa-fab:hover { background: #33333B; }
html[data-appearance="black"] .aa-head { background: #1B1B20; border-bottom: 1px solid #303038; }
html[data-appearance="black"] .aa-row.user .aa-bubble { background: #2E2E36; color: #EDEDF0; }
html[data-appearance="black"] .aa-chip {
  background: #26262C; border-color: #3E3E48; color: #EDEDF0;
}
html[data-appearance="black"] .aa-chip:hover { background: #3A3A44; color: #FFFFFF; }
html[data-appearance="black"] .aa-send { background: #2E2E36; color: #EDEDF0; }
html[data-appearance="black"] .aa-send:hover { background: #3A3A44; }

/* Tailwind-utility popups (SafetyLogModal and friends) hard-code a white
   panel; § 1 re-points the neutral ramp for dark, and Black needs the
   lifted values rather than the near-black ones it would inherit. */
html[data-appearance="black"] .bg-white,
html[data-appearance="black"] .bg-gray-50,
html[data-appearance="black"] .bg-gray-100 { background-color: #1B1B20; }
html[data-appearance="black"] .border-gray-200,
html[data-appearance="black"] .border-gray-300 { border-color: #303038; }

/* Native controls and the shared modal chrome. */
html[data-appearance="black"] .modal-panel,
html[data-appearance="black"] .dropdown-menu,
html[data-appearance="black"] .menu-panel { background: #1B1B20; border-color: #303038; }
html[data-appearance="black"] :where(input, select, textarea) {
  background: #26262C;
  border-color: #3E3E48;
}
html[data-appearance="black"] option { background: #1B1B20; color: #EDEDF0; }

/* Wizard under Black. */
html[data-appearance="black"] .dsrw-backdrop,
html[data-appearance="black"] .dsrw-ta-list {
  --dsrw-brand:#1F4470; --dsrw-brand-deep:#28558A;
  --dsrw-brand-text:#8FB3DB; --dsrw-on-brand:#FFFFFF;
  --dsrw-gold:#D4B36A; --dsrw-gold-ink:#E2CC97; --dsrw-gold-ink-2:#D4B36A;
  --dsrw-gold-border:#4A3D1C; --dsrw-gold-soft:#2A2314;
  --dsrw-gold-wash:#1A1509; --dsrw-gold-wash-2:#1A1509; --dsrw-gold-wash-3:#1F1A0D;
  /* Elevated to match the app ramp: the wizard card is furniture on the
     black scrim, not another sheet of black. */
  --dsrw-surface:#1B1B20; --dsrw-surface-2:#16161A; --dsrw-card:#1F1F25;
  --dsrw-sunken:#0E0E11; --dsrw-sunken-2:#131318; --dsrw-raised:#26262C;
  --dsrw-track:#2A2A32;
  --dsrw-border-soft:#2A2A32; --dsrw-border-soft-2:#303038; --dsrw-border-hover:#42424E;
  --dsrw-border-2:#303038; --dsrw-border:#303038; --dsrw-border-dash:#3E3E48;
  --dsrw-ink:#EDEDF0; --dsrw-ink-strong:#DADAE0; --dsrw-ink-muted:#B4B4BE;
  --dsrw-ink-soft:#9A9AA6; --dsrw-ink-subtle:#83838F;
  --dsrw-ok:#34D399; --dsrw-ok-soft:#0C2A22;
  --dsrw-danger:#F87171; --dsrw-danger-2:#FCA5A5; --dsrw-danger-soft:#2E1416;
  --dsrw-scrim:rgba(0, 0, 0, 0.80);
  --dsrw-card-shadow:0 28px 70px rgba(0, 0, 0, 0.88), 0 3px 10px rgba(0, 0, 0, 0.60);
  --dsrw-focus-ring:rgba(143, 179, 219, 0.24);
}

/* ============================================================
   2c. Recently deleted — the shared recycle strip
   ============================================================
   Built entirely from tokens, so it follows Light / Blue / Black / Auto
   without a per-theme rule. Quiet by design: it is a safety net, not a
   destination. */
.recycle-bin {
  margin: 14px 0 4px;
  border: 1px solid var(--border, #E2E8F0);
  border-radius: 10px;
  background: var(--surface, #FFFFFF);
  overflow: hidden;
}
.recycle-head {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 10px 13px;
  border: 0;
  background: transparent;
  color: var(--ink-muted, #475569);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  text-align: start;
}
.recycle-head:hover { background: var(--surface-raised, #F1F5F9); }
.recycle-title { font-weight: 600; color: var(--ink, #0F172A); }
.recycle-count { color: var(--ink-muted, #475569); font-size: 12px; }
.recycle-toggle { margin-inline-start: auto; font-size: 12px; font-weight: 600; }
.recycle-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--border, #E2E8F0); }
.recycle-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 13px;
  border-bottom: 1px solid var(--border, #E2E8F0);
}
.recycle-row:last-child { border-bottom: 0; }
.recycle-thumb {
  flex: 0 0 36px; width: 36px; height: 36px;
  border-radius: 6px; object-fit: cover;
  background: var(--surface-raised, #F1F5F9);
  border: 1px solid var(--border, #E2E8F0);
}
.recycle-thumb-none {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-subtle, #94A3B8);
}
.recycle-label {
  flex: 1; min-width: 0;
  font-size: 13px; color: var(--ink, #0F172A);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.recycle-days { font-size: 11.5px; color: var(--ink-muted, #475569); white-space: nowrap; }
.recycle-restore {
  flex: none;
  padding: 5px 11px;
  border-radius: 6px;
  border: 1px solid var(--border-strong, #CBD5E1);
  background: var(--surface, #FFFFFF);
  color: var(--ink, #0F172A);
  font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
}
.recycle-restore:hover:not(:disabled) { background: var(--surface-raised, #F1F5F9); }
.recycle-restore:disabled { opacity: .6; cursor: default; }
@media (max-width: 480px) {
  .recycle-days { display: none; }
}

/* Site submissions awaiting a decision. Given a warm edge so it reads as a
   queue that wants attention, without shouting like an error. */
.pending-subs {
  margin: 0 0 16px;
  border: 1px solid var(--warning, #D97706);
  border-radius: 12px;
  background: var(--surface, #FFFFFF);
  overflow: hidden;
}
.pending-subs-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 11px 14px;
  background: var(--warning-soft, #FEF3C7);
  border-bottom: 1px solid var(--border, #E2E8F0);
}
.pending-subs-title { font-size: 13.5px; font-weight: 700; color: var(--ink, #0F172A); }
.pending-subs-count { font-size: 12px; color: var(--ink-muted, #475569); }
.pending-subs-list { list-style: none; margin: 0; padding: 0; }
.pending-subs-row {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border, #E2E8F0);
}
.pending-subs-row:last-child { border-bottom: 0; }
.pending-subs-thumb-btn { padding: 0; border: 0; background: none; cursor: pointer; flex: none; }
.pending-subs-thumb, .pending-subs-row .photo-missing {
  width: 48px; height: 48px; min-height: 48px;
  border-radius: 8px; object-fit: cover;
  border: 1px solid var(--border, #E2E8F0);
}
.pending-subs-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pending-subs-vendor {
  font-size: 13.5px; font-weight: 600; color: var(--ink, #0F172A);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pending-subs-sub, .pending-subs-by { font-size: 11.5px; color: var(--ink-muted, #475569); }
.pending-subs-amt {
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 14px;
  color: var(--ink, #0F172A); white-space: nowrap;
}
.pending-subs-actions { display: flex; gap: 7px; flex: none; }
.pending-approve, .pending-reject {
  min-height: 36px; padding: 7px 14px;
  border-radius: 7px; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.pending-approve {
  border: 1px solid var(--success, #15803D);
  background: var(--success, #15803D); color: #FFFFFF;
}
.pending-reject {
  border: 1px solid var(--border-strong, #CBD5E1);
  background: var(--surface, #FFFFFF); color: var(--danger, #B91C1C);
}
.pending-approve:disabled, .pending-reject:disabled { opacity: .6; cursor: default; }
/* In dark and black the success fill is a light mint, so white text on it
   stops working — same dark-ink-on-light-fill rule as § 1c. */
html[data-color-scheme="dark"] .pending-approve { color: #0B1C16; }
@media (max-width: 620px) {
  .pending-subs-row { flex-wrap: wrap; }
  .pending-subs-actions { width: 100%; }
  .pending-approve, .pending-reject { flex: 1; }
}

/* ============================================================
   2d. Engineer receipt submit — the site-side door
   ============================================================
   Token-built like the recycle strip, so it follows all four appearances
   with no per-theme rule. Deliberately large touch targets: this screen
   is used one-handed, outdoors, in gloves. */
.eng-receipt-form, .eng-receipt-mine {
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border, #E2E8F0);
  border-radius: 12px;
  padding: 16px;
  margin-bottom: 14px;
}
.eng-receipt-drop {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 26px 16px;
  border: 1px dashed var(--border-strong, #CBD5E1);
  border-radius: 10px;
  background: var(--surface-sunken, #F6F8FB);
  color: var(--ink-subtle, #94A3B8);
}
.eng-receipt-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.eng-receipt-actions .btn-primary { min-height: 46px; }
.eng-receipt-preview { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.eng-receipt-preview img {
  max-height: 260px; max-width: 100%;
  border-radius: 10px; object-fit: contain;
  border: 1px solid var(--border, #E2E8F0);
  background: var(--surface-sunken, #F6F8FB);
}
.eng-receipt-hint { margin: 10px 0 0; font-size: 13px; color: var(--ink-muted, #475569); }
.eng-receipt-hint.ok { color: var(--success, #15803D); }
.eng-receipt-hint.warn { color: var(--warning, #B45309); }
.eng-receipt-err { margin: 10px 0 0; font-size: 13px; font-weight: 600; color: var(--danger, #B91C1C); }
.eng-receipt-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px; margin-top: 14px;
}
.eng-receipt-wide { grid-column: 1 / -1; }
.eng-receipt-grid label {
  display: flex; flex-direction: column; gap: 5px;
  font-size: 12.5px; font-weight: 600; color: var(--ink-muted, #475569);
}
.eng-receipt-grid input, .eng-receipt-grid select {
  min-height: 44px; padding: 9px 11px;
  border: 1px solid var(--border-strong, #CBD5E1);
  border-radius: 8px;
  background: var(--surface, #FFFFFF);
  color: var(--ink, #0F172A);
  font: inherit; font-size: 14px; font-weight: 400;
}
.eng-receipt-submit {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 16px;
}
.eng-receipt-submit .btn-primary { min-height: 46px; padding: 12px 22px; }
/* --ink-subtle is 2.4:1 on a light card — the same trap the photo
   placeholder fell into. This line tells someone their submission is about
   to become unchangeable, so it has to be readable. */
.eng-receipt-locknote { font-size: 12px; color: var(--ink-muted, #475569); }
.eng-receipt-empty { margin: 6px 0 0; font-size: 13px; color: var(--ink-muted, #475569); }
.eng-receipt-list { list-style: none; margin: 10px 0 0; padding: 0; }
.eng-receipt-row {
  display: flex; align-items: center; gap: 11px;
  padding: 10px 0; border-bottom: 1px solid var(--border, #E2E8F0);
}
.eng-receipt-row:last-child { border-bottom: 0; }
.eng-receipt-thumb, .eng-receipt-row .photo-missing {
  flex: 0 0 44px; width: 44px; height: 44px;
  border-radius: 7px; object-fit: cover; min-height: 44px;
}
.eng-receipt-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.eng-receipt-vendor {
  font-size: 13.5px; font-weight: 600; color: var(--ink, #0F172A);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.eng-receipt-sub { font-size: 11.5px; color: var(--ink-muted, #475569); }
.eng-receipt-amt {
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 13.5px;
  color: var(--ink, #0F172A); white-space: nowrap;
}
.eng-receipt-lock { color: var(--ink-subtle, #94A3B8); display: inline-flex; }
@media (max-width: 560px) {
  .eng-receipt-grid { grid-template-columns: 1fr; }
  .eng-receipt-amt { display: none; }
}

/* ── Addendum 2 (#17/#19): the in-app file viewer ────────────────────────── */
/* Large by design. The whole point is that a drawing opens big enough to read
   WITHOUT leaving the app — a small preview would just push people back to the
   new-tab habit this replaces. */
.fileview-backdrop {
  position: fixed; inset: 0; z-index: 90;
  background: rgb(var(--scrim-rgb) / .72);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
.fileview {
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border, #E2E8F0);
  border-radius: 14px;
  width: 100%; max-width: 1100px; height: 100%; max-height: 92vh;
  display: flex; flex-direction: column; overflow: hidden;
  box-shadow: 0 24px 64px rgb(var(--scrim-rgb) / .34);
}
.fileview-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border, #E2E8F0);
  background: var(--surface-raised, #F8FAFC);
  flex: 0 0 auto;
}
.fileview-name {
  display: flex; align-items: center; gap: 9px; min-width: 0;
  font-size: 14px; font-weight: 600; color: var(--ink, #0F172A);
}
.fileview-name span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fileview-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.fileview-actions .btn-primary,
.fileview-actions .btn-secondary { min-height: 36px; padding: 7px 13px; font-size: 13px; }
/* The download control is an <a>, so it needs the button's own layout back. */
.fileview-dl {
  display: inline-flex; align-items: center; gap: 6px;
  text-decoration: none; border-radius: 9px;
}
.fileview-body {
  flex: 1 1 auto; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-sunken, #F1F5F9);
  overflow: auto;
}
.fileview-img {
  max-width: 100%; max-height: 100%; object-fit: contain; display: block;
}
.fileview-frame { width: 100%; height: 100%; border: 0; background: #FFFFFF; }
.fileview-msg {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 40px 24px; text-align: center;
  color: var(--ink-muted, #475569); font-size: 14px;
}
.fileview-msg p { margin: 0; }
.fileview-hint { font-size: 12.5px; color: var(--ink-subtle, #94A3B8); }

@media (max-width: 560px) {
  .fileview-backdrop { padding: 0; }
  .fileview { max-height: 100vh; border-radius: 0; border: 0; }
  /* On a phone the filename would eat the toolbar; the actions matter more. */
  .fileview-name span { display: none; }
  .fileview-actions .btn-primary,
  .fileview-actions .btn-secondary { padding: 7px 10px; }
}

/* The register's document title is a button now, not a link (Addendum 2 —
   it used to navigate to the raw storage URL). It must not look like one:
   the typography comes from .dt-title, this only strips the button chrome. */
.av3 .dt-titlebtn {
  background: none; border: 0; padding: 0; margin: 0;
  text-align: inherit; cursor: pointer;
}
.av3 .dt-titlebtn:hover { text-decoration: underline; }

/* ── Batch 13 item 5 (#40): the milestone brief ──────────────────────────── */
/* Two facts, stated large. This replaced a 39-row dated list; if it ever
   grows a third row, it has stopped being a brief. */
.av3 .msbrief-body { display: flex; flex-direction: column; gap: 14px; }
.av3 .msbrief-row {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 12px;
  align-items: baseline;
  padding: 12px 14px; border-radius: 10px;
  background: var(--arbah-surface-sunken, #F1F5F9);
  border-inline-start: 3px solid var(--arbah-border-strong, #CBD5E1);
}
.av3 .msbrief-row.is-current {
  background: var(--arbah-navy-100, #E3EAF3);
  border-inline-start-color: var(--arbah-navy-700, #102A43);
}
.av3 .msbrief-label {
  grid-column: 1 / -1;
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--arbah-ink-muted, #475569);
}
.av3 .msbrief-name {
  font-family: var(--font-serif); font-size: 17px; font-weight: 600;
  line-height: 1.25; color: var(--arbah-ink, #0F172A); min-width: 0;
  overflow-wrap: anywhere;
}
.av3 .msbrief-row.is-current .msbrief-name { font-size: 19px; }
.av3 .msbrief-date {
  font-family: var(--font-mono); font-size: 12.5px; white-space: nowrap;
  color: var(--arbah-ink-muted, #475569);
}
.av3 .msbrief-note {
  margin: 0; font-size: 11.5px; color: var(--arbah-ink-subtle, #94A3B8);
}

/* Batch 13 item 4 (#38): activity rows are buttons now — strip the chrome,
   keep the affordance. */
.av3 .pd-activity-row {
  border: 0; background: none; font: inherit; cursor: pointer; text-align: start;
}
.av3 .pd-activity-row:hover,
.av3 .pd-activity-row:focus-visible {
  background: var(--arbah-surface-raised, #F8FAFC);
}

/* ── Batch 12 item 3c: Documents opens on the projects ───────────────────── */
.docsec-projects { padding: 4px 0 8px; }
.docsec-projgrid {
  list-style: none; margin: 12px 0 0; padding: 0;
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
.docsec-projcard {
  width: 100%; display: flex; align-items: center; gap: 12px;
  padding: 14px 14px; font: inherit; text-align: inherit; cursor: pointer;
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border, #E2E8F0);
  border-radius: 11px; color: var(--ink, #0F172A);
  transition: box-shadow .14s, transform .1s, border-color .14s;
}
.docsec-projcard:hover, .docsec-projcard:focus-visible {
  border-color: var(--border-strong, #CBD5E1);
  box-shadow: 0 4px 14px rgba(15, 23, 42, .08);
  transform: translateY(-1px);
}
.docsec-projicon {
  flex: 0 0 40px; width: 40px; height: 40px; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-sunken, #F1F5F9);
  color: var(--ink-muted, #475569);
}
.docsec-projname {
  flex: 1; min-width: 0; font-size: 14px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.docsec-projcount {
  flex: 0 0 auto; font-size: 12px; color: var(--ink-muted, #475569);
  font-variant-numeric: tabular-nums;
}
.docsec-back {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12.5px; padding: 5px 9px;
}
.docsec-crumb {
  font-size: 13.5px; font-weight: 600; color: var(--ink, #0F172A);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Batch 12: the safety entry, opened ──────────────────────────────────── */
/* Addendum 2 item 1 (#16) — centered, like the wizard.
   This was `align-items: flex-start`, which pinned the entry detail to the top
   of the page. The pair below is the robust centring idiom for a panel that
   may be taller than the viewport: `align-items: center` alone CLIPS the top
   of an over-tall panel and makes it unreachable, because a flex item centred
   in a scroll container cannot scroll above its container's top edge.
   `margin: auto` on the panel centres it when it fits and lets it start at the
   top and scroll when it does not. */
.safety-detail-backdrop {
  position: fixed; inset: 0; z-index: 60;
  background: rgb(var(--scrim-rgb) / .55);
  display: flex; align-items: center; justify-content: center;
  padding: 24px 18px; overflow-y: auto;
}
.safety-detail-backdrop > .safety-detail { margin: auto; }
.safety-detail {
  background: var(--arbah-surface, #FFFFFF);
  border: 1px solid var(--arbah-border, #E2E8F0);
  border-radius: 14px; padding: 18px;
  width: 100%; max-width: 620px;
  box-shadow: 0 18px 48px rgba(15, 23, 42, .28);
}
.safety-detail-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
}
.safety-detail-head h3 {
  margin: 0 0 8px; font-size: 16px; font-weight: 700; color: var(--arbah-ink, #0F172A);
}
.safety-detail-chips { display: flex; gap: 7px; flex-wrap: wrap; }

.safety-detail-photos { margin-top: 16px; }
.safety-detail-photos h4 {
  margin: 0 0 8px; font-size: 11.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .04em; color: var(--arbah-ink-muted, #475569);
}
/* The photograph is the point of the view, so it gets real size — not a
   thumbnail strip you have to squint at. */
.safety-detail-grid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.safety-detail-photo, .safety-detail-grid .photo-missing {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: 9px; border: 1px solid var(--arbah-border, #E2E8F0);
  background: var(--arbah-surface-sunken, #F1F5F9);
}
.safety-detail-nophotos {
  margin: 0; font-size: 12.5px; color: var(--arbah-ink-muted, #475569);
}

.safety-detail-fields {
  margin: 16px 0 0; display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px;
}
.safety-detail-fields > div { min-width: 0; }
.safety-detail-wide { grid-column: 1 / -1; }
.safety-detail-fields dt {
  font-size: 11.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .03em; color: var(--arbah-ink-muted, #475569); margin-bottom: 3px;
}
.safety-detail-fields dd {
  margin: 0; font-size: 14px; color: var(--arbah-ink, #0F172A);
  line-height: 1.5; overflow-wrap: anywhere;
}

.safety-detail-resolve {
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--arbah-border, #E2E8F0);
}
.safety-detail-resolveform { display: flex; flex-direction: column; gap: 8px; }
.safety-detail-resolveform label {
  font-size: 12.5px; font-weight: 600; color: var(--arbah-ink, #0F172A);
}
.safety-detail-resolveform textarea {
  width: 100%; padding: 9px 11px; font: inherit; font-size: 14px; resize: vertical;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 9px;
  background: var(--arbah-surface-raised, #FFFFFF); color: var(--arbah-ink, #0F172A);
}
.safety-detail-actions { display: flex; gap: 9px; justify-content: flex-end; }
.safety-detail-err {
  margin: 0; font-size: 12.5px; font-weight: 600; color: var(--arbah-danger, #B91C1C);
}
.safety-detail-readonly {
  margin: 0; display: flex; align-items: center; gap: 7px;
  font-size: 12.5px; color: var(--arbah-ink-muted, #475569);
}

/* The fingerprints. Quiet, but always present — that is the point. */
.safety-detail-stamps {
  margin-top: 16px; padding-top: 12px;
  border-top: 1px solid var(--arbah-border, #E2E8F0);
  display: flex; flex-direction: column; gap: 5px;
  font-size: 11.5px; color: var(--arbah-ink-muted, #475569);
}
.safety-detail-stamps > div { display: flex; align-items: center; gap: 6px; }
.safety-detail-history summary { cursor: pointer; }
.safety-detail-history ul { margin: 6px 0 0; padding-inline-start: 18px; }
.safety-detail-history li { margin-bottom: 3px; }

@media (max-width: 560px) {
  .safety-detail-fields { grid-template-columns: 1fr; }
  .safety-detail { padding: 14px; }
}

/* ── Batch 12: the real phase list on Project Progress ───────────────────── */
/* Replaced the percentage bar chart. There is no bar because there is no
   measurement — the sequence is the information, and the current-phase marker
   is the only claim being made. */
.av3 .phaseplan {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column;
  counter-reset: phaseplan;
}
.av3 .phaseplan-row { position: relative; counter-increment: phaseplan; }

/* Addendum 2 item 8c (#24) — the connector, centred and pointing.
   It used to be a 2px line at inset-inline-start:9px. The dot is 20px wide
   starting at the button's 6px padding, so its centre is at 16px — the line
   sat 7px to the LEFT of the circles it was meant to join and read as a
   border on the card rather than a sequence. It is now centred on the dot
   and ends in a small downward chevron, so the eye is told which way the
   work runs. `has-next` (not :last-child) because the last row must not
   point at nothing. */
.av3 .phaseplan-row.has-next::after {
  content: ''; position: absolute;
  inset-inline-start: 16px; margin-inline-start: -3px;
  bottom: 1px; width: 5px; height: 5px;
  border-inline-end: 1.5px solid var(--arbah-border-strong, #CBD5E1);
  border-block-end: 1.5px solid var(--arbah-border-strong, #CBD5E1);
  transform: rotate(45deg);
}
/* Behind the work, the trail is green — the sequence shows how far it got. */
.av3 .phaseplan-row.is-done.has-next::after {
  border-inline-end-color: var(--arbah-success, #16A34A);
  border-block-end-color: var(--arbah-success, #16A34A);
}

.av3 .phaseplan-btn {
  width: 100%; display: flex; align-items: center; gap: 10px;
  padding: 7px 6px; border: 0; background: none; border-radius: 8px;
  font: inherit; color: var(--arbah-ink, #0F172A); text-align: inherit;
}
.av3 .phaseplan-btn:not(:disabled) { cursor: pointer; }
.av3 .phaseplan-btn:not(:disabled):hover,
.av3 .phaseplan-btn:not(:disabled):focus-visible {
  background: var(--arbah-surface-raised, #F8FAFC);
}
.av3 .phaseplan-dot {
  flex: 0 0 20px; width: 20px; height: 20px; border-radius: 50%;
  border: 2px solid var(--arbah-border-strong, #CBD5E1);
  background: var(--arbah-surface, #FFFFFF);
  position: relative; z-index: 1;
  /* Centres the check that a passed phase carries. */
  display: inline-flex; align-items: center; justify-content: center;
  box-sizing: border-box;
}

/* Addendum 2 item 8b (#24): a passed phase is a green circle with a white
   check — check, check, check down the list. This is the same HINT the
   current-phase marker is: it means the daily reports have moved beyond this
   phase, NOT that anyone signed it off, which is why no percentage appears. */
.av3 .phaseplan-row.is-done .phaseplan-dot {
  border-color: var(--arbah-success, #16A34A);
  background: var(--arbah-success, #16A34A);
  color: #FFFFFF;
}
.av3 .phaseplan-row.is-done .phaseplan-name { color: var(--arbah-ink-muted, #475569); }
/* Batch 16 (#65): the third state written down. It relied on the defaults
   before, which is how the two surfaces drifted — the strip could paint an
   upcoming phase green and nothing in the stylesheet said it must not. All
   three states are now explicit on BOTH surfaces. */
.av3 .phaseplan-row.is-upcoming .phaseplan-dot {
  border-color: var(--arbah-border-strong, #CBD5E1);
  background: transparent;
}
.av3 .phaseplan-row.is-upcoming .phaseplan-name { color: var(--arbah-ink-subtle, #94A3B8); }
.av3 .phaseplan-name {
  flex: 1; min-width: 0; font-size: 13.5px; line-height: 1.4;
  overflow-wrap: anywhere;
}

/* Batch 13 item 8 (#33): the per-phase fill. Fixed width so every bar starts
   at the same x and the column reads as a set rather than a ragged edge.
   Batch 14 item 2 (#47): the track and the fill were too close in tone for a
   partial value to read — 33% looked like an empty bar, and only 100% looked
   filled at all. The track is now a clearly sunken channel, the fill is a
   solid brand colour with a minimum visible width, and the two differ in
   lightness as well as hue so the proportion is legible in every theme. */
/* ── Phase bars (Batch 15 item 15, #47) ─────────────────────────────────
   Batch 14 made the fill a colour that survives all four appearances, and it
   measurably works: at 33% the fill really is 30px of 92. Sultan still could
   not see it — "the bar shows no visible fill; bars only look green at 100%"
   — and looking at the row explains why. A finished phase carries a green
   tick AND a full green bar; against that, a 30x8px blue sliver two columns
   over reads as an empty track with a smudge on it.

   So the bar is given real presence: taller, wider, a track that is clearly
   an EMPTY CONTAINER rather than a pale line, and a fill with a visible edge
   against it. Same colours, same maths — it just stops being a hairline. */
.av3 .phaseplan-bar {
  flex: 0 0 120px; height: 12px; border-radius: 999px;
  /* The empty track is deliberately a step darker than the row it sits on,
     so "how much is left" is as readable as "how much is done". */
  background: var(--arbah-border, #E2E8F0);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--arbah-ink, #0F172A) 12%, transparent);
  overflow: hidden; position: relative;
}
.av3 .phaseplan-bar > i {
  display: block; height: 100%; border-radius: 999px;
  /* NOT navy. Measured across the four appearances, --arbah-navy-500 in the
     Dark theme is #0D2238 against a #0B1420 track — a 2% luminance step, i.e.
     invisible, which is the same failure the Plan-vs-Reality line had. --info
     is the one ramp that stays clear of BOTH a white and a black track. */
  background: var(--arbah-info, #2C4A6E);
  /* A non-zero percentage must be VISIBLE as a sliver rather than rounding
     away to nothing. 4px on a 120px track is still legible as "started". */
  min-width: 4px;
  /* An edge, so the fill reads as a solid block ending at a point rather
     than as a gradient fading out. */
  box-shadow: 1px 0 0 0 color-mix(in srgb, var(--arbah-ink, #0F172A) 25%, transparent);
  transition: width .3s ease;
}
/* 0% is the one case that must show no fill at all — a sliver on a phase
   nobody has started says work has begun.
   BOTH spellings, because Preact serialises a style string as "width: 0%"
   WITH a space and the original selector had none: measured on Progress, a
   0% phase was drawing the 4px min-width sliver the whole time. */
.av3 .phaseplan-bar > i[style*="width:0%"],
.av3 .phaseplan-bar > i[style*="width: 0%"] { min-width: 0; box-shadow: none; }
/* A finished phase goes green — that is what "done" has meant on this row
   since Batch 13, and it is the contrast that made the partial bars look
   empty by comparison. Kept, now that the partial bar can hold its own. */
.av3 .phaseplan-bar > i[style*="width:100%"],
.av3 .phaseplan-bar > i[style*="width: 100%"] { background: var(--arbah-success, #047857); }
@media (max-width: 560px) { .av3 .phaseplan-bar { flex-basis: 84px; } }
.av3 .phaseplan-row.is-done .phaseplan-bar > i { background: var(--arbah-success, #16A34A); }
/* The current phase is the one partial bar on the list, so it is the one that
   must read as "part filled" — same reasoning, same token. */
.av3 .phaseplan-row.is-current .phaseplan-bar > i { background: var(--arbah-info, #2C4A6E); }
.av3 .phaseplan-pct {
  flex: 0 0 auto; min-width: 34px; text-align: end;
  font-size: 11px; color: var(--arbah-ink-muted, #475569);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 560px) {
  /* The name needs the room on a phone; the number still carries the value. */
  .av3 .phaseplan-bar { flex-basis: 52px; }
}
.av3 .phaseplan-chip { flex: 0 0 auto; font-size: 11px; }

/* The one row making a claim gets the only emphasis on the card. */
.av3 .phaseplan-row.is-current .phaseplan-dot {
  border-color: var(--arbah-navy-700);
  background: var(--arbah-navy-700);
  box-shadow: 0 0 0 3px var(--arbah-navy-100, rgba(30, 58, 95, .14));
}
.av3 .phaseplan-row.is-current .phaseplan-name { font-weight: 600; }

.av3 .phaseplan-note {
  margin: 10px 0 0; font-size: 11.5px; line-height: 1.5;
  color: var(--arbah-ink-muted, #475569);
}

/* ── Batch 12: photo guidance, loud scan failure, tappable rows, detail ── */

/* Sits under the snap/choose buttons: the single biggest cause of an
   unreadable receipt is a photo taken at an angle, so say so before the
   photo is taken rather than after the scan fails. */
.eng-receipt-guidance {
  margin: 4px 0 0; max-width: 34ch; text-align: center;
  font-size: 12.5px; line-height: 1.5; color: var(--ink-muted, #475569);
}

/* A failed scan is an ERROR, not a hint. It gets a bordered well and a
   button, so it reads as "this did not work, here is what to do" rather
   than as one more line of grey helper text nobody reads. */
.eng-receipt-scanfail {
  margin: 12px 0 0; padding: 12px 14px;
  border: 1px solid var(--danger-border, #FCA5A5);
  background: var(--danger-soft, #FEF2F2);
  border-radius: 10px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
}
.eng-receipt-scanfail-msg {
  margin: 0; font-size: 13.5px; font-weight: 600; color: var(--danger, #B91C1C);
}
.eng-receipt-scanfail-why {
  margin: 0; font-size: 12px; color: var(--ink-muted, #475569);
  word-break: break-word;
}
.eng-receipt-scanfail .btn-secondary { min-height: 38px; }

.eng-receipt-openhint {
  margin: 6px 0 0; font-size: 12px; color: var(--ink-muted, #475569);
}

/* The row became a <button>, so the browser's button defaults have to be
   unwound before the flex layout above applies to it. */
.eng-receipt-row.is-tappable {
  width: 100%; text-align: inherit; font: inherit; color: inherit;
  background: none; border: 0; border-bottom: 1px solid var(--border, #E2E8F0);
  cursor: pointer; border-radius: 8px;
}
.eng-receipt-row.is-tappable:hover,
.eng-receipt-row.is-tappable:focus-visible {
  background: var(--surface-raised, #F8FAFC);
}
.eng-receipt-list li:last-child .eng-receipt-row.is-tappable { border-bottom: 0; }

/* Read-only detail. Portalled visually by fixed positioning rather than by a
   DOM portal — the receipts page is not inside an overflow-hidden shell the
   way the wizard is, so a fixed layer is enough. */
.eng-receipt-detail-backdrop {
  position: fixed; inset: 0; z-index: 60;
  background: rgb(var(--scrim-rgb) / .55);
  display: flex; align-items: center; justify-content: center;
  padding: 18px; overflow-y: auto;
}
.eng-receipt-detail {
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border, #E2E8F0);
  border-radius: 14px; padding: 16px;
  width: 100%; max-width: 520px; max-height: 92vh; overflow-y: auto;
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.28);
}
.eng-receipt-detail-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-bottom: 12px;
}
.eng-receipt-detail-head h3 {
  margin: 0; font-size: 16px; font-weight: 700; color: var(--ink, #0F172A);
}
/* Large, as asked: the photo is the point of the detail view. */
.eng-receipt-detail-img,
.eng-receipt-detail .photo-missing {
  width: 100%; max-height: 46vh; min-height: 160px;
  object-fit: contain; border-radius: 10px;
  background: var(--surface-sunken, #F1F5F9);
  border: 1px solid var(--border, #E2E8F0);
}
.eng-receipt-detail-fields {
  margin: 14px 0 0; display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px;
}
.eng-receipt-detail-fields > div { min-width: 0; }
.eng-receipt-detail-wide { grid-column: 1 / -1; }
.eng-receipt-detail-fields dt {
  font-size: 11.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.03em; color: var(--ink-muted, #475569); margin-bottom: 3px;
}
.eng-receipt-detail-fields dd {
  margin: 0; font-size: 14px; color: var(--ink, #0F172A); word-break: break-word;
}
.eng-receipt-detail-lock {
  margin: 14px 0 0; padding-top: 12px;
  border-top: 1px solid var(--border, #E2E8F0);
  display: flex; align-items: center; gap: 7px;
  font-size: 12.5px; color: var(--ink-muted, #475569);
}
@media (max-width: 560px) {
  .eng-receipt-detail-fields { grid-template-columns: 1fr; }
  .eng-receipt-detail { padding: 14px; }
}

/* Documents filed per project, in real sections (Batch 11 item 5). */
.docsec {
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border, #E2E8F0);
  border-radius: 12px;
  padding: 16px;
  margin-top: 16px;
}
.docsec-head { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; justify-content: space-between; }
/* .docsec-picker (the second project dropdown) was deleted in Addendum 2 #18 —
   the binder cards are the only picker now, so its rules go with it. */
/* ── Batch 14 item 8 (#50): the binder, in colour ────────────────────────
   The cabinet was mono ink on white and read as plainer than the coloured
   register above it. Each section owns a hue on BOTH its icon and its title,
   so a drawer is findable by eye. Every colour is a THEME TOKEN — the same
   discipline as #45 — so the palette re-derives in Dark and Black instead of
   being hard-coded hex that disappears on a black page.

   SEVEN tones — one per drawer, as asked, not four shared between pairs.
   An earlier pass grouped them by what the paperwork DOES (technical /
   authority / commercial), which read as three pairs of identical chips:
   Permits and Deeds were the same green, Contracts and Insurance the same
   bronze. The families survive as neighbouring hues, so the binder still
   groups by eye, but no two drawers now wear the same colour:

     drawings   blue     technical record
     reports    indigo   technical record, one step off Drawings
     permits    green    authority
     deeds      teal     authority, one step off Permits
     contracts  bronze   commercial
     insurance  violet   commercial, one step off Contracts
     other      slate    unclassified, deliberately quiet

   Declared as local variables rather than through the bronze tokens, because
   those are only half-overridden: the dark block lifts --arbah-bronze-700 and
   --arbah-bronze-100 but not -500, and --arbah-bronze-soft is not defined in
   ANY appearance — so the old rule fell through to its literal #F5EDE0 and
   the Contracts chip stayed light beige on a black page. Same disease as #45
   and #48. Each appearance below states its own values outright. */
.av3 .docsec-ico {
  flex: 0 0 30px; width: 30px; height: 30px; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--sec-soft); color: var(--sec-ink);
}
.av3 .docsec-section .docsec-title { color: var(--sec-ink); font-weight: 650; }

/* Light. */
.av3 .docsec-section.tone-drawings,
.av3 .docsec-hitwrap.tone-drawings  { --sec-ink: #1E40AF; --sec-soft: #DBEAFE; }
.av3 .docsec-section.tone-reports,
.av3 .docsec-hitwrap.tone-reports   { --sec-ink: #4338CA; --sec-soft: #E0E7FF; }
.av3 .docsec-section.tone-permits,
.av3 .docsec-hitwrap.tone-permits   { --sec-ink: #047857; --sec-soft: #D1FAE5; }
.av3 .docsec-section.tone-deeds,
.av3 .docsec-hitwrap.tone-deeds     { --sec-ink: #0E7490; --sec-soft: #CFFAFE; }
.av3 .docsec-section.tone-contracts,
.av3 .docsec-hitwrap.tone-contracts { --sec-ink: #A06A2C; --sec-soft: #F5EDE0; }
/* Batch 27 A1 — Receipts. Orange, pushed clear of the bronze Contracts it sits
   beside: two adjacent drawers in the same hue defeat the point of colouring
   them at all, which is the note above this block. */
.av3 .docsec-section.tone-receipts,
.av3 .docsec-hitwrap.tone-receipts  { --sec-ink: #C2410C; --sec-soft: #FFEDD5; }
.av3 .docsec-section.tone-insurance,
.av3 .docsec-hitwrap.tone-insurance { --sec-ink: #6D28D9; --sec-soft: #EDE9FE; }
.av3 .docsec-section.tone-other,
.av3 .docsec-hitwrap.tone-other     { --sec-ink: #475569; --sec-soft: #F1F5F9; }

/* Dark — lifted inks, sunk grounds. Same seven hues, same order. */
[data-color-scheme="dark"] .av3 .docsec-section.tone-drawings,
[data-color-scheme="dark"] .av3 .docsec-hitwrap.tone-drawings  { --sec-ink: #93B4DC; --sec-soft: #1C315A; }
[data-color-scheme="dark"] .av3 .docsec-section.tone-reports,
[data-color-scheme="dark"] .av3 .docsec-hitwrap.tone-reports   { --sec-ink: #A5B4FC; --sec-soft: #262C63; }
[data-color-scheme="dark"] .av3 .docsec-section.tone-permits,
[data-color-scheme="dark"] .av3 .docsec-hitwrap.tone-permits   { --sec-ink: #34D399; --sec-soft: #123D35; }
[data-color-scheme="dark"] .av3 .docsec-section.tone-deeds,
[data-color-scheme="dark"] .av3 .docsec-hitwrap.tone-deeds     { --sec-ink: #5EEAD4; --sec-soft: #0F3D42; }
[data-color-scheme="dark"] .av3 .docsec-section.tone-contracts,
[data-color-scheme="dark"] .av3 .docsec-hitwrap.tone-contracts { --sec-ink: #D4B36A; --sec-soft: #3A2E16; }
[data-color-scheme="dark"] .av3 .docsec-section.tone-receipts,
[data-color-scheme="dark"] .av3 .docsec-hitwrap.tone-receipts  { --sec-ink: #FDBA74; --sec-soft: #43230F; }
[data-color-scheme="dark"] .av3 .docsec-section.tone-insurance,
[data-color-scheme="dark"] .av3 .docsec-hitwrap.tone-insurance { --sec-ink: #C4B5FD; --sec-soft: #2E2258; }
[data-color-scheme="dark"] .av3 .docsec-section.tone-other,
[data-color-scheme="dark"] .av3 .docsec-hitwrap.tone-other     { --sec-ink: #CBD5E1; --sec-soft: #16202E; }

/* Black — grounds go near-black so a chip never glows off an OLED page. */
[data-appearance="black"] .av3 .docsec-section.tone-drawings,
[data-appearance="black"] .av3 .docsec-hitwrap.tone-drawings  { --sec-ink: #8FB3DB; --sec-soft: #111E2E; }
[data-appearance="black"] .av3 .docsec-section.tone-reports,
[data-appearance="black"] .av3 .docsec-hitwrap.tone-reports   { --sec-ink: #A5B4FC; --sec-soft: #16172E; }
[data-appearance="black"] .av3 .docsec-section.tone-permits,
[data-appearance="black"] .av3 .docsec-hitwrap.tone-permits   { --sec-ink: #34D399; --sec-soft: #0C2A22; }
[data-appearance="black"] .av3 .docsec-section.tone-deeds,
[data-appearance="black"] .av3 .docsec-hitwrap.tone-deeds     { --sec-ink: #5EEAD4; --sec-soft: #08262A; }
[data-appearance="black"] .av3 .docsec-section.tone-contracts,
[data-appearance="black"] .av3 .docsec-hitwrap.tone-contracts { --sec-ink: #D4B36A; --sec-soft: #2A2314; }
[data-appearance="black"] .av3 .docsec-section.tone-receipts,
[data-appearance="black"] .av3 .docsec-hitwrap.tone-receipts  { --sec-ink: #FDBA74; --sec-soft: #2B1608; }
[data-appearance="black"] .av3 .docsec-section.tone-insurance,
[data-appearance="black"] .av3 .docsec-hitwrap.tone-insurance { --sec-ink: #C4B5FD; --sec-soft: #1C1533; }
[data-appearance="black"] .av3 .docsec-section.tone-other,
[data-appearance="black"] .av3 .docsec-hitwrap.tone-other     { --sec-ink: #CBD5E1; --sec-soft: #0E0E11; }

/* An empty drawer keeps its colour but steps back, so "nothing filed yet"
   still reads as that section rather than as a broken row. */
.av3 .docsec-section.is-empty .docsec-ico { opacity: .55; }

/* The Drawings sub-sections get their own ramp — one discipline, one swatch,
   so Architectural and Structural are tellable apart at a glance. */
.av3 .docsec-subtitle { display: flex; align-items: center; gap: 8px; }
.av3 .docsec-swatch {
  flex: 0 0 auto; width: 8px; height: 8px; border-radius: 3px;
  background: var(--disc-ink, var(--arbah-ink-subtle));
}
.av3 .docsec-sub .docsec-subtitle { color: var(--disc-ink, var(--arbah-ink)); }

/* Light. Trade colours where the trade has one — electrical amber, civil
   red-earth, plumbing green — so a site engineer reads the ramp without a
   legend. Stated outright for the same reason as the section tones above:
   the bronze token this used has no dark value, so Mechanical stayed a light
   brown on a black page. */
.av3 .docsec-sub.disc-architectural { --disc-ink: #1E40AF; }
.av3 .docsec-sub.disc-structural    { --disc-ink: #2C4A6E; }
.av3 .docsec-sub.disc-electrical    { --disc-ink: #D97706; }
.av3 .docsec-sub.disc-mechanical    { --disc-ink: #A06A2C; }
.av3 .docsec-sub.disc-plumbing      { --disc-ink: #047857; }
.av3 .docsec-sub.disc-civil         { --disc-ink: #B91C1C; }
.av3 .docsec-sub.disc-general       { --disc-ink: #475569; }

/* Dark and Black share one lifted ramp: the swatch is 8px, so it needs the
   contrast more than the section chip does. */
[data-color-scheme="dark"] .av3 .docsec-sub.disc-architectural { --disc-ink: #93B4DC; }
[data-color-scheme="dark"] .av3 .docsec-sub.disc-structural    { --disc-ink: #6F91B8; }
[data-color-scheme="dark"] .av3 .docsec-sub.disc-electrical    { --disc-ink: #FBBF24; }
[data-color-scheme="dark"] .av3 .docsec-sub.disc-mechanical    { --disc-ink: #D4B36A; }
[data-color-scheme="dark"] .av3 .docsec-sub.disc-plumbing      { --disc-ink: #34D399; }
[data-color-scheme="dark"] .av3 .docsec-sub.disc-civil         { --disc-ink: #F87171; }
[data-color-scheme="dark"] .av3 .docsec-sub.disc-general       { --disc-ink: #CBD5E1; }

.docsec-hint { margin: 16px 0 0; font-size: 13px; color: var(--ink-muted, #475569); }
.docsec-list { list-style: none; margin: 14px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.docsec-section { border: 1px solid var(--border, #E2E8F0); border-radius: 10px; overflow: hidden; }
.docsec-section.is-empty { opacity: .72; }
.docsec-toggle {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 12px 13px; border: 0; cursor: pointer;
  background: var(--surface-sunken, #F6F8FB); color: var(--ink, #0F172A);
  font: inherit; font-size: 13.5px; text-align: start;
}
.docsec-toggle:hover { background: var(--surface-raised, #F1F5F9); }
.docsec-title { font-weight: 700; }
.docsec-count { margin-inline-start: auto; font-size: 11.5px; font-weight: 600; color: var(--ink-muted, #475569); }
.docsec-body { padding: 10px 13px 12px; }
.docsec-note { margin: 0 0 8px; font-size: 12px; color: var(--ink-muted, #475569); }
.docsec-sub + .docsec-sub { margin-top: 12px; }
.docsec-subtitle {
  display: flex; align-items: center; gap: 8px; margin: 0 0 6px;
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-muted, #475569);
}
/* ── Project activity feed (Batch 16, #72) ─────────────────────────────
   The same sentence shape as the dashboard feed: WHO did WHAT to WHICH thing
   WHEN, and the whole row is the button. */
.av3 .pd-feed { display: flex; flex-direction: column; }
.av3 .pd-activity-row {
  display: flex; align-items: flex-start; gap: 11px; width: 100%;
  padding: 11px 18px; text-align: start; cursor: pointer;
  border: 0; border-bottom: 1px solid var(--arbah-border, #E2E8F0);
  background: none; color: inherit; font: inherit;
  transition: background-color .12s ease;
}
.av3 .pd-feed .pd-activity-row:last-child { border-bottom: 0; }
.av3 .pd-activity-row:hover { background: var(--arbah-surface-sunken, #F6F8FB); }
.av3 .pd-activity-row:focus-visible { outline: 2px solid var(--arbah-info, #2563EB); outline-offset: -2px; }
.av3 .pd-act-body { flex: 1; min-width: 0; }
.av3 .pd-act-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px; font-size: 13px; }
.av3 .pd-act-who { font-weight: 700; color: var(--arbah-ink, #0F172A); }
.av3 .pd-act-who.needs-data { font-weight: 500; }
.av3 .pd-act-verb { color: var(--arbah-ink-muted, #475569); }
.av3 .pd-act-subject { font-weight: 600; color: var(--arbah-ink, #0F172A); overflow-wrap: anywhere; }
.av3 .pd-act-when { flex: 0 0 auto; font-size: 11.5px; color: var(--arbah-ink-subtle, #94A3B8); }

/* ── Task details, read-only (Batch 16, #69) ───────────────────────────
   What the engineer gets instead of the Edit form: every field visible, none
   of it editable, no Save. */
.tdv { display: flex; flex-direction: column; gap: 16px; }
.tdv-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.tdv-eyebrow {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--arbah-ink-muted, #475569);
}
.tdv-title { margin: 2px 0 0; font-size: 18px; font-weight: 700; color: var(--arbah-ink, #0F172A); }
.tdv-close {
  flex: 0 0 auto; width: 32px; height: 32px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--arbah-border, #E2E8F0);
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink-muted, #475569);
  display: inline-flex; align-items: center; justify-content: center;
}
/* Explains the ABSENCE of a Save button, so it does not read as a failure. */
.tdv-note {
  margin: 0; padding: 9px 12px; border-radius: 9px;
  background: var(--arbah-surface-sunken, #F6F8FB);
  border-inline-start: 3px solid var(--arbah-info, #2563EB);
  font-size: 12.5px; line-height: 1.5; color: var(--arbah-ink-muted, #475569);
}
.tdv-fields { margin: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 18px; }
@media (max-width: 560px) { .tdv-fields { grid-template-columns: 1fr; } }
.tdv-row { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.tdv-label {
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--arbah-ink-muted, #475569);
}
.tdv-value { margin: 0; font-size: 13.5px; font-weight: 600; color: var(--arbah-ink, #0F172A); overflow-wrap: anywhere; }
.tdv-desc, .tdv-chain { display: flex; flex-direction: column; gap: 6px; }
.tdv-desctext { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--arbah-ink, #0F172A); overflow-wrap: anywhere; }
/* ── Batch 19 (#93): warmer, and the three buttons are the act of the screen ──
 *
 * Sultan: "more color, warmer, less flat gray", and the three buttons centred
 * and sized as the primary act.
 *
 * The fields and the trail were the same near-white as the modal behind them,
 * so the whole card read as one undifferentiated sheet. Each block now sits on
 * its own soft surface with a hairline, which is what gives a card rhythm
 * without adding a single new colour to the palette — every value below is an
 * existing token, so all four themes follow automatically and none of them
 * needs a special case.
 */
.tdv-fields,
.tdv-desc,
.tdv-chain {
  background: var(--arbah-surface-sunken, #F6F8FB);
  border: 1px solid var(--arbah-border, #E2E8F0);
  border-radius: 11px;
  padding: 12px 14px;
}
.tdv-desc, .tdv-chain { gap: 8px; }
/* The description is the one block a person reads as prose, so it gets the
   quoted-block treatment the rest of the app uses for quoted text. */
.tdv-desc { border-inline-start: 3px solid var(--arbah-border-strong, #CBD5E1); }

/* THE THREE BUTTONS. Centred, and sized as the thing the screen is FOR — a
   site engineer taps these with a thumb, in sunlight, wearing gloves. */
.tdv-actions {
  display: flex; flex-wrap: wrap; gap: 10px;
  justify-content: center;            /* #93: centre of the card */
  padding: 14px 0 2px;
  border-top: 1px solid var(--arbah-border, #E2E8F0);
}
/* A task that is not actionable by this viewer — already sent for approval, or
   approved — correctly offers NO buttons (see the #70 note in
   TaskLifecycle.js). Without this the wrapper still drew its rule and its
   padding, leaving a ruled empty band between the trail and the comments that
   read as a control row that had failed to load. */
.tdv-actions:empty { display: none; }
.tdv-actions .tt-act {
  min-height: 46px; padding: 12px 22px; font-size: 14px; border-radius: 10px;
  flex: 1 1 0; min-width: 120px; max-width: 220px; justify-content: center;
}
/* One button alone must not stretch across the whole card — that reads as a
   full-width bar, not as one of a set. */
.tdv-actions .tt-act:only-child { flex: 0 1 240px; }

/* Semantic colour, applied wherever these buttons appear (table rows included)
   so the meaning never depends on which screen you are looking at. The `is-on`
   marker still wins, because "this is the state you are already in" outranks
   the button's own colour. */
:is(.av3, .modal-panel) .tt-act--done {
  background: var(--arbah-success, #047857); color: #fff; border: 1px solid transparent;
  box-shadow: 0 1px 2px rgba(4, 120, 87, .25);
}
:is(.av3, .modal-panel) .tt-act--done:hover { background: #036348; }
:is(.av3, .modal-panel) .tt-act--stuck {
  background: var(--arbah-warning-soft, #FEF6E7); color: var(--arbah-warning, #B45309);
  border: 1px solid #F0D69A;
}
:is(.av3, .modal-panel) .tt-act--stuck:hover { background: #FBEBCE; }
:is(.av3, .modal-panel) .tt-act--go {
  background: var(--arbah-info, #2563EB); color: #fff; border: 1px solid transparent;
  box-shadow: 0 1px 2px rgba(37, 99, 235, .25);
}
:is(.av3, .modal-panel) .tt-act--go:hover { background: #1D4FD8; }
/* The state the task is already IN stays marked rather than coloured — see the
   note in TaskLifecycle.js about hiding buttons making the set inconsistent. */
:is(.av3, .modal-panel) .tt-act.is-on { outline: 2px solid var(--arbah-navy-700, #102A43); outline-offset: 1px; }

.tdv-comments { border-top: 1px solid var(--arbah-border, #E2E8F0); padding-top: 14px; }

/* ── Tasks table density (Batch 16, #71) ───────────────────────────────
   "The task card/row occupies far more space than its content justifies, and
   the #b15-ta ID chip is oversized everywhere it appears."

   The row was tall because the lifecycle cell stacks a two-step trail over a
   row of buttons, and every other cell then inherited that height with its
   own generous padding. Tightening the cell padding and the trail's internal
   spacing fits noticeably more rows per screen without dropping anything. */
.av3 .dtable td { padding-block: 8px; }
:is(.av3, .modal-panel) .tt-cell { padding-block: 6px; }
:is(.av3, .modal-panel) .tt-trail { gap: 2px; margin-bottom: 5px; }
:is(.av3, .modal-panel) .tt-step { font-size: 11.5px; line-height: 1.35; }
:is(.av3, .modal-panel) .tt-step .tt-dot { margin-top: 4px; }
/* A stuck reason or a send-back objection can be a full sentence, and letting
   it wrap made some rows half again as tall as their neighbours. One line in
   the table — the whole text is in the details view, one click away. */
:is(.av3, .modal-panel) .tt-cell .tt-note {
  font-size: 11px; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
  overflow: hidden; max-width: 260px;
}
/* ...but never clamped inside the details view, where the point is to read it. */
.tdv .tt-note { display: block; overflow: visible; max-width: none; font-size: 11.5px; line-height: 1.45; }
:is(.av3, .modal-panel) .tt-actions { gap: 5px; }
.av3 .btn-sm { min-height: 28px; padding: 4px 10px; font-size: 12px; }
/* The button matching the state the task is already in — marked, not hidden,
   so the set of three never changes shape between rows (#70). */
.av3 .btn-sm.is-on {
  box-shadow: inset 0 0 0 2px var(--arbah-ink, #0F172A);
  font-weight: 700;
}
/* The ID: a quiet label, not a headline. It was rendering at the table's
   body size in mono, which made "#b15-ta" as loud as the task title. */
.av3 .dtable .dt-id {
  font-size: 10.5px; font-weight: 600; letter-spacing: -.01em;
  color: var(--arbah-ink-subtle, #94A3B8);
  background: var(--arbah-surface-sunken, #F1F5F9);
  border-radius: 5px; padding: 1px 5px;
}
.av3 .dtable th:first-child, .av3 .dtable td:first-child { width: 1%; }

/* ── Activity feed icons (Batch 16, #67) ────────────────────────────────
   The feed icons rendered colourless. Not a stripped class — the primitives
   only ever defined FOUR badge colours (task / photo / risk / approval), and
   Batch 15 added six more event types (report, document, money, safety,
   visit, message). Six of the ten types had no rule at all, so they fell
   through to the bare `.type-badge` default.
   (Checked against the Batch 15 icon-size finding: unrelated. That was a
   missing w-N class on the SVG; this is a missing colour on the badge.)

   Sultan's three pinned cases, plus a deliberate colour for everything else:
     approvals / done checks  GREEN
     scheduled site visits    BLUE
     warnings / attention     YELLOW

   TONE wins over type where the event has one, which is what makes the first
   rule work: an approved receipt and a finished task both carry tone `good`
   and both come out green, while the same money/task types stay in their own
   colour for every other event. */
.av3 .type-badge { color: #fff; }
/* Reports are INDIGO, not navy-700. Navy-700 resolves to #2E2E36 on the
   Black appearance — a near-black badge on a near-black card, and the daily
   report is the most common event in the feed by a distance, so the one that
   most needed to be visible was the one that vanished. Indigo is the same
   hue the Documents binder gives "Reports & Certificates", so the two
   surfaces speak the same colour language. */
.av3 .tb--report   { background: #4338CA; color: #fff; }
html[data-color-scheme="dark"] .av3 .tb--report { background: #A5B4FC; color: #0B1420; }
.av3 .tb--document { background: var(--arbah-info, #2563EB); color: #fff; }
.av3 .tb--money    { background: var(--arbah-bronze-500, #A06A2C); color: #fff; }
.av3 .tb--safety   { background: var(--arbah-warning, #B45309); color: #fff; }
.av3 .tb--visit    { background: var(--arbah-info, #2563EB); color: #fff; }
.av3 .tb--message  { background: var(--arbah-navy-500, #2C4A6E); color: #fff; }
/* Approvals are GREEN, not bronze. */
.av3 .tb--approval { background: var(--arbah-success, #047857); color: #fff; }
/* Warnings and attention posts are YELLOW. */
.av3 .tb--risk     { background: var(--arbah-warning, #B45309); color: #fff; }

/* Tone overrides — a done check or an approval is green whatever bucket it
   arrived in; a rejection or an incident is red; a submission awaiting
   someone is amber. */
.av3 .type-badge.tone-good { background: var(--arbah-success, #047857); color: #fff; }
.av3 .type-badge.tone-bad  { background: var(--arbah-danger, #B91C1C); color: #fff; }
.av3 .type-badge.tone-warn { background: var(--arbah-warning, #B45309); color: #fff; }
/* Black and Dark lighten the semantic tokens, so white ink on them drops to
   ~2:1. The badge takes a dark ink there, the same fix the Batch 9 chip sweep
   applied to every other filled semantic surface. */
html[data-color-scheme="dark"] .av3 .type-badge.tone-good,
html[data-color-scheme="dark"] .av3 .type-badge.tone-bad,
html[data-color-scheme="dark"] .av3 .type-badge.tone-warn,
html[data-color-scheme="dark"] .av3 .tb--approval,
html[data-color-scheme="dark"] .av3 .tb--safety,
html[data-color-scheme="dark"] .av3 .tb--money,
html[data-color-scheme="dark"] .av3 .tb--document,
html[data-color-scheme="dark"] .av3 .tb--visit { color: #0B1420; }

/* ── Build phases strip (Batch 16, #65 + #74) ───────────────────────────
   The grammar, from data/phase-state.js and nowhere else:
     DONE     green, and the marker is a CHECK MARK
     CURRENT  white / highlighted — the one in play, never green
     UPCOMING greyed out — no green anywhere on it

   The strip already carried the right STATE; what was wrong was the paint.
   Every upcoming phase showed a green dot because the Batch 15 task-lifecycle
   trail was written with the same `.tl-step` / `.tl-dot` class names as these
   primitives, and `.av3 .tl-step .tl-dot` (0,3,0) out-specified the
   primitive's own `.av3 .tl-dot` (0,2,0). The trail's classes are `tt-*` now;
   these rules make the remaining grammar explicit rather than relying on the
   primitive file's defaults. */
.av3 .timeline { position: relative; }
/* CURRENT: the highest-contrast thing on the strip — unmistakably the one in
   play. The ring is `--arbah-ink`, not navy: navy on the Black appearance is
   #0D2238 against a near-black card, so the "highlighted" phase came out the
   least visible of the three. Ink is near-black on light and near-white on
   dark, which is what "white / highlighted" has to mean in both. Never green. */
.av3 .tl-step.current .tl-bar {
  background: var(--arbah-surface, #FFF);
  box-shadow: inset 0 0 0 2px var(--arbah-ink, #0F172A);
}
.av3 .tl-step.current .tl-dot {
  background: var(--arbah-surface, #FFF);
  color: var(--arbah-ink, #0F172A);
  box-shadow: 0 0 0 2px var(--arbah-ink, #0F172A);
}
.av3 .tl-step.current .tl-name { font-weight: 700; color: var(--arbah-ink, #0F172A); }
.av3 .tl-step.current .tl-sub { color: var(--arbah-ink-muted, #475569); font-weight: 600; }
/* UPCOMING: an empty ring. No glyph inside — a dot in there only ever got
   mistaken for a state marker. */
.av3 .tl-step.upcoming .tl-bar { background: var(--arbah-surface-sunken, #EEF2F7); }
.av3 .tl-step.upcoming .tl-dot {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--arbah-border-strong, #CBD5E1);
}
.av3 .tl-step.upcoming .tl-name { color: var(--arbah-ink-subtle, #94A3B8); }
.av3 .tl-step.upcoming .tl-sub { color: var(--arbah-ink-subtle, #94A3B8); }
/* DONE keeps the primitive's green; the check is the marker. */
.av3 .tl-step.done .tl-dot > * { display: block; }

/* #74 — where the plan says the work should be by today, ON the strip.
   The marker rides INSIDE its own step (see the note in the surface), so it
   stays on the phase it names however far the strip is scrolled. The step
   gets top room for the tag. */
.av3 .timeline .tl-step { position: relative; }
/* The room for the tag is reserved on the WHOLE strip, not on the tagged
   step — padding on one step alone dropped that column 22px below its
   neighbours and the phase names stopped lining up. */
.av3 .timeline:has(.tl-step.is-planned) { padding-top: 24px; }
/* :has() is well supported in this build's target, but a strip that reserves
   no room would clip the tag rather than misalign, so the fallback is safe. */
@supports not selector(:has(*)) {
  .av3 .timeline { padding-top: 24px; }
}
.av3 .tl-planned {
  position: absolute; top: -24px; inset-inline-start: 50%;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center;
  pointer-events: none; z-index: 2;
}
[dir="rtl"] .av3 .tl-planned { transform: translateX(50%); }
/* Batch 17 (#86). The tag was danger-coloured text on the surface colour
   inside a thin danger border — correct, and quiet enough to scan past. It
   is a filled danger pill now, so the marker announces itself. */
.av3 .tl-planned-tag {
  white-space: nowrap;
  font-size: 10.5px; font-weight: 800; letter-spacing: .02em;
  color: #fff;
  background: var(--arbah-danger, #B91C1C);
  border: 1px solid var(--arbah-danger, #B91C1C);
  border-radius: 999px; padding: 1px 7px;
}
/* Dark and Black lighten --arbah-danger, so white ink on it falls to ~2:1 —
   the same dark-ink fix every other filled semantic surface takes. */
html[data-color-scheme="dark"] .av3 .tl-planned-tag { color: #0B1420; }
/* Batch 17 (#86). This was 2px wide, 8px tall and DASHED, which at a glance
   was a fleck rather than a line: "a tiny red tick that disappears into the
   bar". Solid, four times taller, with a halo so it reads over the phase
   colours as well as over the empty track. */
.av3 .tl-planned-line {
  width: 4px; height: 22px;
  border-radius: 2px;
  background: var(--arbah-danger, #B91C1C);
  box-shadow: 0 0 0 1.5px var(--arbah-surface, #FFF);
}
/* Stacked on a phone: the tag sits with the row rather than above it. */
@media (max-width: 720px) {
  .av3 .timeline .tl-step.is-planned { padding-top: 12px; }
  .av3 .tl-planned { inset-inline-start: auto; inset-inline-end: 8px; transform: none; }
  [dir="rtl"] .av3 .tl-planned { transform: none; }
  .av3 .tl-planned-line { display: none; }
}

/* Legends for both markers (#74 + #75) — "labeled or legended so nobody
   guesses". */
.av3 .tl-legend, .av3 .pbar-legend {
  display: flex; flex-wrap: wrap; gap: 14px; margin-top: 22px;
  font-size: 11.5px; color: var(--arbah-ink-muted, #475569);
}
.av3 .pbar-legend { margin-top: 8px; }
.av3 .tl-lg { display: inline-flex; align-items: center; gap: 6px; }
.av3 .tl-lg > i { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
.av3 .tl-lg-done { background: var(--arbah-success, #047857); }
.av3 .tl-lg-now {
  background: var(--arbah-surface, #FFF);
  box-shadow: inset 0 0 0 2px var(--arbah-ink, #0F172A);
}
.av3 .tl-lg-todo {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--arbah-border-strong, #CBD5E1);
}
.av3 .tl-lg-actual { background: var(--arbah-navy-700, #1E3A5F); }
/* Batch 17 (#86): the swatch has to look like the marker it explains, so it
   goes solid with the line. */
.av3 .tl-lg-planned {
  background: var(--arbah-danger, #B91C1C);
  border-radius: 2px;
  box-shadow: 0 0 0 1px var(--arbah-surface, #FFF);
}

/* #75 — the same red line on the header progress bar.
   Batch 17 (#86): it was a 2px hairline that overhung the bar by 3px, which
   on screen read as a smudge on the fill rather than as a line across it.
   Thicker, and tall enough to break the bar's outline top and bottom, so the
   eye finds it without being told where to look. The halo keeps it legible
   over the navy fill AND over the empty track in all four appearances. */
.av3 .pbar { position: relative; overflow: visible; }
.av3 .pbar-planned {
  position: absolute; top: -7px; bottom: -7px; width: 4px;
  transform: translateX(-2px);
  background: var(--arbah-danger, #B91C1C);
  border-radius: 2px; pointer-events: none;
  z-index: 3;
  box-shadow: 0 0 0 1.5px var(--arbah-surface, #FFF);
}
.av3 .pbar-planned::after {
  content: ''; position: absolute; inset-inline-start: -3px; top: -6px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--arbah-danger, #B91C1C);
  box-shadow: 0 0 0 1.5px var(--arbah-surface, #FFF);
}

/* ── Task lifecycle (Batch 15 item 14, #57) ─────────────────────────────
   The chain Sultan designed in Batch 11 and had never seen move. The trail
   is the evidence — especially the "Opened" step, which is the difference
   between "not looked at yet" and "looked at and ignored". */
/* The cell holds a two-step trail plus the buttons; without a cap it made
   every row about 180px tall and the table stopped being scannable. */
:is(.av3, .modal-panel) .tt-cell { min-width: 240px; max-width: 320px; padding-block: 8px; }
:is(.av3, .modal-panel) .tt-trail { list-style: none; margin: 0 0 6px; padding: 0; display: flex; flex-direction: column; gap: 3px; }
:is(.av3, .modal-panel) .tt-step { display: flex; align-items: flex-start; gap: 8px; font-size: 12px; }
:is(.av3, .modal-panel) .tt-step .tt-dot {
  flex: 0 0 8px; width: 8px; height: 8px; margin-top: 5px; border-radius: 50%;
  background: var(--arbah-success, #047857);
}
/* A pending step is hollow. "Not opened yet" has to look unfinished, because
   that is the whole information it carries. */
:is(.av3, .modal-panel) .tt-step.is-pending .tt-dot {
  background: none; box-shadow: inset 0 0 0 1.5px var(--arbah-ink-subtle, #94A3B8);
}
:is(.av3, .modal-panel) .tt-step.is-pending .tt-label { color: var(--arbah-ink-subtle, #94A3B8); font-style: italic; }
:is(.av3, .modal-panel) .tt-step.tone-bad .tt-dot { background: var(--arbah-danger, #B91C1C); }
:is(.av3, .modal-panel) .tt-step.tone-warn .tt-dot { background: var(--arbah-warning, #B45309); }
:is(.av3, .modal-panel) .tt-step.tone-good .tt-dot { background: var(--arbah-success, #047857); }

/* ── Batch 19 (#93): the trail dot became an icon chip ────────────────────
 * Six identical grey dots made six different events look like one repeated
 * event. The dot is now a soft tinted disc holding the step's own glyph, so
 * "opened", "stuck" and "approved" are told apart before the label is read.
 * The tint is the dot's existing colour at low opacity — no new palette
 * entries, so every theme follows without a special case. */
:is(.av3, .modal-panel) .tt-step .tt-dot {
  flex: 0 0 20px; width: 20px; height: 20px; margin-top: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--arbah-success-soft, #E8F6F0);
  color: var(--arbah-success, #047857);
}
:is(.av3, .modal-panel) .tt-step .tt-dot svg { width: 12px; height: 12px; }
:is(.av3, .modal-panel) .tt-step.is-pending .tt-dot {
  background: var(--arbah-surface-raised, #F1F5F9);
  color: var(--arbah-ink-subtle, #94A3B8);
  box-shadow: inset 0 0 0 1px var(--arbah-border-strong, #CBD5E1);
}
:is(.av3, .modal-panel) .tt-step.tone-bad .tt-dot {
  background: var(--arbah-danger-soft, #FDECEC); color: var(--arbah-danger, #B91C1C);
}
:is(.av3, .modal-panel) .tt-step.tone-warn .tt-dot {
  background: var(--arbah-warning-soft, #FEF6E7); color: var(--arbah-warning, #B45309);
}
:is(.av3, .modal-panel) .tt-step.tone-good .tt-dot {
  background: var(--arbah-success-soft, #E8F6F0); color: var(--arbah-success, #047857);
}
/* In the TABLE the trail is a two-line summary, not the record — the icon chip
   would cost a third of the row's width there, so it stays a plain small disc. */
:is(.av3, .modal-panel) .tt-cell .tt-step .tt-dot {
  flex-basis: 8px; width: 8px; height: 8px; margin-top: 5px; box-shadow: none;
}
:is(.av3, .modal-panel) .tt-cell .tt-step .tt-dot svg { display: none; }
:is(.av3, .modal-panel) .tt-cell .tt-step .tt-dot { background: var(--arbah-success, #047857); }
:is(.av3, .modal-panel) .tt-cell .tt-step.is-pending .tt-dot {
  background: none; box-shadow: inset 0 0 0 1.5px var(--arbah-ink-subtle, #94A3B8);
}
:is(.av3, .modal-panel) .tt-cell .tt-step.tone-bad .tt-dot { background: var(--arbah-danger, #B91C1C); }
:is(.av3, .modal-panel) .tt-cell .tt-step.tone-warn .tt-dot { background: var(--arbah-warning, #B45309); }
:is(.av3, .modal-panel) .tt-cell .tt-step.tone-good .tt-dot { background: var(--arbah-success, #047857); }
:is(.av3, .modal-panel) .tt-body { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; min-width: 0; }
:is(.av3, .modal-panel) .tt-label { font-weight: 600; color: var(--arbah-ink, #0F172A); }
:is(.av3, .modal-panel) .tt-when { font-size: 11px; color: var(--arbah-ink-subtle, #94A3B8); }
:is(.av3, .modal-panel) .tt-who { font-size: 11px; color: var(--arbah-ink-muted, #475569); }
:is(.av3, .modal-panel) .tt-note {
  flex-basis: 100%; font-size: 11.5px; line-height: 1.4;
  color: var(--arbah-ink-muted, #475569); overflow-wrap: anywhere;
}
:is(.av3, .modal-panel) .tt-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.av3 .btn-sm { min-height: 32px; padding: 5px 11px; font-size: 12.5px; }
:is(.av3, .modal-panel) .tt-ask { display: flex; flex-direction: column; gap: 6px; min-width: 220px; }
:is(.av3, .modal-panel) .tt-asklabel { font-size: 11.5px; font-weight: 700; color: var(--arbah-ink-muted, #475569); }
:is(.av3, .modal-panel) .tt-askinput {
  min-height: 38px; padding: 7px 10px;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 8px;
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink, #0F172A);
  font: inherit; font-size: 13px;
}
:is(.av3, .modal-panel) .tt-askrow { display: flex; gap: 6px; justify-content: flex-end; }
:is(.av3, .modal-panel) .tt-err { margin: 0; font-size: 11.5px; font-weight: 600; color: var(--arbah-danger, #B91C1C); }

/* ── Report period picker (Batch 15 item 13.6, #63) ─────────────────────
   "You're making it seem like somebody wants to click only one date — that
   is not how a real app does it." Seven presets plus custom, and Today is
   still the first chip so the common case stays one tap. */
.av3 .rrp {
  margin: 12px 0 14px; padding: 12px;
  border: 1px solid var(--arbah-border, #E2E8F0); border-radius: 11px;
  background: var(--arbah-surface-sunken, #F8FAFC);
}
.av3 .rrp-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 9px; }
.av3 .rrp-label {
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--arbah-ink-muted, #475569);
}
.av3 .rrp-count { font-size: 12px; color: var(--arbah-ink-subtle, #94A3B8); }
.av3 .rrp-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.av3 .rrp-chip {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 36px; padding: 6px 12px; cursor: pointer;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 999px;
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink, #0F172A);
  font: inherit; font-size: 12.5px;
}
.av3 .rrp-chip:hover { border-color: var(--arbah-ink-subtle, #94A3B8); }
.av3 .rrp-chip.on {
  background: var(--arbah-navy-700, #1E3A5F); color: #FFF;
  border-color: var(--arbah-navy-700, #1E3A5F); font-weight: 600;
}
.av3 .rrp-custom { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 10px; }
.av3 .rrp-custom label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11.5px; color: var(--arbah-ink-muted, #475569);
}
.av3 .rrp-custom input {
  min-height: 38px; padding: 7px 10px;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 8px;
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink, #0F172A);
  font: inherit; font-size: 13px;
}
.av3 .rrp-invalid { margin: 8px 0 0; font-size: 12.5px; font-weight: 600; color: var(--arbah-danger, #B91C1C); }

/* The period summary that replaces the single-day brief on a span. */
.av3 .ps-kpis { margin-bottom: 16px; }
.av3 .ps-listtitle {
  margin: 4px 0 8px; font-size: 11px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
  color: var(--arbah-ink-muted, #475569);
}
.av3 .ps-list { list-style: none; margin: 0; padding: 0; }
.av3 .ps-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 46px; padding: 8px 10px; cursor: pointer; text-align: start;
  border: 0; border-bottom: 1px solid var(--arbah-border, #E2E8F0);
  border-radius: 8px; background: none; color: inherit; font: inherit;
}
.av3 .ps-row:hover { background: var(--arbah-surface-sunken, #F6F8FB); }
.av3 .ps-date { flex: 0 0 auto; font-size: 13px; font-weight: 600; }
.av3 .ps-pct { flex: 1; font-size: 13px; color: var(--arbah-ink-muted, #475569); }

/* ── Daily reports list (Batch 15 item 13, #63) ─────────────────────────
   The list had no column headers at all — "nothing on top of it says name,
   or date, or what the hell that is" — and 6px of padding on every row. */
.dsr-v16-table thead th {
  padding: 9px 10px;
  text-align: start;
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--arbah-ink-muted, #475569);
  border-bottom: 2px solid var(--arbah-border-strong, #CBD5E1);
  white-space: nowrap;
}
.dsr-v16-table thead th.th-actions { text-align: end; }
/* Real rhythm: rows are 12px tall inside, alternate quietly, and light up on
   hover so the eye can hold a line across four columns. */
.dsr-v16-table tbody td {
  padding: 12px 10px;
  border-bottom: 1px solid var(--arbah-border, #E2E8F0);
  font-size: 13.5px;
  vertical-align: middle;
}
.dsr-v16-table tbody td.td-actions { text-align: end; }
.dsr-v16-table tbody tr:nth-child(even) { background: var(--arbah-surface-sunken, #F8FAFC); }
.dsr-v16-table tbody tr:hover { background: var(--arbah-surface-raised, #F1F5F9); }
.dsr-v16-table tbody tr:last-child td { border-bottom: 0; }

.dsr-filters {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px;
  margin: 14px 0 10px; padding: 12px;
  border: 1px solid var(--arbah-border, #E2E8F0); border-radius: 10px;
  background: var(--arbah-surface-sunken, #F8FAFC);
}
.dsr-filter { display: flex; flex-direction: column; gap: 4px; font-size: 12px; }
.dsr-filter > span { font-weight: 700; color: var(--arbah-ink-muted, #475569); }
.dsr-filter select,
/* Batch 18 (#91a) — the find box shares the selects' metrics exactly, so the
   filter bar stays one row of controls rather than three mismatched heights. */
.dsr-filter input[type="search"] {
  min-height: 38px; padding: 7px 10px; min-width: 150px;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 8px;
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink, #0F172A);
  font: inherit; font-size: 13px;
}
.dsr-filter input[type="search"] { min-width: 230px; }

/* The document reference. Monospace and tabular so a column of numbers lines up
   digit under digit — which is what makes one wrong date findable by eye. */
.dsr-v16-table tbody td.td-number {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 12.5px; font-weight: 600; white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--arbah-ink, #0F172A);
}

.dsr-filter-count {
  margin-inline-start: auto; font-size: 12px;
  color: var(--arbah-ink-muted, #475569);
  font-family: var(--font-mono, ui-monospace, monospace);
}
.dsr-more {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; min-height: 42px; margin-top: 4px; cursor: pointer;
  border: 1px dashed var(--arbah-border-strong, #CBD5E1); border-radius: 9px;
  background: none; color: var(--arbah-ink-muted, #475569);
  font: inherit; font-size: 13px; font-weight: 600;
}
.dsr-more:hover { background: var(--arbah-surface-sunken, #F8FAFC); color: var(--arbah-ink, #0F172A); }
.dsr-less { margin-top: 6px; border-style: solid; }

/* ── Project fact sheet (Batch 15 item 12, #51) ─────────────────────────
   Seven groups, and a field with no value STAYS in the list saying "not
   recorded". Hiding empty rows would make a half-filled project look
   complete, which is the opposite of what a fact sheet is for. */
.av3 .fs-groups { display: flex; flex-direction: column; gap: 20px; }
.av3 .fs-group + .fs-group { border-top: 1px solid var(--arbah-border, #E2E8F0); padding-top: 18px; }
.av3 .fs-gtitle {
  display: flex; align-items: center; gap: 8px; margin: 0 0 10px;
  font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--arbah-ink-muted, #475569);
}
.av3 .fs-gico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 7px;
  background: var(--arbah-surface-sunken, #F1F5F9);
  color: var(--arbah-ink-muted, #475569);
}
.av3 .fs-rows { margin: 0; display: flex; flex-direction: column; gap: 1px; }
.av3 .fs-row {
  display: grid; grid-template-columns: minmax(120px, 40%) 1fr;
  gap: 12px; align-items: baseline;
  padding: 7px 8px; border-radius: 7px;
}
.av3 .fs-row:nth-child(odd) { background: var(--arbah-surface-sunken, #F8FAFC); }
.av3 .fs-label { margin: 0; font-size: 12.5px; color: var(--arbah-ink-muted, #475569); }
.av3 .fs-value {
  margin: 0; font-size: 13.5px; font-weight: 600; color: var(--arbah-ink, #0F172A);
  text-align: end; overflow-wrap: anywhere;
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
}
.av3 .fs-value a { color: var(--arbah-info, #2563EB); text-decoration: underline; }
.av3 .fs-value.tone-bad { color: var(--arbah-danger, #B91C1C); }
.av3 .fs-value.tone-warn { color: var(--arbah-warning, #B45309); }
.av3 .fs-row.is-missing .fs-value { font-weight: 400; }
.av3 .fs-note { font-size: 11px; font-weight: 600; }
.av3 .fs-note.tone-warn { color: var(--arbah-warning, #B45309); }
.av3 .fs-note.tone-bad { color: var(--arbah-danger, #B91C1C); }
@media (max-width: 520px) {
  .av3 .fs-row { grid-template-columns: 1fr; gap: 2px; }
  .av3 .fs-value { text-align: start; align-items: flex-start; }
}

/* ── Phase dates (Batch 15 item 11, #54) ────────────────────────────────
   Every phase now shows the range it is planned to run. The name and the
   dates stack so a long phase name does not push the range off the row. */
.av3 .phaseplan-namewrap {
  display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1;
}
.av3 .phaseplan-dates {
  font-size: 11px; color: var(--arbah-ink-subtle, #94A3B8);
  white-space: nowrap;
}
@media (max-width: 560px) { .av3 .phaseplan-dates { font-size: 10.5px; } }

/* The "Review attention" call to action (#59) — it names where it goes and
   why, so it is checkable before you press it. */
.av3 .attn-cta { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.av3 .attn-why {
  margin: 0; font-size: 12px; line-height: 1.4;
  color: var(--arbah-ink-muted, #475569); text-align: end; max-width: 340px;
}

/* ── Recent activity (Batch 15 item 7, #64) ─────────────────────────────
   Every row is a BUTTON now, because every row opens the thing it describes.
   The reset keeps the row looking exactly like the div it replaced. */
.av3 .act-row {
  width: 100%; text-align: start; cursor: pointer;
  border: 0; border-radius: 10px; background: none; font: inherit;
  color: inherit; transition: background-color .12s ease;
}
.av3 .act-row:hover { background: var(--arbah-surface-sunken, #F6F8FB); }
.av3 .act-row:focus-visible {
  outline: 2px solid var(--arbah-info, #2563EB); outline-offset: -2px;
}
/* T16 (item 7). A feed row whose target this role cannot open is a plain
   row: same sentence, no pointer, no hover lift — it must not look like a
   door onto "You don't have access". Same for a triage chip. */
.av3 .act-row--static, .av3 .act-row--static:hover { cursor: default; background: none; }
.av3 .tchip--static, .av3 .tchip--static:hover { cursor: default; transform: none; box-shadow: none; }
/* WHO did WHAT on WHICH project — one readable sentence, not a bare noun. */
.av3 .act-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px; }
.av3 .act-who { font-weight: 700; color: var(--arbah-ink, #0F172A); }
.av3 .act-who.needs-data { font-weight: 500; }
.av3 .act-verb { color: var(--arbah-ink-muted, #475569); }
.av3 .act-row .target { font-weight: 600; color: var(--arbah-ink, #0F172A); }
.av3 .act-row .a-detail {
  color: var(--arbah-ink-muted, #475569); font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.av3 .act-avatar .av {
  display: inline-flex; align-items: center; justify-content: center;
  color: #FFF; font-size: 11px; font-weight: 700;
  font-family: var(--font-mono, ui-monospace, monospace);
}
.av3 .act-time.tone-bad { color: var(--arbah-danger, #B91C1C); }
.av3 .act-time.tone-good { color: var(--arbah-success, #047857); }
.av3 .act-time.tone-warn { color: var(--arbah-warning, #B45309); }
/* The chip row can hold eleven types now, so it scrolls rather than wrapping
   into three ragged lines. */
.av3 .activity-head .tabs { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: thin; }
.av3 .activity-head .tabs::-webkit-scrollbar { height: 6px; }
.av3 .activity-head .tabs .tab { flex: 0 0 auto; }

/* ── Project group chat (Batch 15 item 5) ───────────────────────────────
   One group per project, everyone on the project in it. It has to read like
   the messaging apps a site engineer already uses, or it will not be used:
   grouped bursts, a name once per burst, day separators, newest at the
   bottom, Enter to send. */
.av3 .gchat { display: flex; flex-direction: column; }
.av3 .gchat-list {
  flex: 1; min-height: 180px; max-height: 420px; overflow-y: auto;
  padding: 12px 14px; display: flex; flex-direction: column; gap: 12px;
  background: var(--arbah-surface-sunken, #F6F8FB);
}
.av3 .gchat.is-compact .gchat-list { max-height: 300px; }
.av3 .gchat-day {
  display: flex; align-items: center; justify-content: center;
  margin: 4px 0 2px;
}
.av3 .gchat-day span {
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--arbah-ink-muted, #475569);
  background: var(--arbah-surface, #FFF);
  border: 1px solid var(--arbah-border, #E2E8F0);
  border-radius: 999px; padding: 3px 10px;
}
.av3 .gchat-grp { display: flex; gap: 10px; align-items: flex-start; }
.av3 .gchat-avatar {
  flex: 0 0 30px; width: 30px; height: 30px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; color: #FFF;
  font-family: var(--font-mono, ui-monospace, monospace);
}
.av3 .gchat-body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 3px; }
.av3 .gchat-who {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 12.5px; color: var(--arbah-ink, #0F172A);
}
.av3 .gchat-time { font-size: 11px; color: var(--arbah-ink-subtle, #94A3B8); font-family: var(--font-mono, monospace); }
.av3 .gchat-bubble {
  align-self: flex-start; max-width: 88%;
  padding: 7px 11px; border-radius: 12px;
  border-start-start-radius: 4px;
  background: var(--arbah-surface, #FFF);
  border: 1px solid var(--arbah-border, #E2E8F0);
  font-size: 13.5px; line-height: 1.45; color: var(--arbah-ink, #0F172A);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
/* Your own messages lean the other way and take the brand tint, so a glance
   tells you which side of the conversation you are on.
   `justify-content: flex-end` on the reversed row is what keeps the avatar
   INSIDE the panel — row-reverse alone let it hang off the trailing edge and
   the first render clipped it in half. */
.av3 .gchat-grp.is-mine { flex-direction: row-reverse; justify-content: flex-end; }
.av3 .gchat-grp.is-mine .gchat-who { flex-direction: row-reverse; }
.av3 .gchat-grp.is-mine .gchat-body { align-items: flex-end; }
.av3 .gchat-grp.is-mine .gchat-bubble {
  align-self: flex-end;
  background: var(--arbah-info-soft, #DBEAFE);
  border-color: color-mix(in srgb, var(--arbah-info, #2563EB) 30%, transparent);
  border-start-start-radius: 12px; border-start-end-radius: 4px;
}
.av3 .gchat-empty { text-align: center; padding: 22px 8px; }
.av3 .gchat-note {
  margin: 0; padding: 8px 14px;
  font-size: 11.5px; color: var(--arbah-ink-subtle, #94A3B8);
}
.av3 .gchat-readonly { font-style: italic; }
.av3 .gchat-error {
  margin: 0; padding: 8px 14px;
  font-size: 12.5px; font-weight: 600; color: var(--arbah-danger, #B91C1C);
}
.av3 .gchat-compose {
  display: flex; gap: 8px; align-items: flex-end;
  padding: 10px 12px;
  border-top: 1px solid var(--arbah-border, #E2E8F0);
  background: var(--arbah-surface, #FFF);
}
.av3 .gchat-input {
  flex: 1; min-height: 42px; max-height: 120px; resize: vertical;
  padding: 10px 12px;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 10px;
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink, #0F172A);
  font: inherit; font-size: 14px; line-height: 1.4;
}
.av3 .gchat-input:focus-visible {
  outline: none; border-color: var(--arbah-info, #2563EB);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--arbah-info, #2563EB) 20%, transparent);
}
.av3 .gchat-send { flex: 0 0 auto; min-height: 42px; padding-inline: 14px; }

/* ── Site visits (Batch 15 item 6) ──────────────────────────────────────
   Scheduled by the owner and everyone under him, INCLUDING the engineer —
   a passer-by says it to the man on site, so the man on site logs it. */
.av3 .visits-list { list-style: none; margin: 0 0 4px; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.av3 .visits-list.is-past { opacity: .78; }
.av3 .visits-group-title {
  margin: 12px 0 6px; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
  color: var(--arbah-ink-muted, #475569);
}
.av3 .visits-group-title:first-child { margin-top: 0; }
/* Batch 16 item 12 (#76). The row is a BUTTON now — it used to be read-only
   text, so you could see the consultant was coming on Tuesday with nowhere
   to say anything about it. The <li> keeps the divider; the button inside it
   carries the layout and the whole hit area, so the target is the row and
   not a link buried in it. */
.av3 .visit-row {
  border-bottom: 1px solid var(--arbah-border, #E2E8F0);
}
.av3 .visit-row:last-child { border-bottom: 0; }
.av3 .visit-open {
  width: 100%;
  display: flex; align-items: flex-start; gap: 12px;
  padding: 9px 8px; border-radius: 9px;
  background: transparent; border: 0; cursor: pointer;
  text-align: start; font: inherit; color: inherit;
}
.av3 .visit-open:hover { background: var(--arbah-surface-sunken, #F6F8FB); }
.av3 .visit-open:focus-visible {
  outline: 2px solid var(--arbah-focus-ring-color, #2563EB); outline-offset: -2px;
}
/* The affordance. Muted until hover, so a list of visits does not turn into
   a wall of arrows. */
.av3 .visit-chev {
  flex: 0 0 auto; align-self: center; display: inline-flex;
  color: var(--arbah-ink-subtle, #94A3B8); opacity: .55;
}
.av3 .visit-open:hover .visit-chev { opacity: 1; }
.av3 .visit-when {
  flex: 0 0 92px; display: flex; flex-direction: column; gap: 1px;
  font-size: 12.5px; color: var(--arbah-ink, #0F172A);
}
/* ink-subtle (#94A3B8) on white is 2.6:1 — under the 4.5 floor for text this
   small. These two lines carry the date and the attribution, which are the
   whole reason the row is worth opening, so they take ink-muted (7.5:1). */
.av3 .visit-date { font-size: 10.5px; color: var(--arbah-ink-muted, #475569); }
.av3 .visit-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.av3 .visit-who { font-weight: 600; font-size: 13.5px; color: var(--arbah-ink, #0F172A); }
.av3 .visit-note { font-size: 12.5px; color: var(--arbah-ink-muted, #475569); overflow-wrap: anywhere; }
/* "Every visit shows posted by …" — quiet, but always there, and legible:
   see the note on .visit-date above. */
.av3 .visit-poster { font-size: 11.5px; color: var(--arbah-ink-muted, #475569); }
.av3 .visits-form { display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--arbah-border, #E2E8F0); }
.av3 .visits-label { font-size: 12px; font-weight: 700; color: var(--arbah-ink-muted, #475569); margin-top: 4px; }
.av3 .visits-input {
  width: 100%; min-height: 42px; padding: 9px 12px;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 9px;
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink, #0F172A);
  font: inherit; font-size: 14px;
}
.av3 .visits-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.av3 .chipbtn {
  min-height: 36px; padding: 6px 12px; cursor: pointer;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 999px;
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink, #0F172A);
  font: inherit; font-size: 13px;
}
.av3 .chipbtn:hover { background: var(--arbah-surface-sunken, #F6F8FB); }
.av3 .chipbtn.on {
  background: var(--arbah-navy-700, #1E3A5F); color: #FFF;
  border-color: var(--arbah-navy-700, #1E3A5F);
}
.av3 .visits-matches { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.av3 .visits-match {
  width: 100%; text-align: start; min-height: 40px; padding: 8px 12px; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1px solid transparent; border-radius: 8px;
  background: var(--arbah-surface-sunken, #F6F8FB); color: var(--arbah-ink, #0F172A);
  font: inherit; font-size: 13.5px;
}
.av3 .visits-match:hover { border-color: var(--arbah-border-strong, #CBD5E1); }
.av3 .visits-group { font-size: 11px; color: var(--arbah-ink-subtle, #94A3B8); }
.av3 .visits-picked {
  display: flex; align-items: center; gap: 6px; margin: 2px 0 0;
  font-size: 13px; font-weight: 600; color: var(--arbah-success, #047857);
}
.av3 .visits-hint { margin: 4px 0 0; font-size: 11.5px; color: var(--arbah-ink-subtle, #94A3B8); }
.av3 .visits-err { margin: 2px 0 0; font-size: 12.5px; font-weight: 600; color: var(--arbah-danger, #B91C1C); }
.av3 .visits-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }
.av3 .visits-add { flex: 0 0 auto; }
/* The project switcher on Today — same btn-group primitive Progress uses. */
.av3 .et-projswitch { margin-inline-start: auto; flex: 0 0 auto; }

/* ── Plan vs Reality (Batch 15 #55/#56) ─────────────────────────────────
   The chart had preserveAspectRatio="none", so it was squashed to the card's
   width and every stroke drew thin horizontally and thick vertically. It now
   scales uniformly inside a fixed aspect box: it gets bigger and smaller, it
   never distorts. */
.av3 .pvr-chart {
  width: 100%; height: auto; display: block;
  /* 640x300 viewBox. aspect-ratio keeps the box honest before the SVG
     paints, so the card does not jump on first render. */
  aspect-ratio: 640 / 300;
  max-height: 340px;
  overflow: visible;
}
/* Axis text is drawn INSIDE the svg so it stays aligned with the data at
   every width — an HTML axis strip underneath drifts as soon as the plot
   has margins. */
.av3 .pvr-tick { font-family: var(--font-mono, ui-monospace, monospace); letter-spacing: -.02em; }
.av3 .pvr-anchor { font-family: var(--font-sans, system-ui, sans-serif); letter-spacing: .01em; }

/* The plain-language verdict. This is the standing law made visible: not a
   badge to be trusted, a sentence to be checked. */
.av3 .pvr-readout {
  margin: 12px 0 0; padding: 10px 12px;
  border-radius: 9px;
  background: var(--arbah-surface-sunken, #F6F8FB);
  border-inline-start: 3px solid var(--arbah-border-strong, #CBD5E1);
  font-size: 13px; line-height: 1.5; color: var(--arbah-ink, #0F172A);
}
.av3 .pvr-readout.is-behind {
  background: var(--arbah-danger-soft, #FEE2E2);
  border-inline-start-color: var(--arbah-danger, #B91C1C);
}
.av3 .pvr-readout.is-ahead {
  background: var(--arbah-success-soft, #D1FAE5);
  border-inline-start-color: var(--arbah-success, #047857);
}
/* The legend gained a third entry explaining the dots, which is a note rather
   than a series — quieter, and its marker is a dot not a bar. */
.av3 .mini-legend .lg-note { color: var(--arbah-ink-muted, #475569); font-style: italic; }
.av3 .mini-legend .lg-dot { width: 7px; height: 7px; border-radius: 50%; }

/* ── Icon size floor (Batch 15, found while building #53) ───────────────
   Icon.js turns its `size` prop into Tailwind classes: size={4} → "w-4 h-4".
   Only whole-number w-N/h-N classes exist in this build, so every
   size={3.5} / {4.5} / {5.5} icon — about 60 of them app-wide — renders with
   NO width or height at all. They mostly survive because a fixed-size flex
   parent happens to box them in; where nothing does, the SVG expands to fill
   its container. That is what happened to the building glyph in the document
   search results: a 14px icon painted 500px tall and shoved the project name
   off the card.

   viewBox="0 0 24 24" is Icon.js's own signature and nothing else in the app
   uses it, so this floors every icon without touching chart or logo SVGs.
   A real w-N class is a class selector and still wins over this element
   selector, so nothing that was already sized changes. */
svg[viewBox="0 0 24 24"] { width: 1.25rem; height: 1.25rem; flex: 0 0 auto; }

/* ── Document rows (Batch 15 #53) ───────────────────────────────────────
   These were a bare line of text with a chip on the end: name, a grey meta
   string, done. Sultan's read was that the binder looked "flat" — and it
   did, because the colour work stopped at the section header, so a coloured
   lid sat on a grey box.

   The drawer's own `--sec-ink` / `--sec-soft` now cascade into the body, so
   each row carries its section's hue in its file glyph and its hover state.
   A row is also a RECORD now, not a line: name on top, then who filed it and
   when — the two questions the dead register's columns existed to answer. */
.docsec-rows { list-style: none; margin: 0; padding: 0; }
.docsec-row {
  display: flex; align-items: center; gap: 10px;
  padding: 4px 8px; border-radius: 9px;
  border-bottom: 1px solid var(--border, #E2E8F0);
  transition: background-color .12s ease, box-shadow .12s ease;
}
.docsec-row:last-child { border-bottom: 0; }
/* The hover tint is the section's own colour at low strength, so hovering a
   permit feels green and hovering a contract feels bronze. */
.docsec-row:hover {
  background: var(--sec-soft, var(--surface-sunken, #F6F8FB));
  box-shadow: inset 2px 0 0 0 var(--sec-ink, var(--border-strong, #CBD5E1));
  border-bottom-color: transparent;
}
.docsec-rowbtn {
  display: flex; align-items: center; gap: 11px; flex: 1; min-width: 0;
  border: 0; background: none; padding: 8px 0; cursor: pointer;
  color: var(--ink, #0F172A); font: inherit; font-size: 13.5px; text-align: start;
}
/* The file glyph is the row's anchor: a tinted square in the drawer's hue,
   with a shape that says PDF / photo / sheet before you read the name. */
.docsec-fileico {
  flex: 0 0 32px; width: 32px; height: 32px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--sec-soft, var(--surface-sunken, #F1F5F9));
  color: var(--sec-ink, var(--ink-muted, #475569));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sec-ink, #475569) 18%, transparent);
}
.docsec-rowtext { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.docsec-rowbtn:hover .docsec-name { text-decoration: underline; }
.docsec-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600; color: var(--ink, #0F172A);
}
/* Who filed it and when. Small, quiet, and always present — a document with
   no uploader says so in `needs-data` grey rather than showing a blank. */
.docsec-sub2 {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: 11.5px; color: var(--ink-muted, #475569); min-width: 0;
}
.docsec-dot { opacity: .55; }
.docsec-inproj {
  font-weight: 650; color: var(--sec-ink, var(--ink-muted, #475569));
}
/* ── Batch 27 A1 — the photo count and the "Open in …" door ───────────────
   A daily report with eleven site photos should say so on the row, and a row
   the binder derived from a record needs a way back to that record. The door
   is a quiet ghost button so it never competes with the file name, and it
   sits outside `.docsec-rowbtn` because a button inside a button is invalid
   HTML and swallows the outer click on some engines. */
.docsec-photos { display: inline-flex; align-items: center; gap: 3px; }
.docsec-photos svg { opacity: .75; }
.docsec-openin {
  flex: 0 0 auto; padding: 4px 9px; border-radius: 999px; cursor: pointer;
  font: inherit; font-size: 11.5px; font-weight: 600; white-space: nowrap;
  color: var(--sec-ink, var(--ink-muted, #475569));
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--sec-ink, #475569) 30%, transparent);
}
.docsec-openin:hover {
  background: color-mix(in srgb, var(--sec-ink, #475569) 12%, transparent);
}
.docsec-openin:focus-visible {
  outline: 2px solid var(--focus-ring, #2563EB); outline-offset: 2px;
}
@media (max-width: 560px) {
  /* On a phone the row is already three lines; the door moves under the
     name rather than squeezing the chip off the end. */
  .docsec-openin { font-size: 11px; padding: 3px 7px; }
}
.docsec-meta { font-size: 11.5px; color: var(--ink-muted, #475569); white-space: nowrap; }
@media (max-width: 560px) {
  .docsec-meta { display: none; }
  /* The sub-line stays — on a phone it is the only place the date lives. */
  .docsec-sub2 { font-size: 11px; }
}

/* ── Search (Batch 15 #52) ──────────────────────────────────────────────
   The register was proposed as the search-all tool and had no search box.
   This is that box, and it is the reason the register could be deleted
   without losing anything. */
.docsec-search {
  position: relative; display: flex; align-items: center;
  margin: 14px 0 4px;
}
.docsec-search-ico {
  position: absolute; inset-inline-start: 13px;
  display: inline-flex; color: var(--ink-subtle, #94A3B8); pointer-events: none;
}
.docsec-search-input {
  width: 100%; min-height: 44px;
  padding: 10px 14px; padding-inline-start: 40px; padding-inline-end: 40px;
  border: 1px solid var(--border-strong, #CBD5E1); border-radius: 11px;
  background: var(--surface, #FFFFFF); color: var(--ink, #0F172A);
  font: inherit; font-size: 14px;
}
.docsec-search-input::placeholder { color: var(--ink-subtle, #94A3B8); }
.docsec-search-input:focus-visible {
  outline: none;
  border-color: var(--brand, #1E3A5F);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand, #1E3A5F) 22%, transparent);
}
/* Chrome draws its own clear affordance on type=search; ours is themed and
   RTL-aware, so the native one is suppressed rather than sitting beside it. */
.docsec-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.docsec-search-clear {
  position: absolute; inset-inline-end: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border: 0; border-radius: 7px; cursor: pointer;
  background: var(--surface-sunken, #F1F5F9); color: var(--ink-muted, #475569);
}
.docsec-search-clear:hover { background: var(--surface-raised, #E2E8F0); }

.docsec-kpis { margin-top: 4px; }
.docsec-phead { margin-bottom: 4px; }

/* Cross-project results: the one place documents from different projects sit
   together, so every group states which project it is. */
.docsec-results { margin-top: 14px; }
.docsec-resulthead {
  margin: 0 0 10px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-muted, #475569);
}
.docsec-resultgrp + .docsec-resultgrp { margin-top: 18px; }
.docsec-resulttitle {
  display: flex; align-items: center; gap: 8px; margin: 0 0 4px;
  font-size: 13px; color: var(--ink-muted, #475569);
}
.docsec-resultlink {
  border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; font-weight: 700; color: var(--ink, #0F172A);
}
.docsec-resultlink:hover { text-decoration: underline; }
/* A search hit still wears its drawer's colour, so you can see at a glance
   that three of your five matches are permits. */
.docsec-hitwrap { display: block; }

/* Camera spots (Batch 11 item 6). The credentials block is the point of the
   screen, so it gets monospace, generous hit areas and a copy button on every
   line someone has to retype into a camera. */
.cam-spots {
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border, #E2E8F0);
  border-radius: 12px;
  padding: 16px;
  margin-top: 16px;
}
.cam-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; justify-content: space-between; }
.cam-title { margin: 0; font-size: 15px; font-weight: 700; color: var(--ink, #0F172A); }
.cam-sub { margin: 4px 0 0; font-size: 12.5px; color: var(--ink-muted, #475569); }
.cam-add { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.cam-input {
  flex: 1; min-width: 200px; min-height: 42px; padding: 9px 12px;
  border: 1px solid var(--border-strong, #CBD5E1); border-radius: 8px;
  background: var(--surface, #FFFFFF); color: var(--ink, #0F172A);
  font: inherit; font-size: 14px;
}
.cam-err { margin: 10px 0 0; font-size: 13px; font-weight: 600; color: var(--danger, #B91C1C); }
.cam-empty { margin: 12px 0 0; font-size: 13px; color: var(--ink-muted, #475569); }
.cam-list { list-style: none; margin: 14px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.cam-card {
  border: 1px solid var(--border, #E2E8F0); border-radius: 10px;
  background: var(--surface-sunken, #F6F8FB); padding: 12px;
}
.cam-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; color: var(--ink, #0F172A); }
.cam-name { font-weight: 600; font-size: 13.5px; }
.cam-seen { margin-inline-start: auto; font-size: 11.5px; color: var(--ink-muted, #475569); }
.cam-creds { margin-top: 10px; display: flex; flex-direction: column; gap: 8px; }
.cam-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cam-row-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-muted, #475569); min-width: 108px; }
.cam-row-value {
  flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap;
  font-family: var(--font-mono, ui-monospace, monospace); font-size: 12px;
  background: var(--surface, #FFFFFF); color: var(--ink, #0F172A);
  border: 1px solid var(--border, #E2E8F0); border-radius: 6px; padding: 7px 9px;
}
.cam-copy, .cam-rotate {
  flex: none; min-height: 34px; padding: 6px 12px;
  border: 1px solid var(--border-strong, #CBD5E1); border-radius: 7px;
  background: var(--surface, #FFFFFF); color: var(--ink, #0F172A);
  font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
}
.cam-copy:hover, .cam-rotate:hover { background: var(--surface-raised, #F1F5F9); }
.cam-rotate { align-self: flex-start; }
.cam-how { margin: 2px 0 0; font-size: 12px; color: var(--ink-muted, #475569); }
.cam-example { font-size: 12px; color: var(--ink-muted, #475569); }
.cam-example summary { cursor: pointer; font-weight: 600; }
.cam-example code {
  display: block; margin-top: 6px; padding: 9px;
  font-family: var(--font-mono, ui-monospace, monospace); font-size: 11.5px; line-height: 1.7;
  background: var(--surface, #FFFFFF); color: var(--ink, #0F172A);
  border: 1px solid var(--border, #E2E8F0); border-radius: 6px; overflow-x: auto;
}
@media (max-width: 560px) {
  .cam-row-label { min-width: 0; }
  .cam-seen { margin-inline-start: 0; width: 100%; }
}

/* Full-size control on the photo lightbox (Batch 11 item 7). Sits on the
   photo itself, so it has to read over whatever the photo happens to be —
   hence its own dark scrim rather than a theme surface. */
.photo-lightbox-expand {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 999px;
  background: rgba(15, 18, 24, .72);
  color: #FFFFFF;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  backdrop-filter: blur(4px);
}
.photo-lightbox-expand:hover { background: rgba(15, 18, 24, .88); }
.photo-lightbox-expand--card {
  position: absolute;
  inset-block-end: 10px;
  inset-inline-end: 10px;
}
.photo-lightbox-expand.fixed { z-index: 51; }
/* On a narrow screen the label costs more than it earns — keep the icon. */
@media (max-width: 480px) {
  .photo-lightbox-expand-label { display: none; }
  .photo-lightbox-expand--card { padding: 9px; }
}
.photo-lightbox-image-full { display: block; margin: 0 auto; cursor: zoom-out; }

/* ============================================================
   3. Photo fallback tile — shared by every photo surface
   ============================================================
   A photo whose bytes will not decode used to paint nothing at all: an
   empty box that reads as "no photo here" rather than "this photo did
   not load". The tile is deliberately quiet — it marks a gap, it is not
   an error state to be alarmed by. */
.photo-missing {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  height: 100%;
  min-height: 64px;
  padding: 8px;
  text-align: center;
  background: var(--surface-raised, #F1F5F9);
  /* --ink-subtle is #94A3B8 in LIGHT, which on the raised surface this tile
     sits on is 2.34:1 — the placeholder was the one thing in this batch that
     read worse in light than in dark. --ink-muted is the quietest ink that
     still passes in all three appearances (light 6.9:1, dark 9.8:1). */
  color: var(--ink-muted, #475569);
  border: 1px dashed var(--border-strong, #CBD5E1);
  border-radius: inherit;
  box-sizing: border-box;
}
.photo-missing-label {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.25;
  max-width: 100%;
  overflow-wrap: anywhere;
}
/* In tight tiles the caption would crowd the icon out; keep the icon. */
@container (max-width: 90px) { .photo-missing-label { display: none; } }

/* Timeline view stage segments (Batch 15 #57). Five discrete steps, not a
   fabricated percentage — see the note in TasksV3Surface. */
.av3 .task-timeline-track { display: flex; gap: 3px; align-items: center; }
:is(.av3, .modal-panel) .tt-seg {
  flex: 1; height: 6px; border-radius: 3px;
  background: var(--arbah-surface-sunken, #E2E8F0);
  box-shadow: inset 0 0 0 1px var(--arbah-border, #E2E8F0);
}
:is(.av3, .modal-panel) .tt-seg.on { background: var(--arbah-navy-700, #1E3A5F); box-shadow: none; }
:is(.av3, .modal-panel) .tt-seg.bad { background: var(--arbah-danger, #B91C1C); }
.av3 .task-timeline-stage {
  flex: 0 0 auto; font-size: 11.5px; font-weight: 600;
  color: var(--arbah-ink-muted, #475569); min-width: 84px; text-align: end;
}

/* ───────────────────────────────────────────────────────────────────────────
   Batch 16 item 9 (#73) — the Team panel, account IDs, add-by-ID.

   The account ID is a short code (AAM-7K4Q) — the thing you read out so
   somebody can add you to a project. It is set in the mono face and forced
   LTR: a Latin-and-digits code inside an Arabic paragraph reorders without
   it, and a code you cannot read back correctly is worse than no code.

   All four appearances come from tokens, so black/dark are covered by the
   same rules rather than by a copy.
   ─────────────────────────────────────────────────────────────────────── */

.av3 .pd-team-row { display: flex; align-items: center; gap: 11px; padding: 11px 18px; }

.pd-team-code {
  margin-inline-start: auto;
  flex: 0 0 auto;
  font-family: var(--arbah-font-mono, ui-monospace, "SF Mono", Menlo, monospace);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  color: var(--arbah-ink-muted, #475569);
  background: var(--arbah-surface-2, rgba(148, 163, 184, 0.14));
  border: 1px solid var(--arbah-border, #E2E8F0);
  border-radius: 6px;
  padding: 2px 7px;
  white-space: nowrap;
}

/* Add-a-member ------------------------------------------------------------ */

.addmem-bar { display: flex; flex-direction: column; gap: 8px; padding-top: 4px; }

.addmem-open {
  display: inline-flex; align-items: center; gap: 7px; align-self: flex-start;
  font-size: 12.5px; font-weight: 600;
  color: var(--arbah-navy-700, #1E3A5F);
  background: transparent;
  border: 1px dashed var(--arbah-border, #E2E8F0);
  border-radius: 8px; padding: 7px 12px; cursor: pointer;
}
.addmem-open:hover {
  background: var(--arbah-surface-2, rgba(148, 163, 184, 0.12));
  border-style: solid;
}
/* On Black, navy-700 is very nearly the page itself — the same trap the
   phase ring hit in item 1. Ink instead. */
[data-appearance="black"] .addmem-open,
[data-color-scheme="dark"] .addmem-open { color: var(--arbah-ink, #E2E8F0); }

.addmem-form { display: block; padding-top: 4px; }
.addmem-label {
  display: block; font-size: 11.5px; font-weight: 700; letter-spacing: 0.03em;
  text-transform: uppercase; color: var(--arbah-ink-muted, #475569); margin-bottom: 6px;
}
.addmem-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.addmem-input {
  flex: 1 1 130px; min-width: 0;
  font-family: var(--arbah-font-mono, ui-monospace, "SF Mono", Menlo, monospace);
  font-size: 13px; letter-spacing: 0.06em; text-transform: uppercase;
  direction: ltr; text-align: start;
  color: var(--arbah-ink, #0F172A);
  background: var(--arbah-surface, #FFFFFF);
  border: 1px solid var(--arbah-border, #E2E8F0);
  border-radius: 8px; padding: 8px 10px;
}
.addmem-input:focus {
  outline: 2px solid var(--arbah-focus-ring-color, #2563EB); outline-offset: 1px;
  border-color: transparent;
}
.addmem-go, .addmem-cancel {
  flex: 0 0 auto; font-size: 12.5px; font-weight: 600;
  border-radius: 8px; padding: 8px 14px; cursor: pointer;
  border: 1px solid transparent;
}
.addmem-go {
  color: #FFFFFF; background: var(--arbah-navy-700, #1E3A5F);
}
.addmem-go:disabled { opacity: 0.6; cursor: default; }
[data-appearance="black"] .addmem-go { background: var(--arbah-bronze-500, #A97142); }
.addmem-cancel {
  color: var(--arbah-ink-muted, #475569); background: transparent;
  border-color: var(--arbah-border, #E2E8F0);
}
.addmem-hint {
  margin: 8px 0 0; font-size: 11.5px; line-height: 1.5;
  color: var(--arbah-ink-subtle, #94A3B8);
}
.addmem-err, .addmem-ok {
  margin-top: 8px; font-size: 12px; line-height: 1.5;
  border-radius: 8px; padding: 7px 10px;
}
.addmem-err {
  color: var(--arbah-danger, #B91C1C);
  background: var(--arbah-danger-soft, rgba(185, 28, 28, 0.10));
}
.addmem-ok {
  color: var(--arbah-success, #15803D);
  background: var(--arbah-success-soft, rgba(21, 128, 61, 0.10));
}

/* Your own ID, for reading out -------------------------------------------

   This lives in the SIDEBAR, which is navy on every appearance — it does not
   follow the page's light/dark tokens. Painting it with --arbah-ink put
   near-black text (#0F172A) on the navy panel in Light: an ID you cannot
   read is worse than no ID, and it is the same class of mistake as the navy
   phase ring on Black. The sidebar's own palette is white-on-navy, so these
   match .user-name / .user-role rather than the page tokens. */

.myacct {
  display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 5px;
}
.myacct-label { font-size: 10.5px; color: rgba(255, 255, 255, 0.55); }
.myacct-code {
  font-family: var(--arbah-font-mono, ui-monospace, "SF Mono", Menlo, monospace);
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.06em;
  color: #FFFFFF;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.20);
  border-radius: 6px; padding: 2px 7px;
}
.myacct-copy {
  font-size: 11px; font-weight: 600; cursor: pointer;
  color: rgba(255, 255, 255, 0.72);
  background: transparent; border: 0; padding: 2px 4px;
}
.myacct-copy:hover { color: #FFFFFF; }

/* Narrow phones: the code would push the name into two lines, so it drops
   under it rather than squeezing the person's name. */
@media (max-width: 420px) {
  .pd-team-code { margin-inline-start: 0; }
  .av3 .pd-team-row { flex-wrap: wrap; }
  .addmem-row { flex-direction: column; align-items: stretch; }
  .addmem-go, .addmem-cancel { width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════════
   BATCH 17
   ═══════════════════════════════════════════════════════════════════════ */

/* ── #78 + #79 · Activity badges: one glyph, one colour ─────────────────
   The badge colour is decided by the GLYPH now (utils/activity-icons.js →
   ICON_TONE), not by the rows

/* ═══════════════════════════════════════════════════════════════════════
   BATCH 17 — post-Batch-16 sweep fixes (#77–#88)
   ═══════════════════════════════════════════════════════════════════════ */

/* ── #78 + #79 · Activity badges: one glyph, one colour ─────────────────
   The badge colour is decided by the GLYPH now (utils/activity-icons.js →
   ICON_TONE), not by the row's tone and not by the event bucket. These
   `tbi--*` classes are that table's rendering. The older `tb--<type>` +
   `.tone-<tone>` pair above is left in place for surfaces not yet migrated,
   but nothing the feeds render uses it any more.

   Why it mattered: `.tone-warn` beat `.tb--task`, so a FINISHED task came out
   YELLOW while an approved one came out green — two check marks, two
   colours, one meaning. And `task` mapped to `check` for every state, so
   STUCK rendered as a red square with a CHECK MARK in it. */
.av3 .type-badge.tbi--good   { background: var(--arbah-success, #047857); color: #fff; }
.av3 .type-badge.tbi--bad    { background: var(--arbah-danger,  #B91C1C); color: #fff; }
.av3 .type-badge.tbi--warn   { background: var(--arbah-warning, #B45309); color: #fff; }
.av3 .type-badge.tbi--info   { background: var(--arbah-info,    #2563EB); color: #fff; }
.av3 .type-badge.tbi--navy   { background: var(--arbah-navy-500, #2C4A6E); color: #fff; }
.av3 .type-badge.tbi--money  { background: var(--arbah-bronze-500, #A06A2C); color: #fff; }
/* Reports keep the indigo the Batch 16 note argued for: navy-700 resolves to
   near-black on the Black appearance, and the daily report is the commonest
   event in the feed by a distance. */
.av3 .type-badge.tbi--report { background: #4338CA; color: #fff; }

/* Dark + Black lighten the semantic tokens, so white ink on them drops to
   ~2:1. Same dark-ink fix the Batch 9 chip sweep applied everywhere else. */
html[data-color-scheme="dark"] .av3 .type-badge.tbi--good,
html[data-color-scheme="dark"] .av3 .type-badge.tbi--bad,
html[data-color-scheme="dark"] .av3 .type-badge.tbi--warn,
html[data-color-scheme="dark"] .av3 .type-badge.tbi--info,
html[data-color-scheme="dark"] .av3 .type-badge.tbi--money { color: #0B1420; }
html[data-color-scheme="dark"] .av3 .type-badge.tbi--report { background: #A5B4FC; color: #0B1420; }

/* The ProjectDetail feed paints its badge OUTSIDE `.act-avatar`, so it never
   picked up the primitives' sizing rule and rendered as a bare glyph with no
   disc behind it — which is a large part of why those badges read as
   "colourless" on that screen specifically. */
.av3 .pd-feed .type-badge {
  width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
}

/* Notifications read the same table (kindMap.toneForKind). The avatar is
   already an `.avatar-md`, so the tone only supplies ground + ink. */
.av3 .ntf-badge { color: #fff; }
.av3 .ntf-badge.tbi--good   { background: var(--arbah-success, #047857); }
.av3 .ntf-badge.tbi--bad    { background: var(--arbah-danger,  #B91C1C); }
.av3 .ntf-badge.tbi--warn   { background: var(--arbah-warning, #B45309); }
.av3 .ntf-badge.tbi--info   { background: var(--arbah-info,    #2563EB); }
.av3 .ntf-badge.tbi--navy   { background: var(--arbah-navy-500, #2C4A6E); }
.av3 .ntf-badge.tbi--money  { background: var(--arbah-bronze-500, #A06A2C); }
.av3 .ntf-badge.tbi--report { background: #4338CA; }
html[data-color-scheme="dark"] .av3 .ntf-badge.tbi--good,
html[data-color-scheme="dark"] .av3 .ntf-badge.tbi--bad,
html[data-color-scheme="dark"] .av3 .ntf-badge.tbi--warn,
html[data-color-scheme="dark"] .av3 .ntf-badge.tbi--info,
html[data-color-scheme="dark"] .av3 .ntf-badge.tbi--money { color: #0B1420; }
html[data-color-scheme="dark"] .av3 .ntf-badge.tbi--report { background: #A5B4FC; color: #0B1420; }


/* ── #77 · Daily site report: the two pickers are one control group ─────
   The project <select> is a single 40px control; the period control is the
   whole `.rrp` widget (label row + chip row + optional custom dates). Side
   by side at `align-items: flex-end` they were visibly different boxes of
   different heights. They are one bordered group now: equal height because
   the row stretches, equal width basis, and the seam between them is a
   single shared border rather than two independent outlines. */
.av3 .daily-report .dr-pickers {
  align-items: stretch;
  gap: 0;
  border: 1px solid var(--arbah-border, #E2E8F0);
  border-radius: 11px;
  background: var(--arbah-surface, #FFF);
  overflow: hidden;
}
.av3 .daily-report .dr-field {
  flex: 1 1 260px;
  min-width: 0;
  gap: 0;
  padding: 12px 14px;
  justify-content: flex-start;
}
/* The seam. `inset-inline` so RTL mirrors it without a second rule. */
.av3 .daily-report .dr-field + .dr-field {
  border-inline-start: 1px solid var(--arbah-border, #E2E8F0);
}
.av3 .daily-report .dr-flabel { margin-bottom: 7px; }
/* The period widget is INSIDE the group now, so it drops its own box. */
.av3 .daily-report .dr-field .rrp {
  margin: 0; padding: 0; border: 0; background: transparent;
}
/* Its label row duplicated the field label above it. */
.av3 .daily-report .dr-field .rrp .rrp-label { display: none; }
.av3 .daily-report .dr-field .rrp .rrp-head { margin-bottom: 7px; justify-content: flex-end; }
/* The project control fills its half so the two read as equal boxes. */
.av3 .daily-report .dr-field select { width: 100%; }
@media (max-width: 720px) {
  /* Stacked on a phone: the seam becomes a horizontal rule. */
  .av3 .daily-report .dr-pickers { flex-direction: column; }
  .av3 .daily-report .dr-field + .dr-field {
    border-inline-start: 0;
    border-top: 1px solid var(--arbah-border, #E2E8F0);
  }
}


/* ── #81 · Today quick actions fill the row ─────────────────────────────
   Daily report / Add photo / Scan receipt were bunched at the start of the
   row with dead space beside them. Equal columns across the full width, at
   every width and in both directions.

   Batch 19 (#95): FOUR now, with "Send quotation" joining them. The count is
   driven by the QUICK_ACTIONS array rather than hard-coded here — `auto-fit`
   would collapse to fewer columns at narrow widths and break the stretch law,
   so the column count comes from a custom property the surface sets from the
   array length. Add a fifth action and the row stays even without touching
   this rule. */
.av3 .et-quick-grid {
  display: grid;
  grid-template-columns: repeat(var(--et-quick-cols, 4), minmax(0, 1fr));
  gap: 10px;
  width: 100%;
}
.av3 .et-quick-btn {
  width: 100%;
  min-height: 76px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px;
  text-align: center;
}
.av3 .et-quick-label { font-size: 12.5px; font-weight: 600; line-height: 1.2; }
/* Four labels do not fit across a phone. Two-by-two keeps every tile the same
   size and the row still full-width — the stretch law holds at both counts. */
@media (max-width: 560px) {
  .av3 .et-quick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .av3 .et-quick-label { font-size: 12px; }
}


/* ── Batch 19 (#95) · quotations ────────────────────────────────────────
 *
 * The quotation block sits between "send a receipt" and "my receipts" on the
 * engineer's page, so it has to read as a DIFFERENT errand rather than more of
 * the same form. A bronze leading rule does that — it is the accent this app
 * already uses to mark "this is the other thing on this page" — without adding
 * a colour or a border style that exists nowhere else.
 *
 * Everything else deliberately reuses the receipt classes (.eng-receipt-drop,
 * -grid, -list, -row, -thumb): the two documents are photographed the same way
 * by the same person, and giving them two visual languages would be inventing
 * a difference that is not there.
 */
.av3 .quo-block {
  border-inline-start: 3px solid var(--arbah-bronze-500, #C9A961);
}
.av3 .quo-head { margin-bottom: 10px; }
.av3 .quo-sub {
  margin: 2px 0 0; font-size: 12.5px; color: var(--arbah-ink-muted, #475569);
}
.av3 .quo-formtitle {
  margin: 0 0 8px; font-size: 13px; font-weight: 700;
  color: var(--arbah-ink, #0F172A);
}
.av3 .quo-form { display: flex; flex-direction: column; gap: 10px; }
.av3 .quo-mine { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--arbah-border, #E2E8F0); }

/* The promise. This is the sentence that makes the feature worth using, so it
   is set as a statement the eye lands on, not as fine print. */
.av3 .quo-promise {
  display: flex; align-items: flex-start; gap: 8px; margin: 0;
  padding: 10px 12px; border-radius: 9px;
  background: var(--arbah-success-soft, #E8F6F0);
  color: var(--arbah-success, #047857);
  font-size: 12.5px; font-weight: 600; line-height: 1.5;
}
.av3 .quo-promise svg { flex: 0 0 auto; margin-top: 1px; }

/* The attach picker on the receipt form. */
.av3 .quo-attach { display: flex; flex-direction: column; gap: 6px; }
.av3 .quo-attach-label {
  font-size: 12px; font-weight: 700; color: var(--arbah-ink-muted, #475569);
}
.av3 .quo-attach-hint,
.av3 .quo-attach-none {
  margin: 0; font-size: 12px; line-height: 1.5;
  color: var(--arbah-ink-muted, #475569);
}
.av3 .quo-attach-none { font-style: italic; color: var(--arbah-ink-subtle, #94A3B8); }

/* The approver's queue. The row and its two buttons stay on one line on a
   desktop and stack on a phone, so the decision is never a horizontal scroll. */
.av3 .quo-pending-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.av3 .quo-pending-row > .eng-receipt-row { flex: 1 1 260px; }
.av3 .quo-actions { display: flex; gap: 8px; flex: 0 0 auto; }
@media (max-width: 560px) {
  .av3 .quo-actions { width: 100%; }
  .av3 .quo-actions > * { flex: 1; justify-content: center; }
}

/* ── Batch 20 item 4 (#100) · comments: caged, and in bubbles ───────────
 *
 * TWO problems, one component, therefore one fix everywhere comments render.
 *
 * 1. THE CAGE. The list grew without limit, so a task with twenty comments
 *    pushed its own action buttons off the screen and the modal kept getting
 *    taller. It has a ceiling now and scrolls inside itself. `overscroll-behavior`
 *    stops a flick at the bottom of the thread from scrolling the page behind
 *    it, which is the thing that makes an inner scroller feel broken on a phone.
 *
 * 2. THE BUBBLES. Bare stacked lines gave the eye no edge between one person
 *    and the next. The treatment is the group chat's, because a comment thread
 *    is a conversation and the app already knows how to draw one.
 */
.cmt-scroll {
  display: flex; flex-direction: column; gap: 14px;
  max-height: 340px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-inline-end: 4px;   /* room for the scrollbar, so text never sits under it */
}
/* A short thread must not look caged — the ceiling only shows up when the
   conversation is actually long enough to need it. */
.cmt-scroll::-webkit-scrollbar { width: 8px; }
.cmt-scroll::-webkit-scrollbar-thumb {
  background: var(--arbah-border-strong, #CBD5E1); border-radius: 4px;
}
.cmt-scroll::-webkit-scrollbar-track { background: transparent; }

.cmt-row { display: flex; gap: 10px; align-items: flex-start; }
.cmt-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.cmt-meta { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.cmt-author { font-size: 12.5px; font-weight: 700; color: var(--arbah-ink, #0F172A); }
.cmt-when { font-size: 11px; color: var(--arbah-ink-subtle, #94A3B8); }

.cmt-bubble {
  align-self: flex-start;
  max-width: 100%;
  padding: 9px 13px;
  border-radius: 14px;
  border-start-start-radius: 4px;      /* the group chat's "other person" tail */
  background: var(--arbah-surface-sunken, #F6F8FB);
  border: 1px solid var(--arbah-border, #E2E8F0);
  color: var(--arbah-ink, #0F172A);
  font-size: 13.5px; line-height: 1.5;
  overflow-wrap: anywhere; white-space: pre-wrap;
}

.cmt-actions { display: flex; align-items: center; gap: 8px; margin-top: 1px; font-size: 11.5px; }
.cmt-act {
  background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
  color: var(--arbah-ink-muted, #475569); font-weight: 600;
}
.cmt-act:hover { color: var(--arbah-ink, #0F172A); text-decoration: underline; }
.cmt-act--danger { color: var(--arbah-danger, #B91C1C); }
.cmt-sep { color: var(--arbah-ink-subtle, #94A3B8); }

@media (max-width: 560px) {
  /* Shorter on a phone: the composer underneath must stay reachable without
     the thumb travelling the whole screen. */
  .cmt-scroll { max-height: 260px; }
}

/* ── Batch 20 (#98) · the phone log ─────────────────────────────────────
 *
 * Search is the point, so the search box is the biggest thing on the screen
 * after the title — not a filter tucked into a corner. Each row leads with the
 * NAME and the TITLE CHIP side by side, because "Ahmed — Painter" is the unit
 * of information a person is actually hunting for; the number itself is set in
 * the mono face so a column of them scans digit under digit.
 */
.av3 .pl-searchbar {
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
}
.av3 .pl-searchicon { flex: 0 0 auto; color: var(--arbah-ink-subtle, #94A3B8); display: inline-flex; }
.av3 .pl-search {
  flex: 1 1 auto; min-width: 0; min-height: 44px; padding: 8px 4px;
  border: 0; background: none; font: inherit; font-size: 15px;
  color: var(--arbah-ink, #0F172A);
}
.av3 .pl-search:focus { outline: none; }
.av3 .pl-count {
  flex: 0 0 auto; font-size: 12px; color: var(--arbah-ink-muted, #475569);
  font-family: var(--font-mono, ui-monospace, monospace);
}

.av3 .pl-form { display: flex; flex-direction: column; gap: 12px; }
.av3 .pl-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.av3 .pl-grid > label { display: flex; flex-direction: column; gap: 5px; font-size: 12.5px; font-weight: 600; color: var(--arbah-ink-muted, #475569); }
.av3 .pl-grid input {
  min-height: 42px; padding: 9px 11px; font: inherit; font-size: 14px;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 9px;
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink, #0F172A);
}
.av3 .pl-wide, .av3 .pl-titlefield { grid-column: 1 / -1; }
.av3 .pl-help { font-weight: 500; font-size: 11.5px; color: var(--arbah-ink-subtle, #94A3B8); }
.av3 .pl-suggest { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.av3 .pl-formactions { display: flex; justify-content: flex-end; gap: 10px; }
@media (max-width: 640px) { .av3 .pl-grid { grid-template-columns: 1fr; } }

.av3 .pl-list { list-style: none; margin: 14px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.av3 .pl-row {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px;
  padding: 12px 14px; border-radius: 12px;
  background: var(--arbah-surface, #FFF);
  border: 1px solid var(--arbah-border, #E2E8F0);
}
.av3 .pl-row.is-deep { border-color: var(--arbah-bronze-500, #C9A961); box-shadow: 0 0 0 2px rgba(201,169,97,.18); }
.av3 .pl-main { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.av3 .pl-nameline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.av3 .pl-name { font-size: 15px; font-weight: 700; color: var(--arbah-ink, #0F172A); }
.av3 .pl-title, .av3 .pl-city { font-size: 11.5px; }
.av3 .pl-phone {
  font-family: var(--font-mono, ui-monospace, monospace); font-size: 15px; font-weight: 600;
  color: var(--arbah-info, #2563EB); text-decoration: none; letter-spacing: .02em;
}
.av3 .pl-phone:hover { text-decoration: underline; }
.av3 .pl-note { font-size: 12.5px; color: var(--arbah-ink-muted, #475569); }
.av3 .pl-meta { font-size: 11px; color: var(--arbah-ink-subtle, #94A3B8); }
.av3 .pl-actions { flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 6px; align-items: flex-start; }
.av3 .pl-call {
  display: inline-flex; align-items: center; gap: 5px; text-decoration: none;
  background: var(--arbah-info, #2563EB); color: #fff; border-color: transparent;
}
.av3 .pl-call:hover { background: #1D4FD8; }
.av3 .pl-remove:hover { color: var(--arbah-danger, #B91C1C); border-color: #F2C2C2; }

/* The aging suggestion. Amber, because it asks for a decision rather than
   reporting a problem — and it NEVER deletes on its own. */
.av3 .pl-row.is-stale { border-inline-start: 4px solid var(--arbah-warning, #B45309); }
.av3 .pl-stale {
  flex: 1 0 100%; display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 10px; margin-top: 4px;
  padding: 10px 12px; border-radius: 9px;
  background: var(--arbah-warning-soft, #FEF6E7);
  color: var(--arbah-warning, #B45309);
  font-size: 12.5px; line-height: 1.5;
}
.av3 .pl-stale-text { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.av3 .pl-stale-text svg { flex: 0 0 auto; margin-top: 2px; }
.av3 .pl-stale-actions { display: flex; gap: 8px; flex: 0 0 auto; }
@media (max-width: 560px) {
  .av3 .pl-actions { width: 100%; }
  .av3 .pl-actions > * { flex: 1; justify-content: center; }
  .av3 .pl-stale-actions { width: 100%; }
  .av3 .pl-stale-actions > * { flex: 1; justify-content: center; }
}

/* ── Batch 20 (#95a) · the priced lines ─────────────────────────────────
 * "Cement, 50kg bag — 30.00 × 5". Three inputs on one line so the shape of
 * the sentence IS the shape of the arithmetic; the × between them is a real
 * character rather than a label, because that is how the person reading the
 * supplier's paper sees it written.
 */
.av3 .quo-lines { display: flex; flex-direction: column; gap: 8px; }
.av3 .quo-lines-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.av3 .quo-line-rows { display: flex; flex-direction: column; gap: 6px; }
.av3 .quo-line {
  display: flex; align-items: center; gap: 6px;
  padding: 6px; border-radius: 9px;
  background: var(--arbah-surface-sunken, #F6F8FB);
  border: 1px solid var(--arbah-border, #E2E8F0);
}
.av3 .quo-line input {
  min-height: 38px; padding: 7px 9px; font: inherit; font-size: 13px;
  border: 1px solid var(--arbah-border-strong, #CBD5E1); border-radius: 8px;
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink, #0F172A);
}
.av3 .quo-line-desc { flex: 1 1 auto; min-width: 0; }
.av3 .quo-line-num  { flex: 0 0 92px; width: 92px; text-align: center; }
.av3 .quo-line-x { flex: 0 0 auto; color: var(--arbah-ink-subtle, #94A3B8); font-weight: 700; }
.av3 .quo-line-drop {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 8px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--arbah-border, #E2E8F0);
  background: var(--arbah-surface, #FFF); color: var(--arbah-ink-muted, #475569);
}
.av3 .quo-line-drop:hover { color: var(--arbah-danger, #B91C1C); border-color: #F2C2C2; }
@media (max-width: 560px) {
  /* On a phone the description takes its own row so the two numbers and the
     × stay side by side, which is the half that has to read as arithmetic. */
  .av3 .quo-line { flex-wrap: wrap; }
  .av3 .quo-line-desc { flex: 1 0 100%; }
  .av3 .quo-line-num { flex: 1 1 0; width: auto; }
}

/* The arithmetic, stated. Shown on the form before sending, in the detail
   view, and on an auto-approved receipt — the same treatment in all three, so
   "5 × 30.00 = 150.00" looks like one idea wherever it appears. */
.av3 .quo-math {
  display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin: 2px 0 0;
  padding: 9px 12px; border-radius: 9px;
  background: var(--arbah-success-soft, #E8F6F0);
  color: var(--arbah-success, #047857);
  font-size: 12.5px; font-weight: 600; line-height: 1.5;
}
.av3 .quo-math svg { flex: 0 0 auto; }

.av3 .quo-detail-lines { margin-top: 12px; display: flex; flex-direction: column; gap: 7px; }
.av3 .quo-detail-linelist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.av3 .quo-detail-linelist li {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 7px 10px; border-radius: 8px;
  background: var(--arbah-surface-sunken, #F6F8FB);
  border: 1px solid var(--arbah-border, #E2E8F0);
  font-size: 12.5px;
}
.av3 .quo-detail-desc { color: var(--arbah-ink, #0F172A); font-weight: 600; min-width: 0; }
.av3 .quo-detail-price { color: var(--arbah-ink-muted, #475569); white-space: nowrap; }
.av3 .quo-detail-price em { font-style: italic; color: var(--arbah-ink-subtle, #94A3B8); }

/* "Auto-approved against quotation QUO-…" — a link, because the law is that
   every mention of a thing reaches the thing. */
.av3 .quo-link {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--arbah-success, #047857); font-weight: 700; text-decoration: none;
}
.av3 .quo-link:hover { text-decoration: underline; }

/* ── #82 · Today's tasks are rows you can open ──────────────────────────
   Same affordance as a visit row: the whole row is a button, with the
   check-box still its own control inside it. */
.av3 .et-task-row { display: flex; align-items: center; gap: 10px; min-height: 46px; }
.av3 .et-task-open {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  background: none; border: 0; padding: 6px 2px; margin: 0;
  font: inherit; color: inherit; text-align: start; cursor: pointer;
  border-radius: 8px;
}
.av3 .et-task-open:hover { background: var(--arbah-surface-sunken, #F8FAFC); }
.av3 .et-task-open:focus-visible { outline: 2px solid var(--arbah-navy-500); outline-offset: 1px; }
.av3 .et-task-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.av3 .et-task-chev { flex: 0 0 auto; color: var(--arbah-ink-subtle, #94A3B8); display: inline-flex; }

/* ── Batch 19 item 4 (#94) · the row shows urgency and lateness ─────────────
 *
 * "Not white-on-gray spreadsheet rows." Three changes, each carrying one fact:
 * a priority stripe on the leading edge, chips for priority and status, and a
 * due date that states its own condition. Every colour is an existing token,
 * so all four themes follow with no per-theme override.
 */
.av3 .et-tasks { display: flex; flex-direction: column; gap: 8px; }
.av3 .et-task-row {
  display: flex; align-items: flex-start; gap: 10px; min-height: 46px;
  padding: 8px 10px; border-radius: 10px;
  background: var(--arbah-surface, #FFF);
  border: 1px solid var(--arbah-border, #E2E8F0);
  /* The stripe. `border-inline-start` rather than `border-left` so the RTL
     mirror puts it on the correct edge without a [dir] override. */
  border-inline-start: 4px solid var(--arbah-border-strong, #CBD5E1);
}
.av3 .et-task-row .check-box { margin-top: 3px; }
.av3 .et-task-row--urgent { border-inline-start-color: var(--arbah-danger, #B91C1C); }
.av3 .et-task-row--high   { border-inline-start-color: var(--arbah-danger, #B91C1C); }
.av3 .et-task-row--medium { border-inline-start-color: var(--arbah-warning, #B45309); }
.av3 .et-task-row--low    { border-inline-start-color: var(--arbah-ink-subtle, #94A3B8); }

.av3 .et-task-open { align-items: flex-start; padding: 2px; }
.av3 .et-task-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.av3 .et-task-main .et-task-title { flex: none; white-space: normal; overflow: visible; font-weight: 600; line-height: 1.35; }
.av3 .et-task-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.av3 .et-task-chip { font-size: 11px; padding: 3px 8px; }

/* The due date. Overdue is the one an engineer must see from across the room,
   so it is the only one that gets a filled danger treatment. */
.av3 .et-due {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 999px;
  border: 1px solid transparent; white-space: nowrap;
  background: var(--arbah-surface-raised, #F1F5F9);
  color: var(--arbah-ink-muted, #475569);
}
.av3 .et-due svg { width: 11px; height: 11px; }
.av3 .et-due--overdue {
  background: var(--arbah-danger, #B91C1C); color: #fff; border-color: transparent;
}
.av3 .et-due--today {
  background: var(--arbah-warning-soft, #FEF6E7); color: var(--arbah-warning, #B45309);
  border-color: #F0D69A;
}
.av3 .et-due--upcoming {
  background: var(--arbah-info-soft, #EFF4FE); color: var(--arbah-info, #2563EB);
  border-color: #BFD3F5;
}
/* A task with no due date says so — the dates-always-visible law — but quietly,
   because "not scheduled" is not a warning. */
.av3 .et-due--none { font-style: italic; font-weight: 500; color: var(--arbah-ink-subtle, #94A3B8); }

.av3 .et-task-row:hover { border-color: var(--arbah-border-strong, #CBD5E1); }
.av3 .et-task-row .et-task-open:hover { background: var(--arbah-surface-sunken, #F8FAFC); }


/* ── Item 9 · The "Where it is" trail was glued together ────────────────
   "Sent11h agoby Omar Al Zahrani". The three spans are siblings with no
   whitespace between them in the template, and `.tt-body` had no gap, so
   the words ran into each other. Same family as #30c. A flex row with a gap
   spaces them without putting literal spaces in the markup — which also
   keeps RTL correct, where a hard-coded space would sit on the wrong side. */
:is(.av3, .modal-panel) .tt-body {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 4px 6px;
  min-width: 0;
}
:is(.av3, .modal-panel) .tt-note { flex-basis: 100%; }


/* ── #86 · The planned-by-today marker ──────────────────────────────────
   Fixed IN PLACE, further up this file, rather than shadowed from down here:
   see `.pbar-planned`, `.pbar-planned::after`, `.tl-planned-line`,
   `.tl-planned-tag` and `.tl-lg-planned` in the Batch 16 marker block.

   Two rules for one selector is how a stylesheet stops being readable — you
   fix the first one, nothing moves, and the second is three hundred lines
   away. One rule each. (The phone rule that hides the line and keeps the tag
   is up there too, and is unchanged — the tag carries the colour on its own
   at that width, so nothing is lost by hiding the line.) */


/* ── #87 · The project group chat, embedded under Recent activity ───────
   Sized so it fills the column the fact sheet was running past, without
   taking the page over: it scrolls internally and keeps its composer at the
   bottom. Same component as the Today page — one conversation, three
   windows. */
.av3 .pd-chat-embed { display: flex; flex-direction: column; }
.av3 .pd-chat-embed .ch-scroll,
.av3 .pd-chat-embed .chat-scroll { max-height: 340px; overflow-y: auto; }


/* ═══════════════════════════════════════════════════════════════════════
   #88 · THE HARD-CODED LIGHT BACKGROUND SWEEP
   ═══════════════════════════════════════════════════════════════════════
   The kanban on ProjectDetail → Tasks rendered To Do dark and In Progress /
   In Review / Done WHITE on a dark theme. The columns are Tailwind literals:
   `bg-gray-100` for To Do, `bg-blue-50` / `bg-purple-50` / `bg-green-50` for
   the other three. The appearance layer above neutralises the NEUTRAL ramp
   (`bg-white`, `bg-gray-50/100`, `bg-slate-50/100`) and deliberately left
   coloured utilities alone, on the reasoning that "coloured utilities carry
   meaning and already read on a dark ground".

   That reasoning is right for SATURATED colours (`bg-blue-600`) and wrong
   for the pale TINTS. `bg-blue-50` is #EFF6FF — a near-white. It carries no
   meaning a dark theme can use; it is simply white with a hint of hue, and
   on a dark card it is a white slab. Every 50/100/200 tint in the app had
   the same bug waiting in it; the kanban is just where it was three columns
   wide and impossible to miss.

   So the tints are re-pointed here, hue preserved, luminance inverted: a red
   alert panel stays red, it just stops being a white one. `color-mix` keeps
   the ground tied to the live `--surface-raised` token, so Black and Dark
   each get their own answer from one rule and no third copy is needed.

   The plain-colour fallback before each `color-mix` is what a renderer
   without `color-mix` gets: the neutral raised surface. Never white.

   The kanban ALSO got a source-level fix (pages/ProjectDetail.js) so it
   stops depending on this net at all — but the net is what makes the other
   ~30 occurrences safe, which is the "I don't want this bug to be nowhere"
   half of the instruction. */

html[data-color-scheme="dark"] .bg-red-50,
html[data-color-scheme="dark"] .bg-rose-50,
html[data-color-scheme="dark"] .bg-orange-50,
html[data-color-scheme="dark"] .bg-amber-50,
html[data-color-scheme="dark"] .bg-yellow-50,
html[data-color-scheme="dark"] .bg-lime-50,
html[data-color-scheme="dark"] .bg-green-50,
html[data-color-scheme="dark"] .bg-emerald-50,
html[data-color-scheme="dark"] .bg-teal-50,
html[data-color-scheme="dark"] .bg-cyan-50,
html[data-color-scheme="dark"] .bg-sky-50,
html[data-color-scheme="dark"] .bg-blue-50,
html[data-color-scheme="dark"] .bg-indigo-50,
html[data-color-scheme="dark"] .bg-violet-50,
html[data-color-scheme="dark"] .bg-purple-50,
html[data-color-scheme="dark"] .bg-fuchsia-50,
html[data-color-scheme="dark"] .bg-pink-50,
html[data-color-scheme="dark"] .bg-stone-50,
html[data-color-scheme="dark"] .bg-zinc-50,
html[data-color-scheme="dark"] .bg-neutral-50,
html[data-color-scheme="dark"] .bg-stone-100,
html[data-color-scheme="dark"] .bg-zinc-100,
html[data-color-scheme="dark"] .bg-neutral-100 { background-color: var(--surface-raised); }

/* Hue-preserving versions, for engines that have color-mix. */
html[data-color-scheme="dark"] .bg-red-50     { background-color: color-mix(in srgb, #EF4444 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-rose-50    { background-color: color-mix(in srgb, #F43F5E 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-orange-50  { background-color: color-mix(in srgb, #F97316 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-amber-50   { background-color: color-mix(in srgb, #F59E0B 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-yellow-50  { background-color: color-mix(in srgb, #EAB308 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-lime-50    { background-color: color-mix(in srgb, #84CC16 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-green-50   { background-color: color-mix(in srgb, #22C55E 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-emerald-50 { background-color: color-mix(in srgb, #10B981 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-teal-50    { background-color: color-mix(in srgb, #14B8A6 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-cyan-50    { background-color: color-mix(in srgb, #06B6D4 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-sky-50     { background-color: color-mix(in srgb, #0EA5E9 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-blue-50    { background-color: color-mix(in srgb, #3B82F6 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-indigo-50  { background-color: color-mix(in srgb, #6366F1 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-violet-50  { background-color: color-mix(in srgb, #8B5CF6 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-purple-50  { background-color: color-mix(in srgb, #A855F7 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-fuchsia-50 { background-color: color-mix(in srgb, #D946EF 12%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-pink-50    { background-color: color-mix(in srgb, #EC4899 12%, var(--surface-raised)); }

/* The 100/200 steps are the same hues one notch stronger. */
html[data-color-scheme="dark"] .bg-red-100,
html[data-color-scheme="dark"] .bg-rose-100,
html[data-color-scheme="dark"] .bg-orange-100,
html[data-color-scheme="dark"] .bg-amber-100,
html[data-color-scheme="dark"] .bg-yellow-100,
html[data-color-scheme="dark"] .bg-lime-100,
html[data-color-scheme="dark"] .bg-green-100,
html[data-color-scheme="dark"] .bg-emerald-100,
html[data-color-scheme="dark"] .bg-teal-100,
html[data-color-scheme="dark"] .bg-cyan-100,
html[data-color-scheme="dark"] .bg-sky-100,
html[data-color-scheme="dark"] .bg-blue-100,
html[data-color-scheme="dark"] .bg-indigo-100,
html[data-color-scheme="dark"] .bg-violet-100,
html[data-color-scheme="dark"] .bg-purple-100,
html[data-color-scheme="dark"] .bg-fuchsia-100,
html[data-color-scheme="dark"] .bg-pink-100,
html[data-color-scheme="dark"] .bg-red-200,
html[data-color-scheme="dark"] .bg-amber-200,
html[data-color-scheme="dark"] .bg-blue-200,
html[data-color-scheme="dark"] .bg-green-200 { background-color: var(--surface-raised); }

html[data-color-scheme="dark"] .bg-red-100     { background-color: color-mix(in srgb, #EF4444 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-rose-100    { background-color: color-mix(in srgb, #F43F5E 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-orange-100  { background-color: color-mix(in srgb, #F97316 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-amber-100   { background-color: color-mix(in srgb, #F59E0B 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-yellow-100  { background-color: color-mix(in srgb, #EAB308 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-lime-100    { background-color: color-mix(in srgb, #84CC16 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-green-100   { background-color: color-mix(in srgb, #22C55E 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-emerald-100 { background-color: color-mix(in srgb, #10B981 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-teal-100    { background-color: color-mix(in srgb, #14B8A6 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-cyan-100    { background-color: color-mix(in srgb, #06B6D4 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-sky-100     { background-color: color-mix(in srgb, #0EA5E9 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-blue-100    { background-color: color-mix(in srgb, #3B82F6 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-indigo-100  { background-color: color-mix(in srgb, #6366F1 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-violet-100  { background-color: color-mix(in srgb, #8B5CF6 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-purple-100  { background-color: color-mix(in srgb, #A855F7 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-fuchsia-100 { background-color: color-mix(in srgb, #D946EF 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-pink-100    { background-color: color-mix(in srgb, #EC4899 20%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-red-200     { background-color: color-mix(in srgb, #EF4444 28%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-amber-200   { background-color: color-mix(in srgb, #F59E0B 28%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-blue-200    { background-color: color-mix(in srgb, #3B82F6 28%, var(--surface-raised)); }
html[data-color-scheme="dark"] .bg-green-200   { background-color: color-mix(in srgb, #22C55E 28%, var(--surface-raised)); }

/* Hover states on the same tints, so a row does not flash white under the
   cursor on a dark theme. */
html[data-color-scheme="dark"] .hover\:bg-red-50:hover,
html[data-color-scheme="dark"] .hover\:bg-blue-50:hover,
html[data-color-scheme="dark"] .hover\:bg-green-50:hover,
html[data-color-scheme="dark"] .hover\:bg-amber-50:hover,
html[data-color-scheme="dark"] .hover\:bg-yellow-50:hover,
html[data-color-scheme="dark"] .hover\:bg-indigo-50:hover,
html[data-color-scheme="dark"] .hover\:bg-purple-50:hover { background-color: var(--surface-raised); }

/* Dark INK that was chosen to sit on those pale tints. `text-red-800` on
   `bg-red-50` is correct in light and unreadable once the ground inverts, so
   the deep end of each coloured ramp lightens to match. */
html[data-color-scheme="dark"] .text-red-900,
html[data-color-scheme="dark"] .text-red-800,
html[data-color-scheme="dark"] .text-red-700    { color: #FCA5A5; }
html[data-color-scheme="dark"] .text-rose-800,
html[data-color-scheme="dark"] .text-rose-700   { color: #FDA4AF; }
html[data-color-scheme="dark"] .text-orange-800,
html[data-color-scheme="dark"] .text-orange-700 { color: #FDBA74; }
html[data-color-scheme="dark"] .text-amber-900,
html[data-color-scheme="dark"] .text-amber-800,
html[data-color-scheme="dark"] .text-amber-700  { color: #FCD34D; }
html[data-color-scheme="dark"] .text-yellow-900,
html[data-color-scheme="dark"] .text-yellow-800,
html[data-color-scheme="dark"] .text-yellow-700 { color: #FDE047; }
html[data-color-scheme="dark"] .text-green-900,
html[data-color-scheme="dark"] .text-green-800,
html[data-color-scheme="dark"] .text-green-700  { color: #86EFAC; }
html[data-color-scheme="dark"] .text-emerald-800,
html[data-color-scheme="dark"] .text-emerald-700 { color: #6EE7B7; }
html[data-color-scheme="dark"] .text-teal-800,
html[data-color-scheme="dark"] .text-teal-700   { color: #5EEAD4; }
html[data-color-scheme="dark"] .text-blue-900,
html[data-color-scheme="dark"] .text-blue-800,
html[data-color-scheme="dark"] .text-blue-700   { color: #93C5FD; }
html[data-color-scheme="dark"] .text-indigo-900,
html[data-color-scheme="dark"] .text-indigo-800,
html[data-color-scheme="dark"] .text-indigo-700 { color: #A5B4FC; }
html[data-color-scheme="dark"] .text-purple-900,
html[data-color-scheme="dark"] .text-purple-800,
html[data-color-scheme="dark"] .text-purple-700 { color: #D8B4FE; }
html[data-color-scheme="dark"] .text-pink-800,
html[data-color-scheme="dark"] .text-pink-700   { color: #F9A8D4; }

/* Borders drawn from the same pale ramps. */
html[data-color-scheme="dark"] .border-red-100,
html[data-color-scheme="dark"] .border-red-200,
html[data-color-scheme="dark"] .border-blue-100,
html[data-color-scheme="dark"] .border-blue-200,
html[data-color-scheme="dark"] .border-green-100,
html[data-color-scheme="dark"] .border-green-200,
html[data-color-scheme="dark"] .border-amber-100,
html[data-color-scheme="dark"] .border-amber-200,
html[data-color-scheme="dark"] .border-yellow-100,
html[data-color-scheme="dark"] .border-yellow-200,
html[data-color-scheme="dark"] .border-purple-100,
html[data-color-scheme="dark"] .border-indigo-100,
html[data-color-scheme="dark"] .border-orange-100,
html[data-color-scheme="dark"] .border-teal-100,
html[data-color-scheme="dark"] .border-rose-100 { border-color: var(--border); }

/* The five inline `background:#f…` literals the sweep turned up. They are
   written as element style attributes, so only a token-backed override with
   `!important` can reach them — the alternative was editing five unrelated
   components to say the same thing five times. */
html[data-color-scheme="dark"] [style*="background:#fff7e6"],
html[data-color-scheme="dark"] [style*="background:#fef2f2"],
html[data-color-scheme="dark"] [style*="background:#fdf4dd"],
html[data-color-scheme="dark"] [style*="background:#f6f8fa"],
html[data-color-scheme="dark"] [style*="background:#F8FAFC"],
html[data-color-scheme="dark"] [style*="background: #fff"],
html[data-color-scheme="dark"] [style*="background:#fff\""] {
  background-color: var(--surface-raised) !important;
}

/* Batch 17 item 3 (#80) — the activity total beside its heading, so the
   scope chips have a visible effect without scrolling to the pager. */
.av3 .activity-head .sec-count {
  margin-inline-start: 8px;
  font-size: 12.5px; font-weight: 600;
  color: var(--arbah-ink-muted, #475569);
  background: var(--arbah-surface-sunken, #F8FAFC);
  border: 1px solid var(--arbah-border, #E2E8F0);
  border-radius: 999px; padding: 1px 8px;
  vertical-align: middle;
}
