/* SmartKitchenDocs — manager console.
 *
 * Visual direction (2026-08-26): light, compact operations desk. The console
 * is read at arm's length in a back office, so page < card < control follows
 * a clear value hierarchy, figures remain tabular, and blue stays reserved
 * for navigation and the action that moves work forward.
 *
 * Semantic colour is reserved for outcomes. In particular steps are NOT
 * green: a procedure that has not been performed yet should not read as
 * already done — see the .step rules.
 */
@font-face{font-family:"Barlow";src:url("assets/fonts/Barlow-Regular.woff2") format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:"Barlow";src:url("assets/fonts/Barlow-Medium.woff2") format("woff2");font-weight:500;font-display:swap}
@font-face{font-family:"Barlow";src:url("assets/fonts/Barlow-SemiBold.woff2") format("woff2");font-weight:600;font-display:swap}
@font-face{font-family:"Barlow";src:url("assets/fonts/Barlow-Bold.woff2") format("woff2");font-weight:700;font-display:swap}
@font-face{font-family:"Barlow Condensed";src:url("assets/fonts/BarlowCondensed-Regular.woff2") format("woff2");font-weight:400;font-display:swap}
@font-face{font-family:"Barlow Condensed";src:url("assets/fonts/BarlowCondensed-Medium.woff2") format("woff2");font-weight:500;font-display:swap}
@font-face{font-family:"Barlow Condensed";src:url("assets/fonts/BarlowCondensed-SemiBold.woff2") format("woff2");font-weight:600;font-display:swap}
@font-face{font-family:"Barlow Condensed";src:url("assets/fonts/BarlowCondensed-Bold.woff2") format("woff2");font-weight:700;font-display:swap}

:root{
  /* The brief's stable light operations tokens. */
  --bg:#f2f2f3; --panel:#ffffff; --panel2:#e9e9ea; --panel3:#f2f2f3;
  --ink:#1d1f20; --dim:#5d5d60; --accent:#5980a6; --warn:#8a5a08; --bad:#8f2230;
  --line:#d4d4d7; --line2:#98989b; --faint:#5d5d60;
  /* The visual boundary of a control that has NO fill -- a secondary or
     bare button. It is the only thing identifying that control, so WCAG
     1.4.11 wants 3:1 against the page: #98989b measured 2.55:1 on #f2f2f3
     and #7a7a7d measures 3.8:1.

     Text fields use it too. I first gave them the lighter --line on the
     grounds that their fill, not their border, is what says "field" -- then
     measured the fill: --panel2 on the page ground is 1.09:1, and the
     border was 1.4:1, so an empty field was identified by neither. This is
     a deliberate, small departure from the prototype, which draws its
     .input with the same faint divider every other edge uses. On a bright
     kitchen tablet an invisible field is worse than a slightly heavier
     one. Flag it if the heavier edge reads wrong. */
  --control-line:#7a7a7d;
  --accent-ink:#ffffff; --accent-soft:#eaf0f6;
  --ok:#2f6b47; --lock:#416180;
  /* PM calendar occurrence status (plan Task 8). Status is the ONE colour
     channel that survives light mode, print, and colour blindness at 20px --
     category is shape/glyph, never colour (see .cal-cat / STATUS_GLYPH in
     web/views/calendar.js). 'upcoming' and 'skipped' deliberately reuse the
     existing neutral tokens (--panel2/--line/--faint) rather than inventing
     more colour for states that are not calls to action. */
  --occ-overdue-bg:#f6e3e4; --occ-overdue-line:#b4414c; --occ-overdue-ink:#8f2230;
  --occ-today-bg:#f8eeda;   --occ-today-line:#b98a2a;   --occ-today-ink:#8a5a08;
  --occ-done-bg:#e3efe6;    --occ-done-line:#4f8a66;    --occ-done-ink:#2f6b47;
  /* Category tint. REDUNDANT to the shape, never a substitute for it: the
     glyph still carries the category on its own, in print and at 10px and to
     a colour-blind reader, which is what WCAG 1.4.1 asks for. Drawn entirely
     from the COOL end -- teal, violet, indigo, slate -- because status owns
     warm (overdue red, due-today amber) plus done-green. A category tint
     therefore cannot be misread as a status, which is the collision the
     "status is the only colour channel" rule was written to prevent. */
  --cat-cleaning:#0e7490; --cat-inspection:#6941c6;
  --cat-filter:#3538cd;   --cat-calibration:#475467; --cat-other:#667085;
  /* Industry (the prototype's own name for its look): components are
     wireframe objects -- square, transparent, hairline-bordered, never
     shadowed. --radius and --shadow survive as tokens so every rule that
     references them retunes from here, but their VALUE is now the flat
     blueprint frame. Overlays that must occlude the page behind them
     (sheets, toasts, the mobile bar) keep their own explicit shadow. */
  --shadow:none;
  --radius:0;
  --radius-chip:2px;
  --accent-dark:#416180;
  /* The identity plate, and every marker that echoes it: the band, the
     current destination's rule, a pressed button, the mobile bar's current
     tab. They are one decision, so they are one token -- retuning three of
     four is how the markers silently stop matching the band. */
  --ink-deep:#1d2d3d;
  --sans:"Barlow","Segoe UI",sans-serif;
  --heading:"Barlow Condensed","Arial Narrow",sans-serif;
  --mono:"Barlow",ui-monospace,Consolas,monospace;
}
*{box-sizing:border-box}
html{background:var(--bg)}
/* Flat ground, no wash. The prototype's page is one even #f2f2f3 plane and
   the blue vignette read as a gradient nothing else in the system had. */
body{margin:0;color:var(--ink);font:15px/1.55 var(--sans);background:var(--bg);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
h1,h2,h3,h4,h5,h6{margin:0;font-family:var(--heading);font-weight:600;letter-spacing:-.01em;line-height:1.12}

/* Anything numeric — page numbers, KPI values, codes — gets the mono face
   with tabular figures so columns line up and digits stop jittering. */
.mono,.kpi .v,code{font-family:var(--mono);font-variant-numeric:tabular-nums}

button{font:inherit;cursor:pointer;border:none;white-space:nowrap}
/* Text-entry controls only. `input` unqualified also matches checkboxes and
   radios, and width:100% turned the Onboarding roster's checkboxes into
   1026px-wide bars -- label text flung to the far edge, and the entire bar
   live as a hit target, so a click meant for a manual's title silently
   crossed that manual off. See input[type=checkbox] below. */
select,input:not([type=checkbox]):not([type=radio]),textarea{font:inherit;background:var(--panel2);color:var(--ink);
  border:1px solid var(--control-line);padding:9px 11px;width:100%;
  transition:border-color .18s ease,background .18s ease}
select:hover,input:not([type=checkbox]):not([type=radio]):hover,textarea:hover{border-color:var(--line2)}

/* Checkboxes size to themselves and stay next to their label. flex:none so a
   flex parent cannot stretch them again; 16px is comfortably clickable while
   the <label> wrapper keeps the text itself as the real hit area. */
input[type=checkbox],input[type=radio]{
  width:16px;height:16px;flex:none;margin:0;accent-color:var(--accent);cursor:pointer}
textarea{resize:vertical;min-height:96px}
/* :focus-visible, not :focus — a mouse click on a select should not leave a
   ring behind, but every keyboard path must show one. */
:focus{outline:none}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:0}
select:focus-visible,input:not([type=checkbox]):not([type=radio]):focus-visible,textarea:focus-visible{
  border-color:var(--accent);outline-offset:1px}
a{color:var(--accent-dark);text-decoration-color:rgba(65,97,128,.35);
  text-underline-offset:3px}
a:hover{text-decoration-color:currentColor}
/* ONE measure for the whole console: the bands and the working surface
   share the same 28px inset and both run to the edge. Nothing is centred in
   a narrower column inside them.

   The prototype does put its content in a centred column while its bands
   run full-bleed -- measured on its own render, its tabs start at x=45 and
   its page title at x=157 -- and this file used to reproduce that
   faithfully. Dylan's call: that offset is how the prototype bundle was
   built, not something he designed, and the empty ground it leaves either
   side of the content is the "white border" he wanted gone. The design
   intent is edge to edge, so the console is edge to edge. */
/* One inset all the way round: 28px at the sides, and the page closes at
   28px too. It used to trail off in 96px of empty ground below the last
   card, which is what made the bottom of every screen read as unfinished
   rather than ended. */
.wrap{max-width:none;margin:0;padding:0 0 28px}
#appScreen>*:not(header):not(nav),
#authScreen>*:not(header){
  max-width:none;margin-left:0;margin-right:0;padding-left:28px;padding-right:28px}

/* The identity band. One dark plate across the top, then everything below
   it is the light working surface -- the console used to carry TWO stacked
   navy bands (this one and the tab bar), which is half of why it read as a
   different product than the design. */
header.top{display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  margin:0;padding:16px 28px;background:var(--ink-deep);color:#fff;border:0;border-radius:0;box-shadow:none}
.brand{font-family:var(--heading);font-size:22px;font-weight:700;
  text-transform:uppercase;letter-spacing:.02em;line-height:1}
.brand span{color:#94bce3;margin-left:.34em}
.top .tiny,.top .muted{color:#9fb0c0}
.top .textAction{color:#fff}
.top a{color:#b5d9fd}
/* A chip, not a lozenge: square with a hairline, condensed uppercase --
   the same object the design uses for the signed-in name. */
.pill{font-family:var(--heading);font-size:11px;font-weight:600;padding:5px 11px;
  background:transparent;text-transform:uppercase;
  border:1px solid rgba(255,255,255,.28);color:#fff;letter-spacing:.08em;white-space:nowrap}
.spacer{flex:1}
.who{display:flex;align-items:center;gap:9px}
.who select{width:auto;min-width:170px}

.banner{margin:14px 0;padding:11px 14px;font-size:12.5px;
  color:#704b00;background:#fff8e8;border:1px solid #ebc46f}
.banner b{color:#5c3d00}
.banner.info{color:var(--dim);background:#eef7fc;border-color:#a9d6ef}
.banner.info b{color:var(--ink)}
.banner.bad{color:#8f1d35;background:#fff1f3;border-color:#f3a5b4}
.banner.bad b{color:#6e1026}
/* A standing caveat that never changes and is not actionable. It has to stay
   on screen (the names really are unverified) but it should not out-weigh
   the working surface below it, which is what a full-width banner did. */
.banner.quiet{font-size:11.5px;padding:8px 12px;color:var(--faint);
  background:transparent;border-color:var(--line);margin:12px 0}
.banner.quiet b{color:var(--dim);font-weight:600}

/* Tabs as one segmented control rather than six free-floating buttons: the
   set reads as a single switch, and the active state is a soft fill instead
   of a saturated block competing with the primary action. */
/* Destinations sit on the LIGHT ground, under the dark identity plate, and
   the current one is marked by a 3px ink rule rather than a second dark
   band. Typographic only: no fills, no pills. */
nav.tabs{display:flex;gap:26px;margin:0 0 30px;padding:0 28px;flex-wrap:wrap;
  background:transparent;border:0;border-bottom:1px solid var(--line);
  border-radius:0;width:auto;max-width:none}
nav.tabs button{background:transparent;color:var(--dim);padding:13px 0 12px;
  border:0;border-bottom:3px solid transparent;border-radius:0;font-family:var(--heading);
  font-size:15px;font-weight:600;text-transform:uppercase;letter-spacing:.09em;
  transition:color .18s ease,border-color .18s ease}
nav.tabs button:hover{color:var(--ink);background:transparent}
/* Measured off the prototype's own render: the current destination's rule is
   the ACCENT (#6f90b1 sampled, antialiased from the accent), not the
   identity navy. */
nav.tabs button.on{background:transparent;color:var(--ink);border-color:var(--accent)}
/* Settings is not a fifth destination. It sits at the far right with the
   gear, apart from the four places you actually work -- the prototype's
   arrangement. It stays inside nav.tabs so the router wires and marks it
   like any other tab; only its position differs. */
nav.tabs .tabs-settings{margin-left:auto;display:inline-flex;align-items:center;gap:7px}
nav.tabs .tabs-settings svg{width:16px;height:16px;fill:none;stroke:currentColor;
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none}
.textAction{background:transparent;border:0;padding:7px 4px;text-decoration:underline;text-underline-offset:3px}
.askAction{background:transparent;color:#fff;border:1px solid rgba(255,255,255,.34);padding:9px 14px}
.askAction:hover:not(:disabled){background:rgba(255,255,255,.1);border-color:#fff;filter:none}
.pageHeading{display:flex;justify-content:space-between;gap:20px;align-items:flex-end;
  margin:0 0 22px;border-bottom:0;padding-bottom:0}
.pageHeading h1{font-size:34px;line-height:1.05;letter-spacing:-.015em}
.pageHeading p{margin:6px 0 0;color:var(--dim);font-size:13px}
/* The line above the title -- date on the dashboard, context elsewhere.
   Letterspaced small caps, the design's standing eyebrow treatment. */
.eyebrow{font-family:var(--heading);font-size:12px;font-weight:600;
  text-transform:uppercase;letter-spacing:.14em;color:var(--faint);
  margin:0 0 7px;line-height:1}
.settingsLayout{display:grid;grid-template-columns:210px minmax(0,1fr);gap:24px;align-items:start}
.settingsNav{display:grid;border:1px solid var(--line);background:transparent}
.settingsNav button{padding:13px 14px;text-align:left;background:transparent;color:var(--dim);border-left:3px solid transparent}
.settingsNav button.on{color:var(--ink);background:var(--accent-soft);border-left-color:var(--accent);font-weight:600}

/* A card is a frame, not a raised tile: hairline border, no fill, no
   shadow, square corners. Everything the console draws sits on the one page
   plane. */
.card{background:transparent;border:1px solid var(--line);
  border-radius:var(--radius);padding:17px 18px;box-shadow:none}
.card+.card{margin-top:14px}
/* Several views reserve a card as a status/error slot (#logStatus,
   #planStatus, #rosterStatus) and leave it empty when there is nothing to
   report. Empty, it still painted a bordered box — on the Log tab that was a
   blank panel sitting above the KPIs. An empty container should occupy no
   space rather than each view having to remember to hide its own. */
.card:empty{display:none}
.card h3{font-size:11px;text-transform:uppercase;letter-spacing:.1em;
  color:var(--faint);margin-bottom:14px;font-weight:600}
/* A panel header inside a card is a label, not a page title: the same
   letterspaced small caps as .card h3, with its one action on the right and
   a hairline under the pair. */
.card>.pageHeading{align-items:center;margin:-2px 0 4px;padding-bottom:13px;
  border-bottom:1px solid var(--line)}
.card>.pageHeading h2{font-size:12px;text-transform:uppercase;letter-spacing:.11em;
  color:var(--faint);font-weight:600;line-height:1.2}
.card>.pageHeading a{font-size:13px;text-decoration:none;white-space:nowrap}
.card>.pageHeading a:hover{text-decoration:underline}
.cols{display:grid;grid-template-columns:minmax(330px,400px) 1fr;gap:18px;align-items:start}
@media(max-width:980px){.cols{grid-template-columns:1fr}}

label.lab{display:block;font-size:12px;color:var(--dim);margin:15px 0 6px;font-weight:500}
label.lab:first-child{margin-top:0}
/* Condensed uppercase, square, hairline-bordered. The primary action is a
   filled slate plate; secondary is the same shape with no fill, so the two
   read as one family at a glance instead of two unrelated controls. */
.btn{background:var(--accent-dark);color:var(--accent-ink);
  border:1px solid var(--accent-dark);padding:10px 16px;
  font-family:var(--heading);font-size:14px;font-weight:600;line-height:1.2;
  text-transform:uppercase;letter-spacing:.07em;
  width:auto;transition:background .18s ease,color .18s ease,border-color .18s ease}
.btn:hover:not(:disabled){background:#2c455d;border-color:#2c455d;filter:none}
.btn:active:not(:disabled){background:var(--ink-deep);border-color:var(--ink-deep)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.sec{background:transparent;color:var(--ink);border:1px solid var(--control-line)}
.btn.sec:hover:not(:disabled){background:rgba(29,31,32,.06);border-color:var(--ink);filter:none}
.btn.ok{background:var(--ok);border-color:var(--ok);color:#fff}
.btn.ok:hover:not(:disabled){background:#265a3a;border-color:#265a3a}
.btn.bad{background:var(--bad);border-color:var(--bad);color:#fff}
.btn.bad:hover:not(:disabled){background:#751a26;border-color:#751a26}
.btn.block{width:100%}
.row{display:flex;gap:8px}
.row>*{flex:1;min-width:0}

/* A step is an instruction that has NOT been carried out yet. The old rule
   washed every step in success-green, so a procedure the cook had not
   started already looked finished. Neutral card; the accent lives only in
   the numeral, which is what you scan for when you lose your place. */
.step{background:transparent;border:1px solid var(--line);
  padding:13px 14px;margin-bottom:9px}
.step:last-child{margin-bottom:0}
/* .step.ok is the ordinary state and is kept as a no-op so existing markup
   (and the mobile/web parity tests that assert on it) keeps working. There
   is deliberately no .step.hazard variant: a hazard step is a normal,
   actionable step plus a chip — it must never read as disabled or locked
   out. See web/views/ask.js. */
.step .hd{display:flex;gap:11px;align-items:flex-start}
.step .n{font-family:var(--mono);font-variant-numeric:tabular-nums;
  font-size:12px;font-weight:600;color:var(--accent);background:var(--accent-soft);
  border:1px solid rgba(89,128,166,.4);
  min-width:24px;height:22px;display:inline-flex;
  align-items:center;justify-content:center;flex:none;margin-top:1px}
.step .txt{flex:1;line-height:1.55;overflow-wrap:anywhere}
.tag{font-family:var(--heading);font-size:11px;font-weight:600;letter-spacing:.08em;
  padding:3px 8px;border-radius:var(--radius-chip);
  text-transform:uppercase;white-space:nowrap}
.tag.ok{background:#e7f6ee;color:var(--ok)}
.tag.hazard{background:#fff0f2;color:var(--bad);border:1px solid #f3a5b4}
.tag.unverified{background:#fff8e8;color:var(--warn)}
.cites{margin-top:9px;display:flex;flex-wrap:wrap;gap:6px;align-items:center}
/* Only INSIDE a step does the citation row hang under the text column — the
   answer-level citation list at the bottom is not indented under anything. */
.step .cites{margin-left:35px}
.cites:empty{display:none}
.cite{font-size:11px;color:var(--dim);text-decoration:none;
  background:transparent;border:1px solid var(--line);
  padding:3px 8px;transition:color .18s ease,border-color .18s ease}
.cite:hover{color:var(--accent);border-color:rgba(22,105,168,.4)}
.cite .p{font-family:var(--mono);font-variant-numeric:tabular-nums;
  font-size:10.5px;color:var(--faint);margin-left:5px}
.cite:hover .p{color:var(--accent)}
/* The answer-level source list. Labelled, because unlabelled it reads as a
   second copy of the per-step citations rather than "every page consulted". */
.sources{margin-top:14px;padding-top:12px;border-top:1px solid var(--line)}
.sources .lbl{font-size:10px;text-transform:uppercase;letter-spacing:.1em;
  color:var(--faint);font-weight:600;margin-bottom:7px}
.hazardBanner{background:#fff1f3;border:1px solid #f3a5b4;
  padding:9px 11px;margin-top:9px;font-size:12px;color:#8f1d35}
.hazardBanner b{color:#6e1026}
.proBanner{background:#eef7fc;border:1px solid #a9d6ef;
  padding:13px 15px;margin-bottom:14px}
.proBanner h4{margin:0 0 4px;color:var(--accent);font-size:13px;font-weight:600}
.acts{display:flex;gap:7px;margin-top:11px}
.acts button{flex:1;font-size:12px;padding:9px}

.prose{background:var(--panel2);border:1px solid var(--line);
  padding:14px;font-size:13.5px;line-height:1.6}

/* The "nothing here yet" state. It is the first thing a manager sees on the
   Ask tab, so it gets a centered, deliberate treatment rather than a bare
   sentence pinned to the top-left of an empty box. */
.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:9px;min-height:190px;text-align:center;color:var(--faint);padding:24px}
.empty svg{width:26px;height:26px;stroke:currentColor;fill:none;
  stroke-width:1.5;opacity:.5}
.empty .t{color:var(--dim);font-size:13.5px}

table{width:100%;border-collapse:collapse;font-size:12.5px}
th{text-align:left;color:var(--faint);font-family:var(--heading);font-weight:600;
  font-size:11px;text-transform:uppercase;letter-spacing:.1em;padding:8px;
  border-bottom:1px solid var(--line)}
td{padding:9px 8px;border-bottom:1px solid rgba(29,31,32,.09);vertical-align:top}
tr:last-child td{border-bottom:none}
tr:hover td{background:rgba(29,31,32,.04)}
.res{font-family:var(--heading);font-size:11px;font-weight:600;padding:3px 8px;
  border-radius:var(--radius-chip);text-transform:uppercase;letter-spacing:.07em;white-space:nowrap}
.res.self{background:#e7f6ee;color:var(--ok)}
.res.contracted{background:#fff0f2;color:var(--bad)}
.res.open{background:#fff8e8;color:var(--warn)}

/* A KPI reads label-then-figure, top to bottom: the word tells you what you
   are about to look at, the figure answers. The markup order is
   value/label/summary (the render tests pin it), so the three are reordered
   here rather than in every view that emits one.

   The figure is the condensed heading face at display size -- it is the one
   thing on the dashboard meant to be read from across the office -- and it
   takes the status ink of whatever it counts. A tile with nothing to report
   stays in plain ink: only real overdue and real due-today work is allowed
   to spend colour. */
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:16px}
.kpi{background:transparent;border:1px solid var(--line);
  padding:15px 16px 16px;display:flex;flex-direction:column}
.kpi .v{order:2;font-family:var(--heading);font-variant-numeric:tabular-nums;
  font-size:44px;font-weight:600;letter-spacing:-.02em;line-height:1;margin:9px 0 0}
.kpi .l{order:1;font-family:var(--heading);font-size:11.5px;color:var(--faint);
  text-transform:uppercase;letter-spacing:.13em;font-weight:600;margin-top:0;line-height:1}
.kpi .s{order:3;font-size:12px;color:var(--dim);margin-top:9px;line-height:1.35}
.kpi[data-tone="overdue"] .v{color:var(--occ-overdue-ink)}
.kpi[data-tone="today"] .v{color:var(--occ-today-ink)}

.muted{color:var(--dim);font-size:12.5px}
.tiny{font-size:11px;color:var(--faint)}
.hide{display:none}
.spin{display:inline-block;width:13px;height:13px;border:2px solid var(--line);
  border-top-color:var(--accent);border-radius:50%;animation:sp .7s linear infinite;vertical-align:-2px}
@keyframes sp{to{transform:rotate(360deg)}}

.selfAsserted{font-size:10.5px;color:var(--faint);font-style:italic}

.centerCard{max-width:420px;margin:64px auto}
.gap{height:8px}

/* Motion here is decoration, never the only signal that something changed —
   the spinner has a static border, tabs change colour as well as position.
   So it is all safe to drop outright. */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important}
}

@media(max-width:640px){
  /* 44px floor for every control on a phone (UI brief rule 2). Measured with
     playwright: selects came out at 42px and the Log tab's Refresh at 33px --
     each individually "nearly right", which is how a control ends up missable
     with a gloved thumb. Applied to the shared control rules rather than
     patched per view, so a control added later inherits it. */
  select,input:not([type=checkbox]):not([type=radio]),textarea,
  .btn,.btn.sec,button.btn{min-height:44px}
  select,input:not([type=checkbox]):not([type=radio]){touch-action:manipulation}
  .btn,.btn.sec{touch-action:manipulation}
  .wrap{padding:0 0 36px}
  #appScreen>*:not(header):not(nav),
  #authScreen>*:not(header){padding-left:12px;padding-right:12px}
  header.top,nav.tabs{padding-left:12px;padding-right:12px}
  .settingsLayout{grid-template-columns:1fr}.settingsNav{display:flex;overflow-x:auto}.settingsNav button{white-space:nowrap;border-left:0;border-bottom:3px solid transparent}.settingsNav button.on{border-bottom-color:var(--accent)}
  header.top{align-items:stretch;padding:12px;gap:10px}
  .spacer{display:none}
  .who{width:100%;order:3}
  .who input{min-width:0!important;flex:1}
  .btn.sec{margin-left:auto}
  nav.tabs{width:100%;flex-wrap:wrap;padding:4px}
  nav.tabs button{flex:1 1 30%;min-width:100px;padding:9px 12px}
  .card{padding:14px}
  .row,.acts{flex-direction:column}
  .centerCard{margin:28px auto}
  .step .cites{margin-left:0}
  table{display:block;overflow-x:auto;white-space:nowrap}
}

/* --- PM calendar (plan Task 8) ------------------------------------------
 * Status is the only colour channel; category is shape (.cal-cat, an inline
 * SVG picked in web/views/calendar.js) and never colour. Every status also
 * carries a glyph, so nothing here depends on hue alone (WCAG 1.4.1).
 */
.cal-header{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
/* flex-wrap is load-bearing, not cosmetic. Without it this legend is 407px of
   unwrappable content, and because it is the widest min-content box on the
   page, mobile Chrome EXPANDS THE WHOLE LAYOUT VIEWPORT to fit it: measured
   with playwright, window.innerWidth reported 434 on a 320, 360 and 414px
   device, and only ever on the Calendar tab. The entire console then renders
   at 434 logical px and is scaled down — every font, every tap target, on
   every surface reachable from that tab. One un-wrapping flex row was
   shrinking the app on every phone. */
.cal-legend{display:flex;flex-wrap:wrap;gap:8px 14px;font-size:11px;color:var(--faint);margin-bottom:10px}
.cal-legend .swatch{font-weight:600}

table.cal-month,table.cal-week{width:100%;border-collapse:collapse;table-layout:fixed}
table.cal-month th,table.cal-week th{text-align:left;color:var(--faint);font-weight:600;
  font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;padding:6px 4px}
.cal-staff{white-space:nowrap;padding-right:8px}

/* The cell's data-status is the WORST status it contains, so an overdue day
   reads at a glance even collapsed — it carries only a thin outline, never
   a fill, so it never competes with the .cal-row fills inside it. */
.cal-cell{border:1px solid var(--line);vertical-align:top;padding:5px;height:74px;
  overflow:hidden;transition:height .15s ease}
.cal-cell.cal-cell-empty{background:var(--panel3);border-color:var(--panel3)}
.cal-cell.expanded{height:auto}
.cal-daynum{font-size:11px;color:var(--faint);font-weight:600;margin-bottom:3px}

/* Status colour lives ONLY here — the 3px left border plus a background
   fill on the row, never on .cal-title's text color. Category (.cal-cat) is
   drawn in currentColor so it always matches the row's ink, never a
   separate colour channel of its own. */
.cal-row{display:flex;align-items:center;gap:5px;border-left:3px solid var(--line);
  background:var(--panel2);border-radius:4px;padding:2px 5px;margin-bottom:3px;
  font-size:11px;color:var(--ink);overflow:hidden}
.cal-row:last-of-type{margin-bottom:0}
.cal-row .cal-glyph{flex:none;font-size:10px;line-height:1}
.cal-row .cal-cat{flex:none}
/* Category tint, applied wherever a category glyph appears (calendar rows,
   Today cards, the daily checklist, the record, unit detail). The glyph is
   drawn with currentColor, so setting `color` here tints the shape without
   touching the row's status colours, which live on the row's border and
   background. See the --cat-* tokens for why these are all cool hues. */
.cal-cat[data-cat="cleaning"],.daily-cat[data-cat="cleaning"]{color:var(--cat-cleaning)}
.cal-cat[data-cat="inspection"],.daily-cat[data-cat="inspection"]{color:var(--cat-inspection)}
.cal-cat[data-cat="filter"],.daily-cat[data-cat="filter"]{color:var(--cat-filter)}
.cal-cat[data-cat="calibration"],.daily-cat[data-cat="calibration"]{color:var(--cat-calibration)}
.cal-cat[data-cat="other"],.daily-cat[data-cat="other"]{color:var(--cat-other)}
/* reviewGlyph (task_glyphs.js, 2026-09-15): a task the manual reserves to a
   technician is scheduled now, not withheld, so it must stand out on the
   same row rather than only in a tooltip. Uses --bad, the same colour
   .tag.hazard already spends on "licensed tech" elsewhere in this app --
   one meaning, one colour, wherever it appears. */
.cal-row .cal-review,.daily-review{flex:none;color:var(--bad)}
/* Two stacked lines: the machine, then the task. Each truncates to ONE line
   and never wraps — a wrapping line changes row height and breaks the grid
   every other cell relies on.

   The machine goes FIRST and gets its own line because it used to be
   appended to the title, and the row has never had space for both: every
   sampled month-grid row overflowed, so the machine was always what got
   ellipsised. A half-read task still tells you what to go and look at; a
   machine you cannot read does not. */
.cal-row .cal-rowtext{flex:1;min-width:0;display:flex;flex-direction:column;
  gap:1px;overflow:hidden}
.cal-row .cal-machine{font-size:10px;line-height:1.25;color:var(--faint);
  font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cal-row .cal-title{min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
/* The glyphs sit against the first line rather than centred across both. */
.cal-row{align-items:flex-start}
.cal-row .cal-glyph,.cal-row .cal-cat{margin-top:2px}

.cal-row[data-status="overdue"]{background:var(--occ-overdue-bg);
  border-left-color:var(--occ-overdue-line);color:var(--occ-overdue-ink)}
.cal-row[data-status="today"]{background:var(--occ-today-bg);
  border-left-color:var(--occ-today-line);color:var(--occ-today-ink)}
.cal-row[data-status="done"]{background:var(--occ-done-bg);
  border-left-color:var(--occ-done-line);color:var(--occ-done-ink)}
.cal-row[data-status="skipped"]{background:var(--panel3);
  border-left-color:var(--line2);color:var(--dim)}
/* 'upcoming' takes .cal-row's own neutral default — no override needed. */

/* The routine band is the quiet 'upcoming' treatment even when the tasks it
   summarizes are, individually, overdue — it exists specifically so a
   30-times-repeated daily chore does not shout for attention 30 times. */
.cal-row.cal-routine{background:var(--panel2);border-left-color:var(--line)}

.cal-cell[data-status="overdue"]{outline:1px solid var(--occ-overdue-line)}
.cal-cell[data-status="today"]{outline:1px solid var(--occ-today-line)}

/* Measured live at 320px: 282x20 -- 24px under the glove-sized touch floor
   (UI brief rule 2). Padding does the work of reaching 44px; the text stays
   small so it still reads as a caption, not a button. */
.cal-more{display:flex;align-items:center;width:100%;min-height:44px;
  background:none;border:none;padding:6px 0;font-size:10.5px;color:var(--accent);
  text-align:left;cursor:pointer;touch-action:manipulation}
.cal-more:hover{text-decoration:underline}

/* Task L3: the "include daily cleaning" toggle + the band-echo banner it
   controls. min-height on the LABEL, not just the checkbox, so the whole
   row (box or word) is the 44px tap target (UI brief rule 2) -- same
   label-is-the-hit-area pattern as the daily checklist's own checkboxes. */
.cal-cadence-toggle{display:flex;align-items:center;gap:8px;min-height:44px;
  padding:4px 0;font-size:12.5px;color:var(--ink);touch-action:manipulation}
.cal-cadence-toggle input{width:20px;height:20px;flex:none}
.cal-cadence-banner{font-size:12px;color:var(--faint);margin:2px 0 10px;min-height:1em}
/* Today view isn't part of the periodic/all toggle (web/views/calendar.js
   renderToday always requests 'all') -- hiding both here rather than
   leaving a control on screen that does nothing on that tab. */
.cal-page[data-view="today"] .cal-cadence-toggle,
.cal-page[data-view="today"] .cal-cadence-banner{display:none}

/* Task N1: below 640px table.cal-month (measured 472px inside a 266px
   container -- still sideways-scrolling however it's wrapped) is replaced
   by .cal-agenda, the date-grouped card list calendar.js paints alongside
   it from the same data (UI brief rule 5: card layout, not a scroll
   wrapper, for a 7-column grid this narrow). Mobile-first: cards are the
   base rule, the table only reappears at Marie's desktop width. */
.cal-agenda{display:block}
table.cal-month{display:none}
@media(min-width:640px){
  .cal-agenda{display:none}
  table.cal-month{display:table}
}
.cal-page[data-view="agenda"] table.cal-month{display:none}
.cal-page[data-view="agenda"] .cal-agenda{display:block}
.cal-agenda-card{background:var(--panel2);border:1px solid var(--line);
  border-radius:10px;padding:10px 12px;margin-bottom:10px}
.cal-agenda-card:last-child{margin-bottom:0}
.cal-agenda-card[data-status="overdue"]{outline:1px solid var(--occ-overdue-line)}
.cal-agenda-card[data-status="today"]{outline:1px solid var(--occ-today-line)}
.cal-agenda-date{font-size:12px;font-weight:700;color:var(--faint);
  text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px}
.cal-agenda .cal-row{font-size:13px;padding:6px 8px;margin-bottom:6px;min-height:32px}

.cal-today-card{background:var(--panel2);border:1px solid var(--line);
  border-radius:10px;padding:12px;margin-bottom:10px}
.cal-today-card:last-child{margin-bottom:0}
.cal-today-card .cal-row{font-size:13px;padding:6px 8px;margin-bottom:10px}
.cal-today-card .cal-markdone{min-height:44px}

/* MOVE ONE JOB TO ANOTHER DAY (pm.js's moveOccurrence). The button lives on
   every actionable row including the packed month grid, so it has to be small
   enough not to compete with the title -- it is the quietest thing in the row
   until hovered. .cal-row is flex, so `margin-left:auto` pins it right and the
   title keeps the space it had.
   The inline form is `flex-basis:100%` so it wraps onto its OWN line inside
   the row rather than squeezing the title out of a 1/7th-width month cell. */
.cal-row .cal-move{flex:none;margin-left:auto;padding:1px 6px;font-size:10px;
  line-height:1.5;opacity:.55;min-height:0}
.cal-row:hover .cal-move,.cal-move:focus{opacity:1}
.cal-row{flex-wrap:wrap}
.cal-moveform{flex-basis:100%;display:flex;gap:4px;align-items:center;margin-top:4px}
.cal-moveform .cal-movedate{flex:1 1 auto;min-width:0;padding:2px 4px;font-size:11px}
.cal-moveform .btn{padding:2px 8px;font-size:11px;min-height:0}
/* Today's card has room, so the two actions sit side by side at full tap size
   -- 44px is the same minimum .cal-markdone already claims above. */
.cal-actions{display:flex;gap:8px;align-items:stretch}
.cal-actions .cal-markdone{flex:1 1 auto}
.cal-actions .cal-move{flex:none;min-height:44px;padding:0 14px;opacity:1;font-size:13px}
.cal-today-card .cal-moveform{margin-top:8px}
/* The sub-step checklist sub-project A's rollup exists to preserve (spec
   §5.2) -- today view only, never in a month cell (see the comment beside
   its render call in calendar.js). */
.cal-steps{list-style:none;margin:0 0 10px;padding:0 0 0 20px}
.cal-steps li{font-size:12.5px;color:var(--dim);padding:3px 0;position:relative}
.cal-steps li::before{content:"☐";position:absolute;left:-18px;color:var(--faint)}

.cal-viewbtn.on{background:var(--accent-soft);color:var(--accent);border-color:transparent}

.pm-sheet-backdrop{position:fixed;inset:0;z-index:80;background:rgba(17,29,41,.52);display:flex;
  justify-content:flex-end;align-items:stretch}
.pm-sheet{width:min(520px,100%);height:100%;overflow:auto;background:var(--panel);padding:24px;
  box-shadow:-12px 0 36px rgba(17,29,41,.18)}
.pm-sheet-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;
  border-bottom:1px solid var(--line);padding-bottom:14px;margin-bottom:16px}
.pm-sheet-head h2{margin:2px 0 0}.pm-sheet section{border-bottom:1px solid var(--line);padding:8px 0 16px}
.pm-sheet-action{display:grid;gap:9px}.pm-sheet-action input,.pm-sheet-action textarea{width:100%}
.pm-instructions{padding-left:22px}.pm-instructions li{padding:4px 0}.pm-source-quote{margin:8px 0;
  border-left:3px solid var(--accent);padding:7px 10px;background:var(--panel2);font-size:12px}
.pm-sheet-danger{border-bottom:0!important}.cal-open,.daily-task-open,.maintenance-task-open{appearance:none;
  background:transparent;border:0;color:inherit;font:inherit;text-align:left;padding:0;cursor:pointer}
.cal-open{min-width:0;flex:1}.cal-open:hover .cal-title,.daily-task-open:hover{text-decoration:underline}
@media(max-width:640px){.pm-sheet{padding:18px}.pm-sheet-backdrop{align-items:flex-end}.pm-sheet{height:min(88vh,760px)}}

@media(max-width:640px){
  .cal-cell{height:auto;min-height:56px}
  table.cal-week{display:block;overflow-x:auto}
  /* The month/week steppers measured 41px wide -- a chevron is a small glyph
     and the button had shrunk to fit it. Width is the axis that fails here,
     not height. */
  #calPrev,#calNext{min-width:44px}
}

/* On-screen footprint for the two print-only hooks below (plan Task 9) —
   .cal-checkbox stays weightless on screen (it has no content until print).
   .cal-photo is the today-card's 48px unit-photo slot (spec §6.3) -- shares
   the same swappable-inner-element pattern as .unit-photo below (see that
   comment for why), sized separately since the today card and the unit list
   call for different footprints. */
.cal-photo{width:48px;height:48px;flex:none;border-radius:6px;background:var(--panel3);
  border:1px solid var(--line);display:flex;align-items:center;justify-content:center;overflow:hidden}
.cal-photo img{width:100%;height:100%;object-fit:cover;display:block}
.cal-photo svg{width:42%;height:42%;stroke:currentColor;fill:none;stroke-width:1.5;color:var(--faint)}
.printed-date{color:var(--faint)}
.cal-kitchen-label{font-weight:600;margin-left:10px}

/* --- unit photos + fallback tile (plan "Sub-project C" Task 3, spec §7) ---
 * .unit-photo is a fixed-size wrapper shared by the unit list and the
 * per-unit header; only its INNER element ever swaps (the category-icon
 * <svg> <-> a real <img>) so a photo arriving late never reflows the row --
 * see the layout-shift guarantee in web/views/units.js. Sizes per spec §7:
 * 64-96px in the unit list / per-unit header (size-72 below); 48-56px on
 * the today-checklist card (.cal-photo above handles that one). Never in
 * month/week calendar cells (spec §6.3) -- .cal-photo/.unit-photo are never
 * used there.
 */
.unit-photo{flex:none;border-radius:8px;background:var(--panel3);border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center;overflow:hidden}
.unit-photo img{width:100%;height:100%;object-fit:cover;display:block}
/* The fallback glyph sits centred at ~40% of tile size, tinted --faint so it
   reads as a deliberate placeholder rather than a broken/missing asset. */
.unit-photo svg{width:40%;height:40%;stroke:currentColor;fill:none;stroke-width:1.5;color:var(--faint)}
.unit-photo.size-72{width:72px;height:72px}
.unit-photo.size-48{width:48px;height:48px}

.units-table-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:8px}
.units-table{width:100%;border-collapse:collapse;min-width:760px}
.units-table th{text-align:left;font-family:"Barlow Condensed",sans-serif;font-size:11px;
  font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);
  padding:12px 16px;border-bottom:1px solid var(--line)}
.units-table td{padding:13px 16px;border-bottom:1px solid var(--line);font-size:13px;
  color:var(--dim);vertical-align:middle}
.units-table tbody tr:last-child td{border-bottom:0}
.units-record{cursor:pointer}
.units-record:hover,.units-record:focus-visible{background:var(--panel3);outline:none}
.units-unit-cell{display:flex;align-items:center;gap:10px;min-width:180px}
.units-record-link{display:block;color:var(--ink);font-size:14px;font-weight:600;text-decoration:none}
.units-record-link:hover{text-decoration:underline}
.units-unit-cell span,.units-table td small{display:block;color:var(--faint);font-size:11px;
  letter-spacing:.04em;text-transform:uppercase}
.units-doc{display:inline-block;font-family:"Barlow Condensed",sans-serif;font-size:11px;
  font-weight:600;letter-spacing:.08em;text-transform:uppercase;padding:3px 8px;border-radius:2px;
  background:var(--panel3);color:var(--dim)}
.units-doc[data-state="ready"]{background:var(--occ-done-bg);color:var(--ok)}
.units-due[data-state="overdue"]{color:var(--occ-overdue-ink);font-weight:700}

@media(max-width:720px){
  .units-table-wrap{overflow:visible;border:0}
  .units-table{display:block;min-width:0}
  .units-table thead{display:none}
  .units-table tbody{display:grid;gap:10px}
  .units-table .units-record{display:grid;grid-template-columns:1fr 1fr;background:var(--panel2);
    border:1px solid var(--line);border-radius:10px;padding:12px;gap:10px 16px}
  .units-table td{display:block;padding:0;border:0;min-width:0}
  .units-table td:first-child{grid-column:1/-1;padding-bottom:8px;border-bottom:1px solid var(--line)}
  .units-table td::before{content:attr(data-label);display:block;font-family:"Barlow Condensed",sans-serif;
    font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);
    margin-bottom:2px}
  .units-table td:first-child::before{display:none}
  .units-unit-cell{min-width:0}
}

.unit-group{margin-bottom:18px}
/* Heading and its one action on a line, so "Reorder" reads as something
   you turn on rather than something the list is always doing. */
.units-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
.units-head h3{margin:0}
.units-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap}
.unit-location{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;
  color:var(--faint);margin:0 0 8px}
.unit-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
/* Photo, then nickname, then make/model -- that visual priority (plan Task
   4 Step 3). */
/* A row is a link, and the cursor says so. It said `grab` on every row,
   permanently, which is half of why Dylan asked "why drag-and-move" about
   this screen: the list looked like a thing to rearrange even to someone
   who only wanted to open a machine. `grab` now appears only inside
   Reorder mode, on rows that really are handles. */
.unit-row{display:flex;align-items:center;gap:12px;background:var(--panel2);
  border:1px solid var(--line);border-radius:10px;padding:8px 12px;cursor:pointer}
.unit-row.reordering{cursor:grab}
.unit-row.dragging{opacity:.5}
/* Cross-location drop is refused (review FIX 4, web/views/units.js) --
   this is the affordance that shows it DURING the drag, not silently on
   release. */
.unit-row.drop-blocked{outline:2px dashed var(--bad);outline-offset:-2px;cursor:not-allowed}
.unit-drag{color:var(--faint);font-size:15px;flex:none;cursor:grab;user-select:none}
.unit-info{min-width:0}
.unit-nickname{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.unit-submodel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Per-unit status badge (Task M1's rollup, surfaced on the list -- team-lead
   review). Overdue is the ONLY state that shouts, reusing the SAME
   --occ-overdue-* tokens calendar.js already defines -- no new colours. A
   settled unit reuses the calm --occ-done-* and --ok tokens (good news, not
   an empty/error state). NOTE the spelling: writing that pair as
   `--occ-done-*` immediately followed by `/--ok` closes this comment early
   -- the `*` and `/` become a terminator -- and every CSS rule after this
   point is silently dropped. That is not hypothetical: it happened, and it
   took out the mobile bottom bar, the More sheet, the daily checklist, the
   calendar agenda cards and the print stylesheet in one go, with no error
   anywhere. Found by the playwright probe, which measured the desktop tab
   bar reappearing at 320px.
   'due' (open work, nothing overdue) stays neutral. The
   glyph plus the word carry the fact -- colour is never the only channel
   (WCAG 1.4.1, see web/views/units.js's unitStatusBadge). Wraps rather than
   truncating (no white-space:nowrap) so a long "next periodic due" suffix
   never pushes the card into horizontal overflow at 320px. */
.unit-status{display:inline-flex;align-items:center;gap:5px;font-size:11px;
  font-weight:600;padding:3px 8px;border-radius:999px;margin-top:5px;
  max-width:100%;overflow-wrap:anywhere}
.unit-status-glyph{flex:none;font-size:10px;line-height:1}
.unit-status[data-state="overdue"]{background:var(--occ-overdue-bg);
  color:var(--occ-overdue-ink);border:1px solid var(--occ-overdue-line)}
.unit-status[data-state="due"]{background:var(--panel3);color:var(--dim);
  border:1px solid var(--line);font-weight:500}
.unit-status[data-state="settled"]{background:var(--occ-done-bg);color:var(--ok);
  border:1px solid var(--occ-done-line);font-weight:500}

/* --- print (plan Task 9) --------------------------------------------------
 * Not a description of print rules — these rules. See the plan's own
 * commentary (docs/superpowers/plans/2026-08-30-subproject-b-pm-calendar.md,
 * Task 9) for why each one is load-bearing; kept verbatim here.
 */
@media print {
  /* Chrome/Safari strip background-color by default ("Print background
     graphics" is opt-in), and .card/.step/.banner all set explicit
     backgrounds that otherwise survive. !important is required, not tidiness. */
  .wrap, nav.tabs, header.top .who, .btn { display: none !important; }
  body, .cal-page { background: #fff !important; }
  .cal-cell, .cal-cell * {
    background: transparent !important; color: #000 !important; box-shadow: none !important;
  }

  /* Status by border STYLE as well as width. Two 4px black borders are
     identical in grayscale, so solid/double/grey is what actually
     distinguishes them on a photocopy. Colour carries nothing here. */
  .cal-cell { border: 1px solid #999; }
  .cal-row[data-status="overdue"] { border-left: 4px solid #000; }
  .cal-row[data-status="today"]   { border-left: 4px double #000; }
  .cal-row[data-status="done"]    { border-left: 4px solid #999; }

  /* Without the :last-child exception this emits a trailing blank page. */
  .cal-page { page-break-after: always; }
  .cal-page:last-child { page-break-after: auto; }

  .cal-header { display: flex; justify-content: space-between; font-size: 11pt; }
  /* The wall copy needs its provenance -- which kitchen, run off when --
     because a printed month on a noticeboard has no other way to say. */
  .screen-hidden { display: inline !important; }
  .cal-header .printed-date::before { content: "Printed "; }

  /* position: fixed puts the legend on EVERY page. Placed in normal flow it
     appears only on the last one — and a wall reader has no other way to
     learn the code. */
  .cal-legend {
    position: fixed; bottom: 0; left: 0; right: 0;
    display: flex; gap: 16px; font-size: 9pt;
    border-top: 1px solid #000; padding-top: 4px;
  }
  .cal-legend .swatch { font-weight: 700; }  /* a glyph, never a colour chip */

  .cal-checkbox::before { content: "☐"; font-size: 15px; }  /* pen-fillable */

  .cal-photo { max-width: 40px; max-height: 40px; }
  .cal-page[data-view="month"] .cal-photo { display: none; }
}

/* --- Mobile navigation (day plan Task N2) --------------------------------
 *
 * Nine tabs in three wrapped rows is not navigation. Measured at 320px it was
 * 142px of a 720px screen -- 19% -- before a single row of content. The
 * ruleset caps a bottom bar at five destinations; the other five live behind
 * More.
 *
 * Fixed to the bottom because that is where a thumb is at 9pm with one hand
 * on a sheet pan, and padded by env(safe-area-inset-bottom) because a bar
 * underneath the iPhone home indicator is not merely ugly, it is untappable.
 *
 * Desktop is untouched: this whole block is display:none above 640px, and
 * nav.tabs is unchanged there. Marie's console does not move.
 */
.mobilenav{display:none}
.morepanel{display:none}

@media(max-width:640px){
  body.signed-in nav.tabs{display:none}

  body.signed-in .mobilenav{
    display:grid;grid-template-columns:repeat(5,1fr);
    position:fixed;left:0;right:0;bottom:0;z-index:40;
    background:var(--panel);border-top:1px solid var(--line);
    padding:4px 4px calc(4px + env(safe-area-inset-bottom));
    box-shadow:0 -2px 12px rgba(16,24,40,.08);
  }
  .mobilenav button{
    /* 48px, not 44: 44 is the iOS floor and this is used with gloves on.
       touch-action:manipulation removes the 300ms double-tap delay, which on
       a checklist reads as the app ignoring you. */
    min-height:48px;touch-action:manipulation;
    display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
    background:none;color:var(--dim);font-size:10px;font-weight:600;
    padding:4px 2px;border-radius:8px;
  }
  .mobilenav button svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.6;
    stroke-linecap:round;stroke-linejoin:round}
  .mobilenav button.on{color:var(--accent);background:var(--accent-soft)}
  .mobilenav button:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

  /* The bar is fixed, so the last row of content would sit underneath it
     forever without this. 56px bar + the inset. */
  body.signed-in .wrap{padding-bottom:calc(76px + env(safe-area-inset-bottom))}

  body.signed-in .morepanel:not(.hide){
    display:grid;gap:6px;
    position:fixed;right:8px;left:8px;bottom:calc(64px + env(safe-area-inset-bottom));z-index:41;
    background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
    padding:8px;box-shadow:var(--shadow);
  }
  .morepanel button{
    min-height:48px;touch-action:manipulation;text-align:left;
    background:var(--panel2);color:var(--ink);border:1px solid var(--line);
    padding:0 14px;font-weight:600;
  }
  .morepanel button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
}

/* --- Today / daily closing checklist (day plan Task L2) ------------------
 *
 * Grouped by UNIT, not by task, because Theo walks the kitchen unit by unit.
 * Sub-steps are collapsed by default: 28 pilot tasks carry them and expanding
 * them all buries the list this surface exists to shorten.
 */
.daily-header{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  flex-wrap:wrap;margin-bottom:12px}
.daily-progress{font-variant-numeric:tabular-nums}
.daily-unit-card{margin-bottom:12px;padding:12px}
.daily-unit-card:last-child{margin-bottom:0}
.daily-unit-header{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:8px}
.daily-unit-info{min-width:0;flex:1}
.daily-unit-name{font-weight:600;overflow-wrap:anywhere}
.daily-unit-sub{overflow-wrap:anywhere}
.daily-unit-count{white-space:nowrap;font-variant-numeric:tabular-nums}
.daily-task-list{list-style:none;margin:0;padding:0}
.daily-task-row{border-top:1px solid var(--line);padding:2px 0;display:grid;grid-template-columns:auto minmax(0,1fr);align-items:start}
.daily-task-row:first-child{border-top:none}
/* The whole label is the hit target, at 48px, so a gloved thumb aimed at the
   task title does not miss a 16px checkbox. */
.daily-check-label{display:flex;align-items:center;gap:10px;min-height:48px;
  touch-action:manipulation;cursor:pointer;padding:2px 0}
.daily-task-open{min-height:48px;display:flex;align-items:center;width:100%;padding:2px 6px}
.daily-checkbox{flex:none}
.daily-task-title{flex:1;min-width:0;overflow-wrap:anywhere}
/* .daily-cat is the <svg> itself (categorySvg puts className on the element),
   so the old `.daily-cat svg{width:10px}` descendant rule matched nothing and
   the glyph has always drawn at the 12px its width/height attributes ask for
   -- which is what the checklist wants at its larger row height. Rule removed
   rather than "fixed" to 10px: the 12px was the intended size. */
.daily-cat{flex:none}
.daily-steps-toggle{min-height:44px;touch-action:manipulation;background:none;
  color:var(--dim);font-size:12px;padding:0 6px;display:inline-flex;align-items:center;gap:6px}
.daily-steps-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.daily-chevron{transition:transform .18s ease;display:inline-block}
.daily-steps-toggle[aria-expanded="true"] .daily-chevron{transform:rotate(90deg)}
.daily-steps-wrap{padding:0 0 6px 6px;grid-column:2}
.daily-steps{list-style:none;margin:0;padding:0}
.daily-steps li{font-size:12.5px;color:var(--dim);padding:3px 0;position:relative;padding-left:18px}
.daily-steps li::before{content:"☐";position:absolute;left:0;color:var(--faint)}
/* Keyed off data-status, NOT a .done class. taskRowHtml has only ever
   written data-status="done"; the old `.daily-task-row.done` selector
   matched nothing, so a completed task rendered identically to an open one
   and the only sign anything had happened was the tick itself. Confirmed in
   the running console before the fix: 8 of 24 rows done, 0 with the class,
   computed text-decoration `none`. data-status is also what .cal-row uses,
   so this keeps one convention across the views. */
.daily-task-row[data-status="done"] .daily-task-title{color:var(--faint);text-decoration:line-through}

/* --- Unit detail (day-plan Task M2/M4) ------------------------------------
 * Marie navigates by time, Theo by equipment, Sam by symptom -- all three
 * converge here (day-plan §1), which is why it borrows visual language from
 * three other surfaces rather than inventing a fourth: .unit-photo (units.js),
 * .cal-row/.cal-cat status colour+shape (calendar.js), .daily-steps* disclosure
 * (daily.js). Only the NEW shapes below are new rules.
 */
/* The unit page shares the one measure too -- capped at 1180 it was the one
   screen that still sat in a narrower column than the tabs above it. */
.udet-page{max-width:none}
.udet-back{display:inline-flex;align-items:center;min-height:44px;
  font-weight:600;font-size:13px;touch-action:manipulation}
.udet-hero-grid,.udet-content-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:24px;align-items:start}
.udet-content-grid{margin-top:24px}
.udet-hero-grid>.card+.card{margin-top:0}
.udet-column{min-width:0}
@media(max-width:900px){
  .udet-hero-grid,.udet-content-grid{grid-template-columns:1fr;gap:14px}
  .udet-content-grid{margin-top:14px}
}
/* .udet-header is the CARD (block); .udet-header-row is the photo+text flex
   row inside it -- split so the photo-capture controls below (Task P1) can
   sit under that row, full width, without becoming a third flex child. */
.udet-header-row{display:flex;gap:14px;align-items:flex-start}
.udet-info{min-width:0;flex:1}
.udet-name{overflow-wrap:anywhere}
.udet-sub,.udet-serial,.udet-badges{overflow-wrap:anywhere;margin-top:2px}
.udet-serial{color:var(--dim)}
.udet-badges{color:var(--warn)}

/* Staff-snapshot photo capture (Task P1) -- a compact control, deliberately
   NOT a full-width block button, so it never competes with the two doors
   (below) for visual weight or space above the fold at 320px. The real
   file input is visually hidden; its LABEL is styled as the button (the
   standard accessible pattern -- a native file input cannot be restyled
   consistently across browsers). */
.udet-photo-actions{margin-top:12px;display:flex;align-items:center;
  gap:10px;flex-wrap:wrap}
.udet-photo-btn{min-height:44px;touch-action:manipulation;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;padding:8px 14px}
.udet-photo-input{display:none}
.udet-photo-progress{flex:1;min-width:80px;height:6px;background:var(--panel3);
  border-radius:999px;overflow:hidden}
.udet-photo-progress-bar{height:100%;background:var(--accent);
  transition:width .2s ease}
.udet-photo-error{width:100%;color:var(--bad);overflow-wrap:anywhere}

/* Sam's two doors (day-plan Task M4) -- above the schedule, both >=44px
   regardless of viewport (the brief's exact wording), not gated to the
   mobile breakpoint the way the rest of this file's control-sizing rules are. */
.udet-doors{display:flex;flex-direction:column;gap:10px}
.udet-door{min-height:44px;touch-action:manipulation}

/* Remove this unit.

   The card sits LAST on the page and, at rest, holds one quiet secondary
   button -- the same weight as "Something wrong? Get help" above it. Nothing
   red is on screen until someone has actually asked to remove something.
   Spending red on the resting state would make the most destructive control
   in the console also its most eye-catching, on a tablet operated with
   gloves.

   Once confirming, the card takes a --bad hairline and a tinted ground so it
   reads as a different mode rather than one more card in the stack, and the
   two choices sit side by side at equal size: "Keep it" is not a whispered
   link, because backing out must be at least as easy as going through. Both
   clear 44px at every viewport, matching .udet-door's reasoning. */
.udet-remove{margin-top:14px}
.udet-remove-open{min-height:44px;touch-action:manipulation;color:var(--dim)}
.udet-remove[data-stage]{border-color:var(--bad);background:#fff7f8}
.udet-remove-h{margin:0 0 6px;font-size:14px;color:var(--ink);overflow-wrap:anywhere}
.udet-remove-what{margin:0 0 10px;color:var(--dim);line-height:1.5}
.udet-remove-error{margin:8px 0 0;color:var(--bad);overflow-wrap:anywhere}
.udet-remove-actions{display:flex;gap:8px;flex-wrap:wrap}
.udet-remove-actions .btn{flex:1 1 auto;min-height:44px;min-width:120px;
  touch-action:manipulation;overflow-wrap:anywhere}

.udet-section{margin-top:16px}
.udet-section:first-child{margin-top:0}
.udet-section-h{font-size:11px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--faint);font-weight:600;margin:0 0 8px}
.udet-row{background:var(--panel);border:1px solid var(--line);border-radius:10px;
  padding:10px 12px;margin-bottom:8px}
.udet-row:last-child{margin-bottom:0}
/* Sizing/spacing only -- deliberately does NOT touch .cal-row's own
   background/border-left, which is where status colour (overdue/today/
   upcoming) actually lives; overriding it here would fight
   .cal-row[data-status] on equal specificity by source order. Same pattern
   as .cal-today-card .cal-row above. */
.udet-row .cal-row{font-size:13.5px;padding:8px 10px;margin-bottom:8px}
.udet-row .daily-steps-wrap{padding-left:18px}
.udet-markdone{min-height:44px}

/* --- Ask, pre-scoped from a unit (day-plan Task M4) -----------------------
 * Sam never picks a machine from a dropdown one-handed -- the dropdown is
 * swapped for a static readout plus an explicit "Change" escape hatch. */
.ask-preset-row{display:flex;align-items:center;justify-content:space-between;
  gap:10px;background:var(--panel2);border:1px solid var(--line);
  border-radius:10px;padding:10px 12px;margin-bottom:12px}
.ask-preset-label{font-size:13px;font-weight:600;overflow-wrap:anywhere}

/* A collapsed run of future daily occurrences on the unit screen (Task M2
   follow-up). Reads as context, not as a call to action, and carries no
   control: completing work that is not due yet re-anchors the task from today
   and supersedes today's real row, so a tappable future daily is a foot-gun.
   Today's copy of the same task sits in "Due today" above with its button. */
.udet-band .cal-row{opacity:.72}
.udet-band-note{margin:2px 0 0 22px;overflow-wrap:anywhere}

/* An upcoming (non-daily) row's due date -- same indent as udet-band-note,
   same reason: it sits under the icon+title row it belongs to. Added
   because upcoming periodic rows previously carried NO date at all, so
   several occurrences of the same task (e.g. a weekly filter clean's next
   dozen dates) rendered as identical, undated, unticked-looking duplicates
   (Fable's e2e finding). */
.udet-upcoming-date{margin:2px 0 0 22px}

/* --- Schedule start + feed scope (sub-project Q, web half) ----------------
 * Two cards, siblings of #onbTimezoneCard: "When should scheduled work
 * start?" and "Calendar subscription includes". Both use plain <label>
 * wraps around native inputs so the WHOLE row is the tap target (>=44px),
 * not just the 16px checkbox/radio circle -- the same reasoning as
 * .udet-photo-btn above, just for a row instead of a button. */
.onbsch-row{display:flex;align-items:flex-start;gap:10px;
  min-height:44px;padding:9px 2px;cursor:pointer}
.onbsch-row input{margin-top:2px;flex:none}
.onbsch-row .txt{flex:1;overflow-wrap:anywhere}
.onbsch-row .txt .sub{display:block;color:var(--faint);font-size:11px;margin-top:2px}
/* Field-level error, directly under the control it belongs to -- the UI
   brief's "errors render next to the field that caused them", not only in
   a banner at the top. */
.onbsch-err{color:var(--bad);font-size:11.5px;margin-top:4px}
.onbsch-note{color:var(--faint);font-size:11px;margin:4px 0 0}
/* The live "your Today list will be empty" warning under Start date --
   --warn, not --faint/--bad: this is a real heads-up (not routine helper
   text, not an error), same token unit_detail.js's udet-badges uses for
   "next due" callouts. */
.onbsch-future{color:var(--warn)}

/* Manuals list on the console unit page. Rows, not buttons: each one is a
   real link to a real PDF, and it should behave like one (middle-click,
   copy link address). The arrow says it leaves for a new tab. */
.udet-manual-list{display:flex;flex-direction:column;gap:6px}
/* Two elements share this class: an <a> for catalog manuals and a <button>
   for uploaded ones, which need an auth header a bare navigation cannot send
   (see manualsCardHtml). font/cursor/text-align/width are here so the button
   is visually indistinguishable from the link -- a browser's default button
   chrome would otherwise make one row in the list look like a different
   control than the row above it. */
.udet-manual{display:flex;align-items:center;gap:10px;min-height:44px;
  padding:8px 12px;border:1px solid var(--line);border-radius:10px;
  background:var(--panel2);color:var(--ink);text-decoration:none;font-size:13px;
  font-family:inherit;text-align:left;width:100%;cursor:pointer}
.udet-manual:hover{border-color:var(--accent);color:var(--accent)}
.udet-manual-icon{flex:none;color:var(--faint);font-size:13px}
.udet-manual-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.udet-manual-go{flex:none;color:var(--faint);font-size:12px}
/* A document with no bytes to open yet (still processing, or failed) --
   deliberately its OWN class, not a disabled `.udet-manual`, so it never
   picks up that class's link hover/cursor styling. Dashed border reads as
   "placeholder", not "broken control". */
.udet-manual-pending{display:flex;align-items:center;gap:10px;min-height:44px;
  padding:8px 12px;border:1px dashed var(--line);border-radius:10px;
  background:var(--panel3);color:var(--dim);font-size:13px}

/* --- Document review (plan task A0.3) --------------------------------------
 * Per-document detail below the open link/button: doc_type + ingestion
 * badges, page count, source URL, match_reason, Delete -- shared markup
 * (web/views/shared/doc_review.js) used by both the unit page's Manuals
 * card and the Documents tab's per-unit review list, so the two surfaces
 * cannot drift into showing this differently.
 */
.doc-review-row{border-top:1px dashed var(--line);margin-top:8px;padding-top:8px}
.udet-manual-list .doc-review-row:first-child{border-top:none;margin-top:0;padding-top:0}
.doc-review-row .doc-review-main{margin-bottom:6px}
.doc-review-meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-bottom:4px}
.doc-review-pages{white-space:nowrap}
.doc-review-source{overflow-wrap:anywhere;margin-bottom:2px}
.doc-review-reason{margin-bottom:6px}
/* Colour rides the SAME tone vocabulary as the badges beside it (good/check/
   bad/unknown) -- redundant with the words, never the only signal. */
.doc-review-reason[data-tone="good"]{color:var(--ok)}
.doc-review-reason[data-tone="check"]{color:var(--warn)}
.doc-review-reason[data-tone="bad"]{color:var(--bad)}
.doc-review-reason[data-tone="unknown"]{color:var(--faint)}
.doc-review-actions{margin-top:4px}
.doc-review-actions .btn{min-height:36px;padding:7px 12px;font-size:12.5px}
.doc-review-confirm{background:#fff7f8;border:1px solid var(--bad);border-radius:8px;padding:8px 10px}
.doc-review-confirm p{margin:0 0 6px}
.doc-review-confirm .row{gap:6px}
.doc-review-confirm .btn{flex:1 1 auto;min-width:100px}
/* A neutral badge for a tone this table has no colour opinion about yet
   (match_reason absent on an older document, an unrecognized doc_type) --
   visible without reading as either good or bad news. */
.tag.info{background:var(--panel3);color:var(--dim);border:1px solid var(--line)}

.udet-manual-research{margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}

/* Toasts (api.toast) -- what replaced alert() on every failure path.
   Bottom-centre rather than a corner: on a wall tablet the corner is the
   easiest place on the screen not to look at, and a failed "mark done" that
   nobody sees is a task the kitchen believes is finished. Fixed, above
   everything, and never blocking -- unlike the native modal, the console
   stays usable while it is up. */
#skdToasts{position:fixed;left:50%;bottom:20px;transform:translateX(-50%);
  z-index:9999;display:flex;flex-direction:column;align-items:center;gap:8px;
  pointer-events:none;width:min(560px,calc(100vw - 32px))}
.toast{pointer-events:auto;cursor:pointer;width:100%;box-sizing:border-box;
  padding:11px 14px;border-radius:10px;font-size:12.5px;line-height:1.45;
  box-shadow:0 6px 20px rgba(29,41,57,.16);
  animation:toast-in .18s ease-out}
.toast-bad{color:#8f1d35;background:#fff1f3;border:1px solid #f3a5b4}
.toast-ok{color:#04603a;background:var(--occ-done-bg);border:1px solid #6fce9f}
.toast.leaving{opacity:0;transition:opacity .2s ease}
@keyframes toast-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){
  .toast{animation:none}
  .toast.leaving{transition:none}
}

/* --- manual ingestion progress (plan P2"/P5) --------------------------------
   Determinate and indeterminate are two DIFFERENT things on purpose. The
   server sends a denominator only where a real one exists -- pages after
   extraction, embedding batches after batching -- and none for a web search or
   a non-streaming download. The indeterminate variant sweeps; it never shows a
   number, because a bar that sticks at 40% reads as a hang. */
.udet-ingest-line {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: baseline;
  margin-bottom: 10px;
}
.udet-ingest-bar {
  height: 4px;
  border-radius: 999px;
  /* The TRACK, not the fill: --faint is body-text grey and painted this bar
     almost black with a blue sliver moving across it. */
  background: var(--panel2);
  overflow: hidden;
}
.udet-ingest-fill {
  height: 100%;
  background: var(--accent, #2563eb);
  border-radius: 999px;
  transition: width .4s ease;
}
.udet-ingest-bar.indeterminate .udet-ingest-fill {
  width: 40% !important;
  animation: udet-ingest-sweep 1.4s ease-in-out infinite;
}
@keyframes udet-ingest-sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}
/* A kitchen screen left on overnight should not animate forever at someone
   who has asked the OS to stop moving things. */
@media (prefers-reduced-motion: reduce) {
  .udet-ingest-bar.indeterminate .udet-ingest-fill { animation: none; width: 100% !important; opacity: .5; }
  .udet-ingest-fill { transition: none; }
}
.udet-ingest .btn { margin-top: 12px; }
/* Technician settings use the reference's compact, bordered trade rows.
   Tokens, not literals: these were the same four greys the tokens already
   name, written out by hand, so a retune of --line or --panel2 moved every
   surface in the console EXCEPT this one. */
.technicianRow { display:flex; align-items:end; flex-wrap:wrap; gap:16px; border:1px solid var(--line); padding:16px 18px; margin:16px 0; }
.technicianRow label { display:flex; flex:1 1 150px; flex-direction:column; gap:5px; color:var(--dim); font-size:12px; }
.technicianRow input { width:100%; min-height:40px; background:var(--panel2); border:1px solid var(--line); padding:8px; color:var(--ink); }
.technicianActions { display:flex; flex-wrap:wrap; gap:12px; margin-top:18px; }
.technicianStatus { min-height:24px; }
.technicianForm fieldset:disabled { opacity:.7; }

/* Physical equipment identity and editable profile controls. The detail
   page remains a narrow field sheet; these rows use the same grey controls
   and blue action hierarchy as the rest of the operations console. */
#unitsSearch{margin:0 0 16px}
.udet-profile fieldset{border:0;padding:0;margin:0;display:grid;grid-template-columns:1fr 1fr;gap:12px}
.udet-profile label{display:flex;flex-direction:column;gap:5px;color:var(--dim);font-size:12px}
.udet-profile label:first-child{grid-column:1/-1}
.udet-profile button[type=submit]{justify-self:start;align-self:end;min-height:42px}
.udet-form-error{color:var(--bad);margin:10px 0 0}
.udet-form-ok{color:var(--ok);margin:10px 0 0}
.udet-contact-row,.ask-contact-card{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap}
.udet-contact-row>div:first-child,.ask-contact-card>div:first-child{display:grid;gap:2px}
.udet-contact-row>div:last-child,.ask-contact-actions{display:flex;gap:8px;flex-wrap:wrap}
.udet-contact-row .btn,.ask-contact-actions .btn{min-height:40px;padding:8px 12px;text-decoration:none}
.udet-model-change,.udet-manual-change{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:12px 0}
.udet-model-change>div,.udet-manual-change>div{background:var(--panel2);border:1px solid var(--line);padding:12px;border-radius:9px;display:grid;gap:4px}
.udet-manual-change ul{padding-left:18px;margin:7px 0 0;color:var(--dim)}
.udet-confirm-actions{display:flex;gap:9px;flex-wrap:wrap;margin-top:14px}
.ask-unit-context{display:flex;justify-content:space-between;gap:12px;border-top:1px solid var(--line);padding-top:12px;margin-top:14px;font-weight:600}
.ask-contact-card{background:var(--accent-soft);border:1px solid #b9cadb;border-radius:9px;padding:11px 12px;margin-top:10px}
.unit-choice{display:grid;gap:8px}
.unit-choice-row{display:flex;justify-content:space-between;gap:14px;text-align:left;background:var(--panel2);border:1px solid var(--line);padding:12px;color:var(--ink)}
.unit-choice-row:hover{border-color:var(--accent)}
.unit-choice-row span:first-child{display:grid;gap:2px}
.unit-choice-row small{color:var(--dim)}
.udet-task-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:9px}
.udet-task-actions .btn{min-height:40px;flex:1 1 140px}
.ask-mode{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:0 0 12px}
.ask-mode-choice{min-height:44px}
.ask-mode-choice.on{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}
.ask-task-context{background:var(--accent-soft);border:1px solid #b9cadb;border-radius:9px;padding:10px 12px;margin-bottom:12px}
.ask-step-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
.ask-step-actions .btn{min-height:40px}
.ask-incident-state{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}
.ask-outcome-backdrop{position:fixed;inset:0;z-index:85;background:rgba(17,29,41,.52);display:flex;justify-content:flex-end;align-items:stretch}
.ask-outcome-sheet{width:min(520px,100%);height:100%;overflow:auto;background:var(--panel);padding:24px;box-shadow:-12px 0 36px rgba(17,29,41,.18)}
.ask-outcome-sheet label{display:grid;gap:5px;color:var(--dim);font-size:12px;margin:12px 0}
.ask-outcome-sheet input,.ask-outcome-sheet textarea{width:100%;box-sizing:border-box}
.ask-outcome-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px}
.ask-outcome-actions .btn{min-height:42px}
.ask-outcome-actions[aria-busy="true"]{opacity:.7;pointer-events:none}
.ask-interrupted{display:grid;gap:8px;justify-items:start;margin-top:12px}
.ask-packet{display:grid;gap:10px}
.ask-packet pre{white-space:pre-wrap;overflow-wrap:anywhere;background:var(--panel2);border:1px solid var(--line);padding:12px}
.activity-actions,.activity-filters{display:flex;gap:12px;align-items:end;flex-wrap:wrap}
.activity-filters label{display:grid;gap:5px;flex:1;min-width:140px}
.activity-filters input,.activity-filters select{width:100%;min-height:44px}
.activity-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;margin:20px 0}
.dashboard-kpi{color:var(--ink);text-decoration:none;transition:border-color .18s ease}
.dashboard-kpi:hover{background:transparent;border-color:var(--ink)}
.dashboard-kpi:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.activity-evidence{white-space:pre-wrap;overflow-wrap:anywhere;max-width:48ch;font-size:12px}
/* The two panels are one row, so they share a bottom edge. I had set
   align-items:start to let each size to its own content, which left the
   shorter one floating against a taller neighbour -- two boxes that are
   obviously a pair, not lining up. Stretch is the default; it is here
   explicitly so the next person does not "fix" it back. */
.dashboard-columns{display:grid;grid-template-columns:1fr 1fr;gap:20px;align-items:stretch}
.dashboard-attention{color:var(--ink)}
.settingsPanel form label,.profile-form label{display:grid;gap:8px;margin:14px 0}
.settingsPanel textarea{width:100%;min-height:160px;padding:12px;font:inherit}
.table-wrap{overflow:auto}.table-wrap table{width:100%}
@media(max-width:760px){.activity-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.dashboard-columns{grid-template-columns:1fr}}
@media(max-width:640px){
  .udet-profile fieldset,.udet-model-change,.udet-manual-change{grid-template-columns:1fr}
  .udet-profile label:first-child{grid-column:auto}
  .udet-contact-row,.ask-contact-card,.unit-choice-row{align-items:stretch;flex-direction:column}
  .ask-mode{grid-template-columns:1fr}
  .ask-outcome-backdrop{align-items:flex-end}
  .ask-outcome-sheet{height:min(88vh,760px);padding:18px}
}

/* ==========================================================================
 * INDUSTRY — the blueprint frame (2026-09-16)
 * ==========================================================================
 *
 * The design system's own closing block, ported from the prototype. Every
 * component above was drawn as a raised tile: 9-12px corners, a white fill
 * lifted off the page by a shadow, 999px lozenges for status. The design is
 * the opposite object — a wireframe drawing. Square, transparent, hairline,
 * flat. One page plane; the border is the only edge.
 *
 * It sits LAST, the way the prototype's own frame block does, so a component
 * added later inherits the shape without anyone having to remember it. The
 * rules above keep their colour, spacing and behaviour decisions; this block
 * only ever overrides shape, fill and elevation.
 *
 * What is deliberately NOT squared: the spinner and the radio dot (circles
 * are their meaning), and native checkbox/radio chrome. What keeps its
 * shadow: things that must occlude the page behind them — the side sheets,
 * the toast stack, the fixed mobile bar — because a flat overlay on a flat
 * page has no way to say it is in front.
 */

.pill,.tag,.res,.cite,.step,.step .n,.prose,.banner,.card,
.kpi,.unit-row,.unit-photo,.cal-photo,.cal-row,.cal-agenda-card,.cal-today-card,
.udet-row,.udet-manual,.udet-manual-pending,.udet-remove,.units-table-wrap,
.units-doc,.unit-status,.unit-choice-row,.ask-preset-row,.ask-contact-card,
.ask-task-context,.ask-packet pre,.doc-review-confirm,.hazardBanner,.proBanner,
.technicianRow,.technicianRow input,.udet-model-change>div,.udet-manual-change>div,
.udet-photo-progress,.udet-ingest-bar,.udet-ingest-fill,.morepanel button,
.mobilenav button,.toast,.morepanel,.pm-sheet,.ask-outcome-sheet,
select,input:not([type=checkbox]):not([type=radio]),textarea,button{
  border-radius:0;
}
/* Chips stay a hair off square — 2px, the design's --radius-sm — so a
   status chip does not read as a table cell at 11px. */
.tag,.res,.units-doc,.unit-status{border-radius:var(--radius-chip)}

/* No component carries a fill of its own. The tinted ones that remain are
   carrying STATUS (an overdue row, a done row, a hazard banner), which is
   the one thing colour is spent on in this system. */
.kpi,.unit-row,.udet-row,.ask-preset-row,.unit-choice-row,
.cal-agenda-card,.cal-today-card,.udet-manual,.prose,.step,
.settingsNav,.settingsNav button{
  background:transparent;
}
/* .card is absent from both lists above and below on purpose: it sets its
   own background and box-shadow where it is defined, and restating them
   here would mean an edit there quietly loses to this block. */
.morepanel{box-shadow:none}
.unit-row,.udet-row,.ask-preset-row,.unit-choice-row,.udet-manual,
.cal-agenda-card,.cal-today-card{border-color:var(--line)}

/* Status chips, squared. Same inks as everywhere else; only the shape moved. */
.unit-status{padding:3px 8px;font-family:var(--heading);font-size:11px;
  letter-spacing:.07em;text-transform:uppercase}

/* A panel in that row fills the height the row gives it, and its list of
   rows sits at the top of that space rather than spreading to fill it.

   The margin reset is load-bearing: `.card+.card{margin-top:14px}` is the
   rule for cards STACKED down a page, and in a two-column grid it lands on
   the second item as a 14px top offset -- so the pair shared a bottom edge
   and missed at the top by exactly that. Same reset .udet-hero-grid already
   carries, for the same reason. */
.dashboard-columns>.card{display:flex;flex-direction:column}
.dashboard-columns>.card+.card{margin-top:0}

/* --- dashboard panels ---------------------------------------------------
 * Both columns are lists of hairline-separated rows inside a bordered
 * frame, and each row is: an identifier, a quieter line of provenance
 * under it, and one fact pinned to the right edge. That shape is what the
 * design repeats on every surface — the calendar agenda, the unit sheet,
 * the equipment table all restate it — so it is written once here.
 */
.dashboard-columns{gap:24px}
.dashboard-attention,.dashboard-event{
  display:flex;flex-direction:row;align-items:flex-start;gap:14px;padding:13px 0;
  border-bottom:1px solid rgba(29,31,32,.1);text-decoration:none}
.dashboard-attention:last-child,.dashboard-event:last-child{border-bottom:0}
.dashboard-attention:hover{background:rgba(29,31,32,.035)}
.dashboard-attention-main,.dashboard-event-main{
  flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}
/* The identifier is the condensed heading face: it is what the eye lands
   on first in a column of six, and it is the only thing in the row that
   needs to survive being read at arm's length. */
.dashboard-attention strong,.dashboard-event strong{
  font-family:var(--heading);font-weight:600;font-size:17px;line-height:1.2;
  letter-spacing:-.005em;color:var(--ink);overflow-wrap:anywhere}
.dashboard-attention-main>span,.dashboard-event-main>span{
  font-size:12.5px;color:var(--faint);overflow-wrap:anywhere}
/* A fixed, tabular time gutter. Fixed because the point of a left-hand time
   column is that the headlines beside it line up; tabular because otherwise
   09:12 and 13:40 are different widths and they do not. */
.dashboard-event-when{flex:none;width:52px;font-size:12px;color:var(--faint);
  font-variant-numeric:tabular-nums;padding-top:3px}
.dashboard-event-type{flex:none;font-size:11.5px;color:var(--faint);
  text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;padding-top:3px}
/* Overdue is the only thing on this page allowed to shout, and it shouts in
   the same red the calendar and the unit list already use. */
.tag.late{flex:none;background:var(--occ-overdue-bg);color:var(--occ-overdue-ink);
  border:1px solid var(--occ-overdue-line);align-self:center}

/* A secondary button on the dark band is drawn in the band's own ink. The
   page-level rule paints it near-black, which on #1d2d3d is an invisible
   control -- Sign out disappeared entirely. */
.top .btn.sec{color:#fff;border-color:rgba(255,255,255,.34);background:transparent}
.top .btn.sec:hover:not(:disabled){background:rgba(255,255,255,.1);border-color:#fff}
.top input,.top select{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.28);color:#fff}
.top input::placeholder{color:rgba(255,255,255,.55)}

/* The denominator in a "39/43" figure is context, not the number being
   reported -- it reads at the sub-line's size and ink so the count itself
   keeps the display weight. */
.kpi .v small{font-size:.5em;font-weight:600;color:var(--faint);letter-spacing:0}

/* A <button> with no class of its own. Before the blueprint frame these
   were merely flat; now that every other control is a bordered rectangle,
   an unframed one reads as a paragraph -- "Save contacts", "Add a trade",
   "Remove" and "Copy number" were all sitting in Settings looking like
   prose. Giving the bare element the secondary treatment fixes the ones
   that exist and the next one somebody writes; a button that wants to look
   like text asks for .textAction. */
/* Scoped to the view, NOT to every classless <button> on the page. The
   navigation's own buttons are written without a class in index.html and
   only get one when the router marks the current tab -- so an app-wide rule
   would dress five destinations as bordered controls on any paint that
   reached the user before the router had touched them. Today the router
   always wins that race; a rule that is only correct because of a race is a
   defect waiting for the next nav button somebody adds. */
.view button:not([class]),.view button.primary,button.primary{
  background:transparent;color:var(--ink);border:1px solid var(--control-line);
  padding:10px 16px;font-family:var(--heading);font-size:14px;
  font-weight:600;line-height:1.2;text-transform:uppercase;letter-spacing:.07em;
  transition:background .18s ease,border-color .18s ease}
.view button:not([class]):hover:not(:disabled),button.primary:hover:not(:disabled){
  background:rgba(29,31,32,.06);border-color:var(--ink)}
.view button:not([class]):disabled,button.primary:disabled{opacity:.45;cursor:not-allowed}
/* The form's own submit is the primary action on that panel. */
button.primary{background:var(--accent-dark);border-color:var(--accent-dark);color:var(--accent-ink)}
button.primary:hover:not(:disabled){background:#2c455d;border-color:#2c455d}

/* `hidden` must actually hide. The UA sheet says [hidden]{display:none},
   which ANY display declaration in this file outranks -- and several do, on
   elements views toggle with `el.hidden = true`. Measured live: the
   calendar's week-only "Group week by" select reported hidden:true and
   display:flex at the same time, so a control that does nothing in month
   view sat on screen under every month. One rule, because the next
   component to set `display` on a toggled element would reintroduce it. */
[hidden]{display:none!important}

/* The month is the calendar's title, not a caption. `.card h3` turns every
   h3 inside a card into an 11px section label, which is right for a panel
   header and wrong for the one line that says which month you are reading. */
#calMonthLabel{font-size:22px;text-transform:none;letter-spacing:-.01em;
  color:var(--ink);margin:14px 0 10px}

/* Carried by the printed sheet, not the screen. The print block below
   reveals these; see the .cal-header rules there. */
.screen-hidden{display:none}

/* The line under a paginated table, and the control that extends it. Both
   sit in the same row so "showing N" and "load more" read as one statement
   about the same list rather than two loose objects at the end of the page. */
.activity-foot{display:flex;align-items:center;justify-content:space-between;
  gap:16px;flex-wrap:wrap;margin:12px 0 8px}
.activity-foot p{margin:0}
.activity-foot p:empty{display:none}

/* --- equipment table ----------------------------------------------------- */
.units-table-wrap{border:1px solid var(--line)}
.units-doc{border:1px solid var(--line);background:transparent}
.units-doc[data-state="ready"]{border-color:var(--occ-done-line)}

/* --- calendar ------------------------------------------------------------
 * A month cell is a drawn box on a drawing, not a card: the grid's own
 * hairlines are the structure. Rows inside keep their 3px status edge,
 * which is the whole colour channel, and lose the rounded fill around it.
 */
.cal-cell{border-color:var(--line)}
.cal-row{background:transparent;border:1px solid var(--line);
  border-left-width:3px;padding:3px 6px}
.cal-row[data-status="overdue"]{border-color:var(--occ-overdue-line)}
.cal-row[data-status="today"]{border-color:var(--occ-today-line)}
.cal-row[data-status="done"]{border-color:var(--occ-done-line)}
.cal-viewbtn.on{background:transparent;color:var(--ink);border-color:var(--ink)}
.cal-agenda-date{font-family:var(--heading);font-size:13px;letter-spacing:.1em}

/* --- ask ----------------------------------------------------------------- */
.ask-mode-choice.on{background:transparent;border-color:var(--ink);color:var(--ink)}
.ask-contact-card,.ask-task-context{background:var(--accent-soft);border-color:#b9cadb}

/* --- mobile -------------------------------------------------------------
 * The bottom bar is fixed over the page, so it is one of the few surfaces
 * that keeps a solid ground and its shadow; only its corners and its
 * active-tab pill change. A square tab marked by a rule, like the desktop
 * destinations above it.
 */
@media(max-width:640px){
  /* The page title and its one action stop sharing a line at phone width:
     condensed uppercase button labels are wider than the sentence-case ones
     they replaced, and "Add documents" was pushing the whole layout into
     horizontal overflow rather than dropping below the heading. */
  .pageHeading{flex-wrap:wrap;align-items:flex-start}
  .pageHeading>div{flex:1 1 100%}
  .pageHeading .btn{width:100%}
  header.top{flex-wrap:wrap}
  .who{flex-wrap:wrap;min-width:0}
  .who .textAction{flex:none}
  /* The desktop destinations are display:none for a signed-in phone (the
     bottom bar takes over); these keep the pre-sign-in shell sane rather
     than stretching five typographic tabs to a third of the width each. */
  nav.tabs{gap:0 18px;padding:0 12px}
  nav.tabs button{flex:0 1 auto;min-width:0;font-size:14px;letter-spacing:.06em}
  body.signed-in .mobilenav{background:var(--bg)}
  .mobilenav button{border-top:2px solid transparent}
  .mobilenav button.on{background:transparent;color:var(--ink);border-top-color:var(--ink-deep)}
  .morepanel button{background:transparent}
}
