/*
 * RiskTech UI — the one stylesheet (Phase 1 of docs/CI4-UI-Upgrade-Plan.md).
 * Extracted verbatim from the designer-signed-off mockup: docs/risktech-ui-mockup.html.
 * :root tokens are byte-identical to the mockup. Light mode is the default on purpose — the
 * mockup keys dark off [data-ui="dark"], not prefers-color-scheme, and Phase 1 ships light only;
 * the dark tokens are parked here (present, unused) per the plan's open decision #3.
 * Loaded LAST in the 4 chrome files so converted markup wins over Bootstrap/main.css.
 */
:root {
  --navy:#1A3C6E; --blue:#003399; --blue-dark:#012E89; --blue-link:#1B48A3; --blue-soft:#EAF0FB;
  --bg:#F4F6F9; --card:#FFFFFF; --line:#E3E7ED; --line-2:#CFD6E0;
  --text:#1C2430; --text-2:#4A5568; --muted:#667282;

  /* A11y (product-owner approved): six text-bearing tokens darkened to clear WCAG AA 4.5:1 against
     the background each one is actually rendered on — hue and saturation preserved, so the ramp
     order, the chip shapes and the chart identity are unchanged. Measured before -> after:
       --p-high     2.76 -> 4.65   --p-moderate/--warn 2.36 -> 4.56   --s-progress 2.77 -> 4.55
       --s-withdrawn 1.30 -> 4.55  --muted      3.41 -> 4.53
     (--s-withdrawn also fixes the near-invisible Withdrawn donut arc, which shares this token.)
     STILL OPEN for a designer pass: --line-2 borders at 1.46:1 — design judgement, not contrast maths.
     (The --s-complete/--p-low "same green, two meanings" collision listed here is now resolved, as a
     side effect rather than by design: the client's palette moved --s-complete to #167902 while
     --p-low kept #2E7D4F, so status-green and priority-green are no longer the same swatch.) */
  --p-extreme:#C62828; --p-high:#AC5408; --p-moderate:#8B6B04; --p-low:#2E7D4F; --p-mgmt:#5A6675;
  --p-extreme-bg:#FDECEA; --p-high-bg:#FDF0E3; --p-moderate-bg:#FBF4DF; --p-low-bg:#E8F4EC; --p-mgmt-bg:#EEF1F4;

  /* Status = lifecycle, a SEPARATE scale from priority on purpose.
     Deliberately contains no red/orange/amber, so a status can never be misread as a risk level.
     Six hues, each clearly distinct from its neighbours.

     Hues are the client's (Deb Crump, "RiskTech | Pie Graph Colourscheme", 2026-09). Note her scheme
     puts Outstanding on purple and On Hold on navy — the reverse of what this file carried — so the
     two swapped rather than merely restyled.

     --c-* below are the ARC colours and are her hexes EXACTLY. These --s-* are the same six hues used
     as chip TEXT, which is a different job: a filled arc only has to be visible, but text on a tint
     must clear WCAG AA 4.5:1. Three of her six do not, as text, on their own chip tint:
       Not Accepted #B7B7B7 1.84:1   In Progress #019EFF 2.46:1   Complete #1A8E02 3.56:1
     so those three are darkened along the same hue, exactly as the A11y note above did. The other
     three are her values untouched. Measured after: complete 4.55, outstanding 5.71, hold 9.59,
     progress 4.50, notacc 4.63, withdrawn 8.41. Chart identity is preserved because hue is. */
  --s-complete:#167902;
  --s-outstanding:#6A00FF;
  --s-progress:#006DB1;
  --s-hold:#002A85;
  --s-notacc:#686868;
  --s-withdrawn:#0E0E0E;

  /* Donut arcs — the client's palette verbatim. Split from --s-* so the graph can be exactly the
     signed-off artwork while the chips stay legible; before this they were one token doing both. */
  --c-complete:#1A8E02;
  --c-outstanding:#6A00FF;
  --c-hold:#002A85;
  --c-progress:#019EFF;
  --c-notacc:#B7B7B7;
  --c-withdrawn:#0E0E0E;

  --overdue:#C62828; --done:#2E7D4F; --warn:#8B6B04;
  --logo-filter:none;

  --r:10px;
  --shadow:0 1px 2px rgba(28,36,48,.05), 0 2px 8px -2px rgba(28,36,48,.07);
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
/* Dark tokens: parked, not wired to any toggle in Phase 1. Kept intact for whenever the open
   decision on dark mode (plan doc, "open decisions" #3) is made. */
:root[data-ui="dark"] {
  --navy:#0E1726; --blue:#0F2A5E; --blue-dark:#0B2050; --blue-link:#7FAAF0; --blue-soft:#1B2842;
  --bg:#0C1119; --card:#141C28; --line:#26303F; --line-2:#3A4657;
  --text:#EDF1F7; --text-2:#B4BECD; --muted:#8492A6;
  --p-extreme:#F1837A; --p-high:#F0A85B; --p-moderate:#E3C55C; --p-low:#6FC191; --p-mgmt:#9AA6B5;
  --p-extreme-bg:#2C1614; --p-high-bg:#2C2013; --p-moderate-bg:#2A2513; --p-low-bg:#13291D; --p-mgmt-bg:#1D2530;

  /* Same six client hues, lifted instead of darkened because the ground is dark. Still parked. */
  --s-complete:#1FAB02;
  --s-outstanding:#AA6EFF;
  --s-progress:#019EFF;
  --s-hold:#5D90FF;
  --s-notacc:#B7B7B7;
  --s-withdrawn:#D4D4D4;

  /* Arc palette, dark. NOT her hexes: #0E0E0E Withdrawn on a #141C28 card is the invisible-arc bug
     the A11y note above records fixing once already, so the two darkest are lifted to clear 3:1. */
  --c-complete:#1A8E02;
  --c-outstanding:#8229FF;
  --c-hold:#0655FF;
  --c-progress:#019EFF;
  --c-notacc:#B7B7B7;
  --c-withdrawn:#676767;

  --overdue:#F1837A; --done:#6FC191; --warn:#E3C55C;
  --logo-filter:brightness(0) invert(1);
  --shadow:0 1px 2px rgba(0,0,0,.4);
}

* { box-sizing:border-box; }
body { margin:0; background:var(--bg); color:var(--text); font-family:var(--sans);
       font-size:15px; line-height:1.6; -webkit-font-smoothing:antialiased; }
a { color:var(--blue-link); }
:focus-visible { outline:3px solid var(--blue-link); outline-offset:2px; border-radius:4px; }
/* Skip link (WCAG 2.4.1). Eight nav links precede <main> on every page; this is the first focusable
   element in the document so one Tab clears them. Positioned off-screen rather than display:none —
   display:none would remove it from the tab order and defeat the entire point. It lives inside
   .header-wrapper on purpose: that region is already excluded from the golden master's text/link
   walk, so a global chrome addition costs zero $TRANSFORMS entries. */
/* Visually hidden, still in the accessibility tree. Used where the design deliberately shows no
   text but assistive tech needs one (the dashboard's <h1>). NOT display:none — that hides it from
   AT too, which is the opposite of the point. */
.vh { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
      clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; }
.skip-link { position:absolute; left:-9999px; top:0; z-index:20; }
.skip-link:focus { left:12px; top:12px; padding:10px 14px; background:var(--card); color:var(--blue-link);
                   border:1px solid var(--blue-link); border-radius:8px; font-weight:600; text-decoration:none; }
@media (prefers-reduced-motion: reduce) { *,*::before,*::after { transition:none !important; animation:none !important; } }

.wrap { max-width:1120px; margin:0 auto; padding:0 24px; }

/* ---------- HEADER ---------- */
.logo-bar { background:var(--card); border-bottom:1px solid var(--line); }
/* P1-01 REVERTED (product owner): the client-supplied risktech-logo.png is used AS SUPPLIED — we do
   not re-cut their brand asset. It is a 215x80 lockup whose top 51 rows are a navy rectangle sitting
   flush on the cap-line of "RISK" (same colour, zero gap), which is why it reads as a bar across the
   wordmark at 58px/38px. That is a defect in the source artwork, not in this CSS: the fix is a
   properly-built lockup from the client. Original geometry restored below, bleed included.
   padding-block again: this element is also a .wrap, which owns the horizontal padding. Top padding
   is 0 on purpose so the navy block bleeds to the very top of the page, as on the current site. */
.logo-row { display:flex; align-items:center; gap:16px; padding-block:0 14px; }
.logo { display:flex; align-self:stretch; }
.logo img { height:58px; width:auto; display:block; filter:var(--logo-filter); }
.cobrand { display:flex; align-items:center; gap:11px; padding-left:16px; border-left:1px solid var(--line); }
.cobrand-mark { width:32px; height:32px; border-radius:6px; flex:none; background:var(--navy); color:#fff;
                display:grid; place-items:center; font-size:12px; font-weight:700; }
.cobrand-t { font-size:14px; font-weight:600; color:var(--text-2); line-height:1.3; display:block; }
.cobrand-s { font-size:12.5px; color:var(--muted); }
.who { margin-left:auto; display:flex; align-items:center; gap:8px; color:var(--muted); font-size:14px; }
.who b { color:var(--text-2); font-weight:600; }

.navbar { background:var(--blue); }
.navbar .wrap { display:flex; align-items:center; gap:1px; flex-wrap:wrap; }
.navbar a { color:#fff; opacity:.85; text-decoration:none; padding:13px 15px; font-size:14.5px; font-weight:500;
            transition:background .15s ease, opacity .15s ease; display:inline-flex; align-items:center; gap:7px; }
.navbar a:hover { opacity:1; background:rgba(255,255,255,.10); }
.navbar a[aria-current="page"] { opacity:1; background:var(--blue-dark); font-weight:600; }
.navbar .right { margin-left:auto; }
.navbar .right a { background:var(--blue-dark); }
/* QA-09: mobile hamburger. Hidden on desktop; on small screens it collapses the nav into a toggled
   vertical menu instead of wrapping to two rows. */
.nav-toggle { display:none; background:none; border:0; color:#fff; cursor:pointer; padding:11px 6px;
              align-items:center; }
.nav-pill { background:var(--overdue); color:#fff; border-radius:20px; padding:1px 7px; font-size:12px; font-weight:700; }

/* ---------- PAGE ---------- */
/* padding-block only — .wrap owns the horizontal padding, don't let the shorthand clobber it */
.page { padding-block:28px 44px; }
.crumbs { font-size:14px; color:var(--muted); margin-bottom:10px; }
.crumbs a { text-decoration:none; }
.crumbs a:hover { text-decoration:underline; }
.head { display:flex; align-items:center; gap:16px; flex-wrap:wrap; margin-bottom:22px; }
h1 { font-size:29px; font-weight:700; letter-spacing:-.02em; margin:0; text-wrap:balance; }
.head p { margin:3px 0 0; color:var(--muted); font-size:15px; }
.head-right { margin-left:auto; display:flex; gap:8px; flex-wrap:wrap; }

.btn { display:inline-flex; align-items:center; gap:7px; padding:10px 16px; min-height:44px;
       border:1px solid var(--line-2); border-radius:8px; background:var(--card); color:var(--text-2);
       font:inherit; font-size:14.5px; font-weight:600; cursor:pointer; text-decoration:none;
       transition:background .15s ease, border-color .15s ease, color .15s ease; }
.btn:hover { background:var(--bg); border-color:var(--muted); color:var(--text); }
.btn-primary { background:var(--blue); border-color:var(--blue); color:#fff; }
.btn-primary:hover { background:var(--blue-dark); border-color:var(--blue-dark); color:#fff; }
.btn-quiet { border-color:transparent; background:none; padding:8px 10px; min-height:40px; }
.btn-quiet:hover { background:var(--bg); border-color:var(--line); }
/* P2-09: .danger used to be a HOVER-only colour change, so at rest "Delete Location" was pixel-for-pixel
   "Edit Location" and the two sat 4px apart — the review's "destructive actions are visually identical
   to safe ones". It now reads destructive without being hovered. One rule, every destructive control in
   the build (three lists, the comment cards, the attachment remove). */
.btn-quiet.danger { color:var(--overdue); }
.btn-quiet.danger:hover { color:var(--overdue); background:var(--p-extreme-bg);
                          border-color:color-mix(in srgb, var(--overdue) 25%, transparent); }
/* ...and it is set apart from the safe actions beside it rather than being the next item in the row. */
.acts-danger { display:inline-flex; align-items:center; margin-left:6px; padding-left:8px;
               border-left:1px solid var(--line); }

.card { background:var(--card); border:1px solid var(--line); border-radius:var(--r); box-shadow:var(--shadow); }
.card-h { padding:18px 20px 0; display:flex; align-items:flex-start; gap:12px; }
.card-h h2 { margin:0; font-size:17px; font-weight:700; letter-spacing:-.01em; }
.card-h p { margin:2px 0 0; color:var(--muted); font-size:13.5px; }
.card-h .btn { margin-left:auto; padding:6px 12px; min-height:36px; font-size:13.5px; }
/* only the FIRST button gets the auto margin — otherwise the free space splits
   between them and they drift apart instead of sitting together on the right */
.card-h .btn + .btn { margin-left:8px; }
.card-b { padding:18px 20px; }

/* ---------- SUMMARY ---------- */
.summary { display:grid; grid-template-columns:repeat(3,1fr); gap:14px; margin-bottom:22px; }
.sum { background:var(--card); border:1px solid var(--line); border-radius:var(--r); box-shadow:var(--shadow);
       padding:18px 20px; display:flex; align-items:center; gap:16px; text-decoration:none; color:inherit;
       transition:border-color .15s ease, transform .15s ease; }
.sum:hover { border-color:var(--line-2); transform:translateY(-1px); }
.sum-icon { width:44px; height:44px; border-radius:10px; flex:none; display:grid; place-items:center; }
.sum-n { font-size:30px; font-weight:700; line-height:1.1; letter-spacing:-.02em; display:block; }
.sum-l { font-size:14.5px; color:var(--text-2); font-weight:600; display:block; }
.sum-note { font-size:13px; color:var(--muted); }

.cols { display:grid; grid-template-columns:minmax(0,1.35fr) minmax(0,1fr); gap:18px; }
.stack { display:flex; flex-direction:column; gap:18px; }

/* ---------- ROWS ---------- */
.attn { border-top:1px solid var(--line); margin-top:16px; }
/* Overdue work-queue row. Grid: a body column (title on one line, a meta line of priority/status
   chips + location below) that TRUNCATES instead of wrapping to one-word-per-line, and a fixed trail
   (urgency badge + chevron) pinned right. Stays clean in the narrow dashboard column and on mobile. */
.attn .qrow { display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:12px 14px;
              padding:13px 20px; border-bottom:1px solid var(--line);
              text-decoration:none; color:inherit; transition:background .13s ease; }
.attn .qrow:last-child { border-bottom:0; }
.attn .qrow:hover { background:var(--bg); }
.attn .qrow-body { min-width:0; display:flex; flex-direction:column; gap:6px; }
/* P3-04: one line + ellipsis cut "Sprinkler Block Plan and Mai…" mid-word — a recommendation title is
   the only thing identifying the row, and the truncated half was 45px short of fitting. Two lines
   before the ellipsis; still clamped, so a long title can never push the queue out of the column. */
.attn .qrow-t { font-weight:600; color:var(--text); overflow:hidden;
                display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2; }
.attn .qrow-meta { display:flex; align-items:center; flex-wrap:wrap; gap:7px; }
.attn .qrow-loc { font-size:13px; color:var(--muted); }
.attn .qrow-trail { display:flex; align-items:center; gap:9px; flex:none; }
.chev { color:var(--muted); flex:none; }

/* ---------- PRIORITY / STATUS ---------- */
.pri { display:inline-flex; align-items:center; gap:6px; padding:3px 10px; border-radius:20px;
       font-size:13px; font-weight:600; white-space:nowrap; border:1px solid transparent; }
.pri::before { content:""; width:7px; height:7px; border-radius:50%; background:currentColor; flex:none; }
.pri-extreme  { color:var(--p-extreme);  background:var(--p-extreme-bg);  border-color:color-mix(in srgb, var(--p-extreme) 25%, transparent); }
.pri-high     { color:var(--p-high);     background:var(--p-high-bg);     border-color:color-mix(in srgb, var(--p-high) 25%, transparent); }
.pri-moderate { color:var(--p-moderate); background:var(--p-moderate-bg); border-color:color-mix(in srgb, var(--p-moderate) 25%, transparent); }
.pri-low      { color:var(--p-low);      background:var(--p-low-bg);      border-color:color-mix(in srgb, var(--p-low) 25%, transparent); }
.pri-mgmt     { color:var(--p-mgmt);     background:var(--p-mgmt-bg);     border-color:color-mix(in srgb, var(--p-mgmt) 25%, transparent); }
.st { font-size:14px; color:var(--text-2); white-space:nowrap; }

/* Status chip — same shape as .pri, driven by the status scale (--s-*). The mockup only ever
   shows status as plain text (.st), but ui_helper.php's status_chip() needs a chip to satisfy
   "chips carry text as well as colour, never colour alone" for status too, not just priority.
   Uses only the locked --s-* tokens already above; no new hexes. */
.stat { display:inline-flex; align-items:center; gap:6px; padding:3px 10px; border-radius:20px;
        font-size:13px; font-weight:600; white-space:nowrap; border:1px solid transparent; }
.stat::before { content:""; width:7px; height:7px; border-radius:50%; background:currentColor; flex:none; }
.stat-complete    { color:var(--s-complete);    background:color-mix(in srgb, var(--s-complete) 14%, var(--card));    border-color:color-mix(in srgb, var(--s-complete) 25%, transparent); }
.stat-outstanding { color:var(--s-outstanding); background:color-mix(in srgb, var(--s-outstanding) 10%, var(--card)); border-color:color-mix(in srgb, var(--s-outstanding) 25%, transparent); }
.stat-progress    { color:var(--s-progress);    background:color-mix(in srgb, var(--s-progress) 14%, var(--card));    border-color:color-mix(in srgb, var(--s-progress) 25%, transparent); }
.stat-hold        { color:var(--s-hold);        background:color-mix(in srgb, var(--s-hold) 14%, var(--card));       border-color:color-mix(in srgb, var(--s-hold) 25%, transparent); }
.stat-notacc      { color:var(--s-notacc);      background:color-mix(in srgb, var(--s-notacc) 14%, var(--card));     border-color:color-mix(in srgb, var(--s-notacc) 25%, transparent); }
/* 35%/45% was tuned for the old slate --s-withdrawn (#3C5169), which needed the extra weight to read
   at all. Against the client's near-black it produced a dark grey block twice the weight of the other
   five chips. Same 14%/25% as its neighbours now; still 14.4:1, since the hue does the work. */
.stat-withdrawn   { color:var(--s-withdrawn);   background:color-mix(in srgb, var(--s-withdrawn) 14%, var(--card));  border-color:color-mix(in srgb, var(--s-withdrawn) 25%, transparent); }

.due { font-size:14px; color:var(--text-2); white-space:nowrap; }
.due.late { color:var(--overdue); font-weight:700; }
/* P3-16: the date above already refuses to wrap, but the overdue sub-line under it did — so every
   overdue row read "17 mths" / "overdue" on two lines, orphaning the one word that carries the
   meaning. Measured at 1440px on /recommendation/index/7: the sub-line was 42px (two 20.8px lines)
   and is 21px now; the auto-layout table absorbs it by widening Due from 117px to 137px, with no
   cell overflow and no horizontal page scroll. Scoped to the sub-line that follows a .due, so the
   other .t-sub users (the location cards' address line) still wrap as before. */
.due + .t-sub { white-space:nowrap; }
.tag-late { display:inline-flex; align-items:center; gap:5px; background:var(--p-extreme-bg); color:var(--overdue);
            border:1px solid color-mix(in srgb, var(--overdue) 25%, transparent);
            padding:2px 9px; border-radius:20px; font-size:12.5px; font-weight:700; white-space:nowrap; }
.tag-ok { display:inline-flex; align-items:center; gap:5px; background:var(--p-low-bg); color:var(--done);
          border:1px solid color-mix(in srgb, var(--done) 25%, transparent);
          padding:2px 9px; border-radius:20px; font-size:12.5px; font-weight:700; white-space:nowrap; }

/* ---------- BARS / DONUT ---------- */
.bars { display:flex; flex-direction:column; gap:13px; }
.bar { display:grid; grid-template-columns:150px 1fr 28px; align-items:center; gap:12px; }
.bar.wide { grid-template-columns:270px 1fr 28px; }
.bar .l { font-size:14px; color:var(--text-2); }
.bar .track { display:block; height:10px; background:var(--bg); border:1px solid var(--line);
              border-radius:6px; overflow:hidden; }
/* display:block matters — a bare span would stay inline and drop width/height.
   No min-width: a zero count must render as nothing, not a sliver. */
.bar .fill { display:block; height:100%; border-radius:6px; }
.bar .n { text-align:right; font-weight:700; font-size:14px; }
.axis { display:flex; justify-content:space-between; font-size:12px; color:var(--muted); margin-top:10px;
        padding-left:162px; padding-right:40px; }
.bars.wide-bars ~ .axis { padding-left:282px; }

.donut-row { display:flex; align-items:center; gap:22px; flex-wrap:wrap; }
.donut { position:relative; flex:none; }
.donut svg { transform:rotate(-90deg); display:block; }
.donut-mid { position:absolute; inset:0; display:grid; place-content:center; text-align:center; }
.donut-mid .n { font-size:24px; font-weight:700; letter-spacing:-.02em; }
.donut-mid .l { font-size:12.5px; color:var(--muted); }
.keys { flex:1; min-width:168px; display:flex; flex-direction:column; gap:7px; }
.key { display:flex; align-items:center; gap:9px; font-size:14px; }
.key .sw { width:11px; height:11px; border-radius:3px; flex:none; }
.key .nm { color:var(--text-2); }
.key .vl { margin-left:auto; font-weight:700; }
/* P1-05: horizontal key under a bar chart whose fill colour encodes a category rather than the
   value beside it. Reuses .key/.sw so there is still one swatch treatment in the build. */
.chart-legend { display:flex; flex-wrap:wrap; align-items:center; gap:6px 15px; margin-top:6px;
                padding-top:12px; border-top:1px solid var(--line); }
.chart-legend .key { font-size:13px; gap:7px; }
.chart-legend .lg-t { font-size:13px; color:var(--muted); }

/* ---------- NEWS ---------- */
.news { display:flex; gap:14px; padding:16px 20px; border-bottom:1px solid var(--line); }
.news:last-child { border-bottom:0; }
.news-img { width:82px; height:60px; border-radius:8px; flex:none; background:var(--blue-soft);
            display:grid; place-items:center; color:var(--blue-link); }
.news h3 { margin:0 0 2px; font-size:15px; font-weight:650; line-height:1.35; }
.news h3 a { color:var(--text); text-decoration:none; }
.news h3 a:hover { color:var(--blue-link); }
.news .m { font-size:13px; color:var(--muted); }

/* ---------- PLANNED MAINTENANCE ---------- */
.qmaint { padding:14px 20px; border-bottom:1px solid var(--line); }
.qmaint:last-child { border-bottom:0; }
.qmaint-h { display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
.qmaint-t { font-weight:600; color:var(--text); }
.qmaint-w { margin-top:4px; font-size:13.5px; color:var(--text-2); }
.qmaint-d { margin-top:3px; font-size:13px; color:var(--muted); }
.qmaint-empty { padding:16px 20px; color:var(--muted); font-size:14px; }

/* ---------- TABLE ---------- */
/* Walkthrough #8: every horizontally scrolling table hid its own row actions with no hint that it
   scrolled — no scrollbar at rest, no shadow, nothing. On Admin > Current Users that put the ONLY
   action (Edit) off-screen at 1440; the same thing hides the overdue list's actions at 768. Fixed
   once, on the shared container, not per table.
   The `local` gradients are the page background painted over each edge; the `scroll` ones are the
   shadows underneath. When the content is scrolled to an edge the local layer sits on top of the
   shadow and hides it, so a shadow appears exactly when there is more content that way. Pure CSS,
   no JS, no scroll listener. */
.scroll { overflow-x:auto;
          background:
            linear-gradient(to right,  var(--card), transparent)  left  / 24px 100% no-repeat local,
            linear-gradient(to left,   var(--card), transparent)  right / 24px 100% no-repeat local,
            radial-gradient(farthest-side at 0    50%, rgba(28,36,48,.16), transparent) left  / 12px 100% no-repeat scroll,
            radial-gradient(farthest-side at 100% 50%, rgba(28,36,48,.16), transparent) right / 12px 100% no-repeat scroll; }
table { width:100%; border-collapse:collapse; }
th { text-align:left; font-size:13px; font-weight:700; color:var(--muted);
     padding:12px 16px; border-bottom:1px solid var(--line); white-space:nowrap; }
th button { background:none; border:0; font:inherit; color:inherit; cursor:pointer;
            display:inline-flex; align-items:center; gap:5px; padding:0; }
th button:hover { color:var(--text); }
/* P1-17: every sortable header label is a real <button>, so the column is in the tab order and
   Enter/Space sort it. aria-sort on the <th> is what a screen reader announces; this caret is the
   sighted half of the same signal, so sort state is never conveyed by position alone. ::after is
   CSS-generated content — not in the DOM, so it cannot reach the golden-master extractor. */
th[aria-sort] button { color:var(--text); }
th[aria-sort] button::after { content:"\25B2"; font-size:9px; line-height:1; }
th[aria-sort="descending"] button::after { content:"\25BC"; }
td { padding:14px 16px; border-bottom:1px solid var(--line); vertical-align:middle; }
tbody tr:last-child td { border-bottom:0; }
tbody tr { transition:background .13s ease; }
tbody tr:hover { background:var(--bg); }
.t-title { font-weight:600; }
.t-title a { color:var(--text); text-decoration:none; }
.t-title a:hover { color:var(--blue-link); text-decoration:underline; }
.t-sub { font-size:13px; color:var(--muted); }
.rec-no { color:var(--muted); font-size:14px; white-space:nowrap; }
/* P1-04: the cross-location drill-down carries NINE columns — Location and Insurer that the
   per-location list doesn't need (the client's brief keeps Insurer), plus the new Due. At the
   shared 16px cell padding that pushed Status, Replies and Actions off the right edge of a
   1440px window. Tightening the inline padding and letting the one long Rec. No. wrap brings all
   nine back inside .wrap; .scroll still covers narrower viewports. Scoped to the tables that opt
   in, so no other list changes. */
table.dense th, table.dense td { padding-inline:9px; }
table.dense .rec-no { white-space:normal; }
.acts { display:flex; gap:2px; justify-content:flex-end; }

/* ---------- FILTERS ---------- */
.filters { display:flex; align-items:flex-end; gap:12px; flex-wrap:wrap; padding:16px 20px;
           border-bottom:1px solid var(--line); background:var(--bg); border-radius:var(--r) var(--r) 0 0; }
/* The CI3 filter forms wrap their fields in a <form> (needed for the POST). Without this, that form
   is the sole flex child of .filters and its .f fields stack vertically instead of sitting in one
   row. display:contents lets the .f children lay out as direct flex items of .filters — form
   submission is unaffected (contents changes rendering only, not the DOM/submit semantics). */
.filters > form { display:contents; }
.f { display:flex; flex-direction:column; gap:5px; }
.f label { font-size:13px; font-weight:600; color:var(--text-2); }
/* P1-06: input[type="month"] added — the estimated-completion month box is on screen now that the
   Yes/No defaults from the stored value, and it was inheriting the bare UA control.
   P2-02: input[type="datetime-local"] added. It was the ONE native type this list omitted, so the
   five most important fields in the product (impairment Date Off-Line / Expected Restoration Date,
   and the maintenance editor's three windows) rendered as a bare 1px-black ~20px monospace UA
   control next to 44px rounded siblings — and, being unstyled, kept the UA's wide intrinsic width,
   which is what pushed the impairment form past the viewport on a phone. `font:inherit` is what
   fixes both at once: it drops the UA monospace AND the intrinsic width that came with it.
   min-width:0 lets it shrink inside a grid track rather than forcing the track wider than the
   screen (`1fr` = `minmax(auto,1fr)`, and `auto` floors at the control's min-content width). */
select, input[type="text"], input[type="date"], input[type="datetime-local"], input[type="month"], input[type="time"], input[type="email"], input[type="tel"], input[type="password"] {
  padding:10px 12px; min-height:44px; border:1px solid var(--line-2); border-radius:8px;
  background:var(--card); color:var(--text); font:inherit; font-size:14.5px; min-width:0;
}
select { padding-right:34px; cursor:pointer; appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%237A8697' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 11px center; }
/* P1-16 — the global focus ring, restored. This rule used to read
   `input:focus, select:focus, textarea:focus { ... outline:none; box-shadow:0 0 0 3px var(--blue-soft) }`.
   Two defects in one line:
     1. specificity 0,1,1 beat the global `:focus-visible` ring (0,1,0, top of this file), so
        `outline:none` removed the only high-contrast indicator from EVERY form control in the app;
     2. the replacement halo, --blue-soft #EAF0FB on --card #FFFFFF, measures 1.14:1 — invisible.
        Checkboxes and radios have no border to recolour either, so the 24 impairment check radios
        and the 28 report-wizard checkboxes had no focus indicator at all. WCAG 2.4.7 / 2.4.11.
   Fixed by (a) dropping `outline:none` so the global 3px --blue-link outline paints again —
   #1B48A3 on white is 8.39:1, and it is what marks focus on every control type including the
   borderless ones; (b) keying off :focus-visible so a mouse click on a select or a checkbox does
   not draw the ring (per spec, text fields and textareas still match :focus-visible on mouse focus,
   so the design's existing "click a text box, it goes blue" behaviour is unchanged); (c) raising
   the halo from --blue-soft to a 60% --blue-link mix — 3.16:1 on white, clearing WCAG 2.4.11's
   3:1 floor on its own, so the indicator survives even where a UA suppresses the outline.
   Mixed against --card, not a literal, so the dark token set stays correct with no second rule. */
input:focus-visible, select:focus-visible, textarea:focus-visible {
  border-color:var(--blue-link); box-shadow:0 0 0 3px color-mix(in srgb, var(--blue-link) 60%, var(--card));
}
.f-right { margin-left:auto; display:flex; align-items:flex-end; gap:10px; }
.count { font-size:14px; color:var(--muted); }
.count b { color:var(--text); }
.toggles { display:flex; gap:7px; flex-wrap:wrap; }
.toggles button { padding:9px 14px; min-height:40px; border-radius:20px; border:1px solid var(--line-2);
  background:var(--card); color:var(--text-2); font:inherit; font-size:14px; font-weight:600; cursor:pointer;
  display:inline-flex; align-items:center; gap:6px; transition:all .15s ease; }
.toggles button:hover { border-color:var(--muted); color:var(--text); }
.toggles button[aria-pressed="true"] { background:var(--blue-soft); border-color:var(--blue-link); color:var(--blue-link); }
.toggles .cnt { font-weight:700; }

/* ---------- DETAIL ---------- */
.detail { display:grid; grid-template-columns:minmax(0,1.7fr) minmax(0,1fr); gap:18px; align-items:start; }
.prose p { margin:0 0 14px; color:var(--text-2); line-height:1.7; font-size:15px; }
.prose h3 { margin:24px 0 8px; font-size:15px; font-weight:700; color:var(--text); }
.prose h3:first-child { margin-top:0; }
/* P2-04: the recommendation body had no heading of its own and sat below an <h2> for the reply form,
   so the data-entry control outranked the content. Section headings inside .prose now match .card-h's
   so the two read as peers, and the body copy is set at reading size rather than smaller than the form. */
.prose h2 { margin:26px 0 10px; font-size:17px; font-weight:700; letter-spacing:-.01em; color:var(--text); }
.prose h2:first-child { margin-top:0; }

.banner { display:flex; align-items:center; gap:11px; border-radius:8px; padding:12px 15px; margin-bottom:20px;
          font-weight:700; font-size:14.5px; }
.banner span { font-weight:400; color:var(--text-2); }
.banner-red { background:var(--p-extreme-bg); border:1px solid color-mix(in srgb, var(--overdue) 30%, transparent); color:var(--overdue); }
.banner-amber { background:var(--p-moderate-bg); border:1px solid color-mix(in srgb, var(--warn) 35%, transparent); color:var(--warn); }

.facts { list-style:none; margin:0; padding:0; }
.facts li { display:flex; align-items:center; gap:12px; padding:11px 0; border-bottom:1px solid var(--line); }
.facts li:last-child { border-bottom:0; }
.facts .k { color:var(--muted); font-size:14px; width:112px; flex:none; }
.facts .v { font-weight:600; font-size:14.5px; }
/* P2-06: the two detail panels (recommendation_view, impairments_viewdetails) are layout <table>s that
   the golden master compares row-for-row — table[i].row/.rowcount/.order are HARD_GATED, so swapping
   the element for a <ul class="facts"> is not something a $TRANSFORMS entry may excuse. They keep the
   <table> + role="presentation" and borrow the list's look here, reusing .k/.v unchanged. */
table.facts { width:100%; border-collapse:collapse; }
table.facts td { padding:11px 0; border-bottom:1px solid var(--line); vertical-align:middle; }
table.facts tr:last-child td { border-bottom:0; }
table.facts td.k { padding-right:12px; }
/* P2-05: inline "change one field and save it" control inside a detail panel. */
.inline-edit { display:flex; align-items:center; gap:6px; }
.inline-edit select { min-width:0; }

textarea { width:100%; min-height:108px; resize:vertical; padding:13px 14px; border:1px solid var(--line-2);
           border-radius:8px; background:var(--card); color:var(--text); font:inherit; font-size:15px; line-height:1.6; }
textarea::placeholder, input::placeholder { color:var(--muted); }
.reply-foot { display:flex; align-items:center; gap:10px; margin-top:12px; flex-wrap:wrap; }
.thread { margin-top:26px; display:flex; flex-direction:column; gap:12px; }
.msg { background:var(--bg); border:1px solid var(--line); border-radius:8px; padding:13px 15px; }
.msg-h { display:flex; align-items:center; gap:8px; font-size:13.5px; color:var(--muted); margin-bottom:4px; }
.msg-h b { color:var(--text-2); font-weight:600; }
.msg p { margin:0; color:var(--text-2); font-size:14.5px; }
/* P2-07: row actions belong AFTER the content they act on, not above it. Right-aligned so Delete is
   not the first thing under the reply text; .btn-quiet.danger already colours it apart from Edit. */
.msg-f { display:flex; align-items:center; justify-content:flex-end; gap:6px; margin-top:10px;
         padding-top:9px; border-top:1px solid var(--line); }
.msg-f form { margin:0; }

/* ---------- FORMS ---------- */
.form-grid { display:grid; grid-template-columns:1fr 1fr; gap:18px; }
.field { display:flex; flex-direction:column; gap:6px; }
.field.full { grid-column:1 / -1; }
.field > label { font-size:14px; font-weight:600; color:var(--text); }
.field .hint { font-size:13px; color:var(--muted); }
.field .req { color:var(--overdue); }
.field input, .field select, .field textarea { width:100%; }
fieldset { border:0; padding:0; margin:0 0 26px; }
fieldset > legend { font-size:16px; font-weight:700; padding:0; margin-bottom:4px; }
fieldset > .legend-note { font-size:13.5px; color:var(--muted); margin:0 0 16px; }
.maint-row { border:1px solid var(--line); border-radius:var(--r); padding:14px 16px 4px; margin-bottom:14px; }
.maint-row-h { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:10px; }
/* P3-26: this row heading was the app's ONLY uppercase, letter-spaced label, and .maint-new turned it
   blue — a third label style used on exactly one screen. It now matches `.field > label` and
   `fieldset.field > legend`, the style every other label on every other form already uses. The add
   row keeps its dashed border and tinted background (.maint-new), which is what actually marks it
   out; the blue uppercase was a second, redundant signal wearing a one-off style to send it. */
.maint-row-t { font-size:14px; font-weight:600; color:var(--text); }
.maint-remove-lbl { display:inline-flex; align-items:center; gap:7px; font-size:13.5px; font-weight:600; color:var(--overdue); cursor:pointer; }
.maint-remove-lbl input { width:16px; height:16px; accent-color:var(--overdue); cursor:pointer; }
.maint-new { border-style:dashed; border-color:var(--line-2); background:var(--bg); }

/* Yes/No segmented control — 44px targets, no tiny radios */
.checks { display:flex; flex-direction:column; gap:2px; }
.check { display:grid; grid-template-columns:1fr auto; gap:16px; align-items:center;
         padding:14px 0; border-bottom:1px solid var(--line); }
.check:last-of-type { border-bottom:0; }
.check-q { font-size:14.5px; color:var(--text); }
.check-q b { color:var(--muted); font-weight:600; margin-right:7px; }
.yn { display:flex; gap:0; border:1px solid var(--line-2); border-radius:8px; overflow:hidden; flex:none; }
.yn button { padding:9px 18px; min-height:42px; min-width:62px; border:0; background:var(--card);
             color:var(--text-2); font:inherit; font-size:14px; font-weight:600; cursor:pointer;
             transition:background .13s ease, color .13s ease; }
.yn button + button { border-left:1px solid var(--line-2); }
.yn button:hover { background:var(--bg); }
.yn button[aria-pressed="true"][data-v="y"] { background:var(--p-low-bg); color:var(--done); }
.yn button[aria-pressed="true"][data-v="n"] { background:var(--p-extreme-bg); color:var(--overdue); }
.check-why { grid-column:1 / -1; margin-top:-4px; }
.check-why[hidden] { display:none; }
.check-why input { width:100%; }

/* Safety-check radio pairs (Phase 4, impairments' 12 checks) — .check-set combines with
   .choice-set (flex-wrap, above) so unwrapped radios can use a plain :checked ~ sibling
   selector for progressive disclosure with zero JS (no :has() dependency). Distinct from
   .check/.yn/.check-why above, which is the JS-toggled button variant. */
/* P2-22: .choice-set sets no align-items, so the default `stretch` left each bare 18px radio pinned
   to the TOP of its 44px-tall label — measured 13px above the label's text baseline, so all 24
   controls read as "label below and to the right of its button". align-self on the radio (not
   align-items on the container) keeps the full-width .check-comment row below unaffected. */
.check-set input[type="radio"] { width:18px; height:18px; accent-color:var(--blue); margin:0 6px 0 12px; align-self:center; }
.check-set label.choice { padding:6px 10px; }
.check-comment { display:none; flex:1 1 100%; margin-top:8px; }
.check-no:checked ~ .check-comment { display:block; }

/* Server-side validation error banner — same red as .banner-red, no new colour. */
.form-errors { background:var(--p-extreme-bg); border:1px solid color-mix(in srgb, var(--overdue) 30%, transparent);
               color:var(--overdue); border-radius:8px; padding:12px 16px; margin:0 0 16px; font-weight:600; }
.form-errors ul { margin:0; padding-left:18px; }

.form-actions { display:flex; gap:10px; align-items:center; padding-top:20px; border-top:1px solid var(--line); flex-wrap:wrap; }

/* QA-05: recommendation "Estimated Completion Date" — the date wrapper is revealed only when "Yes".
   Rendered with the [hidden] attr from the server (works with JS off); app.js toggles it live. */
.est-when { margin-top:6px; }
.est-when[hidden] { display:none; }
/* P1-06: that block is a <fieldset class="field"> now. Drop the standalone-fieldset bottom margin
   (it sits in .form-grid, which owns the gap) and size the legend and the group question like the
   <label>s in the fields beside it, so the group reads as one field, not a new section. */
fieldset.field { margin:0; }
fieldset.field > legend { font-size:14px; font-weight:600; color:var(--text); margin:0; padding:0; }
.field > .q { font-size:14px; color:var(--text-2); margin:0; }
/* The month input is on screen now that the Yes/No defaults honestly, so it needs to stop being
   full-bleed and read like the Due Date box under it. */
.est-when input { max-width:280px; }

/* checkbox grid (report wizard) + inline radio group (Phase 4 forms — priority/status).
   Same choice-item look for both; .choice has no <span> wrapper so it carries the text
   styling itself. .choice input defeats .field input's width:100% (app.css:296). */
/* P2-23: the report wizard's "All" is a no-filter switch, not one of the values — it sits on its own
   row above the value grid, with a rule under it so the two read as different kinds of control. */
.cb-all { display:flex; align-items:center; gap:6px; flex-wrap:wrap; padding-bottom:8px; margin-bottom:8px;
          border-bottom:1px solid var(--line); }
.cb-all:empty { display:none; }
.hint { font-size:13px; color:var(--muted); }
.cbs { display:grid; grid-template-columns:repeat(auto-fill, minmax(190px, 1fr)); gap:2px; }
.choice-set { display:flex; flex-wrap:wrap; gap:2px; }
.cb, .choice { display:flex; align-items:center; gap:10px; padding:10px 12px; border-radius:8px;
      min-height:44px; cursor:pointer; transition:background .13s ease; }
.cb:hover, .choice:hover { background:var(--bg); }
.cb input, .choice input { width:18px; height:18px; accent-color:var(--blue); flex:none; margin:0; cursor:pointer; }
.cb span, .choice { font-size:14.5px; color:var(--text-2); font-weight:normal; }

/* ---------- LOGIN ---------- */
.login-shell { min-height:100dvh; display:grid; grid-template-columns:1fr 1fr; }
.login-left { display:flex; flex-direction:column; justify-content:center; padding:48px; background:var(--card); }
.login-box { width:100%; max-width:380px; margin:0 auto; }
.login-box .logo { margin-bottom:32px; display:block; }
.login-box .logo img { height:38px; filter:var(--logo-filter); } /* P1-01 reverted: client asset as supplied */
.login-box h1 { font-size:26px; margin-bottom:6px; }
.login-box .sub { color:var(--muted); font-size:15px; margin-bottom:26px; }
.login-fields { display:flex; flex-direction:column; gap:16px; }
/* Was `.login-fields .btn-primary`. Generalised to .btn so a stack of choices (the 2FA method
   list) goes full-width too; the 6px lead-in stays scoped to a submit <button>, which is what it
   was for — the method list is <a>, and there the gap alone is the right spacing. */
.login-fields .btn { justify-content:center; width:100%; }
.login-fields button.btn-primary { margin-top:6px; }

/* The five twofa_* views shipped with Bootstrap classes (.container, .btn-block, .form-control,
   .alert) that Phase 6 deleted, so they rendered flush-left and unstyled. They reuse the login
   card above; these three cover the content login has none of. */
.code-block { margin:0; padding:14px 16px; background:var(--bg); border:1px solid var(--line);
    border-radius:8px; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:15px;
    line-height:1.85; letter-spacing:.04em; white-space:pre-wrap; word-break:break-all; }
.qr-box { display:flex; justify-content:center; padding:18px; background:#fff;
    border:1px solid var(--line); border-radius:8px; }
.check-inline { display:flex; align-items:center; gap:9px; font-size:14px; color:var(--text-2); }
.check-inline input { width:auto; }
/* "Resend code" has to be a POST (it sends an email/SMS), but it is a secondary action sitting in a
   line of links. A <form>+<button> that looks like the link it behaves as, rather than a second
   full-width button competing with Verify. */
.inline-post { display:inline; }
.linklike { background:none; border:0; padding:0; font:inherit; color:var(--blue); cursor:pointer;
    text-decoration:underline; }
.linklike:hover { color:var(--blue-dark); }
.login-foot { margin-top:22px; font-size:14px; color:var(--muted); text-align:center; }
/* Right-hand navy panel of the split login (from the mockup; the base styles were never ported). */
.login-right { background:var(--navy); color:#fff; padding:48px; display:flex; flex-direction:column; justify-content:center; }
.login-right h2 { font-size:21px; margin:0 0 6px; color:#fff; }
.login-right > p { color:rgba(255,255,255,.6); margin:0 0 24px; font-size:14.5px; }
.lnews { border-top:1px solid rgba(255,255,255,.14); padding:16px 0; }
.lnews:last-child { border-bottom:1px solid rgba(255,255,255,.14); }
.lnews h3 { margin:0 0 3px; font-size:15.5px; font-weight:650; }
.lnews h3 a { color:#fff; text-decoration:none; }
.lnews h3 a:hover { text-decoration:underline; }
.lnews .m { font-size:13px; color:rgba(255,255,255,.5); }
/* (Deleted here: .login-empty — it styled the "No maintenance scheduled." line that P3-07 stopped
   rendering, and had no other consumer.) */
/* P3-07: with no notice to show, login_view drops the panel entirely rather than spending half the
   viewport on its empty state. One column, form still centred by .login-box's auto margins. */
.login-solo { grid-template-columns:1fr; }
/* (Deleted here: a verbatim second copy of the ten .login-right/.lnews rules above — same selectors,
   same declarations, no override. Dead bytes on every page's stylesheet.) */

/* ---------- LOCATION LIST ROWS + PAGER ---------- */
/* Location/Impairments index, search and viewdeleted render each row as a card, not a <table> row.
   Phase 6 deletes _refresh_styles.php (the only place these were styled) — ported forward here,
   on tokens, so removing that partial doesn't leave 4 views unstyled. */
/* List-item card: one aligned row — name/meta on the left (grows), actions on the right, both
   vertically centred. Wraps to stacked on narrow widths. .li-main groups the two text lines so
   they stack while .acts sits beside them. */
.location-item { border:1px solid var(--line); border-radius:var(--r); background:var(--card);
                  padding:15px 18px; margin-bottom:10px; box-shadow:var(--shadow);
                  display:flex; align-items:center; gap:10px 20px; flex-wrap:wrap; }
.location-item:hover { border-color:var(--line-2); }
.location-item .li-main { flex:1 1 260px; min-width:0; display:flex; flex-direction:column; gap:2px; }
.location-item .acts { margin-left:auto; flex:none; gap:4px; }
/* legacy fallback: views not yet wrapped in .li-main still read acceptably (text stacks, actions right) */
.location-item > .t-title, .location-item > .t-sub { flex:1 1 100%; }
.location-item > .acts { flex:0 0 auto; }

/* Client-side pager built by app.js's initPagination() — plain <ul class="pager"><li><a class="page_link">. */
.pagination { margin-top:14px; }
/* user_logs renders its OWN server-side pager as <ul class="pagination"><li class="active|disabled">
   — a second markup shape whose two state classes had no rule anywhere in this file, so a disabled
   Prev was pixel-identical to an enabled one (and still focusable; the view now also marks it
   aria-disabled + tabindex="-1"). Folded into the existing .pager selectors rather than given a
   parallel block: one pager look in the build. The state hangs off the <li> here and off the <a>
   in the JS pager, which is why those two selectors differ. */
.pager, ul.pagination { list-style:none; display:flex; gap:4px; flex-wrap:wrap; margin:0; padding:0; }
ul.pagination { justify-content:center; }
.pager a, ul.pagination a { display:inline-flex; align-items:center; justify-content:center; min-width:32px; height:32px;
           padding:0 8px; border-radius:6px; color:var(--blue-link); text-decoration:none;
           font-size:14px; font-weight:600; }
.pager a:hover, ul.pagination li:not(.disabled) a:hover { background:var(--bg); }
.pager a.active, ul.pagination li.active a { background:var(--navy); color:#fff; }
ul.pagination li.disabled a { color:var(--muted); cursor:default; }

/* ---------- ADMIN ---------- */
.admin-grid { display:grid; grid-template-columns:minmax(0,1.5fr) minmax(0,1fr); gap:18px; align-items:start; }
/* Walkthrough #8: a card placed DIRECTLY in the grid (rather than inside one of the two .stacks) is a
   full-width row. Current Users is the one that needs it — five columns plus a row action do not fit
   in a 1.5fr column, and what fell off the edge was the only way to edit a user. */
.admin-grid > .card { grid-column:1 / -1; }
/* QA-08: keep the users/divisions tables inside their card on desktop — tighter cells so the
   "User Status"/actions columns don't overflow the scroll boundary and clip. Only the email column
   is allowed to wrap (long addresses); action links must never break mid-word ("Ed it"). */
.admin-grid table th, .admin-grid table td { padding:11px 12px; }
.admin-grid table th:first-child, .admin-grid table td:first-child { padding-left:4px; }
/* P2-14: this was `overflow-wrap:anywhere`, which is what broke "rheywood@risktech.co / m.au" across
   two lines on 8+ rows. `anywhere` is the one wrap value that also shrinks the element's MIN-CONTENT
   width, so the auto table-layout algorithm was told the email column could be one character wide and
   duly starved it. `break-word` breaks the same long strings when they genuinely do not fit, but does
   not offer the layout algorithm a narrower minimum — so the column claims its longest address first
   and only breaks as a last resort. Root cause, not a width tweak. */
.admin-grid table td:nth-child(3) { overflow-wrap:break-word; }
/* P2-17: client-side row filter above a long list (admin's Current Users). Server-rendered hidden;
   app.js reveals it, so JS-off never sees a dead control. */
.list-filter { display:flex; align-items:center; gap:10px; padding:0 0 14px; flex-wrap:wrap; }
.list-filter label { font-size:13px; color:var(--text-2); font-weight:600; }
.list-filter input { max-width:280px; }
.admin-grid table td a { white-space:nowrap; }
.admin-grid .acts { justify-content:flex-start; }
.pill { display:inline-flex; align-items:center; gap:5px; padding:2px 9px; border-radius:20px;
        font-size:12.5px; font-weight:700; }
.pill-a { background:var(--p-low-bg); color:var(--done); }
.pill-d { background:var(--p-mgmt-bg); color:var(--p-mgmt); }
.tags { display:flex; gap:6px; flex-wrap:wrap; padding:4px 0; }
.tag { padding:5px 11px; border-radius:20px; background:var(--bg); border:1px solid var(--line);
       font-size:13.5px; color:var(--text-2); font-weight:500; }

/* Walkthrough #2: once a report has run, its filter form collapses to this one-line toggle so the
   results are not two screens down. Native <details> — no JS, no ARIA to get wrong, and it keeps
   the browser's own open/closed keyboard behaviour. */
.filter-fold { margin-bottom:18px; }
.filter-fold > summary { cursor:pointer; list-style:none; display:inline-flex; align-items:center; gap:8px;
                         padding:9px 14px; border:1px solid var(--line); border-radius:var(--r);
                         background:var(--card); box-shadow:var(--shadow);
                         font-size:13.5px; font-weight:600; color:var(--text-2); }
.filter-fold > summary::-webkit-details-marker { display:none; }
.filter-fold > summary::before { content:"\25B8"; color:var(--muted); }
.filter-fold[open] > summary { margin-bottom:14px; }
.filter-fold[open] > summary::before { content:"\25BE"; }
.filter-fold > summary:hover { color:var(--text); border-color:var(--line-2); }
.filter-fold > summary:focus-visible { outline:2px solid var(--blue-link); outline-offset:2px; }
/* Walkthrough #2: the result count. A <caption> is the table's own label, so it reads before the
   table for a screen reader as well as for the eye. */
table > caption { caption-side:top; text-align:left; padding:16px 20px 0;
                  font-size:14px; font-weight:600; color:var(--text-2); }

.empty { padding:44px 20px; text-align:center; color:var(--muted); }
.empty svg { color:var(--line-2); margin-bottom:8px; }

footer { border-top:1px solid var(--line); padding:22px 0 30px; text-align:center;
         font-size:13.5px; color:var(--muted); background:var(--card); }
footer a { text-decoration:none; font-weight:600; }
footer .abn { margin-top:4px; font-size:12.5px; }

@media (max-width: 900px) {
  .summary, .cols, .detail, .form-grid, .admin-grid, .login-shell { grid-template-columns:1fr; }
  /* P3-01 moved the maintenance card into the left stack to balance the two desktop columns. Once
     the columns stack, source order would put the Overdue queue — the only urgent card — last.
     Hoist the queue's stack back to the top: same DOM, the mobile dashboard keeps urgent-first. */
  .cols > .stack:last-child { order:-1; }
  .cobrand { display:none; }
  h1 { font-size:25px; }
  .login-right { display:none; }
  .check { grid-template-columns:1fr; }
  .yn { align-self:flex-start; }
  .axis, .bar.wide ~ .axis { padding-left:0; }
  .bar, .bar.wide { grid-template-columns:1fr; gap:6px; }
  .bar .n { text-align:left; }

  /* QA-09: collapse the nav behind the hamburger instead of wrapping to two rows */
  .nav-toggle { display:inline-flex; }
  .navbar .wrap { flex-direction:column; align-items:stretch; gap:0; }
  .navbar .wrap > a, .navbar .right { display:none; }
  .navbar.open .wrap > a { display:flex; }
  .navbar.open .right { display:block; margin-left:0; }
  .navbar .wrap > a, .navbar .right a { border-top:1px solid rgba(255,255,255,.12); }
  .navbar.open .right a { display:block; }
}
