/* The page asked for "Google Sans" and "Google Sans Text" from the start, but neither was ever
   loaded — no @font-face, no files, nothing in `document.fonts` at runtime. Every visitor fell
   through to whichever system font their OS happened to supply (SF Pro on a Mac, Segoe UI on
   Windows), which is exactly why the page reads differently, and less crisp, on different
   machines. Google open-sourced Google Sans in 2025; this is the variable font (wght 400-700,
   text optical size) subset to the Latin characters this page actually uses — 24KB, not the
   4.8MB the full download ships. */
@font-face {
  font-family: "Google Sans";
  src: url("fonts/GoogleSans-Variable.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

/* Design tokens and global defaults */
:root {
  color-scheme: light;
  --ink: #0c0d0d;
  --accent: #e21c34;
  --surface: #edecf1;
  --card: #ffffff;
  font-family:
    "Google Sans",
    "Google Sans Text",
    Inter,
    ui-sans-serif,
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif;
  color: var(--ink);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  min-width: 320px;
  background: var(--surface);
}
button,
input,
select {
  font: inherit;
}
/* Application shell and top navigation */
.app-shell {
  min-height: 100vh;
  min-width: 0;
}
.app-header {
  position: sticky;
  top: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 14px clamp(20px, 4vw, 58px);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
  background: var(--card);
}
/* Carries the gap below the header, so nothing scrolls through it. */
.app-header::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  /* Exactly the gap between the header and the first panel. */
  height: 20px;
  background: var(--surface);
}
.brand {
  display: flex;
  align-items: center;
  gap: 13px;
}
.brand-logo {
  width: 62px;
  height: 50px;
  object-fit: contain;
  flex: 0 0 auto;
}
.brand-powered {
  display: block;
  color: color-mix(in srgb, var(--ink) 58%, var(--card));
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 10px;
  letter-spacing: 0.02em;
  line-height: 1.2;
  white-space: nowrap;
}
.brand-powered span {
  display: inline;
  margin-right: 5px;
}
.matrix-logo {
  width: 92px;
  height: auto;
  max-height: 14px;
  object-fit: contain;
  vertical-align: middle;
  margin-left: 2px;
}
.dashboard-nav {
  display: flex;
  gap: 5px;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}
.nav-item {
  min-height: 40px;
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
  box-shadow: 3px 3px 8px color-mix(in srgb, var(--ink) 9%, transparent);
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  text-align: left;
  border-radius: 8px;
  padding: 11px 12px;
  cursor: pointer;
  font-weight: 600;
}
.nav-item:hover,
.nav-item.is-active {
  background: var(--accent);
  box-shadow: none;
  color: var(--card);
}
.nav-item:active {
  transform: translateY(1px);
}
/* Page titles and shared content layout */
html {
  /* Always reserve the scrollbar, so a scrolling view and a short one align. */
  overflow-y: scroll;
  scrollbar-gutter: stable;
}
main {
  padding: 12px clamp(20px, 4vw, 58px) 56px;
  max-width: 1600px;
  width: 100%;
  min-width: 0;
  margin: 0 auto;
}
.page-header {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 10px;
}
.page-context-bar {
  align-items: center;
  min-height: 48px;
  padding: 0 2px 8px;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, var(--surface));
}
h1,
h2,
p {
  margin-top: 0;
}
h1 {
  font-size: 24px;
  letter-spacing: -0.03em;
  margin-bottom: 0;
}
h2 {
  /* One size everywhere, per specs/0006. Reduced from 18px on 2026-08-21 so the
     panel titles sit quieter against the figures they introduce. */
  font-size: 16px;
  letter-spacing: -0.025em;
  margin-bottom: 0;
}
.as-of {
  text-align: right;
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  font-size: 12px;
}
.as-of span,
.as-of strong {
  display: block;
}
.as-of strong {
  color: var(--ink);
  margin-top: 4px;
}
/* Page visibility and overview metrics */
.view {
  display: none;
}
.view.is-active {
  display: block;
}
.metric-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.metric-card,
.panel {
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
  border-radius: 14px;
}
.metric-card {
  padding: 16px;
  min-height: 124px;
  display: flex;
  flex-direction: column;
}
.metric-card span {
  color: color-mix(in srgb, var(--ink) 58%, var(--card));
  font-weight: 600;
  font-size: 13px;
}
.metric-card strong {
  font-size: 35px;
  letter-spacing: -0.06em;
  margin: 11px 0 auto;
}
.metric-card small {
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
}
.overview-attention {
  display: flex;
  align-items: stretch;
  gap: 10px;
  margin-top: 10px;
}
.attention-card {
  min-width: 0;
  min-height: 82px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
  border-radius: 8px;
  background: var(--card);
  padding: 14px 16px;
}
.attention-card-out-of-order {
  flex: 1.25 1 0;
  background: color-mix(in srgb, #ffefcc 38%, var(--card));
  border-color: color-mix(in srgb, #b87500 28%, var(--card));
}
.attention-card-waiting-callback {
  flex: 1 1 0;
  background: color-mix(in srgb, var(--accent) 11%, var(--card));
}
.attention-card-pro-forma {
  flex: 0.9 1 0;
  background: color-mix(in srgb, #d9f0ff 45%, var(--card));
}
.attention-card span,
.attention-card strong {
  display: block;
}
.attention-card span {
  color: color-mix(in srgb, var(--ink) 66%, var(--card));
  font-size: 12px;
  font-weight: 800;
}
.attention-card strong {
  color: var(--ink);
  font-size: 30px;
  letter-spacing: -0.035em;
  margin-top: 2px;
}
.attention-link {
  flex: 0 0 auto;
  border: 1px solid color-mix(in srgb, var(--ink) 14%, var(--card));
  border-radius: 8px;
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
  font-size: 12px;
  font-weight: 800;
  padding: 8px 10px;
  white-space: nowrap;
}
.attention-link:hover,
.attention-link:focus-visible {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, var(--card));
}
/* Shared dashboard panels */
.content-grid,
.updates-grid,
.requests-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 12px;
}
.overview-grid {
  grid-template-columns: 1.2fr 0.8fr;
}
.updates-grid {
  grid-template-columns: 1fr 1fr;
}
.panel {
  padding: 18px;
}
/* Numbers that sit in columns or compare vertically must align */
.overview-kpi-duo,
.overview-kpi-grid,
.status-list strong,
.overview-attention-row strong,
.updates-table,
.request-register,
#request-count {
  font-variant-numeric: tabular-nums;
}
.panel-heading {
  display: flex;
  justify-content: space-between;
  align-items: start;
  gap: 12px;
  margin-bottom: 10px;
}
.panel-heading > strong,
.panel-heading > span {
  color: var(--accent);
  font-weight: 800;
}
/* Overview chart and region list */
.chart-wrap {
  height: 230px;
  padding-top: 8px;
}
.chart-wrap svg {
  display: block;
  width: 100%;
  height: 176px;
  overflow: visible;
}
.chart-grid {
  stroke: color-mix(in srgb, var(--ink) 8%, var(--card));
  stroke-width: 0.5;
}
.trend-area {
  fill: color-mix(in srgb, var(--accent) 12%, var(--card));
}
.trend-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.2;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.trend-dot {
  fill: var(--accent);
}
.chart-plot {
  position: relative;
  padding-left: 34px;
}
.chart-axis {
  position: absolute;
  inset: 0 auto 0 0;
  width: 30px;
}
.chart-axis-value {
  position: absolute;
  right: 0;
  transform: translateY(-50%);
  color: color-mix(in srgb, var(--ink) 55%, var(--card));
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}
.chart-note {
  margin: 6px 0 0;
  color: color-mix(in srgb, var(--ink) 55%, var(--card));
  font-size: 10px;
}
.chart-axis-max {
  top: 0;
}
.chart-axis-zero {
  bottom: 8px;
}
.chart-labels {
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  font-size: 10px;
  display: flex;
  justify-content: space-between;
  gap: 6px;
  margin-left: 34px;
  font-variant-numeric: tabular-nums;
}
.status-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
}
.status-list li {
  display: flex;
  justify-content: space-between;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, var(--card));
  padding-bottom: 11px;
  color: color-mix(in srgb, var(--ink) 68%, var(--card));
}
.status-list strong {
  color: var(--ink);
}
/* Shared controls and status labels */
.page-intro {
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  margin-bottom: 8px;
}
.page-intro p {
  margin-bottom: 0;
}
.reference {
  font-size: 12px;
  color: color-mix(in srgb, var(--ink) 58%, var(--card));
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.status-pill {
  display: inline-flex;
  align-items: center;
  background: color-mix(in srgb, var(--accent) 17%, var(--card));
  color: var(--ink);
  border-radius: 14px;
  padding: 6px 9px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}
.empty-state {
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  padding: 25px 0;
  text-align: center;
}
/* Same control as the header navigation, down to the dimensions. */
.segmented-control {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: var(--surface);
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
}
.segmented-control button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 38px;
  border: 1px solid color-mix(in srgb, var(--ink) 8%, var(--card));
  background: var(--card);
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  padding: 8px 14px;
  border-radius: 8px;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}
.segmented-control button:hover {
  background: color-mix(in srgb, var(--ink) 7%, var(--card));
  border-color: color-mix(in srgb, var(--ink) 22%, var(--card));
  color: var(--ink);
}
.segmented-control button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.segmented-control button.is-active,
.segmented-control button.is-active:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--card);
  box-shadow: none;
}
.segmented-control span {
  color: inherit;
  margin-left: 4px;
}
/* Daily updates table */
.table-scroll {
  overflow: auto;
  border: 1px solid color-mix(in srgb, var(--ink) 8%, var(--card));
  border-radius: 8px;
}
.table-text-small {
  font-size: 12px;
}
.updates-panel .table-scroll {
  overflow: visible;
}
.updates-panel {
  padding: 14px;
}
.updates-table {
  border-collapse: collapse;
  table-layout: fixed;
  width: 100%;
  min-width: 0;
}
.updates-table th {
  background: color-mix(in srgb, var(--surface) 46%, var(--card));
  color: color-mix(in srgb, var(--ink) 70%, var(--card));
  text-align: left;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 10px;
}
.updates-table th,
.updates-table td {
  padding: 9px 7px;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, var(--card));
  vertical-align: middle;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.updates-table td {
  color: color-mix(in srgb, var(--ink) 75%, var(--card));
  font-weight: 400;
  line-height: 1.35;
}
.updates-table .daily-club,
.updates-table .daily-product {
  color: var(--ink);
  font-weight: 700;
}
.updates-table .daily-club strong,
.updates-table .daily-product strong {
  font-weight: inherit;
}
.updates-table .status-pill {
  font-weight: 400;
  white-space: normal;
  overflow-wrap: anywhere;
  max-width: 100%;
}
.updates-table .reported-issue {
  min-width: 0;
  line-height: 1.35;
}
.daily-update-summary {
  color: var(--ink);
  font-weight: 700;
  line-height: 1.35;
}
.daily-current-status .status-pill {
  max-width: 100%;
  justify-content: flex-start;
}
.updates-table .daily-request-type-column {
  width: 28px;
}
/* The reference is a fixed-length monospace code ("JDK" + digits) — 105px fits it with no
   wasted slack, closing the gap the 140px guess used to leave before the Club column. */
.updates-table .daily-request-column {
  width: 105px;
}
/* Widened from 185px (2026-08-26) — the same real 60-club set that needed the register's Club
   column widened wraps here too, and the New tab in particular has room to spare (Reported issue
   ran to 409px for one- or two-sentence text). 240px covers the single longest name
   ("PureGym Brøndby strand centrum 50", ~215px of text plus 22px padding) with no wrap. */
.updates-table .daily-club-column {
  width: 240px;
}
/* Serial numbers are a fixed-length code (e.g. "CTM7492111K03440", 16-17 characters) — same
   shape in every mode, so one fixed px width replaces the three tuned percentages this used to
   carry. 150px covers the longest real serials with no wrap. */
.updates-table .daily-serial-column {
  width: 150px;
}
/* Product, like Serial and Club above, is a bounded catalog rather than free text — giving it a
   percentage (tried 2026-08-27, reverted same day) let it inflate past every real name and leave a
   dead gap before Serial, while Reported issue — genuinely variable length, one or two sentences —
   stayed cramped. 220px covers the longest trimmed real name ("Versa Plus Hip Adductor/Abductor",
   32 characters) with room to spare. Reported issue is left with NO declared width on purpose: in
   `table-layout: fixed`, the one column without a width absorbs whatever the fixed/percentage
   columns don't use, which is the room this table actually needs to give it. */
.updates-table .daily-product-column {
  width: 220px;
}
/* Widened from 13% (~174px at 1500) to a fixed 210px, 2026-08-27, when the Update column started
   naming both ends of a status move ("Parts on order in factory → Parts available") instead of
   saying "Status updated". At 174px that wrapped to three lines on 42 of the 51 real rows.
   Measured across 174/210/240/270: 210px is the knee — the smallest width that puts every cell at
   two lines or fewer, and it costs Reported issue the least (260px → 224px, three more rows
   reaching four lines). Fixed px rather than a percentage for the same reason Product is: the
   vocabulary is bounded — status names from `statuses.ts` plus four fixed labels — so it does not
   need to scale, and Reported issue stays the one column that absorbs the remainder. */
.updates-table-updates .daily-update-column {
  width: 210px;
}
.updates-table-updates .daily-current-status-column {
  width: 12%;
}
.updates-table-new .daily-current-status-column {
  width: 14%;
}
/* specs/0013 follow-up: three trailing columns instead of one, so they run narrower here than in
   the other two modes' single trailing column. */
.updates-table-solved .daily-updated-column {
  width: 8%;
}
.updates-table-solved .daily-outcome-column {
  width: 10%;
}
.updates-table-solved .daily-status-explanation-column {
  width: 12%;
}
.updates-table tbody tr:last-child td {
  border-bottom: 0;
}
.updates-table tbody tr:hover {
  background: color-mix(in srgb, var(--accent) 8%, var(--card));
}
/* The request-type column has no heading (Timo's call) — the icon carries the accessible name
   and this key, shown once beneath the table in every mode, is the visible one. Muted because it
   is a reference, not content: greyed out relative to the table above it. */
.request-type-icon {
  display: block;
  flex-shrink: 0;
  color: color-mix(in srgb, var(--ink) 55%, var(--card));
}
.daily-request-type {
  text-align: center;
}
.request-type-legend {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  color: color-mix(in srgb, var(--ink) 55%, var(--card));
  font-size: 11px;
}
.request-type-legend li {
  display: flex;
  align-items: center;
  gap: 6px;
}
.request-type-legend .request-type-icon {
  color: inherit;
}
/* On the Open requests page the legend sits outside both the register and map panels (Timo's
   call, 2026-08-27) rather than nested in either — it applies to the request-type icon in both.
   It needs its own panel-equivalent padding here since, unlike the Daily updates copy, it isn't
   nested inside a `.panel` that already supplies it. `grid-column: 1 / -1` only matters once
   `.requests-layout` goes two columns at >=1400px; below that it's already the sole item in its
   row. */
.requests-layout > .request-type-legend {
  grid-column: 1 / -1;
  margin: 0;
  padding: 2px 18px 0;
}
/* One line on a phone (Timo's call) rather than three stacked — tighter gaps and a smaller
   face fit all three items in 375px. 4px read too close to the last tile on review; 8px still
   trims the shared 10px desktop margin without crowding it. */
@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  .request-type-legend {
    margin-top: 8px;
    flex-wrap: nowrap;
    justify-content: center;
    gap: 4px 10px;
    font-size: 10px;
  }
  .request-type-legend li {
    gap: 4px;
    white-space: nowrap;
  }
}
/* Open requests map and filters */
.map-panel,
.requests-panel {
  padding: 14px;
}
.map-canvas {
  position: relative;
  height: 323px;
  overflow: hidden;
  border-radius: 8px;
  background: var(--surface);
  isolation: isolate;
}
.map-actions {
  display: flex;
  align-items: flex-end;
  flex-direction: column;
  gap: 8px;
}
.map-coverage {
  color: color-mix(in srgb, var(--ink) 65%, var(--card)) !important;
  font-size: 12px;
  white-space: nowrap;
}
.reset-map {
  /* Keyed to Leaflet's own control colour rather than to our palette, deliberately:
     this button now sits on the canvas opposite the zoom control, and the point is
     that the two read as a pair. `.leaflet-bar` uses rgba(0, 0, 0, 0.2), so that is
     the value here — matching a token would drift away from it. */
  border: 1px solid rgba(0, 0, 0, 0.2);
  border-radius: 8px;
  background: var(--card);
  color: var(--ink);
  padding: 7px 10px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 700;
}
.request-map-marker,
.request-marker-cluster {
  display: grid;
  place-items: center;
  background: transparent;
  border: 0;
}
.marker-count {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 3px solid var(--card);
  border-radius: 50%;
  background: var(--accent);
  color: var(--marker-count-foreground);
  box-shadow: 0 2px 10px color-mix(in srgb, var(--ink) 30%, transparent);
  font-size: 11px;
  font-weight: 900;
}
.marker-count.is-approximate {
  border: 3px dashed var(--card);
  background: var(--accent);
}
.cluster-count {
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  border: 3px solid var(--card);
  border-radius: 50%;
  background: var(--accent);
  color: var(--marker-count-foreground);
  box-shadow: 0 3px 12px color-mix(in srgb, var(--ink) 34%, transparent);
  font-size: 12px;
  font-weight: 900;
}
.request-map-marker:focus-visible .marker-count,
.request-marker-cluster:focus-visible .cluster-count {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}
.leaflet-control-attribution {
  font-size: 10px;
}
/* Search first (2026-08-28), Region and Out of order narrowed to make room — their option text
   ("Zealand", "Yes"/"No"/"Any") is short, where Club and Status need the space. */
.filters {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.7fr) minmax(0, 1.3fr) minmax(0, 1.1fr) minmax(0, 0.7fr) auto;
  align-items: end;
  gap: 9px;
  margin-bottom: 13px;
}
.filters label {
  color: color-mix(in srgb, var(--ink) 70%, var(--card));
  font-size: 11px;
  font-weight: 700;
}
.filters input,
.filters select {
  width: 100%;
  margin-top: 5px;
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--ink) 16%, var(--card));
  border-radius: 8px;
  padding: 9px;
  color: var(--ink);
}
.clear-filters {
  border: 1px solid color-mix(in srgb, var(--ink) 16%, var(--card));
  border-radius: 8px;
  background: var(--card);
  color: var(--ink);
  padding: 9px 11px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}
.clear-filters:hover,
.clear-filters:focus-visible,
.reset-map:hover,
.reset-map:focus-visible {
  border-color: var(--accent);
  color: var(--ink);
}
/* Open requests register and expanded details */
.requests-panel,
.view {
  scroll-margin-top: 100px;
}
.requests-panel .table-scroll {
  /* Vertical scrolling belongs to the register; this selector outranks .register-scroll. */
  overflow-x: hidden;
  overflow-y: auto;
  border-color: color-mix(in srgb, var(--ink) 10%, var(--card));
}
.request-register {
  border-collapse: collapse;
  table-layout: fixed;
  width: 100%;
  color: color-mix(in srgb, var(--ink) 74%, var(--card));
}
/* Same icon, same "no heading" treatment as the Daily updates table — the leftover width comes
   out of Reported issue, the one column here with no declared width of its own. Widened from 28px
   (2026-08-28) to hold the recall star alongside the type icon: two 16px icons plus a 4px gap need
   36px of content, and the 12px cell padding on each side eats 24px of whatever this is set to —
   measured after 44px still clipped the pair to a 20px box and let them bleed into Request. */
.request-register .request-request-type-column {
  width: 60px;
}
.request-register .request-request-type {
  text-align: center;
}
.request-type-icons {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
}
.request-register .request-reference-column {
  width: 9%;
}
/* Widened from 13% (2026-08-26, Timo's call) — club names were wrapping to two lines too often.
   First tried 20%, sized against the *daily-updates* table's width by mistake — the register
   itself is only ~952px at 1440px wide, once >=1400px puts the map beside it (`.requests-layout`
   below), not the ~1279px used elsewhere. Re-measured against the register's own rendered width:
   the 95th-percentile of the real 60-club set needs ~219px including padding, the single longest
   ("PureGym Brøndby strand centrum 50") ~239px. 25% of ~952px comfortably covers both. Reported
   issue has no declared width of its own here, so the room comes out of it without touching
   anything else. */
.request-register .request-club-column {
  width: 25%;
}
.request-register .request-product-column {
  width: 13%;
}
.request-register .request-status-column {
  width: 170px;
}
.request-register .request-opened-column {
  width: 96px;
}
.request-register .request-disclosure-column {
  width: 106px;
}
.request-register .request-age-column {
  width: 64px;
}
.request-age,
.request-opened {
  white-space: nowrap;
}
/* The register scrolls inside its own panel: about eight collapsed rows. */
.request-issue-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.register-selection {
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.register-scroll {
  /* A fixed height, so filtering to one row does not shrink the panel or the map. */
  height: min(700px, calc(100vh - 200px));
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* Below 1400px the map stacks under the register instead of beside it — and the register can
   run to dozens or hundreds of rows, so the map and its legend landed thousands of pixels down
   the page. Reported as "the legend is missing on desktop" (2026-08-26) because most real browser
   windows are narrower than 1400px CSS pixels; it was never actually hidden, just unreachable
   without a long scroll. `order` puts the map first in this stacked case only — the >=1400px rule
   below never sets `order`, so the side-by-side layout (register left, map right) is untouched. */
@media (max-width: 1399.98px) {
  .map-panel {
    order: -1;
  }
}
/* Wide screens show the map beside the register instead of below it. */
@media (min-width: 1400px) {
  .requests-layout {
    grid-template-columns: minmax(0, 1fr) 300px;
    align-items: stretch;
  }
  .map-panel {
    display: flex;
    flex-direction: column;
  }
  .map-canvas {
    flex: 1;
    height: auto;
    min-height: 320px;
  }
  .map-panel .panel-heading {
    display: block;
  }
  .map-panel .map-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 8px;
  }
  /* Club and product identify a row; the reference lives in the details. Column 2, not 1 — the
     request-type icon was inserted before Reference (2026-08-26) and stays visible here; these
     positional selectors used to double as "the Reference column" back when it was first. */
  .request-register .request-reference-column {
    width: 0;
  }
  .request-register th.is-collapsed,
  .request-register td.is-collapsed,
  .request-register thead th:nth-child(2),
  .request-register tbody tr.request-row td:nth-child(2) {
    width: 0;
    max-width: 0;
    padding-left: 0;
    padding-right: 0;
    overflow: hidden;
    white-space: nowrap;
    visibility: hidden;
  }
}
.sort-button {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  cursor: pointer;
}
.sort-button:hover {
  color: var(--ink);
}
.sort-button.is-active {
  color: var(--accent);
}
.sort-indicator {
  font-size: 9px;
}
.register-scroll thead th {
  position: sticky;
  top: 0;
  z-index: 2;
}
.request-register th {
  background: color-mix(in srgb, var(--surface) 46%, var(--card));
  color: color-mix(in srgb, var(--ink) 70%, var(--card));
  text-align: left;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 10px;
  font-weight: 700;
}
.request-register th,
.request-register td {
  padding: 11px 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, var(--card));
  vertical-align: top;
}
.request-register td {
  font-weight: 400;
  line-height: 1.45;
}
.request-register .request-club,
.request-register .request-product {
  color: var(--ink);
  font-weight: 700;
}
.request-register .request-club strong {
  font-weight: inherit;
}
.request-register .request-region {
  display: block;
  color: color-mix(in srgb, var(--ink) 58%, var(--card));
  font-weight: 400;
}
.request-register .request-product,
.request-register .request-issue {
  overflow-wrap: anywhere;
  word-break: break-word;
}
.request-register .request-status,
.request-register .request-opened {
  white-space: nowrap;
}
.request-status-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
}
.request-status-label {
  display: block;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
}
.request-status-label-out-of-order {
  color: #b42318;
  font-weight: 700;
}
.request-register tbody tr.request-row:hover {
  background: color-mix(in srgb, var(--accent) 8%, var(--card));
}
.request-disclosure {
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--card));
  border-radius: 8px;
  background: var(--card);
  color: var(--ink);
  padding: 6px 8px;
  cursor: pointer;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}
.request-disclosure:hover,
.request-disclosure:focus-visible {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, var(--card));
}
.request-detail-row td {
  padding: 0;
  background: color-mix(in srgb, var(--surface) 46%, var(--card));
}
.request-detail-row > td {
  /* A stronger rule closes the expanded block. */
  border-bottom-color: color-mix(in srgb, var(--ink) 24%, var(--card));
}
.request-detail {
  /* Matches the closing rule below, so the block is bounded top and bottom. */
  border-top: 1px solid color-mix(in srgb, var(--ink) 24%, var(--card));
  padding: 16px 18px 20px;
  font-size: 12px;
}
/* One aligned grid of facts, then the two long text fields. */
.detail-facts,
.detail-text {
  display: grid;
  gap: 10px 12px;
  margin: 0;
  align-items: start;
}
.detail-facts {
  grid-template-columns: repeat(6, minmax(0, 1fr));
}
.detail-text {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: 12px;
  align-items: stretch;
}
.request-detail div {
  min-width: 0;
}
.detail-facts > div,
.detail-text > div {
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: 8px;
}
.detail-text > div {
  background: var(--card);
  border-color: color-mix(in srgb, var(--ink) 10%, var(--card));
}
.request-detail dt {
  color: color-mix(in srgb, var(--ink) 70%, var(--card));
  font-weight: 700;
}
.request-detail dd {
  margin: 4px 0 0;
  line-height: 1.5;
  color: color-mix(in srgb, var(--ink) 74%, var(--card));
  overflow-wrap: anywhere;
}
.detail-facts .date-highlight {
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
  border-color: color-mix(in srgb, var(--accent) 44%, var(--card));
}
.detail-facts .date-highlight dd {
  color: var(--ink);
  font-weight: 800;
}
.error-state {
  background: var(--card);
  border-radius: 14px;
  padding: 30px;
}
/* Accessible keyboard focus */
button:focus-visible,
input:focus-visible,
select:focus-visible,
summary:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}
/* Narrow-screen fallbacks */
@media (max-width: 1000px) {
  /* The nav is absolutely centred above this width; below it, let flex place it. */
  .dashboard-nav {
    position: static;
    transform: none;
  }
  .app-header {
    gap: 16px;
  }
  .metric-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .overview-attention {
    flex-wrap: wrap;
  }
  .attention-card {
    flex-basis: calc(50% - 5px);
  }
  .overview-grid,
  .updates-grid,
  .requests-layout {
    grid-template-columns: 1fr;
  }
  .map-canvas {
    height: 330px;
  }
}
@media (max-width: 700px) {
  .app-header {
    position: sticky;
    top: 0;
    z-index: 3;
    min-width: 0;
    padding: 12px 15px;
    gap: 15px;
  }
  .brand-powered {
    display: none;
  }
  .dashboard-nav {
    min-width: 0;
    overflow: auto;
    position: static;
    transform: none;
    margin-left: auto;
  }
  .nav-item {
    white-space: nowrap;
    padding: 8px;
    font-size: 12px;
  }
  main {
    padding: 26px 16px 40px;
  }
  .page-header {
    align-items: start;
  }
  .as-of {
    max-width: 130px;
  }
  .metric-grid {
    grid-template-columns: 1fr 1fr;
  }
  .overview-attention {
    display: grid;
    grid-template-columns: 1fr;
  }
  .attention-card {
    min-height: 0;
  }
  .metric-card {
    padding: 14px;
    min-height: 125px;
  }
  .metric-card strong {
    font-size: 27px;
  }
  .panel-heading {
    flex-direction: column;
  }
  .segmented-control {
    width: 100%;
    overflow: auto;
  }
  .segmented-control button {
    white-space: nowrap;
    flex: 1;
  }
  .updates-panel .table-scroll {
    overflow: auto;
  }
  .updates-table {
    min-width: 900px;
  }
  .filters {
    grid-template-columns: 1fr;
  }
  .request-row summary {
    display: block;
  }
  .request-meta {
    justify-content: space-between;
    margin-top: 9px;
  }
  .request-detail {
    grid-template-columns: 1fr;
  }
  .request-detail-core,
  .request-detail-metadata,
  .request-detail-top {
    grid-template-columns: 1fr;
  }
}

/* Final Overview refinement: reference-style controls and compact hierarchy */
/* Same control as the segmented one on Daily updates: a track makes the buttons read as buttons. */
.dashboard-nav {
  display: grid;
  grid-auto-flow: column;
  /* Equal columns, sized to the widest label, so all three targets match. */
  grid-auto-columns: 1fr;
  gap: 4px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
  border-radius: 10px;
}
.nav-item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 38px;
  border-radius: 8px;
  padding: 8px 14px;
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--ink) 8%, var(--card));
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  box-shadow: none;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}
.nav-icon {
  flex: none;
  color: #1f2937;
}
.nav-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.nav-item.is-active .nav-icon {
  color: currentColor;
  stroke-opacity: 1;
}
.nav-item:hover {
  background: color-mix(in srgb, var(--ink) 7%, var(--card));
  border-color: color-mix(in srgb, var(--ink) 22%, var(--card));
  color: var(--ink);
}
.nav-item.is-active,
.nav-item.is-active:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--card);
}
.page-context-bar {
  min-height: 34px;
  padding-bottom: 6px;
}
.page-context-bar h1 {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.as-of {
  display: grid;
  gap: 2px;
}
.as-of-label {
  color: var(--ink);
  font-weight: 700;
}
.overview-kpi-grid {
  grid-template-columns: 1.15fr repeat(3, minmax(0, 1fr));
}
.overview-daily-card {
  position: relative;
  min-height: 156px;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
  padding: 16px 18px;
  background: var(--card);
}
.overview-daily-card-copy span {
  font-size: 14px;
}
.overview-daily-card-copy strong {
  margin-top: 13px;
  font-size: 38px;
}
.overview-daily-card .overview-action-button {
  align-self: flex-start;
}
.overview-update-badge {
  position: absolute;
  top: 50%;
  right: -10px;
  transform: translateY(-50%);
  z-index: 1;
  padding: 4px 7px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--card);
  font-size: 9px;
  font-style: normal;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.overview-grid {
  margin-top: 10px;
}
.overview-content-grid {
  display: grid;
  grid-template-columns: minmax(260px, 0.85fr) minmax(0, 2fr);
  gap: 10px;
}
.overview-attention {
  display: block;
}
.overview-attention-panel {
  height: 100%;
  padding: 14px;
}
/* `.empty-state` is shared with the chart and the map, both plain body-size text — this panel's
   copy sat next to a 12px row label, so the page default (16px) read as too large beside it
   (Timo, 2026-08-25). Matches `.overview-attention-row span`'s own 12px exactly, rather than
   picking a third size for the same panel. Scoped here rather than on `.empty-state` itself, so
   the chart's and map's empty states are untouched. Outside the `@supports` block below on
   purpose: the size should still apply in the one browser old enough to lack `subgrid`. */
.overview-attention-panel .empty-state {
  font-size: 12px;
}
.overview-attention-row {
  background: transparent;
}
.overview-attention-row.attention-card-waiting-callback,
.overview-attention-row.attention-card-pro-forma,
.overview-attention-row.attention-card-out-of-order {
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
}
.overview-attention-row.attention-card-out-of-order {
  border-bottom: 0;
}
.overview-analytics-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(230px, 0.8fr);
  gap: 10px;
}
.overview-analytics-grid .panel {
  min-width: 0;
}
@media (max-width: 1100px) {
  /* Reference and opened date go first: Age carries the same meaning in less room. Column
     positions shifted by one (2 and 7, not 1 and 6) when the request-type icon was inserted
     before Reference (2026-08-26) — it stays visible here, only Reference and Opened collapse. */
  .request-register .request-reference-column,
  .request-register .request-opened-column {
    width: 0;
  }
  .request-register thead th:nth-child(2),
  .request-register thead th:nth-child(7),
  .request-register tbody tr.request-row td:nth-child(2),
  .request-register tbody tr.request-row td:nth-child(7) {
    width: 0;
    max-width: 0;
    padding-left: 0;
    padding-right: 0;
    overflow: hidden;
    white-space: nowrap;
    visibility: hidden;
  }
  .overview-kpi-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .overview-content-grid,
  .overview-analytics-grid {
    grid-template-columns: 1fr;
  }
}

.header-as-of {
  display: grid;
  gap: 2px;
  margin-left: auto;
  color: color-mix(in srgb, var(--ink) 58%, var(--card));
  font-size: 11px;
  line-height: 1.2;
  text-align: right;
  white-space: nowrap;
}
.header-as-of-label {
  color: var(--ink);
  font-weight: 700;
}
.header-as-of strong {
  color: var(--ink);
  font-size: 12px;
}
@media (max-width: 700px) {
  .header-as-of {
    display: none;
  }
}
@media (max-width: 700px) {
  .overview-kpi-grid {
    grid-template-columns: 1fr;
  }
}

/* Layout order override: attention left, analytics right */
.overview-content-grid {
  grid-template-columns: minmax(260px, 0.85fr) minmax(0, 2fr);
}
.overview-attention-panel.overview-attention {
  display: block;
}
.overview-analytics-grid {
  grid-template-columns: minmax(0, 1.2fr) minmax(230px, 0.8fr);
}
@media (max-width: 1100px) {
  .overview-content-grid,
  .overview-analytics-grid {
    grid-template-columns: 1fr;
  }
}

/* Last-pass alignment overrides */
.overview-kpi-duo {
  text-align: center;
}
.overview-daily-card {
  text-align: center;
}
.overview-action-button {
  border: 1px solid color-mix(in srgb, var(--ink) 20%, var(--card));
  background: transparent;
  color: color-mix(in srgb, var(--ink) 76%, var(--card));
  padding: 5px 9px;
  font-size: 10px;
}

/* Overview tile polish */
main {
  padding-top: 0;
}
.page-context-bar {
  margin-top: 0;
  min-height: 22px;
  padding: 0 2px 5px;
}
.overview-kpi-duo {
  align-items: center;
  text-align: center;
}
.overview-kpi-duo > div {
  width: 100%;
}
.overview-kpi-duo strong,
.overview-kpi-duo span,
.overview-daily-card-copy,
.overview-daily-card-copy span,
.overview-daily-card-copy strong {
  text-align: center;
}
.overview-daily-card {
  min-height: 132px;
  align-items: center;
  text-align: center;
  padding: 14px 16px;
}
.overview-daily-card-copy {
  width: 100%;
}
.overview-daily-card-copy span {
  font-size: 13px;
}
.overview-daily-card-copy strong {
  margin-top: 9px;
  font-size: 34px;
}
.overview-daily-card .overview-action-button {
  align-self: center;
}
/* The updates tile draws a customer's eye first, chosen after comparing ten notification
   treatments against the two calm neighbours (2026-08-24). The bar and chip both use the page's
   own accent, so nothing new is added to the colour system — the tradeoff, accepted deliberately,
   is that this tile now shares red with "out of order" and the active tab rather than keeping red
   reserved for urgency alone (R26). */
.overview-daily-card.is-updated {
  /* An inset shadow, not a border — a real border-top here was 3px against the neighbours'
     1px (mobile) or 0px (desktop), so the updated cell's number sat 2px lower than the other
     two. An inset shadow draws the same line without adding to the box, so every cell's content
     starts at the same y regardless of which one is marked (CLAUDE.md, "measure it"). */
  box-shadow: inset 0 3px 0 0 var(--accent);
}
.overview-daily-card-copy .daily-card-notify-chip {
  display: inline-flex;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 9px;
  border-radius: 20px;
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
  color: color-mix(in srgb, var(--accent) 82%, var(--ink));
}
.overview-action-button {
  border: 1px solid color-mix(in srgb, var(--ink) 20%, var(--card));
  background: transparent;
  color: color-mix(in srgb, var(--ink) 76%, var(--card));
  padding: 5px 9px;
  font-size: 10px;
  font-weight: 700;
}
.overview-action-button:hover,
.overview-action-button:focus-visible {
  background: color-mix(in srgb, var(--ink) 8%, var(--card));
  color: var(--ink);
}

/* Overview dashboard composition */
.page-context-bar {
  min-height: 38px;
  padding-bottom: 7px;
  margin-bottom: 10px;
}
.metric-grid {
  display: block;
}
.overview-kpi-grid {
  display: grid;
  grid-template-columns: 1.45fr repeat(3, 1fr);
  gap: 10px;
}
.overview-kpi-duo,
.overview-kpi {
  min-height: 112px;
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
  border-radius: 14px;
  padding: 16px 18px;
}
.overview-kpi-duo {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}
.overview-kpi-duo > div + div {
  border-left: 1px solid color-mix(in srgb, var(--ink) 12%, var(--card));
}
.kpi-out-of-order strong {
  color: var(--accent);
}
.overview-kpi-duo strong,
.overview-kpi strong {
  display: block;
  color: var(--ink);
  font-size: 35px;
  letter-spacing: -0.06em;
  line-height: 1;
  margin-bottom: 11px;
}
.overview-kpi-duo span,
.overview-kpi span {
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  font-size: 13px;
  font-weight: 700;
}
.overview-kpi {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.overview-daily-panel {
  margin-top: 10px;
  padding: 14px 16px 16px;
  background: var(--card);
  border: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
  border-radius: 14px;
}
.overview-daily-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.overview-daily-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
  padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--ink) 9%, var(--card));
  border-radius: 8px;
  background: color-mix(in srgb, var(--surface) 38%, var(--card));
}
.overview-daily-card-copy {
  position: relative;
  min-width: 0;
}
.overview-daily-card-copy span,
.overview-daily-card-copy strong {
  display: block;
}
.overview-daily-card-copy span {
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  font-size: 12px;
  font-weight: 700;
}
.overview-daily-card-copy strong {
  margin-top: 3px;
  font-size: 25px;
  letter-spacing: -0.04em;
}
.overview-daily-card-copy em {
  position: absolute;
  top: -2px;
  right: -46px;
  padding: 3px 6px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--card);
  font-size: 9px;
  font-style: normal;
  font-weight: 800;
  text-transform: uppercase;
}
.overview-action-button {
  flex: 0 0 auto;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--card);
  cursor: pointer;
  font-size: 11px;
  font-weight: 800;
  padding: 9px 12px;
  white-space: nowrap;
}
.overview-action-button:hover,
.overview-action-button:focus-visible {
  background: color-mix(in srgb, var(--ink) 78%, var(--card));
}
.overview-grid {
  display: block;
  margin-top: 10px;
}
.overview-content-grid {
  display: grid;
  grid-template-columns: 1.05fr 1.3fr 0.8fr;
  align-items: stretch;
  gap: 10px;
}
.overview-attention {
  display: contents;
}
.overview-attention-panel,
.overview-chart,
.overview-regions {
  min-width: 0;
}
.overview-attention-panel {
  padding: 14px;
}
.overview-attention-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 0;
}
.overview-attention-row > div {
  min-width: 0;
}
.overview-attention-row span,
.overview-attention-row strong {
  display: block;
}
.overview-attention-row span {
  color: color-mix(in srgb, var(--ink) 66%, var(--card));
  font-size: 12px;
  font-weight: 700;
}
.overview-attention-row strong {
  margin-top: 3px;
  font-size: 25px;
  letter-spacing: -0.04em;
}
.overview-attention-row.attention-card-waiting-callback,
.overview-attention-row.attention-card-pro-forma {
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 9%, var(--card));
}
.overview-attention-row.attention-card-out-of-order {
  padding-bottom: 0;
}
.overview-attention-row.attention-card-out-of-order span {
  color: color-mix(in srgb, #a7192c 84%, var(--ink));
}
.overview-attention-row.attention-card-out-of-order strong {
  color: #a7192c;
}
.overview-chart .chart-wrap {
  height: 252px;
}
.overview-regions .status-list {
  gap: 0;
}
.overview-regions .status-list li {
  padding: 9px 0;
  font-size: 13px;
}
@media (max-width: 1100px) {
  .overview-kpi-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .overview-content-grid {
    grid-template-columns: 1fr 1fr;
  }
  .overview-attention-panel {
    grid-column: 1 / -1;
  }
}
@media (max-width: 700px) {
  .overview-kpi-grid,
  .overview-daily-grid,
  .overview-content-grid {
    grid-template-columns: 1fr;
  }
  .overview-kpi-duo {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Final order: attention panel left, analytics group right */
.overview-content-grid {
  grid-template-columns: minmax(260px, 0.85fr) minmax(0, 2fr);
}
.overview-attention-panel.overview-attention {
  display: block;
}
.overview-analytics-grid {
  grid-template-columns: minmax(0, 1.2fr) minmax(230px, 0.8fr);
}
@media (max-width: 1100px) {
  .overview-content-grid,
  .overview-analytics-grid {
    grid-template-columns: 1fr;
  }
}

/* Final action sizing */
.overview-action-button {
  border: 1px solid color-mix(in srgb, var(--ink) 20%, var(--card));
  background: transparent;
  color: color-mix(in srgb, var(--ink) 76%, var(--card));
  padding: 5px 9px;
  font-size: 10px;
}

/* Final overview alignment */
.page-context-bar {
  min-height: 0;
  height: 0;
  margin: 0;
  padding: 0 2px;
}
.overview-daily-card-copy strong {
  font-size: 35px;
}
.overview-content-grid {
  align-items: start;
}
.overview-attention-panel,
.overview-analytics-grid {
  margin-top: 0;
}

/* Final spacing and attention-row alignment */
main {
  padding-top: 20px;
}
.page-context-bar {
  border-bottom: 0;
}
.overview-attention-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 44px auto;
  align-items: center;
  column-gap: 12px;
}
.overview-attention-row > span {
  min-width: 0;
}
.overview-attention-row > strong {
  text-align: center;
}
.overview-attention-row > .overview-action-button {
  justify-self: end;
}

/* `specs/0015` — an announcement, violet rather than red or amber: red already means the active
   tab and "Out of order" (`REVIEW.md` R26), amber already means "waiting on you" (`specs/0011`),
   and a third reuse of either would repeat exactly what R26 fixed. `#7033FF` clears both WCAG
   ratios on its own — 5.82:1 against `--card` (needs 4.5:1 for the text) and same for the edge
   (needs 3:1) — so, unlike amber, no darkening was needed. */
.overview-attention-announcement {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  padding: 10px 12px;
  border-left: 4px solid #7033FF;
  border-radius: 4px;
  background: color-mix(in srgb, #7033FF 8%, var(--card));
  color: #7033FF;
  font-size: 12px;
  font-weight: 700;
  /* Measured, not guessed (2026-08-28): a long message's unbroken max-content width was feeding
     into the panel's `minmax(0, max-content)` label-column sizing under `@supports (subgrid)`
     below, collapsing it to 0px and overlapping "Pro Forma sent" with its own count. `contain:
     inline-size` opts this row out of contributing to that ancestor sizing pass; it still fills
     its `grid-column: 1 / -1` area exactly as before, confirmed by measuring its rendered width
     against the sum of the three tracks plus their gaps. */
  contain: inline-size;
}
.overview-attention-announcement svg {
  flex-shrink: 0;
}

/* ---------------------------------------------------------------------------
   Fixes from the 2026-08-21 design review (DESIGN_REVIEW.md).
   Appended rather than edited in place: scripts/build-customer.test.ts pins ~58
   minified CSS substrings, so editing an existing rule breaks the build test
   while appending does not (REVIEW.md R15, R16). R16 is the fix for that
   incentive and is still open.
   --------------------------------------------------------------------------- */

/* R20 — the action buttons lost their label on hover and on keyboard focus.
   "Final action sizing" above reset the resting state to a transparent bordered
   button but left both :hover/:focus-visible blocks behind, so the winning
   background came from the pill generation (dark, ink 78%) and the winning
   colour from the bordered generation (near-black, var(--ink)). Measured
   1.94:1 at 10px/800 against 4.5:1 required, and it was the focused state.

   The state inverts to a solid pill rather than taking a light tint. A tint has
   to be mixed against a surface, and these five buttons sit on two different
   ones — three on the grey daily cards, two on a white panel — so a tint mixed
   against var(--card) measured 1.12:1 against the grey cards: readable, but the
   state change itself was invisible, which is what Timo caught on 2026-08-21.
   An opaque fill is surface-independent: 18.3:1 on the cards, 19.5:1 on white,
   with the label at 19.5:1 either way. It also restores what the dark-pill
   generation above was doing before the reset stranded its hover, and it stays
   off the accent, which R26 reserves for urgency and the current tab. */
.overview-action-button:hover,
.overview-action-button:focus-visible {
  background: var(--ink);
  color: var(--card);
  border-color: var(--ink);
}

/* R18 (reopened) — the header overflowed from about 701px to 830px. Its flex row
   needs brand 236px + nav 404px + as-of 119px plus padding and gaps, which is
   821px against a 753px viewport at 768px wide, so the page scrolled sideways and
   the "Data updated" stamp fell outside the white header band and was clipped.
   Nothing in the row could shrink: .brand-powered and .header-as-of are both
   nowrap. The "Powered by Matrix" lockup is already hidden below 700px, so it is
   dropped for the rest of the band too, which returns about 174px — enough for
   the date to sit inside the header at every width. */
@media (max-width: 900px) {
  .brand-powered {
    display: none;
  }
}

/* R26 — the accent was carrying three unrelated meanings: the active tab, the
   "Out of order" urgency state, and this neutral count. `.panel-heading > span`
   (0,1,1) silently outranked `.register-selection` (0,1,0), which had already
   asked for muted ink. Restores that intent, so red means urgency again. */
.panel-heading > .register-selection {
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
}

/* R25 — with no data there is nothing to switch to, so the tabs say so. */
.nav-item[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
  box-shadow: none;
}

/* The hover on the nav pills and the segmented control took the border to
   ink 22%. Darkening it further is the whole feedback signal on these controls,
   so it is worth the step: ink 42% reads as deliberate without looking like the
   focus ring, which is a 2px accent outline. The active pill had no hover state
   at all — its border and fill both stayed flat accent — so it now darkens its
   border too, which keeps the accent (legitimate on the current tab, R26) while
   still answering the pointer. */
.nav-item:hover,
.segmented-control button:hover {
  border-color: color-mix(in srgb, var(--ink) 42%, var(--card));
}
.nav-item.is-active:hover,
.segmented-control button.is-active:hover {
  border-color: color-mix(in srgb, var(--ink) 30%, var(--accent));
}

/* The one number out of line in the KPI row was 45, nine pixels high. Its cell
   is taller than the other two because "Clubs impacted" wraps to two lines, and
   the cells are vertically centred, so the taller one rides up. Top-aligning the
   cells and paying the 17px back as padding puts the short cells exactly where
   they already were — 110 and 13 do not move, and neither do the three cards,
   which already share this line — while 45 drops onto it. */
.overview-kpi-duo {
  align-items: start;
  padding-top: 33px;
}

/* Map option B, chosen 2026-08-21. Clusters keep the accent; every final dot is
   ink, so "this is one club" reads at a glance. Before this the two were
   identical — same accent, same 3px white ring, same circle — and only 42px
   against 34px told them apart, while their numbers meant different things
   (clubs in a cluster, open requests on a club). */
.request-map-marker .marker-count {
  background: var(--ink);
  color: var(--card);
  border: 2px solid var(--card);
  box-shadow: 0 1px 4px color-mix(in srgb, var(--ink) 35%, transparent);
}

/* The legend that says what the two colours mean. Replaces the one-line
   guidance note, which explained the dashed ring but not the fills. */
.map-legend {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: grid;
  gap: 5px;
  color: color-mix(in srgb, var(--ink) 68%, var(--card));
  /* 11px wrapped the second line by a handful of pixels at the panel's fixed 300px width in
     side-by-side mode (2026-08-26) — every desktop visitor saw it, not an edge case. 10px matches
     the mobile request-type legend's own size and gives real margin, not a pixel-perfect fit. */
  font-size: 10px;
  line-height: 1.35;
}
.map-legend li {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  gap: 8px;
  align-items: start;
}
.map-legend-key {
  width: 14px;
  height: 14px;
  margin-top: 1px;
  border-radius: 50%;
  box-shadow: 0 1px 3px color-mix(in srgb, var(--ink) 30%, transparent);
}
.map-legend-key.is-cluster {
  background: var(--accent);
  border: 2px solid var(--card);
}
.map-legend-key.is-club {
  background: var(--ink);
  border: 2px solid var(--card);
}

/* Reset map moved out of the map panel's heading and onto the canvas, 2026-08-21.
   In side-by-side mode the panel is only 270px wide, so the title and the button
   could not share a line: the heading was 60px against the register's 21px, which
   pushed the canvas 21px below the filter dropdowns. As a map control it costs the
   heading nothing, the canvas gains 39px, and its top lands level with the filter
   labels. The button keeps its size, so the hit target is unchanged. */
.map-frame {
  position: relative;
  display: flex;
  min-width: 0;
}
.map-frame .map-canvas {
  flex: 1 1 auto;
  width: 100%;
}
.map-frame .reset-map {
  position: absolute;
  top: 10px;
  right: 10px;
  /* Above Leaflet's own controls, which sit at 1000. */
  z-index: 1001;
  box-shadow: 0 1px 4px color-mix(in srgb, var(--ink) 30%, transparent);
}
@media (min-width: 1400px) {
  .map-frame {
    flex: 1;
  }
}

/* The legend's action half is the changeable part, so it reads as an aside. */
.map-legend em {
  font-style: italic;
}

/* Line the filter caption up with the value inside its dropdown: the select pads
   9px and draws a 1px border, so its text starts 10px in. The caption lives in its
   own span because the select is inside the label — padding on the label would
   move both. */
.filters label > span {
  display: block;
  padding-left: 10px;
}

/* ── Status tone, made visible (REVIEW.md R29) ───────────────────────────────
   app.js has always computed a tone per status — customer-action, parts, progress,
   complete, out-of-order — and the stylesheet defined none of them, so every status
   rendered in the same pink. On a page whose job is triage, that threw away the one
   distinction the customer needs: "we are waiting on you" against "parts are moving".
   Colour is reinforcement only; each pill also names its status in words. */
.status-pill-out-of-order {
  background: #fde5e7;
  color: #8f1620;
  font-weight: 700;
}
.status-pill-customer-action {
  background: #fdf1dc;
  color: #6b4106;
}
.status-pill-parts {
  background: #e7eef7;
  color: #1f4e79;
}
.status-pill-progress {
  background: #edeef0;
  color: #3a3d40;
}
.status-pill-complete {
  background: #e3f2e6;
  color: #1c6b3a;
}
.request-status-label-customer-action {
  color: #6b4106;
  font-weight: 700;
}
.request-status-label-parts {
  color: #1f4e79;
}
.request-status-label-complete {
  color: #1c6b3a;
}

/* ── Spec 0005 — mobile behaviour ────────────────────────────────────────────
   The dashboard is built for laptops; a phone is a courtesy. Both layouts are in the
   DOM and the stylesheet chooses, so rotating a phone cannot strand a stale layout and
   nothing here detects a device. Desktop is the default and is untouched. */
.narrow-screen-notice,
.daily-tiles {
  display: none;
}
.narrow-screen-notice h2 {
  margin: 0 0 8px;
  font-size: 17px;
}
.narrow-screen-notice p {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: color-mix(in srgb, var(--ink) 74%, var(--card));
}

@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  /* The register is replaced, not squeezed. */
  .requests-layout {
    display: none;
  }
  .narrow-screen-notice {
    display: block;
  }
  /* One tile per request instead of a seven-column table. */
  .updates-panel .table-scroll {
    display: none;
  }
  .daily-tiles {
    display: grid;
    gap: 10px;
  }
  /* All three tabs reachable, and the freshness stamp stays visible — a customer
     on a phone needs to know how old the figures are as much as anyone. */
  .app-header {
    flex-wrap: wrap;
    row-gap: 10px;
  }
  .dashboard-nav {
    order: 3;
    width: 100%;
    margin-left: 0;
    overflow: visible;
  }
  .nav-item {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    padding: 9px 4px;
  }
  .nav-item .nav-icon {
    display: none;
  }
  .nav-item span {
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .header-as-of {
    display: flex;
    margin-left: auto;
  }
  .header-as-of span {
    font-size: 10px;
  }
  /* Nothing scrolls sideways. */
  .app-shell,
  main {
    overflow-x: hidden;
  }
}

/* ── Screen-reader-only text (REVIEW.md R31) ─────────────────────────────────
   app.js has used .visually-hidden since the register was built — the "Details"
   column header, and the "changed to" that gives the status arrow a meaning in
   words — and the class was never defined, so every one of them rendered on
   screen at full size. This is the standard clip utility, defined once. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The three daily views do not fit one phone-width row, so they wrap rather than
   hiding the third behind a scrollbar. */
@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  .updates-panel .panel-heading {
    flex-wrap: wrap;
  }
  .segmented-control {
    flex-wrap: wrap;
    width: 100%;
  }
  .segmented-control button {
    flex: 1 1 auto;
    justify-content: center;
  }
}

/* One label of the two, never both. Desktop keeps the full wording it has always had.
   `.overview-daily-card-copy .daily-card-label-*`, not the bare class: `.overview-daily-card-copy
   span { display: block }` is (0,1,1) and a bare class alone is (0,1,0) — it would lose silently
   regardless of the media query, the same trap the notify chip was scoped to avoid (R15). */
.segment-label-short,
.overview-daily-card-copy .daily-card-label-short {
  display: none;
}
@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  .segment-label-full,
  .overview-daily-card-copy .daily-card-label-full {
    display: none;
  }
  .segment-label-short,
  .overview-daily-card-copy .daily-card-label-short {
    display: inline;
  }
  /* With three short labels the control fits one row again, so it stops wrapping. */
  .segmented-control {
    flex-wrap: nowrap;
  }
}

/* ── The daily tile ──────────────────────────────────────────────────────────
   Consolidated 2026-08-22, replacing three generations of appended rules — which is
   why it had seven type sizes, two competing bold lines and no clear rhythm
   (`REVIEW.md` R15 in miniature, and safe to do here because no build assertion pins
   these selectors).

   The tile answers four questions in order, and the design makes that order visible:

     which club and machine   identity, quiet
     what state it is in      the status, and what moved
     what that means          THE EMPHASIS — the grey band
     what the club reported   context, quietest, last

   One scale (10 / 11 / 12 / 13 / 15), one weight per level, and 6px inside a group
   against 12px between groups. Only the explanation carries full ink; everything else
   is muted in proportion to how far it sits from the thing the customer must act on. */
.daily-tile {
  --tile-pad: 16px;
  --tile-rule: 1px solid color-mix(in srgb, var(--ink) 11%, var(--card));
  border: 1px solid color-mix(in srgb, var(--ink) 13%, var(--card));
  border-radius: 14px;
  background: var(--card);
  overflow: hidden;
}

/* Identity and status share the top block, so the grey band below is the first edge. */
.daily-tile-body {
  padding: var(--tile-pad) var(--tile-pad) 14px;
}
.daily-tile-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.daily-tile-club {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.25;
}
.daily-tile-head .daily-tile-reference {
  flex: 0 0 auto;
  font-size: 11px;
  white-space: nowrap;
}
.daily-tile-product {
  margin: 3px 0 0;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  line-height: 1.35;
  color: color-mix(in srgb, var(--ink) 58%, var(--card));
}

/* The pill and the movement it describes sit on one wrapping line: they are one fact. */
.daily-tile-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 12px 0 0;
}
.daily-tile-previous {
  font-size: 11px;
  color: color-mix(in srgb, var(--ink) 52%, var(--card));
}
/* New's tile has no movement to lead with, so the pill needs its own plain-text introduction
   instead of relying on an arrow that isn't there (2026-08-26). Same muted weight as the arrow
   pairing above, so it reads as context rather than as its own heading. */
.daily-tile-state-label {
  font-size: 11px;
  color: color-mix(in srgb, var(--ink) 52%, var(--card));
}
.daily-tile-arrow {
  font-size: 11px;
  color: color-mix(in srgb, var(--ink) 40%, var(--card));
}
/* The emphasis, and the only tinted band, so the eye lands here first whatever else the tile
   contains. What moved and what it means share the band because they are one statement — a
   caption in muted ink, then the sentence in full. */
.daily-tile-explanation {
  padding: 12px var(--tile-pad) 13px;
  background: color-mix(in srgb, var(--surface) 46%, var(--card));
  border-top: var(--tile-rule);
}
.daily-tile-change {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.3;
  color: color-mix(in srgb, var(--ink) 60%, var(--card));
}
/* The same body style the register's expanded detail uses for the same sentence — regular
   weight, 1.5 line-height, full ink — so the two views render one voice.

   Why this paragraph looked like a different font, measured rather than guessed: neither
   "Google Sans" nor Inter is installed, so the whole page renders in `system-ui` — on macOS
   that is SF Pro, which ships *real* Medium and Semibold faces. Asking for `font-weight: 500`
   therefore pulled in SF Pro Medium, a genuinely different face from the Regular every other
   paragraph uses, sitting right next to it. Nothing was synthesised; the weight was simply off
   the design's scale, which is 400 and 700 and nothing between (2026-08-22).

   So the emphasis comes from the tinted band, from being the only full-ink text on the tile,
   and from the muted caption above it — never from a weight the rest of the page never uses. */
.daily-tile-meaning {
  margin: 4px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink);
}
/* "New service requests" has no movement to report, so the band opens with the meaning. */
.daily-tile-meaning:first-child {
  margin-top: 0;
}
/* And a movement with nothing to explain carries the band on its own. */
.daily-tile-change:only-child {
  font-size: 13px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink);
}

/* The club's own words, last and quietest. Labelled, because the tile otherwise ends with
   two paragraphs in different voices and no way to tell whose is whose — and labelled with
   the same words as the desktop column, so the two views agree. */
.daily-tile-reported {
  padding: 11px var(--tile-pad) 13px;
  border-top: var(--tile-rule);
}
.daily-tile-label {
  display: block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ink) 45%, var(--card));
}
.daily-tile-reported p {
  margin: 3px 0 0;
  font-size: 12px;
  line-height: 1.45;
  white-space: pre-line;
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
}

/* ── Attention: "waiting on you" (spec 0011, option C) ───────────────────────
   Amber, never red: red is the accent, the active tab and "Out of order" already, and
   `REVIEW.md` R26 is the record of one accent carrying three meanings going wrong.

   Edge and band moved to Claude orange 2026-08-28, replacing the original amber Timo felt read
   too pale next to the new violet announcement (`specs/0015`) — the band is now a light coral
   tint of the same orange as the edge, rather than an unrelated cream. Ink is untouched: it
   still clears every ratio below against the new band without needing to move.

   Every value below was measured with the same ratio function the build test uses, and
   two of them were wrong first time — the count badge at 2.16:1 and the edge below the
   3:1 a meaningful non-text indicator needs. A colour that looks right on a white card
   very often fails, so these are checked rather than picked:

     --attention-ink on --attention-band     5.34:1   text, AA
     --attention-ink on --surface            5.04:1   heading, AA
     white on --attention-ink                5.92:1   count badge, AA
     --attention-edge on --card              3.11:1   4px edge, WCAG 1.4.11

   Colour is never the only signal: the pill names the status, the caption names the
   action, and the heading names the group. */
:root {
  --attention-edge: #DA7756;
  --attention-band: #fbf1ee;
  /* A softer tint for the desktop table (2026-08-22). The same amber over a whole table row is
     a far larger area than a band inside a tile, so it reads much stronger at the same value —
     a tint has to get lighter as the area it covers grows. */
  --attention-band-soft: color-mix(in srgb, var(--attention-band) 55%, var(--card));
  --attention-band-rule: #f4d6cc;
  --attention-ink: #8a5a06;
}

/* The group headings, shared by both views. The quiet one has no count: "everything else"
   is a boundary, not a total worth reading. */
.daily-group-heading,
.daily-group-row > th {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--attention-ink);
  text-align: left;
}
.daily-group-heading {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 2px 0;
}
.daily-group-heading.is-quiet,
.daily-group-row.is-quiet > th {
  /* Darkened from 45% on 2026-08-22: quiet should still be readable, and at 45% it was fainter
     than the muted body copy it sits above. */
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
}
.daily-group-heading.is-quiet {
  margin-top: 12px;
}
.daily-group-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  margin-left: 8px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--attention-ink);
  color: var(--card);
  font-size: 10px;
  letter-spacing: 0;
}

/* The phone tile: an edge to find it by, the band tinted, and the action line in amber ink
   one size up — so it is the second thing read after the club name rather than the fourth. */
.daily-tile.is-waiting {
  border-left: 4px solid var(--attention-edge);
}
.daily-tile.is-waiting .daily-tile-explanation {
  background: var(--attention-band);
  border-top-color: var(--attention-band-rule);
}
.daily-tile.is-waiting .daily-tile-change {
  font-size: 12px;
  color: var(--attention-ink);
}

/* The desktop table, same idea in a table's vocabulary. */
.updates-table .daily-group-row > th {
  padding: 14px 10px 6px;
  background: var(--card);
}
.updates-table tbody tr.is-waiting > td {
  background: var(--attention-band-soft);
}
.updates-table tbody tr.is-waiting > td:first-child {
  box-shadow: inset 4px 0 0 var(--attention-edge);
}
.updates-table tbody tr.is-waiting .daily-update-summary {
  color: var(--attention-ink);
}

/* ── The attention rows: the count sits nearer its label ──────────────────────
   It was in a 44px column after `minmax(0, 1fr)`, so the label took every spare pixel and
   pushed the number hard right, against the button.

   Capping the label column was the first attempt and it moved the number **4px** — the panel
   is only 343px wide on a phone, so `1fr` was already about the same width as any sane cap.
   The columns cannot be tightened per row either: each row is its own grid, so a
   content-sized column would leave the two counts at different x positions.

   So the *panel* becomes the grid and the rows inherit its columns with `subgrid`. The label
   column is then `max-content` — the width of the longest label, whatever `statuses.ts` calls
   it — the counts align with each other directly after it, and the spare space moves to the
   right of the count where it belongs, carrying the button. Guarded, because subgrid is the
   one part of this that a browser can lack; without it the rows keep exactly today's
   behaviour. */
@supports (grid-template-columns: subgrid) {
  .overview-attention-panel.overview-attention {
    display: grid;
    /* `minmax(0, max-content)` rather than `max-content`: the label track takes the width of the
       longest label when there is room and **shrinks and wraps when there is not**, instead of
       squeezing the button column until the number overlaps it. On a 375px phone, once the cards
       added their own text inset, `max-content` did exactly that. Every row shares the track
       either way, so the counts stay in one column at any width.

       The third track is `minmax(max-content, 1fr)`, not `minmax(0, 1fr)`: with a zero minimum the
       *button* column is the one that collapses when space runs short — a flexible track gives up
       its space before an intrinsic one does — and the button then overflowed into the count.
       Making the button's own width the track's minimum forces the label to yield instead. */
    grid-template-columns: minmax(0, max-content) 38px minmax(max-content, 1fr);
    column-gap: 10px;
    align-content: start;
  }
  .overview-attention-panel.overview-attention > .panel-heading {
    grid-column: 1 / -1;
  }
  /* The empty state has no row to inherit the subgrid's columns, so without this it auto-placed
     into the label column alone — 161px wide, pinned to the top of an otherwise empty card
     (found from a screenshot, 2026-08-25). Spans every column, and `1fr` on its own row lets it
     claim the panel's full remaining height so the message can centre in the whole card rather
     than just the top of it. Scoped to `.overview-attention-empty` so the populated grid above is
     untouched. */
  .overview-attention-panel.overview-attention.overview-attention-empty {
    grid-template-rows: auto 1fr;
  }
  .overview-attention-panel.overview-attention > .empty-state {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    text-align: center;
    margin: 0;
    padding: 0;
  }
  .overview-attention-row {
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
  }
  /* Same reason as `.empty-state` just above: a direct child of the grid with no row of its own
     to inherit columns from auto-places into the first track alone, rather than spanning. */
  .overview-attention-panel.overview-attention > .overview-attention-announcement {
    grid-column: 1 / -1;
  }
}
.overview-attention-row > strong {
  text-align: left;
}

/* ── Overview on a phone: spec 0012, option 3 ────────────────────────────────
   The estate counts and the daily movement become one card, so the page is four blocks
   instead of five and everything actionable is in the first screen. Desktop is untouched:
   every rule below is inside the spec 0005 breakpoint.

   Measured before: 1585px on an 812px viewport, with the third KPI wrapping under a divider
   it did not belong to and three identical "View requests" buttons costing 396px. */
.overview-daily-caption {
  display: none;
}

@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  /* One card, two halves. The children give up their own backgrounds to it. */
  .overview-kpi-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
    padding: 14px;
    background: var(--card);
    border-radius: 12px;
  }
  .overview-kpi-duo,
  .overview-daily-card {
    grid-column: auto;
    min-height: 0;
    padding: 0;
    background: none;
    border: 0;
    box-shadow: none;
  }
  /* Three across, which is the fix for the orphan: the numbers are short enough. */
  .overview-kpi-duo {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
    padding-bottom: 12px;
  }
  .overview-kpi-duo > div + div {
    border-left: 1px solid color-mix(in srgb, var(--ink) 8%, var(--card));
  }
  .overview-kpi-duo strong {
    font-size: 24px;
    line-height: 1.1;
  }
  .overview-kpi-duo span {
    font-size: 11px;
  }
  /* The daily half: one strip, and the whole column is the tap target. Centred, not top-anchored
     (2026-08-25) — the updates chip wraps to two lines, so "Solved" and "New" need to centre in
     the same space rather than hug the top, or they read as sitting higher than the chip. Padding
     is symmetric for the same reason: an off-centre padding budget un-centres what justify-content
     just centred. */
  .overview-daily-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: relative;
    padding: 8px 4px;
    border-top: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
  }
  .overview-daily-card-copy {
    display: flex;
    flex-direction: column-reverse;
    align-items: center;
    gap: 2px;
  }
  .overview-daily-card-copy strong {
    margin-top: 0;
    font-size: 22px;
    line-height: 1.1;
  }
  .overview-daily-card-copy span {
    font-size: 11px;
  }
  /* Three identical buttons were the worst of the old layout. The button stays — it is the
     accessible, focusable control — and covers its own column instead of sitting under it, so
     the tap target is the whole cell and the visible label is the number the customer reads. */
  .overview-daily-card .overview-action-button {
    position: absolute;
    inset: 0;
    align-self: stretch;
    width: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    color: transparent;
    font-size: 0;
  }
  .overview-daily-card:focus-within {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
    border-radius: 8px;
  }
  /* The label for the half above it. Same shape as the group headings, lighter, so it names
     the group without competing with it. */
  .overview-daily-caption {
    display: block;
    grid-column: 1 / -1;
    padding-top: 10px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    text-align: center;
    color: color-mix(in srgb, var(--ink) 38%, var(--card));
  }
  /* Item 5: panel padding and headings are tuned for a 1280px column. */
  .panel {
    padding: 14px;
  }
  .panel-heading h2 {
    font-size: 15px;
  }
}

/* ── Where one group ends and the next begins (2026-08-22) ───────────────────
   The headings said it and the boundaries did not, which is fine when you are looking for
   the split and not when you are scrolling past it. Both groups are now ruled off top and
   bottom, in the same weight the register uses to close an expanded row — strong enough to
   read as a boundary, not strong enough to become a table. */
.daily-tile-section {
  display: grid;
  gap: 10px;
  padding: 10px 0 14px;
  border-top: 1px solid color-mix(in srgb, var(--ink) 24%, var(--card));
}
/* The trailing 14px above separates one section from the next — after the *last* section there
   is nothing to separate from but the legend, which already carries its own gap (2026-08-26,
   Timo's call: the Updates tab, the only mode grouped into sections, was leaving visibly more air
   above the legend than Solved or New). */
.daily-tile-section:last-child {
  padding-bottom: 0;
}
/* The closing rule after "Everything else" made sense before each tile had its own border
   (2026-08-22) — now every tile is already its own bordered card, so a line closing the list
   underneath them just repeats what the last tile's own edge already says (Timo, 2026-08-25). */
.daily-tile-section .daily-group-heading {
  margin: 0;
}
.updates-table .daily-group-row > th {
  border-top: 1px solid color-mix(in srgb, var(--ink) 24%, var(--card));
}
.updates-table tbody tr.is-group-end > td {
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 24%, var(--card));
}

/* Count position 2 (chosen 2026-08-22): directly after the longest label plus a 24px breath.
   Width-independent, unlike the rule this replaced — see spec 0012. */
@supports (grid-template-columns: subgrid) {
  .overview-attention-row > span {
    padding-right: 24px;
  }
}

/* Caption version B: underneath, outside the card, so the card holds only numbers. */
@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  .overview-daily-caption {
    padding-top: 8px;
    padding-bottom: 2px;
  }
}



/* ── The summary card's two rows read as one grid (2026-08-22) ────────────────
   They were aligned all along — cell centres at 79 / 178 / 277 in both rows, measured — and it
   still looked sloppy, for two reasons a measurement does not show. The horizontal rule was
   three separate per-card borders, each carrying the desktop card's 8px radius, so it had
   little rounded hooks at both junctions instead of being one line. And the top row had
   vertical dividers where the bottom row had none, so the halves did not read as one grid.
   One continuous rule, dividers in both rows, no radius on a cell that is no longer a card. */
@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  .overview-kpi-duo {
    border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, var(--card));
  }
  .overview-daily-card {
    border-top: 0;
    border-radius: 0;
  }
  .overview-daily-card + .overview-daily-card {
    border-left: 1px solid color-mix(in srgb, var(--ink) 8%, var(--card));
  }
}

/* ── The waiting notice above the table (version D) ──────────────────────────
   One sentence, once, where the section headings used to repeat themselves. The count badge
   and the sentence both carry the number: I read that as a defect and removed the badge, but it
   is the version that was picked as built, so it is back (2026-08-22). */
.updates-waiting-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
  padding: 10px 12px;
  border: 1px solid var(--attention-band-rule);
  border-radius: 8px;
  background: var(--attention-band-soft);
  font-size: 13px;
}
.updates-waiting-notice strong {
  color: var(--attention-ink);
}
.updates-waiting-notice .daily-group-count {
  margin-left: 0;
}
/* ── Overview's attention rows as notifications (spec 0011 option 4, 2026-08-22) ─
   Each row becomes its own amber card, and the counts stay in one column across rows.

   That alignment is the whole difficulty, and it decides how the card is drawn. The rows are
   `subgrid` items taking the panel's three tracks, and **a horizontal inset on a subgrid item
   shifts its tracks** — so a card built the obvious way, with `padding: 10px 12px` and a
   `border-left`, sizes its own columns and the numbers stop lining up. That is exactly what the
   mock did, and why its two numbers sat at different x positions.

   So the card takes no horizontal padding and no left border:
     - the amber edge is an inset box-shadow, which costs no layout at all — the same trick the
       desktop table rows already use;
     - the text inset lives on the *cells* rather than on the card, so it moves the content
       inside a track instead of moving the track.
   The middle track stays 44px wide for every row, so the number is always in the same place. */
.overview-attention-row.is-attention-card {
  padding: 10px 0;
  border-radius: 8px;
  background: var(--attention-band-soft);
  box-shadow: inset 4px 0 0 var(--attention-edge),
    inset 0 0 0 1px var(--attention-band-rule);
}
.overview-attention-row.is-attention-card + .overview-attention-row.is-attention-card {
  margin-top: 8px;
}
.overview-attention-row.is-attention-card > span {
  padding-left: 12px;
}
/* The card's insets plus a full-size button do not leave "Waiting for callback" a single line in
   a 343px panel — measured, it wrapped at 360, 375 and 1280px. The button carries the compact
   size inside a card, which returns about 18px and is enough at every width tested. */
.overview-attention-row.is-attention-card > .overview-action-button {
  margin-right: 10px;
  padding: 6px 9px;
  font-size: 11px;
}
/* The panel's own row rules would double up with the card edges. */
.overview-attention-row.is-attention-card,
.overview-attention-row.is-attention-card.attention-card-waiting-callback,
.overview-attention-row.is-attention-card.attention-card-pro-forma {
  border-top: 0;
  border-bottom: 0;
}

/* ── Open requests: the register borrows the Daily updates row treatment ──────
   Desktop only by construction — the register is not rendered below the breakpoint at all
   (`specs/0005`), so this needs no media query. It keys off `.request-row-customer-action`,
   which `app.js` has always emitted from `statusTone` and which `app.test.ts` binds to
   `STATUS_CONFIG`, so a status can only get the tint if `statuses.ts` says it needs the
   customer. No notice above the table: the register is for reading the estate, and one amber
   sentence per page is enough (2026-08-22). */
.request-register tbody tr.request-row-customer-action > td {
  background: var(--attention-band-soft);
}
.request-register tbody tr.request-row-customer-action > td:first-child {
  box-shadow: inset 4px 0 0 var(--attention-edge);
}
.request-register tbody tr.request-row-customer-action .request-status-label-customer-action {
  color: var(--attention-ink);
}
/* The hover tint has to stay visible over the amber, or the row stops responding to the mouse. */
.request-register tbody tr.request-row-customer-action:hover > td {
  background: color-mix(in srgb, var(--attention-band) 70%, var(--card));
}

/* ── "Needs your attention": less air inside, and the space it keeps is at the foot ───
   The panel sits in a grid row with the chart, so it is as tall as its neighbour whatever it
   contains; `align-content: start` puts that slack at the bottom, which is where it is wanted.
   What was wasteful was the air *between* things (2026-08-22). */
/* The heading keeps the standard 10px every other panel uses, so "Needs your attention" is spaced
   like "Open requests by region" beside it. Reverted 2026-08-22: tightening it to 4px was the one
   part of the trim that went too far — the air *between the rows* was the waste, not the air under
   the title. */
/* The panel is a grid, so its own `row-gap` was adding to the cards' margins — 10 + 6 measured as
   a 16px gap where 6 was asked for. Spacing between rows lives in one place: the margin. */
.overview-attention-panel.overview-attention {
  row-gap: 0;
}
.overview-attention-row {
  padding: 7px 0;
}
.overview-attention-row.is-attention-card {
  padding: 8px 0;
}
.overview-attention-row.is-attention-card + .overview-attention-row.is-attention-card {
  margin-top: 6px;
}

/* ── The chart fills its panel instead of floating in the top of it ───────────
   Measured at 1280px before this: the chart panel was 319px tall, its content ended at 188px,
   and there were **64px of dead space inside `.chart-wrap`** plus 75px between the date labels
   and the foot of the panel. The panel is stretched to match its neighbours in the analytics
   row — that part is right, panels should line up — but the plot was not taking the space, so
   the chart sat in the upper half of an otherwise empty card.

   The fixed `height` on `.chart-wrap` was what pinned it (230px, overridden to 252px later).
   Replaced with a column that grows: the panel is a flex column, the wrap fills it, and the
   plot takes whatever is left after the labels. A floor keeps it sane if the panel is ever
   short. The axis values were already aligned to their gridlines within half a pixel, so
   nothing there needed touching (2026-08-22). */
.overview-chart {
  display: flex;
  flex-direction: column;
}
.overview-chart .chart-wrap {
  /* Hugs its own content — the fixed 230/252px was the 64px of dead space — and is centred in
     whatever height the analytics row gives the panel, so the chart sits in the middle of the
     card rather than pinned to the top of it with 75px underneath. */
  height: auto;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-top: 0;
}
.overview-chart .chart-labels {
  padding-top: 8px;
}
/* Not `height: 100%` on the svg: in a flex column of indefinite height that resolves against the
   1:1 viewBox, and the chart became as tall as it was wide — 406px at 1280px, which grew the
   panel from 319 to 499. A definite height keeps it predictable. */
.overview-chart .chart-plot {
  height: 190px;
}
.overview-chart .chart-wrap svg {
  height: 190px;
}

/* ── The daily strip reads as buttons on a phone (spec 0012 open question 4, option B) ──
   On desktop the three cards still carry a visible "View requests" button, so nothing there
   needed changing. On a phone those buttons became invisible overlays covering their column —
   good for the tap target, silent about being tappable, with the number the only thing on
   screen. Outlining each column says it without words, and without the three repeated labels
   that were removed in the first place (2026-08-22). */
@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  .overview-kpi-grid {
    /* The tiles need air between them, and their own borders replace the dividers. */
    column-gap: 8px;
    row-gap: 0;
  }
  /* The trio spans all three columns *and the gaps between them*, so it divides a wider box into
     three — while the tiles sit in the three narrower columns. Same grid, two different column
     widths, and the centres drifted 3px apart. Giving the trio the same gap makes both rows use
     one geometry. Measured after: centres identical to the pixel in both rows (2026-08-22). */
  .overview-kpi-duo {
    column-gap: 8px;
  }
  .overview-daily-card {
    margin-top: 12px;
    padding: 10px 4px;
    border: 1px solid color-mix(in srgb, var(--ink) 16%, var(--card));
    border-radius: 10px;
    background: color-mix(in srgb, var(--surface) 35%, var(--card));
  }
  /* Those two carried the old flat-cell look and would fight the tile. */
  .overview-daily-card + .overview-daily-card {
    border-left: 1px solid color-mix(in srgb, var(--ink) 16%, var(--card));
  }
  .overview-kpi-duo {
    border-bottom: 0;
    padding-bottom: 0;
  }
  /* A pressed state, because a tile that looks like a button should behave like one. */
  .overview-daily-card:active {
    background: color-mix(in srgb, var(--surface) 70%, var(--card));
  }
}

/* ── "Needs your attention": one row geometry, amber or not (2026-08-22) ──────
   Reported: with one row amber and one not, "they don't align", and the rule between them
   is ugly. Measured at 1280px before this, and both halves of that are true:

     row              label text x   button right x   button height   gap to next row
     plain            66             380              24              —
     amber card       78             370              27              0

   Two separate causes.

   *Alignment.* The card's insets were hung on `.is-attention-card` — 12px of `padding-left` on
   the label, 10px of `margin-right` and a compact size on the button. They are what makes a card
   look like a card, so they only applied to the amber row, and the plain row above it sat 12px
   to the left with a button 10px further right and 3px shorter. The fix is that the insets are
   the *panel's* row geometry, not the card's decoration: every row carries them, and going amber
   changes only fill and edge. Note this is why the card still takes no horizontal padding of its
   own — it is a subgrid item, and a horizontal inset on one would shift that row's tracks and
   move its number out of the count column (see the block above).

   *The rule.* `margin-top` was on `.is-attention-card + .is-attention-card`, so two amber cards
   were 6px apart and every other combination was 0px apart — the rule was drawn on the bottom
   edge of the row above and the card's ring started on the very next pixel. So the gap belongs
   between any two rows, and the rule is centred in it rather than attached to either row: 8px of
   air, the hairline, 8px of air. It is now visible in all three states (both quiet, one amber,
   both amber) instead of being crushed in two of them.

   Selectors are prefixed with the panel so they outrank the `.overview-attention-row.attention-
   card-*` and `.is-attention-card > *` generations above without editing them (REVIEW.md R15). */
.overview-attention-panel .overview-attention-row {
  position: relative;
  padding: 8px 0;
  border-top: 0;
  border-bottom: 0;
}
/* 12px in front of the text, and the breath behind it cut from 24px to 12px so the pair still
   totals 24px. That total is what sizes the label track, and the track is what decides both where
   the count lands and whether the longest label fits on one line — so keeping it unchanged keeps
   spec 0012's count offset (170px from the panel edge, width-independent) and keeps "Waiting for
   callback" on one line. Taking the 12px purely as extra `padding-left` did neither: measured, the
   count moved to 173.6px and the label wrapped to two lines at 375px *and* at 1280px. */
.overview-attention-panel .overview-attention-row > span {
  padding-left: 12px;
  padding-right: 12px;
}
.overview-attention-panel .overview-attention-row > .overview-action-button {
  margin-right: 10px;
  padding: 6px 9px;
  font-size: 11px;
}
/* Both selectors, because the card-to-card `margin-top: 6px` above is four classes deep and
   outranks the plain one: measured, the both-amber state kept its 6px gap while every other
   state took 16px, which put the centred rule 2px inside the card above it. */
.overview-attention-panel .overview-attention-row + .overview-attention-row,
.overview-attention-panel .overview-attention-row.is-attention-card + .overview-attention-row.is-attention-card {
  margin-top: 16px;
}
/* Every row is a tile, and only the fill says which one needs acting on (asked for 2026-08-22:
   "apply the border to the none-updated one too, similar to the new requests tile"). The border,
   radius and fill are the ones `.overview-daily-card` already uses, so the two groups on the page
   are made of the same object.

   It is an inset `box-shadow`, not a `border`, for the reason the amber card gives above: these
   rows are subgrid items, and a real horizontal border would size that row's own tracks and take
   its count out of the shared column. A shadow costs no layout.

   The hairline that used to sit in the gap is gone with it. Two bordered tiles already read as two
   things; a rule between them was a third edge in 16px of space. The air stays. */
.overview-attention-panel .overview-attention-row:not(.is-attention-card) {
  border-radius: 8px;
  background: color-mix(in srgb, var(--surface) 38%, var(--card));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 9%, var(--card));
}


/* ── Daily updates tiles: where one tile ends and the next begins (2026-08-22) ─
   Asked after reading a phone: it is hard to see the end of a tile. Measured, and it is not a
   matter of taste — the tile is `#FFFFFF` on a `#FFFFFF` panel, and its only boundary is a 1px
   border at **1.32:1** against that ground. Worse, the outer border is `--ink 13%` while the rules
   *inside* the tile are `--ink 11%`: the edge of the object is no stronger than its own internal
   divisions, so a 248px tile reads as four loose bands rather than one card.

   Two changes, because a shadow alone would not have fixed it. The gap was 10px on a 248px tile,
   and a shadow needs somewhere to fall: at 10px the shadow of one tile lands on the next and reads
   as a smudge instead of a separation. So the gap goes to 16px and the shadow is soft and short.
   Tiles are a phone-only rendering — `.daily-tiles` is `display: none` until this breakpoint — so
   this cannot touch the desktop table. */
@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  /* `.daily-tiles` spaces the *sections*; the tiles themselves are grid items of
     `.daily-tile-section`, which carries its own 10px. Measured after the first attempt: setting
     the gap on `.daily-tiles` alone changed nothing at all between two tiles in the same group. */
  .daily-tiles,
  .daily-tile-section {
    gap: 16px;
  }
  .daily-tile {
    box-shadow: 0 1px 2px color-mix(in srgb, var(--ink) 8%, transparent),
      0 3px 10px color-mix(in srgb, var(--ink) 7%, transparent);
  }
}

/* The chart overlay — the endpoint, the hover cursor and its label (2026-08-28). All of it sits in
   HTML rather than in the SVG on purpose: the plot uses `preserveAspectRatio="none"`, so the box is
   stretched horizontally and anything round drawn inside it comes out as an ellipse. Percentages on
   an absolutely positioned overlay map to exactly the same coordinates the polyline uses, because
   the stretch is linear. */
.chart-canvas {
  position: relative;
}
.chart-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.chart-latest,
.chart-marker {
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  transform: translate(-50%, -50%);
}
.chart-marker {
  width: 9px;
  height: 9px;
  background: var(--card);
  border: 2px solid var(--accent);
}
.chart-latest-value {
  position: absolute;
  transform: translate(-100%, -50%);
  margin-left: -10px;
  padding: 1px 4px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  color: var(--accent);
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.chart-cursor {
  position: absolute;
  top: 12%;
  bottom: 12%;
  width: 1px;
  background: color-mix(in srgb, var(--ink) 22%, var(--card));
}
.chart-tooltip {
  position: absolute;
  transform: translate(12px, -50%);
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 4px 7px;
  border-radius: 4px;
  background: var(--ink);
  color: var(--card);
  font-size: 11px;
  line-height: 1.3;
  white-space: nowrap;
  box-shadow: 0 2px 8px color-mix(in srgb, var(--ink) 22%, transparent);
}
.chart-tooltip[data-side="left"] {
  transform: translate(calc(-100% - 12px), -50%);
}
.chart-tooltip b {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.chart-tooltip span {
  color: color-mix(in srgb, var(--card) 78%, var(--ink));
  font-variant-numeric: tabular-nums;
}
.chart-month-grid {
  stroke: color-mix(in srgb, var(--ink) 10%, var(--card));
  stroke-width: 0.4;
  stroke-dasharray: 2 2;
}
/* `[hidden]` is a UA rule of `display: none`, and any `display` in an author stylesheet beats it —
   so `.chart-tooltip { display: flex }` above made the hidden tooltip render as a black block in the
   top-left corner of the panel. Found by looking at the built page, not by reading the CSS, which is
   the only way this class of bug ever turns up. */
.chart-tooltip[hidden],
.chart-cursor[hidden],
.chart-marker[hidden] {
  display: none;
}
/* The follow-up sentence needs more than a sixth of the row: "This is a follow-up ticket of
   JDK062603629" is about 300px of text, and a single column of the six-column facts grid is around
   170px at 1280. Two columns fit it on one line and leave the row balanced (2026-08-28). */
.detail-facts .detail-follow-up {
  grid-column: span 2;
}

/* The regional panel's second layer arrives on a sheet over the panel — option 4 of the five
   prototyped on 2026-08-28. The version before it expanded inline, which grew the panel from 279px to
   759px on the worst region and dragged the chart panel beside it along. A sheet leaves every height
   alone: nothing below the panel moves, whatever is inside it.

   The panel needs `position: relative` for the sheet to cover it, and `overflow: hidden` so the sheet
   slides in from the panel's own edge rather than from the page's. */
.overview-regions {
  position: relative;
  overflow: hidden;
}
/* The row is name — control — count, so the flex row needs the name to take the slack and the
   control to sit next to it rather than drifting to the middle. */
.status-list li.region-row {
  align-items: center;
}
.region-row .region-name {
  margin-right: auto;
}
.region-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 18px circle, 24px target: the padding is what makes it a comfortable hit area without making
     the circle look heavy next to the region name. */
  padding: 3px;
  margin-left: 8px;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.region-chevron {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--ink) 20%, var(--card));
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  transition: border-color 120ms ease, color 120ms ease;
}
.region-toggle:hover .region-chevron,
.region-toggle:focus-visible .region-chevron {
  border-color: var(--accent);
  color: var(--accent);
}
.region-sheet {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: var(--card);
  border-radius: 14px;
  padding: 18px 20px 20px;
  box-shadow: -10px 0 24px -14px color-mix(in srgb, var(--ink) 40%, transparent);
  animation: region-sheet-in 200ms cubic-bezier(.4, 0, .2, 1);
}
/* `[hidden]` is a UA rule of `display: none`, and any author `display` beats it — the same trap the
   chart tooltip fell into on 2026-08-28, when a hidden tooltip rendered as a black block. */
.region-sheet[hidden] {
  display: none;
}
@keyframes region-sheet-in {
  from { transform: translateX(12%); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
/* Sized to the panel heading it covers — 20px tall, 10px below it — so the first area row lands on
   exactly the same line as the first region row. Measured before and after: the head was 24px with a
   12px margin, which put the rows 6px lower than the ones they replace, and the sheet read as a
   different card rather than the same one showing different rows.

   The close button stays 24px and is allowed to overflow the 20px row by 2px either side. A 20px
   target to keep the arithmetic tidy would be a worse trade than two pixels of overflow inside a
   panel that has 18px of padding around it. */
.region-sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  height: 20px;
  margin-bottom: 10px;
}
#region-sheet-title {
  font-size: 14px;
  font-weight: 800;
}
.region-sheet-count {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.region-sheet-close {
  flex: none;
  width: 24px;
  height: 24px;
  margin: -2px 0;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--ink) 18%, var(--card));
  background: none;
  color: color-mix(in srgb, var(--ink) 62%, var(--card));
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.region-sheet-close:hover {
  border-color: var(--accent);
  color: var(--accent);
}
/* The same rhythm as the region list it covers — gap, divider and padding all identical — so the
   sheet reads as the same panel showing different rows rather than as a different component.

   `align-content: start` is the whole of the layout fix made on 2026-08-28. A grid's default
   `align-content: stretch` distributes the container's spare height into its auto rows, so three
   areas inside a five-row panel came out spread across the whole card with gaps a reader had to
   look past. Rows now keep their own height and stack from the top, and the card simply has space
   underneath.

   No scrolling and no `flex: 1`: five rows is the cap (`dashboard-data.ts`), which is exactly the
   height of the five-region list behind it. */
.region-sheet-areas {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
  align-content: start;
}
.region-sheet-areas li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, var(--card));
  padding-bottom: 11px;
  color: color-mix(in srgb, var(--ink) 68%, var(--card));
}
.region-sheet-areas li:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}
.region-sheet-areas strong {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* Desktop only (Timo, 2026-08-28). Hiding the control is the whole mechanism: a `display: none`
   button cannot be clicked, cannot be tabbed to, and cannot be activated by a keyboard — so the
   sheet has no way to open on a phone, and the row still reads as a name and a number. The
   breakpoint is `specs/0005`'s one breakpoint, landscape phones included. */
@media (max-width: 767px), (orientation: landscape) and (max-height: 500px) {
  .region-toggle {
    display: none;
  }
  .region-sheet {
    display: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .region-chevron {
    transition: none;
  }
  .region-sheet {
    animation: none;
  }
}
