/* The signed-in app shell.
   Implements the design handoff "defib.je signed-in app" (2026-09-21), distilled in
   docs/design/app-shell.md. Everything here is scoped under .shell so the public map
   at #/ keeps its own chrome untouched — the handoff puts the map out of scope and
   this file honours that boundary.

   Three rules run through the whole file and are worth knowing before you edit it:

   1. GREEN IS NOT FURNITURE. Green, amber and red carry status — in service,
      attention, offline. The brand colour is also green, so chrome never uses a
      saturated green fill, or the user reads "everything is fine" from the paint.
      Brand green is for primary buttons, active nav, links, section labels and the
      greeting panel's corner brackets. Nothing else.

   2. ROLE COLOUR IS A THIRD SYSTEM and appears in exactly three places: the role
      chip, the active nav item's inset bar, and a soft shadow under the top bar and
      off the rail. It never tints content. --accent carries it.

   3. NO ICON-ONLY CONTROLS, 44px minimum target, body text at 4.5:1 — including on
      the amber and red row tints, which is the case that catches people out. These
      are volunteers, outdoors, one-handed, in sun or rain. */

.shell {
  /* Status and brand */
  --green: #1a9640;
  --green-dark: #157a33;
  --green-light: #edf9f1;
  --green-btn-border: #178a3a;
  --green-pill-border: #cbe7d5;
  --amber-light: #fff8e6;
  --amber-row: #fffdf7;
  --amber-ink: #92400e;
  --amber-border: #e3c98f;
  --red: #e11d48;
  --red-ink: #bd1740;           /* small red text; --red fails 4.5:1 below ~14px */
  --red-row: #fff1f3;

  /* Neutrals. Faintly green-tinted on purpose — a pure grey next to these greens
     reads cold and slightly blue. */
  --s-ink: #1c211e;
  --s-ink-2: #4a524d;
  --s-muted: #636a66;           /* clears 4.5:1 on the amber and red row tints */
  --stamp: #5f6662;

  /* Surfaces */
  --s-bg: #fff;
  --page: #f2f4f3;
  --content: #f6f7f6;           /* content tint: white panels must lift off the page */
  --rail-bg: #eef1ef;
  --strip: #fafbfa;             /* column headers and panel footers */

  /* Lines */
  --frame: #c3ccc6;
  --s-line: #dfe4e1;            /* panel and chrome */
  --row-line: #eaeeec;
  --sec-line: #e0e6e2;
  --greet-line: #e4e9e6;
  --fig-line: #d7ded9;
  --btn-2-line: #a9b3ad;
  --btn-quiet-line: #d3dad6;
  --field-line: #cfd6d2;
  --rail-rule: #dde3df;

  /* Role accent. Overwritten per role on .shell; grey is "no role yet". */
  --accent: #5f6662;
  --accent-tint: rgba(95, 102, 98, .04);
  --accent-edge: rgba(95, 102, 98, .12);
  --accent-shadow: rgba(95, 102, 98, .34);

  --ui: 'Instrument Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --shadow-1: 0 1px 2px rgba(17, 17, 17, .18);
  --shadow-2: 0 1px 1px rgba(17, 17, 17, .07);
  --shadow-field: inset 0 1px 2px rgba(17, 17, 17, .05);
}

.shell[data-role="guardian"]  { --accent: #157a33; --accent-tint: rgba(21,122,51,.04);  --accent-edge: rgba(21,122,51,.14);  --accent-shadow: rgba(21,122,51,.34); }
.shell[data-role="custodian"] { --accent: #185fa5; --accent-tint: rgba(24,95,165,.04);  --accent-edge: rgba(24,95,165,.14);  --accent-shadow: rgba(24,95,165,.34); }
.shell[data-role="admin"]     { --accent: #5a4b9c; --accent-tint: rgba(90,75,156,.05);  --accent-edge: rgba(90,75,156,.14);  --accent-shadow: rgba(90,75,156,.34); }
/* Owner was not in the vendor's four-role table. It gets its own accent rather than
   borrowing the custodian's: a custodian's register and an owner's estate are
   different datasets, not one filtered two ways (D-055). Teal sits away from every
   status colour, and the role word carries the meaning in any case. */
.shell[data-role="owner"]     { --accent: #0f6f78; --accent-tint: rgba(15,111,120,.05); --accent-edge: rgba(15,111,120,.14); --accent-shadow: rgba(15,111,120,.34); }
/* Maintainer. Q-019 said every remaining candidate collided with something — amber and
   red are status, green is the brand, blue and violet and teal are taken. It is violet,
   and the collision is with the platform administrator's #5a4b9c rather than with a
   status colour, which is the cheapest of the five: the two are never on one screen and
   never on one account. The value is not new — session.js has carried #7b52ab as the
   maintainer's context colour since the contexts were written; this makes the chrome
   agree with it instead of showing "you hold no role here". */
.shell[data-role="maintainer"] { --accent: #7b52ab; --accent-tint: rgba(123,82,171,.05); --accent-edge: rgba(123,82,171,.15); --accent-shadow: rgba(123,82,171,.34); }

/* ── Frame ────────────────────────────────────────────────────────────────── */

body.shell-page { background: var(--page); }

/*
 * The map texture, in the margin either side of the frame and nowhere else.
 *
 * The handoff specified the page as #f2f4f3 PLUS a map layer behind the frame;
 * when the bundle was distilled into docs/design/app-shell.md the colour came
 * across and the texture did not, so it was never built.
 *
 * BEHIND THE FRAME ONLY. The frame is opaque, so the texture is visible in the
 * strip either side and never under a word of content. Fixed rather than
 * absolute: it is wallpaper, not part of the page, and nothing about it should
 * move or repaint when the page scrolls. pointer-events:none so it can never
 * take a click meant for something else.
 *
 * NOT ON A PHONE. The frame is 1340px wide plus its border, so below about
 * 1342px there is no margin and nothing to draw in. It loads from 1400px, which
 * is the first width where the strip is 29px either side and reads as a texture
 * rather than a hairline. A guardian at a cabinet never fetches it.
 *
 * The file is greyscaled and quantised on the way in rather than in the browser,
 * so this needs opacity and a touch of tone, not a grayscale() filter — a filter
 * on a full-viewport fixed layer is work every frame for a picture that never
 * changes.
 *
 * TEMPORARY, by the vendor's own account: the source is a screenshot standing in
 * for a pale vector map, and real map tiles would bring their own attribution
 * requirement with them.
 */
@media (min-width: 1400px) {
  body.shell-page::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image: url("/assets/map-bg.png");
    background-repeat: repeat;
    background-size: 1180px auto;
    /* The handoff says .18, over a COLOUR source passed through grayscale(.85) —
       which leaves 15% of the colour in and mutes the whole thing. This asset is
       fully greyscaled and quantised on the way in, so it reads stronger at the
       same number; .15 lands where .18 landed in the handoff. Looked at both at
       1800px: at .18 the street names beside the frame start reading as words. */
    opacity: .15;
    filter: contrast(.9) brightness(1.05);
  }
}

.shell {
  /* Above the texture, and opaque, which is what keeps it out of the content. */
  position: relative;
  z-index: 1;
  max-width: 1340px;
  margin: 0 auto;
  min-height: 100vh;
  background: var(--s-bg);
  border: 1px solid var(--frame);
  display: flex;
  flex-direction: column;
  font-family: var(--ui);
  color: var(--s-ink);
  font-size: 15px;
  line-height: 1.45;
}
/* Weights stop at 600 throughout. 700 read as shouty. */
.shell strong, .shell b { font-weight: 600; }
.shell :focus-visible { outline: 2px solid var(--green-dark); outline-offset: 2px; }
/* Motion is colour and opacity only, 120–150ms. Nothing moves. */
.shell a, .shell button, .shell .srow { transition: background-color .13s, color .13s, border-color .13s; }

.shell-body { flex: 1; display: flex; min-height: 0; }

/* ── Top bar ──────────────────────────────────────────────────────────────── */

.shell-top {
  flex: 0 0 66px;
  height: 66px;
  background: var(--s-bg);
  border-bottom: 1px solid #e6eae8;
  box-shadow: 0 10px 18px -12px var(--accent-shadow);
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 0 24px;
  position: relative;
  z-index: 2;
}
/* Three zones. The chip is centred because it answers "who am I being right now",
   which is the question a volunteer with two roles actually asks. */
.shell-top .zone-l { flex: 1; display: flex; align-items: center; min-width: 0; }
.shell-top .zone-c { flex: 0 0 auto; }
.shell-top .zone-r { flex: 1; display: flex; align-items: center; justify-content: flex-end; min-width: 0; }
.shell-logo img { height: 34px; width: auto; display: block; }

.rolechip {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 38px;
  padding: 0 15px;
  border-radius: 7px;
  background: var(--accent-tint);
  border: 1px solid var(--accent-edge);
  white-space: nowrap;
}
.rolechip .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex: 0 0 auto; }
.rolechip .role {
  font: 500 9.5px/1 var(--mono);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
}
.rolechip .ctx { font-size: 14.5px; font-weight: 600; color: var(--s-ink); }

/* The search field the user reaches for. Its label is a word, not a magnifying
   glass — no icon-only controls anywhere. */
.shell-find {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 284px;
  max-width: 100%;
  height: 38px;
  padding: 0 13px;
  background: var(--s-bg);
  border: 1px solid var(--field-line);
  border-radius: 7px;
  box-shadow: var(--shadow-field);
}
.shell-find label {
  font: 400 11px/1 var(--mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--stamp);
  flex: 0 0 auto;
}
.shell-find input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: none;
  font: 400 14.5px/1 var(--ui);
  color: var(--s-ink);
}
.shell-find input::placeholder { color: var(--stamp); }   /* not lighter: this is the field they aim at */

/* ── Nav rail ─────────────────────────────────────────────────────────────── */

.shell-rail {
  flex: 0 0 196px;
  width: 196px;
  background: var(--rail-bg);
  padding: 12px 0 10px;
  display: flex;
  flex-direction: column;
  box-shadow: inset -1px 0 0 var(--s-line), 10px 0 18px -12px var(--accent-shadow);
  position: relative;
  /* ASM: the top bar's shadow must not fall across the rail — it stops at the rail's
     right edge, everywhere on the site. The rail paints OVER the bar's shadow (the bar
     is z-index 2), rather than the bar drawing round it, so there is one rule here and
     no special case per screen. */
  z-index: 3;
}
.shell-rail a, .shell-rail button.navitem {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 48px;
  padding: 0 18px;
  font: 500 15.5px/1 var(--ui);
  color: var(--s-ink);
  text-decoration: none;
  border: 0;
  background: none;
  width: 100%;
  cursor: pointer;
  text-align: left;
}
.shell-rail a:hover { background: #e6eae8; text-decoration: none; }
.shell-rail a.on {
  background: var(--s-bg);
  color: var(--accent);
  font-weight: 600;
  box-shadow: inset 4px 0 0 var(--accent);
}
.shell-rail .navlabel { flex: 1; min-width: 0; }
.navbadge {
  flex: 0 0 auto;
  min-width: 22px;
  height: 21px;
  padding: 0 6px;
  border-radius: 4px;
  font: 600 12.5px/21px var(--ui);
  text-align: center;
  background: var(--s-bg);
  color: var(--s-ink-2);
}
.navbadge.amber { background: var(--amber-light); color: var(--amber-ink); }
.navbadge.red   { background: var(--red-row);     color: var(--red-ink); }

.railfoot { margin-top: auto; }
.railstamp { font: 400 10px/1.6 var(--mono); color: var(--stamp); padding: 0 18px 10px; }
/* The page background is a greyscaled crop of OpenStreetMap tiles, so it is
   credited. Quiet, but always present rather than only at the widths that draw it —
   an attribution that comes and goes is a worse answer than one nobody notices. The
   public map credits its own tiles through Leaflet's own control, so this line says
   "Background" and claims nothing else; the two never appear together, because the
   public map does not wear this shell. */
.railcredit { font: 400 9.5px/1.5 var(--mono); color: var(--stamp); padding: 0 18px 10px; }
.shell-rail .railcredit a {
  display: inline; height: auto; padding: 0; margin: 0; border-radius: 0;
  background: none; box-shadow: none; font: inherit; color: inherit;
  text-decoration: underline; text-underline-offset: 2px;
}
.railacct {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 54px;
  padding: 0 18px;
  border-top: 1px solid var(--rail-rule);
}
.railacct .tile {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  border-radius: 5px;
  background: var(--s-bg);
  border: 1px solid var(--s-line);
  display: grid; place-items: center;
  font: 600 11.5px/1 var(--ui);
  color: var(--s-ink-2);
}
.railacct .who { min-width: 0; flex: 1; display: block; }
/* The name appears here and nowhere else. It is not repeated in the top bar. Both
   lines are one line each: a wrapped name turns a 54px block into a 70px one and
   pushes Sign out off the fold on a short screen. */
.railacct .nm, .railacct .em {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.railacct .nm { font-size: 13.5px; font-weight: 600; line-height: 1.15; }
.railacct .em { font-size: 11.5px; color: var(--s-muted); line-height: 1.3; }
.railsignout {
  display: flex; align-items: center;
  height: 48px; padding: 0 18px;
  border-top: 1px solid var(--rail-rule);
  font: 500 15px/1 var(--ui);
  color: var(--s-ink-2);
  background: none; border-left: 0; border-right: 0; border-bottom: 0;
  width: 100%; cursor: pointer; text-align: left;
}
.railsignout:hover { background: #e6eae8; }

/* ── Content ──────────────────────────────────────────────────────────────── */

.shell-main {
  flex: 1;
  min-width: 0;
  background: var(--content);
  padding: 22px 24px 28px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

/* Views that have not been redesigned yet still mount in here. .page carries its own
   width and padding for the public surface, which would double up inside the frame. */
.shell-main .page { max-width: none; margin: 0; padding: 0; }

/* ── Greeting panel ───────────────────────────────────────────────────────── */

.greet {
  position: relative;
  background: var(--s-bg);
  border: 1px solid var(--greet-line);
  padding: 23px 26px;
  display: flex;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
}
.greet .corner { position: absolute; width: 22px; height: 22px; pointer-events: none; }
.greet .corner.tl { top: -1px; left: -1px;  border-top: 2px solid var(--green); border-left: 2px solid var(--green); }
.greet .corner.tr { top: -1px; right: -1px; border-top: 2px solid var(--green); border-right: 2px solid var(--green); }
.greet .corner.bl { bottom: -1px; left: -1px;  border-bottom: 2px solid var(--green); border-left: 2px solid var(--green); }
.greet .corner.br { bottom: -1px; right: -1px; border-bottom: 2px solid var(--green); border-right: 2px solid var(--green); }
.greet-say { flex: 1; min-width: 280px; }
.greet h1 {
  font-size: 30px; font-weight: 500; letter-spacing: -.02em; line-height: 1.05;
  margin: 0 0 10px;
}
.greet p { font-size: 16px; color: var(--s-ink-2); margin: 0; max-width: 560px; }
.greet p strong { color: var(--s-ink); font-weight: 600; }
.greet-act { display: flex; align-items: center; gap: 18px; margin-top: 18px; flex-wrap: wrap; }

/* Headline figures, separated by short rules — not full-height cell walls. */
.figs { display: flex; align-items: center; flex: 0 0 auto; }
.fig { padding: 0 20px; text-align: center; }
.fig:last-child { padding-right: 4px; }
.fig + .fig { border-left: 1px solid var(--fig-line); }
.fign {
  font-size: 29px; font-weight: 500; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.figc {
  font: 400 9.5px/1 var(--mono);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--s-muted); margin-top: 7px; white-space: nowrap;
}
.fig.good .fign { color: var(--green-dark); }
.fig.warn .fign, .fig.warn .figc { color: var(--amber-ink); }
.fig.bad  .fign { color: var(--red); }
.fig.bad  .figc { color: var(--red-ink); }

/* ── Section header: a label, a rule, an optional link ────────────────────── */

.sec { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.sechead { display: flex; align-items: center; gap: 14px; }
.seclabel {
  font: 500 11px/1 var(--mono);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--green-dark); white-space: nowrap;
}
.secrule { flex: 1; height: 1px; background: var(--sec-line); }
.seclink, .secnote {
  font: 400 11px/1 var(--mono);
  letter-spacing: .08em; white-space: nowrap;
}
.seclink { color: var(--green-dark); text-decoration: none; }
.seclink:hover { color: var(--green); text-decoration: underline; }
.secnote { color: var(--s-muted); }

/* ── Panels, tables, rows ─────────────────────────────────────────────────── */

.panel { background: var(--s-bg); border: 1px solid var(--s-line); }
.panel-pad { padding: 16px 18px; }

/* Every table starts with a mono column header. If a panel repeats a grid, it
   repeats the header — a row grid with no header is a puzzle. */
.colhead {
  padding: 10px 18px;
  background: var(--strip);
  border-bottom: 1px solid var(--s-line);
  font: 400 10px/1 var(--mono);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--stamp);
}
.srow { padding: 13px 18px; border-bottom: 1px solid var(--row-line); align-items: center; }
.srow:last-child { border-bottom: 0; }
.colhead, .srow { display: grid; gap: 16px; }

/* Two row grids are in use. Where a column carries the thing the row exists to
   show, it gets the width (docs/design/app-shell.md, amendment 2026-09-21). */
.grid-site    { grid-template-columns: 64px minmax(0,1.6fr) minmax(0,1.15fr) 150px 176px; }
.grid-site-np { grid-template-columns: minmax(0,1.6fr) minmax(0,1.15fr) 150px 176px; }
.grid-org     { grid-template-columns: minmax(0,2.1fr) 150px 180px 160px; gap: 18px; }
/* A half-width panel is not a narrow full-width one. The four-column organisation grid
   collides with itself at ~530px: the header labels overlap and the name wraps into
   the coverage bar. Two columns, and the width goes to the name. */
.grid-half    { grid-template-columns: minmax(0,1.4fr) minmax(0,1fr); gap: 14px; }
.grid-half .num { font-size: 13.5px; color: var(--s-muted); }
.grid-half .num strong { color: var(--s-ink-2); }

/* Row tints carry status and nothing else. This is what guides the eye down a
   list — never tint a row for any other reason. */
.srow.t-off  { background: var(--red-row); }
.srow.t-attn { background: var(--amber-row); }
.srow:hover  { background: var(--strip); }
.srow.t-off:hover  { background: #ffe9ee; }
.srow.t-attn:hover { background: #fdfaf0; }

.thumb {
  width: 64px; height: 48px;
  object-fit: cover;
  border: 1px solid #e2e7e4;
  background: var(--page);
  display: block;
}
.srow.t-off  .thumb { border-color: #e6c3cb; }
.srow.t-attn .thumb { border-color: #ece0c4; }

.rtitle { font-size: 16px; font-weight: 500; line-height: 1.25; }
.rsub   { font-size: 13.5px; color: var(--s-muted); margin-top: 2px; }
.rdet   { font-size: 13.5px; color: var(--s-muted); line-height: 1.45; }
.rdet strong { color: var(--s-ink-2); }
.rcode  { font: 400 12px/1 var(--mono); color: var(--s-muted); }

/* An exception that is true of a few rows goes inline beside the name as a small
   pill. Never ship a labelled column that is empty in most rows. */
.tag {
  display: inline-flex; align-items: center;
  height: 22px; padding: 0 9px; border-radius: 4px;
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
  margin-left: 8px; vertical-align: 2px;
  background: #f2f4f3; border: 1px solid var(--fig-line); color: var(--s-ink-2);
}
.tag.green { background: var(--green-light); border-color: var(--green-pill-border); color: var(--green-dark); }

.spill {
  display: inline-flex; align-items: center; justify-content: center;
  height: 26px; padding: 0 11px; border-radius: 4px;
  font-size: 12.5px; font-weight: 600; white-space: nowrap;
}
.spill.off  { background: var(--red); border: 1px solid var(--red); color: #fff; }   /* loudest */
.spill.attn { background: var(--amber-light); border: 1px solid var(--amber-border); color: var(--amber-ink); }
.spill.ok   { background: var(--green-light); border: 1px solid var(--green-pill-border); color: var(--green-dark); }
.spill.neut { background: #f2f4f3; border: 1px solid var(--fig-line); color: var(--s-ink-2); }
.pills { display: flex; flex-wrap: wrap; gap: 8px; }

/* ── Buttons. One primary per panel, on the most urgent row. ──────────────── */

.sbtn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 44px; box-sizing: border-box;
  padding: 0 16px; border-radius: 7px;
  font: 600 15px/1 var(--ui);
  cursor: pointer; text-decoration: none; white-space: nowrap;
}
.sbtn.wide { width: 100%; }
.sbtn.primary {
  background: var(--green); border: 1px solid var(--green-btn-border);
  color: #fff; box-shadow: var(--shadow-1);
}
.sbtn.primary:hover { background: var(--green-btn-border); text-decoration: none; }
.sbtn.secondary {
  background: var(--s-bg); border: 1px solid var(--btn-2-line);
  color: var(--s-ink); box-shadow: var(--shadow-2);
}
.sbtn.secondary:hover { background: #f4f6f5; text-decoration: none; }
.sbtn.quiet {
  background: var(--s-bg); border: 1px solid var(--btn-quiet-line);
  color: var(--s-ink-2); font-weight: 500;
}
.sbtn.quiet:hover { background: #f4f6f5; text-decoration: none; }

.shell a.tlink { color: var(--green-dark); font-weight: 600; text-decoration: none; }
.shell a.tlink:hover { color: var(--green); text-decoration: underline; }

.panelfoot {
  padding: 14px 18px;
  background: var(--strip);
  border-top: 1px solid var(--row-line);
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  font-size: 13.5px; color: var(--s-muted);
}

/* ── Filter bar: a segmented control above a table ────────────────────────── */

.filters { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.seg {
  display: flex; height: 38px;
  border: 1px solid var(--s-line); border-radius: 7px;
  overflow: hidden; background: var(--s-bg);
}
.seg button {
  padding: 0 15px; height: 100%;
  border: 0; border-right: 1px solid var(--s-line);
  background: var(--s-bg);
  font: 400 14px/1 var(--ui); color: var(--s-ink-2);
  cursor: pointer; white-space: nowrap;
}
.seg button:last-child { border-right: 0; }
.seg button:hover { background: #f4f6f5; }
/* The active segment is neutral — never role colour, never status colour. */
.seg button.on {
  background: var(--rail-bg); color: var(--s-ink); font-weight: 600;
  box-shadow: inset 0 -2px 0 #9aa6a0;
}

/* ── Two small instruments: the coverage bar and the checks meter ─────────── */

.cbar { height: 5px; background: #e8ece9; border-radius: 2px; overflow: hidden; margin-top: 6px; }
.cbar.big { height: 8px; }
.cbar i { display: block; height: 100%; background: var(--green); }
.cbar.none i { background: var(--amber-ink); }
.cov { font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }
.cov.live { color: var(--green-dark); }
.cov.dark { color: var(--amber-ink); }

/* Seven squares, one per check. Green passed, amber or red outstanding — the shape
   of the gap is readable at a glance without counting. */
.meter { display: flex; align-items: center; gap: 10px; }
.meter .sq { display: flex; gap: 3px; }
.meter i { width: 9px; height: 9px; background: #e2e7e4; display: block; }
.meter i.pass { background: var(--green); }
.meter i.fail { background: var(--amber-border); }
.meter i.bad  { background: #f4a3b5; }
.meter .left { font-size: 13.5px; color: var(--s-ink-2); font-weight: 500; white-space: nowrap; }

/* ── The "what is holding them back" reason row ───────────────────────────── */

.reason { display: grid; grid-template-columns: 56px minmax(0,1fr) 190px; gap: 16px; align-items: center; padding: 14px 18px; border-bottom: 1px solid var(--row-line); }
.reason:last-child { border-bottom: 0; }
.reason .n { font-size: 27px; font-weight: 500; line-height: 1; text-align: right; font-variant-numeric: tabular-nums; color: var(--amber-ink); }
.reason.hard { background: var(--red-row); }
.reason.hard .n { color: var(--red); }
.reason.soft { background: var(--amber-row); }
.reason .h { font-size: 16px; font-weight: 500; }
.reason .e { font-size: 13.5px; color: var(--s-muted); margin-top: 2px; }

/* ── Halves ───────────────────────────────────────────────────────────────── */

.halves { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; align-items: start; }
.thirds { display: grid; grid-template-columns: 1fr 1fr 1fr; }
.thirds > * { padding: 18px; }
.thirds > * + * { border-left: 1px solid var(--row-line); }
.stepno { font: 400 11px/1 var(--mono); letter-spacing: .1em; color: var(--green-dark); }
.steph  { font-size: 16px; font-weight: 600; margin: 8px 0 6px; }
.stepp  { font-size: 13.5px; color: var(--s-muted); }

/* ── Loading and failure ──────────────────────────────────────────────────── */

/* Skeleton rows at the same height as the real thing. Never a spinner over the
   whole shell — the shell is already true, only the rows are not there yet. */
.skel { padding: 13px 18px; border-bottom: 1px solid var(--row-line); }
.skel:last-child { border-bottom: 0; }
.skel i { display: block; height: 12px; background: #eef1ef; border-radius: 2px; animation: skelpulse 1.1s ease-in-out infinite; }
.skel i + i { margin-top: 8px; width: 40%; }
@keyframes skelpulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }

/* A failed call says so in plain words and offers one action. It never shows a
   zero — a zero is a claim about a life-safety register. */
.failed { padding: 18px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.failed p { font-size: 15px; color: var(--s-ink-2); margin: 0; flex: 1; min-width: 240px; }
.fig.unknown .fign { color: var(--s-muted); }

/* ── Responsive ───────────────────────────────────────────────────────────── */

/* Below ~1100px the rail becomes a labelled list across the top — never an
   icon-only strip, because a volunteer who signs in twice a year cannot read
   icons they have never been taught. The role chip stays visible at every width. */
@media (max-width: 1100px) {
  .shell { border-left: 0; border-right: 0; }
  .shell-body { flex-direction: column; }
  .shell-rail {
    flex: 0 0 auto; width: 100%;
    box-shadow: inset 0 -1px 0 var(--s-line), 0 10px 18px -14px var(--accent-shadow);
    padding: 6px 0 0;
    display: flex; flex-direction: row; flex-wrap: wrap;
  }
  /* The rail's own flex direction is now a row, so the items need their own — without
     this the links inherit nothing and stack one per line, which is the rail again,
     only wider. */
  .shell-rail .navitems { display: flex; flex-wrap: wrap; flex: 1 1 100%; }
  .shell-rail a {
    width: auto; height: 44px; padding: 0 14px;
    border-bottom: 3px solid transparent;
  }
  .shell-rail a.on { box-shadow: none; border-bottom-color: var(--accent); background: transparent; }
  .railfoot {
    margin-top: 0; width: 100%;
    display: flex; align-items: center; gap: 0;
    border-top: 1px solid var(--rail-rule);
  }
  .railstamp, .railcredit { display: none; }
  .railacct { border-top: 0; flex: 1; }
  .railsignout { border-top: 0; width: auto; padding: 0 18px; }
  .shell-main { padding: 18px 16px 24px; }
  .greet { padding: 20px; gap: 18px; }
  .greet-say { min-width: 100%; }      /* figures wrap to their own row */
  .figs { flex-wrap: wrap; row-gap: 14px; }
  .fig:first-child { padding-left: 0; }
  .halves, .thirds { grid-template-columns: 1fr; }
  .thirds > * + * { border-left: 0; border-top: 1px solid var(--row-line); }

  /* Rows stack into blocks with a full-width 48px action. The column header is a
     grid header and means nothing once the grid is gone, so it goes. */
  .colhead { display: none; }
  .srow, .colhead.keep { display: flex; flex-direction: column; align-items: stretch; gap: 10px; padding: 14px 16px; }
  /* A site photograph at full block width is a hero image. It is a thumbnail: enough
     to recognise the place, not enough to push the status off the screen. */
  .srow .thumb { width: 96px; height: 72px; }
  /* A grid needs the empty cell to keep its columns honest. A stack does not, and an
     empty grey rectangle above a site name is just a puzzle. */
  .srow span.thumb { display: none; }
  .srow .act { width: 100%; }
  .srow .act .sbtn { width: 100%; height: 48px; }
  .reason { grid-template-columns: 48px minmax(0,1fr); }
  .reason .act { grid-column: 1 / -1; }
  .reason .act .sbtn { width: 100%; height: 48px; }
  .shell-top { gap: 10px; padding: 0 14px; }
  .shell-find { width: 190px; }
}

@media (max-width: 720px) {
  /* flex-basis, not height, is what fixes a flex item's size. The base rule pins the
     bar at 66px; without releasing that here the wrapped search row overflowed and
     sat on top of the nav. */
  .shell-top { flex: 0 0 auto; height: auto; min-height: 66px; flex-wrap: wrap; padding: 10px 14px; row-gap: 10px; }
  .shell-top .zone-l { flex: 0 1 auto; min-width: 0; }
  .shell-top .zone-c { flex: 1 1 auto; min-width: 0; }
  .shell-top .zone-r { flex: 0 0 100%; width: 100%; }
  .shell-find { width: 100%; }
  /* The chip stays visible at every width — but nowrap on the context was setting a
     floor under the whole frame, and the page scrolled sideways at 430px. It
     truncates instead; the role word, which is the part that matters, never does. */
  .rolechip { max-width: 100%; min-width: 0; }
  .rolechip .ctx { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .greet h1 { font-size: 25px; }
  .fig { padding: 0 14px; }
  .fig:first-child { padding-left: 0; }
}
