/* ============================================================================
   RPS 2.0 — shared platform UI
   Loaded LAST on every page (after each shell's inline <style> and style.css),
   so this file is the single place to refresh the whole app's look.

   Design language: InfiniDesk UI guidelines — clean, flat, Apple-inspired.
   No elevation gimmicks, no decorative chrome, no shadow-* anywhere. Borders
   on white against a light gray page background carry all the contrast.

   Component overrides here are intentionally low-specificity (element / single
   class) — status modifiers (.tag.overdue …), id rules (#login-btn) and ghost
   button variants keep their higher specificity and are left untouched.
   ============================================================================ */

/* ============================================================================
   Type — Geist, self-hosted, variable.

   Vendored at build time by api/Dockerfile, same rule as MapLibre and driver.js:
   nothing on this platform fetches from a CDN at runtime. Paths are relative to
   this file, which is correct under CompressedStaticFilesStorage (no manifest
   hashing, so nothing rewrites them).

   TWO SUBSETS PER FAMILY, AND latin-ext IS LOAD-BEARING. Maltese is written with
   ħ ġ ċ ż Ħ Ġ Ċ Ż — every one of them in Latin Extended-A, none of them in the
   `latin` subset. The mt catalogue carries roughly 2,780 across 1,403 translated
   strings, so a latin-only build would drop to a fallback font mid-word on
   nearly every Maltese screen. The unicode-range on each face is what keeps the
   English UI from paying for the ext file it never needs.

   font-display:swap — a government form must be readable before it is pretty.
   ============================================================================ */
@font-face{
  font-family:"Geist Variable"; font-style:normal; font-weight:100 900; font-display:swap;
  src:url("vendor/fonts/geist-latin.a147f99cd533.woff2") format("woff2-variations");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:"Geist Variable"; font-style:normal; font-weight:100 900; font-display:swap;
  src:url("vendor/fonts/geist-latin-ext.4841e590e2a9.woff2") format("woff2-variations");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,
    U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,
    U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:"Geist Mono Variable"; font-style:normal; font-weight:100 900; font-display:swap;
  src:url("vendor/fonts/geist-mono-latin.a15f9d568fb0.woff2") format("woff2-variations");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:"Geist Mono Variable"; font-style:normal; font-weight:100 900; font-display:swap;
  src:url("vendor/fonts/geist-mono-latin-ext.693cf932fe6b.woff2") format("woff2-variations");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,
    U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,
    U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* ---------------------------------------------------------------------------
   The map's colours are NOT in this file, and must not be moved into it.

   `rwp.js`, `map.js`, `lesa*.js`, `ttm.js`, `digonce.js` and `road-picker.js`
   paint geometry in literal hex, and each of those colours is mirrored by a
   swatch in a template legend so the key and the line say the same thing. Two
   of them (#2B35FF, #0B0C2A) happen to equal the brand's retired blue and navy,
   which makes them look like palette drift. They are not: changing one means
   changing the JS and the legend together, and every one of those legends is
   inside a {% blocktranslate %} — editing the string drops its Maltese
   translation back to English. A ~4% hue shift on a map line is not worth that.
   Decide it once, deliberately, moving JS and templates in the same commit.
   --------------------------------------------------------------------------- */

/* ---- design tokens (both legacy naming schemes kept in sync) ------------- */
:root{
  --navy:#02023A; --blue:#1640FF; --blue-ink:#1233d1;
  --blue-light:#EFF6FF; --blue-mid:#BFDBFE;
  --bg:#F9FAFB; --background:#F9FAFB;
  --surface:#FFFFFF; --border:#E5E7EB; --border-soft:#F3F4F6;
  /* Secondary text. Was #9CA3AF, which scores only 2.56:1 on white — well below
     the 4.5:1 WCAG AA minimum, and reported as genuinely hard to read. #4B5563
     scores 7.5:1. This file loads after each module's inline <style>, so this
     value is what every module actually renders. */
  --muted:#4B5563;
  /* Only for de-emphasised furniture (footers, counts, timestamps) — 4.8:1.
     Not for prose. */
  --muted-soft:#6B7280;
  --success:#059669; --danger:#DC2626; --warning:#D97706;
  /* Module accents. These used to live in the shell that needed them, which is
     why they outlived the palette move — they are here so there is exactly one
     place a brand colour is written down. --purple is LESA's ANPR mark; the
     other three are the authority levels rwp prints on a rule. */
  --purple:#6C3FB5;
  --law:#7048E8; --guidance:#0C8599; --policy:#6B7280;
  /* The old system stack stays behind Geist as the fallback, so a font that
     fails to load leaves the platform rendering exactly as it did before rather
     than dropping to Times. Both names are kept because modules reference both. */
  --font:"Geist Variable","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,Arial,sans-serif;
  --font-sans:"Geist Variable","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,Arial,sans-serif;
  /* For things that are read as characters rather than as words: permit refs,
     registration marks, ledger amounts, hashes. */
  --font-mono:"Geist Mono Variable",ui-monospace,Menlo,Consolas,monospace;
  --radius:12px; --radius-sm:8px;
  --ring:0 0 0 2px rgba(22,64,255,.2);
  --tr:.15s ease;
  /* The design language has no elevation on the page — but three things float
     ABOVE a map (the picker panel, the live-data control, a closure popup) and
     need to read as lifted off it. Those shadows carry the navy rather than
     black, so the lift is the palette's own colour and not a grey wash. They
     were written as rgba(11,12,42,…) — the retired navy — in three files. */
  --shadow-navy:rgba(2,2,58,.14);
  --shadow-navy-firm:rgba(2,2,58,.18);
  --shadow-navy-deep:rgba(2,2,58,.22);
}

/* ---- base polish --------------------------------------------------------- */
body{
  font-family:var(--font);
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}

/* Headings. Geist is a touch narrower than the system stack these sizes were set
   against, so the tracking tightens with size rather than at h1 alone — the point
   of a heading is to be read as one shape, and loose tracking at 22px undoes that. */
h1{ letter-spacing:-.4px; font-weight:700; text-wrap:balance; }
h2{ letter-spacing:-.2px; font-weight:700; text-wrap:balance; }
h3{ letter-spacing:-.1px; font-weight:600; text-wrap:balance; }

/* Prose. `pretty` only forbids the single-word last line; it does not re-flow the
   whole paragraph the way `balance` would, which is what you want for body copy. */
p, .hint, .sub, .dash-note, .dash-lede, .legend-row p{ text-wrap:pretty; }
/* Roughly 65 characters. Long measures are the reason nobody finishes the note
   above a form. Only unconstrained prose — a table cell sets its own width. */
.hint, .sub, .dash-note, .dash-lede{ max-width:68ch; }

/* ---------------------------------------------------------------------------
   Figures that change in place.

   Proportional digits are set to look right inside a word; in a column they
   shimmer, because 1 is narrower than 8 and the number jumps sideways as it
   updates. Every surface below is one a reader compares down a column or watches
   refresh: table cells, stat tiles, dashboard rings, counts on tabs and badges.
   --------------------------------------------------------------------------- */
td, th,
.tile .n, .tile .l,
.hero-card .ring b, .work-row .w-age,
.count, .tab-count, .cnt, .rps-bell-badge, .rps-flag-badge,
.pagination, .score{
  font-variant-numeric:tabular-nums;
}

/* ---------------------------------------------------------------------------
   Set as characters, not as words.

   A permit reference, a registration mark and a ledger amount are read one glyph
   at a time and checked against a piece of paper. Mono makes that a comparison
   rather than a squint, and it is the one place on the platform where the type
   is allowed to be a different family. */
.mono, code, kbd, samp,
.permit-card .ref, .cl-ref,
.w-ref, .ref-cell, .plate{
  font-family:var(--font-mono); font-variant-numeric:tabular-nums;
}
/* Mono runs optically larger at the same px, so it is stepped down where it sits
   inline with body copy rather than in a column of its own. */
.permit-card .ref, .cl-ref, .w-ref{ font-size:.94em; letter-spacing:-.1px; }

/* ---------------------------------------------------------------------------
   Weight.

   Segoe UI ships 400/600/700, so the 47 rules asking for 800 and the 71 asking
   for 700 rendered as the same Bold — the hierarchy was written but never seen.
   Geist is variable across 100–900 and draws every one of them, so that intent
   now shows. Nothing was renumbered; what was missing was the step BELOW
   semibold. A label, a column head and a stat-tile caption are secondary text
   that still has to hold its own against the value beside it — 400 makes them
   recede too far, 600 makes them compete. 500 is the answer, and it did not
   exist anywhere on the platform before.
   --------------------------------------------------------------------------- */
th, .tile .l, .kv-label, .hint, .sub, .app-nav-note, .rps-bell-when, .cl-item-meta{
  font-weight:500;
}
/* Except where the label IS the control's name and sits above a field the reader
   is about to fill in — that one keeps its semibold. */
label{ font-weight:600; }

::selection{ background:var(--blue-mid); }

/* ---- cards & stat tiles: border only, no shadow, no hover elevation ------ */
.card{ border:1px solid var(--border); border-radius:var(--radius); box-shadow:none; }
.tile{ border:1px solid var(--border); border-radius:var(--radius); box-shadow:none; transition:border-color var(--tr); }
.tiles .tile:hover{ border-color:var(--blue-mid); }

/* ---- breathing room: stat-tile grids sit flush against whatever follows
   them (cards/.cols) with zero gap in every module's inline CSS - fix once,
   globally, instead of patching every dashboard template. -------------------- */
.grid.tiles{ margin-bottom:20px; }
.card h2, .box h3{ margin-top:0; margin-bottom:14px; }
.card table, .box table{ margin-top:2px; }
th, td{ padding:10px 12px; }

/* ---- buttons: flat, no shadow, no lift; darken-on-hover only -------------
   :not([role=tab]) — a tab is a button element but not a button control. Left in,
   this rule's radius rounded the ends of the selected tab's 2px underline, which
   read as the line curling up, and its font-weight outranked the tab styling. */
button:not(.linkish):not(.ghost):not(.no):not(.ok):not(.login-skip):not([role=tab]):not(.lang-opt),
.btn{ border-radius:var(--radius-sm); font-weight:700; box-shadow:none; transition:background var(--tr), filter var(--tr); }
button:not(.linkish):not(.ghost):not(.no):not(.ok):not(.login-skip):not([role=tab]):not(.lang-opt):hover,
.btn:hover{ filter:brightness(1.06); box-shadow:none; }
button:focus-visible, .btn:focus-visible, a.btn:focus-visible{ outline:none; box-shadow:var(--ring); }

/* ---- form controls: soft radius + soft focus ring ------------------------ */
input, select, textarea{ border-radius:var(--radius-sm); transition:border-color var(--tr), box-shadow var(--tr); }
input:focus, select:focus, textarea:focus{ outline:none; border-color:var(--blue); box-shadow:var(--ring); }
label{ letter-spacing:.1px; }

/* ---- tables: airier header, quiet gray-100 row dividers, subtle hover ---- */
th{ background:var(--bg); font-weight:700; text-transform:none; letter-spacing:normal; }
td, th{ border-color:var(--border-soft); }
tbody tr{ transition:background var(--tr); }
tbody tr:hover{ background:rgba(22,64,255,.035); }
table tr:last-child td{ border-bottom:none; }

/* ============================================================================
   Language switch — EN | MT, one segmented control, in every shell's top bar.

   Sized to sit beside the role pill without competing with it: it is a setting
   somebody touches once, not a navigation target. The selected side is filled so
   the current language is readable at a glance rather than inferred from which
   one looks clickable.
   ============================================================================ */
.lang-switch{
  display:inline-flex; margin:0; padding:2px; gap:2px;
  border:1px solid var(--border); border-radius:999px; background:var(--bg);
}
.lang-opt{
  border:none; background:none; cursor:pointer; padding:2px 9px; border-radius:999px;
  font:700 11px/1.6 var(--font); letter-spacing:.4px; color:var(--muted);
  transition:background var(--tr), color var(--tr);
}
.lang-opt:hover{ background:rgba(22,64,255,.08); color:var(--blue); }
.lang-opt.on{ background:var(--blue); color:#fff; }
.lang-opt.on:hover{ background:var(--blue-ink); color:#fff; }
.lang-opt:focus-visible{ outline:2px solid var(--blue); outline-offset:1px; box-shadow:none; }

/* On the navy header the same control has to read against a dark ground: the rail
   becomes a translucent white outline, and the selected side stays solid blue. */
.lang-switch.on-dark{ border-color:rgba(255,255,255,.22); background:rgba(255,255,255,.06); }
.lang-switch.on-dark .lang-opt{ color:rgba(255,255,255,.72); }
.lang-switch.on-dark .lang-opt:hover{ background:rgba(255,255,255,.14); color:#fff; }
.lang-switch.on-dark .lang-opt.on{ background:#fff; color:var(--navy); }
.lang-switch.on-dark .lang-opt:focus-visible{ outline-color:#fff; }

/* Screen-reader-only text: the switch's buttons say EN and MT, and a reader that
   cannot see the pair needs the language's own name. */
.visually-hidden{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* ---- module top bar: flat, no shadow ------------------------------------- */
header.top{ position:sticky; top:0; z-index:30; border-bottom:1px solid rgba(255,255,255,.08); }
header.top nav a{ padding:5px 11px; border-radius:9px; transition:background var(--tr); }
header.top nav a:hover{ background:rgba(255,255,255,.09); text-decoration:none; }
header.top nav a.active{ background:rgba(255,255,255,.15); color:#fff; border-bottom:none; }

/* ============================================================================
   Tabs — ONE look for the whole platform. This block is the only place tabs are
   styled; every module's own copy was deleted in favour of it, so a change here
   is a change everywhere.

   Two markup shapes exist and must read identically:
     .tabs > a                    link tabs — status filter rows on the queues,
                                  registers, notification centre, permit list
     [role=tablist] > button      ARIA tabs — the rwp proposal / review panels
   No pill and no box: one hairline runs under the whole strip and the selected
   tab sits on a thicker blue segment of that same line. The line is the whole
   affordance, which is why an unselected tab draws nothing at all.
   ============================================================================ */
.tabs,
.tablist{
  display:flex; align-items:stretch; gap:0; margin:0 0 16px; padding:0;
  background:none; border:none; border-bottom:1px solid var(--border); border-radius:0;
}
/* Filter rows are short and may wrap (a narrow sidebar holds six of them); a
   panel strip must not. Each tab therefore carries its own segment of the rail
   rather than relying on the strip's — otherwise the first of two wrapped rows
   floats with no line under it while the second gets one. */
.tabs{ flex-wrap:wrap; }
.tablist{
  flex-wrap:nowrap; overflow-x:auto; overflow-y:hidden;
  /* Sticky because a panel can be long and the strip is the way out of it. Both
     pages that use it sit on the page background, so the strip is seamless. */
  position:sticky; top:0; z-index:3; background:var(--bg);
}
.tabs > a,
.tablist > button{
  flex:0 0 auto; display:inline-flex; align-items:center; gap:7px;
  padding:9px 14px; margin-bottom:-1px;  /* the tab's rule lands ON the strip's */
  background:none; border:none; border-bottom:2px solid var(--border); border-radius:0;
  color:var(--muted); font:inherit; font-size:13px; font-weight:600; line-height:1.3;
  white-space:nowrap; text-decoration:none; cursor:pointer;
  transition:color var(--tr), border-color var(--tr);
}
.tabs > a:hover,
.tablist > button:hover{
  background:none; color:var(--navy); border-bottom-color:var(--muted-soft); text-decoration:none;
}
.tabs > a.active,
.tablist > button[aria-selected=true]{
  background:none; color:var(--blue); font-weight:800; border-bottom-color:var(--blue);
}
.tabs > a:focus-visible,
.tablist > button:focus-visible{ outline:2px solid var(--blue); outline-offset:-2px; box-shadow:none; }

/* Count on a tab: how much is behind it, so opening it is a decision not a guess. */
.tabs .tab-count,
.tablist .count{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:18px; height:18px; padding:0 6px; border-radius:9px; margin:0;
  background:var(--border-soft); color:var(--muted-soft);
  font-size:10.5px; font-weight:800; line-height:1; vertical-align:middle;
}
.tabs > a.active .tab-count,
.tablist > button[aria-selected=true] .count{ background:var(--blue-light); color:var(--blue); }
.tablist .count.danger{ background:#FEF2F2; color:var(--danger); }
.tablist .count.warning{ background:#FFFBEB; color:#B45309; }

/* ---- second level: .subtabs -------------------------------------------------
   For a tab whose contents are several full cards long — LESA traffic is four
   tables and a map behind one word — where the only alternative is a screen and
   a half of scrolling.

   Deliberately NOT a second rail. Two underline strips stacked read as two rows
   of equals, and the reader cannot tell which one they are inside. A segmented
   track is the same blue in a different shape: the group is visibly one control,
   and one control nested under a rail is unambiguously below it. The selected
   segment is filled rather than outlined for the same reason the language switch
   is — the current one should be readable, not inferred.
   ---------------------------------------------------------------------------- */
.subtabs{
  /* max-content, not full width: a control that hugs its segments reads as a
     control. Stretched to the page it reads as a bar with the buttons pushed
     into one corner, which is what the first cut looked like. max-width keeps
     a narrow window wrapping inside the track instead of overflowing it. */
  display:flex; width:max-content; max-width:100%;
  flex-wrap:wrap; gap:3px; margin:0 0 16px; padding:3px;
  border:1px solid var(--border); border-radius:11px; background:var(--bg);
  /* The strip is its own anchor: every segment links to #<the strip's id>, so a
     panel switch lands here instead of at the page title. The offset clears the
     sticky navy bar (~55px) — without it the browser scrolls the strip to y=0
     and the header sits on top of the tab you just pressed. */
  scroll-margin-top:70px;
}
.subtabs > a{
  flex:0 0 auto; display:inline-flex; align-items:center; gap:7px;
  padding:6px 12px; border:none; border-radius:8px; background:none;
  color:var(--muted); font:700 12.5px/1.35 var(--font);
  white-space:nowrap; text-decoration:none; cursor:pointer;
  transition:background var(--tr), color var(--tr);
}
.subtabs > a:hover{ background:var(--blue-light); color:var(--blue); text-decoration:none; }
.subtabs > a.active{
  background:var(--surface); color:var(--blue); font-weight:800;
  box-shadow:0 1px 2px var(--shadow-navy);
}
.subtabs > a:focus-visible{ outline:2px solid var(--blue); outline-offset:-1px; box-shadow:none; }
.subtabs .tab-count{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:18px; height:18px; padding:0 6px; border-radius:9px; margin:0;
  background:var(--border-soft); color:var(--muted-soft);
  font-size:10.5px; font-weight:800; line-height:1;
}
.subtabs > a.active .tab-count{ background:var(--blue-light); color:var(--blue); }

/* The panels a .subtabs strip switches between. Stated rather than left to the
   UA default for `hidden`, because `display` is set explicitly on so much of
   this platform's furniture that one `display:block` in the wrong place would
   show every panel at once. */
.panel[hidden]{ display:none; }

/* ---- tags / chips: tint the border to match the status colour, generically,
   so every module's .tag.<status> rule (bg + text colour only) automatically
   reads as a proper bg/text/border chip triad without touching ~40 rules. -- */
.tag{ padding:2px 10px; border:1px solid color-mix(in srgb, currentColor 32%, transparent); }

/* ---- semantic status tag colours — single source of truth for all modules -
   Every name a module uses is in one of these five lists. The shells used to
   carry their own tints (a third green, a third red) for the handful of names
   that were missing here, so the same idea was two colours depending on which
   module you were standing in. Add the name to a list; never re-tint locally. */
/* green: positive / terminal-success */
.tag.approved,.tag.accepted,.tag.completed,.tag.collected,.tag.resolved,.tag.pass,.tag.active,.tag.good,.tag.remedied,.tag.verified,.tag.licensed,.tag.overturned{
  background:#ECFDF5; color:#059669;
}
/* red: error / overdue / rejected */
.tag.rejected,.tag.declined,.tag.fail,.tag.overdue,.tag.cancelled,.tag.court,.tag.no_show,.tag.written_off,.tag.flagged,.tag.restricted,.tag.expired,.tag.bad,.tag.anom,.tag.suspended,.tag.upheld{
  background:#FEF2F2; color:#DC2626;
}
/* amber: in-progress / attention needed */
.tag.pending,.tag.submitted,.tag.waiting,.tag.unpaid,.tag.reminder_sent,.tag.final_notice,.tag.in_progress,.tag.under_review,.tag.programmed,.tag.open,.tag.due_soon,.tag.warn,.tag.appeal_lodged,.tag.mobile{
  background:#FFFBEB; color:#D97706;
}
/* blue: informational / neutral states */
.tag.allocated,.tag.planned,.tag.full,.tag.coordination,.tag.waived,.tag.superseded,.tag.fixed{
  background:#EFF6FF; color:#2563EB;
}
/* gray: no-data / disabled */
.tag.unlicensed,.tag.DRAFT{ background:#F3F4F6; color:#6B7280; }
/* rwp names its workflow states in caps, so they never collide with the lists
   above and are grouped here rather than tinted a fourth time in its shell. */
.tag.SUBMITTED{ background:#FFFBEB; color:#D97706; }
.tag.TM_TECHNICAL_REVIEW{ background:#EFF6FF; color:#2563EB; }
.tag.TM_APPROVED,.tag.PERMIT_ISSUED{ background:#ECFDF5; color:#059669; }
.tag.REJECTED{ background:#FEF2F2; color:#DC2626; }
/* Its own colour, deliberately: "changes requested" is the one state that is
   waiting on the reader, and amber would file it with everything merely pending. */
.tag.CHANGES_REQUESTED{ background:#FFF4E6; color:#D9480F; }
/* purple: LESA's camera kind. Its own rule because it is a category, not a
   state — nothing else on the platform should reach for this colour. */
.tag.anpr{ background:color-mix(in srgb, var(--purple) 12%, #fff); color:var(--purple); }

/* ---- inspections dashboard contractor score colour ----------------------- */
.score.g{ color:#059669; font-weight:800; }
.score.a{ color:#D97706; font-weight:800; }
.score.r{ color:#DC2626; font-weight:800; }

/* ---- rows the reader is meant to stop at: ODO's flagged rollbacks and
   DTEST's anomalies. One tint for both — they mean the same thing to the
   person scanning the table, and each module used to pick its own pink. --- */
tbody tr.rollback, tbody tr.anom{ background:#FFF5F5; }
tbody tr.rollback:hover, tbody tr.anom:hover{ background:#FEE2E2; }

/* ============================================================================
   Wide tables.

   Column counts on this platform run to 28 (lesa/traffic), 24 (rwp/proposal),
   23 (rwp/revisions), 19 (dtest/home). With `table{width:100%}` and nothing
   around it, a table wider than its column does not scroll — the PAGE does, and
   it takes the nav rail and the top bar sideways with it. rwp had this rule in
   its own shell and the other eight modules did not; it belongs here.

   `max-width:100%` is what actually forces the overflow: without it a table with
   enough columns simply stretches its parent instead of triggering the scroll.
   ============================================================================ */
.table-scroll{
  overflow-x:auto; max-width:100%;
  /* Momentum on iOS, and a scrollbar that does not steal a row's height. */
  -webkit-overflow-scrolling:touch; overscroll-behavior-x:contain;
}
/* Inside a scroller a table stops being 100% of a narrow screen and becomes as
   wide as its content needs, which is the thing being scrolled to. */
.table-scroll > table{ width:100%; min-width:max-content; }

/* ---------------------------------------------------------------------------
   `.table-scroll.flow` — the same scroller for a table that carries a SENTENCE.

   `min-width:max-content` is right for a queue: every cell is an atom (a ref, a
   date, a status) and the honest width is the sum of them. It is wrong the
   moment a column holds prose, because max-content for a paragraph is that
   paragraph on ONE line — rwp/permit's "Requirement / Detail / Source" would
   become a 3,000px table whose third column you reach by scrolling for six
   seconds. `min-content` instead lets the prose column wrap and only scrolls
   when even the longest word in each column no longer fits, which on a desk
   screen is essentially never.

   So: a list of records gets `.table-scroll`, a table with a text column gets
   `.table-scroll flow`. Both are a scroller; they differ in what they refuse
   to squash.
   --------------------------------------------------------------------------- */
.table-scroll.flow > table{ min-width:min-content; }

/* An identifier is read against a piece of paper, so it never wraps — a ref
   broken as `RWP-2026-` / `04187` across two lines is not the same string to
   the person checking it, and a browser will happily break at those hyphens.

   `.nowrap-ref` was rwp's own class for exactly this and it is the right name,
   so it moves here and the other modules' queues use it rather than each
   inventing one.
   It says nothing about type: a cell that is a ref AND a line of hint text
   underneath keeps the body face. `.ref-cell` and `.plate` already set mono
   further up this file — they are identifiers by definition, so they get the
   same rule rather than needing the class spelled out beside them.

   Opt-in per cell rather than on `.mono` wholesale, because the other mono
   things in a cell are checksums and URLs, which must break — see below.

   A date is the same case as a ref: split between the month and the year it is
   read twice. Each line inside the cell still stacks — it is the LINE that is
   kept whole, not the cell. */
.nowrap-ref, .ref-cell, .plate{ white-space:nowrap; }
/* …and the cell's own contents have to keep it, which they were not doing.
   `text-wrap` is a SHORTHAND for text-wrap-mode + text-wrap-style, so the
   `text-wrap:pretty` set on `.hint`/`.sub` further up this file resets the mode
   to `wrap` and silently throws away the nowrap inherited from the cell. Nearly
   every queue cell is `<td class="nowrap-ref">REF<br><small class="hint">…`, so
   the rule above was being defeated on its second line — a requested window
   rendered as "to 17 Aug 2026" / "11:00", a date broken between the month and
   the year, which is the exact thing it exists to prevent. `inherit` rather than
   `nowrap`: it says the contents of an identifier cell are part of the
   identifier, and it stays correct if the cell itself ever stops being one. */
.nowrap-ref *, .ref-cell *, .plate *{ white-space:inherit; }
/* The opposite case, and the reason the rule above is opt-in: a `<code>` holds
   one unbreakable token — a sha256, a rule id, a legislation URL — that is wider
   than any sane column or list item, and with nothing said it pushes its
   container past the page. `anywhere` only breaks a line that would otherwise
   overflow, so a short ref is untouched; `.hash` keeps its stronger
   `word-break:break-all`, which breaks a checksum on every line. */
code{ overflow-wrap:anywhere; }

/* ---------------------------------------------------------------------------
   The dark top bar, on the three pages that draw their own.

   shell.css wraps `header.top`; portal, the demo-accounts sheet and the system
   manual each predate it and carry their own copy of the bar without that one
   declaration. Their bar is the longest on the platform — brand, help, release
   flag, bell, "Signed in as <name>", language, manual, sign out — so a long
   display name is all it takes to push sign-out off the right edge and take the
   page sideways with it. This file loads after all three.
   --------------------------------------------------------------------------- */
header.top{ flex-wrap:wrap; }
header.top .who{ min-width:0; }

/* A grid child's `min-width` is `auto`, which means "at least my min-content" —
   so a `1fr 1fr` grid with a table in one cell does not divide the row in two,
   it grows until the table's narrowest layout fits and takes the page sideways
   with it. Every `.grid` on the platform is a layout grid whose children are
   allowed to be narrower than their content; the ones that must scroll say so
   with `.table-scroll`. */
.grid > *, .cols > *, .layout > *{ min-width:0; }

/* Printing a scroller clips it at the container's edge in most engines, so the
   rwp permit export would lose its right-hand columns on paper. On paper there
   is no viewport to overflow: let every table lay out at its natural width. */
@media print{
  .table-scroll{ overflow:visible; max-width:none; }
  .table-scroll > table, .table-scroll.flow > table{ min-width:0; }
}

/* ---------------------------------------------------------------------------
   The content column on a large screen.

   `main` is capped per module (--main-w, 700–1340px) and that cap was written as
   a maximum for READABILITY. It also acts as a maximum for the TABLES, which is
   the part that does not hold: a six-column queue is exactly as cramped on a
   1920px monitor as on a 1280px laptop, with ~580px of the screen sitting empty
   beside it.

   `:has(.table-scroll)` is what keeps this honest, and it is not a shortcut —
   it is the condition itself. Extra width is worth something only where there
   is a table that would otherwise scroll; on a form it stretches the fields, and
   on the release notes (--main-w:700px) or the system manual (900px) it runs the
   prose to a measure nobody finishes. Those pages have no scroller, so they keep
   the width they were drawn at. The platform already uses `:has` for the
   launcher rail further down, so this is the same tool, not a new dependency.

   Below 1500px nothing changes anywhere — that is the width these pages were
   designed at. A page that wants a different ceiling sets `--main-w-wide`.
   --------------------------------------------------------------------------- */
@media (min-width:1500px){
  main:has(.table-scroll){ max-width:var(--main-w-wide, 1500px); }
}

/* ---------------------------------------------------------------------------
   Module maps on a narrow screen.

   Each shell pins its own map height (520px in lesa/ttm/digonce, 460px on the
   rwp permit map, 360px in inspections) in a block that loads BEFORE this file,
   so this is the only place a rule can outrank all of them at once. 520px of map
   on a 640px-tall phone is the whole screen and nothing else.
   --------------------------------------------------------------------------- */
@media (max-width:760px){
  #map, #permit-map{ height:min(340px, 46dvh) !important; }
}

/* ---- sortable table headers --------------------------------------------- */
th .sort-th, a.sort-th{ color:inherit; text-decoration:none; cursor:pointer; white-space:nowrap; display:inline-flex; align-items:center; gap:3px; }
a.sort-th:hover{ color:var(--blue); text-decoration:none; }

/* ---- infinite-scroll sentinel ------------------------------------------- */
.rps-sentinel{ text-align:center; color:var(--muted); font-size:12px; padding:12px 0 2px; }
.rps-sentinel td{ text-align:center; color:var(--muted); font-size:12px; padding:12px; border:none; }
.rps-sentinel .spin{ display:none; }
.rps-sentinel.loading .spin{ display:inline-block; animation:rps-spin .8s linear infinite; }
.rps-sentinel.loading .idle{ display:none; }
@keyframes rps-spin{ to{ transform:rotate(360deg); } }

/* ---- labeled key/value fields: label ABOVE the value, always ------------- */
.kv-row{ display:flex; flex-direction:column; gap:3px; padding:8px 0; border-bottom:1px solid var(--border-soft); }
.kv-row:last-child{ border-bottom:none; }
.kv-label{ display:block; color:var(--muted); font-size:11px; font-weight:600; text-transform:none; letter-spacing:normal; }
.kv-value{ color:var(--navy); font-weight:600; font-size:14px; line-height:1.4; }
.kv-block{ margin-top:14px; }
.kv-block .kv-label{ margin-bottom:5px; }
.kv-text{ margin:0; color:var(--navy); font-size:13.5px; line-height:1.55; background:var(--bg); border:1px solid var(--border); border-radius:8px; padding:9px 11px; }

/* ---- de-shout: stat-tile labels are plain small gray, never uppercase ---- */
.tile .l{ text-transform:none; letter-spacing:normal; }

/* ---- reusable server-side search bar (includes/_searchbar.html) --------- */
.rps-search{ display:flex; align-items:center; gap:8px; margin:0 0 16px; flex-wrap:wrap; position:relative; }
.rps-search .rps-search-ic{ position:absolute; margin-left:12px; color:var(--muted); pointer-events:none; display:flex; }
.rps-search input[type=search]{
  width:auto; flex:1; min-width:200px; max-width:380px; margin:0;
  padding:10px 12px 10px 38px; border:1px solid var(--border); border-radius:var(--radius-sm);
  background:var(--surface); color:var(--navy); font:inherit; font-size:14px; box-shadow:none;
}
.rps-search button{ margin:0; }
.rps-search .rps-search-clear{ color:var(--muted); font-size:13px; font-weight:600; text-decoration:none; }
.rps-search .rps-search-clear:hover{ color:var(--danger); text-decoration:underline; }

/* ============================================================================
   Collapsible left icon nav (module shells: digonce/dtest/inspections/lesa/
   odo/ttm/vera/yplate). Structure:
     <div class="rps-body">
       <aside class="rps-nav" id="rpsNav">
         <button class="rps-nav-toggle" data-nav-toggle>...</button>
         <div class="rps-nav-links"><a class="rps-nav-link">...</a>...</div>
       </aside>
       <main>...</main>
     </div>
   Icons are inline SVG (stroke-only, 14x14, round caps) — never emoji, per
   the icon vocabulary in ui-guidelines.md. Collapse state persists.
   ============================================================================ */
.rps-body{ display:flex; align-items:flex-start; }
.rps-body > main{ flex:1; min-width:0; }
.rps-nav{
  flex:none; width:216px; background:var(--surface); border-right:1px solid var(--border);
  position:sticky; top:0; align-self:flex-start; height:100dvh; overflow-y:auto; overflow-x:hidden;
  transition:width .16s ease; z-index:15;
}
.rps-nav.collapsed{ width:60px; }
.rps-nav-toggle{
  display:flex; align-items:center; justify-content:center; width:100%; background:none; border:none;
  border-bottom:1px solid var(--border); padding:14px 0; cursor:pointer; color:var(--muted);
}
.rps-nav-toggle:hover{ background:var(--blue-light); color:var(--blue); }
.rps-nav-links{ padding:8px 0; }
.rps-nav-link{
  display:flex; align-items:center; gap:12px; padding:10px 16px; color:var(--navy);
  font-weight:600; font-size:13.5px; white-space:nowrap; border-left:3px solid transparent;
  transition:background .12s, border-color .12s, color .12s;
}
.rps-nav-link:hover{ background:var(--blue-light); color:var(--blue); text-decoration:none; }
.rps-nav-link.active{ border-left-color:var(--blue); color:var(--blue); background:var(--blue-light); font-weight:800; }
.rps-nav-link .ic, .rps-nav-toggle .ic{ display:inline-flex; width:20px; flex:none; justify-content:center; }
.rps-nav-link .ic svg, .rps-nav-toggle .ic svg{ stroke:currentColor; }
.rps-nav.collapsed .lbl{ display:none; }
.rps-nav.collapsed .rps-nav-link{ justify-content:center; padding:10px 0; }
.rps-nav.collapsed .rps-nav-toggle{ justify-content:center; padding:14px 0; }
/* ----------------------------------------------------------------------------
   The rail on a narrow screen.

   This used to switch the rail to `position:fixed; left:0`, which takes it OUT
   of the flex row — so `main` reflowed to the full width of the screen and then
   the rail was painted on top of its first 216px. Every module page under 760px
   had its left edge covered by its own navigation.

   It stays in flow and presents itself collapsed instead: icons only, 60px, the
   content beside it rather than behind it. The toggle still expands it, and an
   expansion the reader asked for is allowed to be wide. `:not(.app-nav)` because
   the launcher rail has its own narrow-screen behaviour further down — it
   becomes a disclosure above the page, which is right for a rail whose links all
   leave the page you are on.
   ---------------------------------------------------------------------------- */
@media (max-width:760px){
  /* Resting state is the icons-only rail, in flow — `.collapsed` already draws
     that at every width, and platform.js defaults to it below this breakpoint.
     Expanded is then a deliberate act, and it overlays with a scrim rather than
     crushing the page into a column too narrow to read: the reader who opened
     it is navigating, not reading. The scrim is a spread shadow so it needs no
     extra element; platform.js closes the rail on a tap outside it. */
  .rps-nav:not(.app-nav):not(.collapsed){
    position:fixed; top:0; left:0; bottom:0; height:auto; width:216px;
    box-shadow:0 0 0 100vmax rgba(2,2,58,.34);
  }
}

/* ============================================================================
   app-nav — the launcher rail on Portal / Manual / Demo accounts.

   It lists every module the role can open, so it is a FINDING surface, not a
   location map (none of its module links is ever the page you're on). One
   collapsed row per module, bucketed by owning authority and tinted with the
   same accent as that module's portal card, keeps all ten modules on one
   screen; a filter box narrows them. Markup: includes/_app_nav.html.

     nav.app-nav
       .app-nav-bar     header: mobile Menu button / heading / Expand all
       .app-nav-body    scrolls: filter, Portal home, .nav-sec > details.nav-group
       .app-nav-foot    pinned: manual + demo accounts
   ============================================================================ */
.rps-nav.app-nav{
  width:272px; padding:0; display:flex; flex-direction:column;
  overflow:hidden;  /* .app-nav-body owns the scrolling, so bar + foot stay put */
  /* All three host pages sit under the same sticky dark top bar; parking its
     height in a variable keeps the rail exactly one viewport tall BELOW it, so
     the pinned footer lands on screen instead of under the fold. */
  --top-h:55px;
  top:var(--top-h); height:calc(100dvh - var(--top-h));
}
.rps-nav.app-nav a{ text-decoration:none; }
/* The filter hides rows with the `hidden` attribute, whose UA `display:none`
   any `display:flex` here would silently outrank. */
.rps-nav.app-nav [hidden]{ display:none !important; }

/* ---- header bar ---- */
.app-nav-bar{
  flex:none; display:flex; align-items:center; gap:8px; padding:12px 14px 10px;
  border-bottom:1px solid var(--border-soft);
}
.app-nav-heading{
  flex:1; display:inline-flex; align-items:center; gap:7px; min-width:0;
  font-size:11px; font-weight:800; letter-spacing:.6px; text-transform:uppercase; color:var(--muted);
}
.app-nav-heading svg{ color:var(--blue); flex:none; }
.app-nav-all{
  flex:none; background:none; border:1px solid transparent; border-radius:7px; padding:3px 7px;
  color:var(--muted-soft); font:inherit; font-size:11px; font-weight:700; cursor:pointer;
  transition:background var(--tr), color var(--tr), border-color var(--tr);
}
.app-nav-all:hover{ background:var(--blue-light); border-color:var(--blue-mid); color:var(--blue); }
.app-nav-all:focus-visible{ outline:none; box-shadow:var(--ring); }
.app-nav-burger{ display:none; }

/* ---- filter ---- */
.app-nav-search{ position:relative; padding:10px 12px 6px; }
.app-nav-search .ic{
  position:absolute; left:23px; top:50%; transform:translateY(-40%);
  color:var(--muted-soft); pointer-events:none; display:flex;
}
.app-nav-search input{
  width:100%; margin:0; padding:8px 10px 8px 32px; font:inherit; font-size:13px;
  border:1px solid var(--border); border-radius:9px; background:var(--bg); color:var(--navy);
}
.app-nav-search input:focus{ background:var(--surface); }
.app-nav-search input::placeholder{ color:var(--muted-soft); }

/* ---- scrolling list ---- */
.app-nav-body{
  flex:1; min-height:0; overflow-y:auto; padding:0 10px 14px;
  /* So a row brought into view by opening its module does not land flush
     against the cut. */
  scroll-padding-bottom:16px;
  /* The default bar is 15px of solid grey down a 272px rail, which reads as
     furniture rather than as a hint that there is more list. */
  scrollbar-width:thin; scrollbar-color:var(--border) transparent;
}
.app-nav-body::-webkit-scrollbar{ width:8px; }
.app-nav-body::-webkit-scrollbar-track{ background:transparent; }
.app-nav-body::-webkit-scrollbar-thumb{ background:var(--border); border-radius:8px; }
.app-nav-body::-webkit-scrollbar-thumb:hover{ background:var(--muted-soft); }
.app-nav-note{ color:var(--muted-soft); font-size:12.5px; padding:14px 8px; margin:0; }

.rps-nav.app-nav .rps-nav-link{
  white-space:normal; line-height:1.35; border-left:none; border-radius:9px;
  /* Stated, not inherited. `sub` is the modifier on a module's page links AND
     the class four of the five host pages used for their own lede paragraph, so
     a page-level `.sub{margin:0 0 18px}` was adding 18px under every link in the
     rail — the rail read as broken on the pages that had it and fine on the one
     that did not. Those pages now say `.lede`; this line is the reason it cannot
     happen again from any page, including one written next year. */
  margin:0;
}
.rps-nav.app-nav .rps-nav-link.home{
  gap:10px; padding:9px 10px; margin:2px 0 4px; font-weight:700; font-size:13.5px;
}
.rps-nav.app-nav .rps-nav-link.home .ic{ color:var(--muted-soft); }
.rps-nav.app-nav .rps-nav-link.home:hover .ic{ color:var(--blue); }
.rps-nav.app-nav .rps-nav-link.active{
  background:var(--blue-light); color:var(--blue); font-weight:800;
}
.rps-nav.app-nav .rps-nav-link.active .ic{ color:var(--blue); }

/* ---- authority section heading ---- */
.nav-sec{ margin-top:12px; }
.nav-sec-title{
  display:flex; align-items:center; gap:7px; margin:0 0 4px; padding:0 8px;
  font-size:10.5px; font-weight:800; letter-spacing:.5px; text-transform:uppercase;
  color:var(--muted-soft); line-height:1.3;
}
.nav-sec-title .sec-mark{
  flex:none; padding:1px 6px; border-radius:5px; font-size:9.5px; letter-spacing:.3px;
  color:#fff; background:var(--sec, var(--muted-soft));
}

/* ---- one module = one <details> row ---- */
.nav-group{ border-radius:10px; }
.nav-group-head{
  display:flex; align-items:center; gap:10px; padding:7px 9px; border-radius:10px;
  cursor:pointer; list-style:none; user-select:none;
  font-size:13.5px; font-weight:700; color:var(--navy);
  transition:background var(--tr), color var(--tr);
}
.nav-group-head::-webkit-details-marker{ display:none; }
.nav-group-head:hover{ background:color-mix(in srgb, var(--accent) 9%, transparent); }
.nav-group-head:focus-visible{ outline:none; box-shadow:var(--ring); }
.nav-group[open] > .nav-group-head{ color:var(--accent); }
/* `nav-tile`, not `tile`. shell.css owns `.tile` — the stat tile a module page
   puts a number in, with 14px of padding on it — and a shell that loads both
   files inflated this 26px icon square to 30px, because a border-box width
   cannot go below its own padding. The icons then sat larger on those pages
   than on the Portal, which loads no shell.css. A name of its own is the fix
   that cannot come back: there is nothing left to collide with. */
.nav-group-head .nav-tile{
  flex:none; width:26px; height:26px; padding:0; border-radius:8px;
  display:grid; place-items:center;
  color:var(--accent); background:color-mix(in srgb, var(--accent) 13%, #fff);
  border:1px solid color-mix(in srgb, var(--accent) 28%, var(--border));
}
.nav-group-head .lbl{ flex:1; min-width:0; line-height:1.3; }
.nav-group-head .cnt{
  flex:none; min-width:18px; text-align:center; padding:1px 6px; border-radius:999px;
  background:var(--border-soft); color:var(--muted-soft); font-size:10.5px; font-weight:800;
}
.nav-group[open] > .nav-group-head .cnt{
  background:color-mix(in srgb, var(--accent) 14%, #fff); color:var(--accent);
}
.nav-group-head .chev{
  flex:none; display:flex; color:var(--muted-soft); transition:transform .16s ease;
}
.nav-group[open] > .nav-group-head .chev{ transform:rotate(180deg); color:var(--accent); }

/* pages: hung off a guide line so they read as belonging to the module above */
.nav-group-pages{
  margin:1px 0 6px 21px; padding:2px 0 2px 11px;
  border-left:1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
}
.rps-nav.app-nav .rps-nav-link.sub{
  padding:6px 9px; font-weight:600; font-size:12.5px; color:var(--muted);
}
.rps-nav.app-nav .rps-nav-link.sub:hover{
  background:color-mix(in srgb, var(--accent) 9%, transparent); color:var(--accent);
}

/* ---- pinned footer ---- */
.app-nav-foot{
  position:relative;
  flex:none; padding:8px 10px 12px; border-top:1px solid var(--border-soft); background:var(--surface);
}
/* There is more list above the foot than fits. The module list scrolls inside a
   rail whose foot is pinned, so opening a module near the bottom leaves its last
   page sliced in half against the foot's edge — and a row cut mid-height with
   nothing to explain it reads as a broken render, not as a scroll region. The
   fade says which of the two it is. platform.js adds `has-more` only while the
   list is actually scrollable AND not at its end, so a rail whose modules all
   fit shows no fade over its last row. */
.rps-nav.app-nav.has-more .app-nav-foot::before{
  content:""; position:absolute; left:0; right:0; bottom:100%; height:26px;
  background:linear-gradient(to top, var(--surface), transparent);
  pointer-events:none;
}
.rps-nav.app-nav .rps-nav-link.foot{
  gap:10px; padding:8px 10px; font-size:13px; font-weight:600; color:var(--muted);
}
.rps-nav.app-nav .rps-nav-link.foot .ic{ color:var(--muted-soft); }
.rps-nav.app-nav .rps-nav-link.foot:hover .ic{ color:var(--blue); }
/* The version stamp. Mono and a size down, like every other identifier that is
   read a character at a time — it gets quoted into a bug report rather than
   read as a word. `.static` is the non-admin rendering: the same row without a
   changelog behind it, so it must not offer a link's affordances. */
.rps-nav.app-nav .rps-nav-link.foot.ver .lbl{
  font-family:var(--font-mono); font-size:11.5px; letter-spacing:-.1px;
}
.rps-nav.app-nav .rps-nav-link.foot.ver.static{
  cursor:default; color:var(--muted-soft);
}
.rps-nav.app-nav .rps-nav-link.foot.ver.static:hover{
  background:transparent; color:var(--muted-soft);
}

/* ---- narrow screens: the rail stops being a rail and becomes a disclosure
   above the page, so it can't cover the content it launches ---------------- */
@media (max-width:860px){
  /* :has, so module shells (whose rail is the collapsible icon nav) keep theirs */
  .rps-body:has(> .app-nav){ display:block; }
  .rps-nav.app-nav{
    position:static; width:auto; height:auto; overflow:visible;
    border-right:none; border-bottom:1px solid var(--border);
  }
  .app-nav-bar{ padding:8px 14px; }
  .app-nav-burger{
    display:inline-flex; align-items:center; gap:8px; background:none; border:1px solid var(--border);
    border-radius:9px; padding:7px 12px; font:inherit; font-size:13px; font-weight:700;
    color:var(--navy); cursor:pointer;
  }
  .app-nav-burger:hover{ background:var(--blue-light); border-color:var(--blue-mid); color:var(--blue); }
  .app-nav-burger:focus-visible{ outline:none; box-shadow:var(--ring); }
  .app-nav-heading{ display:none; }
  /* Nothing to expand while the list is closed */
  .app-nav-all{ display:none; }
  .app-nav.open .app-nav-all{ display:inline-block; }
  .app-nav-body, .app-nav-foot{ display:none; }
  .app-nav.open .app-nav-body{ display:block; max-height:min(60vh, 460px); overflow-y:auto; }
  .app-nav.open .app-nav-foot{ display:block; }
}

/* ---- map-shell surfaces (permits + login): flat, no shadow --------------- */
.permit-card{ border-radius:var(--radius-sm); box-shadow:none; transition:border-color var(--tr); }
.permit-card:hover{ box-shadow:none; }
.box{ border-radius:var(--radius-sm); box-shadow:none; }
#sidebar{ box-shadow:none; }
.login-card{ border-radius:18px; box-shadow:none; }

/* ============================================================================
   grayscale toggle — auto-injected floating button on pages with a map view.
   Desaturation is done in-map (raster-saturation on the basemap layer, in
   platform.js), so vector overlays keep their colours — not a CSS filter.
   ============================================================================ */
button.gs-toggle{
  display:flex; align-items:center; gap:7px;
  background:var(--navy); color:#fff; border:1px solid var(--navy);
  border-radius:var(--radius-sm); padding:9px 15px;
  font:700 12px var(--font); cursor:pointer; box-shadow:none;
}
button.gs-toggle svg{ stroke:currentColor; flex:none; }
button.gs-toggle.on{ background:#fff; color:var(--navy); }
.grayscaleable{ position:relative; }
button.gs-float{ position:absolute; bottom:12px; left:12px; z-index:5; }

/* ============================================================================
   confirm-before-submit modal (platform.js)
   ============================================================================ */
.rps-modal-overlay{
  position:fixed; inset:0; background:rgba(2,2,58,.55);
  display:none; align-items:center; justify-content:center; z-index:1000;
}
.rps-modal-overlay.show{ display:flex; animation:rps-fade .12s ease; }
.rps-modal{
  background:#fff; border:1px solid var(--border); border-radius:var(--radius); padding:22px;
  max-width:420px; width:92%; box-shadow:none; font-family:var(--font);
  animation:rps-pop .14s ease;
}
@keyframes rps-fade{ from{opacity:0} to{opacity:1} }
@keyframes rps-pop{ from{opacity:0; transform:translateY(8px) scale(.98)} to{opacity:1; transform:none} }
.rps-modal-title{ font-weight:800; font-size:16px; margin:0 0 6px; color:var(--navy); letter-spacing:-.2px; }
.rps-modal-msg{ margin:0; font-size:14px; color:var(--navy); line-height:1.5; }
/* The consequence that outlives the click, for the few actions that have one
   (submitting an application freezes it). Carried separately from the question
   because inside one paragraph it reads as small print — and the whole reason
   for asking is that this part is the part nobody expected. */
.rps-modal-detail{
  margin:12px 0 0; padding:9px 11px; border-radius:var(--radius-sm);
  border-left:3px solid var(--warning); background:#FFF9DB; color:#7A4E00;
  font-size:13px; line-height:1.45;
}
.rps-modal-detail[hidden]{ display:none; }
.rps-modal-actions{ display:flex; gap:10px; justify-content:flex-end; margin-top:18px; }
.rps-modal-actions button{
  border:none; border-radius:var(--radius-sm); padding:9px 17px; font-weight:700;
  cursor:pointer; font-family:inherit; font-size:14px; transition:filter var(--tr);
}
.rps-modal-actions button:hover{ filter:brightness(1.06); }
.rps-modal-cancel{ background:var(--blue-light); color:var(--blue); }
.rps-modal-ok{ background:var(--blue); color:#fff; }
.rps-modal-ok.danger{ background:var(--danger); }

/* ============================================================================
   notification bell (includes/_notification_bell.html + platform.js)

   One control, two backgrounds: the eight module shells and the portal put it on
   the navy header.top (.on-dark), the permits map shell puts it in the white
   sidebar. Only the button changes between them — the panel is a white card on
   both, because a dropdown that inverted with its host would read as two
   different components.
   ============================================================================ */
.rps-bell{ position:relative; display:inline-flex; flex:none; }
/* Map shell (permits): the bell rides at the end of the sidebar's brand row. The
   panel is narrowed there because the sidebar is 400px wide and scrolls — a panel
   as wide as the one in a full-width header would give it a scrollbar. */
.brand > .rps-bell{ margin-left:auto; }
.brand > .rps-bell .rps-bell-panel{ width:336px; }
.rps-bell-btn{
  display:inline-flex; align-items:center; justify-content:center; position:relative;
  width:34px; height:34px; padding:0; border:1px solid transparent; border-radius:var(--radius-sm);
  background:transparent; color:var(--muted); cursor:pointer;
  transition:background var(--tr), color var(--tr), border-color var(--tr);
}
.rps-bell-btn:hover{ background:var(--blue-light); color:var(--blue); }
.rps-bell-btn:focus-visible{ outline:none; box-shadow:var(--ring); }
.rps-bell-btn svg{ stroke:currentColor; }
.rps-bell.on-dark .rps-bell-btn{ color:#C9CCEC; }
.rps-bell.on-dark .rps-bell-btn:hover{ background:rgba(255,255,255,.12); color:#fff; }
/* The count, not a dot: "3 waiting" and "31 waiting" are different situations
   and the badge is the only place that difference is visible at a glance. */
.rps-bell-badge{
  position:absolute; top:-3px; right:-3px; min-width:17px; height:17px; padding:0 4px;
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--danger); color:#fff; border-radius:9px;
  font:800 10.5px/1 var(--font); letter-spacing:-.2px;
  border:2px solid var(--surface);
}
.rps-bell.on-dark .rps-bell-badge{ border-color:var(--navy); }
.rps-bell-badge[hidden]{ display:none; }

/* ----------------------------------------------------------------------------
   The "what's new" flag, beside the bell. Same button geometry so the two read
   as one pair of header controls, and a badge in the platform blue rather than
   the bell's danger red: a changelog is news, not a problem.
   ============================================================================ */
.rps-flag{
  position:relative; flex:none;
  display:inline-flex; align-items:center; justify-content:center;
  width:34px; height:34px; border:1px solid transparent; border-radius:var(--radius-sm);
  color:var(--muted); text-decoration:none;
  transition:background var(--tr), color var(--tr), border-color var(--tr);
}
.rps-flag:hover{ background:var(--blue-light); color:var(--blue); text-decoration:none; }
.rps-flag:focus-visible{ outline:none; box-shadow:var(--ring); }
.rps-flag svg{ stroke:currentColor; }
.rps-flag.on-dark{ color:#C9CCEC; }
.rps-flag.on-dark:hover{ background:rgba(255,255,255,.12); color:#fff; }
/* Unread reads twice: the icon comes forward, and the badge appears. Colour
   alone would leave a reader who cannot see it with no signal at all. */
.rps-flag.has-new{ color:var(--blue); }
.rps-flag.on-dark.has-new{ color:#fff; }
.rps-flag-badge{
  position:absolute; top:-3px; right:-3px; min-width:17px; height:17px; padding:0 4px;
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--blue); color:#fff; border-radius:9px;
  font:800 10.5px/1 var(--font); letter-spacing:-.2px;
  border:2px solid var(--surface);
}
.rps-flag.on-dark .rps-flag-badge{ border-color:var(--navy); }

/* ----------------------------------------------------------------------------
   Help, beside the bell and the flag (includes/_help_button.html + platform.js).

   Was a "? Help" text button wedged between the language switch and sign-out:
   underlined 12px text among other underlined 12px text, so the one control on
   the page that explains the page read as a footnote. It is now the third
   member of the header's button set — same 34px box, same hover, same ground
   behaviour on navy and on white — because a circled question mark is the shape
   every reader already knows help to be, and it is findable without reading.
   ============================================================================ */
.rps-help{ position:relative; display:inline-flex; flex:none; }
.rps-help-btn{
  display:inline-flex; align-items:center; justify-content:center;
  width:34px; height:34px; padding:0; border:1px solid transparent; border-radius:var(--radius-sm);
  background:transparent; color:var(--muted); cursor:pointer;
  transition:background var(--tr), color var(--tr), border-color var(--tr);
}
.rps-help-btn:hover{ background:var(--blue-light); color:var(--blue); }
.rps-help-btn:focus-visible{ outline:none; box-shadow:var(--ring); }
.rps-help-btn svg{ stroke:currentColor; }
/* Open is a state worth showing: the panel can be dismissed by clicking the
   button again, and an unlit button gives no hint that it would. */
.rps-help[data-open] .rps-help-btn{ background:var(--blue-light); color:var(--blue); }
.rps-help.on-dark .rps-help-btn{ color:#C9CCEC; }
.rps-help.on-dark .rps-help-btn:hover,
.rps-help.on-dark[data-open] .rps-help-btn{ background:rgba(255,255,255,.12); color:#fff; }
/* Map shell: the header is the white sidebar, and the panel would otherwise be
   as wide as a 400px scrolling column. The three controls travel as one group at
   the end of the brand row, so the auto-margin belongs to the first of them —
   the bell's own (below, from when it was alone up there) is cancelled. */
.brand > .rps-help{ margin-left:auto; }
.brand > .rps-help ~ .rps-bell{ margin-left:0; }

/* …and the panel is measured against the whole brand ROW, not against the button
   that opens it. Help is the first of those three icons, so anchoring its right
   edge to its own 34px box put a 336px panel about 64px off the left of a 400px
   column — and `#sidebar` scrolls, which means its overflow-x is `auto` rather
   than `visible`, so the overhang was clipped rather than merely ugly. The bell
   never showed this because it is last in the row: its right edge already IS the
   column's. Making .brand the containing block gives help the same edge.

   position:static on the control, not a new relative ancestor inside it — the
   panel has to escape the 34px box to find the row. */
#sidebar .brand{ position:relative; }
#sidebar .brand > .rps-help{ position:static; }
.brand > .rps-help .rps-help-panel{ width:336px; }

/* The same fault, on the dark bar every module shell draws — and there it is not
   a 400px column it overhangs but the screen.

   `.rps-help` and `.rps-bell` are `position:relative`, so a panel set to
   `right:0` measures from the right edge of its own 34px button. That is fine
   only while the button happens to sit more than a panel-width from the left of
   the screen. It does not: the bar wraps, the wrapped line starts at the left,
   and the panel is then pinned to a button 40px in and runs off the left edge of
   the viewport — where nothing can scroll to it, because there is no scrolling
   left of zero. Measured on the real pages, the help panel's left edge was -83px
   at 600, -52px at 768 and, on Dig-Once, -31px at 900.

   The band moves with what is in the bar: Dig-Once fails at 900 where LESA is
   fine at 600, and a longer display name or the Maltese labels move it again. So
   this is not a breakpoint to find — it is the anchor that is wrong. Measuring
   from the ROW instead is width-independent and needs no media query, and it is
   what `#sidebar .brand` above already does. The panel now drops from under the
   bar, flush with its right edge, at every width.

   `top:calc(100% + 8px)` follows the containing block, so it clears the whole bar
   rather than just the button — including when the bar has wrapped to two lines. */
header.top > .rps-help, header.top > .rps-bell{ position:static; }
/* Offsets on an absolutely positioned box resolve against the containing block's
   PADDING box, so `right:0` against the bar is the screen's edge rather than the
   edge of the bar's content. 18px is `header.top`'s own side padding, which puts
   the panel's edge on the same line as the sign-out beside it. (Portal draws its
   bar at 22px; 4px is not worth a second rule.) */
header.top > .rps-help > .rps-help-panel,
header.top > .rps-bell > .rps-bell-panel{ right:18px; }

.rps-help-panel{
  position:absolute; top:calc(100% + 8px); right:0; z-index:200; width:400px;
  max-width:calc(100vw - 24px);
  background:var(--surface); color:var(--navy); border:1px solid var(--border);
  border-radius:var(--radius); overflow:hidden; text-align:left;
  font:14px/1.5 var(--font);
}
.rps-help-panel[hidden]{ display:none; }
.rps-help-head{
  position:relative; padding:11px 40px 11px 14px;
  border-bottom:1px solid var(--border); background:var(--bg);
}
/* The page's name, and above it what the name IS. Without the eyebrow the
   heading reads as the panel's own title rather than as the page's. */
.rps-help-eyebrow{
  display:block; color:var(--muted); font-size:10.5px; font-weight:800;
  text-transform:uppercase; letter-spacing:.4px; margin-bottom:2px;
}
.rps-help-head b{ font-size:14px; color:var(--navy); line-height:1.3; }
.rps-help-x{
  position:absolute; top:8px; right:8px; width:26px; height:26px; padding:0;
  display:inline-flex; align-items:center; justify-content:center;
  background:none; border:none; border-radius:var(--radius-sm);
  color:var(--muted); font:400 20px/1 var(--font); cursor:pointer;
}
.rps-help-x:hover{ background:var(--blue-light); color:var(--navy); }
.rps-help-body{ padding:13px 14px 14px; max-height:min(66vh,480px); overflow-y:auto; }
.rps-help-lede{ margin:0; font-size:13px; line-height:1.55; color:var(--navy); }
.rps-help-body h4{
  margin:15px 0 6px; font-size:10.5px; font-weight:800; text-transform:uppercase;
  letter-spacing:.4px; color:var(--muted);
}
.rps-help-body ul{ margin:0; padding:0; list-style:none; }
.rps-help-body li{
  position:relative; padding-left:18px; margin-bottom:7px;
  font-size:12.5px; line-height:1.5;
}
.rps-help-body li:last-child{ margin-bottom:0; }
/* Two lists, two marks. An action is something you go and do, a rule is
   something that is already true — and a reader scanning the panel should not
   have to read a bullet to find out which kind it is. */
.rps-help-do li::before{
  content:""; position:absolute; left:3px; top:6px; width:5px; height:5px;
  border-radius:50%; background:var(--blue);
}
.rps-help-know li{ color:var(--muted); }
.rps-help-know li::before{
  content:""; position:absolute; left:5px; top:3px; width:2px; bottom:3px;
  border-radius:2px; background:var(--border);
}
.rps-help-foot{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:10px 14px; border-top:1px solid var(--border); background:var(--bg);
}
.rps-help-tour{
  background:none; border:none; padding:0; cursor:pointer;
  color:var(--blue); font:700 12.5px var(--font); text-decoration:underline;
}
.rps-help-tour:hover{ background:none; color:var(--blue-ink, var(--blue)); }
.rps-help-foot a{ font-size:12.5px; font-weight:700; color:var(--muted); }
@media (max-width:520px){
  .rps-help-panel{ position:fixed; top:56px; right:8px; left:8px; width:auto; }
}

/* The guided tour's popovers (driver.js). Its own stylesheet sizes the box; what
   it has no opinion about is the list the opening step puts inside the
   description — a browser's default <ul> indents 40px, which in a 300px popover
   leaves the actions in a column two words wide. */
.driver-popover{ font-family:var(--font); }
.driver-popover-title{ font-size:14.5px; letter-spacing:-.2px; }
.driver-popover-description{ font-size:12.8px; line-height:1.55; }
.driver-popover-description p{ margin:0 0 8px; }
.driver-popover-description p:last-child{ margin-bottom:0; }
.driver-popover-description ul{ margin:0; padding-left:16px; }
.driver-popover-description li{ margin-bottom:5px; }
.driver-popover-description li:last-child{ margin-bottom:0; }

.rps-bell-panel{
  position:absolute; top:calc(100% + 8px); right:0; z-index:200; width:370px; max-width:calc(100vw - 24px);
  background:var(--surface); color:var(--navy); border:1px solid var(--border);
  border-radius:var(--radius); overflow:hidden; text-align:left;
  font:14px/1.45 var(--font);
}
.rps-bell-panel[hidden]{ display:none; }
.rps-bell-head{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:11px 14px; border-bottom:1px solid var(--border); background:var(--bg);
}
.rps-bell-head b{ font-size:13.5px; color:var(--navy); }
.rps-bell-n{ color:var(--muted-soft, var(--muted)); font-size:11.5px; font-weight:700; }
.rps-bell-list{ list-style:none; margin:0; padding:0; max-height:min(60vh,420px); overflow-y:auto; }
.rps-bell-item{ border-bottom:1px solid var(--border-soft); }
.rps-bell-item:last-child{ border-bottom:none; }
.rps-bell-item > a{
  display:block; padding:10px 14px 11px; color:var(--navy); text-decoration:none;
  border-left:3px solid transparent; transition:background var(--tr);
}
.rps-bell-item > a:hover{ background:var(--blue-light); text-decoration:none; }
.rps-bell-item.unread > a{ border-left-color:var(--blue); }
.rps-bell-item.unread.lv-alert > a{ border-left-color:var(--danger); }
.rps-bell-mod{
  display:block; color:var(--muted); font-size:10.5px; font-weight:800;
  text-transform:uppercase; letter-spacing:.4px; margin-bottom:2px;
}
.rps-bell-title{ display:block; font-size:13.5px; font-weight:600; line-height:1.35; }
.rps-bell-item.unread .rps-bell-title{ font-weight:800; }
.rps-bell-body{
  /* Two lines, then ellipsis: the panel is a summary, and the page it links to
     carries the full text. */
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
  color:var(--muted); font-size:12px; line-height:1.45; margin-top:2px;
}
.rps-bell-when{ display:block; color:var(--muted-soft, var(--muted)); font-size:11px; margin-top:4px; }
.rps-bell-empty{ padding:20px 14px; color:var(--muted); font-size:13px; text-align:center; }
.rps-bell-foot{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:10px 14px; border-top:1px solid var(--border); background:var(--bg);
}
.rps-bell-foot a{ font-size:12.5px; font-weight:700; color:var(--blue); }
.rps-bell-foot form{ margin:0; }
.rps-bell-allread{
  background:none; border:none; padding:0; cursor:pointer;
  color:var(--muted); font:700 12.5px var(--font); text-decoration:underline;
}
.rps-bell-allread:hover{ color:var(--danger); background:none; }
@media (max-width:520px){
  .rps-bell-panel{ position:fixed; top:56px; right:8px; left:8px; width:auto; }
}
