/* ==========================================================================
   C/M to JOB SHEET — design system
   Brand: Rollashield Shutters (rollashieldshutters.com.au)
   Palette sampled from the live site + logo pixels: the real brand is a
   vivid blue (#0200fc, logo #0000f0 family) on a white/charcoal base, with
   greys for structure. Red is reserved here for hard-stop / danger only,
   which the blue brand frees it to mean unambiguously.

   Every component consumes the tokens below — no hardcoded colours.
   ========================================================================== */

:root {
  /* --- Brand (blue) — restrained: primary actions, active states, accents - */
  --brand: #0200fc;          /* real Rollashield blue */
  --brand-hover: #0100cc;    /* darker for hover */
  --brand-active: #0a0aa0;   /* darker still for pressed */
  --brand-tint: #ecebff;     /* wash for active nav / selected rows */
  --brand-ring: rgba(2, 0, 252, 0.35);

  /* --- Neutrals (charcoal + greys) --------------------------------------- */
  --ink: #1b2431;            /* primary text — cool charcoal */
  --ink-soft: #566072;       /* secondary text / labels */
  --ink-faint: #8b93a1;      /* placeholders / hints */
  --surface: #ffffff;        /* cards, inputs, table body */
  --surface-2: #f7f9fc;      /* subtle fills, zebra, table head */
  --bg: #eef1f6;             /* app background */
  --border: #d7dde6;         /* default borders */
  --border-strong: #c2cad6;  /* input borders / dividers */

  /* --- Header bar --------------------------------------------------------- */
  --topbar-bg: #151b26;      /* near-black charcoal with a cool undertone */
  --topbar-fg: #ffffff;
  --topbar-fg-soft: #b7c0d0;

  /* --- Semantic colours (harmonised with the palette) -------------------- */
  --success: #1f7a44;
  --success-bg: #e7f4ec;
  --success-border: #b4dcc4;
  --warning: #8a5a00;
  --warning-bg: #fcf3d9;
  --warning-border: #ecca77;
  --danger: #c0392b;
  --danger-bg: #fbe6e3;
  --danger-border: #ecb1aa;

  /* --- Review line-item colour coding ------------------------------------ */
  /* Tick pills: hue carries meaning, so a reviewer learns "amber = pelmet"
     rather than re-reading the label on every row. */
  --tick-g: #0200fc;         /* +G guides — width-critical, brand blue */
  --tick-spring: #6d3bd4;    /* SPRING — changes the drive */
  --tick-s: #0f7c8a;         /* +S plus slat */
  --tick-b: #a8620a;         /* +B plus pelmet */
  --tick-sill: #1f7a44;      /* SILL */
  --tick-caps: #4a5568;      /* +CAPS */
  --tick-rev: #4a5568;       /* REV BOLTS L / R */
  /* MOTOR SIDE L / R sits immediately right of REV BOLTS L / R and carries the
     same two letters, so it gets its own strong hue rather than the rev greys:
     the one mistake this pair invites is reading one row as the other. Magenta,
     not the SPRING violet — motor SIDE isn't a drive-type change. */
  --tick-motor: #9b2c6f;     /* MOTOR SIDE LHS / RHS */

  /* DROP-band tint by +B/+S combination — the three cases must be told apart
     from across the desk, not by reading two pills. */
  --combo-bs: #efe6fb;       /* both  */
  --combo-bs-line: #b79ae4;
  --combo-b: #fbf0dd;        /* +B only */
  --combo-b-line: #e0b978;
  --combo-s: #e0f2f4;        /* +S only */
  --combo-s-line: #8fc7ce;

  /* A non-zero minus/plus correction box */
  --corr-bg: #fff3d6;
  --corr-line: #e6b95c;
  --corr-ink: #7a4d00;

  /* --- Type scale -------------------------------------------------------- */
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, sans-serif;
  --text-xs: 0.8125rem;   /* 13px */
  --text-sm: 0.9rem;      /* ~14px */
  --text-base: 1rem;      /* 16px */
  --text-lg: 1.125rem;
  --text-xl: 1.375rem;
  --text-2xl: 1.75rem;
  --leading: 1.5;

  /* --- Spacing scale (4px base) ------------------------------------------ */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-8: 3rem;

  /* --- Radius ------------------------------------------------------------ */
  --radius-sm: 6px;
  --radius: 10px;
  --radius-lg: 14px;
  --radius-pill: 999px;

  /* --- Shadows (soft, cool-tinted) --------------------------------------- */
  --shadow-sm: 0 1px 2px rgba(21, 27, 38, 0.06);
  --shadow: 0 1px 3px rgba(21, 27, 38, 0.08), 0 4px 12px rgba(21, 27, 38, 0.05);
  --shadow-lg: 0 8px 24px rgba(21, 27, 38, 0.12);
}

/* --- Reset / base --------------------------------------------------------- */
* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading);
  margin: 0;
  /* Deep-space backdrop matching the hero art — the flying-neutrons canvas
     (#neutron-bg) paints over this, and page content floats above both. */
  background:
    radial-gradient(ellipse 120% 80% at 50% 12%, #0b1224 0%, #070a14 45%, #04050b 100%);
  background-attachment: fixed;
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

/* --- Flying-neutrons background canvas ------------------------------------ */
#neutron-bg {
  position: fixed;
  top: 0;
  left: 0;
  /* canvas is a replaced element — it needs an explicit size or it falls back
     to its intrinsic 300x150. width/height:100% of the fixed viewport fills it
     exactly, and (unlike 100vw/vh) excludes the scrollbar so there's no
     horizontal overshoot. */
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  display: block;
}
/* Lift every real layer above the starfield. */
body > header,
body > main { position: relative; z-index: 1; }

/* --- Hero image ----------------------------------------------------------- */
/* screen blend drops the art's black background into the live starfield, so
   its glowing icons + neutrons read as part of the page-wide field. */
.hero {
  margin: 0 0 var(--space-6);
  text-align: center;
}
.hero-img {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
  mix-blend-mode: screen;
  /* Fade the rectangular edges so the art dissolves into the backdrop. */
  -webkit-mask-image: radial-gradient(ellipse 82% 82% at 50% 50%, #000 55%, transparent 100%);
  mask-image: radial-gradient(ellipse 82% 82% at 50% 50%, #000 55%, transparent 100%);
}
@media (prefers-reduced-motion: no-preference) {
  .hero-img { animation: hero-in 0.8s ease-out both; }
}
@keyframes hero-in {
  from { opacity: 0; transform: scale(0.985); }
  to   { opacity: 1; transform: scale(1); }
}

/* --- Content readability on the dark backdrop ----------------------------- */
/* Only text that sits DIRECTLY on the starfield is lightened; cards keep
   their own dark-on-light treatment (they have opaque light surfaces). */
/* Page-level prose sits directly on the dark backdrop, so it must be light.
   Applies to direct children of <main> AND of the transparent full-width page
   wrappers (.prod-page / .stn-page / .dash-page / .dash-wrap) that the
   production, station and dashboard screens nest their content in — otherwise
   their headings/intro text fall back to dark --ink and vanish on the near-black
   background. Text inside opaque cards/tables is nested deeper and keeps its own
   dark-on-light treatment. */
main > h1, main > h2,
.prod-page > h1, .prod-page > h2,
.stn-page > h1, .stn-page > h2,
.dash-page > h1, .dash-page > h2 { color: #eef2fb; }

main > p,
.prod-page > p,
.stn-page > p,
.dash-wrap > p { color: var(--topbar-fg-soft); }

main > p a,
.prod-page > p a,
.stn-page > p a,
.dash-wrap > p a { color: #6ea8ff; }
main > p a:hover,
.prod-page > p a:hover,
.stn-page > p a:hover,
.dash-wrap > p a:hover { color: #9cc3ff; }

h1 {
  font-size: var(--text-2xl);
  line-height: 1.2;
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-5);
}
h2 { font-size: var(--text-xl); line-height: 1.25; margin: 0 0 var(--space-4); }

a { color: var(--brand); text-decoration: none; }
a:hover { color: var(--brand-hover); text-decoration: underline; }

/* Visible focus for keyboard users across every interactive element */
:focus-visible {
  outline: 3px solid var(--brand-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* --- Top bar -------------------------------------------------------------- */
.topbar {
  background: var(--topbar-bg);
  color: var(--topbar-fg);
  padding: var(--space-3) var(--space-4);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
}
.topbar .brand {
  font-weight: 700;
  letter-spacing: 0.01em;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
/* Small brand mark in the real Rollashield blue */
.topbar .brand::before {
  content: "";
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 3px;
  background: var(--brand);
  box-shadow: 0 0 0 3px rgba(2, 0, 252, 0.25);
}
.topnav { display: flex; align-items: center; gap: var(--space-4); }
.topnav a {
  color: var(--topbar-fg-soft);
  text-decoration: none;
  font-size: var(--text-sm);
  font-weight: 500;
}
.topnav a:hover { color: var(--topbar-fg); text-decoration: none; }

main {
  /* Wide content column, shared by every tab (jobs, forms, production,
     station, dashboard) so nothing is strangled into horizontal scroll.
     Inner page containers that want to stay narrower set their own width. */
  max-width: 1400px;
  margin: 0 auto;
  padding: var(--space-5) var(--space-4) var(--space-8);
}

/* --- Cards / fieldsets ---------------------------------------------------- */
fieldset {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  margin: 0 0 var(--space-5);
  padding: var(--space-5);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
fieldset.sub {
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: var(--space-4);
  box-shadow: none;
}
legend {
  /* float + full width makes the legend a normal in-card title instead of
     straddling the top border (where it half-overlapped the dark backdrop). */
  float: left;
  width: 100%;
  margin: var(--space-1) 0 var(--space-3);
  font-weight: 600;
  font-size: var(--text-sm);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding: 0;
}
/* Content after the floated legend starts on its own line, below it. */
legend + * { clear: both; }
fieldset.sub legend { text-transform: none; letter-spacing: 0; color: var(--ink); font-size: var(--text-base); }

/* --- Forms ---------------------------------------------------------------- */
label {
  display: block;
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink-soft);
}
label:last-child { margin-bottom: 0; }

input, select {
  display: block;
  width: 100%;
  padding: 0.6rem 0.7rem;
  margin-top: var(--space-2);
  font-size: var(--text-base);
  font-family: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
input::placeholder { color: var(--ink-faint); }
input:focus, select:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-ring);
}
/* Right-align numeric entry so digits line up as they're typed */
input[type="number"] { text-align: right; font-variant-numeric: tabular-nums; }

input[type="checkbox"] {
  display: inline-block;
  width: auto;
  margin: 0 var(--space-2) 0 0;
  accent-color: var(--brand);
  transform: scale(1.15);
}
/* :not(.tick) — the review page's tick pills are checkbox labels too, and
   :has()'s specificity (0,1,2 here) would otherwise beat .tick's own colour
   and font-weight and flatten the ticked/unticked contrast the scan relies on. */
label:has(input[type="checkbox"]):not(.tick) {
  display: flex;
  align-items: center;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
}

/* --- Job details: one-row field strip -------------------------------------
   This page is reviewed on a desktop, so the layout spends the width it has:
   all eleven header fields sit on ONE row, each column sized to the content
   it actually holds rather than a uniform auto-fill track. Caption tucked
   above a 30px input. Sits BELOW the openings (see job_form.html) — the
   measurements are what's being checked, the header is reference.
   -------------------------------------------------------------------------- */
.job-card { padding: var(--space-3) var(--space-4) var(--space-4); min-width: 0; }
.job-card > legend { margin: 0 0 var(--space-2); }

.job-grid {
  display: grid;
  /* Explicit per-field tracks: mins are what the caption + a realistic value
     need, the fr share hands the slack to the free-text fields. Total min is
     ~1253px incl. gaps, but the fallback below trips at 1400px rather than at
     that hard minimum: between the two, the row technically fits while the
     longest real values (LINDEN CONSTRUCTIONS, JAMES FRANKLIN) start to clip,
     and on a page whose job is reading values off a paper sheet, wrapping to
     a second row beats truncating one.
     Floors are set by REAL values seen on the test sheets — FAULCONBRIDGE,
     SURFMIST MATT, JAMES FRANLIN, LINDEN CONSTRUCTIONS. Retune against real
     data, not placeholders: every one of those clipped a first attempt. */
  grid-template-columns:
    minmax(140px, 1.2fr)   /* product    — "BLOCKOUT FZ 66G"          */
    minmax(160px, 1.6fr)   /* job name   — "LINDEN CONSTRUCTIONS"     */
    minmax(70px,  0.5fr)   /* job #      */
    minmax(150px, 1.4fr)   /* address    — "47 RAILWAY PARADE"        */
    minmax(115px, 1.2fr)   /* suburb     — "FAULCONBRIDGE"            */
    minmax(115px, 1.1fr)   /* contact    — "JAMES FRANLIN"           */
    minmax(100px, 0.8fr)   /* phone      — fixed-width content        */
    minmax(115px, 1.2fr)   /* colour     — "SURFMIST MATT"            */
    minmax(48px,  0.4fr)   /* by         — initials                   */
    minmax(62px,  0.5fr)   /* total qty  — 2-3 digits                 */
    minmax(58px,  0.4fr);  /* RTS motors */
  gap: var(--space-2) var(--space-3);
  align-items: end;
}

.job-grid .f {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  min-width: 0;
}
.job-grid .f > span:first-child {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.job-grid input,
.job-grid select {
  height: 30px;
  margin: 0;
  padding: 0.2rem 0.4rem;
  font-size: var(--text-sm);
  min-width: 0;
}
.job-grid select { padding-right: 0; }
.job-grid input[type="number"] { text-align: left; }
/* The job-wide RTS tick reuses the openings' pill so the two read as one
   language — purple, matching SPRING, since both are drive-type requests. */
.job-grid .tick { height: 30px; font-size: 11px; }
.tick-rts:has(input:checked), .tick-rts.is-on { background: var(--tick-spring); }

/* Below the one-row width the strip wraps rather than truncate: uniform
   tracks, with the long free-text fields taking two. Still ~180px, so even
   the fallback is a quarter of the original stacked layout. Not a phone
   layout — just a graceful fall rather than a broken one. */
@media (max-width: 1400px) {
  .job-grid {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  }
  .job-grid .w2 { grid-column: span 2; }
}

/* --- Opening line items ---------------------------------------------------
   The review page's job is a fast eyeball comparison against the paper sheet,
   so openings render as one dense line each rather than a card. Every column
   is a fixed width shared by the header and every row (--op-cols), which is
   what makes the vertical scan work: a tick that should be on and isn't shows
   as a hole in an otherwise solid column of colour.
   -------------------------------------------------------------------------- */
/* min-width:0 is load-bearing: a <fieldset> defaults to min-inline-size:
   min-content, so without it the 1256px grid widens the fieldset itself and
   the whole PAGE scrolls sideways instead of just the grid. */
.openings-card { padding: var(--space-4); min-width: 0; }
.openings-card > legend { margin-bottom: var(--space-2); }
.op-count {
  display: inline-block;
  margin-left: var(--space-1);
  padding: 0 0.4rem;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* Colour key ------------------------------------------------------------- */
.op-key {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  margin-bottom: var(--space-3);
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.op-key .key-item { display: inline-flex; align-items: center; gap: 0.35rem; }
.op-key .key-sep {
  width: 1px; height: 14px; background: var(--border-strong);
}
.op-key .swatch {
  width: 16px; height: 16px; border-radius: 4px;
  border: 1px solid var(--border-strong);
}
.op-key .sw-bs { background: var(--combo-bs); border-color: var(--combo-bs-line); }
.op-key .sw-b  { background: var(--combo-b);  border-color: var(--combo-b-line); }
.op-key .sw-s  { background: var(--combo-s);  border-color: var(--combo-s-line); }
.op-key .sw-corr { background: var(--corr-bg); border-color: var(--corr-line); }
.op-key .sw-notes { background: var(--danger-bg); border-color: var(--danger); border-width: 2px; }

/* Table shell ------------------------------------------------------------ */
/* Deliberately NOT a scroll container at desktop widths: `overflow-x: auto`
   computes `overflow-y` to `auto` too, which would capture the sticky column
   header and stop it pinning to the viewport. Horizontal scrolling only kicks
   in below the width where the 1256px grid stops fitting (see the media query
   at the bottom of this file), and the header goes static there. */
.op-scroll { min-width: 0; padding-bottom: 2px; }
.op-table {
  /* One source of truth for the column grid. Header + every row use it. */
  --op-cols:
    100px minmax(100px, 1.3fr) 44px         /* shutter # (+ thumb), location, qty */
    74px 44px 50px 50px 62px                /* width, +G, -, +, SPRING       */
    74px 42px 42px 50px 50px                /* drop, +S, +B, -, +            */
    50px 52px 36px 36px 36px 36px           /* SILL, CAPS, REV L/R, MOT L/R  */
    106px minmax(120px, 1fr) 30px;          /* drive, notes, remove          */
  min-width: 1284px;
}
.op-head,
.op-row {
  display: grid;
  grid-template-columns: var(--op-cols);
  align-items: center;
  column-gap: 0;   /* group tints must read as one solid band, so no gaps */
}
.op-head {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  padding: 0.3rem 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.op-head .cell { justify-content: center; text-align: center; }
.op-head .c-loc, .op-head .c-notes { justify-content: flex-start; text-align: left; }

.op-row {
  position: relative;
  border: 1px solid var(--border);
  border-top: none;
  background: var(--surface);
  padding: 2px 0;
}
.op-row:nth-child(even) { background: var(--surface-2); }
.op-row:hover { background: var(--brand-tint); }
.op-row:last-child { border-radius: 0 0 var(--radius-sm) var(--radius-sm); }
/* Focus anywhere in a row lifts it out of the stripe pattern. */
.op-row:focus-within { background: #fff; box-shadow: inset 3px 0 0 var(--brand); }
.op-row.has-notes:focus-within {
  box-shadow: inset 0 0 0 2px var(--danger), inset 3px 0 0 var(--brand);
}

.op-row .cell,
.op-head .cell {
  display: flex;
  align-items: center;
  min-width: 0;
  padding: 0 3px;
  height: 30px;
}
/* Thin rules mark the width / drop / options / meta groups. */
.op-row .g-start, .op-head .g-start { box-shadow: inset 1px 0 0 var(--border); }

/* Inputs — stripped down to fit a 30px row ------------------------------- */
.op-row input,
.op-row select {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0.2rem 0.3rem;
  height: 26px;
  font-size: var(--text-xs);
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--ink);
}
.op-row input:hover, .op-row select:hover { border-color: var(--border-strong); background: var(--surface); }
.op-row input:focus, .op-row select:focus {
  border-color: var(--brand);
  background: #fff;
  box-shadow: 0 0 0 2px var(--brand-ring);
}
.op-row .c-no input {
  font-weight: 700;
  font-size: var(--text-sm);
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.op-row .c-mm input,
.op-row .c-qty input,
.op-row .c-corr input {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.op-row .c-mm input { font-weight: 600; font-size: var(--text-sm); }
.op-row .c-drive select { font-size: 11px; padding-right: 0; }
/* Number spinners eat the width these columns don't have. */
.op-row input[type="number"]::-webkit-outer-spin-button,
.op-row input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.op-row input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

/* A non-zero correction is the single most expensive thing to misread on
   these sheets, so it never renders as quiet grey text. */
.op-row .corr.has-val {
  background: var(--corr-bg);
  border-color: var(--corr-line);
  color: var(--corr-ink);
  font-weight: 700;
}

/* Ticks — fixed-size pills, colour-coded by meaning ---------------------- */
.tick {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 22px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  background: var(--surface);
  color: var(--ink-faint);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
  user-select: none;
  margin: 0;
  transition: background 0.1s ease, color 0.1s ease, border-color 0.1s ease;
}
.tick input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 0; height: 0; margin: 0;
  pointer-events: none;
}
.tick:hover { border-color: var(--ink-faint); }
.tick:has(input:checked), .tick.is-on { color: #fff; border-color: transparent; }
.tick:has(input:focus-visible) { box-shadow: 0 0 0 2px var(--brand-ring); }

.tick-g:has(input:checked),      .tick-g.is-on      { background: var(--tick-g); }
.tick-spring:has(input:checked), .tick-spring.is-on { background: var(--tick-spring); }
.tick-s:has(input:checked),      .tick-s.is-on      { background: var(--tick-s); }
.tick-b:has(input:checked),      .tick-b.is-on      { background: var(--tick-b); }
.tick-sill:has(input:checked),   .tick-sill.is-on   { background: var(--tick-sill); }
.tick-caps:has(input:checked),   .tick-caps.is-on   { background: var(--tick-caps); }
.tick-rev:has(input:checked),    .tick-rev.is-on    { background: var(--tick-rev); }
.tick-motor:has(input:checked),  .tick-motor.is-on  { background: var(--tick-motor); }
/* Key swatches are display-only pills — keep them from stretching. */
.op-key .tick { width: auto; padding: 0 0.4rem; }

/* MOTOR SIDE is written to D27/H27 on BLOCKOUT / BAL FZ only — on 2hr FIRE
   D27 is the master's motorised-indicator formula and the build never touches
   it, so the pair is hidden there (openings.js toggles the class, mirroring
   what it does for the job-wide RTS tick). The COLUMNS stay in the grid:
   --op-cols is shared by the header and every row, so removing tracks for one
   product would have to be done in three places to keep the scan aligned. */
.op-table.no-motor-side .c-motor { visibility: hidden; }

/* +B / +S combination tints the whole DROP band, so the three cases are
   distinguishable at a glance without reading the pills. */
.op-row:has(.tick-b input:checked):has(.tick-s input:checked) .d-cell { background: var(--combo-bs); }
.op-row:has(.tick-b input:checked):not(:has(.tick-s input:checked)) .d-cell { background: var(--combo-b); }
.op-row:has(.tick-s input:checked):not(:has(.tick-b input:checked)) .d-cell { background: var(--combo-s); }

/* Notes on the sheet = read this row before you build it. */
.op-row.has-notes {
  border-color: var(--danger);
  box-shadow: inset 0 0 0 2px var(--danger);
  z-index: 1;
}
.op-row.has-notes .notes-input {
  color: var(--danger);
  font-weight: 600;
  background: var(--danger-bg);
  border-color: var(--danger-border);
}

button.remove-opening {
  width: 22px;
  height: 22px;
  padding: 0;
  font-size: 15px;
  line-height: 1;
  border-color: transparent;
  background: transparent;
  color: var(--ink-faint);
  font-weight: 700;
}
.op-row:hover button.remove-opening { color: var(--danger); }
button.remove-opening:hover { background: var(--danger-bg); border-color: var(--danger-border); color: var(--danger); }

/* --- Buttons -------------------------------------------------------------- */
button, .button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-size: var(--text-base);
  font-family: inherit;
  font-weight: 600;
  line-height: 1.2;
  padding: 0.65rem 1.1rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  text-align: center;
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease, color 0.12s ease;
}
button:hover, .button:hover {
  background: var(--surface-2);
  border-color: var(--ink-faint);
  text-decoration: none;
}
button:active, .button:active { box-shadow: inset 0 1px 2px rgba(21, 27, 38, 0.12); }

button.primary, .button.primary {
  background: var(--brand);
  color: #fff;
  border-color: var(--brand);
}
button.primary:hover, .button.primary:hover {
  background: var(--brand-hover);
  border-color: var(--brand-hover);
  color: #fff;
}
button.primary:active, .button.primary:active { background: var(--brand-active); border-color: var(--brand-active); }

button.remove-opening {
  border-color: var(--danger-border);
  color: var(--danger);
  background: var(--surface);
  font-size: var(--text-sm);
  font-weight: 600;
  padding: 0.35rem 0.7rem;
}
button.remove-opening:hover { background: var(--danger-bg); border-color: var(--danger); }

button:disabled, .button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  box-shadow: none;
}

/* Add-opening: a clear but secondary full-width action */
#add-opening { width: 100%; margin-top: var(--space-2); border-style: dashed; color: var(--brand); border-color: var(--border-strong); }
#add-opening:hover { border-color: var(--brand); background: var(--surface-2); }

/* --- Sticky action bar ---------------------------------------------------- */
.actions {
  position: sticky;
  bottom: 0;
  background: rgba(7, 10, 20, 0.82);
  backdrop-filter: blur(6px);
  padding: var(--space-4) 0;
  margin-top: var(--space-5);
  border-top: 1px solid rgba(120, 150, 210, 0.22);
  display: flex;
  gap: var(--space-3);
}
.actions button { flex: 1; padding-top: 0.8rem; padding-bottom: 0.8rem; }

/* --- Issues / alerts ------------------------------------------------------ */
.issues { margin-bottom: var(--space-5); display: flex; flex-direction: column; gap: var(--space-2); }
.issue {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  border: 1px solid transparent;
  border-left-width: 4px;
}
.issue.hard {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  border-left-color: var(--danger);
  color: #7f2018;
}
.issue.soft {
  background: var(--warning-bg);
  border-color: var(--warning-border);
  border-left-color: var(--warning);
  color: #6b4600;
}
.issue.hard ul, .issue.soft ul { margin: var(--space-2) 0 0; padding-left: var(--space-5); }

/* Extraction status banner — green when both vision passes agreed, amber
   when they disagreed. Distinct from warnings so it can't be skimmed as one. */
.issue.status_ok {
  background: var(--success-bg);
  border-color: var(--success-border);
  border-left-color: var(--success);
  color: #14532d;
}
.issue.status_warn {
  background: var(--warning-bg);
  border-color: var(--warning-border);
  border-left-color: var(--warning);
  color: #6b4600;
  font-weight: 600;
}

/* Quiet info lines (e.g. deliberate partial run) — stated, not shouted. */
.issue.info {
  background: transparent;
  border-color: var(--border);
  border-left-color: var(--border-strong);
  color: #5b6572;
  font-size: var(--text-xs);
}

/* SITE NOTES — whole-job instructions written on the measure. First thing on
   the page, in the notes red the review grid already uses for "somebody wrote
   this by hand", not the warnings' amber: a note is not a fault, and the one
   thing that must not happen is the floor learning to discount amber. Hidden
   entirely when the job has none (see job_form.html). */
.site-notes {
  margin-bottom: var(--space-5);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--danger-border);
  border-left: 4px solid var(--danger);
  border-radius: var(--radius-sm);
  background: var(--danger-bg);
}
.site-notes-head {
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--danger);
  margin-bottom: var(--space-2);
}
.site-notes ul { margin: 0; padding-left: 1.2em; }
.site-notes li {
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.45;
  color: var(--ink);
}

/* The matching editable field, down with the other job details. Full width of
   the card, below the one-row field strip rather than a 12th track in it. */
.job-notes-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: var(--space-3) 0 0;
}
.job-notes-field > span:first-child {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.job-notes-field textarea {
  width: 100%;
  font-size: var(--text-sm);
  line-height: 1.45;
  padding: 0.35rem 0.5rem;
  resize: vertical;
}

/* EXTRAS — the job's own lines for shutter #1's EXTRAS box (job_form.html).
   Standard lines in two columns like the sheet's box; a reworded line is
   tinted brand blue (an office / installer decision, same language as OFFICE
   EDITS), a line left off is struck through. */
.extras-card { margin-top: var(--space-3); }
.extras-hint { margin: 0 0 var(--space-2); font-size: var(--text-sm); color: var(--ink-soft); }
.extras-sub {
  display: block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: var(--space-2) 0 2px;
}
.extras-std {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 4px var(--space-3);
}
.extras-line { display: flex; align-items: center; gap: 6px; margin: 0; min-width: 0; }
.extras-line input:not([type="hidden"]) {
  flex: 1;
  min-width: 0;
  height: 30px;
  margin: 0;
  padding: 0.2rem 0.4rem;
  font-size: var(--text-sm);
}
.extras-line.is-changed input:not([type="hidden"]) {
  background: var(--brand-tint);
  border-color: var(--brand);
  font-weight: 700;
}
.extras-line.is-removed input:not([type="hidden"])::placeholder {
  text-decoration: line-through;
  color: var(--ink-faint);
}
.x-chip {
  flex: none;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 999px;
  color: var(--brand);
  background: var(--brand-tint);
}
.x-chip.x-off { color: var(--ink-soft); background: var(--surface-2); }
.x-was { flex: none; max-width: 45%; font-size: var(--text-sm); color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.extras-added { display: flex; flex-direction: column; gap: 4px; max-width: 660px; }
.extras-added input {
  height: 30px;
  margin: 0;
  padding: 0.2rem 0.4rem;
  font-size: var(--text-sm);
  font-weight: 700;
}
.extras-added input:placeholder-shown { font-weight: 400; }
.extras-card .issue { margin-top: var(--space-2); }

/* OFFICE EDITS — what has been changed on screen since the site measure.
   In the +G brand blue, NOT the warnings' amber: these are decisions the
   office made, and dressing them as problems would teach the floor to
   discount the colour that means one. Its own block, outside .issues, so it
   never lands in a flag count (see web/edits.py). */
.edits {
  margin-bottom: var(--space-5);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--tick-g);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: #f3f5ff;
  color: #0b1f8f;
}
.edits-head {
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  opacity: 0.85;
  margin-bottom: var(--space-2);
}
.edit {
  font-size: var(--text-sm);
  padding: 2px 0;
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
}
/* Fixed-width chip so the shutter numbers line up as a column the eye can
   run down — the same reason the openings grid is dense. */
.edit-chip {
  flex: 0 0 auto;
  min-width: 3.5em;
  font-weight: 700;
  font-size: var(--text-xs);
  color: var(--tick-g);
}

/* The model's own wording for a problem the deterministic check already
   states, tucked under that line instead of sitting beside it as a second,
   differently-worded warning. Inherits its parent's colour so it reads as
   part of the same flag, at 90% size so the headline still leads. */
.issue-detail {
  margin-top: var(--space-2);
  padding-left: var(--space-3);
  border-left: 2px solid currentColor;
  font-size: 0.9em;
  opacity: 0.8;
}

/* Knock-on flags: everything that fired off the same measurement as the
   headline. Collapsed, because checking the one number against the paper
   resolves all of them at once. */
.issue-children {
  margin-top: var(--space-2);
  font-size: 0.9em;
}
.issue-children summary {
  cursor: pointer;
  opacity: 0.75;
}
.issue-child {
  margin-top: var(--space-2);
  padding-left: var(--space-3);
  border-left: 2px solid currentColor;
  opacity: 0.8;
}

/* Flag-count badges — Jobs page "Waiting for review" + upload confirmation. */
.flag-badge {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 600;
  border: 1px solid transparent;
  white-space: nowrap;
}
.flag-badge.hard {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  color: var(--danger);
}
.flag-badge.soft {
  background: var(--warning-bg);
  border-color: var(--warning-border);
  color: var(--warning);
}
.flag-badge.clean {
  background: var(--success-bg);
  border-color: var(--success-border);
  color: var(--success);
}
/* An unlocked built job sitting back in "Waiting for review". Deliberately
   NOT one of the severity colours — it reports what a row IS, not how bad it
   is, so it must not read as a third level of alarm. */
.flag-badge.rebuild {
  background: #e6eaf1;          /* darker than --surface-2, which is near-white
                                   and would disappear on a table row */
  border-color: var(--border-strong);
  color: #3d4756;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* A job submitted from the tablet measure form: no paper sheet exists. Like
   .rebuild it says what the row IS, not how bad it is — brand-blue family
   (the OFFICE EDITS band's colours), not a severity colour. The tablet form's
   own bright sunlight tokens are the --m-* set in static/measure/measure.css;
   keep the two palettes in step with review_tab.py (house rule). */
.flag-badge.digital {
  background: #f3f5ff;
  border-color: #b9c4f2;
  color: #0b1f8f;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.flag-badge + .flag-badge { margin-left: var(--space-2); }

/* Opening-photo thumbnail under the SHUTTER # input (digital measure jobs).
   Small on purpose — it answers "which opening is this?", the full-size view
   is one click away. */
/* The thumb is a fixed 32x24 beside the number (object-fit keeps it a photo,
   never a sliver); the number keeps the rest of the (widened) column. */
.op-row .c-no { gap: 4px; }
.op-row .c-no input { flex: 1 1 auto; min-width: 0; padding: 0.2rem 0.15rem; }
.op-photos { position: relative; flex: 0 0 auto; width: 32px; height: 24px; }
.op-photo { display: block; line-height: 0; }
.op-photo img {
  width: 32px;
  height: 24px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  object-fit: cover;
}
/* QTY > 1 (tablet D5): the extra shutters' own locations stack under the
   first's — compact boxes, labelled by their placeholder ("#4 location"),
   bunched tight so a 3-shutter row costs the page as little as it can. */
.op-row .c-loc:has(.unit-loc) { height: auto; padding: 2px 3px; flex-direction: column; align-items: stretch; justify-content: center; }
.op-row .c-loc:has(.unit-loc) input { height: 20px; padding: 0 4px; font-size: 11px; line-height: 18px; }
.op-row .c-loc .unit-loc { margin-top: 1px; }
/* The other shutters' thumbs (QTY > 1) sit behind a "+N" badge on the first
   thumb's corner and pop OVER the rows below on click — a popover, not a
   stack, so the row keeps its height (office, 2026-10-14). */
.op-more { position: static; font-size: 10px; line-height: 1; }
.op-more > summary {
  position: absolute;
  right: -6px;
  bottom: -6px;
  list-style: none;
  cursor: pointer;
  font-weight: 800;
  color: #fff;
  padding: 2px 4px;
  border-radius: 999px;
  white-space: nowrap;
  background: var(--brand);
  box-shadow: 0 0 0 2px #fff;
}
.op-more > summary::-webkit-details-marker { display: none; }
.op-more[open] > summary { background: var(--danger, #c0392b); }
.op-more-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 6;
  display: flex;
  gap: 4px;
  padding: 4px;
  background: #fff;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
}
.op-more .op-photo { position: relative; margin: 0; }
.op-more .op-photo img { width: 64px; height: 48px; }
.op-photo-tag {
  position: absolute;
  left: 2px;
  bottom: 2px;
  font-size: 10px;
  font-weight: 800;
  line-height: 14px;
  padding: 0 4px;
  border-radius: 3px;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
}
.discard-form, .unlock-form { display: inline; margin-left: var(--space-2); }

/* --- Unlock & rebuild -------------------------------------------------- */

/* Standing context while a built job is reopened: which file goes, what the
   PO does, and how far production has already taken it. Informational, so it
   sits below the blocked panel and above the ordinary flags. */
.rebuild-banner {
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-strong);
  border-left: 4px solid #4a5568;
  border-radius: var(--radius-sm);
  background: #eef1f6;
  font-size: var(--text-sm);
  color: #3d4756;
}
.rebuild-banner .rb-prod { display: block; margin-top: var(--space-2); }

/* A colour change after the frames are prepared or sent. No cut_items row can
   catch this — the frames are the right SIZE in the wrong colour — so it gets
   its own loud treatment rather than joining the amber soft warnings. */
.issue.colour-warning {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  border-left-color: var(--danger);
  color: #7f2018;
  font-weight: 600;
  margin-bottom: var(--space-4);
}

/* The block. This is the one panel on the site that must stop a reader who is
   skim-reading, so it is the only one with a heavy border and a heading. */
.rebuild-blocked {
  margin-bottom: var(--space-5);
  padding: var(--space-4) var(--space-5);
  border: 2px solid var(--danger);
  border-radius: var(--radius-sm);
  background: var(--danger-bg);
  color: #7f2018;
}
.rebuild-blocked h2 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg);
  color: var(--danger);
}
.rebuild-blocked .rb-lede { margin: 0 0 var(--space-4); font-size: var(--text-sm); }
.rb-item {
  padding: var(--space-3) 0;
  border-top: 1px solid var(--danger-border);
}
.rb-what { font-weight: 700; font-size: var(--text-sm); }
.rb-saw {
  font-weight: 500;
  font-size: var(--text-xs);
  opacity: 0.75;
  margin-left: var(--space-2);
}
.rb-why { font-size: var(--text-sm); margin-top: 0.15rem; }
.rb-when { font-size: var(--text-xs); opacity: 0.75; margin-top: 0.15rem; }
.rb-next {
  margin: var(--space-4) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--danger-border);
  font-size: var(--text-sm);
}
.merge-warnings { margin: var(--space-2) 0 0; padding-left: var(--space-5); }

/* Upload confirmation page (phone-first). */
.uploaded-next {
  margin-top: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.uploaded-next p { margin: 0 0 var(--space-3); }
.uploaded-next p:last-child { margin-bottom: 0; }
.uploaded-secondary { font-size: var(--text-sm); color: #5b6572; }
.uploaded-flags { margin: var(--space-2) 0 0; }

/* Collapsed minor-notes section: cosmetic-field uncertainty lives here. */
.minor-notes summary {
  cursor: pointer;
  font-size: var(--text-xs);
  color: #5b6572;
  padding: var(--space-2) 0;
}
.minor-notes .issue.minor {
  background: transparent;
  border-color: var(--border);
  border-left-color: var(--border-strong);
  color: #5b6572;
  font-size: var(--text-xs);
  margin-top: var(--space-2);
}

/* --- Download / success box ----------------------------------------------- */
.download-box {
  background: var(--success-bg);
  border: 1px solid var(--success-border);
  border-left: 4px solid var(--success);
  padding: var(--space-5);
  border-radius: var(--radius);
  color: #15522f;
}
.download-box strong { display: block; font-size: var(--text-lg); margin-bottom: var(--space-2); color: var(--ink); }
.download-box ul { margin: var(--space-2) 0 var(--space-4); padding-left: var(--space-5); color: var(--ink-soft); }
.download-box a.button { margin-top: var(--space-2); }

/* --- Login ---------------------------------------------------------------- */
/* Auth pages get their own centered composition: a contained hero + the login
   card as one group, vertically centered in the viewport (rather than a giant
   full-width banner shoving the card to the bottom of the screen). */
.auth {
  min-height: calc(100vh - 140px);   /* viewport minus topbar + main padding */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-5);
  padding: var(--space-5) 0;
}
.auth-hero {
  display: block;
  width: 100%;
  max-width: 440px;                  /* contained — a logo, not a billboard */
  height: auto;
  mix-blend-mode: screen;
  -webkit-mask-image: radial-gradient(ellipse 82% 82% at 50% 50%, #000 55%, transparent 100%);
  mask-image: radial-gradient(ellipse 82% 82% at 50% 50%, #000 55%, transparent 100%);
}
@media (prefers-reduced-motion: no-preference) {
  .auth-hero { animation: hero-in 0.8s ease-out both; }
}
.login-form {
  width: 100%;
  max-width: 360px;
  margin: 0 auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow);
}
.login-form h1 { text-align: center; font-size: var(--text-xl); }
.login-form button { width: 100%; margin-top: var(--space-2); padding-top: 0.75rem; padding-bottom: 0.75rem; }

/* --- Upload status / helper text ------------------------------------------ */
.upload-status {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--ink-soft);
  text-align: center;
}

/* --- Jobs dashboard table ------------------------------------------------- */
/* On narrow screens the 4-column table is wider than the viewport; let it
   scroll inside its own box instead of forcing the whole page to scroll. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.jobs-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  font-size: var(--text-sm);
}
.jobs-table th, .jobs-table td {
  padding: 0.7rem 0.85rem;
  text-align: left;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.jobs-table thead th {
  background: var(--surface-2);
  font-weight: 600;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--ink-soft);
}
.jobs-table tbody tr:nth-child(even) { background: var(--surface-2); }
.jobs-table tbody tr:hover { background: var(--brand-tint); }
.jobs-table tbody tr:last-child td { border-bottom: none; }
.jobs-table td:first-child { font-weight: 600; color: var(--ink); overflow-wrap: break-word; }
/* Keep the build date on one line */
.jobs-table th:nth-child(2), .jobs-table td:nth-child(2) { white-space: nowrap; color: var(--ink-soft); }
/* Right-align the numeric size column and the action column */
.jobs-table th:nth-child(3), .jobs-table td:nth-child(3) { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink-soft); white-space: nowrap; }
.jobs-table th:nth-child(4), .jobs-table td:nth-child(4) { text-align: right; white-space: nowrap; }
.jobs-table .button { padding: 0.4rem 0.85rem; font-size: var(--text-sm); }

/* --- Utilities ------------------------------------------------------------ */
/* Real <input type=file> kept in the DOM (not display:none — some mobile
   browsers won't open the picker via .click() on a display:none input) but
   visually hidden; a styled button triggers it. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --- Responsive ----------------------------------------------------------- */
/* Below this the 1256px opening grid stops fitting the content column. The 22
   columns can't usefully fold — the review scan is a desktop job — so scroll
   the grid intact rather than crushing it. Sticky headers are the casualty:
   an overflow container becomes the sticky scrollport. */
@media (max-width: 1380px) {
  .op-scroll { overflow-x: auto; }
  .op-head { position: static; }
}

@media (max-width: 640px) {
  main { padding: var(--space-4) var(--space-3) var(--space-8); }
  h1 { font-size: var(--text-xl); }
  fieldset { padding: var(--space-4); }
  .actions { flex-direction: column; }
  .openings-card { padding: var(--space-3); }

  /* Jobs table → stacked cards: filename title, labelled meta rows, and a
     full-width Download button. No horizontal scroll on mobile. */
  .table-scroll { overflow-x: visible; }
  .jobs-table {
    border: none;
    box-shadow: none;
    background: transparent;
    border-radius: 0;
    overflow: visible;
  }
  .jobs-table thead { display: none; }
  .jobs-table,
  .jobs-table tbody,
  .jobs-table tr,
  .jobs-table td { display: block; width: 100%; }
  /* Each row becomes a card (also neutralises the zebra/hover fills). */
  .jobs-table tr,
  .jobs-table tbody tr:nth-child(even),
  .jobs-table tbody tr:hover {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: var(--space-4);
    margin-bottom: var(--space-3);
  }
  .jobs-table tbody tr:last-child { margin-bottom: 0; }
  /* Reset the desktop per-column alignment/nowrap rules. */
  .jobs-table td,
  .jobs-table td:nth-child(2),
  .jobs-table td:nth-child(3),
  .jobs-table td:nth-child(4) {
    border: none;
    padding: 0;
    text-align: left;
    white-space: normal;
  }
  .jobs-table td:first-child {
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--ink);
    margin-bottom: var(--space-3);
    overflow-wrap: anywhere;
  }
  /* Built / Size: uppercase label on the left, value on the right. */
  .jobs-table td[data-label="Built"],
  .jobs-table td[data-label="Size"] {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    font-size: var(--text-sm);
    color: var(--ink-soft);
    margin-bottom: var(--space-2);
  }
  .jobs-table td[data-label="Built"]::before,
  .jobs-table td[data-label="Size"]::before {
    content: attr(data-label);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--ink-faint);
  }
  .jobs-table td.download-cell { margin-top: var(--space-3); }
  .jobs-table td.download-cell .button {
    display: block;
    width: 100%;
    padding: 0.7rem;
    font-size: var(--text-base);
  }
}

/* "+ New measure" in the top nav: an action among navigation links, so it
   reads as something you DO rather than somewhere you go. */
.topnav a.nav-action {
  color: var(--topbar-fg);
  font-weight: 700;
  border: 1px solid var(--topbar-fg-soft);
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
}
.topnav a.nav-action:hover { background: rgba(255, 255, 255, 0.12); }

/* --- Personal logins: login hint, account page, Users / Activity (2026-10-16) */
.login-hint {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
  text-align: center;
}
.topnav a.nav-user {
  color: var(--topbar-fg);
  font-weight: 600;
  border-left: 1px solid rgba(255, 255, 255, 0.2);
  padding-left: var(--space-4);
}
.acct-page, .admin-page { max-width: 1100px; margin: 0 auto; }
.acct-page { max-width: 560px; }
.acct-page > h1, .admin-page > h1 { color: #eef2fb; }
.acct-page > p, .admin-page > p { color: var(--topbar-fg-soft); }
.acct-page > p a, .admin-page > p a { color: #6ea8ff; }
.acct-page > p strong { color: #eef2fb; }
.acct-page > .issue, .admin-page > .issue, .admin-page > .admin-reveal { margin-bottom: var(--space-4); }
.acct-card, .admin-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  margin: 0 0 var(--space-5);
  box-shadow: var(--shadow-sm);
}
.acct-card h2, .admin-card h2 { font-size: var(--text-lg, 1.1rem); margin: 0 0 var(--space-4); }
.acct-card button { margin-top: var(--space-2); }
.acct-hint { font-size: var(--text-xs); color: var(--ink-soft); font-weight: 400; }
.admin-card > p.acct-hint { margin-top: 0; }
.issue.ok-note {
  background: var(--success-bg);
  border-color: var(--success-border);
  border-left-color: var(--success);
  color: #14532d;
}
.admin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0 var(--space-4);
  align-items: end;
}
.admin-grid label { margin-bottom: var(--space-3); }
.admin-check { display: flex; align-items: center; gap: var(--space-2); }
.admin-actions { margin-bottom: var(--space-3); }
.admin-reveal {
  background: #eef6ff;
  border: 1px solid #b6d4fe;
  border-left: 4px solid var(--brand);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--ink);
}
.admin-reveal code {
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 2px 8px;
  margin: 0 var(--space-2);
  user-select: all;
}
.admin-reveal span { display: block; margin-top: var(--space-2); color: var(--ink-soft); font-size: var(--text-xs); }
.admin-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-sm);
}
.admin-table th, .admin-table td {
  padding: 0.6rem 0.8rem;
  text-align: left;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.admin-table thead th {
  background: var(--surface-2);
  font-weight: 600;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--ink-soft);
}
.admin-table tbody tr:last-child td { border-bottom: none; }
.admin-table tr.admin-inactive td { color: var(--ink-faint); }
.admin-table tr.admin-fail td { background: var(--danger-bg); }
.admin-manage summary { cursor: pointer; color: var(--brand); font-weight: 600; }
.admin-manage[open] { min-width: 320px; }
.admin-manage form { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px dashed var(--border); }
.admin-manage .admin-grid { grid-template-columns: 1fr; }
.admin-filter {
  display: flex; flex-wrap: wrap; align-items: end; gap: var(--space-4);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
}
.admin-filter label { margin-bottom: 0; }

/* Per-instance badge beside the brand (INSTANCE_LABEL env, e.g. TEST COPY on a
   parallel copy of the app that must not be used for real work). */
.instance-label {
  margin-left: .75rem;
  padding: .15rem .55rem;
  border-radius: .3rem;
  background: #d9480f;
  color: #fff;
  font-weight: 700;
  font-size: .8rem;
  letter-spacing: .05em;
  vertical-align: middle;
}
