/* Eauxwell Connect — the design system in design_handoff_eauxwell_connect/.
 *
 * One stylesheet, no build step, no font fetch. Three things about it are
 * deliberate and worth knowing before editing:
 *
 * Nothing is rounded. Structure is drawn with rules instead — 2px under a
 * section heading, 1px between table rows, 1px gaps inside a 2px frame for the
 * metric grids. Everything is flush left, including button labels.
 *
 * The handoff's tokens use color-mix(). This file writes the same values as
 * rgba() because the field devices this runs on are not all new, and a colour
 * that fails to parse takes the rule with it.
 *
 * Archivo is the typeface, and it is served from here rather than fetched
 * from Google Fonts: the capture form has to work with no network, and a
 * webfont request is a network request. One variable file carries every
 * weight the system uses — 400, 600 and 800 — in 35 KB.
 *
 * The addresses below are relative to this stylesheet, not to the site root,
 * because this application is mounted under a path (/connectasset) and a
 * root-relative "/static/..." would miss it.
 */

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;              /* one variable file, every weight */
  font-display: swap;                /* the text is readable before it lands */
  src: url("fonts/archivo-latin.woff2") format("woff2");
  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;
}
/* The extended Latin, for the accents that appear in names and place names. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/archivo-latin-ext.woff2") format("woff2");
  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;
}

:root {
  --bg: #f3f2f2;
  --surface: #eae9e9;
  --ink: #201e1d;
  --paper: #f3f2f2;

  --accent: #03088C;

  --accent-100: #eef0fb;
  --accent-200: #dde1f7;
  --accent-300: #c2c9ef;
  --accent-400: #5d68c8;
  --accent-600: #02066f;
  --accent-700: #02055a;
  --accent-800: #010445;
  --accent-900: #010332;

  /* The one place a distinct colour earns its keep. The system says no status
     rainbow, and that rule is about tags — a mandatory field nobody has filled in
     is not a status, it is the thing standing between a record and a claim, and
     it has to be findable at a glance on a phone in the sun. */
  --need: #B3261E;
  --need-tint: #fdecea;

  --divider: rgba(32, 30, 29, .40);
  --ink-04: rgba(32, 30, 29, .04);
  --ink-07: rgba(32, 30, 29, .07);
  --ink-14: rgba(32, 30, 29, .14);
  --ink-55: rgba(32, 30, 29, .55);
  --ink-60: rgba(32, 30, 29, .60);
  --ink-65: rgba(32, 30, 29, .65);
  --ink-72: rgba(32, 30, 29, .72);
  --ink-78: rgba(32, 30, 29, .78);

  --font: "Archivo", "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
  --mono: "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace;

  --gutter: clamp(16px, 3vw, 40px);
  --page: 1320px;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.55;
  text-wrap: pretty;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font);
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -0.015em;
  margin: 0 0 8px;
}
h1 { font-size: 32px; }
h2 { font-size: 20px; letter-spacing: -0.01em; }
h3 { font-size: 17px; text-transform: none; letter-spacing: -0.01em; color: inherit; }

p { margin: 0 0 12px; }
a { color: var(--accent-700); text-underline-offset: 3px; }
a:hover { color: var(--accent); }
img { display: block; max-width: 100%; }

:focus { outline: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
::selection { background: var(--accent-200); }

.mono, code { font-family: var(--mono); font-size: .94em; }
/* A customer number must never break across two lines. It is read off a wall
   and typed back in; a hyphen is exactly where a browser wants to wrap, and
   half a number on each line is worse than no number. Not applied to `code`,
   which holds paths and commands that should still wrap. */
.mono { white-space: nowrap; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Title and standfirst on the left, the page's own control flush right and
   baseline with them — the design puts no ruled bar between a heading and the
   thing it heads. */
.pagehead {
  display: flex; flex-wrap: wrap; gap: 16px 32px;
  align-items: flex-end; justify-content: space-between;
  margin-bottom: 28px;
}
.pagehead > div { min-width: 0; }
.pagehead h1 { margin-bottom: 6px; }
.pagehead .lede { margin-bottom: 0; }
.filters.bare {
  border: 0; padding: 0; margin: 0; background: none;
  display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap;
}
/* A notice whose action belongs on the same line as its sentence. */
.notice.sameline {
  display: flex; gap: 12px 20px; align-items: baseline;
  justify-content: space-between; flex-wrap: wrap;
}
.notice.sameline > span { min-width: 0; }
.notice-action { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 0; }
/* A label a screen reader reads and the eye does not need, because the button
   beside it already says what the control is for. */
.backlink { font-size: 13px; margin: 0 0 6px; }
/* A label above a figure, not a heading in the document's outline. */
.microhead {
  font-size: 13px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-55); margin: 16px 0 4px;
}

/* A checklist reads as a list, not as a column three screens tall. */
.checkgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 6px 24px; margin: 4px 0 12px;
}
.sr-label {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.lede { font-size: 14.5px; line-height: 24px; color: var(--ink-72); margin: 8px 0 0; max-width: 66ch; }
.hint, .sub, .footnote { font-size: 12.5px; line-height: 20px; color: var(--ink-65); }
.hint { margin: 6px 0 0; }
.sub { margin: 8px 0 0; }
.footnote { margin: 12px 0 0; }
.error-text { font-size: 12.5px; color: var(--need); margin: 5px 0 0; }

/* --- the shell ----------------------------------------------------------- */

/* The application bar is solid accent. The landing page keeps the light one —
   it is a public page, not a place you are working. */
header.top {
  background: var(--accent);
  color: var(--paper);
}
/* Tier one of the design handoff's two-tier bar: brand, the six work areas,
   the person. 56px of accent blue; the active area carries a white underline. */
header.top .tier1 {
  display: flex;
  align-items: center;
  gap: 26px;
  min-height: 56px;
  padding: 0 max(var(--gutter), calc((100% - var(--page)) / 2 + var(--gutter)));
}
header.top .areas { display: flex; gap: 22px; flex-wrap: nowrap; overflow-x: auto; }
header.top .areas a {
  color: rgba(243, 242, 242, .78);
  font-size: 14px;
  text-decoration: none;
  white-space: nowrap;
  padding: 17px 0 15px;
  border-bottom: 2px solid transparent;
}
header.top .areas a:hover { color: var(--paper); }
header.top .areas a.on {
  color: var(--paper);
  font-weight: 600;
  border-bottom-color: var(--paper);
}
header.top .who-bar { margin-left: auto; }
/* Somebody's own stars, beside their own name on the account button. Small
   and in the bar's own ink: it is a standing, not a badge, and the button is
   on every page — a gold row of stars up there would shout on all of them. */
.chip-stars {
  font-size: 11px; letter-spacing: .5px; color: rgba(255, 255, 255, .72);
  margin-left: 2px; white-space: nowrap;
}

.account { position: relative; }
.account-chip {
  display: flex;
  align-items: center;
  gap: 9px;
  background: var(--accent-600);
  color: var(--paper);
  border: 0;
  border-radius: 0;
  padding: 6px 10px;
  font: inherit;
  font-size: 13.5px;
  cursor: pointer;
}
.account-chip:hover { background: var(--accent-700); }
.account-chip .monogram {
  width: 26px;
  height: 26px;
  background: var(--paper);
  color: var(--accent);
  font-weight: 800;
  font-size: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.account-chip .caret { font-size: 10px; opacity: .8; }
.account-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 70;
  min-width: 230px;
  background: var(--bg);
  border: 2px solid var(--divider);
  box-shadow: 0 10px 26px rgba(32, 30, 29, .18);
}
.account-menu .account-head {
  padding: 12px 14px 10px;
  border-bottom: 1px solid var(--divider);
}
.account-menu .account-head strong { display: block; font-size: 14px; }
.account-menu .account-head span { font-size: 12px; color: var(--ink-60); }
/* The stars keep their own two colours in here. The rule above paints every
   span in this block one grey, which made two earned stars and three unearned
   ones render identically — a menu saying five where the register says two. */
.account-menu .account-head .stars { font-size: 13px; letter-spacing: 1px;
                                     color: #b45309; }
.account-menu .account-head .stars .stars-off { color: var(--ink-14); }
.account-menu > a,
.account-menu > button {
  display: block;
  width: 100%;
  text-align: left;
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
  padding: 10px 14px;
  font-size: 13.5px;
  color: var(--ink);
  text-decoration: none;
}
.account-menu > a:hover,
.account-menu > button:hover { background: var(--accent-100, #eef0fb); color: var(--accent-700); }
/* The release, after the links: quiet in size, unmistakable in colour. On
   the light menu it wears the house blue; in the navy drawer, near-white —
   it was --ink-65 on navy once, which is how "what is this smudge?" got
   asked from the field. */
.account-menu .version-tag {
  display: block;
  padding: 9px 14px;
  font-size: 12px;
  color: var(--accent-700);
  border-top: 1px solid var(--divider);
}
.account-menu form { border-top: 1px solid var(--divider); margin: 0; }
.account-menu form button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 14px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--accent-700);
  cursor: pointer;
}
.account-menu form button:hover { background: var(--accent-100, #eef0fb); }
/* Tier two: only the active area's pages, on the page's own ground, the
   active page's underline overlapping the divider. */
header.top .tier2 {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
  flex: none;
  padding: 0 max(var(--gutter), calc((100% - var(--page)) / 2 + var(--gutter)));
  border-bottom: 2px solid var(--divider);
  background: var(--bg);
}
/* header.top nav a paints links paper for the navy bar; this row sits on the
   page's own ground, so its rules must outrank that one. */
header.top .tier2 a {
  font-size: 13.5px;
  font-weight: 400;
  color: var(--ink-72);
  text-decoration: none;
  white-space: nowrap;
  padding: 12px 0 10px;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
}
header.top .tier2 a:hover { color: var(--accent); }
header.top .tier2 a.on {
  color: var(--accent-700);
  font-weight: 600;
  border-bottom-color: var(--accent);
}
header.top .brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-right: auto;
  font-weight: 800;
  font-size: 24px;
  letter-spacing: -0.01em;
  color: var(--paper);
}
header.top .brand a { display: block; color: inherit; text-decoration: none; }
header.top .brand img { height: 30px; width: auto; }
header.top .brand span { font-size: 24px; line-height: 1; }
header.top .brand span br { display: none; }

header.top nav { display: flex; gap: 18px; flex-wrap: wrap; flex: 1; }
header.top nav a {
  color: rgba(243, 242, 242, .78);
  text-decoration: none;
  font-size: 14px;
  padding: 4px 0;
  border-bottom: 2px solid transparent;
}
header.top nav a:hover { color: var(--paper); }
header.top nav a.on { color: var(--paper); font-weight: 600; border-bottom-color: var(--paper); }

header.top .who {
  font-size: 14px;
  line-height: 20px;
  text-align: right;
  color: rgba(243, 242, 242, .75);
}
header.top .who strong {
  display: block;
  color: var(--paper);
  font-weight: 600;
  font-size: 15px;
}
header.top .who a { color: rgba(243, 242, 242, .7); text-decoration: none; }
header.top .who a:hover { color: var(--paper); }
header.top .who form { display: inline; }
header.top .who .guide-link::before { content: " · "; color: rgba(243, 242, 242, .45); }
header.top .who .version-tag { color: var(--paper); font-size: 12px; }
header.top .who .version-tag::before { content: " · "; color: rgba(243, 242, 242, .45); }
header.top .who button {
  background: none;
  border: 1px solid rgba(243, 242, 242, .5);
  color: var(--paper);
}
header.top .who button:hover { background: rgba(243, 242, 242, .14); color: var(--paper); }

main {
  max-width: var(--page);
  margin: 0 auto;
  padding: 36px var(--gutter) 64px;
}
main.narrow { max-width: 1100px; }

/* --- rules and sections -------------------------------------------------- */

hr, .rule { height: 2px; border: 0; margin: 16px 0; background: var(--divider); }

.section-head {
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-60);
  font-weight: 600;
  margin: 36px 0 0;
}
.section-head:first-of-type { margin-top: 20px; }

/* A panel is a titled section, drawn with a rule rather than a box. */
.panel { background: none; border: 0; padding: 0; margin: 0 0 36px; }
/* A table with nowhere to go squashes its columns until a row no longer reads
   as a line: on the capture settings screen the label and its tick box ended up
   in different places, and the left-hand column was off the side of the phone
   entirely. Thirty-three tables were sitting in a panel that could not scroll —
   only the ones wrapped in `.scroll` could. Every panel scrolls now, so wide
   content moves inside its own box and the page itself never goes sideways. */
.panel { overflow-x: auto; }
/* A panel that is a question rather than a report: it stands away from the
   page so it is not read as another table of facts. */
.warn-panel {
  border-left: 3px solid var(--warn, #B26A00); padding-left: 14px;
}
/* And it scrolls rather than crushing: below this width the columns keep their
   own line and the panel gains a scrollbar. */
@media (max-width: 760px) { .panel table { min-width: 540px; } }
/* A label and the control that belongs to it read as one thing or they read as
   two. On the settings screens the tick box and the word "require" wrapped onto
   separate lines as the column narrowed, so the row stopped being a row. */
/* The live scanner. A viewfinder is the primary way to read a barcode, so the
   button reads as an action and not as a footnote to the photograph. */
.pick-scan {
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--paper);
  border-radius: 0;
  padding: 11px 16px;
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  align-self: center;
  /* Its own row under the label and the action, so a long label never pushes
     it off the side of a phone. */
  grid-area: 3 / 1 / 4 / 3;
  justify-self: stretch;
}
/* On a phone the scan is the act itself: a full line of its own, with the
   photograph's quiet controls tucked beneath it. */
@media (max-width: 620px) {
  .pick .pick-scan { order: 4; flex: 1 1 100%; padding: 13px 16px; }
  .pick .pick-acts { order: 6; }
  .pick .pick-state { order: 7; flex: 1 1 auto; text-align: right; }
  .pick .pick-label { flex: 1 1 100%; margin-right: 0; }
}
.pick-scan:hover { background: var(--accent-700); }
.scan-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(0, 0, 0, .86);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
/* An author display beats the hidden attribute, so spell it out — without
   this, a finished scan leaves a dead black overlay nobody can dismiss. */
.scan-overlay[hidden] { display: none; }
.scan-box { width: min(560px, 100%); text-align: center; }
.scan-stage { position: relative; }
.scan-video {
  width: 100%;
  max-height: 66vh;
  border-radius: 4px;
  background: #000;
  object-fit: cover;
  display: block;
}
/* The box a person aims with. A barcode is read across its bars, so the guide
   is wide and short — it teaches the right framing without a word. */
.scan-guide {
  position: absolute;
  left: 9%;
  right: 9%;
  top: 50%;
  height: 28%;
  transform: translateY(-50%);
  border: 2px solid rgba(255, 255, 255, .9);
  border-radius: 3px;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .28);
  pointer-events: none;
}
.scan-say {
  color: #fff;
  margin: 12px 0;
  font-size: 15px;
  line-height: 1.4;
}
.scan-shot {
  display: block;
  width: 100%;
  margin: 12px 0 0;
  padding: 15px;
  font: inherit;
  font-size: 17px;
  font-weight: 700;
  background: var(--paper);
  color: var(--accent);
  border: 0;
  border-radius: 4px;
  cursor: pointer;
}
.scan-shot:disabled { opacity: .55; cursor: default; }
.scan-count {
  color: rgba(255, 255, 255, .45);
  font-size: 11px;
  letter-spacing: .06em;
  margin: 0 0 8px;
  font-variant-numeric: tabular-nums;
}
.scan-alt { margin: 4px 0 8px; }
.scan-alt-hint { color: rgba(255, 255, 255, .85); font-size: 13.5px; margin: 0 0 10px; }
.scan-alt button {
  display: block;
  width: 100%;
  margin: 0 0 8px;
  padding: 12px;
  font: inherit;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 1px;
  background: var(--paper);
  color: var(--accent);
  border: 0;
  border-radius: 4px;
  cursor: pointer;
}
.scan-overlay .actions { justify-content: center; }
.scan-overlay button { background: none; color: #fff; border-color: rgba(255,255,255,.6); }

/* A disabled action still has to read as the action it will be. Faded, not
   hidden — hiding it is what made the delete buttons look absent. */
button[disabled] { opacity: .45; cursor: not-allowed; }
td .check, th .check { white-space: nowrap; display: inline-flex; align-items: center; }
td .check input, th .check input { flex: none; }
.panel > h2 {
  margin: 0 0 12px;
  padding-bottom: 12px;
  border-bottom: 2px solid var(--divider);
}
.panel > h2 .tag { margin-left: 8px; vertical-align: middle; }
.panel > h3 { margin: 20px 0 8px; }

.grid { display: grid; gap: clamp(24px, 3vw, 48px); }
.grid.two { grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); }
.grid.three { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 20px; }

/* --- ruled metric grid --------------------------------------------------- */

/* The ruled frame is a 1px gap showing a divider-coloured ground through it.
   Flex rather than grid because the number of cells varies from one to eight: a
   fixed column count leaves empty tracks, and an empty track in this pattern is
   a grey block where a cell should be. */
/* The design's portfolio grid is four across and two down. The flex default
   below packs as many as fit, which is right for the variable strips on other
   pages; this one is asked for four. */
.grid.metrics.eight { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1000px) { .grid.metrics.eight { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .grid.metrics.eight { grid-template-columns: minmax(0, 1fr); } }
.grid.metrics {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  background: var(--divider);
  border: 2px solid var(--divider);
  margin: 24px 0 20px;
}
.metric { background: var(--bg); padding: 20px; flex: 1 1 190px; min-width: 0; }

/* The same framed pattern at card size — report definitions, evidence packs. */
.grid.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  background: var(--divider);
  border: 2px solid var(--divider);
  margin: 24px 0 36px;
}
.grid.cards > .panel {
  background: var(--bg);
  padding: 24px;
  margin: 0;
  flex: 1 1 360px;
  min-width: 0;
}
.grid.cards .kv { grid-template-columns: 110px minmax(0, 1fr); font-size: 13.5px; }
.grid.cards .kv dt, .grid.cards .kv dd { padding: 6px 12px 6px 0; border-bottom: 0; }
.grid.cards .grid.three { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.grid.cards > .panel > h2 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-size: 19px;
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 16px;
}
.metric .k {
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-60);
  margin: 0;
}
.metric .n {
  font-weight: 800;
  font-size: 38px;
  line-height: 1;
  color: var(--accent);
  margin: 10px 0 0;
  font-variant-numeric: tabular-nums;
}
.metric .sub { font-size: 12.5px; color: var(--ink-65); margin: 8px 0 0; }
/* No status rainbow: a metric that needs attention says so in its own words. */
.metric.good .n, .metric.warn .n, .metric.bad .n { color: var(--accent); }

/* --- notices ------------------------------------------------------------- */

.notice {
  border: 2px solid var(--divider);
  padding: 16px 20px;
  margin: 0 0 24px;
  font-size: 14px;
  line-height: 23px;
}
.notice p { margin: 0 0 8px; }
.notice p:last-child { margin-bottom: 0; }
.notice.info { border-color: var(--divider); }
.notice.warn { border-color: var(--accent); background: var(--accent-100); }
/* A refusal is drawn in the same red as a field being asked for, and for the
   same reason: it is the thing standing between the officer and a saved record.
   Drawn in the house blue it read as one more piece of information. */
.notice.bad { border-color: var(--need); background: var(--need-tint); }
.notice.need { border-color: var(--need); background: var(--need-tint); }
/* Two ways to give a photograph, side by side. Android's own chooser was going
   straight to the gallery on some handsets, so the choice is made explicit here
   rather than left to whatever the phone decides to offer. */
/* Each photograph is a ledger row, the way the design pack draws lists:
   label · state · camera · + Add, ruled underneath. Five photographs read
   as five lines, and the state fills in where "not yet taken" was. */
/* A grid and not a wrapping row. As a flex row the empty state span took a
   flex share, grew to the width of the line and pushed the button onto the
   next one — so "+ Add" landed wherever the label happened to end, at 322px
   on one row and 297px on the next. Five photographs read as five ruled
   lines only if the action is in the same place on every one of them.

   Placed by area: label and action share the first row, the state goes under
   them, the scanner under that. A long label wraps inside its own cell
   instead of moving the button. */
.pick {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 12px;
  row-gap: 4px;
  padding: 10px 2px;
  margin: 0;
  border-bottom: 1px solid var(--divider);
}
.pick .pick-label { margin: 0; grid-area: 1 / 1 / 2 / 2; }
.pick-state {
  grid-area: 2 / 1 / 3 / 3;
  color: var(--ink-60);
  font-size: 12.5px;
}
.pick-state:empty { display: none; }
/* Both ways in sit in the action cell together: the camera for the officer
   at the wall, the upload for the office and for the handsets that open the
   gallery by themselves. One cell, so a long label still wraps inside its
   own column and neither button moves. */
.pick-acts {
  grid-area: 1 / 2 / 2 / 3;
  justify-self: end;
  display: flex; align-items: center; gap: 14px;
}
.pick-cam, .pick-btn {
  border: 0;
  background: none;
  padding: 6px 2px;
  cursor: pointer;
  color: var(--accent);
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
}
.pick-cam:hover, .pick-btn:hover { color: var(--accent-700); text-decoration: underline; }
.pick-cam .ico { font-size: 13px; }

/* A row that carries no label of its own hugs the left under the page's own
   heading: actions first, the state trailing quietly. Stretched, it left a
   void with the buttons marooned at the far edge. */
.pick-bare {
  display: inline-flex; align-items: center; gap: 8px 12px;
  border-bottom: 0; padding: 4px 0 2px;
}
.pick-bare .pick-state { flex: none; order: 3; min-width: 0; }
.pick-bare .pick-acts { order: 2; justify-self: auto; }
/* The card: one photograph, camera first, no choices. */
.pick-card {
  display: block;
  border-bottom: 0;
  padding: 4px 0 6px;
}
.pick-card-title { font-size: 20px; margin: 0 0 6px; }
.pick-card-say { color: var(--ink-72); font-size: 14px; line-height: 1.5;
                 margin: 0 0 14px; max-width: 56ch; }
.pick-card-camera {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: var(--accent);
  color: var(--paper);
  border: 0;
  border-radius: 4px;
  padding: 15px;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
}
.pick-card-camera:hover { background: var(--accent-700); }
.pick-card-camera .ico { font-size: 18px; }
.pick-card-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 10px;
}
.pick-card-gallery {
  border: 0;
  background: none;
  padding: 2px 0;
  font: inherit;
  font-size: 14px;
  color: var(--accent);
  text-decoration: underline;
  cursor: pointer;
}
.pick-card-skip { font-size: 14px; color: var(--ink-60); }
.pick-card .pick-state { flex: none; min-width: 0; }
.pick-card .pick-state:not([hidden]) { color: var(--accent-700); font-weight: 600; }
/* Reachable by keyboard and by a screen reader, invisible to everyone else.
   display:none would take it out of the form on some browsers. */
.pick-input {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0;
  clip: rect(0 0 0 0);
  overflow: hidden;
}
.pick-chosen { margin: 4px 0 2px; }

/* Page links. Small, quiet, and out of the way of the table above them. */
/* The one-line message after an action. It had no styling at all, so it arrived
   as a bare line of text above the page and was easy to miss entirely. */
/* The saved/failed message, fixed to the middle of the screen wherever the
   page is scrolled — a long capture form leaves the thumb at the bottom, and
   a strip at the top of the page was invisible exactly when it mattered.
   Dimmed page behind it, bold words, one OK to acknowledge. */
.flash-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(32, 30, 29, .45);
}
.flash {
  border: 2px solid var(--accent-300);
  border-left-width: 5px;
  background: var(--bg);
  color: var(--accent-700);
  padding: 20px 22px;
  border-radius: 0 4px 4px 0;
  font-size: 16.5px;
  font-weight: 700;
  max-width: 440px;
  width: 100%;
  box-shadow: 0 14px 34px rgba(32, 30, 29, .3);
}
.flash p { margin: 0 0 14px; }
.flash-ok {
  display: block;
  width: 100%;
  font-size: 16px;
  padding: 11px;
}

/* A message about something that went wrong. Red, and it stays on the page the
   person was already on rather than replacing it with an error screen. */
.flash-bad {
  border-color: var(--need);
  color: var(--need);
}
.flash-bad .flash-ok { background: var(--need); }

.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 14px 0 4px;
  font-size: 14px;
}
.pager-count { color: var(--ink-60); }
/* The top pager sits between the filters and the table, so it needs a rule to
   read as a boundary rather than as another row of the form above it. */
.pager-top {
  margin: 4px 0 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--ink-14);
}
.pager-links { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.pager-links a, .pager-here, .pager-off {
  min-width: 34px;
  padding: 6px 10px;
  border: 1px solid var(--ink-14);
  border-radius: 3px;
  text-align: center;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
}
.pager-links a { color: var(--accent); }
.pager-links a:hover { border-color: var(--accent); background: var(--accent-100); }
.pager-here { border-color: var(--accent); background: var(--accent); color: var(--paper); font-weight: 600; }
.pager-off { border-color: transparent; color: var(--ink-14); }
.pager-gap { padding: 0 2px; color: var(--ink-60); }

.need-list { margin: 10px 0 0; padding-left: 20px; columns: 2; }
/* On a phone two columns wrap every item onto two lines. A field name is a
   phrase, not a word, so it gets the full width. */
@media (max-width: 560px) { .need-list { columns: 1; } }
.need-list a { color: var(--need); font-weight: 600; }
/* The way from a kept row to the thing still using it. Reads as a quiet link
   at the end of the reason, not a button competing with the reason itself. */
.reason-link { white-space: nowrap; font-weight: 600; color: var(--accent-700);
               text-decoration: none; }
.reason-link:hover { text-decoration: underline; color: var(--accent); }
.notice.ok { border-color: var(--ink); }
.notice .actions { margin-top: 12px; }

/* --- tags ---------------------------------------------------------------- */

.tag {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  letter-spacing: .02em;
  padding: 3px 10px;
  background: var(--surface);
  color: var(--ink);
}
.tag.ok, .tag.good { background: var(--surface); color: var(--ink); }
.tag.warn, .tag.bad { background: var(--accent-100); color: var(--accent-800); }
.tag.info { background: none; border: 1px solid var(--accent); color: var(--accent); }
.tag.need { background: var(--need-tint); color: var(--need); font-weight: 600; }
/* The network lamp is the one tag that earns traffic-light colour: it is a
   state light, not a label, and it is read at arm's length in the sun. */
.tag.net-on { background: #0e6b31; color: #fff; font-weight: 700; }
.tag.net-off { background: #8c1503; color: #fff; font-weight: 700; }

/* A box still to be filled: marked where it is, and lit when arrived at from the
   outstanding list, so following a link lands somewhere obvious. */
.needed { border-left: 3px solid var(--need); padding-left: 10px; color: var(--need); }
.needed input, .needed select, .needed textarea { border-color: var(--need); }
.needed:target, :target > .needed { background: var(--need-tint); }
:target { scroll-margin-top: 90px; }
label.check.needed { padding-left: 10px; }

/* --- buttons ------------------------------------------------------------- */

button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  gap: 6px;
  cursor: pointer;
  text-decoration: none;
  font-family: var(--font);
  font-weight: 800;
  font-size: 14px;
  line-height: 1.2;
  padding: 10px 15px;
  border: 1px solid transparent;
  border-radius: 0;
  background: var(--accent);
  color: var(--paper);
}
button:hover, .btn:hover { background: var(--accent-600); color: var(--paper); }
button:active, .btn:active { background: var(--accent-700); }
button:disabled { opacity: .45; cursor: not-allowed; }

.btn.ghost, button.ghost {
  background: none;
  color: var(--accent-700);
  border-color: var(--divider);
}
.btn.ghost:hover, button.ghost:hover { background: var(--ink-07); color: var(--accent); }

/* Destructive actions keep their own weight. The system's rule against a status
   rainbow is about tags; a button that erases a customer is not a status. */
button.danger, .btn.danger { background: var(--ink); color: var(--paper); }
button.danger:hover, .btn.danger:hover { background: #000; }

button.small, .btn.small { padding: 6px 10px; font-size: 12.5px; }
.actions { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 16px; }
form.inline { display: inline-flex; gap: 8px; align-items: center; vertical-align: middle; }
form.inline input, form.inline select { width: auto; max-width: 240px; margin: 0; }
form.inline button { margin: 0; white-space: nowrap; }

/* --- forms --------------------------------------------------------------- */

label { display: block; font-size: 12px; margin-bottom: 5px; color: var(--ink-72); }
input, select, textarea {
  width: 100%;
  min-height: 38px;
  padding: 7px 10px;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  caret-color: var(--accent);
  background: var(--surface);
  border: 1px solid var(--divider);
  border-radius: 0;
}
input:hover, select:hover, textarea:hover { border-color: rgba(32, 30, 29, .45); }
input:focus-visible, select:focus-visible, textarea:focus-visible {
  border-color: var(--accent);
  outline-offset: 0;
}
textarea { min-height: 90px; resize: vertical; }
input[type="checkbox"], input[type="radio"] { width: auto; min-height: 0; }
input.field-error, select.field-error, textarea.field-error { border-color: var(--need); border-width: 2px; }

fieldset { border: 0; border-top: 2px solid var(--divider); padding: 20px 0 0; margin: 0 0 28px; }
legend {
  padding: 0 12px 0 0;
  font-weight: 800;
  font-size: 13px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-60);
}
fieldset.lead { border-top-color: var(--accent); border-top-width: 2px; }
fieldset.lead > legend { color: var(--accent); }
.lead-photo input { padding: 14px; font-size: 16px; }

label.check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--ink);
  margin: 0 0 8px;
}
label.check input { width: auto; margin: 0; }

/* --- filter bar and selection bar ---------------------------------------- */

.filters {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  align-items: flex-end;
  padding: 16px 0;
  border-bottom: 2px solid var(--divider);
  margin-bottom: 16px;
}
.filters label { margin: 0 0 4px; }
.filters > div { min-width: 160px; }
.filters .check { padding: 9px 0; margin: 0; white-space: nowrap; }
.filter-actions { display: flex; gap: 8px; align-items: flex-end; }

.select-bar {
  display: flex;
  gap: 14px;
  align-items: center;
  flex-wrap: wrap;
  margin: 0 0 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--divider);
}
.select-bar .check { margin: 0; font-weight: 600; }
/* The why beneath the verbs: one reason, written into every selected
   connection's status history. */
.bulk-reason { display: block; max-width: 460px; margin: -8px 0 16px;
               font-size: 13px; }

/* --- the field bar ------------------------------------------------------- */
/* Where you are, whether there is a network, what is still held on the phone,
   and the button that empties it. The desk filter bar was doing this and wrapped
   into four ragged rows on a small screen, each with its own label above it. */
.fieldbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
  padding: 10px 0;
  border-top: 1px solid var(--ink-14);
  border-bottom: 1px solid var(--ink-14);
}
.fieldbar select { flex: 1 1 200px; min-width: 0; margin: 0; }
.fieldbar-site { font-weight: 600; }
/* A photograph's own label, above its button. Without this it reads as the tail
   of the hint above it rather than as the name of the thing below. */
.pick-label { display: inline-block; font-weight: 600;
              margin: 12px 14px 2px 0; vertical-align: middle; }
.pick-label .tag { margin-left: 6px; font-weight: 400; vertical-align: middle; }
.fieldbar-state { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.fieldbar-state .tag { margin: 0; }
/* On a phone the select takes the width and the state sits under it, which is
   two tidy rows rather than four ragged ones. */
@media (max-width: 560px) {
  .fieldbar { gap: 8px; }
  .fieldbar select, .fieldbar-site { flex: 1 1 100%; }
  .fieldbar-state { margin-left: 0; width: 100%; }
  .fieldbar-state button { margin-left: auto; }
}

/* --- tables -------------------------------------------------------------- */

.scroll { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
  text-align: left;
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-60);
  padding: 10px 12px 10px 0;
  border-bottom: 2px solid var(--divider);
  white-space: nowrap;
}
td { padding: 11px 12px 11px 0; border-bottom: 1px solid var(--divider); vertical-align: top; }
tbody tr:hover, table tr:hover { background: var(--ink-04); }
/* Right-aligned, but never gapless: cells carry no left padding, so a .num
   column that zeroes its right padding leaves adjacent columns touching the
   moment the table sits at content width — READIMPORTEDQUARANTINED, as a
   phone rendered it. Only the last column earns the flush edge. */
th.num, td.num { text-align: right; }
th:last-child, td:last-child { padding-right: 0; }
th.pick, td.pick { width: 28px; padding-right: 0; }
td.pick input, th.pick input { width: auto; margin: 0; }
.row-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

dl.kv { display: grid; grid-template-columns: 190px minmax(0, 1fr); margin: 0; }
dl.kv dt {
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-60);
  padding: 11px 12px 11px 0;
  border-bottom: 1px solid var(--divider);
}
dl.kv dd { margin: 0; padding: 11px 0; border-bottom: 1px solid var(--divider); font-size: 14px; }

.thumbs { display: flex; gap: 12px; flex-wrap: wrap; }
.thumbs img { filter: grayscale(1) contrast(1.08); border: 1px solid var(--divider); }

/* Two photographs of one meter, side by side: the label read at assignment and
   the meter on the wall. Equal cells and a fixed shape, because a portrait
   phone photograph beside a landscape one reads as two unrelated pictures.
   In colour, unlike the evidence thumbnails elsewhere — the whole use of these
   is judging a serial and a seal, and grey costs the reader the wire colours. */
.photo-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.photo-pair figure, .photo-row figure { margin: 0; min-width: 0; }
/* Any number of them, on any record. The pole and the transformer used a
   class with no rules behind it at all, so their evidence rendered as a bare
   image at whatever size the file happened to be — the one page where a
   photograph is the record. Same frame as the meter's, which was thought
   about; three across where there is room, one on a phone. */
.photo-row {
  display: grid; gap: 14px;
  /* 230px was a thumbnail. These are the record — a transformer's rating
     plate is read off the photograph precisely when the plate itself is
     painted over or four metres up, and a serial does not survive being
     shrunk to a contact sheet. */
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
}
/* A wall of small framed pictures, each keeping its own shape.
   For context shots — the rooms of a house, six of them — where the square
   mat of .frame made every picture the same size and shape and six rooms
   read as six copies of one. Columns rather than a grid, so a portrait and a
   landscape sit at their own heights and the wall is asymmetric by the
   pictures' own doing rather than by an arrangement imposed on them. */
.photo-wall { columns: 168px; column-gap: 10px; }
.photo-wall figure { break-inside: avoid; margin: 0 0 10px; }
/* The caption is the row's caption, one size down. It inherits nothing from
   .photo-row's rules — which is how "1 of 4" and the date first ran together
   into "1 of 42026-09-11" — so the wall states its own. */
.photo-wall figcaption {
  margin-top: 5px; font-size: 11px; line-height: 1.3; color: var(--ink-65);
}
.photo-wall figcaption strong { display: block; color: var(--ink); font-weight: 600; }
/* The frame without the square. The mat and the border stay — a photograph
   on this platform is always in a frame — but the shape is the picture's.
   Written with both classes because .frame sets the square lower down the
   file: a single class either side would leave the winner to source order,
   which is how the wall first rendered as six squares again. */
.frame.frame-free { aspect-ratio: auto; padding: 4px; }
.frame.frame-free img { height: auto; object-fit: contain; }

/* Where the photographs have the page to themselves rather than half of it. */
.photo-row-large {
  gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(min(380px, 100%), 1fr));
}
.frame {
  display: block;
  position: relative;
  /* Square. Phone photographs of a meter come portrait, photographs of a run
     of cable come landscape, and a portrait frame turns the second into a
     letterboxed strip. A square takes both with the same modest margin, and
     two squares side by side read as a pair without cropping either. */
  aspect-ratio: 1 / 1;
  background: var(--surface);
  border: 1px solid var(--ink-14);
  border-radius: 4px;
  overflow: hidden;
  padding: 6px;                     /* the mat */
  box-shadow: 0 1px 2px var(--ink-07);
}
.frame img {
  width: 100%; height: 100%;
  /* Contained, not cropped. These are evidence: the serial can sit anywhere in
     the frame, and a fill that crops to a tidy shape is a fill that can cut the
     number off. The mat around it is what makes two different shapes read as a
     matched pair. */
  object-fit: contain;
  display: block;
  border: 0;
  filter: none;
  background: var(--ink-07);
}
a.frame:hover { border-color: var(--accent-400); }
a.frame:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The empty half. A meter assigned but not yet fitted has one photograph, and
   the missing one is worth seeing as a gap of the same size rather than as an
   absence of anything. */
.frame.empty {
  display: grid; place-items: center;
  border-style: dashed;
  color: var(--ink-55);
  font-size: 12px;
  text-align: center;
  padding: 8px;
}
/* Choosing one of the photographs the register already holds: small frames in
   a row, each its own form, because picking one is an act on the record. */
.photo-pick-row {
  display: grid; gap: 12px; margin-top: 10px;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.photo-pick-row form { display: flex; flex-direction: column; gap: 6px; }
.photo-pick-row button { width: 100%; }
.frame-small { aspect-ratio: 1 / 1; padding: 4px; }
.photo-pick-row figcaption { font-size: 11px; color: var(--ink-65); line-height: 1.3; }

.photo-pair figcaption, .photo-row figcaption {
  margin-top: 6px;
  font-size: 12px;
  color: var(--ink-65);
  line-height: 1.35;
}
.photo-pair figcaption strong, .photo-row figcaption strong { display: block; color: var(--ink); font-weight: 600; }
@media (max-width: 420px) {
  .photo-pair { grid-template-columns: 1fr; }
}

.issued {
  font-size: 18px;
  font-weight: 600;
  padding: 2px 8px;
  background: var(--bg);
  border: 1px solid var(--divider);
  user-select: all;
}

details.tight summary { cursor: pointer; font-size: 12px; color: var(--accent-700); margin-top: 4px; }
details.tight form { padding: 8px 0 0; }
details.tight .check { margin: 0 0 4px; white-space: nowrap; }

.trace { font-family: var(--mono); font-size: 12px; white-space: pre-wrap; color: var(--ink-65); }

/* --- sign-in ------------------------------------------------------------- */

body.signin { background: var(--accent); }
.signin-split { min-height: 100vh; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.signin-brand {
  padding: clamp(28px, 5vw, 64px);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  color: var(--paper);
}
.signin-brand img { width: 180px; height: auto; }
.signin-brand h1 {
  font-size: clamp(34px, 3.6vw, 52px);
  line-height: 1.06;
  margin: 0 0 0 -0.058em;
  color: var(--paper);
}
.signin-brand h1 span { display: block; }
.signin-brand .statement { font-size: 15px; line-height: 25px; margin: 24px 0 0; max-width: 44ch; color: rgba(243, 242, 242, .75); }
.signin-brand .ndpa { font-size: 12.5px; line-height: 20px; margin: 0; max-width: 52ch; color: rgba(243, 242, 242, .65); }
.signin-form { background: var(--bg); padding: clamp(28px, 5vw, 64px); display: flex; align-items: center; }
.signin-form > div { width: min(400px, 100%); }
.signin-form .kicker {
  font-size: 13px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent-700);
  margin: 0 0 16px;
}
.signin-form h2 { font-size: 28px; margin: 0 0 8px; }
.signin-form form { display: grid; gap: 18px; }

/* --- landing ------------------------------------------------------------- */

.landing { --page: 1200px; }
.landing main { max-width: 1200px; padding-top: 0; }
/* One left edge down the whole landing page. The signed-in application is
   1320px wide on its own gutter and the landing is 1200px on the design's;
   the two were being mixed, so brand, hero, poster and footer each began at a
   different x — against the handoff's first rule, that everything is flush
   left. The masthead was also inset twice, once here and once by .tier1. */
.landing main {
  max-width: 1200px;
  padding-inline: clamp(20px, 4vw, 64px);
}
.landing header.top .tier1 { padding: 0; }
.landing header.top {
  padding-inline: max(clamp(20px, 4vw, 64px), calc((100% - 1200px) / 2 + clamp(20px, 4vw, 64px)));
  background: var(--bg);
  color: var(--ink);
  border-bottom: 2px solid var(--divider);
}
.landing header.top nav { flex: 0 1 auto; gap: 24px; }
.landing header.top .brand { color: var(--ink); }
.landing header.top nav a { color: var(--ink); }
.landing header.top nav a:hover { color: var(--accent); }
/* The landing page's trail. The map page had its own set of these and took
   the shared rule with it when it went. */
.crumb { color: var(--accent-700); text-decoration: underline; }
.landing header.top .crumb { color: var(--ink-60); }
.landing header.top .crumb::after { content: " /"; color: var(--ink-14); }
@media (max-width: 1080px) and (min-width: 861px) {
  /* Between wide and phone the row cannot hold everything; it becomes two
     deliberate decks — brand and buttons, then the links — never a rag. */
  .landing header.top .masthead { flex-wrap: wrap; row-gap: 0; }
  .landing header.top .masthead .brand { order: 1; }
  .landing header.top .masthead .actions { order: 2; margin-left: auto !important; }
  .landing header.top .masthead nav { order: 3; flex-basis: 100%; padding: 0 0 12px; }
}

.hero { padding: clamp(64px, 9vw, 112px) 0 84px; }
.hero h1 { font-size: clamp(42px, 6vw, 80px); line-height: 1.06; letter-spacing: -0.02em; margin: 0 0 0 -0.058em; }
.hero h1 span { display: block; }
.hero p { font-size: 17px; line-height: 28px; max-width: 52ch; margin: 36px 0 0; color: var(--ink-78); }

.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 42px 28px; padding: 56px 0; }
.stat-row .n {
  font-weight: 800; font-size: clamp(34px, 3.4vw, 48px); line-height: 1;
  color: var(--accent); margin: 0; text-align: left;
  /* The token sheet asks for tabular figures on every metric numeral, and
     these are the metrics. */
  font-variant-numeric: tabular-nums;
}
/* The design's photo split is five parts text to seven parts photograph, not
   two equal halves — the photograph is the larger of the two. */
.split-57 {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: 28px clamp(24px, 5vw, 88px);
}
/* A note on a card that has to be read, rather than a grey aside. */
.cardnote { font-size: 12.5px; line-height: 20px; color: var(--accent-700); margin: 10px 0 0; }
.stat-row .k { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-72); margin: 12px 0 0; }

/* Scope chips: the pack's segmented construction — a hidden input inside
   the label, the checked state wearing the accent. A tap is a choice; no
   Ctrl, no listbox, the same gesture on a phone and a desk. */
/* The record head: photograph beside the name, one glance. The same
   compact size on a phone and a desk — a passport photo is small by
   nature, and the name never yields its line. */
.record-head { display: flex; gap: 16px; align-items: flex-start; }
.record-head-words { min-width: 0; flex: 1 1 auto; }
.record-head h1 { margin-top: 0; }
.head-portrait { flex: 0 0 auto; margin: 6px 0 0; }
.head-portrait img {
  width: 84px; height: 104px;
  object-fit: cover;
  display: block;
  border: 1px solid var(--ink-14);
}
.head-portrait-add {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 84px; height: 104px;
  border: 1px dashed var(--ink-14);
  color: var(--accent-700, var(--accent));
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.head-portrait-add:hover { border-color: var(--accent); }
.portrait-actions {
  display: flex;
  gap: 10px;
  margin-top: 6px;
  align-items: baseline;
  white-space: nowrap;
}
.portrait-actions form { margin: 0; }
.portrait-actions .linklike { display: inline-block; line-height: 1.4; }
.nowrap { white-space: nowrap; }
.field-flash {
  outline: 3px solid #8C1503;
  outline-offset: 2px;
}

/* Held metering acts, pinned to the bottom of the screen: the strip used to
   sit at the top of the page, which from the bottom of a long form is
   nowhere. It exists only while something is held, so pinning costs nothing
   the rest of the time; main gets room so the bar never covers a button. */
.actions-queue {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 150;
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  background: var(--accent-100, #eef0fb);
  border: 0;
  border-top: 2px solid var(--accent);
  padding: 12px 16px;
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  box-shadow: 0 -8px 22px rgba(32, 30, 29, .18);
}
body.has-actions-queue main { padding-bottom: 84px; }

.install-bar {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
  background: var(--accent-100, #eef0fb);
  border: 1px solid var(--accent);
  padding: 10px 14px;
  margin: 0 0 18px;
  font-size: 14px;
}
.install-bar span { flex: 1 1 240px; }
.install-bar button { font-size: 13px; padding: 7px 14px; }

.viewbar { margin: 4px 0 14px; font-size: 14px; }
.viewbar strong { color: var(--ink); }
.viewbar a { color: var(--accent-700, var(--accent)); }

.metric-link { text-decoration: none; color: inherit; display: block; }
/* display: block above beat the browser's own [hidden], so a tile the reader
   switched off stayed on. */
.metric[hidden] { display: none; }
.metric-link:hover .n { color: var(--accent-700); }
.metric-link:hover { background: var(--accent-100, #eef0fb); }

/* The scope picker: one button wearing the answer, one panel holding the
   question. Projects are groups; their sites sit beneath them, ruled and
   flush like everything else on this platform. */
.scopepick { position: relative; }
.scopepick-btn {
  /* .filters button paints white-on-navy; this one is paper, so it must
     say its own ink or the label vanishes white-on-white. */
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 220px;
  border: 1px solid var(--ink-14);
  background: var(--paper);
  padding: 9px 14px;
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
}
.scopepick-btn:hover, .scopepick-btn:focus-visible {
  border-color: var(--accent);
  background: var(--paper);
  color: var(--ink);
}
.scopepick-caret { color: var(--ink-60); flex: 0 0 auto; }
.scopepick-panel {
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  z-index: 60;
  min-width: 300px;
  max-width: min(92vw, 380px);
  max-height: min(60vh, 440px);
  overflow-y: auto;
  background: var(--bg);
  border: 2px solid var(--divider);
  box-shadow: 0 10px 26px rgba(32, 30, 29, .18);
  padding: 0;
}
/* Pinned while the middle scrolls: the way in at the top, the way out at the
   bottom, however long the portfolio between them. */
.scopepick-search {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg);
  padding: 10px 14px;
  border-bottom: 1px solid var(--divider);
}
.scopepick-search input {
  width: 100%;
  font-size: 13.5px;
  padding: 7px 10px;
}
.scopepick-all {
  color: var(--ink);
  display: block;
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--divider);
  background: none;
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  text-align: left;
  padding: 11px 14px;
  cursor: pointer;
}
.scopepick-all:hover { background: var(--accent-100, #eef0fb); color: var(--ink); }
.scopepick-group + .scopepick-group { border-top: 1px solid var(--divider); }
.scopepick-head {
  display: flex;
  gap: 9px;
  align-items: center;
  padding: 8px 14px;
}
/* The group's name is the handle that opens it; the checkbox beside it takes
   the whole project without ever opening the fold. */
.scopepick-disclose {
  flex: 1;
  min-width: 0;
  display: flex;
  gap: 9px;
  align-items: center;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-60);
  text-align: left;
  padding: 2px 0;
  cursor: pointer;
}
.scopepick-disclose:hover { color: var(--ink); }
.scopepick-tally {
  margin-left: auto;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}
.scopepick-tally.has-some { color: var(--accent-700); font-weight: 700; }
.scopepick-chev {
  flex: 0 0 auto;
  color: var(--ink-60);
  transition: transform .15s ease;
}
.scopepick-group.open .scopepick-chev { transform: rotate(90deg); }
.scopepick-row {
  display: flex;
  gap: 9px;
  align-items: center;
  padding: 7px 14px 7px 26px;
  font-size: 13.5px;
  cursor: pointer;
}
.scopepick-row:hover { background: var(--accent-100, #eef0fb); }
.scopepick-row .mono { margin-left: auto; color: var(--ink-60); font-size: 12px; }
.scopepick-head input, .scopepick-row input { margin: 0; width: auto; flex: 0 0 auto; }
.scopepick-name { min-width: 0; }
.scopepick-foot {
  position: sticky;
  bottom: 0;
  background: var(--bg);
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 10px 14px;
  border-top: 1px solid var(--divider);
  margin-top: 4px;
}
/* The freeze: a bar nobody can miss, and a switch panel that wears the
   state. Amber, not red — records are safe, just still. */
.freeze-bar {
  background: #8C5A03;
  color: #fff;
  padding: 9px 22px;
  font-size: 14px;
}
.freeze-bar a { color: #fff; }
.freeze-on { border-left: 6px solid #8C5A03; }
button.danger { background: #8C1503; }
button.danger:hover { background: #6d1002; }
table.rights td.num input { margin: 0; }

.linklike {
  white-space: nowrap;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 13px;
  color: var(--accent-700, var(--accent));
  text-decoration: underline;
  cursor: pointer;
}
.linklike:hover { background: none; color: var(--accent); }

.scope { margin: 0 0 18px; }
.scope-inline { flex: 1 1 100%; }
.scope-row { display: flex; align-items: baseline; gap: 14px; margin: 0 0 10px; }
.scope-label { font-size: 12px; text-transform: uppercase; letter-spacing: .06em;
               color: var(--ink-60); flex: 0 0 auto; min-width: 64px; }
.scope-label .hint { text-transform: none; letter-spacing: 0; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { cursor: pointer; }
.chip input { position: absolute; opacity: 0; pointer-events: none; }
.chip span {
  display: inline-block;
  border: 1px solid var(--ink-14);
  background: var(--paper);
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.chip:hover span { border-color: var(--accent); color: var(--accent); }
.chip input:checked + span {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper);
}
.chip input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Legends inside a long form: the pack's uppercase label over a hairline,
   so a form reads as chapters instead of a pile. */
.form-legend {
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent-700);
  border-bottom: 1px solid var(--divider);
  padding-bottom: 6px;
  margin: 26px 0 14px;
}
form .form-legend:first-of-type { margin-top: 6px; }

.kicker { font-size: 13px; letter-spacing: .08em; text-transform: uppercase; color: var(--accent-700); margin: 0 0 20px; }

.feature {
  display: grid;
  grid-template-columns: minmax(64px, 140px) minmax(0, 400px) minmax(0, 1fr);
  gap: 20px clamp(24px, 4vw, 64px);
  align-items: baseline;
  padding: 36px 0;
}
.feature + .feature { border-top: 2px solid var(--divider); }
.feature .no { font-weight: 800; font-size: 15px; margin: 0; font-variant-numeric: tabular-nums; }
.feature h3 { font-size: 24px; line-height: 28px; margin: 0; }
.feature p { font-size: 15.5px; line-height: 26px; margin: 0; color: var(--ink-78); max-width: 52ch; }

.screen-links { display: flex; gap: 10px 24px; flex-wrap: wrap; align-items: baseline; }
.screen-links a { font-weight: 800; font-size: 17px; text-decoration: none; }

.poster { background: var(--accent); color: var(--paper); }
.poster .inner { max-width: 1200px; margin: 0 auto; padding: 84px clamp(20px, 4vw, 64px); }
.poster h2 { font-size: clamp(34px, 4.2vw, 56px); line-height: 1.06; margin: 0 0 0 -0.058em; color: var(--paper); }
.poster h2 span { display: block; }
.poster .actions { margin-top: 42px; }
.poster .btn.ghost { color: var(--paper); border-color: var(--paper); }
.poster .btn.ghost:hover { background: rgba(243, 242, 242, .12); color: var(--paper); }

.site-footer {
  max-width: 1200px;
  margin: 0 auto;
  padding: 42px clamp(20px, 4vw, 64px);
  font-size: 13px;
  line-height: 22px;
  color: var(--ink-72);
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
  justify-content: space-between;
}

/* --- the corporate landing ----------------------------------------------- */

.corp-nav { display: flex; gap: 22px; }
.corp-nav a { color: var(--ink); text-decoration: none; font-size: 14px; }
.corp-nav a:hover { color: var(--accent); }
.corp-photo {
  display: block;
  width: 100%;
  max-height: 420px;
  object-fit: cover;
  filter: grayscale(1) contrast(1.08);
}
.corp-h2 { font-size: clamp(28px, 3.4vw, 40px); margin: 0 0 8px; }
.corp-lede { color: var(--ink-72); margin: 0 0 34px; }
/* Six products in a ruled grid: 1px lines inside a 2px frame, the pack's own
   metric-grid construction. */
.tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 2px;
  background: var(--divider);
  border: 2px solid var(--divider);
}
/* Every tile the same height with its last line pinned to the bottom, so
   "Open Asset" and every "Coming soon" sit on one line across the grid — they
   floated up under the shorter blurbs and the grid read ragged. */
.tiles > * {
  background: var(--bg); padding: 32px 28px; min-height: 210px;
  display: flex; flex-direction: column; gap: 14px;
}
.tiles .no { font-weight: 800; font-size: 14px; margin: 0;
             font-variant-numeric: tabular-nums; color: var(--accent); }
.tiles h3 { font-size: 24px; margin: 0; }
.tiles p:not(.no) { font-size: 14px; line-height: 1.5; color: var(--ink-72); margin: 0; }
.tiles > * > :last-child { margin-top: auto; }
.tile-live { text-decoration: none; color: inherit; }
.tile-live:hover { background: var(--accent-100, #eef0fb); }
.tile-go { font-weight: 800; font-size: 15px; color: var(--accent); }
/* Not a status: a tag carries attention, ok or info, and "coming soon" is a
   label. */
.tile-soon { font-size: 13px; color: var(--ink-55); }
/* Five parts heading to seven parts prose, as the design splits it. */
.corp-split {
  padding: 64px 0 80px;
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: 28px clamp(24px, 5vw, 88px); align-items: start;
}
.corp-split h2 { max-width: 16ch; }
.corp-split p { max-width: 58ch; }
/* A heading with its dek opposite, on the same baseline. */
.corp-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; margin-bottom: 24px;
}
.corp-head h2 { margin: 0; }
.corp-dek { font-size: 14px; color: var(--ink-65); margin: 0; }
/* --- the phone drawer ---------------------------------------------------- */

.nav-button, .nav-backdrop, .nav-drawer-head { display: none; }
.nav-drawer > nav > .nav-group > .group-head { display: none; }
/* On a wide screen the two tiers are the navigation and the drawer does not
   exist; on a phone the reverse. Both are always in the markup — the menu has
   to work with no JavaScript either way. */
.nav-drawer { display: none; }

@media (max-width: 860px) {
  /* The landing's two splits become one column. */
  .split-57, .corp-split { grid-template-columns: minmax(0, 1fr); }
  header.top .tier1 { padding: 0 16px; gap: 10px; min-height: 56px; }
  header.top .brand { padding: 10px 0; flex: 1; margin-right: 0; }
  header.top .brand span { display: none; }
  /* The phone: the drawer is the menu; the tiers are the desktop's. */
  header.top .areas, header.top .who-bar, header.top .tier2 { display: none; }

  .nav-button {
    display: inline-flex; align-items: center; gap: 9px; cursor: pointer;
    color: var(--paper); font-size: 14px; font-weight: 800; padding: 9px 14px;
    border: 1px solid rgba(243, 242, 242, .5); background: none;
    -webkit-tap-highlight-color: transparent;
  }
  .nav-bars, .nav-bars::before, .nav-bars::after {
    display: block; width: 15px; height: 2px; background: var(--paper); content: "";
  }
  /* The landing bar stays light, so its button does too. */
  .landing .nav-button { color: var(--ink); border-color: var(--divider); }
  .landing .nav-bars, .landing .nav-bars::before, .landing .nav-bars::after {
    background: var(--ink);
  }
  .nav-bars { position: relative; }
  .nav-bars::before { position: absolute; top: -5px; }
  .nav-bars::after { position: absolute; top: 5px; }

  .nav-backdrop {
    display: block; position: fixed; inset: 0; z-index: 59;
    background: rgba(32, 30, 29, .55); opacity: 0; pointer-events: none;
    transition: opacity .2s ease;
  }
  .nav-toggle:checked ~ .nav-backdrop { opacity: 1; pointer-events: auto; }

  .nav-drawer {
    display: flex; flex-direction: column;
    border-left: 2px solid rgba(243, 242, 242, .25);
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 60;
    width: min(86vw, 330px);
    background: var(--accent); color: var(--paper);
    transform: translateX(101%); transition: transform .22s ease;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    padding: 0 0 env(safe-area-inset-bottom);
  }
  .nav-toggle:checked ~ .nav-drawer { transform: translateX(0); }

  .nav-drawer-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 16px 18px; font-size: 12px; letter-spacing: .08em;
    text-transform: uppercase; color: rgba(243, 242, 242, .6);
    border-bottom: 2px solid rgba(243, 242, 242, .25);
    position: sticky; top: 0; background: var(--accent);
  }
  .nav-close { cursor: pointer; font-size: 26px; line-height: 1; color: var(--paper);
               padding: 0 6px; margin: -4px -6px 0 0; }

  header.top nav { display: block; padding: 0; flex: 1; }

  .nav-group { display: block; }
  .nav-group + .nav-group { border-top: 1px solid rgba(243, 242, 242, .18); }
  .nav-drawer > nav > .nav-group > .group-head {
    display: flex; align-items: center; justify-content: space-between;
    cursor: pointer; margin: 0; padding: 14px 18px;
    font-size: 12px; letter-spacing: .09em; text-transform: uppercase;
    color: rgba(243, 242, 242, .6); -webkit-tap-highlight-color: transparent;
  }
  .nav-drawer > nav > .nav-group > .group-head::after {
    content: ""; width: 7px; height: 7px; margin-left: 10px;
    border-right: 2px solid rgba(243, 242, 242, .5);
    border-bottom: 2px solid rgba(243, 242, 242, .5);
    transform: rotate(45deg) translate(-2px, -2px); transition: transform .15s ease;
  }
  .group-toggle:checked ~ .group-head { color: var(--paper); }
  .group-toggle:checked ~ .group-head::after { transform: rotate(-135deg) translate(-2px, -2px); }
  .group-links { display: none; }
  .group-toggle:checked ~ .group-links { display: block; padding-bottom: 6px; }
  .nav-group:not(:has(a)) { display: none; }

  header.top nav a {
    display: block; padding: 11px 18px; font-size: 15.5px;
    color: rgba(243, 242, 242, .9); border-bottom: none;
    border-left: 3px solid transparent;
  }
  header.top nav a:hover { color: var(--paper); }
  /* A fold in the drawer: a heading that opens its acts, drawn like a link with
     the acts indented under it, so the eye reads nine headings and not thirty
     rows. A checkbox and a label, as the groups are. */
  .drawer-fold > .fold-head {
    display: block; padding: 11px 18px; font-size: 15.5px; cursor: pointer;
    color: rgba(243, 242, 242, .9);
  }
  .drawer-fold > .fold-head::after {
    content: ""; display: inline-block; width: 0; height: 0; margin-left: 8px;
    vertical-align: middle; border-left: 4px solid transparent; border-right: 4px solid transparent;
    border-top: 5px solid currentColor;
  }
  .drawer-fold > .fold-links { display: none; }
  .fold-toggle:checked ~ .fold-head { color: var(--paper); }
  .fold-toggle:checked ~ .fold-head::after { transform: rotate(180deg); }
  .fold-toggle:checked ~ .fold-links { display: block; }
  .drawer-fold .fold-links a { padding-left: 34px; font-size: 15px; }
  header.top nav a.on {
    color: var(--paper); font-weight: 600; background: rgba(243, 242, 242, .12);
    border-left-color: var(--paper); border-bottom-color: transparent;
  }

  header.top .who {
    text-align: left; padding: 16px 18px; font-size: 12px;
    border-top: 2px solid rgba(243, 242, 242, .25); color: rgba(243, 242, 242, .7);
  }
  header.top .who strong { font-size: 15px; color: var(--paper); }
  header.top .who a { color: rgba(243, 242, 242, .7); }
  header.top .who form { display: block; margin-top: 12px; }
  header.top .who button {
    width: 100%; padding: 11px; font-size: 15px; justify-content: center;
    background: var(--paper); color: var(--accent);
  }

  main { padding: 24px 16px 48px; }
  h1 { font-size: 26px; }
  .grid.two, .grid.three { grid-template-columns: 1fr; }
  /* A field that sits three across on a desktop is a short field by
     construction: a kind, a condition, a count, a voltage. One per line on a
     phone turned a capture form into nine screens of scrolling, and the
     transformer's eight part-conditions into eight. Two across is still a
     comfortable target and halves the walk down the form. `.tight` says the
     same of a two-across group whose fields are short. */
  .grid.three, .grid.two.tight {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px 12px;
  }
  /* Except where the field is a sentence rather than a word. */
  .grid.three > label.wide, .grid.two.tight > label.wide { grid-column: 1 / -1; }
  .metric { flex-basis: 44%; padding: 16px; }
  .metric .n { font-size: 30px; }
  /* --- filter bars on a phone ------------------------------------------
     A grid, not a wrapping row. Fields of three different widths wrapping
     where they landed put the day's date, a dropdown and a button on three
     ragged lines with the button stranded in the middle — it read as an
     accident because it was one.

     Two equal columns. Every control is the same width as the one beside it,
     so nothing can land ragged whatever the page puts in the bar; a control
     that genuinely needs the width says so and takes the row; the buttons
     always take the last row. */
  .filters {
    display: grid; grid-template-columns: 1fr 1fr; gap: 12px 8px;
    align-items: end;
  }
  .filters > div { min-width: 0; }
  /* A search box, a scope picker or a tick box is a sentence, not a word.
     Matched on what a text box is rather than on type="search", because the
     search boxes in this application are plain text inputs and the tidy
     rule then left "Tag or street" in half a row. */
  .filters > div:has(input[type="search"]),
  .filters > div:has(input[type="text"]),
  .filters > div:has(input:not([type])),
  .filters > div.wide, .filters .scopepick,
  .filters > div:has(.check) { grid-column: 1 / -1; }
  /* A hidden field is not a control and must not hold a cell open: one was
     taking the first cell of the first row and pushing the bar out of step. */
  .filters > input[type="hidden"], .filters > input[hidden] { display: none; }
  .filters :where(input, select) { width: 100%; min-width: 0; }
  .filters .check { white-space: normal; }
  .filter-actions {
    grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 8px;
    align-items: stretch;
  }
  /* Each button carries its own words and shares the row. A tap target on a
     phone is a whole button, not the middle of one. */
  .filter-actions > .btn, .filter-actions > button, .filter-actions > a {
    flex: 1 1 auto; min-width: 88px; text-align: center;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* The one that does the thing goes first and is given the room. */
  .filter-actions > button[type="submit"] { flex: 2 1 110px; }

  /* A tag that has been separated from the sentence it belongs to reads as a
     bullet with nothing after it. */
  .nowrap { white-space: nowrap; }
  table { display: block; overflow-x: auto; white-space: nowrap; }
  dl.kv { grid-template-columns: 1fr; }
  dl.kv dt { border-bottom: 0; padding-bottom: 0; }
  input, select, textarea { font-size: 16px; }   /* stops iOS zooming */

  .signin-split { grid-template-columns: 1fr; }
  .signin-brand { min-height: auto; gap: 28px; }
  .feature { grid-template-columns: 1fr; }
  .site-footer { flex-direction: column; gap: 10px; }
}

/* 2-up is the floor: a metric one to a row turns a glanceable strip into a
   scroll, and 375px is an ordinary phone rather than a narrow one. */
@media (max-width: 360px) {
  header.top .brand img { height: 20px; }
  .metric { flex-basis: 100%; }
}

/* --- the phone's landing and sign-in ------------------------------------ */

/* On a phone the brand panel is a masthead, not a second screen. Stacked
   above the form, it says who this is in four lines and hands over — the
   full-height spread is a desktop composition. */
@media (max-width: 860px) {
  .signin-split { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  .signin-brand { padding: 24px 20px; gap: 14px; justify-content: flex-start; }
  .signin-brand img { width: 110px; }
  .signin-brand h1 { font-size: 26px; }
  .signin-brand .statement { display: none; }
  .signin-brand .ndpa { font-size: 11.5px; line-height: 17px; }
  .signin-form { padding: 28px 20px 40px; }
}

/* On a phone the landing bar carries exactly two things: the name and a way
   in. The section links are anchors into a page that will be scrolled anyway,
   and the second button lives 100px lower in the hero — repeating either up
   here is what made the bar wrap into a heap. */
@media (max-width: 860px) {
  .landing header.top { flex-wrap: nowrap; gap: 12px; min-height: 0; padding: 12px 20px; }
  .landing header.top .brand { flex: 0 1 auto; padding: 0; }
  .landing header.top .brand span { display: inline; font-size: 19px; }
  .landing header.top .brand img { height: 24px; }
  .landing header.top nav { display: none; }
  .landing header.top .actions { margin-left: auto; flex-wrap: nowrap; }
  .landing header.top .actions .btn { display: none; }
  .landing header.top .actions .btn.ghost {
    display: inline-block;
    padding: 9px 18px;
    font-size: 14px;
    white-space: nowrap;
  }
  /* Two stats a row, sized for a phone. One a row read as a tower of
     numbers with a screen of scrolling to pass them. */
  .landing .stat-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px 18px;
    padding: 36px 0;
  }
  .landing .stat-row .n { font-size: 30px; }
  .landing .stat-row .k { font-size: 10.5px; margin-top: 8px; }
  .landing .hero { padding: 44px 0 40px; }
  /* The hero and the closing poster set their buttons full width, equal
     height, in one column — not two widths ragged against each other. */
  .landing .hero .actions, .landing .poster .actions {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  .landing .hero .actions .btn, .landing .poster .actions .btn {
    text-align: center;
    justify-content: center;
    padding: 13px 18px;
  }
}

/* The corporate landing on a phone: the tile grid narrows, the anchor links
   yield to the buttons. Kept down here so the desktop slice of this file
   stays whole — the first phone block must be the drawer's. */
@media (max-width: 860px) {
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .corp-nav { display: none; }
  /* The corporate bar has one button; the landing rule that trims the pair
     down must not take the only one. */
  .landing header.top .actions .btn.corp-talk { display: inline-block; }
}
@media (max-width: 560px) {
  .tiles { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 860px) {
  .filters .scopepick { flex: 1 1 100%; }
  .scopepick-btn { width: 100%; min-width: 0; }
  .scopepick-panel { min-width: 0; width: 100%; max-width: none; }
}

@media (max-width: 860px) {
  .hero h1 { font-size: 34px; }
  .stat-row { gap: 28px 20px; padding: 40px 0; }
}

/* The capture-settings scope picker. Three depths shown as three: the
   portfolio alone, the projects as a row, the sites under the project they
   belong to. A dropdown mixing them into one alphabetical run with "Project —"
   glued on the front made the reader do the sorting. */
.scopes { padding-bottom: 6px; }
/* The way into a thousand sites. Server-side: what does not match is never
   rendered, so the page stays small on a slow line. */
.scope-search { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; }
.scope-search input { flex: 1; min-width: 0; max-width: 420px; font-size: 13.5px; }
.scope-head {
  margin: 18px 0 8px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-60);
}
.scope-row { display: flex; flex-wrap: wrap; gap: 8px; }
.scope-group { margin-bottom: 12px; }
.scope-group-name {
  display: block;
  margin-bottom: 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-65);
}
.scope-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  padding: 8px 13px;
  border: 1px solid var(--divider);
  background: var(--bg);
  color: var(--ink);
  font-size: 13.5px;
  text-decoration: none;
  border-radius: 3px;
}
.scope-chip:hover { border-color: var(--accent-400); color: var(--accent-700); }
.scope-chip.wide { display: flex; justify-content: center; font-weight: 600; }
/* The one in force reads as chosen at a glance, in the house blue. */
.scope-chip.on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper);
  font-weight: 600;
}
.scope-chip.on .mono, .scope-chip.on .scope-own { color: rgba(243, 242, 242, .8); }
.scope-chip .mono { font-size: 11.5px; color: var(--ink-60); }
/* A scope that sets its own rules says so where it stands, so the exceptions
   are a map rather than a list somewhere below. */
.scope-own {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--accent-700);
}

/* --- the FAQ -------------------------------------------------------------
   A long page that has to be findable rather than read. Questions are closed
   by default so a section reads as a list of what it can answer; the plain
   answer opens first and the exact detail sits under it in a quieter block. */

.faq-search {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin: 20px 0 28px;
}
.faq-search label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ink-65);
}
.faq-search input[type="search"] {
  flex: 1 1 260px;
  min-width: 0;
  font-size: 16px;              /* 16px or the phone zooms on focus */
}

.faq-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 32px;
}
.faq-jump a {
  border: 1px solid var(--ink-14);
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 13px;
  text-decoration: none;
  color: var(--ink-78);
  background: var(--ink-04);
}
.faq-jump a:hover { border-color: var(--accent-400); color: var(--accent-700); }

.faq-item { border-bottom: 1px solid var(--ink-07); }
.faq-item > summary {
  cursor: pointer;
  padding: 12px 4px 12px 0;
  font-size: 14.5px;
  line-height: 22px;
  font-weight: 600;
  color: var(--accent-700);
  list-style-position: outside;
}
.faq-item > summary:hover { color: var(--accent); }
.faq-item[open] > summary { color: var(--ink); }
.faq-term > summary { font-weight: 400; color: var(--ink-78); }
.faq-term > summary strong { color: var(--accent-700); }

.faq-body { padding: 0 4px 16px 20px; max-width: 72ch; }
.faq-plain { font-size: 14.5px; line-height: 24px; margin: 0 0 12px; }
.faq-steps { margin: 0 0 12px; padding-left: 20px; }
.faq-steps li { font-size: 13.5px; line-height: 22px; margin-bottom: 6px; }

/* The precise half. Quieter than the plain answer on purpose: the person who
   needs a permission key is not the person who needed the answer. */
.faq-precise {
  background: var(--ink-04);
  border-left: 2px solid var(--accent-300);
  padding: 10px 14px;
  margin: 0 0 12px;
}
.faq-precise .k {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ink-55);
  margin: 0 0 6px;
}
.faq-precise ul { margin: 0; padding-left: 18px; }
.faq-precise li {
  font-size: 13px;
  line-height: 21px;
  color: var(--ink-72);
  margin-bottom: 5px;
}

.faq-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin: 0;
}
.faq-meta .sub { font-size: 11.5px; }

/* Hidden by the instant filter. Kept as a class rather than inline style so a
   printed page and a no-JavaScript page both show everything. */
.faq-hidden { display: none; }

/* --- paper ---------------------------------------------------------------
 *
 * There was no print rule at all. Pressing Cmd-P on a customer produced the
 * blue header, the nav drawer, both tiers of tabs, every button, every form
 * and the flash overlay, spread over three sheets — and, quietly, less than
 * the record: a printed page lays out at the width of the paper, about 700px
 * inside A4's margins, so `@media (max-width: 860px)` fires on every print
 * run. That block turns every table into `display: block; white-space: nowrap`
 * inside a `.panel` that is `overflow-x: auto`, and an overflow container on
 * paper does not scroll — it clips. Columns past the right edge were simply
 * gone, with nothing on the sheet to say so.
 *
 * So this block must come LAST in the file and re-state what the mobile block
 * set, at the same specificity: media queries add none, and later source order
 * is what wins.
 *
 * Three rules of thumb behind everything below.
 *
 * Anything you could press is chrome. Buttons, pickers, filters, tick boxes,
 * pagers, correction panels: on paper they are ink spent on things that cannot
 * be done. The exception is the form ELEMENT itself, which on five list pages
 * wraps the whole table — hiding `form` would print those pages empty.
 *
 * Nothing may rely on a background. Browsers drop them by default, so white
 * text on the accent blue prints as white on white and disappears: the network
 * lamp, the chosen chip, the current page number, the freeze bar. Each one is
 * given ink of its own here rather than being left to vanish.
 *
 * Paper leaves the building. A portrait, an identity document, a signature and
 * a decrypted document number are all behind `kyc.verify` on screen; a sheet is
 * behind nothing. They are dropped from every print, from the pages of the
 * people who are allowed to see them, on purpose.
 */

/* The photographs as pictures rather than as rows: four across, captioned, and
   the same square frame the meter's pair uses — a contact sheet is what somebody
   prints, files, or carries to a site meeting. */
.contact-sheet {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 16px;
}
.contact-sheet figure { margin: 0; min-width: 0; }
.contact-sheet .frame {
  display: block;
  aspect-ratio: 1 / 1;
  background: var(--surface);
  border: 1px solid var(--ink-14);
  border-radius: 4px;
  overflow: hidden;
  padding: 6px;
  box-shadow: 0 1px 2px var(--ink-07);
}
.contact-sheet .frame img {
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
  border: 0;
  filter: none;
  background: var(--ink-07);
}
.contact-sheet a.frame:hover { border-color: var(--accent-400); }
.contact-sheet a.frame:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.contact-sheet figcaption {
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--ink-65);
}
.contact-sheet figcaption strong { color: var(--ink); font-weight: 600; }
.contact-sheet .sheet-pick {
  display: block;
  font-size: 11.5px;
  margin-bottom: 2px;
}
.sheet-panel { padding-bottom: 20px; }

/* --- the map page ----------------------------------------------------------
   The register, placed, filling the screen: the reading down the left — where
   you are, the regions at that level, the legend, what you clicked — and the
   map taking everything else. `main.bleed` lets it out of the page column. */
main.bleed { max-width: none; padding: 0; }
/* The Poles and Transformers pages: the nearest few, as rows a thumb can hit
   in the sun — the tag large, the distance beside it, the rest small. */
.nearby { display: grid; gap: 6px; margin: 8px 0; }
.nearby-row {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr); align-items: baseline; gap: 4px 12px;
  width: 100%; min-height: 56px; padding: 10px 14px; text-align: left;
  background: var(--paper); border: 1px solid var(--ink-14); border-radius: 6px; cursor: pointer;
  font: inherit; color: var(--ink);
}
.nearby-row.chosen { border-color: var(--accent); box-shadow: inset 0 0 0 2px var(--accent); }
.nearby-tag { font-size: 20px; font-weight: 700; letter-spacing: -.01em; }
.nearby-away { font-size: 14px; color: var(--ink-65); font-variant-numeric: tabular-nums; }
.nearby-words { grid-column: 1 / -1; font-size: 12.5px; color: var(--ink-65); }
.hint.warn { color: #b45309; }
tr.total td { font-weight: 700; border-top: 2px solid var(--ink-14); }

/* The tile on the dashboard that opens the map. */
@media (max-width: 860px) {
}
/* The map the server draws into the page: the states, coloured by the
   register, with the sites on them — or one state's LGAs, or the houses and
   meters on the ground at a site. There is no engine behind it; this is the
   map.

   How big a name comes out is the page's business, not the picture's. The
   drawing is always a thousand units wide whatever it covers, and the browser
   fits that into whatever the panel is, so the same 13px is nine pixels on a
   laptop and five on a phone. The picture writes its sizes as this measure
   multiplied by how much of itself is on screen; the phone says a bigger
   measure below — which only works because the value is declared with the
   rest of the page's measures, above everything that overrides it. */
/* --- the network map ------------------------------------------------------
   Three panels: what is drawn, the ground it is drawn on, and what one thing
   turns out to be. After EWL-CONNECT-GIS-2026-01, in the design's own
   language — square corners, ruled edges, flush left. */
.netmap-tier2 {
  display: flex; gap: 16px; align-items: center; flex-wrap: wrap;
  padding: 0 clamp(12px, 2vw, 20px); border-bottom: 2px solid var(--ink-14);
  background: var(--bg);
}
.netmap-tab {
  font-size: 13.5px; padding: 11px 0 9px; white-space: nowrap;
  color: var(--ink-55); text-decoration: none;
}
.netmap-tab.on {
  color: var(--accent-700); font-weight: 600;
  border-bottom: 2px solid var(--accent); margin-bottom: -2px;
}
.netmap-find { margin-left: auto; display: flex; gap: 14px; align-items: center; padding: 6px 0; }
.netmap-find select { min-height: 32px; font-size: 13px; }
/* Each control says what it chooses. An unlabelled dropdown reading "Whole
   portfolio" is a statement, not a question, and nobody looks in it for a
   project. */
.netmap-pick { display: flex; align-items: center; gap: 6px; }
.netmap-pick > span {
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-65);
}
@media (max-width: 860px) {
  .netmap-find { margin-left: 0; flex-wrap: wrap; gap: 8px 12px; }
}

.netmap {
  display: grid; grid-template-columns: 260px minmax(0, 1fr) 340px;
  height: calc(100vh - 158px); min-height: 460px;
}
.netmap-rail, .netmap-inspector {
  padding: 16px 18px; background: var(--bg); overflow-y: auto;
}
.netmap-rail { border-right: 2px solid var(--ink-14); }
.netmap-inspector { border-left: 2px solid var(--ink-14); }
.netmap-head {
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-55); margin: 0 0 8px;
}
.netmap-note { font-size: 11.5px; line-height: 18px; color: var(--ink-65); margin: 8px 0 0; }
.netmap-gaps { font-size: 10.5px; line-height: 15px; margin: 0 0 6px 26px; }
/* A segmented control: one row of buttons sharing a frame, the chosen one filled. */
.netmap-seg {
  display: grid; gap: 2px; background: var(--ink-14); border: 2px solid var(--ink-14);
}
.netmap-seg button {
  background: var(--bg); color: var(--ink); border: 0; cursor: pointer;
  font: inherit; font-size: 13px; padding: 8px 12px; text-align: left;
  display: flex; justify-content: space-between; gap: 8px; border-radius: 0;
}
.netmap-seg button.on { background: var(--accent); color: var(--paper); }
.netmap-z { opacity: .65; font-size: 11.5px; font-variant-numeric: tabular-nums; }
/* The layer switches themselves are never seen — but they are still there to
   be reached by keyboard, which display:none would prevent. */
.lyr { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.netmap-toggle {
  display: flex; align-items: center; gap: 9px; font-size: 13.5px;
  cursor: pointer; margin: 0 0 9px; color: var(--ink);
}
.netmap-box {
  width: 14px; height: 14px; border: 2px solid var(--accent); flex: none;
  display: flex; align-items: center; justify-content: center;
}
/* A ticked switch fills its box and a tick appears in it.

   This was written out one switch at a time, under a comment saying a
   stylesheet cannot ask for "the label that points at me" — true, but only
   because the input sits outside the label. The cost was that every switch
   added afterwards ticked nothing anybody could see, and the list was three
   lists that had to agree. The map cannot draw without its script, so the
   script marks the label instead and this is one rule for every switch
   there is or ever will be. */
.netmap-toggle.is-on .netmap-box,
.netmap-keyrow.is-on .netmap-box {
  background: var(--accent);
}
.netmap-toggle.is-on .netmap-box::after,
.netmap-keyrow.is-on .netmap-box::after {
  content: "\2713"; color: var(--paper); font-size: 10px; line-height: 1;
}
/* Unticked, the row says so too, so a switched-off layer reads as a decision
   rather than as an empty map. */
.netmap-toggle:not(.is-on), .netmap-keyrow:not(.is-on) {
  opacity: .45;
}
.netmap-toggle:not(.is-on) .netmap-count,
.netmap-keyrow:not(.is-on) .netmap-count { text-decoration: line-through; }
/* Unticked, the key beside it says so too. */
#l-tx:not(:checked) ~ .netmap-rail [data-tx-key] { opacity: .4; }
#l-poles:not(:checked) ~ .netmap-rail [data-pole-key] { opacity: .4; }
#l-conn:not(:checked) ~ .netmap-rail [data-status-key] { opacity: .4; }
/* The switch is off-screen so it stays reachable by keyboard; the label is
   what a keyboard user sees, so the focus lands there visibly. */
.netmap-toggle.is-focus, .netmap-keyrow.is-focus {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.netmap-derived { font-size: 11px; color: var(--ink-55); }
/* The sum of the five above it: they are stages of one thing, and the total
   is what stops the first stage being read as the whole. */
.netmap-total { border-top: 1px solid var(--ink-14); margin-top: 4px; padding-top: 6px; font-weight: 600; }
.netmap-total .netmap-dot { visibility: hidden; }
.netmap-key { display: grid; gap: 6px; padding-left: 23px; font-size: 12.5px; }
/* The key is the filter: tick a status off and it leaves the map. */
.netmap-keyrow { display: flex; align-items: center; gap: 8px; cursor: pointer; }
label.netmap-keyrow { user-select: none; }
.netmap-dot { width: 10px; height: 10px; flex: none; }
.netmap-count { margin-left: auto; color: var(--ink-55); font-variant-numeric: tabular-nums; }
/* Equipment by state: green stands, red is down, amber is on the way, and
   a hollow mark is one nobody has looked at yet. Transformers are squares,
   poles are dots, the same way the map draws them. */
.eq-good { background: #1D9E75; }
.eq-attention, .eq-other { background: #EF9F27; }
.eq-bad { background: #E24B4A; }
.eq-none { background: var(--bg); border: 2px solid #888780; }
.netmap-dot { border-radius: 50%; }
.eq-square { border-radius: 0; }
/* The site list is a tray under the bar. It takes a row of its own and the
   map moves down to make room, so it covers nothing; it opens from the
   Sites button and closes on a click anywhere else, or Escape. */
.netmap-sites > summary { cursor: pointer; list-style: none; }
.netmap-sites > summary::-webkit-details-marker { display: none; }
.netmap-caret {
  display: inline-block; width: 0; height: 0; margin-left: 2px; vertical-align: middle;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 4px solid currentColor;
}
.netmap-sites[open] .netmap-caret { transform: rotate(180deg); }
.netmap-sitelist { display: none; }
.netmap-tier2:has(.netmap-sites[open]) > .netmap-sitelist {
  /* A full row of the bar, out to both edges: pulled left by the bar's
     padding and made wider by twice it, or it stops short on the right. */
  display: block; order: 100; box-sizing: border-box;
  flex: 0 0 calc(100% + 2 * clamp(12px, 2vw, 20px));
  margin: 0 calc(-1 * clamp(12px, 2vw, 20px));
  padding: 8px clamp(12px, 2vw, 20px) 10px;
  border-top: 1px solid var(--ink-14); background: var(--accent-100);
}
.netmap-sitegrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 2px 24px;
}
.netmap-siterow { display: flex; align-items: center; gap: 8px; font-size: 13px; cursor: pointer; white-space: nowrap; padding: 3px 0; }
.netmap-siterow input { margin: 0; accent-color: var(--accent); }
.netmap-sitename { overflow: hidden; text-overflow: ellipsis; }
.netmap-siterow .netmap-count { margin-left: auto; padding-left: 12px; }
/* The printed sheet's title block and legend strip exist only on paper. */
.paper-head, .paper-legend, .paper-north, .paper-map, .paper-selection { display: none; }
/* The line through the poles is red, so it reads as the low-voltage run
   rather than another navy overlay; dashed, because it is derived from the
   tags rather than a surveyed conductor route. */
/* The last leg, finer than the run it hangs off: same red, thinner, a
   closer dash. Derived like the spans — the shortest line between two
   surveyed points, not a walked cable. */
/* The window. Two dates and a way out of them, stacked because the rail is
   narrow and a date input will not shrink below its own text. */
/* Two dates across, and the buttons under them. Stacked one to a row this
   was four rows of a rail that has to hold twenty more controls below it. */
.netmap-window {
  /* Two across where two fit, and one where they do not, rather than a fixed
     pair that clips. In the desktop rail the two inputs come out at 107px
     against 105px of content — it fits, with two pixels to spare, which is
     not a margin to trust across browsers and fonts. */
  display: grid; gap: 6px 8px; margin: 0 0 10px;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
.netmap-window label { display: grid; gap: 2px; font-size: 11px; color: var(--ink-55); }
.netmap-window input { width: 100%; min-width: 0; font-size: 12px; padding: 4px 6px; }
.netmap-window-do { grid-column: 1 / -1; display: flex; gap: 6px; }
.netmap-window button { font-size: 11px; padding: 4px 10px; }
/* The window is set but not shown yet. Without this somebody picks two dates,
   does not press the button, and reads a map of the wrong period as though it
   were the answer. */
.netmap-window-do [data-window-apply].is-pending {
  background: var(--accent); color: var(--paper); border-color: var(--accent);
}

.netmap-doubt-key {
  display: inline-block; width: 16px; height: 0; vertical-align: middle;
  border-top: 2px dashed #000; margin: 0 4px 0 0;
}
.netmap-keynote { font-size: 11px; color: var(--ink-55); margin: 4px 0 0 22px; line-height: 15px; }
.netmap-doubt-tip { font-size: 11px; }
}
.netmap-drop-key {
  display: inline-block; width: 16px; height: 0; vertical-align: middle;
  border-top: 1px dashed #C62828; margin: 0 2px; opacity: .8;
}
/* The fault switches sit under the layer they belong to, so the panel reads
   as one control with its parts rather than eleven unrelated ticks. */
.netmap-sub { margin: 2px 0 2px 18px; }
.lvkey {
  display: inline-block; width: 14px; height: 3px; border-radius: 2px;
  margin-right: 6px; vertical-align: middle;
}
.lvkey-sound { background: #1b7f4f; }
.lvkey-bad { background: #d03b3b; }
.lvkey-sag { background: #ec835a; }
.lvkey-tree { background: #b26a00; }
.lvkey-low { background: #eda100; }
.lvkey-nophoto { background: #8a8781; }

.netmap-span-key {
  display: inline-block; width: 16px; height: 0; vertical-align: middle;
  border-top: 2px dashed #C62828; margin: 0 2px;
}
.netmap-apply { margin-left: auto; }
.netmap-apply[disabled] { opacity: .7; cursor: progress; }
.netmap-siteshort { font-size: 12px; margin: 0 0 6px; color: var(--ink-55); display: flex; flex-wrap: wrap; gap: 2px 6px; }
.st-commissioned { background: #0F7A5A; }
.st-installed { background: #03088C; }
.st-assigned { background: #C6740A; }
.st-registered { background: var(--bg); border: 2px solid #6D5BD0; }
.st-suspended { background: #A02334; }
span.tag.st-commissioned, span.tag.st-installed, span.tag.st-assigned,
span.tag.st-suspended { color: #f3f2f2; }
span.tag.st-registered { color: var(--ink); }

.netmap-canvas { position: relative; background: #e9e7e4; overflow: hidden; }
#netmap { position: absolute; inset: 0; z-index: 1; }
.netmap-noscript {
  position: absolute; inset: 24px; z-index: 2; font-size: 14px; line-height: 1.5;
}
.netmap-title, .netmap-crs, .netmap-legend {
  background: var(--bg); border: 2px solid var(--ink-14);
}
.netmap-title {
  position: absolute; z-index: 5; left: 14px; top: 14px; padding: 7px 13px;
  font-size: 12.5px; display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap;
  max-width: calc(100% - 100px);
}
.netmap-title span { color: var(--ink-65); font-variant-numeric: tabular-nums; }
.netmap-tools {
  position: absolute; z-index: 5; right: 14px; top: 14px;
  display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
}
.netmap-btns { display: flex; flex-direction: column; border: 2px solid var(--ink-14); background: var(--bg); }
.netmap-btns button {
  min-height: 36px; width: 36px; border: 0; background: none; cursor: pointer;
  /* Ink, explicitly: the platform's buttons are accent-filled with paper
     text, and these are not — they were four white glyphs on white. */
  color: var(--ink); font: inherit; font-size: 15px; border-radius: 0;
}
.netmap-btns button + button { border-top: 1px solid var(--ink-14); }
.netmap-btns button.on { background: var(--accent); color: var(--paper); }
.netmap-foot {
  position: absolute; z-index: 5; left: 14px; right: 14px; bottom: 14px;
  display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap;
  pointer-events: none;
}
.netmap-foot > * { min-width: 0; max-width: 100%; }
/* On a wide screen there is nothing to fold: the key is simply open and its
   tab is not drawn. */
.netmap-keytab { display: none; }
.netmap-foot[open] .netmap-keytab { display: none; }
.netmap-legend {
  padding: 8px 13px; display: flex; gap: 14px; align-items: center;
  font-size: 11.5px; flex-wrap: wrap;
}
.netmap-legend span { display: flex; align-items: center; gap: 6px; }
.netmap-line { width: 30px; height: 3px; background: #02055a; display: block; }
.netmap-line.dash { height: 0; border-top: 2px dashed #02055a; }
/* A diagnostic mark, in ink — never one of the five status colours. */
.netmap-ring {
  width: 13px; height: 13px; border: 2px dashed var(--ink); border-radius: 50%;
  display: block; flex: none;
}
.netmap-bar { font-variant-numeric: tabular-nums; }
.netmap-bar i { height: 6px; border: 2px solid var(--ink); border-top: 0; display: block; min-width: 20px; }
.netmap-crs {
  margin-left: auto; padding: 8px 13px; font-size: 11.5px; color: var(--ink-65);
  font-variant-numeric: tabular-nums;
}
/* Leaflet's own chrome, in this platform's language rather than its own. */
.leaflet-container { background: #e9e7e4; font: inherit; }
.leaflet-tooltip {
  border-radius: 0; border: 2px solid var(--ink); background: var(--bg);
  box-shadow: none; font-size: 12px; color: var(--ink);
}
.leaflet-tooltip::before { display: none; }
.netmap-label { font-weight: 700; font-size: 12px; }

/* A project's own colour, so the eye groups the map before it reads it.
   Eight hues in a fixed order, assigned by project and never generated: a
   ninth project takes the neutral rather than a colour invented for it. Each
   passes 4.5:1 on the label's white ground, and none of them carries meaning
   alone — the project's name is written right there beside it. */
.netmap-label-project {
  border-left: 4px solid var(--project-hue, var(--ink-55));
  color: var(--project-hue, inherit);
  padding-left: 8px;
}
.netmap-label-project.p0 { --project-hue: #1D4ED8; }
.netmap-label-project.p1 { --project-hue: #B45309; }
.netmap-label-project.p2 { --project-hue: #047857; }
.netmap-label-project.p3 { --project-hue: #B91C1C; }
.netmap-label-project.p4 { --project-hue: #6D28D9; }
.netmap-label-project.p5 { --project-hue: #0E7490; }
.netmap-label-project.p6 { --project-hue: #A21CAF; }
.netmap-label-project.p7 { --project-hue: #4D7C0F; }

.netmap-empty, .netmap-card { display: block; }
.netmap-who { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.netmap-who strong { font-size: 19px; letter-spacing: -.01em; }
.netmap-tags { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
.netmap-kv {
  display: grid; grid-template-columns: 104px minmax(0, 1fr); gap: 8px 12px;
  margin: 16px 0 0; font-size: 13px;
}
.netmap-kv dt { color: var(--ink-55); font-size: 12px; }
.netmap-kv dd { margin: 0; }
.netmap-obs {
  display: flex; gap: 10px; padding: 7px 0; font-size: 12.5px;
  border-bottom: 1px solid var(--ink-14);
}
.netmap-obs span:first-child { color: var(--ink-55); flex: none; font-variant-numeric: tabular-nums; }
/* What hangs off a pole. A long run has fifteen of them, so the list scrolls
   inside the card rather than pushing the links off the bottom of it. */
.netmap-services { margin: 18px 0 0; border-top: 2px solid var(--ink-14); padding-top: 12px; }
.netmap-service-list { list-style: none; margin: 0; padding: 0; max-height: 320px; overflow-y: auto; }
.netmap-service-list li + li { border-top: 1px solid var(--ink-14); }
.netmap-service {
  display: block; padding: 8px 4px; color: inherit; text-decoration: none;
}
.netmap-service .netmap-service-who { text-decoration: underline; }
.netmap-service:hover, .netmap-service:focus-visible { background: var(--ink-07, #f3f2f2); }
.netmap-service-who { display: block; font-size: 13px; }
.netmap-service-what { display: block; font-size: 11.5px; color: var(--ink-55); margin-top: 2px; }

/* What hangs below a feeder: a short list of names, each its own record. Not
   a table — three transformers in a panel this narrow is a list. */
.netmap-list {
  margin: 6px 0 0; padding-left: 18px;
  font-size: 13px; line-height: 20px; color: var(--ink-72);
}
.netmap-list a { color: inherit; }
.netmap-links { font-size: 13px; margin: 14px 0 0; }
/* Three doors on one line. A link may not break across lines — a wrap inside
   "Open in Google Maps" reads as two links that are not there. */
.netmap-links a { white-space: nowrap; }

/* Correcting a position by hand is the exception to how every position in this
   register is made, so it is folded away rather than sitting open. */
.netmap-correct { margin: 16px 0 0; border-top: 2px solid var(--ink-14); padding-top: 12px; }
.netmap-correct > summary {
  cursor: pointer; font-size: 13px; font-weight: 600; color: var(--accent-700);
  list-style: none;
}
.netmap-correct > summary::-webkit-details-marker { display: none; }
.netmap-correct > summary::before { content: "+ "; }
.netmap-correct[open] > summary::before { content: "\2212 "; }
.netmap-correct form { display: grid; gap: 10px; margin-top: 12px; }
.netmap-correct label { display: grid; gap: 4px; font-size: 12px; color: var(--ink-65); }
.netmap-correct input, .netmap-correct textarea { font: inherit; font-size: 13px; }
.netmap-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.netmap-photobtn {
  display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink);
  flex-wrap: wrap;
}
.netmap-photobtn .mono { font-size: 11.5px; color: var(--ink-55); }

@media (max-width: 720px) {
  /* Nothing on the canvas may push the page sideways: the attribution and the
     site chooser both ran off the right edge of a phone. */
  .netmap-find { margin-left: 0; width: 100%; }
  .netmap-find select { flex: 1 1 auto; min-width: 0; }
  .netmap-crs { margin-left: 0; }
  .netmap-legend, .netmap-crs { font-size: 11px; padding: 6px 10px; min-width: 0; }
  .netmap-title { max-width: calc(100% - 24px); font-size: 12px; }
  .netmap-seg button { font-size: 12px; padding: 8px 10px; }
}

@media (max-width: 1100px) {
  /* The map leads on a narrow screen and the rail lies down flat, because a
     column of switches beside a map three inches wide is neither. */
  .netmap { grid-template-columns: minmax(0, 1fr); height: auto; }
  .netmap-canvas { order: -1; height: 60vh; }
  .netmap-rail { order: 1; border-right: 0; border-bottom: 2px solid var(--ink-14); }
  .netmap-inspector { order: 2; border-left: 0; }
  .netmap-seg { grid-auto-flow: column; grid-auto-columns: 1fr; }
}

/* --- the map on a phone: the map, and a sheet ----------------------------
   The rail was a flat panel under a map that got a third of the screen, and
   most of its switches are on by default and never touched. So the map takes
   the screen and the rail becomes a sheet at the foot of it: a grip showing
   what is filtered, dragged or tapped up when somebody wants the switches.

   Driven by a checkbox, like the layer switches next to it, so the sheet
   opens and closes with no JavaScript running at all. */
/* .netmap is a three-column grid, so anything in its flow takes a column.
   The tab had no rule of its own outside the phone block and became a fourth
   grid item on a wide screen: the rail took the whole width, the map was
   squeezed into the third column and the inspector dropped to a second row.
   Both of these belong to the phone and are out of the way everywhere else. */
.railtoggle { position: absolute; opacity: 0; pointer-events: none; }
.cardtoggle { position: absolute; opacity: 0; pointer-events: none; }
.railtab, .railgrip, .cardgrip { display: none; }

@media (max-width: 860px) {
  .netmap { position: relative; }
  /* The map fills what is left of the screen under the two header tiers.
     dvh rather than vh: on a phone vh is the viewport with the browser's own
     bars pretending not to be there, and the bottom of the map ends up under
     the address bar. */
  .netmap-canvas {
    order: -1; height: calc(100dvh - var(--netmap-chrome, 140px));
    min-height: 320px;
  }
  /* The rail is a drawer off the right edge, and the tab that opens it is
     on that edge too. At the foot of the screen it was a bar across the
     bottom of the one thing the page is for; on the side it costs a thumb's
     width and the map keeps the rest. */
  .netmap-rail {
    position: fixed; right: 0; top: 0; bottom: 0; z-index: 40;
    /* Narrow enough to keep a working view of the map beside it: at 86% the
       drawer was the screen and the thing being filtered was a stripe. */
    width: min(68vw, 290px);
    order: unset; border: 0; border-left: 3px solid var(--accent);
    padding: 0 16px 24px; overflow-y: auto;
    box-shadow: -6px 0 20px rgba(0, 0, 0, .22);
    transform: translateX(100%);
    transition: transform .22s ease;
  }
  #rail-open:checked ~ .netmap-rail { transform: translateX(0); }
  @media (prefers-reduced-motion: reduce) {
    .netmap-rail { transition: none; }
  }

  /* The tab. House blue, because it is the control that reaches every filter
     on the page and it could not be found when it was drawn in the page's
     own grey. */
  /* Low on the edge, not halfway up it: at 42% it sat over the middle of the
     map, which is where the points are and where a thumb pans. Down here it
     is clear of the tools at the top and the key at the foot, and it is a
     bigger target besides — this is the way to every filter on the page and
     it was being missed. */
  .railtab {
    display: flex; align-items: center; justify-content: center; gap: 10px;
    position: fixed; right: 0; top: auto; bottom: 92px; z-index: 41;
    writing-mode: vertical-rl;
    background: var(--accent); color: var(--paper);
    padding: 22px 11px; min-height: 118px; border-radius: 10px 0 0 10px;
    font-size: 14px; font-weight: 650; letter-spacing: .05em;
    cursor: pointer; box-shadow: -3px 0 12px rgba(0, 0, 0, .28);
  }
  .railtab-dot { width: 10px; height: 10px; }
  /* Open, the drawer is over it and a second way in is clutter. */
  #rail-open:checked ~ .railtab { display: none; }
  /* A filter is on. The tab says so without being opened. */
  .railtab-dot {
    width: 8px; height: 8px; border-radius: 50%; background: #7fdcaa;
    flex: none;
  }

  /* The drawer's own head: what is filtered, and the way out. */
  .railgrip {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; position: sticky; top: 0; z-index: 1;
    margin: 0 -16px 6px; padding: 12px 16px;
    background: var(--accent); color: var(--paper); cursor: pointer;
  }
  .railgrip-say {
    font-size: 13px; font-weight: 650; color: var(--paper); min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .railgrip-cue { font-size: 20px; line-height: 1; color: var(--paper); }

  /* --- the overlays -----------------------------------------------------
     The title card and the key were two stacked cards taking the top and the
     bottom fifth of the map between them. On a screen where the map is the
     whole point, that is most of a phone spent on furniture. */
  .netmap-title {
    left: 8px; top: 8px; padding: 4px 9px; font-size: 11px;
    gap: 2px 8px; max-width: calc(100% - 62px); border-width: 1px;
  }
  .netmap-title span { font-size: 10.5px; }
  .netmap-tools { right: 8px; top: 8px; gap: 6px; }
  .netmap-btns button { min-height: 32px; width: 32px; }

  .netmap-foot { left: 8px; right: 8px; bottom: 8px; gap: 6px; }
  /* Shut, the key is a tab the size of a word. */
  .netmap-keytab {
    display: inline-block; pointer-events: auto; cursor: pointer;
    background: var(--bg); border: 1px solid var(--ink-14);
    padding: 4px 10px; font-size: 11px; list-style: none; width: fit-content;
  }
  .netmap-keytab::-webkit-details-marker { display: none; }
  .netmap-foot:not([open]) .netmap-legend { display: none; }
  .netmap-legend { padding: 6px 10px; font-size: 10.5px; gap: 4px 10px; }
  .netmap-crs { font-size: 9px; padding: 3px 8px; }

  /* The card a tapped point opens sits above the sheet, not below the fold. */
  .netmap-inspector {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
    border: 0; border-top: 2px solid var(--ink-14);
    border-radius: 14px 14px 0 0; max-height: 72dvh; overflow-y: auto;
    box-shadow: 0 -4px 18px rgba(0, 0, 0, .16);
  }
  /* Nothing to show is nothing to cover the map with. */
  .netmap-inspector:has(.netmap-empty) { display: none; }

  /* Dropped, the sheet leaves its handle standing at the foot of the screen:
     the map is whole again and the answer is one tap away. */
  /* The handle is the sheet's top edge, so the sheet keeps no padding above
     it — 16px of it would otherwise stay on screen under a dropped sheet and
     take a bite out of the handle. */
  .netmap-inspector { transition: transform .18s ease; padding-top: 0; }
  /* 46px of handle plus the sheet's own 2px top border. */
  #card-down:checked ~ .netmap-inspector { transform: translateY(calc(100% - 48px)); }
  @media (prefers-reduced-motion: reduce) {
    .netmap-inspector { transition: none; }
  }
  .cardgrip {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; position: sticky; top: 0; z-index: 1; cursor: pointer;
    margin: 0 -18px 8px; padding: 13px 18px; min-height: 46px;
    background: var(--accent); color: var(--paper);
    border-radius: 14px 14px 0 0;
  }
  .cardgrip-say { font-size: 13px; font-weight: 650; }
  .cardgrip-cue { font-size: 13px; font-weight: 650; opacity: .92; }
  .cardgrip-show { display: none; }
  #card-down:checked ~ .netmap-inspector .cardgrip-hide { display: none; }
  #card-down:checked ~ .netmap-inspector .cardgrip-show { display: inline; }
}


/* A pick typed at: the box above the dropdown, and the line under it. */
.housefind { margin-bottom: 6px; }
.housefind-note { display: block; margin-top: 4px; }

/* The asset base: a quantity, a bar of how much of it is on the record, and
   the gaps in words. The bar's colour is the share, not the class. */
.assetbase td { vertical-align: middle; }
.assetbase .ab-bar { min-width: 160px; white-space: nowrap; }
.ab-track { display: inline-block; vertical-align: middle; width: 110px; height: 8px;
  border-radius: 4px; background: var(--divider); overflow: hidden; }
.ab-fill { display: block; height: 100%; border-radius: 4px; background: var(--accent); }
.ab-fill.ok { background: #1baf7a; }
.ab-fill.warn { background: #eda100; }
.ab-fill.bad { background: #d03b3b; }
.ab-pct { display: inline-block; margin-left: 8px; min-width: 3.2em; font-weight: 650; }
.ab-gaps { color: var(--ink-65); font-size: 13px; }
.ab-gaps span { white-space: nowrap; }

/* Screen: absent. It exists for paper alone, and the rule that shows it lives
   in the print block below. */
.print-head { display: none; }
.print-head-ref { display: none; }
/* The line under a history trimmed for paper: screen has the whole list. */
.print-more { display: none; }

@page {
  margin: 14mm;
}

/* --- the offline page ------------------------------------------------------
   Read on a phone, outdoors, by someone who has just been told the app is
   dead. So: the mark small enough to be furniture rather than the subject,
   the sentence that explains it above the fold, and the doors that DO work
   as separated, thumb-sized targets rather than a row of links. */
body.offline-page { display: flex; justify-content: center; }
.offline-card {
  width: 100%; max-width: 34rem; padding: 32px 20px 48px;
  display: flex; flex-direction: column; align-items: flex-start;
}
.offline-mark { width: 88px; height: auto; margin-bottom: 28px; }
.offline-card .kicker { margin: 0 0 6px; }
.offline-card h1 { margin: 0 0 10px; }
.offline-card .lede { margin: 0 0 24px; }
/* The way out, above the doors: an installed app has no address bar and no
   Reload, so this is the only one there is. */
.offline-do {
  display: flex; gap: 10px; width: 100%; margin: 0 0 24px; flex-wrap: wrap;
}
.offline-do .btn {
  flex: 1 1 auto; padding: 16px 18px; font-size: 16px; text-align: center;
  border-radius: 2px; min-width: 140px;
}
.offline-doors {
  display: grid; gap: 10px; width: 100%; margin: 0 0 24px;
  grid-template-columns: 1fr;
}
.offline-doors .btn {
  /* A whole row each, and tall: a thumb in the sun, not a mouse. */
  display: block; width: 100%; padding: 16px 18px; text-align: left;
  font-size: 16px; border-radius: 2px;
}
.offline-none { margin: 0 0 24px; color: var(--ink-65); }
.offline-card .footnote { margin: 0 0 10px; }
@media (min-width: 620px) {
  .offline-doors { grid-template-columns: 1fr 1fr; }
}

/* Six counts off a checklist. On a phone the ordinary grid gives each one a
   whole row, which is six screens of scrolling for what is six numbers — so
   these stay two abreast at every width. A short number field does not need
   the width of the screen. */
.countgrid { grid-template-columns: 1fr 1fr; }
@media (min-width: 861px) { .countgrid { grid-template-columns: repeat(3, 1fr); } }

/* --- the three checks on the wiring form -----------------------------------
   Graded standing in a half-built house, with a thumb. Three buttons the
   width of the screen beats a dropdown that has to be opened, scrolled and
   read: the answer is one of three every time, and which one is chosen is
   legible from arm's length. */
.gradeset { border: 0; padding: 0; margin: 0 0 18px; }
.gradeset:last-child { margin-bottom: 0; }
.gradeset legend {
  padding: 0; margin-bottom: 8px; font-size: 13px; color: var(--ink-65);
}
.grades { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.grade {
  display: flex; align-items: center; justify-content: center;
  min-height: 52px; padding: 8px 6px; cursor: pointer; text-align: center;
  font-weight: 600; border: 1px solid var(--ink-14); background: var(--surface);
}
.grade input { position: absolute; opacity: 0; width: 0; height: 0; }
.grade:has(input:checked) { background: var(--accent); color: #fff; border-color: var(--accent); }
.grade:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Nothing chosen is not a grade. An empty box on the paper means nobody
   filled it in, and the form says so rather than defaulting to good. */

/* Folded away until asked for: on the ordinary reading of a record the
   photograph is the point and the means of changing it is not. */
.photo-edit { margin-top: 10px; }
.photo-edit > summary {
  cursor: pointer; font-size: 12.5px; color: var(--accent);
  padding: 4px 0; list-style: none;
}
.photo-edit > summary::-webkit-details-marker { display: none; }
.photo-edit > summary::before { content: "+ "; }
.photo-edit[open] > summary::before { content: "\2212 "; }
.photo-edit form {
  border: 1px solid var(--ink-14); padding: 12px; margin-top: 6px;
  display: grid; gap: 10px;
}
.photo-edit .actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* --- a folded second tier --------------------------------------------------
   Fifteen tabs wrapped onto a second line, which is a menu that has stopped
   being one. The acts stay flat; what is looked at rather than done sits under
   a heading.

   Below 860px the whole bar is hidden for the drawer, so there is nothing to
   say about narrow screens here — an earlier attempt at it was dead code. */
/* A fold opens a tray under the whole bar, not a panel under its own label.
   The panel was ten pixels off its label, its top edge two pixels under the
   bar's rule so the two lines never met, and its border ran across the
   divider. A tray has no edges to line up: it is the full width of the bar,
   it sits on the bar's own rule, and its pages are small tabs on a tint. */
.tier2-fold { display: inline-block; }
.tier2-fold > summary {
  cursor: pointer; list-style: none;
  /* Matching the plain tabs beside it, or the folded ones sit off the line. */
  padding: 12px 0 10px; display: inline-block;
  color: var(--ink-72); font-size: 13.5px;
  border-bottom: 2px solid transparent; margin-bottom: -2px;
}
.tier2-fold > summary::-webkit-details-marker { display: none; }
.tier2-fold > summary:hover { color: var(--accent); }
.tier2-fold[open] > summary,
.tier2-fold.on > summary {
  color: var(--accent-700); font-weight: 600; border-bottom-color: var(--accent);
}
.tier2-fold > summary::after {
  content: "";
  display: inline-block; width: 0; height: 0; margin-left: 6px;
  vertical-align: middle;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 4px solid currentColor;
}
.tier2-fold[open] > summary::after { transform: rotate(180deg); }
/* Hidden until its fold opens. The tray is the fold's next sibling — a
   details element keeps its content in a box of its own, which cannot
   become a row of the bar — so it is a flex item that wraps to a full row
   and the page moves down to make room rather than being covered. */
.tier2-under { display: none; }
.tier2-fold[open] + .tier2-under {
  /* Out to the bar's edges, past its own padding: pulled left by the bar's
     padding and made wider by twice it, or it stops short on the right. */
  --bleed: max(var(--gutter), calc((100% - var(--page)) / 2 + var(--gutter)));
  flex: 0 0 calc(100% + 2 * var(--bleed)); order: 100;
  box-sizing: border-box;
  background: var(--accent-100);
  border-top: 2px solid var(--divider);
  margin: 0 calc(-1 * var(--bleed));
  padding: 8px var(--bleed);
  display: flex; flex-wrap: wrap; gap: 6px;
}
header.top .tier2 .tier2-under a {
  padding: 6px 12px; border-bottom: 0; margin: 0; border-radius: 6px;
  white-space: nowrap; line-height: 1.3; font-size: 13px;
  color: var(--accent-700);
}
header.top .tier2 .tier2-under a:hover { background: var(--paper); color: var(--accent); }
header.top .tier2 .tier2-under a.on {
  background: var(--paper); font-weight: 600; color: var(--accent-700);
  box-shadow: inset 0 0 0 1px var(--accent-300);
}

/* Stars on the People page: the earned ones in amber, the rest faint. Text
   beside them says the score, so colour never carries the number alone. */
/* What the work was made of. Drawn as SVG rather than by a charting library:
   a stacked bar is rectangles, and the colours then come from here and need
   no second palette in JavaScript for dark mode. */
.chart-period { font-size: 12.5px; margin: 8px 0 4px; }
.chart-period strong { color: var(--accent); }
.chart-controls { display: flex; flex-wrap: wrap; gap: 14px; margin: 12px 0 10px; }
.chart-controls label { font-size: 12px; color: var(--ink-65); }
.chart-controls select { margin-left: 6px; }
.chart-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.chip {
  font-size: 12px; padding: 4px 10px; cursor: pointer;
  border: 2px solid var(--ink-14); background: none; color: var(--ink-65);
  display: inline-flex; align-items: center; gap: 6px;
}
.chip.on { color: var(--ink); border-color: var(--ink-55); }
.chip:not(.on) { text-decoration: line-through; opacity: .6; }
.chip-dot { width: 10px; height: 10px; display: inline-block; }
.chip:not(.on) .chip-dot { opacity: .35; }
.chart-frame { width: 100%; overflow: hidden; }
.pc-grid { stroke: var(--ink-14); stroke-width: 1; }
.pc-tick, .pc-name, .pc-total { font-size: 11px; fill: var(--ink-55); }
.pc-name { font-size: 12px; fill: var(--ink); }
.pc-total { font-variant-numeric: tabular-nums; }
.pc-seg { shape-rendering: crispEdges; }
/* The second view: where the team's own middle is, and who sits which side
   of it. Shape as well as colour on the marks, because the one pair a reader
   with colour blindness would have to work at is the pair this chart exists
   to show. */
.pc-median { stroke: var(--ink-55); stroke-width: 1; stroke-dasharray: 4 4; }
.pc-quad { font-size: 10.5px; fill: var(--ink-55); letter-spacing: .02em; }
.pc-point-name { font-size: 11px; fill: var(--ink-65); }
.pc-dot { fill: #2a78d6; stroke: var(--bg); stroke-width: 2; }
.pc-dot.pc-low { fill: #d03b3b; }
@media (prefers-color-scheme: dark) {
  .pc-dot { fill: #3987e5; }
  .pc-dot.pc-low { fill: #e66767; }
}

.stars { color: #b45309; letter-spacing: 1px; white-space: nowrap; }
/* The score beside the stars. Five stars is a band four points wide at the
   top and fifteen at the bottom, so two people on the same stars are usually
   not on the same score — and a tooltip carrying that is invisible on the
   phone this page is mostly read on. */
.points {
  font-size: 12.5px; font-weight: 650; color: var(--ink-72);
  font-variant-numeric: tabular-nums; margin-left: 6px; white-space: nowrap;
}
h1 .points { font-size: 15px; }
.stars-off { color: var(--ink-14); }

/* --- one customer, in order ---------------------------------------------- */
/* A rule down the left, a date beside it, the entry to the right of that.
   The date column is fixed so the entries line up into a column of their own
   and the eye reads down the events rather than down the dates. */
.tl { list-style: none; margin: 0; padding: 0; }
.tl-item {
  display: grid; grid-template-columns: 132px 1fr; gap: 0 18px;
  padding: 12px 0 12px 0; border-left: 2px solid var(--divider);
  margin-left: 6px; padding-left: 18px; position: relative;
}
.tl-item::before {
  content: ""; position: absolute; left: -6px; top: 18px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--paper); border: 2px solid var(--ink-55);
}
/* The kinds people scan for. Everything else keeps the neutral marker, so a
   coloured dot always means something rather than decorating every row. */
.tl-money::before { border-color: #1b7f4f; }
.tl-ticket::before { border-color: #b4530a; }
.tl-connection::before, .tl-meter::before { border-color: var(--accent, #03088C); }
.tl-when { font-size: 12.5px; color: var(--ink-55); line-height: 18px; }
.tl-date { display: block; font-weight: 600; color: var(--ink); }
.tl-time { display: block; }
.tl-body { min-width: 0; font-size: 14px; line-height: 21px; }
.tl-body .tag { margin-right: 8px; }
.tl-detail { color: var(--ink-72); margin-top: 2px; }
@media (max-width: 700px) {
  .tl-item { grid-template-columns: 1fr; gap: 2px; }
}

/* --- the token, at reading distance -------------------------------------- */
/* Somebody keys these into a meter on a wall, often in the dark, off a slip.
   Nothing else on the receipt competes with them. */
.token-panel { text-align: center; }
.token-digits {
  font-size: 30px; line-height: 42px; letter-spacing: .12em;
  font-weight: 600; margin: 4px 0 2px; word-break: break-all;
}
@media (max-width: 560px) { .token-digits { font-size: 23px; letter-spacing: .06em; } }

/* --- a day, line by line -------------------------------------------------- */
/* The time on the left, what happened on the right. Not a table: every one of
   these has one time and one thing, and a five-column table carrying one row
   is what made the field report read as broken on a handset. */
.dayline { list-style: none; margin: 0; padding: 0; }
.dayline-item {
  display: grid; grid-template-columns: 116px 1fr; gap: 2px 14px;
  padding: 9px 0; border-top: 1px solid var(--divider);
}
.dayline-item:first-child { border-top: 0; }
.dayline-when {
  font-size: 12.5px; color: var(--ink-55); line-height: 20px;
  font-variant-numeric: tabular-nums;
}
.dayline-what { font-size: 14px; line-height: 21px; min-width: 0; }
.dayline-note { display: block; color: var(--ink-72); font-size: 13px; }
@media (max-width: 560px) {
  .dayline-item { grid-template-columns: 1fr; gap: 0; }
}

/* A row of small counters — one per kind of work that actually happened
   today. Narrower than the headline metrics above it, because there can be
   eight of them and they are a reading, not the headline. */
.metrics.tight { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.metrics.tight .metric { padding: 10px 12px; }
.metrics.tight .metric .n { font-size: 24px; line-height: 30px; }
.metrics.tight .metric .k { font-size: 11.5px; }

/* The meter the search landed on. Loud on purpose: it is the answer to the
   number in somebody's hand, on a phone held at arm's length at a wall. */
select.is-landed {
  outline: 3px solid #1b7f4f; outline-offset: 1px;
  background: #eaf7f0; font-weight: 650;
}

/* Pull down at the top of a page to ask again. The gesture every other app
   on the phone uses, so nobody has to be told it is there — same threshold,
   same place, same spinner. */
.pulltab {
  position: fixed; top: 0; left: 50%; z-index: 70;
  transform: translateY(0); margin-left: -78px; width: 156px;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--ink); color: var(--paper);
  border-radius: 0 0 999px 999px; padding: 7px 14px 9px;
  font-size: 12px; pointer-events: none;
  box-shadow: 0 3px 10px rgba(0, 0, 0, .18);
}
.pulltab-spin {
  width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .35);
  border-top-color: var(--paper); flex: none;
}
.pulltab.is-armed .pulltab-spin { border-top-color: #7fdcaa; }
.pulltab.is-going .pulltab-spin { animation: pullspin .7s linear infinite; }
@keyframes pullspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .pulltab.is-going .pulltab-spin { animation: none; }
}
/* --- the dashboard's charts ---------------------------------------------- */
/* The dashboard said what the numbers are and nothing about their shape.
   Hand-drawn SVG, like the People chart: no library, so the charts print,
   survive dark mode from one stylesheet, and add nothing to the page weight
   beyond their own figures. */
/* The panel table. Tight rows, because thirty-six of them is a pallet and
   the crew reads down it against the paper. */
.paneltable td, .paneltable th { padding: 2px 6px; }
.paneltable input { width: 100%; margin: 0; padding: 5px 8px; font-size: 14px;
                    min-height: 0; line-height: 1.3; }
.paneltable td.num { width: 34px; color: var(--ink-55); font-size: 12px; }
/* Short, and it grows: eight lines is a screen on a phone, and the Record
   button stays in reach. The table adds lines as the last one fills and
   the scanner adds them as it reads. */
.paneltable-wrap { max-height: none; }

/* The per-kind serial downloads. A list of links, not a table: each one is
   a file somebody takes away, and the count beside it says how many rows to
   expect before they open it. */
.serialdl { list-style: none; margin: 6px 0 0; padding: 0;
            display: flex; flex-wrap: wrap; gap: 6px 18px; }
.serialdl li { display: flex; align-items: baseline; gap: 6px; font-size: 13.5px; }

/* --- the dashboard's filters ---------------------------------------------- */
/* Opened from a button, over the page. The first shape of this held a 232px
   column open down the whole page whether or not anybody had opened it — a
   filter costing more room than the thing it filters. */
.pagehead-controls { display: flex; align-items: flex-start; gap: 12px; }
/* The dashboard's own bar: the scope, then View and Figures at the height of
   the select they sit beside — one baseline, not a button floating at each
   corner. On a phone the scope takes the width and the two buttons share
   the line under it, each half of it. */
.dashbar { align-items: flex-end; flex-wrap: wrap; }
.dashbar .filters.bare { flex: 1 1 220px; }
.dashbar-btns { display: flex; gap: 8px; align-items: flex-end; }
/* The same box as the scope button beside it: 9px 14px, 14px type, one
   border colour — so the three read as one row of controls. */
.dashbar .viewpick-btn {
  padding: 9px 14px; font-size: 14px; line-height: 22px;
  border-color: var(--ink-14); background: var(--paper);
}
/* The Figures list is wider than its button and hung back over the last
   tile's number. It slides out into the page margin instead — as far as
   there is margin to slide into, up to its own overhang, and not at all on
   a screen with none, where sliding would mean a sideways scroll. */
.dashbar-btns .tilepick .viewpick-panel {
  transform: translateX(clamp(0px,
    calc((100vw - var(--page)) / 2 + var(--gutter) - 12px), 70px));
}
@media (max-width: 620px) {
  .dashbar { flex-direction: column; align-items: stretch; gap: 10px; flex: 1 1 100%; }
  /* The basis was the row's; stacked, it became a height. */
  .dashbar .filters.bare { flex: none; }
  .dashbar .scopepick, .dashbar .scopepick-btn { width: 100%; }
  .dashbar-btns { align-items: flex-start; }
  .dashbar-btns .viewpick { flex: 1 1 0; min-width: 0; }
  .dashbar-btns .viewpick-btn { width: 100%; justify-content: center; }
  /* Opened on a phone the panel drops over the page under both buttons,
     the width of the row, as it does on a laptop — not in the flow, where
     it pushed the whole page down. */
  .dashbar-btns { position: relative; }
  .dashbar-btns .viewpick { position: static; }
  .dashbar-btns .viewpick-panel {
    position: absolute; left: 0; right: 0; top: calc(100% + 6px);
    width: auto; max-width: none; margin: 0;
    box-shadow: 0 10px 26px rgba(32, 30, 29, .18);
  }
}
.viewpick { position: relative; }
.viewpick > summary { list-style: none; }
.viewpick > summary::-webkit-details-marker { display: none; }
.viewpick-btn {
  cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--divider); background: var(--surface);
  padding: 7px 12px; font-weight: 650; font-size: 13.5px; white-space: nowrap;
}
.viewpick-btn::after { content: " \25be"; color: var(--ink-55); }
.viewpick[open] > .viewpick-btn::after { content: " \25b4"; }
.viewpick-btn:hover { color: var(--accent); }
.viewpick-panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
  width: 250px; max-width: min(92vw, 300px);
  max-height: min(70vh, 560px); overflow-y: auto;
  background: var(--bg); border: 2px solid var(--divider);
  box-shadow: 0 10px 26px rgba(32, 30, 29, .18);
  padding: 4px 14px 12px;
}

@media (max-width: 620px) {
  .viewpick-panel { position: static; width: auto; max-width: none;
                    box-shadow: none; margin-top: 6px; }
}

/* --- the dashboard's own sidebar ------------------------------------------ */
/* Fourteen panels, and no two readers want the same third of them. The
   sidebar is where the reader says which third is theirs and how the figures
   on it are cut — the estate's period and capture, the activity chart's
   grain and day, which were three separate bars across the middle of the
   page before.

   A column on a laptop; above the page and folded shut on a phone, where a
   fixed column would take half the screen from the thing it is filtering. */
.dashmain { min-width: 0; }
.dashside-block { border-top: 1px solid var(--ink-07, #f3f2f2); padding: 10px 0 2px; }
.dashside-head { display: flex; align-items: baseline; gap: 8px; }
.dashside-head h2 {
  margin: 0 0 6px; font-size: 11px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-55); font-weight: 650;
}
.dashside-all {
  margin-left: auto; border: 0; background: none; padding: 0; cursor: pointer;
  color: var(--accent); font: inherit; font-size: 11.5px; font-weight: 650;
}
.dashside-all:hover { text-decoration: underline; }

/* The panel list. A colour beside each name, the one that family keeps on
   every other page, so it reads as a legend of the page rather than fourteen
   identical lines. */
.panelpick { list-style: none; margin: 0; padding: 0; }
.panelpick li { margin: 0; }
.panelpick label {
  display: flex; align-items: center; gap: 7px; padding: 3px 0;
  font-size: 13px; cursor: pointer; line-height: 1.25;
}
.panelpick input { margin: 0; flex: none; }
.panelpick .sw {
  width: 9px; height: 9px; border-radius: 2px; flex: none; display: inline-block;
}
.panelpick label:hover { color: var(--accent); }

.dashside-form label {
  display: block; margin: 0 0 8px; font-size: 12px; color: var(--ink-72);
}
.dashside-form input[type="date"], .dashside-form select {
  width: 100%; margin-top: 3px;
}
.dashside-form .actions { margin: 4px 0 0; }
.dashside-form .chiprow { display: flex; flex-wrap: wrap; gap: 5px; margin: 0 0 8px; }

.dashgrid {
  display: grid; gap: 16px 20px; grid-template-columns: repeat(12, 1fr);
  margin: 0 0 24px;
}
.dashgrid .span4 { grid-column: span 4; }
.dashgrid .span6 { grid-column: span 6; }
.dashgrid .span8 { grid-column: span 8; }
.dashgrid .span12 { grid-column: span 12; }
.dashgrid .panel { margin-bottom: 0; }
/* The estate rings, side by side.
   Five does not divide into a twelve-column grid, so they get a row of their
   own rather than being forced into thirds with two left over underneath.
   auto-fit keeps them on one line while there is room for a legible ring and
   drops them to four, three, two and one as the window narrows, which is the
   same behaviour on a laptop as on a phone. */
.ringrow {
  display: grid; gap: 16px 20px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.ringrow .panel { margin-bottom: 0; }
@media (max-width: 900px) {
  .dashgrid .span4, .dashgrid .span6, .dashgrid .span8 { grid-column: 1 / -1; }
}
.chart { margin: 8px 0 4px; }
/* The grain filter. Buttons and not a select, because the three are one
   choice with three visible states and a closed select hides which one the
   chart is showing. */
.grainpick { display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
             margin: 2px 0 6px; }
.grainpick .chip { font-size: 12px; }
.grainday { font-size: 12px; color: var(--ink-72); display: inline-flex;
            align-items: center; gap: 6px; }
.chart svg { display: block; max-width: 100%; height: auto; }

/* Identity is never colour alone: every series is named in the legend, and
   the four largest are labelled on the mark itself. */
.legend {
  display: flex; flex-wrap: wrap; gap: 6px 14px;
  font-size: 12px; color: var(--ink-72); margin: 6px 0 0;
}
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend .sw { width: 10px; height: 10px; border-radius: 3px; flex: none; }

/* The figures behind a chart. Open it and the numbers are a table — which is
   also what a printer and a screen reader get. */
.figures { margin: 10px 0 0; }
.figures summary {
  font-size: 12px; color: var(--ink-55); cursor: pointer;
  list-style: revert; width: fit-content;
}
.figures[open] summary { margin-bottom: 8px; }
.figures table { font-size: 12.5px; }

/* One tooltip, moved between marks. A chart with three hundred squares should
   not carry three hundred hidden elements. */
.charttip {
  position: fixed; z-index: 60; pointer-events: none;
  background: var(--ink); color: var(--paper);
  padding: 6px 9px; border-radius: 6px; font-size: 12px; line-height: 17px;
  max-width: 240px; box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
}

/* A headline tile in its metric's own colour. This is categorical colour —
   which figure this is — and not a status rainbow: .good/.warn/.bad below
   still say what they mean in words, and are left alone. */
/* Each tile takes the hue of the family it counts, so the tiles and the
   charts below them agree; two tiles never take the same one, or the row
   reads as a grouping that is not there. The head-end tile is deliberately
   left neutral: it is a status tile, and status is not a category. */
.metric[class*="hue-"], .metric.hue { color: #fff; border-radius: 8px; }
.metric.hue-houses { background: #2a78d6; }        /* houses registered */
.metric.hue-commissioned { background: #d55181; }  /* meters commissioned */
.metric.hue-assigned { background: #1baf7a; }      /* active connections */
.metric.hue-installed { background: #c98500; }     /* meters assigned */
.metric.hue-sites { background: #03088C; }         /* the portfolio itself */
.metric.hue-wiring { background: #eb6834; }        /* productive use */
.metric.hue-revenue { background: #6250d6; }       /* revenue */
.metric[class*="hue-"] .k, .metric.hue .k,
.metric[class*="hue-"] .sub, .metric.hue .sub { color: rgba(255, 255, 255, .84); }
.metric[class*="hue-"] .n, .metric.hue .n { color: #fff; }
.spark { margin-top: 6px; height: 26px; }
.spark svg { display: block; }

/* The dashboard's people section. It is a second heading on a page that
   already has one, so it announces itself with a rule rather than an h1 —
   and it carries its own dates, because a period that changed the tiles
   above it would be a different page. */
.crewhead { align-items: flex-end; margin-top: 28px; }
.section-title {
  font-size: 22px; margin: 0 0 4px; border-top: 2px solid var(--ink);
  padding-top: 10px;
}
.crewhead .footnote { margin: 0; max-width: 62ch; }
.crewdates { align-items: flex-end; }
.crewdates label { font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
                   color: var(--ink-60); display: block; }

/* --- the day, on one page ------------------------------------------------- */
/* Three answers a portfolio manager asks in one glance: where the queue is,
   what needs them, and where the work is happening. The site board takes the
   full width because twelve rows is what makes nine silent ones visible. */
.sheet-grid {
  display: grid; gap: 16px;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
}
.sheet-sites { grid-column: 1 / -1; }
@media (max-width: 720px) { .sheet-grid { grid-template-columns: 1fr; } }

/* The top band: every queue on one line, with what needs the manager beside
   it. Asymmetric on purpose — the queues are five narrow cards of two figures
   each and the exceptions are prose, so an even split would starve the one
   and pad the other. Stacked two-across the queues pushed the exceptions off
   the fold, which put the reason the page exists below five panels of
   routine counts. */
.sheet-top {
  grid-column: 1 / -1; display: grid; gap: 16px; align-items: start;
  grid-template-columns: minmax(0, 2.45fr) minmax(0, 1fr);
}
.queuestrip { display: grid; gap: 12px; grid-template-columns: repeat(5, minmax(0, 1fr)); }
.queue { padding: 12px 14px 10px; }
.queue h2 { font-size: 14px; margin: 0 0 2px; }
.qsteps { list-style: none; margin: 0; padding: 0; }
/* The figure and the day's movement read as one phrase — "6, +6 today" — so
   they sit together at the left rather than at opposite edges of the card.
   Pushed apart they stop being one reading and become two columns that
   happen to share a line, which on a phone puts a metre of white space
   between a number and the thing that qualifies it. */
.qstep { display: grid; gap: 0 7px; padding: 7px 0 6px;
         grid-template-columns: auto minmax(0, 1fr); align-items: baseline;
         border-top: 1px solid var(--ink-07, #f3f2f2); }
.qstep:first-child { border-top: 0; }
/* The day is the headline. */
.q-today { grid-area: 1 / 1 / 2 / 2; font-size: 22px; line-height: 26px;
           font-weight: 650; color: var(--accent);
           font-variant-numeric: tabular-nums; text-align: left; }
.q-when { grid-area: 1 / 2 / 2 / 3; font-size: 10.5px; letter-spacing: .05em;
          text-transform: uppercase; color: var(--ink-55); }
/* And the register under it, saying what the day moved out of. */
.q-stock { grid-area: 2 / 1 / 3 / 3; font-size: 11.5px; line-height: 15px;
           color: var(--ink-72); text-decoration: none; }
.q-stock strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.q-stock:hover { text-decoration: underline; }
/* A stage nothing happened at is still on the card — the queue is the queue
   — but it does not shout a figure that did not change. */
.qstep.is-quiet .q-today { color: var(--ink-40, var(--ink-55)); font-weight: 600; }
.qstep.is-quiet .q-bar { background: var(--ink-14, #d8d7d0); }
/* Two lines' worth whether it needs them or not. A stage name that wraps on
   one card and not on the next puts the bar at a different height in every
   card, and five cards meant to be read across stop lining up. */
.q-stock { min-height: 30px; }
.q-track { grid-area: 3 / 1 / 4 / 3; display: block; height: 5px; margin-top: 4px;
           border-radius: 3px; background: var(--ink-07, #f3f2f2); }
.q-bar { display: block; height: 5px; border-radius: 3px; background: var(--accent); }


/* Five across is a card about 150px wide, which is two figures and a name
   and nothing else. Below that it goes to three, then two, then one — the
   cards keep their shape and the strip reflows rather than shrinking the
   type. The exceptions drop underneath at the point the two columns stop
   fitting side by side. */
@media (max-width: 1180px) {
  .queuestrip { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 1020px) {
  .sheet-top { grid-template-columns: 1fr; }
  .queuestrip { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* Two across on a phone, not one. One card per row makes the strip five
   screens long and a queue nobody scrolls to is a queue nobody reads. */
@media (max-width: 720px) {
  .queuestrip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The pipeline. Three counts in order, because a pile-up is only visible as
   a sequence — three totals side by side do not show one. */
.pipe { list-style: none; display: flex; gap: 14px; margin: 6px 0 10px; padding: 0;
        align-items: flex-end; }
.pipe-step { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pipe-n { font-size: 26px; line-height: 30px; font-weight: 650; color: var(--ink);
          font-variant-numeric: tabular-nums; }
.pipe-step.is-empty .pipe-n { color: var(--ink-55); }
/* Length, not thickness. A full-width rule whose weight encodes magnitude is
   not an encoding anybody reads — the eye compares lengths. */
.pipe-track { display: block; height: 8px; border-radius: 4px;
              background: var(--ink-07, #f3f2f2); }
.pipe-bar { display: block; height: 8px; border-radius: 4px; background: var(--accent); }
.pipe-step.is-empty .pipe-bar { background: var(--ink-14, #d8d7d0); }
.pipe-label { font-size: 11.5px; letter-spacing: .05em; text-transform: uppercase;
              color: var(--ink-55); }
.pipe-wait { font-size: 12px; color: var(--ink-72); }

/* What needs the manager. A count, then what caused it — grouped, because
   twelve rows sharing one finding at one site are one problem. */
.exlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.ex { display: grid; grid-template-columns: 54px 1fr; gap: 10px; align-items: center;
      border-left: 3px solid var(--ink-55); padding: 6px 0 6px 10px; }
.ex-critical { border-left-color: #d03b3b; }
.ex-serious { border-left-color: #ec835a; }
.ex-n { font-size: 22px; font-weight: 650; color: var(--ink);
        font-variant-numeric: tabular-nums; text-decoration: none; }
.ex-w { font-size: 13px; line-height: 18px; color: var(--ink-72); }
.ex-w strong { color: var(--ink); }

/* The estate. What stands in the field, and what share of it is flagged.
   The bar is that share and not the total: 4,800 poles beside 14 concentrators
   on one scale draws the concentrators as nothing, and the count is already
   printed beside the name. A share is comparable between classes; a raw
   magnitude across classes is not a comparison anybody needs. */
.sheet-estate, .sheet-defects { grid-column: auto; }
.estate { list-style: none; margin: 4px 0 0; padding: 0; }
.est-row { display: grid; gap: 10px; align-items: baseline; padding: 5px 0;
           grid-template-columns: minmax(78px, 118px) 56px 64px 1fr 40px;
           border-top: 1px solid var(--ink-07, #f3f2f2); }
.est-row:first-child { border-top: 0; }
.est-label a { font-size: 13px; color: var(--ink); text-decoration: none; }
.est-label a:hover { text-decoration: underline; }
.est-total { font-size: 15px; font-weight: 650; color: var(--ink);
             text-align: right; font-variant-numeric: tabular-nums; }
.est-track { height: 8px; border-radius: 4px; background: var(--ink-07, #f3f2f2);
             align-self: center; }
/* Reserved status colour, and it never travels alone: the count and the word
   that names the fault sit next to it. */
.est-bad { display: block; height: 8px; border-radius: 4px; background: #d03b3b;
           min-width: 2px; }
.est-note { font-size: 12px; line-height: 17px; color: var(--ink-55); }
.est-flag { color: #b03434; font-weight: 600; }
.est-moved { font-size: 12.5px; text-align: right; color: var(--ink-72);
             font-variant-numeric: tabular-nums; }
@media (max-width: 720px) {
  .est-row { grid-template-columns: 1fr 56px 40px; }
  .est-track { grid-column: 1 / -1; }
  .est-note { grid-column: 1 / -1; }
}

/* What is broken, ranked by part. One hue: the question is which bar is long. */
.defects { list-style: none; margin: 4px 0 0; padding: 0; }
.def-row { display: grid; grid-template-columns: minmax(90px, 150px) 1fr 46px;
           gap: 12px; align-items: center; padding: 3px 0; }
.def-what { font-size: 13px; color: var(--ink); overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }
.def-track { height: 9px; border-radius: 4px; background: var(--ink-07, #f3f2f2); }
.def-bar { display: block; height: 9px; border-radius: 4px; background: var(--accent);
           min-width: 2px; }
.def-n { font-size: 13px; text-align: right; color: var(--ink-72);
         font-variant-numeric: tabular-nums; }

/* The plant, when it was written down. One line, and simply absent otherwise:
   a box permanently reserving space for zeros teaches the eye to skip it. */
.plantline { font-size: 12.5px; color: var(--ink-55); margin: 14px 0 0; }

/* Rate of change. An arrow and a percentage against the mean of the days
   behind it — the figure a manager reads to know whether to go and look. */
.movelist { list-style: none; margin: 4px 0 0; padding: 0; }
.move {
  display: grid; grid-template-columns: 1fr auto auto; gap: 2px 12px;
  align-items: baseline; padding: 7px 0;
  border-top: 1px solid var(--ink-07, #f3f2f2);
}
.move:first-child { border-top: 0; }
.move-label { font-size: 13px; color: var(--ink); }
.move-n { font-size: 16px; font-weight: 650; font-variant-numeric: tabular-nums; }
.move-change { font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.move-base { grid-column: 1 / -1; font-size: 11.5px; color: var(--ink-55); }
.move-up .move-change { color: #1b7f4f; }
.move-down .move-change { color: #b03434; }
.move-level .move-change { color: var(--ink-55); }
.move-arrow { font-size: 11px; }

/* The site board. */
.siteboard { list-style: none; margin: 4px 0 0; padding: 0; }
.site-row { display: grid; grid-template-columns: minmax(90px, 190px) 1fr 44px;
            gap: 12px; align-items: center; padding: 3px 0; }
.site-name { font-size: 13px; color: var(--ink); overflow: hidden;
             text-overflow: ellipsis; white-space: nowrap; }
.site-track { height: 9px; border-radius: 4px; background: var(--ink-07, #f3f2f2); }
.site-fill { display: block; height: 9px; border-radius: 4px; background: var(--accent); }
.site-n { font-size: 13px; text-align: right; color: var(--ink-72);
          font-variant-numeric: tabular-nums; }
/* Conspicuous on purpose: a site that recorded nothing is the finding. */
.site-row.is-silent .site-name, .site-row.is-silent .site-n,
.site-item.is-silent .site-name, .site-item.is-silent .site-n { color: var(--ink-55); }

/* Each site opens on what its figure is made of. The whole row is the
   control, so there is no separate small link to find and miss. */
.siteboard li { list-style: none; }
.site-item > summary { cursor: pointer; list-style: none; }
.site-item > summary::-webkit-details-marker { display: none; }
.site-item > summary::marker { content: ""; }
.site-item > summary:hover .site-name { color: var(--accent); }
.site-item > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.site-item[open] > summary .site-name { font-weight: 600; }
.site-detail {
  padding: 4px 0 12px 8px; margin-left: 4px;
  border-left: 2px solid var(--ink-07, #f3f2f2);
}
.breakdown { font-size: 12.5px; width: auto; min-width: 220px; }
.breakdown td { padding: 2px 18px 2px 0; border: 0; }
.breakdown-total td { font-weight: 650; border-top: 1px solid var(--ink-14, #d8d7d0); }
.site-detail .footnote { margin: 6px 0 0; }

/* The page says it is live, and says it when it stops being. A page that has
   quietly stopped updating is worse than one that never claimed to. */
.livemark {
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: #1b7f4f; border: 1px solid currentColor; border-radius: 999px;
  padding: 1px 8px; white-space: nowrap;
}
.livemark.is-stale { color: #B26A00; }

/* The day as a message. The box is real and selectable, so a phone that will
   not give the page a clipboard still sends the day. */
.sendmsg .msgbox {
  width: 100%; font-family: var(--mono, ui-monospace, monospace);
  font-size: 12.5px; line-height: 18px; resize: vertical;
}
.msgdo { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
         margin-top: 8px; }
.msgdo .hint { margin: 0; }

/* --- the day sheet on a phone -------------------------------------------- */
/* These sit here, after the rules they overturn, and not up with the other
   mobile rules: a media query adds no specificity, so an @media block earlier
   in the file loses to a plain rule later in it. The strip above was written
   after the mobile section and was quietly winning. */
@media (max-width: 860px) {
  /* The meter pipeline. Three steps across a 390px screen gave each of them
     110px, so every label broke over two lines and the count sat above a bar
     it no longer looked attached to. Down the page each step is one line
     anybody reads at a glance, which is what the strip is for.

     Placed by area rather than by source order: the markup is count, bar,
     label, note — the order a wide screen stacks them in — and a phone wants
     the count and its label on one line with the bar under both. */
  /* align-items: flex-end is what a row of three steps wants; turned on its
     side it shrank every step to its own content and pinned it to the right
     margin. */
  .pipe { flex-direction: column; gap: 12px; align-items: stretch; }
  .pipe-step {
    display: grid; grid-template-columns: auto 1fr;
    column-gap: 10px; row-gap: 4px; align-items: center;
  }
  .pipe-n { grid-area: 1 / 1 / 2 / 2; font-size: 24px; line-height: 28px; }
  .pipe-label { grid-area: 1 / 2 / 2 / 3; }
  .pipe-track { grid-area: 2 / 1 / 3 / 3; width: 100%; }
  .pipe-wait { grid-area: 3 / 1 / 4 / 3; font-size: 11.5px; }

  /* The estate. Five columns became a name, a stranded bar, a note and a
     "+1" adrift in the middle of the row. Two lines: what it is and how
     many, then what is wrong with it. */
  .est-row {
    grid-template-columns: 1fr auto auto;
    column-gap: 8px; row-gap: 3px; padding: 9px 0; align-items: center;
  }
  .est-label { grid-area: 1 / 1 / 2 / 2; }
  .est-total { grid-area: 1 / 2 / 2 / 3; }
  .est-moved { grid-area: 1 / 3 / 2 / 4; }
  .est-track { grid-area: 2 / 1 / 3 / 4; width: 100%; }
  .est-note { grid-area: 3 / 1 / 4 / 4; }

}

/* The way back, in the installed app that has no browser around it. Hidden
   until the script says there is somewhere to go back to. */
.appback {
  background: transparent; border: 0; color: var(--paper);
  font-size: 30px; line-height: 1; padding: 0 12px 4px; margin-right: 2px;
  cursor: pointer; align-self: center; min-width: 44px; min-height: 44px;
}
.appback:hover { background: rgba(243, 242, 242, .14); }
.appback[hidden] { display: none; }

/* Signing with a finger. The pad is the paper, so it is drawn as paper: a
   white field with a rule across it and room above the rule for a hand. */
.signpad { margin: 12px 0 16px; }
.signpad-head {
  display: flex; align-items: center; gap: 10px; margin-bottom: 6px;
}
.signpad-head .pick-label { flex: 1 1 auto; }
.signpad-canvas {
  display: block; width: 100%; height: 150px; background: #fff;
  border: 2px solid var(--ink-14); border-radius: 2px;
  /* A page that scrolls under a finger cannot be signed on. */
  touch-action: none; cursor: crosshair;
}
.sign-image {
  display: block; max-height: 46px; width: auto; margin-bottom: 2px;
}

/* A slice taken out of a ring: the legend row stays, struck through, so it
   is plain that the figure is still there and only the drawing has changed. */
.sw-btn {
  background: none; border: 0; padding: 0 4px 0 0; cursor: pointer;
  line-height: 1; vertical-align: middle;
}
.legend [data-slice].is-out { opacity: .45; }
.legend [data-slice].is-out a,
.legend button[data-slice].is-out { text-decoration: line-through; }
.legend [data-slice].is-out .sw { filter: grayscale(1); }
/* A legend entry that is itself the switch: the name is part of the target,
   because a nine-pixel square is not what anybody aims at. */
.legend-toggle {
  background: none; border: 0; padding: 2px 0; cursor: pointer;
  font: inherit; color: inherit; display: inline-flex; align-items: center;
  gap: 5px;
}
/* Never a filled background. `button:hover` paints the accent across the
   whole control, which over a legend entry hides the name and the colour
   swatch at exactly the moment somebody is deciding whether to press it —
   the one moment they need to read it. The hover is the name going accent
   and underlining instead, so what is about to be clicked stays legible.
   Same for the keyboard: the focus ring is the mark, not a fill. */
.legend-toggle:hover, .legend-toggle:focus,
.legend-toggle:active, .legend-toggle:focus-visible {
  background: none;
  color: var(--accent);
}
.legend-toggle:hover { text-decoration: underline; }
.legend-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The small swatch button on the rings' legends has the same problem. */
.sw-btn:hover, .sw-btn:focus, .sw-btn:active, .sw-btn:focus-visible {
  background: none;
}

/* The rings' own controls, above all five of them. */
.estatebar .filters { margin: 0; }
.estatebar .footnote { margin-top: 8px; }

/* --- the sheet a customer signs -------------------------------------------
   A page shaped like the paper it becomes: one house, one side of A4, ruled
   boxes where the record has nothing to print. It is drawn the same on screen
   as on paper so that what somebody checks before pressing Print is what
   comes out of the printer. */
.sheet {
  background: var(--bg); border: 2px solid var(--ink);
  padding: 18px 20px; max-width: 190mm; margin: 0 0 16px;
}
.sheet-title {
  font-size: 19px; margin: 0 0 14px; padding-bottom: 8px;
  border-bottom: 2px solid var(--ink);
}
.sheet-head {
  font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-65); margin: 16px 0 6px;
}
.sheet-grid, .sheet-items, .sheet-sign { width: 100%; border-collapse: collapse; }
/* The test report: a ruled group line, the unit beside the reading's name. */
.sheet-test tr.sheet-group th { font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-65); padding: 8px 6px 2px; border-bottom: 1px solid var(--ink); text-align: left; }
.sheet-unit { color: var(--ink-55); font-weight: 400; }
/* The project status report: the programme's own sheet, then the pictures.
   Blue bands and a ruled table, as the sheet the lender receives has them. */
.status-sheet, .status-pictures { margin-bottom: 18px; }
.status-brand { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.status-brand img { width: 44mm; height: auto; }
.status-partner { font-size: 12px; font-weight: 700; color: #2f6f2f; }
.status-head { border-collapse: collapse; margin-bottom: 10px; font-size: 12px; }
.status-head th { text-align: left; font-weight: 700; padding: 2px 14px 2px 0; white-space: nowrap; }
.status-head td { padding: 2px 0; }
.status-band { background: #dce6f2; border: 1px solid #9fb3cc; font-weight: 700; font-size: 11.5px;
  text-transform: uppercase; letter-spacing: .04em; padding: 3px 6px; margin: 10px 0 0; text-align: center; }
.status-sub { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--ink-65); margin-left: 8px; }
.status-table { width: 100%; border-collapse: collapse; font-size: 11px; }
.status-table th, .status-table td { border: 1px solid #b7c4d6; padding: 2px 5px; vertical-align: middle; }
.status-table .status-cols th { background: #eef2f8; text-align: center; font-size: 10.5px; }
.status-table .sn { width: 28px; text-align: center; }
.status-table .pct { width: 74px; text-align: center; font-variant-numeric: tabular-nums; }
.status-table .say { width: 34%; text-align: center; color: var(--ink-72); }
.status-table .status-package th { background: #f3f6fa; text-align: left; font-weight: 700; }
.status-table .status-package th.pct { text-align: center; }
.status-table .status-total th { background: #e2e9f3; text-align: right; }
.status-issues { font-size: 11px; line-height: 1.45; border: 1px solid #b7c4d6; border-top: 0; padding: 6px 8px; }
.status-issues p { margin: 0 0 3px; }
.status-gallery { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-top: 8px; }
.status-gallery figure { margin: 0; break-inside: avoid; }
/* Framed as the house's rooms are: the mat and the border, the picture's
   own shape inside. */
.status-gallery .frame.frame-free { border-color: var(--ink-40, #9a9a9a); border-radius: 3px; }
.status-gallery .frame.frame-free img { width: 100%; height: auto; }
.status-gallery figcaption { font-size: 9.5px; color: var(--ink-65); margin-top: 2px; line-height: 1.3; }
.status-edit { width: 100%; }
.status-edit td { vertical-align: middle; }
.status-edit td.num { white-space: nowrap; }
.status-edit input[type="number"] { width: 5em; }

/* The one photograph a certificate carries: small, framed, top right. */
.sheet-top { display: flex; gap: 14px; align-items: flex-start; }
.sheet-top .sheet-grid { flex: 1 1 auto; min-width: 0; }
.sheet-photo { flex: 0 0 150px; width: 150px; margin: 0; }
.sheet-photo img { display: block; width: 150px; height: 112px; object-fit: cover; border: 1px solid var(--ink); }
.sheet-photo figcaption { font-size: 10px; color: var(--ink-65); margin-top: 3px; }
.sheet-sub { font-size: 11px; font-weight: 400; color: var(--ink-65); margin-left: 8px; }
.sheet-grid th, .sheet-items th.lab {
  text-align: left; font-size: 10.5px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-65); font-weight: 600;
  padding: 6px 10px 6px 0; white-space: nowrap; vertical-align: middle;
}
.sheet-grid td, .sheet-items td {
  padding: 6px 16px 6px 0; font-size: 13px; vertical-align: middle;
  border-bottom: 1px solid var(--ink-14);
}
.sheet-items th { font-size: 10.5px; text-align: left; color: var(--ink-65); }
.sheet-items td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Where the register has nothing, the sheet has somewhere to write.
   Named for the sheet: `.rule` was already a horizontal divider with a solid
   background, and every blank on this page came out as a grey block. */
.sheet-rule {
  display: inline-block; width: 100%; min-width: 60px;
  border-bottom: 1px dotted var(--ink-55); height: 14px; vertical-align: bottom;
}
.sheet-box {
  display: inline-block; width: 11px; height: 11px; margin-right: 5px;
  border: 1px solid var(--ink); vertical-align: -1px;
}
.sheet-box.on { background: var(--ink); }
.sheet-say { font-size: 12.5px; line-height: 19px; margin: 0 0 4px; }
.sheet-sign { margin-top: 22px; }
.sheet-sign th {
  text-align: left; font-size: 10.5px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-65); padding-bottom: 4px;
  width: 33.33%;
}
.sheet-sign tr.who td { font-size: 13px; height: 20px; padding-right: 16px; }
.sheet-sign tr.sign td {
  font-size: 10px; color: var(--ink-55); padding: 34px 16px 0 0;
  vertical-align: bottom;
}
.sign-line {
  display: block; border-bottom: 1px solid var(--ink); margin-bottom: 4px;
}
.sheet-foot {
  margin: 16px 0 0; padding-top: 8px; border-top: 1px solid var(--ink-14);
  font-size: 10.5px; color: var(--ink-55);
}


/* --- Ask the register (EOC-CA-ASK-001) ------------------------------------
   An instrument, not an assistant: the platform's navy head, flat tiles,
   square corners, no shadows, no bubbles. Docked right, over the page. */
.ask-launch {
  background: none; border: 1px solid rgba(255, 255, 255, .5); color: #fff;
  padding: 6px 12px; font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  margin-right: 10px;
}
.ask-launch:hover, .ask-launch:focus-visible { background: rgba(255, 255, 255, .14); outline: 2px solid #fff; outline-offset: 1px; }
/* Working on a question while the reader is elsewhere: amber. Answer waiting
   unseen: green. The dot, and the button's title, so colour is not alone. */
.ask-launch.busy::after, .ask-launch.ready::after {
  content: ""; display: inline-block; width: 8px; height: 8px; margin-left: 7px;
  vertical-align: middle; background: #c98500;
}
.ask-launch.ready::after { background: #1baf7a; }
.ask[hidden] { display: none !important; }   /* display: flex below would beat hidden */
.ask {
  position: fixed; top: 0; right: 0; height: 100vh; width: min(412px, 94vw); z-index: 90;
  background: #fff; border-left: 1px solid var(--ink-40, #9a9a9a);
  display: flex; flex-direction: column; color: var(--ink);
}
.ask-head { background: var(--navy, #03088C); color: #fff; padding: 15px 18px 0; }
.ask-headrow { display: flex; align-items: baseline; gap: 12px; }
.ask-headrow h2 { margin: 0; font-size: 15px; font-weight: 800; flex: 1; color: #fff; }
.ask-close { background: none; border: 0; color: #fff; opacity: .75; cursor: pointer; font: inherit; font-size: 18px; padding: 0; }
.ask-close:focus-visible { outline: 2px solid #fff; }
.ask-scope { display: flex; align-items: center; gap: 9px; margin-top: 12px; border-top: 1px solid rgba(255,255,255,.22);
             padding: 9px 0 10px; font-size: 11px; font-weight: 600; letter-spacing: .02em; }
.ask-scopemark { width: 10px; height: 10px; flex: none; }
.ask-scope span:last-child { margin-left: auto; opacity: .7; font-weight: 500; }
.ask-body { flex: 1; overflow-y: auto; padding: 18px; }
.ask-state { display: none; }
.ask-state.on { display: block; }
.ask-label { font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-55); margin: 0 0 9px; }
.ask-intro { font-size: 13px; line-height: 1.55; color: var(--ink-72); margin: 0 0 20px; }
.ask-earlier { margin: 14px 0 0; font-size: 12.5px; }
/* An answer on its own page wears the panel's own dress, at reading width. */
.ask-page { max-width: 72ch; }
.ask-page .ask-tiles { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.ask-qlist { border-top: 1px solid var(--divider); }
.ask-q { display: block; width: 100%; text-align: left; background: none; border: 0; border-bottom: 1px solid var(--divider);
         border-left: 3px solid transparent; padding: 12px 10px 13px; font: inherit; font-size: 13px; font-weight: 600;
         cursor: pointer; line-height: 1.4; color: var(--ink); }
.ask-q small { display: block; font-weight: 400; color: var(--ink-55); font-size: 11.5px; margin-top: 3px; }
.ask-q:hover { background: #f6f6f6; border-left-color: var(--navy, #03088C); }
.ask-q:focus-visible { outline: 2px solid var(--navy, #03088C); outline-offset: -2px; }
.ask-question { border-left: 3px solid var(--navy, #03088C); background: #f6f6f8; padding: 11px 13px; font-size: 13px; line-height: 1.5; margin-bottom: 20px; }
.ask-progress { height: 3px; background: var(--divider); margin-bottom: 14px; }
.ask-progress i { display: block; height: 3px; width: 4%; background: var(--navy, #03088C); transition: width .4s; }
.ask-runline { display: flex; justify-content: space-between; gap: 12px; font-size: 11.5px; color: var(--ink-55); margin-bottom: 16px; }
.ask-steps { list-style: none; margin: 0; padding: 0; }
.ask-steps li { display: flex; gap: 11px; font-size: 12.5px; line-height: 1.45; padding: 8px 0; border-bottom: 1px solid #eee; color: var(--ink-55); }
.ask-steps li.done { color: var(--ink); }
.ask-sdot { width: 8px; height: 8px; margin-top: 4px; flex: none; background: var(--divider); }
.ask-steps li.done .ask-sdot { background: #1baf7a; }
.ask-steps li.live .ask-sdot { background: #c98500; }
.ask-fresh { border-left: 3px solid #c98500; background: #fbf5e7; padding: 10px 12px; font-size: 11.5px; line-height: 1.55; margin-bottom: 18px; }
.ask-fresh.late { border-left-color: #d03b3b; background: #fde2e2; }
.ask-tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; margin-bottom: 18px; }
.ask-mt { padding: 11px 12px 13px; color: #fff; }
.ask-mt em { font-style: normal; font-size: 9.5px; letter-spacing: .09em; font-weight: 700; text-transform: uppercase; opacity: .92; }
.ask-mt b { display: block; font-size: 22px; font-weight: 800; margin-top: 3px; letter-spacing: -.02em; }
.ask-answer { font-size: 13.5px; line-height: 1.62; }
.ask-answer p { margin: 0 0 12px; }
.ask-answer h3 { font-size: 12px; font-weight: 800; margin: 18px 0 7px; }
.ask-answer table { width: 100%; border-collapse: collapse; font-size: 12.5px; margin: 0 0 12px; }
.ask-answer th, .ask-answer td { border-bottom: 1px solid var(--divider); padding: 5px 6px; text-align: left; vertical-align: top; }
.ask-answer ul { margin: 0 0 12px; padding-left: 18px; }
.ask-gap { border: 1px solid #d55181; border-left-width: 3px; padding: 11px 12px; font-size: 12.5px; line-height: 1.5; margin: 0 0 12px; }
.ask-gap b { display: block; font-size: 10px; letter-spacing: .09em; text-transform: uppercase; color: #d55181; margin-bottom: 4px; }
.ask-meta { margin-top: 20px; padding-top: 11px; border-top: 1px solid var(--ink-40, #9a9a9a); font-size: 10.5px; color: var(--ink-55); line-height: 1.65; }
.ask-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 14px; }
.ask-act { border: 1px solid var(--ink-40, #9a9a9a); background: #fff; padding: 8px 13px; font: inherit; font-size: 12px; font-weight: 700; cursor: pointer; color: var(--ink); text-decoration: none; }
.ask-act.primary { background: var(--navy, #03088C); border-color: var(--navy, #03088C); color: #fff; }
.ask-act:focus-visible { outline: 2px solid #c98500; outline-offset: 1px; }
.ask-sql { margin: 12px 0 0; padding: 10px; background: #f6f6f8; font-size: 11px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; max-height: 40vh; overflow: auto; }
.ask-composer { border-top: 1px solid var(--ink-40, #9a9a9a); padding: 12px 14px; display: flex; gap: 8px; }
.ask-composer input { flex: 1; border: 1px solid var(--divider); padding: 10px 12px; font: inherit; font-size: 13px; min-width: 0; }
.ask-composer input:focus { outline: 2px solid var(--navy, #03088C); outline-offset: -1px; }
.ask-send { border: 0; background: var(--navy, #03088C); color: #fff; padding: 0 16px; font: inherit; font-size: 12px; font-weight: 700; cursor: pointer; }
@media (min-width: 1400px) { body.has-ask main { padding-right: 430px; } }
@media (prefers-reduced-motion: reduce) { .ask-progress i { transition: none; } }

@media print {
  .ask, .ask-launch { display: none !important; }

  /* The dashboard's charts print. Its tooltip and its disclosure arrows do
     not: a tooltip is a thing you point at, and on paper the figures table
     is simply open. */
  .charttip { display: none !important; }
  /* A gesture is not information. */
  .pulltab { display: none !important; }
  /* A closed disclosure leaves the page entirely rather than printing as a
     word with nothing under it; an open one prints its table and drops the
     arrow, because a data table on paper is already visible without a label for
     visible. The chart itself prints either way, which is the point of it. */
  .figures:not([open]) { display: none; }
  .figures summary { display: none; }
  /* Stacked, each panel got the full page width — and an SVG keeps its
     aspect, so the meter ring at 260 by 210 came out two-thirds of a page
     tall on its own. The grid stays a grid on paper, and the charts are
     capped besides, so the dashboard prints in two or three pages rather
     than nine. */
  .dashgrid {
    display: grid; grid-template-columns: 1fr 1fr; gap: 6mm 8mm;
  }
  .dashgrid .span4, .dashgrid .span6, .dashgrid .span8 { grid-column: auto; }
  .dashgrid .span12 { grid-column: 1 / -1; }
  .dashgrid .panel { break-inside: avoid; margin-bottom: 0; }
  .dashgrid h2 { font-size: 12pt; margin-bottom: 4px; padding-bottom: 4px; }
  .chart { margin: 4px 0 2px; }
  .chart svg { max-height: 45mm; }
  .legend { font-size: 7.5pt; gap: 2px 8px; margin-top: 3px; }
  .footnote, .hint { font-size: 7.5pt; }
  /* Controls are not information. The grain filter, the people section's own
     date form and the period links all say what the page is set to, and the
     heading already says that on paper. */
  .grainpick, .crewdates, .chart-period { display: none; }
  .metrics.eight, .grid.metrics { gap: 2px; }
  .metric { padding: 6px 9px; }
  .metric .n { font-size: 16pt; }
  .spark { display: none; }
  /* The sites table is the longest thing on the page and the one most often
     read on paper, so it is left whole and the charts give up the height. */
  .dashgrid + .panel, .panel table { break-inside: auto; }

  /* --- the overview on one side ------------------------------------------ */
  /* A brief, not a book: the whole overview on one side of A4, dense. The
     headline in one row of eight, the rings in one row of five, the asset
     base as a compact table, the rest three panels to a row with the charts
     at figure height, the sites table to its first dozen rows. Footnotes
     and data tables stay on the screen; on paper the picture is the point. */
  .dashmain { font-size: 7.5pt; }
  .dashside, .viewpick, .tilepick { display: none !important; }
  .dashmain > .pagehead, .dashmain .pagehead.crewhead { margin: 0 0 1mm; }
  .dashmain .pagehead h1 { font-size: 12pt; margin: 0; }
  .dashmain .pagehead .lede { font-size: 7.5pt; margin: 0; }
  .dashmain .grid.metrics.eight { grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 1px; margin: 1mm 0 2mm; }
  .dashmain .metric { padding: 2px 4px; }
  .dashmain .metric .k { font-size: 5.5pt; letter-spacing: .02em; }
  .dashmain .metric .n { font-size: 10pt; line-height: 1.1; }
  .dashmain .metric .sub { font-size: 5.5pt; }
  /* One grid for the whole page: the screen's grids and rows become
     transparent and every panel takes a share of thirty columns, so the
     rings sit five across, ordinary panels three across, and the wide ones
     the width of the sheet, with no half-empty rows between containers. */
  .dashmain { display: grid; grid-template-columns: repeat(30, minmax(0, 1fr)); gap: 1.5mm 3mm; align-items: start; }
  .dashmain > * { grid-column: span 30; }
  .dashmain .dashgrid, .dashmain .grid.grid.two, .dashmain .ringrow { display: contents; }
  .dashmain .dashgrid > .panel, .dashmain .grid.grid.two > .panel, .dashmain .grid.grid.two > div { grid-column: span 10; }
  .dashmain .ringrow > .panel { grid-column: span 6; }
  .dashmain .dashgrid > .span12, .dashmain .estatebar { grid-column: span 30; }
  /* People by site beside Ratings and Who did what, rather than a row of
     its own under a row with an empty third; the section's own heading and
     date form go, the panels say what they are. */
  .dashmain .dashgrid > [data-dashpanel="people"] { grid-column: span 10; }
  .dashmain .crewhead { display: none !important; }
  /* The whole brief at four fifths: the sheet is A4 and the page is not. */
  .dashmain { zoom: 0.76; }
  .dashmain .grid.metrics.eight { grid-column: span 30; }
  .dashmain .ringrow .chart svg { max-height: 24mm; }
  .dashmain .dashgrid .chart svg { max-height: 24mm; }
  .dashmain [data-dashpanel="activity"] .chart svg { max-height: 34mm; }
  .dashmain .chart { margin: 1px 0; }
  .dashmain h2, .dashmain .panel > h2 { font-size: 8pt; margin: 1mm 0 0.5mm; padding-bottom: 0.5mm; }
  .dashmain h2 .sub { font-size: 6pt; }
  .dashmain .section-title { font-size: 9pt; margin: 1mm 0 0; }
  .dashmain .crewhead { margin: 1mm 0 0; padding: 0; }
  .dashmain .legend { font-size: 6pt; gap: 1px 5px; margin-top: 1px; }
  .dashmain .footnote, .dashmain .hint, .dashmain .figures, .dashmain .estatebar { display: none !important; }
  .dashmain table { font-size: 6.5pt; line-height: 1.2; }
  .dashmain th, .dashmain td { padding: 0 3px !important; }
  .dashmain th { font-size: 5.5pt; }
  .dashmain .grid.two { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5mm 3mm; }
  .dashmain .panel { margin-bottom: 1mm; }
  .assetbase .ab-track { width: 22mm; height: 5px; }
  .assetbase .ab-pct { margin-left: 3px; min-width: 2.6em; font-size: 6.5pt; }
  .assetbase .ab-gaps { font-size: 6pt; }
  .assetbase .ab-bar { min-width: 0; }
  .dashmain [data-dashpanel="capture"] dl.kv { grid-template-columns: minmax(0, 1fr) auto; column-gap: 2mm; }
  .dashmain [data-dashpanel="capture"] dl.kv dt, .dashmain [data-dashpanel="capture"] dl.kv dd { font-size: 6.5pt; padding: 0; }
  .dashmain [data-dashpanel="capture"] h3 { font-size: 7pt; margin: 1mm 0 0.5mm; }
  .dashmain [data-dashpanel="capture"] .grid.metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0; }
  .dashmain [data-dashpanel="capture"] .tag { font-size: 5.5pt; padding: 0 3px; }
  /* The long tables to their first rows; the screen has them all. */
  .dashmain [data-dashpanel="sites"] table tr:nth-child(n + 14),
  .dashmain [data-dashpanel="capture"] table tr:nth-child(n + 9),
  .dashmain [data-dashpanel="ageing"] table tr:nth-child(n + 9),
  .dashmain [data-dashpanel="crew"] table tr:nth-child(n + 9),
  .dashmain [data-dashpanel="people"] table tr:nth-child(n + 10) { display: none !important; }

  /* --- the page ---------------------------------------------------------- */

  :root { --gutter: 0px; }

  body {
    background: #fff;
    color: #000;
    /* 9.5pt rather than 10.5. A register printed for a site visit ran to
       eleven pages of meters; the same list at this size is eight, and it is
       still comfortably above the point where a table stops being readable
       across a desk. Fewer pages is the whole objective — paper gets carried
       to sites on a motorcycle. */
    font-size: 9.5pt;
    line-height: 1.35;
  }

  main {
    max-width: none;
    padding: 0;
    margin: 0;
  }

  /* The h1 and its one-line explanation are the printed document's title
     block; on screen the first panel's own margin separates them, and with the
     chrome gone they ran into the first heading. */
  main > h1 { margin-bottom: 2px; }
  main > .lede { margin-bottom: 16px; }

  /* The strip is hidden below; without this its reserved 84px stays behind as
     dead space, which is enough to spill a last row onto a sheet of its own. */
  body.has-actions-queue main { padding-bottom: 0; }

  /* --- the letterhead ---------------------------------------------------- */

  .print-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    padding-bottom: 6px;
    margin-bottom: 14px;
    border-bottom: 1pt solid var(--ink);
  }
  .print-head img {
    width: 34mm;
    height: auto;
    /* The one image on the page that is not content. Without this Chrome drops
       it with the other backgrounds on a default print. */
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
  .print-head-name { font-size: 12pt; font-weight: 700; }
  .print-head-when {
    margin-left: auto;
    font-size: 9pt;
    color: var(--ink-78);
  }

  /* --- what does not go on paper ----------------------------------------- */

  /* The map goes on paper now. It used to be a WebGL canvas — tiles, panes
     and transforms, which printed as a smear — so the sheet carried only the
     table beside it. It is an SVG the server drew, which prints as cleanly as
     the text does, so it leads the sheet and the table follows it. The
     controls do not go: nobody can choose a scope on paper. */
  /* The table is the register region by region, and on paper it is not a
     panel that scrolls. */
  /* On the screen the map sits on a night sky, which on paper is a solid
     block of ink. The picture is drawn from presentation attributes, so the
     same shapes are re-coloured here for white paper: the ground white, the
     borders grey, the names and the scale bar dark. Only the direct child
     rect is the sky — a pole and a transformer are rects too, inside their
     links, and keep their own colours. */

  /* The chrome. `header.top` carries the brand, both nav tiers, the account
     menu, the drawer and its backdrop, so one rule takes the lot; the rest are
     injected into the body by scripts and are not inside it. */
  header.top,
  .nav-drawer,
  .nav-backdrop,
  .nav-button,
  .freeze-bar,
  .flash-overlay,
  .actions-queue,
  .install-bar,
  .scan-overlay,
  .site-footer {
    display: none !important;
  }

  /* Forms record an event; a printed page reports one. Three exceptions, and
     they are the reason this is not simply `form`: on five list pages a POST
     form WRAPS the whole table, so hiding it prints the page with no list on
     it; the filter bar is what tells the reader which slice of the register
     they are holding; and `.filters` alone was too generous — two POST entry forms borrow the class
     for its layout, and one of them puts a phone number on the sheet. A filter
     bar is a GET. `data-print-content` is the opt-in for the other direction —
     Access, Editing rights, Capture settings, the readiness checklist and the
     upload preview each hold the page's whole subject inside a save form, and
     without it those pages print as headings over blank paper. */
  form:not([data-select-form]):not([method="get"].filters):not([data-print-content]) {
    display: none !important;
  }
  /* A filter bar on paper: only what it was narrowed by. Six controls each
     reading "Any" took the top third of every printed list — and the whole
     bar could not simply go, because a filtered list that does not say what
     it was filtered to reads as the whole register. */
  .filters > div:not([data-set]):not(.filter-actions) { display: none; }
  .filters .filter-actions { display: none; }
  .filters { border: 0; padding: 0; margin: 0 0 6px; background: none; gap: 0 18px; }
  .filters > div[data-set] { display: flex; gap: 6px; align-items: baseline; }
  .filters > div[data-set] label { margin: 0; }
  /* What is left of a form that prints: its labels, its values and its state,
     without the controls for changing them. */
  form[data-print-content] button,
  form[data-print-content] input[type="submit"] { display: none !important; }

  /* A panel that exists only to act. The heading and the prose sit outside the
     form, so hiding the form alone leaves "Erase this customer" over an empty
     space. Marked in the markup: a `.panel:has(> form)` rule would also take
     Identity and KYC, which carries the record. */
  .print-hide { display: none !important; }
  /* The sheet is the page, so it carries no frame of its own and nothing
     breaks it across two sides. */
  .sheet { border: 0; padding: 0; max-width: none; page-break-inside: avoid; }
  /* The wiring sheet's tables, not the day sheet's grid of the same name:
     kept whole, the day sheet jumped to a second side and left the first
     one blank but for the letterhead. */
  .sheet-sign, .sheet-items, table.sheet-grid { page-break-inside: avoid; }

  /* The day sheet on one side: the queue strip five across at figure size,
     the site board with its breakdowns open and its links gone. */
  /* Two columns as on a desk: the phone block stacks the grid at 720px, and
     A4 less its margins is 718. */
  .sheet-grid { gap: 2mm 5mm; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
  .sheet-sites { grid-column: 1 / -1; }
  .sheet-top { grid-template-columns: minmax(0, 2.45fr) minmax(0, 1fr); gap: 3mm; }
  .queuestrip { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 2mm; }
  .queue { padding: 1mm 2mm; border: 0.3pt solid var(--ink-14); }
  .queue h2 { font-size: 8.5pt; margin: 0 0 1px; padding: 0; }
  .qstep { padding: 1px 0; gap: 0 4px; }
  .q-today { font-size: 11pt; line-height: 1.1; }
  .q-when { font-size: 6.5pt; }
  .q-stock { font-size: 7.5pt; line-height: 1.2; min-height: 0; }
  .q-track { height: 2px; margin-top: 1px; }
  .q-bar { height: 2px; }
  .siteboard { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 5mm; }
  .siteboard .site-detail { display: block; }
  .site-detail .footnote { display: none; }
  .site-item { break-inside: avoid; }
  .site-row { padding: 1px 0; }
  .breakdown td { padding: 0 4px !important; font-size: 8pt; }
  .estate .est-row, .defects .def-row { padding: 1px 0; gap: 4px; }
  .est-note { font-size: 7.5pt; line-height: 1.3; }
  .movelist .move { padding: 1px 0; }
  .move-base { font-size: 7.5pt; }
  .sheet-move .num { font-size: 10pt; }

  button,
  .btn,
  .row-actions,
  .actions,
  .pick,
  .pick-chosen,
  .portrait-actions,
  .filter-actions,
  .select-bar,
  .bulk-reason,
  .pager-links,
  .scopepick,
  .scope-search,
  .faq-search,
  .faq-jump,
  .fieldbar,
  input[type="file"],
  th.pick,
  td.pick {
    display: none !important;
  }

  /* The panels that exist to change the record rather than to state it. */
  #correct,
  #correct-customer,
  #correct-visit,
  #correct-premises,
  #no-customer {
    display: none !important;
  }
  /* On its own: a browser that cannot parse :has() discards the entire block it
     appears in, and this one used to be sharing with the five ids above. */
  details:has(form) { display: none !important; }

  /* One statement of "N in total · showing X–Y" is provenance; two is noise.
     The bottom one is kept rather than the top, because two lists include only
     the bottom — hiding it deleted their only count. */
  .pager-top { display: none !important; }

  /* --- what never goes on paper ------------------------------------------ */

  /* The portrait, the identity document and the signature. `kyc.verify` decides
     who may look at these on a screen; it decides nothing about a sheet left in
     a printer tray. Marked in the markup rather than matched on caption text,
     so rewording a caption cannot silently switch the protection off. */
  .head-portrait,
  .thumb-sensitive {
    display: none !important;
  }

  /* The decrypted document number. Revealing it on screen is already written
     to the audit trail as an export; paper is the export that has no trail. */
  .id-number { font-size: 0 !important; }
  .id-number::after {
    content: "withheld from print";
    font-family: var(--font);
    font-size: 9pt;
    color: var(--ink-65);
  }

  /* A password. The one on the handover page exists nowhere else — the platform
     never stores it — so a printed copy is the only copy, in a drawer. */
  .secret { font-size: 0 !important; }
  .secret::after {
    content: "withheld from print";
    font-family: var(--font);
    font-size: 9pt;
    color: var(--ink-65);
  }

  /* A control prints the value in it, not the value in its source. The server
     never sends a document number back into this box — but an officer who has
     just typed one and presses Cmd-P puts it on paper, with no permission
     check anywhere behind it. */
  input[name="id_document_number"] { visibility: hidden !important; }

  /* --- one side of paper ------------------------------------------------- */

  /* The card grid (reports, and the pages built like it): two to a row on
     paper and their titles at heading size, not display size. Stacked and
     spaced for a screen, six cards took a side and a half. */
  .grid.cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3mm; margin: 3mm 0 4mm; background: none; border: 0; }
  .grid.cards > .panel { padding: 2mm 3mm; border: 0.3pt solid var(--ink-14); break-inside: avoid; }
  .grid.cards > .panel > h2 { font-size: 10.5pt; margin: 0 0 2px; }
  .grid.cards > .panel > h2 .tag { font-size: 7pt; }
  .grid.cards .kv { grid-template-columns: 22mm minmax(0, 1fr); font-size: 8pt; }
  .grid.cards .kv dt, .grid.cards .kv dd { padding: 1px 4px 1px 0; }
  .grid.cards p { margin: 2px 0; font-size: 8pt; }

  /* A record page is carried to a site as one sheet. Its histories (vending,
     audit, head-end events) are the one thing on it that grows without
     limit, so on paper a table marked `print-trim` shows its latest ten
     rows and says how many there are; the screen has them all. */
  table.print-trim tr:nth-child(n + 12) { display: none !important; }
  .print-more { display: block; font-size: 8pt; color: var(--ink-65); margin: 2px 0 0; }
  /* A photograph on a record page is evidence at a glance, not the picture:
     two of them at the page's width took half the sheet, and an empty frame
     took the same half for the words "no photograph". */
  .photo-pair { grid-template-columns: repeat(2, 58mm); gap: 6mm; }
  .photo-row .frame, .photo-pair .frame { max-width: 58mm; }
  .frame.empty { aspect-ratio: auto; height: auto; padding: 3px 6px; font-size: 8pt; }

  /* --- structure ---------------------------------------------------------- */

  /* The clipping. `.panel` is overflow-x: auto on 164 elements. */
  .panel, .scroll { overflow: visible !important; }

  .panel { margin-bottom: 12px; }
  /* Keep the small record panels whole. A panel that wraps a long table must be
     allowed to break, or a page of half-height panels prints one to a sheet
     with white space between. */
  .panel:not(:has(table)):not(:has(.contact-sheet)) { break-inside: avoid-page; }
  /* A contact sheet runs to several pages by design; each picture stays whole,
     the sheet does not. Three across on paper rather than the screen's four,
     so a caption is still readable at arm's length. */
  .contact-sheet { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
  .contact-sheet figure { break-inside: avoid; }
  .sheet-pick { display: none !important; }
  /* A record's photographs on paper: small and four across, the way the
     status report prints them. At screen size two rooms filled a page and
     pushed the record's last panel onto a sheet of its own. */
  .photo-wall { columns: 4; column-gap: 4mm; }
  .photo-wall figure { margin: 0 0 3mm; }
  .photo-wall figcaption { font-size: 7pt; margin-top: 1mm; }
  .photo-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 4mm; }
  .photo-row figure { margin: 0; }
  .photo-row .frame, .photo-wall .frame { padding: 2px; }
  .photo-edit { display: none !important; }

  h1, h2, h3, .panel > h2, .form-legend, .section-head {
    break-after: avoid-page;
  }

  /* Undoing the mobile block, which fires at paper width. */
  table {
    display: table !important;
    width: 100% !important;
    table-layout: fixed;
    overflow: visible !important;
    white-space: normal !important;
  }
  .panel table { min-width: 0 !important; }
  /* A register table is scanned, not read. It carries its own size: one long
     repeated cell — a certificate reference identical on every row — was
     wrapping to three lines and taking the whole row with it, on every row of
     a nine-page list. */
  table { font-size: 8.5pt; }
  th, td {
    white-space: normal !important;
    overflow-wrap: anywhere;
    padding: 2px 5px;
    line-height: 1.2;
    vertical-align: top;
  }
  /* Except an identifier read off a wall and typed back in, which must never
     break across two lines. Not "prefer not to" — never: EN-OKO-000001 broken
     after the hyphen is two lines for one number, and with a customer number
     and a meter serial in the same row that is a three-line row on every row
     of a nine-page register. */
  th .mono, td .mono, .mono { overflow-wrap: normal; white-space: nowrap; }
  /* And a heading is one or two words. Breaking CUSTOMER into CUSTOME/R adds a
     line to the head of every table for nothing. */
  th { overflow-wrap: normal; hyphens: none; }
  /* No template writes a <thead>, so this only helps where one is added later —
     but a row that splits across the fold is the common damage, and that it
     does prevent. */
  thead { display: table-header-group; }
  tr { break-inside: avoid; }

  /* The two-column record pages fit on paper; the mobile block stacks them. */
  .grid.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* Unless there is a table in it. Half a sheet divided equally between eight
     columns breaks a figure mid-number and overflows a customer number that
     must not break at all. */
  /* A table of three columns or fewer (appliances: type, quantity, kW) sits
     in half a sheet without that, and keeping the pair side by side is what
     holds a registration to one side. */
  .grid.two:has(table th:nth-child(4)), .grid.three:has(table th:nth-child(4)) {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The key/value ledger every record page is built from, likewise. */
  dl.kv { grid-template-columns: 150px minmax(0, 1fr); }
  dl.kv dt {
    border-bottom: 1px solid var(--ink-14);
    padding-bottom: 11px;
    break-after: avoid-page;
  }

  /* --- things drawn with a background ------------------------------------- */

  /* Ruled frames whose rules are gaps with a colour showing through. Without
     backgrounds the cells run together into a block of text. */
  .grid.metrics, .grid.cards, .tiles {
    gap: 0;
    background: none;
  }
  /* A summary, not the document. Eight tiles at screen proportions opened
     every printed page with a band of very large numerals and pushed the list
     they summarise onto the second sheet. As many across as fit, at the size
     of a figure in a table rather than a figure on a wall. */
  .grid.metrics, .grid.metrics.tight, .metrics {
    /* A grid, not the screen's wrapping flex row: as many across as fit. */
    display: grid;
    gap: 0;
    grid-template-columns: repeat(auto-fit, minmax(min(112px, 100%), 1fr));
  }
  /* The chips choose what the chart shows; on paper it shows what was chosen. */
  .chart-chips { display: none; }
  .chart-frame canvas { max-height: 45mm; }
  .notice { padding: 2mm 3mm; margin: 0 0 3mm; font-size: 8.5pt; }
  .metric, .grid.cards > .panel, .tiles > * {
    background: none;
    border: 1px solid var(--ink-14);
    break-inside: avoid;
    /* Tighter than the screen's. A row of tiles is a summary, not the
       document; on paper it was taking a third of the first page before the
       list it summarises had started. */
    padding: 4px 6px;
  }
  .metric .n { font-size: 12pt; line-height: 1.05; font-weight: 700; }
  .metric .k { font-size: 7pt; letter-spacing: .04em; }
  .metric .sub { font-size: 7pt; line-height: 1.25; }

  /* Status words. Every tag is ink on a tint, and the two lamps are white on a
     fill — which prints as nothing where a word was. */
  .tag {
    background: none !important;
    border: 1px solid var(--ink-14);
    /* An inline-flex box sets its own height, and in a table cell that made a
       one-word status thirty-eight pixels tall — three times the line it sits
       on. Every row of a nine-page register paid for it. Inline, it is a word
       with a box drawn round it. */
    display: inline;
    padding: 0 4px;
    font-size: 8pt;
    line-height: 1.2;
  }
  .tag.net-on, .tag.net-off {
    color: var(--ink);
    font-weight: 700;
  }

  /* The one chip that was chosen is the one chip that would disappear. */
  .scope-chip.on {
    background: none;
    color: var(--ink);
    border: 2px solid var(--ink);
    font-weight: 700;
  }
  .scope-chip.on .mono, .scope-chip.on .scope-own { color: var(--ink-60); }
  .pager-here {
    background: none;
    color: var(--ink);
    border-color: var(--ink);
    font-weight: 700;
  }

  /* The sign-in page, which is a blue field with white type on it. */
  body.signin { background: none; }
  .signin-split { min-height: 0; grid-template-columns: minmax(0, 1fr); }
  .signin-brand, .signin-brand h1,
  .signin-brand .statement, .signin-brand .ndpa { color: var(--ink); }
  .signin-brand img { display: none; }
  .poster { background: none; color: var(--ink); border-top: 2px solid var(--ink-14); }
  .poster h2 { color: var(--ink); }

  /* Form controls print their values. Flattened, so a filter that was set reads
     as words and an empty box is simply absent, rather than every page carrying
     a row of empty rectangles. */
  input, select, textarea {
    background: none;
    border: 0;
    padding: 0;
    color: var(--ink);
    appearance: none;
    -webkit-appearance: none;
    /* The ≤860px block sets 16px to stop iOS zooming a focused field. It fires
       at paper width too, and a printed value larger than the prose around it
       reads as emphasis nobody meant. */
    font-size: inherit;
  }
  /* A tick box flattened to nothing takes its answer with it, and the pages
     that print a form on purpose — the readiness checklist, the permission
     grids — are exactly the ones where the answer IS the page. Drawn as a box
     with a mark in it, which is what a checklist looks like on paper anyway. */
  input[type="checkbox"], input[type="radio"] {
    display: inline-block;
    width: 9pt;
    height: 9pt;
    border: 1px solid var(--ink) !important;
    vertical-align: -1px;
    margin-right: 4px;
  }
  input[type="radio"] { border-radius: 50%; }
  input[type="checkbox"]:checked::after,
  input[type="radio"]:checked::after {
    content: "\2713";
    display: block;
    font-size: 8pt;
    line-height: 8pt;
    text-align: center;
  }

  /* The date box draws a calendar button next to its value. On paper it is a
     small grey square beside a date, and nobody can press it. */
  input[type="date"]::-webkit-calendar-picker-indicator { display: none; }
  /* A filter box nobody typed in prints as nothing rather than as an empty
     rule. The date box has no equivalent state to match on, so an unset date
     still shows its dd/mm/yyyy — which reads as "not set", and is the price of
     never losing a filter that WAS set. The bar is the only thing on a printed
     list that says which slice of the register it is. */
  input:placeholder-shown { visibility: hidden; }

  /* --- legibility --------------------------------------------------------- */

  /* Screen-grey at 11px is the first thing a tired cartridge loses, and these
     carry qualifiers that change what the number above them means: "±5 m",
     "taken at the meter", "read from the photograph". */
  .hint, .sub, .footnote, th,
  .metric .k, dl.kv dt, .faq-precise .k, .pager-count {
    color: var(--ink-78);
  }

  /* Held to one line on screen by an ellipsis this block's `white-space: normal`
     undoes — after which one row of the audit trail becomes twenty lines of raw
     JSON in a column an inch wide. Who, when, what and the note are the record;
     the diff is a screen affordance. */
  .audit-change { display: none !important; }

  /* A photograph the height of the sheet is cut in half by the fold. */
  img { max-height: 18cm; }
  figure, .photo-pair, .metric, .notice { break-inside: avoid; }

  /* Links go grey-blue and say nothing about where they went. Almost every one
     is internal navigation, so only the external ones — the map position under
     a meter — are worth their address in ink. Deliberately not the WhatsApp
     links: their href carries the customer's phone number, and printing it
     would put the number on the sheet a second time. */
  a { color: inherit; }
  a[href^="https://www.google.com/maps"]::after {
    content: " (" attr(href) ")";
    font-size: 8pt;
    color: var(--ink-65);
    overflow-wrap: anywhere;
    /* On its own line, wrapped: inside a monospaced cell the address ran
       across the column beside it. */
    display: block;
    white-space: normal;
    font-family: var(--font);
  }

  /* A collapsed disclosure prints as its summary alone, so the FAQ came out as
     a page of questions with no answers. Only the ones that hold prose — the
     disclosures that wrap forms are hidden above. */
  details:not(:has(form)) > *:not(summary) { display: block !important; }
  details::details-content {
    content-visibility: visible !important;
    block-size: auto !important;
  }
  details > summary { font-weight: 700; }

  /* The filter box hides entries as you type. The comment where that class is
     defined says a printed page shows everything; this is what makes that
     true. */
  .faq-hidden { display: revert !important; }

  /* A drawn area frames the page itself; the dashes are not
     needed on it. */
  .netmap-area { display: none; }

  /* The sheet's own name, small and out of the way of the
     heading, but on every page of it. */
  .print-head-ref { display: block; font-size: 8pt; color: #555; }

  /* --- a record on paper: two pages at most, one where it can be ---------
     A meter's page printed to four sheets: a 60px logo, a heading the size
     of a poster, one field per row with 11px of air above and below, and a
     section heading kept with its table so the table started a new sheet.
     The same record in two columns of key-value pairs, at 9pt, with the
     head cut to a strip, is one sheet. Paper gets carried to sites. */
  body { font-size: 9pt; line-height: 1.25; }
  .print-head { padding-bottom: 3px; margin-bottom: 6px; gap: 8px; }
  .print-head img { width: 22mm; }
  .print-head-name { font-size: 10pt; }
  .print-head-when, .print-head-ref { font-size: 7.5pt; }
  main > h1, h1 { font-size: 15pt; margin: 0 0 1px; line-height: 1.1; }
  .lede { font-size: 9pt; margin: 2px 0 6px; }
  h2, .panel > h2, .form-legend, .section-head {
    font-size: 10.5pt; margin: 7px 0 3px; padding-bottom: 2px;
  }
  h3 { font-size: 9.5pt; margin: 5px 0 2px; }
  .panel { margin-bottom: 5px; padding: 0; border: 0; }
  /* The record itself: two pairs to a row. A dt in small capitals, a dd
     beside it, the next pair across the sheet. */
  dl.kv {
    grid-template-columns: 30mm minmax(0, 1fr) 30mm minmax(0, 1fr);
    column-gap: 5mm;
  }
  /* Already in half the sheet (two panels side by side), one pair to a row:
     four columns in 90mm wrapped every value to a word a line. */
  .grid.two:not(:has(table th:nth-child(4))) dl.kv,
  .grid.three:not(:has(table th:nth-child(4))) dl.kv { grid-template-columns: 26mm minmax(0, 1fr); }
  dl.kv dt { font-size: 7pt; padding: 2px 0; letter-spacing: .05em; }
  dl.kv dd { font-size: 9pt; padding: 2px 0; border-bottom: 0.3pt solid var(--ink-14); }
  /* A blank is a blank; it need not take a row. */
  dl.kv dd:has(> .hint:only-child) { color: var(--ink-55); }
  table { font-size: 8.5pt; line-height: 1.25; }
  th, td { padding: 1px 4px !important; }
  th { font-size: 7pt; }
  .grid.metrics { gap: 4px; }
  .metric { padding: 4px 6px; }
  .metric .k { font-size: 7pt; }
  .metric .n { font-size: 13pt; line-height: 1.1; }
  .metric .sub { font-size: 7pt; }
  .photo-row figure, .thumbs img, .frame img { max-height: 36mm; width: auto; }
  .thumbs { gap: 4mm; }
  .thumbs figure { margin: 0; }
  /* An empty frame says so in one line; it need not be the size of the
     photograph it does not hold. */
  .photo-row figure > *:not(img):not(figcaption) { max-height: 14mm; min-height: 0; }
  .photo-row { gap: 6px; }
  figcaption { font-size: 7.5pt; }
  .hint, .footnote, .sub { font-size: 7.5pt; }
  /* A section may split across the sheet's edge rather than start a new
     sheet for the sake of staying whole; only a heading stays with what
     follows it. */
  .panel:not(:has(table)):not(:has(.contact-sheet)) { break-inside: auto; }
  .grid.two, .grid.three { display: grid; grid-template-columns: 1fr 1fr; gap: 0 6mm; }
}

/* The selector at the head of the network audit's capture pages: one list of the kinds of record. */
.audit-kind { display: grid; gap: 4px; margin: 0 0 16px; max-width: 440px; }
.audit-kind label { font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-65); }
.audit-kind select { width: 100%; min-height: 52px; padding: 10px 14px; font-size: 18px; font-weight: 700;
                     border: 2px solid var(--accent); background: var(--bg); color: var(--ink); }

/* The customer number box: the prefix the site writes, the digits the officer types. */
.cuid-box { display: flex; align-items: center; gap: 0; }
.cuid-box .cuid-prefix { padding: 0 6px 0 0; white-space: nowrap; color: var(--ink-60); }
.cuid-box input { flex: 1; min-width: 0; }
/* A duplicate number being corrected: only the number and the house photograph
   are live, everything else on the record stands as it was captured. */
#capture.number-only :is(input, select, textarea, button, .pick) { pointer-events: none; opacity: .5; }
#capture.number-only #cuid_seq, #capture.number-only #need-cuid,
#capture.number-only .pick[data-pick-for="structure_photo"],
#capture.number-only .pick[data-pick-for="structure_photo"] *,
#capture.number-only button[type="submit"] { pointer-events: auto; opacity: 1; }

/* The Project and Site bar the layout draws on every register page: a strip
   above the page's own filters, not a panel of its own. */
.scope-bar { padding: 10px 0; margin-bottom: 8px; border-bottom: 1px solid var(--divider); }
.scope-bar > div { min-width: 200px; }

/* ESG governance: the consent read as the form reads, the report's figure grid, the
   tree lines typed in a row. */
.consent-form p { max-width: 70ch; }
.consent-form .affirmed { list-style: none; padding: 0; margin: 8px 0 0; }
.consent-form .affirmed li { margin: 0 0 8px; max-width: 70ch; }
img.signature { max-width: 320px; max-height: 110px; display: block; margin-top: 6px; border-bottom: 1px solid var(--divider); }
.report-figures td.num input, .report-figures td.num select { width: 7em; text-align: right; }
.tree-lines input, .tree-lines select { min-width: 5em; }
.tree-lines td { padding: 4px 6px; }
dl.kv.totals { margin-top: 12px; }
.tree-pos { white-space: nowrap; }
.tree-pos input { width: 7em; margin-right: 4px; }
.tree-lines .pick-bare { min-width: 150px; }
img.thumb { max-width: 80px; max-height: 60px; display: block; }
.position-banner { background: var(--accent-100); color: var(--accent-800); border-left: 4px solid var(--accent-800);
  padding: 12px 14px; margin: 0 0 14px; font-weight: 600; }
.position-line { font-size: 14px; color: var(--ink); }
[data-position] .actions { align-items: center; gap: 12px; }
.update-strip { position: sticky; top: 0; z-index: 30; background: var(--accent-800); color: #fff; padding: 10px 16px;
  font-size: 14px; display: flex; gap: 12px; align-items: center; justify-content: space-between; }
.update-strip button { color: #fff; border-color: #fff; }
.gate-strip { position: sticky; top: 0; z-index: 30; background: var(--accent-100); color: var(--accent-800);
  border-bottom: 3px solid var(--accent-800); padding: 10px 16px; font-size: 14px; line-height: 1.45; }
.gate-strip button { margin-left: 8px; }
.position-missing { border-left: 4px solid var(--accent-800); }
.legend-sweep { display: inline-flex; gap: 4px; margin-right: 8px; }
.legend-all { font: inherit; font-size: 12px; font-weight: 600; color: var(--accent-800); background: var(--surface);
  border: 1px solid var(--divider); padding: 3px 9px; cursor: pointer; }
.legend-all:hover, .legend-all:focus-visible { background: var(--accent-100); }
