/* field-portal layout — the Steel recipes (steel.css) do the styling; this file only lays out the app
   shell for a real phone/tablet viewport (the mockup's .phone frame becomes the actual screen).
   Design law (docs/DESIGN.md): squared corners, flat color, hairline rules, no emojis, one maroon accent. */

/* ---- THE APP STRIPE (ratified 2026-09-14, docs/DESIGN.md "App colors") ----
   FIELD IS THE DEFAULT. Key Maroon is this app's ratified stripe AND the value lib/brand ships, so there is
   deliberately no --app or --app-ink here: declaring them would be restating the default in a second place
   and inviting the two to drift. What Field does declare is --app-line.

   WHY THIS ONE. --app-line is the app color as a 3px LINE on --navbg (the bottom nav's current item), and it
   is a separate token from --app because a stripe FILL is chosen as a background: measured against the nav
   ground, all nine ratified stripe fills fail WCAG 1.4.11 (3.0:1) in one of the two modes, Field's own
   #6f1d27 at 1.39:1 on the dark nav among them. Field is the first adopted surface with a real bottom nav,
   so this stopped being theoretical here. It resolves to the maroon LINE tone: 5.80:1 dark, 10.87:1 light,
   and lib/brand/contrast-guard.mjs now enforces that pair directly (it learned to follow one level of var()
   indirection for exactly this reason -- before that it could not see an aliased token at all).

   Stated explicitly rather than inherited so that a future change to lib/brand's default cannot silently
   move this app's nav underline. */
:root, html[data-theme="dark"] { --app-line: var(--maroon-ink); }
html[data-theme="light"] { --app-line: var(--maroon-ink); }

html, body { height: 100%; }
body.steel {
  display: flex; flex-direction: column;
  max-width: 640px; margin: 0 auto;            /* phone-first; a centered column on iPad */
  padding-top: env(safe-area-inset-top);
}
.body { flex: 1; overflow-y: auto; padding: 0 0 16px; -webkit-overflow-scrolling: touch; }
.nav { padding-bottom: calc(6px + env(safe-area-inset-bottom)); }
.nav a { cursor: pointer; }

.boot { padding: 24px 16px; color: var(--dim); font-size: 11px; letter-spacing: .12em; }

/* offline / degraded states in the header sync block */
.hd .sync b.off { color: var(--red); }
.hd .sync b.q { color: var(--amber); }
/* NOT SYNCED — the radio is up but queued writes are not landing. Amber, not red: red is reserved for
   "no signal at all", and this is the recoverable middle state (semantic amber stays, per Clean Steel). */
.hd .sync b.stall { color: var(--amber); }

/* THE FRESHNESS LAYER IS LOAD-BEARING TEXT, NOT CHROME (red-team F6, fixed 2026-08-10).
   --dim measured 3.27:1 on --bg and 2.92:1 on --panel in dark (2.74 / 3.02 light) — under AA's 4.5 for text
   this small. The red team's harm was precise: at 8.5–10.5px in sun "a foreman cannot tell live data from a
   stale cache". That is exactly what this stamp exists to say, so it cannot be the least readable thing on
   the screen. Repointed to --muted: 5.33:1 dark / 5.16:1 light, both clear AA, no token changed.
   Deliberately NOT repointed: :disabled colours (a disabled control SHOULD recede, and WCAG exempts them),
   .shellver and .boot (quiet chrome nobody plans from). The NAV labels are still --dim and still fail — they
   live in the SHARED steel.css and belong to the fleet-wide brand pass, not to a field-portal PR. */
.asof { padding: 4px 16px 0; font-size: 9px; color: var(--muted); letter-spacing: .08em; }

/* super cross-job card: a plain panel between hairlines, chips row inside */
.chiprow { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 16px 2px; align-items: center; }
.jobline { padding: 9px 16px 0; }
.jobline small { color: var(--muted); font-size: 11px; display: block; }
/* THE JHA'S OWN WORDS (David's foreman demo 2026-09-03): the seeded tasks + hazards render as the card body */
.jhatasks { padding: 4px 16px 6px; }
.jhatask { padding: 6px 0; border-top: 1px solid var(--hair); }
.jhatask .tx { font-size: 15px; color: var(--ink); }
.jhatask .hz { color: var(--muted); font-size: 11px; letter-spacing: .04em; margin-top: 2px; }
.jhatask .hz.bad { color: var(--maroon); }
/* a hand-added crew member (no time card yet) is marked as such — never mistaken for a rostered hand */
table.crew td .hand { display: inline-block; margin-left: 6px; color: var(--muted); font-size: 10px; letter-spacing: .06em; }
/* THE HOTEL RED FLAG (David 2026-09-11) — steel's .st.bad chip beside a name; this only keeps it on one line and
   level with the name text (crew tables, the drill's foreman label, the Housing CREW sub-tab). */
.hotelchip { display: inline-block; white-space: nowrap; vertical-align: 1px; }
.jhaadd { display: flex; gap: 6px; padding: 6px 16px 10px; }
.jhaadd input { flex: 1; min-width: 0; font-size: 16px; padding: 8px; background: var(--bg); color: var(--ink); border: 1px solid var(--hair); border-radius: 0; font-family: inherit; }   /* 16px: iOS zoom law */
/* the 14-DAY row: the day's own items lead; the server line rides second, dimmer */
.sumtx small.sumsrv { display: block; color: var(--dim); font-size: 11px; letter-spacing: .04em; margin-top: 5px; }   /* 2026-09-05: 10px read as clutter under a task list; 11px and a breath of space */
/* the near-term job toggle: a plain control, not a chip that could read as a state */
.jobsall { padding: 4px 16px 8px; }
/* JHA v2 (David 2026-09-04): provenance on every talking point, small row controls, the presence check */
.jhatask .src { display: inline-block; margin-left: 8px; font-size: 10px; letter-spacing: .08em; color: var(--muted); border: 1px solid var(--hair); padding: 1px 5px; vertical-align: 1px; }
.jhatask .src.ai { border-style: dashed; }
/* JHA WITHOUT A LOOK-AHEAD (David 2026-09-06): CREATED HERE is a human-authored point (solid, like PLAN / DAY-OF); the
   composer sits inside the card as its own ruled block; a queued task reads as a row with the QUEUED chip and the seed
   promise under it; FROM JHA on the lookahead row is the quiet muted marker in attrLine's small line. Steel tokens only. */
.jhatask .src.createdhere { border-style: solid; }
.jhacompose { margin: 10px 0; padding: 8px 0 4px; border-top: 1px solid var(--hair); border-bottom: 1px solid var(--hair); }
.jhacompose .jhacrewhead { margin-bottom: 6px; }
.jhaqueued { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; padding: 8px 0; border-top: 1px solid var(--hair); }
.jhaqueued .tx { flex: 1 1 60%; min-width: 0; }
.jhaqueued small { flex-basis: 100%; color: var(--muted); }
.jhaprov { margin-top: -4px; }
.fromjha { color: var(--muted); letter-spacing: .08em; }
.jhaptctl { display: flex; gap: 6px; margin-top: 6px; }
.pbtn.sm { min-height: 44px; min-width: 44px; padding: 6px 12px; font-size: 11px; }   /* UP alone measured ~41px wide — the glove floor holds on both axes */
.jhacrewhead { padding: 10px 16px 2px; color: var(--muted); font-size: 11px; letter-spacing: .06em; }
.prescheck { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 4px 12px; background: var(--panel); color: var(--ink); border: 1px solid var(--line); border-radius: 0; font-family: inherit; font-size: 12px; letter-spacing: .06em; cursor: pointer; }
.prescheck.on { border-color: var(--ink); border-width: 2px; }
.jhasign { padding: 6px 0 4px; border-top: 1px solid var(--hair); margin-top: 6px; }
/* the per-man crew SIGN (2026-09-08): beside the presence check in the same cell, on the .pbtn.sm glove floor */
table.crew td .jhacrewsign { margin-left: 6px; vertical-align: middle; }
table.crew td .st + .jhacrewsign { margin-left: 8px; }
/* TODAY'S CONDITIONS (VOICE JHA 2.0, 2026-09-09): the day-level leaf above the huddle control. Steel: mono labels, the
   status chip and the provenance chip the talking points already wear (FORECAST dashed -- a guess looks like a guess;
   FOREMAN / CARRIED / IMPORTED solid), hairlines only, no fills. The amber CONDITIONS CHANGED line is a hairline pair
   and amber text, never a fill. The composer's inputs keep the 16px iOS zoom law. */
.jhacond { padding: 2px 0 4px; border-top: 1px solid var(--hair); margin-top: 6px; }
.jhacondhead { padding: 8px 16px 2px; color: var(--ink); font-size: 11px; letter-spacing: .08em; }
.jhacondrow { padding: 6px 16px; border-top: 1px solid var(--hair); }
.jhacondrow .tx { font-size: 15px; color: var(--ink); }
.jhacondrow .tx .st { margin-left: 8px; vertical-align: 1px; }
.jhacondrow .src { display: inline-block; margin-left: 8px; font-size: 10px; letter-spacing: .08em; color: var(--muted); border: 1px solid var(--hair); padding: 1px 5px; vertical-align: 1px; }
.jhacondrow .src.forecast { border-style: dashed; }
.jhacondrow .hz { color: var(--muted); font-size: 11px; letter-spacing: .04em; margin-top: 2px; }
.jhacondrow.removed .tx { color: var(--muted); }
.jhacondctl { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; align-items: center; }
.jhacondctl .mono { color: var(--muted); font-size: 10.5px; letter-spacing: .05em; }
.jhacondchanged { margin: 8px 16px 0; padding: 6px 0; border-top: 1px solid var(--amber); border-bottom: 1px solid var(--amber); font-size: 12px; }
.jhacondchanged b { color: var(--amber); letter-spacing: .06em; font-size: 10.5px; display: block; }
.jhacondchanged .hz { color: var(--muted); font-size: 11px; letter-spacing: .04em; margin-top: 2px; }
.jhacondadd { display: flex; flex-direction: column; gap: 6px; padding: 6px 16px 4px; }
.jhacondadd input { font-size: 16px; padding: 8px; background: var(--bg); color: var(--ink); border: 1px solid var(--hair); border-radius: 0; font-family: inherit; }   /* 16px: iOS zoom law */
.jhacondadd .jhacondctl { margin-top: 0; }

/* role switcher (only when the actor holds several roles): flat bordered text chips, square */
.roles { display: flex; gap: 6px; padding: 10px 16px 0; }
/* 44px here too. These measured ~24px, and on the SIGN-IN screen they are the first controls a foreman ever
   touches — with gloves, outdoors. The same floor .pbtn already carries; inline-flex for the same reason
   (a taller box only helps if the label centres in it). --muted on the ground is 5.33:1 dark / 5.16:1 light,
   so unlike the --dim layer these labels already clear AA. */
.roles button {
  background: transparent; border: 1px solid var(--dim); color: var(--muted);
  font-size: 9.5px; padding: 5px 9px; cursor: pointer;
  min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
}
/* the question the two channel buttons answer — without it they are two nouns floating above a phone field */
.loginask { padding: 10px 16px 0; color: var(--muted); font-size: 9.5px; letter-spacing: .1em; }
/* the code-step echo of the destination HE typed — the typo check the honest-copy fix depends on, so it
   reads at input size, in ink, centered (never 9.5px chrome like the question label above). */
.loginwho { padding: 10px 16px 0; font-size: 16px; color: var(--ink); text-align: center; letter-spacing: .06em; }
/* the PASTE CODE answer, under the code field it is about (David 2026-09-06): plain words at reading size
   (16px, the .loginwho size above it), in ink — an instruction the crew must act on, not 9.5px chrome. */
.loginnote { padding: 8px 16px 0; font-size: 16px; line-height: 1.35; color: var(--ink); letter-spacing: .04em; }
.roles button.on { border-color: var(--ink); color: var(--ink); }

/* coming-soon title block for unbuilt tabs */
.soon { padding: 10px 16px; color: var(--muted); font-size: 12px; }
.soon small { color: var(--muted); display: block; margin-top: 6px; font-size: 10.5px; }

/* request manifest rows reuse .row/.prog; the queue badge is text in the header (mono) */
.empty { padding: 12px 16px; color: var(--muted); font-size: 11px; }

/* tap targets */
.row, table.crew td { -webkit-tap-highlight-color: transparent; }
button.talk { font-family: inherit; }

/* ---- PLAN screen (the two-week look-ahead) — plan-sheet ruled lines, Steel throughout -------------------- */
/* DAY DEMARCATION (2026-07-31) — the day band is a steel.css .card, so its frame, its --panel ground and
   its 16px margin-top all come from the design system. app.css only has to stop fighting it: kill the old
   hairline rule and the outer padding (the card owns both now), and pad the CONTENT rather than the box —
   the .secbar header must run edge to edge to read as a filled title bar rather than an indented label. */
.pday { border-bottom: 0; padding: 0; }
.pday > *:not(.secbar) { padding-left: 16px; padding-right: 16px; }
.pday > *:last-child { padding-bottom: 12px; }
/* THE DAY HEADER IS CARRIED BY ITS RULE, NOT ITS FILL — measured, not assumed. In DARK, every candidate
   surface token sits within 1.06–1.21 contrast of --panel (--sechead, the natural choice, is 1.06), so a
   "filled" header bar is invisible on a card at arm's length. That is Clean Steel working as designed: the
   dark surfaces are deliberately close. So the header earns its weight structurally instead — a 2px bottom
   rule in --line, the strongest thing available (1.36) and the same hairline vocabulary the whole system
   uses. The fill stays for light mode, where --sechead genuinely reads. */
.pday > .secbar { border-bottom-width: 2px; }
/* TODAY: structural emphasis only — the whole frame goes ink-strength (10.2 against the ground in dark).
   No fill, no second maroon, nothing amber (accent-restraint is ratified fleet-wide). */
.pday.today { border-color: var(--ink); }
.pday.today > .secbar { background: var(--hover); border-bottom-color: var(--ink); }
/* HEADCOUNT-FIRST (David #17, 2026-09-06): the day header's CREW text is a tap target for the acting foreman —
   the glove floor on both axes, inline-flex so the label centres. Steel tokens only. The roster flag is the one
   quiet .st.wait chip in a .chiprow under the header (never inside .crewdock/.crewrow); the .pday child padding
   already indents it, so the chiprow's own side padding is dropped there to keep it on the card's text edge. */
.hcbtn { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; min-width: 44px; padding: 0 12px;
  background: transparent; border: 1px solid var(--btnline); border-radius: 0; color: var(--ink);
  font-family: inherit; font-size: 9.5px; letter-spacing: .08em; cursor: pointer; touch-action: manipulation; }
.hcbtn.on { border-color: var(--ink); border-width: 2px; }
.pday > .chiprow { padding-left: 0; padding-right: 0; }
.hcform { padding-top: 8px; }
.hcnote { display: block; padding-top: 4px; font-size: 9.5px; color: var(--muted); letter-spacing: .06em; }
/* CREW COUNT stepper (David 2026-09-07 — the HOW MANY TODAY? popup entry is retired): label, [-], the number, [+] on
   one line inside the day header. The two ends and the number are each a 44px .hcbtn (the glove floor); the typing
   box that replaces the number is the same 44px box at 16px type (iOS does not zoom a 16px input). No state colour. */
.hcstep { display: inline-flex; align-items: center; gap: 4px; }
.hcstep .hclabel { font-size: 9.5px; letter-spacing: .08em; padding-right: 4px; }
.hcbtn.hcpm { font-size: 18px; font-weight: 800; }
.hcbtn.hcnum { font-size: 16px; font-weight: 800; min-width: 56px; }
.hcstep input[type="number"] { min-height: 44px; min-width: 44px; width: 64px; font-size: 16px; text-align: center; }
.hcnamed { padding-left: 6px; font-size: 9.5px; color: var(--muted); letter-spacing: .06em; }
/* the FILL mark: the task the next name-tap lands on — the same left-rule vocabulary as .armed, at ink strength */
.crewrow.fill { border-left-color: var(--ink); }
.ptask { display: flex; align-items: flex-start; gap: 8px; padding: 6px 0 2px; cursor: pointer; }
.ptask .tx { flex: 1; min-width: 0; font-size: 13.5px; }
.ptask .tx small { display: block; color: var(--muted); font-size: 9.5px; letter-spacing: .06em; }
.ptask.done .tx { color: var(--muted); text-decoration: line-through; text-decoration-color: var(--dim); }
.ptask .own { font-size: 9px; color: var(--muted); border: 1px solid var(--hair); padding: 1px 4px; white-space: nowrap; }
.ptask.pending .tx { color: var(--amber); }
.chg { color: var(--amber); font-weight: 800; font-size: 10px; }   /* changed-since-approved marker */
/* flex-wrap is LOAD-BEARING, not cosmetic (2026-07-31, David's iPad screenshot): .padd is a flex row, and
   inside the PLAN grid its cell is only 150px wide. The open add-task panel's row — ADD + CANCEL + KEYO FILL
   + the hint span — measures 229px. With the default `nowrap` and visible overflow, that row rendered OUTSIDE
   its day cell and landed on top of the next column: the "orphaned text between TUE and WED". Wrapping keeps
   it inside its own day. Applies to every .padd consumer, not just PLAN. */
.padd { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; align-items: center; }
.padd input, .padd textarea, .padd select, .pedit input, .pedit textarea, .latereason input {
  flex: 1; background: var(--bg); border: 1px solid var(--hair); color: var(--ink);
  font-family: inherit; font-size: 16px; padding: 7px 8px; border-radius: 0; min-width: 0;
  min-height: 44px;
}
.padd textarea { resize: vertical; }
/* ---- REQUEST FORM WIDGETS (David, live 2026-08-10) ---------------------------------------------------------
   A date box ignores `placeholder` entirely, so its label has to be a real <label> or the field is an
   unlabelled empty box. Same for the number stepper, whose spinner would crowd a placeholder anyway.
   16px on date/number is not cosmetic: iOS zooms the whole page when a focused input is under 16px, which on
   the request form threw the foreman into a zoomed viewport mid-entry. 44px because everything here is.
   (Forms cleanup, 2026-08-12: that same 16px/44px pair now lives on the BASE .padd control rule above —
   the zoom trap and the glove floor applied to every TEXT box and SELECT too, not just date/number. The
   rule below stays: it is the request-widget ruling's own record, and its selector is test-pinned.) */
.rqlab { display: block; width: 100%; color: var(--muted); font-size: 9.5px; letter-spacing: .1em; margin-bottom: 4px; }
/* FLOATING LABELS (David, 2026-08-12 — overturns the 08-10 placeholder-as-label ruling for FILLED fields).
   The wrapper is column-reverse so the label (last in DOM, for the sibling selector) reads ABOVE the box.
   Empty field → :placeholder-shown → label hidden → identical to before. Filled → label appears, --muted
   (AA, F6). Pure CSS — no keystroke state, no JS. */
.padd.rqfw { flex-direction: column-reverse; align-items: stretch; gap: 0; }
.rqfw .rqfloat { display: none; margin: 0 0 4px; }
.rqfw input:not(:placeholder-shown) + .rqfloat { display: block; }
/* the request card's detail run + dispatch marks. --muted, not --dim: this is text a foreman READS to decide
   whether to open the request at all (F6 — the load-bearing micro-text rule). */
.rqbits { display: block; color: var(--muted); font-size: 10.5px; margin-top: 1px; }
.rqflags { display: block; margin-top: 2px; }
/* a mark, not a fill — semantic amber, the same vocabulary NOT SYNCED uses for "act on this" */
.rqflag { display: inline-block; border: 1px solid var(--amber); color: var(--amber); font-size: 8.5px;
  letter-spacing: .1em; padding: 1px 5px; margin-right: 4px; }
.padd input[type="date"], .padd input[type="number"] { min-height: 44px; font-size: 16px; }
/* the native calendar/spinner glyph is dark-on-dark in the dark theme without this */
.padd input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(0.7); cursor: pointer; }
/* voice-filled value — amber structural mark, cleared the moment the human types (check-me, not chrome) */
.aifill { border-left: 3px solid var(--amber) !important; }

/* ---- COPY DAY / BULK (red-team F1) — the fortnight picker IS the bulk primitive, so there is no separate
   "bulk mode" to learn. Cells are 44px minimum like every other glove target; selection is a BORDER + ink
   state, never a fill (Clean Steel: one maroon per screen, and this screen already spends it). */
.copypanel { padding: 8px 0 4px; border-top: 1px solid var(--hair); margin-top: 8px; }
.cptitle { padding: 0 16px 6px; color: var(--muted); font-size: 9.5px; letter-spacing: .1em; }
.cpgrid { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 16px; }
.cpcell {
  flex: 0 0 auto; min-width: 44px; min-height: 44px; display: inline-flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 1px; cursor: pointer; border-radius: 0;
  background: transparent; border: 1px solid var(--hair); color: var(--muted); font-family: inherit;
}
.cpcell.on { border-color: var(--ink); border-width: 2px; color: var(--ink); }
/* the source day cannot be a target — disabled AND visibly inert, so the rule is legible before the tap */
.cpcell.self { opacity: .4; cursor: default; border-style: dashed; }
.cpcell .dsd { font-size: 8.5px; letter-spacing: .06em; }
.cpcell .dsn { font-size: 13px; font-weight: 800; }
/* an armed toggle reads as state, not chrome — same border-weight vocabulary as .cpcell.on */
.pbtn.on { border-color: var(--ink); border-width: 2px; }
.cpnote, .cpdone { font-size: 9.5px; letter-spacing: .06em; color: var(--muted); }

/* ---- COST CODES (Phase A) — the chip on a linked task, the pick ladder on an unlinked one. Exception-based:
   on-pace says almost nothing (plain .cc); "running hot"/"over budget" gets .hot — amber, NEVER red (the
   no-false-reds rule: budgets get sandbagged, a confidently wrong red trains the field to ignore the color). */
.cctx { padding: 4px 0 2px; }
.ccwait { color: var(--muted); font-size: 10px; letter-spacing: .05em; }
.cc { background: transparent; border: 1px solid var(--muted); color: var(--muted); font-size: 10px;
  letter-spacing: .04em; padding: 5px 8px; cursor: pointer; font-family: inherit; }
.cc.hot { border-color: var(--amber); color: var(--amber); }
/* ---- CHIPS THAT READ IN ENGLISH (2026-07-31) -------------------------------------------------------------
   David: "I'm not sure they know the numbers." The DESCRIPTION is now the primary line — normal weight,
   sentence case, readable at arm's length — and the code + hours-left sit under it in muted mono. Linked and
   suggestion chips stack identically, so the only difference a foreman sees is which one he already picked.
   44px min-height keeps the glove target now that a chip is two lines; flex-start keeps a wrapped second
   line left-aligned instead of centring a ragged edge under the first. */
.cc, .ccpick { display: inline-flex; flex-direction: column; align-items: flex-start; gap: 1px;
  min-height: 44px; justify-content: center; text-align: left; max-width: 100%; }
.ccname { font-size: 13px; letter-spacing: 0; color: var(--ink); font-weight: 400; line-height: 1.25;
  overflow-wrap: anywhere; }          /* a long description wraps INSIDE its chip, never out of it */
.ccmeta, .cclinkedto { font-size: 9.5px; letter-spacing: .06em; color: var(--muted); line-height: 1.2; }
.cc .ccname { color: var(--muted); }                              /* linked reads quieter than an open suggestion */
.cc.hot .ccname, .cc.hot .cclinkedto { color: var(--amber); }     /* amber = STATE: border + text, never a fill */
.ccladder { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0; }
.ccpick { background: transparent; border: 1px solid var(--dim); color: var(--ink); font-size: 10px;
  padding: 5px 8px; cursor: pointer; font-family: inherit; margin: 2px 6px 2px 0; }
/* 44px, and --muted not --dim (F6): this is a real control, not decoration. */
.ccmore { background: transparent; border: none; color: var(--muted); font-size: 9.5px; letter-spacing: .08em;
  font-weight: 800; padding: 4px 0; cursor: pointer; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
.ccselect, .ccsearch { display: block; width: 100%; background: var(--bg); border: 1px solid var(--hair);
  color: var(--ink); font-family: inherit; font-size: 16px; padding: 6px 8px; border-radius: 0; margin: 4px 0; }   /* 16px: iOS zoom law (pilot audit 2026-09-01) */

/* ---- the request status TIMELINE — a plan-sheet rail: flat dots, hairline spine, mono stamps ------------- */
.tl { padding: 6px 16px 2px; }
.tlhop { display: flex; align-items: flex-start; gap: 10px; padding: 7px 0; position: relative; }
.tlhop .dot { width: 10px; height: 10px; border: 2px solid var(--dim); flex: none; margin-top: 2px; background: transparent; }
.tlhop:not(:last-child):before { content: ""; position: absolute; left: 4px; top: 20px; bottom: -8px; width: 1px; background: var(--hair); }
.tlhop.done .dot { border-color: var(--ok); background: var(--ok); }
.tlhop.current .dot { border-color: var(--amber); }
.tlhop.declined .dot { border-color: var(--red); background: var(--red); }
.tlhop .t { flex: 1; min-width: 0; }
.tlhop .t b { font-size: 10.5px; color: var(--muted); letter-spacing: .1em; }
.tlhop.done .t b, .tlhop.current .t b { color: var(--ink); }
.tlhop.declined .t b { color: var(--red); }
.tlhop .t small { display: block; color: var(--muted); font-size: 10.5px; margin-top: 1px; }
.tlhop .stamp { font-size: 10px; color: var(--muted); }
.pedit textarea { width: 100%; min-height: 56px; resize: vertical; }
/* 44px MINIMUM, ON THE BASE CLASS (2026-08-08). .pbtn is the app's workhorse action — 93 uses, including
   SIGN IN and the send-to-CM control — and at 9.5px type with 7px padding it measured ~27px tall. That is
   well under the 44px glove target DESIGN.md calls for, and these are pressed with work gloves on a phone in
   sun. Fixing it on the BASE class rather than per-site is the whole point: nothing written later can inherit
   the old height. The one .pbtn that had already opted bigger (.keyotalkbtn, min-height 48px) still wins on
   its own; the 26px `.gcmsg .hear` glyph is NOT a .pbtn and keeps its own 44px overlay trick, because an
   inline mid-sentence control must not bloat the line height. inline-flex (not the default inline-block) centres the
   label once the box is taller than the text — without it the type sits high in a 44px box. */
.pbtn {
  background: transparent; border: 1px solid var(--muted); color: var(--ink); cursor: pointer;
  font-size: 9.5px; letter-spacing: .1em; padding: 7px 10px; font-weight: 800; border-radius: 0;
  min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
}
.pbtn.bad { border-color: var(--red); color: var(--red); }
/* ARMED — carried by its RULE, not a fill, exactly like the day header above. I tried the fill first
   (--bg on --red) and MEASURED it: 3.0:1 in dark. That passes for a non-text UI component and fails AA for
   what this actually is — a small bold letter-spaced LABEL, which needs 4.5. So the 2px red border carries
   the danger and --ink carries the legibility: 10.2:1 dark, 13.7:1 light. The changed WORDING is the primary
   signal anyway; the border is what makes it findable at a glance.
   NOTE — while measuring I found that the existing red-on-ground `.pbtn.bad` is itself only 3.0:1 in dark.
   That is pre-existing and fleet-wide, not introduced here; reported rather than silently widened into. */
.pbtn.armed {
  border: 2px solid var(--red); color: var(--ink); background: transparent; letter-spacing: .08em;
}
/* DELETE leaves SAVE's side of the row entirely — 6px of separation between "keep" and "destroy" was the
   original complaint, and no amount of confirmation makes adjacency a good idea. */
.erow .dstr { margin-left: auto; }
.pbtn.ok { border-color: var(--ok); color: var(--ok); }
.pbtn:disabled { border-color: var(--hair); color: var(--dim); cursor: default; }
.pactions { display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 16px; align-items: center; }
.pactions .mono { font-size: 9.5px; color: var(--muted); }
.pedit { padding: 8px 16px 10px; border-bottom: 1px solid var(--hair); background: var(--panel); }
.pedit .erow { display: flex; gap: 6px; margin-top: 6px; }
/* loud state banners: flat bordered blocks, colored type — never a glow */
.pbanner { margin: 10px 16px 0; border: 2px solid var(--red); padding: 8px 10px; font-size: 12px; }
.pbanner b { color: var(--red); letter-spacing: .08em; font-size: 10.5px; display: block; }
.pbanner.amber { border-color: var(--amber); }
.pbanner.amber b { color: var(--amber); }
.pbanner small { color: var(--muted); display: block; margin-top: 3px; }
/* .loud — banners whose text IS the message (refusals, the queue-stall warning): reading size, not chrome
   size. The 10.5px <b> above is fine for a one-word headline; it is fine print for a full sentence. */
.pbanner.loud b { font-size: 13px; letter-spacing: .03em; }
.pbanner.loud small { font-size: 12px; }
.latereason { margin: 6px 0 0; }
.latereason input { width: 100%; background: var(--bg); border: 1px solid var(--hair); color: var(--ink); font-size: 16px; padding: 6px 8px; border-radius: 0; }   /* 16px: iOS zooms on focus under it (red team 2026-08-30) */
/* dictation mic squared to the Steel inputs (the lib's own css rounds it; this wins by order + specificity) */
.padd .dict-mic, .pedit .dict-mic {
  border-radius: 0; background: transparent; border: 1px solid var(--muted); color: var(--ink);
  font-family: var(--steel-cond); font-size: 9.5px; letter-spacing: .1em; font-weight: 800;
}
.padd .dict-mic[aria-pressed="true"], .pedit .dict-mic[aria-pressed="true"] {
  background: transparent; border-color: var(--red); color: var(--red); animation: none;
}

/* ---- JHA HELPER (SUGGEST HAZARDS) — ported from foreman-portal's question-led + talking-points aids ------- */
.jhahelp { padding: 6px 16px 0; }
.jhahelpout { margin-top: 6px; }
.jhahint { color: var(--muted); font-size: 10.5px; letter-spacing: .05em; }
.jhalist { border: 1px solid var(--hair); padding: 8px 10px; background: var(--panel); }
.jhalisthead { color: var(--muted); font-size: 9.5px; letter-spacing: .08em; margin-bottom: 6px; }
.jhaqline { font-size: 12px; color: var(--ink); padding: 3px 0; }
.jhachip { display: inline-block; margin: 0 6px 6px 0; text-align: left; white-space: normal; }
/* JHA learning loop — what crews actually recorded on similar past tasks (verbatim, server-verified) vs the model's guess */
.jhahist { margin-bottom: 6px; border-color: var(--ink); }
.jhahistsum { color: var(--muted); font-size: 10px; letter-spacing: .04em; margin: -2px 0 8px; }
.jhahistmeta { display: block; color: var(--muted); font-size: 9px; letter-spacing: .08em; margin-top: 3px; }
.jhahisttag { display: inline-block; margin-left: 6px; color: var(--muted); font-size: 9px; letter-spacing: .08em; border: 1px solid var(--hair); padding: 0 4px; vertical-align: middle; }

/* ---- EQUIPMENT screen (FIELD-DESIGN §4) --------------------------------------------------------------------
   Steel: squared, flat, hairline-ruled. No maroon here — the nav's active tab indicator is this screen's one
   accent. A tagged-out machine gets the structural emphasis (ink rule), never a second red fill: the physical
   .tag block inside it already carries the red. */
.mcard { border-bottom: 1px solid var(--line); padding-bottom: 10px; }
.mcard.tagged { border-left: 3px solid var(--ink); }
/* a GUESSED job-match is a caution, not a tag-out — its rail must never borrow the tagged look (pilot audit 2026-09-01) */
.mcard.guessed { border-left: 3px dashed var(--amber); }
.mcard .jobline b { font-size: 13.5px; margin-right: 7px; }
.mcard .jobline .st { vertical-align: 1px; }

/* TWO DIFFERENT STRIPS, side by side — they must NOT read as duplicates (DESIGN.md: green means "done").
   Each header names its metric; the window count is DERIVED from the data by app.js, never asserted here. */
.strip-h { display: flex; align-items: baseline; font-size: 9px; letter-spacing: .07em; color: var(--muted); margin-bottom: 5px; }
/* the window count + unit are LOAD-BEARING (they carry the two-strips distinction) and small — so --muted,
   not --dim: the F6 freshness-layer fix (top of this file, #629) ruled --dim sub-AA for small text this size. */
.strip-h .sh-win { color: var(--muted); }
.strip-h .sh-unit { margin-left: auto; color: var(--muted); }   /* the y-axis unit, at the top of the bars */

/* utilization: bar height = hours run that day. A MAGNITUDE, so NEUTRAL --muted — never the inspection green.
   Zero = a faint track; the whole set turns amber when idle (a real caution, not a fill for identity's sake). */
.util { padding: 8px 16px 0; }
/* tap-for-actuals (2026-08-12): every bar is a REAL button — the 44px glove floor is the button (and sets
   the bar area's height; proportions are unchanged, just drawn taller). A tap writes that day's prebuilt
   actuals line into the strip's readout below. */
.ubars { display: flex; align-items: flex-end; gap: 3px; }
.ubars .ubtap { flex: 1; min-width: 0; min-height: 44px; margin: 0; padding: 0; border: 0; background: transparent;
  display: flex; align-items: flex-end; cursor: pointer; }
.ubars .ubtap .ub { width: 100%; background: var(--muted); min-height: 2px; display: block; }
.ubars .ubtap .ub.zero { background: var(--progtrack); }
.util.idle .ubars .ub { background: var(--amber); }
.util .foot { padding-left: 0; padding-right: 0; }
/* the tapped day's actuals — load-bearing micro-text, so --muted (F6), never --dim */
.strip-read { color: var(--muted); font-size: 10.5px; letter-spacing: .04em; margin-top: 5px; min-height: 14px; }

/* inspection strip: one cell per day, newest first. A YES/NO-per-day — filled green = inspected (done),
   hollow outline = missed, amber = a defect was noted. A tiny key names the states so a cell is never a bar. */
.ins-col { padding: 8px 16px 0; }
.ins { display: flex; gap: 4px; flex: 1; }
/* iteration 3: TWO phase rows (PRE-OP / POST-OP), each a labeled run of day-cells. The label is load-bearing
   micro-text → --muted (F6). */
.ins-row { display: flex; align-items: center; gap: 8px; }
.ins-rowlbl { min-width: 52px; color: var(--muted); font-size: 8.5px; letter-spacing: .06em; }
/* day cells are buttons too (same tap-for-actuals contract as the bars) — reset the button chrome, keep the
   cell look, take the 44px floor */
.ins .icell { flex: 1; text-align: center; background: transparent; border: 0; margin: 0; padding: 12px 0 0;
  font-family: inherit; color: inherit; cursor: pointer; min-height: 44px; }
.ins .icell i { display: block; height: 12px; border: 1px solid var(--dim); background: transparent; }
.ins .icell.on i { background: var(--ok); border-color: var(--ok); }
.ins .icell.on.iss i { background: var(--amber); border-color: var(--amber); }
.ins .icell small { display: block; margin-top: 3px; font-size: 8.5px; color: var(--muted); letter-spacing: .06em; }
.ins-key { display: flex; align-items: center; margin-top: 6px; font-size: 9px; letter-spacing: .04em; color: var(--muted); }   /* --muted + 9px: load-bearing legend, must clear AA (F6) */
.ins-key .k { display: inline-block; width: 9px; height: 9px; margin-right: 4px; vertical-align: -1px; box-sizing: border-box; }
.ins-key .k.on { background: var(--ok); border: 1px solid var(--ok); }
.ins-key .k.off { background: transparent; border: 1px solid var(--dim); margin-left: 12px; }
.ins-col .foot { padding-left: 0; padding-right: 0; }

/* FOREMAN AUDIT label — names the concept so the audit verdict never blurs with the inspection outcome above. */
.chiprow .crlbl { align-self: center; margin-right: 6px; color: var(--muted); font-size: 8.5px; letter-spacing: .06em; }   /* load-bearing label → --muted (F6 AA) */
/* the three status rows (STATUS / PRE-OP / POST-OP) — chiprow language, fixed-width labels so the three
   verdicts align into a scannable column */
.strows { padding: 8px 16px 0; }
.strows .strow { margin-bottom: 2px; }
.strows .crlbl { min-width: 58px; }

.amber { color: var(--amber); }
.dim { color: var(--muted); font-size: 10px; }
/* `a.pbtn` outranks `.pbtn` (element+class), so this line would quietly undo the base class's inline-flex and
   leave anchor-shaped buttons with the label riding high in a 44px box. It has to agree, not just not-conflict. */
a.pbtn { text-decoration: none; display: inline-flex; }

/* VIEW AS — the persistent cross-tab banner (sits between .hd and .body, never wiped by paint()'s
   #body-only innerHTML swap). Amber, not maroon — this is an admin-testing state, not a safety alert,
   and the nav's active-tab underline already spends this screen's one maroon accent (DESIGN.md).
   The tinted background is DARK-mode specific (a light amber wash for Clean Steel light) — both
   modes must read correctly (docs/DESIGN.md "Two sibling modes"). */
.viewbar { border-top: 2px solid var(--amber); border-bottom: 1px solid var(--hair); background: #302a1f; padding: 8px 16px; }
html[data-theme="light"] .viewbar { background: #FBF1E1; }
.viewbar .vb-b { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.viewbar b { color: var(--amber); font-size: 11px; letter-spacing: .05em; }
.viewbar button { background: transparent; border: 1px solid var(--amber); color: var(--amber); font-size: 9.5px;
  font-weight: 800; letter-spacing: .08em; padding: 5px 9px; cursor: pointer; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }

/* FORCE-UPDATE — the quiet nudge (Phase B). Green (--ok), not amber/red: a new version landed, nothing is
   wrong yet. Same persistent-banner mount as .viewbar (sibling of .hd/.body, survives paint()'s #body swap).
   Same dark/light wash pairing as .viewbar above. */
.updatebar { border-top: 2px solid var(--ok); border-bottom: 1px solid var(--hair); background: #1f2e26; padding: 8px 16px; }
html[data-theme="light"] .updatebar { background: #EAF5EE; }
.updatebar .ub-b { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.updatebar b { color: var(--ok); font-size: 11px; letter-spacing: .05em; }
/* 44px. This is the control that gets a STALE DEVICE onto a new shell — the one button whose
   failure leaves a foreman on old code indefinitely. It measured ~24px. */
.updatebar button { background: transparent; border: 1px solid var(--ok); color: var(--ok); font-size: 9.5px;
  font-weight: 800; letter-spacing: .08em; padding: 5px 9px; cursor: pointer; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }

/* VIEW AS picker (MORE tab, admin-only) */
.vasgroup { padding: 2px 0 4px; }
.vashead { padding: 8px 16px 2px; font-size: 9.5px; color: var(--muted); letter-spacing: .1em; }
.row[data-actas] { cursor: pointer; }
.row[data-actas]:active { background: var(--hair); }

/* KEYO badged-off (view-as) — same block, dimmed, no TALK button */
.keyo.off { opacity: .65; }

/* THE MORNING BRIEFING — a second .talk-styled button beside TALK (the row's own `gap` gives them a hairline
   of daylight; BRIEFING carries the row's one margin-left:auto, TALK just follows) + an expandable transcript
   BELOW the row (never squeezed inside it — the row stays one line, gloves-on tap targets included). */
.keyo #keyobrief { margin-left: auto; }
.keyo #keyobrief:disabled { opacity: .5; cursor: default; }
.keyotx { margin: 0 16px; }
.keyotxtoggle { background: transparent; border: none; color: var(--dim); font-size: 9.5px; letter-spacing: .1em;
  font-weight: 800; padding: 6px 0; cursor: pointer; }
.keyotx p { margin: 0 0 8px; padding: 8px 10px; border: 1px solid var(--hair); background: var(--panel);
  color: var(--muted); font-size: 11.5px; line-height: 1.5; }

/* ---- SAFETY screen (FIELD-DESIGN §4) — JHA day-view + signing, Good Catch (voice-first), tag-outs --------- */
.gc { padding: 8px 16px 0; display: flex; flex-direction: column; gap: 6px; }
.gcmsg { max-width: 88%; padding: 7px 10px; border: 1px solid var(--hair); font-size: 12.5px; }
.gcmsg b { display: block; font-size: 9px; color: var(--muted); margin-bottom: 2px; }
.gcmsg.worker { align-self: flex-end; background: var(--panel); }
.gcmsg.keyo { align-self: flex-start; border-left: 3px solid var(--maroon); }
/* KEYO FEEDBACK RAILS (0102) — a quiet thumbs under a KEYO turn, never a popup. Text buttons only
   (FIELD-DESIGN: no emoji) — rides the same .pbtn voice as every other action in this screen. */
.gcfbrow, .gcfbask { align-self: flex-start; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-top: -2px; }
.gcfbnote { align-self: flex-start; color: var(--muted); font-size: 10.5px; margin-top: -2px; }
.gcfbask textarea { flex: 1; min-width: 140px; font-size: 16px; padding: 5px 8px; }   /* 16px: the app's own iOS-zoom law (red team 2026-08-30) */
.gcphotos { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 16px 0; }
.gcthumb { position: relative; width: 56px; height: 56px; border: 1px solid var(--hair); overflow: hidden; }
.gcthumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gcthumb.busy { display: grid; place-items: center; color: var(--dim); font-size: 11px; }
.gcthumb .rm { position: absolute; top: 0; right: 0; background: rgba(0,0,0,.6); color: #fff; border: 0;
  width: 18px; height: 18px; font-size: 12px; line-height: 1; cursor: pointer; }

/* ---- KEYO TAB (Ask KEYO) — reuses .gc/.gcmsg/.gcfb* verbatim (same house pattern, see app.js). Two
   additions of its own: tap-to-hear (a small flat glyph button riding inline in a .gcmsg.keyo row — a
   play glyph, not a pictograph, DESIGN.md "no emoji") and the primary TALK control, deliberately BIGGER
   than a standard .pbtn — the marquee action of its own dedicated screen, glove-sized (DESIGN.md "touch
   targets stay glove-sized at every width"). */
.gcmsg .hear { margin-left: 8px; background: transparent; color: var(--dim); border: 1px solid var(--line);
  border-radius: 0; width: 26px; height: 26px; line-height: 1; font-size: .85em; cursor: pointer; vertical-align: middle;
  position: relative; }
/* GLOVE TARGET. The glyph stays 26px so a control sitting mid-sentence doesn't shout, but the TAPPABLE area is
   expanded to 44px by a transparent overlay — the field standard (.keyotalkbtn is 48px) without letting an
   inline button bloat the line height. This became load-bearing when tap-to-play started rendering on every
   reply instead of the rare one that carried a `spoken` variant: a 26px target is not a gloved-thumb target. */
.gcmsg .hear::after { content: ''; position: absolute; inset: -9px; }
.gcmsg .hear:active { color: var(--ink); border-color: var(--muted); }
.keyotalkbtn { font-size: 13px; padding: 14px 22px; min-height: 48px; letter-spacing: .08em; }

/* ---- FIELD SIGN-IN (0104) — the alternative to @keyllc Access. Flat centered card, same Steel primitives
   (.padd/.pactions/.pbtn/.roles/.pbanner) as every other screen — nothing new to fight the design law. Full-
   height so it reads as its OWN screen, never a stray box floating over a half-loaded shell. */
.loginwrap { flex: 1; display: flex; align-items: center; justify-content: center; padding: 24px 16px; }
.loginbox { width: 100%; max-width: 360px; border: 1px solid var(--hair); padding: 20px 18px; background: var(--bg); }
.logintitle { font-size: 20px; letter-spacing: .08em; color: var(--ink); text-align: center; }
.loginsub { font-size: 10.5px; letter-spacing: .1em; color: var(--muted); text-align: center; margin: 4px 0 14px; }
.loginbox .roles { justify-content: center; padding: 0 0 10px; }
.loginbox .padd { padding: 0 0 10px; }
/* 44px: the phone/code field measured ~35px. 16px type is already deliberate here (it is what stops iOS
   zooming the whole page on focus) — this only raises the BOX. Scoped to .loginbox so the dense in-plan
   inputs, which live in a different context, are untouched. */
/* fix 1 (red team): the didn’t-get-a-text help line — load-bearing → --muted (F6) */
.loginhelp { color: var(--muted); font-size: 10.5px; letter-spacing: .04em; margin-top: 8px; text-align: center; }
.loginbox .padd input { text-align: center; font-size: 16px; letter-spacing: .04em; min-height: 44px; }
.loginbox .pactions { justify-content: center; padding: 0; }

/* ---- THE DATE STRIP (plan redesign P1, David-approved 2026-08-05) -----------------------------------------
   Successor to the Layout D jump bar, and it keeps the two rules that bar earned the hard way:
   STICKY, never fixed — it rides the .body scroller, so it never covers the app header or the nav bar. The
   stake is HIGHER now than it was for the bar: the strip is the only way to reach another day, so if it
   scrolled off it would strand a foreman on whatever date he was looking at.
   A DAY WITH NOTHING PLANNED IS SHOWN, NOT HIDDEN — an empty Thursday is information a foreman acts on, and
   hiding it would be the plan lying by omission. It now says so with an amber pip instead of a dim tile.
   PHONE = 2 ROWS OF 7, iPAD = ONE ROW OF 14, from the SAME markup (two .dsrow blocks); only the flex
   direction changes in the wide media query, so a rotation re-lays-out for free and no JS width read exists
   to go stale. */
.datestrip { position: sticky; top: 0; z-index: 5; background: var(--sechead);
  border-bottom: 2px solid var(--line); padding: 5px 8px 6px; display: flex; flex-direction: column; gap: 4px; }
.dsrow { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.dscell { background: var(--panel); border: 1px solid var(--hair); border-radius: 0; padding: 4px 2px 5px;
  min-height: 44px; cursor: pointer; font-family: inherit; color: var(--ink);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; }
.dscell .dsd { font-size: 9.5px; color: var(--muted); letter-spacing: .06em; }
.dscell .dsn { font-size: 13px; line-height: 1.15; }
.dscell.today { border-color: var(--ok); }
.dscell.today .dsd { color: var(--ok); }
/* SELECTED is a whole-frame statement, not a tint: this is the day on screen, and at arm's length in
   daylight a background shade alone does not carry. */
.dscell.sel { background: var(--bg); border-color: var(--ink); border-bottom: 3px solid var(--ok); }
/* HEALTH PIPS. ok = planned. warn = amber, the ratified semantic accent: a work day with nothing on it, or
   planned work with nobody on it. none = a legitimately empty Sunday — dimmed to an outline, not flagged,
   because an amber pip on every Sunday is how a warning color stops meaning anything. */
.dspip { width: 6px; height: 6px; display: block; }
.dspip.ok { background: var(--ink); }
.dspip.warn { background: var(--amber); }
.dspip.none { background: transparent; border: 1px solid var(--hair); }

/* ---- P2: DRAWER BUTTONS, THE 10-DAY WEATHER STRIP, THE 14-DAY SUMMARY ------------------------------------
   NO RAILS, NO PERMANENT PANELS (David's ruling: screen size buys comfort, not furniture). The drawer opens
   over the page and closes away, at every width — an iPad does not get a second column just because it has
   room for one. */
/* .drawerbar is RETIRED (2026-08-06) — the buttons moved into the nav row as .dnb.sm. The rule is kept
   because .dbtn is still the drawer-button vocabulary if another surface ever needs a full-size one; the BAR
   is what was too big and too low, not the button style. */
.drawerbar { display: flex; gap: 8px; padding: 8px 16px 0; }
.dbtn { background: transparent; border: 1px solid var(--muted); color: var(--muted); font-family: inherit;
  font-size: 9.5px; letter-spacing: .1em; padding: 10px 14px; min-height: 44px; cursor: pointer; border-radius: 0; }

/* THE 10-DAY STRIP — one compact cell per FORECAST day, aligned to the plan dates. Days 11-14 are simply
   absent: the forecast runs ~10 days and the plan runs 14, and rendering an empty cell for the tail would
   read as "no weather" rather than "not forecastable yet". Horizontal scroll rather than shrink-to-fit, so
   a cell never compresses below readable at arm's length. */
.wxstrip { display: flex; gap: 4px; overflow-x: auto; padding: 8px 16px 0; -webkit-overflow-scrolling: touch; }
.wxstrip.none { color: var(--muted); font-size: 9.5px; letter-spacing: .08em; padding: 10px 16px 0; }
.wxc { flex: none; min-width: 62px; border: 1px solid var(--hair); background: var(--panel); padding: 5px 6px;
  display: flex; flex-direction: column; align-items: center; gap: 1px; }
.wxc .wxd { font-size: 9px; color: var(--muted); letter-spacing: .06em; }
.wxc .wxs { font-size: 9px; color: var(--muted); text-align: center; line-height: 1.15; }
.wxc .wxhl { font-size: 11px; color: var(--ink); }
/* PRECIP IS A PROBABILITY (POP %), NOT A DEPTH — the platform has read this field as inches before. It is
   rendered with its % and never a unit of depth, and it stays muted: a chance of rain is context, not an
   alarm, and amber here would compete with the strip's real warnings. */
.wxc .wxpop { font-size: 9px; color: var(--muted); }

/* ---- THE SUPER'S REVIEW QUEUE — what is waiting on HIM, above everything else on his plan screen ---------
   Deliberately plain: a row per plan, the two actions under it, and a note field that only appears when he
   chooses to send one back. No new accent, no badge — the count in the section header is the whole signal,
   and a queue that shouts is a queue people stop reading. */
.srow .t b { font-size: 12.5px; }
.srow .t small { display: block; color: var(--muted); font-size: 9.5px; letter-spacing: .06em; }
.snote { flex: 1; min-width: 0; background: var(--bg); border: 1px solid var(--hair); color: var(--ink);
  font-family: inherit; font-size: 16px; padding: 9px 8px; border-radius: 0; min-height: 44px; }   /* 16px: iOS zoom law (pilot audit 2026-09-01) */

/* ---- P3: THE EQUIPMENT DRAWER — a 26-week horizon you plan a STAY on --------------------------------------
   Scrolls RIGHT for the further future; ~8 weeks land in view in portrait and more in landscape, both from
   the same 12px-per-day canvas — no breakpoint, no second layout, the viewport just shows more of it.
   overflow-y is VISIBLE, not hidden — this region only ever needs to scroll HORIZONTALLY; a vertical clip
   here served no purpose and was the plausible cause of the reported clipped SAVE TO/CALL OFF buttons. */
.eqscroll { overflow-x: auto; overflow-y: visible; -webkit-overflow-scrolling: touch; padding: 8px 0 10px; }
.eqcanvas { position: relative; }
.eqweeks { display: flex; padding-left: 132px; }   /* clears the pinned label column */
.eqwk { flex: none; font-size: 9px; color: var(--muted); letter-spacing: .06em; border-left: 1px solid var(--hair); padding-left: 3px; }
/* THE PLAN WINDOW, SHADED. Semantic amber at low alpha — the EXISTING accent, used as a ground here rather
   than a border, which is the one place DESIGN.md allows a wash: it marks a REGION, not a control. */
.eqwindow { position: absolute; top: 14px; bottom: 0; left: 132px; background: rgba(214, 158, 46, .10);
  border-left: 1px solid var(--amber); border-right: 1px solid var(--amber); pointer-events: none; }
/* ONE COMPACT ROW PER MACHINE (layout fix — pre-existing debt, surfaced by a live desktop review, diagnosed
   against the equipment-view PR's diff with zero overlap there). Label | track (bar + its own adjuster
   cluster) — a single 44px-tall flex row, not a bar row followed by a second full-width block of controls
   underneath every machine. */
.eqrow { display: flex; align-items: center; gap: 0; min-height: 44px; }
/* THE LABEL COLUMN IS PINNED so a machine keeps its name while the horizon scrolls away underneath it —
   a bar with no visible label is the one thing this drawer cannot afford at 26 weeks wide. An OPAQUE
   background (matching the row it sits on) is what keeps it from ever reading as "overlapping" the bar
   underneath as the canvas scrolls past it — it is meant to cover, cleanly, not blend. */
.eqlabel { position: sticky; left: 0; z-index: 2; flex: none; width: 132px; box-sizing: border-box;
  padding: 4px 8px; background: var(--panel); border-right: 1px solid var(--line);
  align-self: stretch; display: flex; flex-direction: column; justify-content: center; }
.eqlabel b { font-size: 11.5px; display: block; }
.eqlabel small { display: block; font-size: 9px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.eqlabel .eqthru { display: block; font-size: 9.5px; color: var(--amber); letter-spacing: .06em; }
.eqtrack { position: relative; flex: 1; height: 44px; }   /* matches .eqrow's own height — the row's one true height */
/* THE BAR IS OPEN ON THE LEFT — no left border, and it runs off the canvas edge. That absence is the
   RENDERING OF A FACT: no per-machine job-arrival date exists, so the bar says "already here, continuing"
   instead of drawing a start nobody recorded. Only the right edge is a control. Vertically centered in the
   track (was a fixed top:5px against a 28px track; the track is taller now, so centering keeps the bar's
   fixed 18px height literally centered regardless). */
.eqbar { position: absolute; left: 0; top: 50%; transform: translateY(-50%); height: 18px; background: var(--hover);
  border: 1px solid var(--muted); border-left: 0; }
.eqhandle { position: absolute; right: -9px; top: 50%; transform: translateY(-50%); width: 18px; height: 30px;
  cursor: ew-resize; background: var(--panel); border: 1px solid var(--ink); touch-action: none; display: block; }
.eqhidden { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
/* THE ADJUSTER/ACTION CLUSTER HUGS THE BAR'S CURRENT END (the fix): absolutely positioned inside the SAME
   .eqtrack the bar lives in, vertically centered on the row exactly like the bar. Its `left` is set inline
   at render (equipBarHtml) and kept in sync on every drag/step by the same eqSet() that already moves the
   bar's width and the THRU caption — one source of truth for "where does this bar currently end", never a
   second block guessing at it from a fixed offset near the label column. */
.eqsteps { position: absolute; top: 50%; transform: translateY(-50%); left: 0;
  display: flex; gap: 4px; align-items: center; white-space: nowrap; }
.eqstep { background: transparent; border: 1px solid var(--muted); color: var(--muted); font-family: inherit;
  font-size: 9.5px; letter-spacing: .08em; padding: 10px 12px; min-height: 44px; min-width: 44px; cursor: pointer; border-radius: 0; }
/* ONE TAP, ONE CALL-OFF (2026-09-09): the pending words stand where the button was; a span carries no tap. The
   SAVE button hides behind KEEP PENDING (THRU ...) until a drag moves the bar off the saved end -- .pbtn sets its
   own display, so the UA's [hidden] rule alone would not hide it. */
.eqpend { font-size: 9.5px; letter-spacing: .1em; color: var(--amber); padding: 0 6px; white-space: nowrap; font-weight: 800; }
.eqsteps [hidden], .ironrow [hidden] { display: none !important; }
.eqconfirm { padding: 10px 14px; border-top: 1px solid var(--hair); display: flex; align-items: center; gap: 10px; }
.eqok { color: var(--ok); font-size: 9.5px; letter-spacing: .1em; }

/* THE OPEN DAY'S WEATHER, on its card (2026-08-06). Sits directly under the day header as a quiet strip of
   fact — not a panel, not a card of its own. Muted throughout: the day's conditions are CONTEXT for the
   plan below, and anything louder would compete with the task list it exists to inform. */
.daywx { display: flex; align-items: baseline; gap: 8px; padding: 6px 16px 0; flex-wrap: wrap; }
.daywx .wxhl { font-size: 12px; color: var(--ink); }
.daywx .wxpop, .daywx .daywxs { font-size: 9.5px; color: var(--muted); letter-spacing: .06em; }

/* THE SUMMARY DRAWER — one row per day: date, the one-line summary, weather where it exists. The whole row
   is the tap target (it selects that day and closes), so a gloved finger never has to find the date. */
.drawer { margin: 10px 16px 0; border: 1px solid var(--line); background: var(--panel); }
.drawer .secbar { display: flex; align-items: center; justify-content: space-between; }
.dclose { background: transparent; border: 1px solid var(--muted); color: var(--muted); font-family: inherit;
  font-size: 9px; letter-spacing: .1em; padding: 7px 10px; min-height: 36px; cursor: pointer; border-radius: 0; }
.sumlist { display: flex; flex-direction: column; }
.sumrow { display: flex; align-items: flex-start; gap: 10px; width: 100%; text-align: left; cursor: pointer;
  background: transparent; border: 0; border-top: 1px solid var(--hair); border-radius: 0;
  font-family: inherit; color: var(--ink); padding: 12px 12px; min-height: 44px; }
.sumrow:first-child { border-top: 0; }
.sumrow.sel { background: var(--hover); }
.sumrow .sumd { font-size: 9.5px; color: var(--muted); letter-spacing: .06em; flex: none; width: 52px; padding-top: 3px; }
/* ROOM, NOT DENSITY (2026-09-05, "the lookahead format got compressed"): the row's content is the day's task
   list — ONE TASK PER LINE at a body size no smaller than v101's, wrapping at word boundaries (never mid-word,
   never clipped, never ellipsized: a clipped sentence is the one thing a foreman cannot act on). The date and the
   weather sit at the top of a tall row instead of squeezing the text between them. The server's summary line
   rides last, dimmer, but still legible. */
.sumrow .sumtx { flex: 1; min-width: 0; font-size: 13px; line-height: 1.4; overflow-wrap: break-word; }
.sumrow .sumtx .sumitem { display: block; padding: 1px 0; }
.sumrow .sumtx .sumitem + .sumitem { border-top: 1px dotted var(--hair); margin-top: 3px; padding-top: 4px; }
.sumrow .sumwx { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 1px; padding-top: 2px; }

/* PREV / NEXT + the day's name. The label is the answer to "what am I looking at", so it sits between the
   two controls where the eye lands, not off to one side. */
/* THE NAV ROW IS THIN, THE TARGETS ARE NOT (David, 2026-08-06: "thinner row", plus the standing glove rule).
   The row's DRAWN height comes down; every control keeps a >=44px effective tap area via an invisible
   ::after that overflows its box vertically. Clicks on that overflow still land on the button, because a
   pseudo-element is part of its parent's rendering — so the finger target is 44px while the ink is ~28px.
   This is the honest version of "smaller": .dnb.sm shrank the type and kept a 44px BOX, which could not make
   the row itself thinner. Shrinking the box and expanding the hit area is what actually buys the height back.
   NOTE — EXPANSION IS VERTICAL ONLY. Growing these boxes horizontally would overlap the neighbouring control and
   steal its taps — the row is a horizontal strip, so the free space is above and below, never beside. */
.daynav { display: flex; align-items: center; justify-content: space-between; gap: 6px; padding: 4px 10px 0; }
.dnmid { display: flex; align-items: center; justify-content: center; gap: 8px; flex: 1; min-width: 0; }
.daynav .dnlbl { font-size: 11.5px; color: var(--ink); letter-spacing: .08em; text-align: center; }
.dnb { background: transparent; border: 1px solid var(--muted); color: var(--muted); font-family: inherit;
  font-size: 9.5px; letter-spacing: .1em; padding: 5px 12px; min-height: 28px; cursor: pointer;
  border-radius: 0; position: relative; }
/* the invisible glove target — 44px tall, centred on a control that only DRAWS 28px */
.dnb::after, .dnav::after { content: ''; position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-50%); height: 44px; }
.dnb:disabled { border-color: var(--hair); color: var(--dim); cursor: default; }
/* THE ARROWS ARE CHROME, NOT BUTTONS: no border, no fill, just the glyph at the row's outer edges. They read
   as "move through days" at a glance, where the bordered pills beside them read as "open a view". */
.dnav { background: transparent; border: 0; color: var(--muted); font-family: inherit; font-size: 20px;
  line-height: 1; padding: 4px 6px; min-height: 28px; min-width: 28px; cursor: pointer; border-radius: 0;
  position: relative; flex: none; }
.dnav:disabled { color: var(--hair); cursor: default; }
/* THE UTILITY BUTTONS (14-DAY / EQUIP) ride the nav row at reduced scale — David, 2026-08-06: the full-width
   bar below the strip was "too big and too low". They are a way to go LOOK at something, so they read as
   secondary to PREV/NEXT and cost no vertical space.
   .sm trims the horizontal padding and the type. It no longer needs to defend the height: since the thin-row
   pass, .dnb draws 28px and carries its 44px target on the ::after overflow, so .sm inherits a full glove
   target without forcing the row tall. */
.daynavx { display: flex; gap: 6px; flex: none; }
.dnb.sm { font-size: 8.5px; letter-spacing: .08em; padding: 5px 8px; }

/* ---- LANDSCAPE iPAD — the PRIMARY device posture (docs/DESIGN.md "Device posture"); phone portrait is the
   secondary, fully-supported fallback and needs NO rules here — every element above is already a plain block
   that stacks by default. This breakpoint widens the shell and turns specific stacks into grids; same Steel,
   same colors, same type scale — arrangement only.
   1000px, not 1024 (2026-07-28 fix — David's real iPad stayed on the phone layout at min-width:1024px even
   though the smallest real iPad's logical landscape width IS 1024x768 — the exact boundary value left zero
   margin for a real device's viewport ever reporting a hair under 1024). Every current iPad's LANDSCAPE width
   (1024 9.7"/mini · 1080 10.2" · 1133 mini 6 · 1180 Air/10.9" · 1194 11" Pro · 1366 12.9") clears 1000 with
   real headroom; the largest realistic phone LANDSCAPE width (~932, iPhone Pro Max) stays well under it, so
   phones never pick up the wide layout by accident. Test the WHOLE family (layout.test.mjs), not one sample
   width — the 1180-only spec example this shipped with is exactly how the boundary case got missed. */
/* ---- iPAD PORTRAIT JOINS THE WIDE LAYOUT (David, 2026-08-05: "the iPads are the day-one device and the
   whole tool should use the canvas, not float in a phone column").
   THE CAP WAS NEVER THE PROBLEM — it was already lifted, at the wrong breakpoint. min-width:1000px was
   written for iPad LANDSCAPE, and every iPad landscape width clears it. But PORTRAIT does not: mini 6 = 744,
   9.7"/mini = 768, 10.2" = 810, Air/10.9" = 820, 11" Pro = 834 — all BELOW 1000, so an iPad held upright
   dropped to the 640px phone column. That is the "floating in a phone column" David saw.
   NOTE: WIDTH ALONE CANNOT SEPARATE iPAD PORTRAIT FROM PHONE LANDSCAPE — they overlap: the smallest iPad
   portrait is 744 and the largest phone landscape (iPhone Pro Max) is ~932. A bare min-width:744 would drag
   phones in landscape into the tablet grids, which is exactly what the original 1000px boundary was
   protecting against. HEIGHT is what actually distinguishes them: iPad portrait is >=1024 tall, phone
   landscape is ~430. So the second clause pairs a tablet-ish width with a tall viewport.
   The comma is an OR: landscape iPad (any height) OR portrait tablet (tall). A phone fails BOTH — 932 < 1000,
   and 430 < 700 — so phone layout does not move by a pixel, which was the hard constraint. */
@media (min-width: 1000px), (min-width: 744px) and (min-height: 700px) {
  /* 1180, not 1400 (David's readability ruling, same message): wide, but not unbounded — a line of task text
     past ~1200px stops scanning cleanly and starts needing to be read across. The shell still fills a 12.9"
     landscape comfortably; it just stops growing where prose gets hard. */
  body.steel { max-width: 1180px; }

  /* PLAN — the big win: each week's 7 days grid into columns instead of one long stacked list (the desktop
     foreman tool's density is the reference). Each day becomes a bordered column-card; its own add/edit form
     already renders INSIDE that day's block (planBands, app.js) — beside its day, never a modal over anything. */
  /* LAYOUT D (David-ratified 2026-07-31) — the 7-column grid is RETIRED. It was
     `repeat(7, minmax(150px, 1fr))` gated at 1000px, which meant 7x150 + gaps = 1056px of columns inside a
     1024pt iPad landscape: the tracks never grew past their floor, so every column sat at 150px and a
     12.9" only reached ~190px. Not narrow by accident — narrow by arithmetic. Day BANDS give a task the
     full width (~1000px landscape, ~740px portrait) and the sticky jump bar buys back the week-at-a-glance
     the columns were paying for. Bands are the phone layout too, so there is now ONE plan layout, not two. */
  .pweek { display: block; background: transparent; }
  /* DAY DEMARCATION GAP (found 2026-08-01): this predates the demarcation work and was never retired when it
     shipped — it re-flattened the day band back to a 1px hairline on the page background at exactly the width
     (>=1000px, iPad landscape) a foreman actually uses. "Bands are the phone layout too, ONE plan layout" only
     held below 1000px; above it, the .card frame, its --panel ground and its margin-top:16px gap were all
     silently overridden away. No override here now — the base .pday (a real steel.css .card) stands at every
     width, which is the whole point of "ONE plan layout, not two". */
  /* A grid item defaults to min-width:auto — it refuses to shrink below its content, so a long task
     sentence pushes the cell wider than its 150px track and spills. min-width:0 lets the cell hold its
     track, and overflow-wrap lets an unbroken token break rather than escape the box. */
  .pweek .pday { min-width: 0; }
  .pweek .ptask .tx, .pweek .padd .mono { overflow-wrap: anywhere; }
  .pweek .pday.today { padding-left: 10px; }

  /* SAFETY — JHA and Good Catch side by side; the drawn-signature pad gets more canvas (sigpanel widens). */
  /* DATE STRIP: ONE ROW OF 14 on a tablet (David-ruled). The two .dsrow blocks stop stacking and sit side
     by side, so the whole fortnight reads as a single line of dates instead of two half-weeks. */
  .datestrip { flex-direction: row; gap: 6px; }
  .datestrip .dsrow { flex: 1; }

  .safetygrid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
  .sigpanel { max-width: 620px; }
  .sigcanvas { height: 240px; }

  /* HOME — the 5 job-card sections grid 2-up instead of one long scroll. */
  .homegrid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }

  /* REQUESTS — the form and its Confirm summary sit side by side (Housing's Camper Move form; the generic
     trucking/equipment/shop form has no separate confirm step, so only .reqgrid consumers are affected). */
  .reqgrid { display: grid; grid-template-columns: 1.3fr 1fr; gap: 16px; align-items: start; }

  /* CREW / EQUIPMENT — the wider shell already gives table.crew and .mcard more room (both size off the
     container, steel.css); utilization + inspection strip additionally sit side by side instead of stacked
     so both columns of information show together without scrolling. */
  /* UTILIZATION + INSPECTIONS side by side. THE ARITHMETIC MUST LEAVE ROOM FOR THE INLINE-BLOCK WHITESPACE
     GAP — found in the 2026-08-05 iPad-portrait audit. These are two consecutive inline-blocks inside the
     machine card, so the newline between them in the markup renders as a real ~4px space. The old numbers
     were 2x(50% - 8) + 16 margin = 100% + 0 BEFORE that space, so adding it pushed the pair past 100% and
     .ins wrapped — leaving two half-width boxes stacked, each with 50% of empty gutter beside it. It only
     showed once portrait joined the wide layout, because landscape had width to spare.
     2x(50% - 12) + 16 + ~4 = 100% - 4: fits with margin, and the visible gap stays 20px.
     (.ins-col wraps the header + cells + key + foot; .ins itself is now just the inner cell row.) */
  .util, .ins-col { display: inline-block; vertical-align: top; width: calc(50% - 12px); box-sizing: border-box; }
  .util { margin-right: 16px; }
}

/* the drawn-signature pad — a modal overlay, Steel-flat (mirrors foreman-portal's jha-sigpanel, no emoji) */
.sigback { position: fixed; inset: 0; background: rgba(0,0,0,.7); display: flex; align-items: center; justify-content: center; z-index: 50; padding: 16px; }
.sigpanel { background: var(--panel); border: 1px solid var(--line); padding: 14px; width: 100%; max-width: 420px; }
.sigtitle { font-size: 12px; margin-bottom: 10px; }
.signername { width: 100%; background: var(--bg); border: 1px solid var(--hair); color: var(--ink);
  font-family: inherit; font-size: 16px; padding: 8px; border-radius: 0; margin-bottom: 8px; box-sizing: border-box; }   /* 16px: iOS zoom law (pilot audit 2026-09-01) */
/* DELIBERATELY theme-independent: the drawing surface stays dark with a light stroke (app.js draws
   #dcdad5) in BOTH Clean Steel modes, like a signature-capture pad's own fixed chrome — not a page
   surface that should flip to a white background + dark ink in light mode. */
.sigcanvas { width: 100%; height: 160px; background: #232326; border: 1px solid var(--hair); touch-action: none; display: block; }
.sighint { text-align: center; color: var(--muted); font-size: 9.5px; letter-spacing: .08em; margin-top: 6px; }
.sigbtns { display: flex; gap: 6px; margin-top: 10px; }
.sigbtns .pbtn { flex: 1; }

/* ---- SHELL VERSION FOOTER (MORE tab) — the device answers "what version am I running" by being looked at.
   Quieter than .edge (steel.css): this is service information, not brand voice. Sits above the edge line. */
.shellver { text-align: center; color: var(--dim); font-size: 10px; letter-spacing: .14em; margin: 16px 0 0; opacity: .8; }

/* ---- KEYO auto-send beat — "SENDING — TAP TO CANCEL" (the mis-hear brake). Amber = attention, not danger:
   nothing is wrong, something is about to happen and one tap stops it. Glove-height like every action. */
.ktsending { border-color: var(--warn, #b58900); color: var(--warn, #b58900); min-height: 48px; }

/* ---- KEYO live-mic indicator. Conversation mode re-arms listening on its own, so "is the mic open" can
   never be a guess: this is a STATE readout, not a button label. Amber like the send beat — attention,
   not alarm; the dot reads at arm's length in daylight. */
.keyolive { color: var(--warn, #b58900); letter-spacing: .1em; }


/* CREW ENGAGEMENT CHIP (David 2026-08-05, tag don't drop) — how thin a man's tie to THIS foreman is.
   Placed ABOVE the CREW DOCK block on purpose: that block is guarded by the ratified accent-restraint rule
   (crew_drag.test.mjs, "no amber on assignment — it is a neutral fact, not a warning").
   THE RULE AND THE RULING AGREE HERE. David chose TAG over FLOOR precisely because a one-day man may be a
   brand-new hire — so on the ASSIGNMENT surface the chip must NOT read as an alarm, and the dock renders it
   dim. The amber2 TEXT accent (no fill, no border, no new colour) is used on the camper form, where the row
   is an informational list rather than an assignment control. */
.eng { font-size: 10px; color: var(--dim); margin-left: 6px; flex: 0 0 auto; }
.eng.thin { color: var(--amber2, var(--amber)); }   /* <= 1 day in 14 — the transient the ruling surfaces */
.eng.unk { color: var(--dim); font-style: italic; }  /* no HCSS id: UNKNOWN, never a guessed zero */

/* ---- CREW DOCK + on-task chips (2026-07-31) ----------------------------------------------------------------
   Drag a name onto a task, or tap the name then the task — two paths, one op (docs/CAPABILITIES.md row 35).
   The tap path is the PRIMARY one on a wet iPad in gloves, where a drag routinely fails to complete.
   Every chip is >=44px tall. No amber anywhere: assignment is a neutral fact, not a warning. */
.crewdock .dockrow { display: flex; flex-wrap: wrap; gap: 8px; padding: 10px 16px 4px; }
.dockchip { display: inline-flex; align-items: center; gap: 7px; min-height: 44px; padding: 6px 12px;
  background: transparent; border: 1px solid var(--btnline); border-radius: 0; color: var(--ink);
  font-family: inherit; font-size: 13px; cursor: grab; touch-action: manipulation; }
.dockchip:active { cursor: grabbing; }
.dockchip.on { border-color: var(--ok); }
/* HELD (the tap path's first half): an ink frame, so "I am carrying this man" is unmistakable at a glance */
.dockchip.picked { border-color: var(--ink); border-width: 2px; background: var(--hover); }
.dockname { line-height: 1.2; }
/* the assignment mark: a FILLED squared .cb box and NOT the check-mark dingbat — steel.css's .cb.done paints that
   glyph via :after, and the codepoint is banned. A filled box says "set" without a character. */
.crewdock .cb { width: 16px; height: 16px; border-color: var(--dim); }
.crewdock .cb.on { border-color: var(--ok); background: var(--ok); }
.dockx { font-size: 11px; color: var(--ok); letter-spacing: .04em; }   /* "2x", "3x" — the multi-assign count */
.dockhint { padding: 2px 16px 10px; font-size: 9.5px; color: var(--dim); letter-spacing: .06em; }

/* the per-task crew line — chips are one-tap unassign; the ROW is the drop target (a 44px chip is not a
   target you can hit on a moving iPad, and the row is what a foreman is aiming at anyway) */
.crewrow { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 4px 0 2px; min-height: 34px; }
.crewrow.drop { border-left: 2px solid transparent; padding-left: 6px; }
.crewrow.armed { border-left-color: var(--dim); }        /* a name is held — these rows will take it */
.crewrow.over { border-left-color: var(--ok); background: var(--hover); }   /* dragging over THIS one */
.crewchip { display: inline-flex; align-items: center; gap: 6px; min-height: 34px; padding: 4px 8px;
  background: transparent; border: 1px solid var(--ok); border-radius: 0; color: var(--ok);
  font-family: inherit; font-size: 11.5px; cursor: pointer; }
.crewchip .x { font-size: 13px; line-height: 1; color: var(--muted); }
.crewnone { color: var(--dim); font-size: 9.5px; letter-spacing: .06em; }

/* A labelled field: the label sits on its OWN line above its control, and the control fills the row. Replaces
   the "whose camper is …?" run of lowercase mono body text with a select jammed inline beside it. Tokens only
   — no new accent colors. */
.flab { display: block; font-size: 11px; letter-spacing: .06em; color: var(--muted); margin-bottom: 4px; }
.flab + select, .flab + input { display: block; width: 100%; }

/* NEEDS pills (day-attached trucking/shop/equipment) — same tag language as .crewchip/.dockchip: a bordered
   rectangle, no fill, no new accent. The removal × only renders when the row is still draft-removable. */
.needpill { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line); border-radius: 0;
  padding: 6px 10px; margin: 0 8px 8px 0; min-height: 44px; font-size: 12px; }
.needpill b { font-size: 10px; letter-spacing: .04em; color: var(--muted); }
/* The idle × is the ARMING tap, and gloves have to land it too — 44px real target, kept visually compact by
   letting the target bleed into the pill's own padding (negative margins; the pill stays 44px tall). --muted,
   not --dim: an interactive glyph is load-bearing (F6), and 32px was the last sub-44 target in this family. */
.needpill .pillx { border: 0; background: transparent; color: var(--muted); font-size: 16px; line-height: 1;
  padding: 4px; margin: -6px -10px -6px 0; min-width: 44px; min-height: 44px; }
/* The armed pill has to be written HERE, after (and at least as specific as) `.needpill .pillx` above —
   that rule zeroes the border and dims the colour, and at equal specificity the LATER rule wins. Declared up
   with .pbtn.armed it would have been silently overridden and the armed `×` would have looked identical to
   the idle one. Same specificity trap as a.pbtn, one file apart. Armed it stops being a glyph and becomes a
   worded control, so it also sheds the 32px square sizing and takes the full glove target. */
.needpill .pillx.armed { border: 2px solid var(--red); color: var(--ink); background: transparent;
  font-size: 9.5px; letter-spacing: .08em; font-weight: 800; padding: 6px 10px; min-height: 44px; }

/* The bottom-of-lookahead equipment window sliders — a plain card, same family as .crewdock. */
.ironpanel .ironrow { display: block; padding: 10px 16px 14px; border-bottom: 1px solid var(--hair); }
.ironpanel .ironrow:last-child { border-bottom: 0; }
.ironpanel input[type="range"] { width: 100%; height: 44px; }

/* HOME EQUIPMENT BRIEF (David, 2026-08-05) — the Home card became a scrollable brief instead of a count-only
   teaser. The list scrolls WITHIN the card so Home's Safety and Requests cards stay reachable rather than
   sitting under a mile of iron.
   The max-height is deliberately ~4.5 rows, not 4 or 5: a half-clipped row is the affordance that says "there
   is more below". A clean cut at a row boundary reads as the end of the list, and a foreman would never scroll.
   No new colours — the tagged left-rule and the chip are the SAME ones the Equipment tab's machine card uses
   (.mcard.tagged / .st.bad), because it is the same fact on two surfaces. */
.ironbrief { max-height: 158px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.ibrow { display: flex; align-items: baseline; gap: 8px; padding: 7px 16px; border-top: 1px solid var(--hair); }
.ibrow:first-child { border-top: 0; }
.ibrow b { font-size: 13px; flex: 0 0 auto; }
.ibrow small { color: var(--muted); font-size: 11px; flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ibrow .st { flex: 0 0 auto; }
.ibrow.tagged { border-left: 3px solid var(--ink); }

/* On the DOCK the chip is deliberately NEUTRAL — see the accent note above. Assignment never borrows a
   state colour, and a thin tie is a fact for the foreman to weigh, not a warning to answer. */
.dockchip .eng, .dockchip .eng.thin { color: var(--dim); margin-left: 5px; }

/* REVIEW WRAP-UP (2026-08-12) — the one-item-at-a-time walk, dispatch's openWrapUp shape in Clean Steel.
   A fixed overlay ABOVE the nav; the card scrolls, the page behind does not move. Every control inside is
   the standard 44px .pbtn; the date input inherits the base 16px/44px form rule. */
.rvwrap { position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: 16px; }
.rvcard { background: var(--panel); border: 1px solid var(--line);
  width: 100%; max-width: 560px; max-height: 85vh; overflow-y: auto; border-radius: 0; }
.rvcard .pactions { flex-wrap: wrap; }

/* ---- GIVE FEEDBACK (beta, David 2026-08-24) — a persistent slim bar above the nav (mounted OUTSIDE
   #body, like .viewbar, so paint() never tears a draft out from under a typing thumb) opens a fixed
   modal overlay, same posture as .rvwrap/.rvcard. FEEDBACK_ENABLED-gated — retires by hiding, never a
   dead button left behind once the beta ends. */
.feedbackbar { padding: 6px 16px; text-align: right; border-top: 1px solid var(--hair); }
.fbwrap { position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: 16px; }
.fbcard { background: var(--panel); border: 1px solid var(--line); border-radius: 0;
  width: 100%; max-width: 420px; max-height: 90vh; overflow-y: auto; padding: 14px; box-sizing: border-box; }
.fbthumb { width: 100%; max-height: 220px; object-fit: contain; background: var(--bg);
  border: 1px solid var(--hair); display: block; margin-bottom: 8px; }
.fbtext { width: 100%; background: var(--bg); border: 1px solid var(--hair); color: var(--ink);
  font-family: inherit; font-size: 16px; padding: 8px; border-radius: 0; min-height: 90px;   /* 16px: iOS zoom law (red team 2026-08-30) */
  box-sizing: border-box; resize: vertical; margin-top: 8px; }
/* the minimized-flow pill (David 2026-09-01): bottom edge above the nav, the page fully usable under it.
   NEUTRAL steel, not amber — the accent-restraint law (crew_drag.test.mjs) reserves state colours for
   state; a parked action announces itself by position and words. */
.fbpill { position: fixed; left: 50%; transform: translateX(-50%); bottom: 76px; z-index: 60;
  min-height: 44px; padding: 8px 16px; background: var(--panel); color: var(--ink);
  border: 1px solid var(--line); border-radius: 0; font-family: inherit; font-size: 12px;
  letter-spacing: .06em; cursor: pointer; }
.fbctx { margin-top: 6px; color: var(--dim); font-size: 9px; letter-spacing: .06em; }
/* a chip that is also a TAP TARGET gets the glove floor (red team 2026-08-30): same 44px law as .pbtn/.roles */
.st.stbtn { min-height: 44px; display: inline-flex; align-items: center; padding: 2px 12px; cursor: pointer; font-size: 12px; }
.fbbtns { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }

/* JHA HUDDLE (docs/field-jha-voice-design.md 2.6 / 4.4). The recording bar rides the same persistent mount as .viewbar
   and .updatebar (a sibling of .hd/.body, survives paint()'s #body swap) so it is on screen on EVERY tab while a huddle
   is live or sending. DANGER IS A HAIRLINE AND TEXT, NEVER A FILL (DESIGN.md): the red is the rule and the words. */
.huddlebar { border-top: 2px solid var(--red); border-bottom: 1px solid var(--hair); background: var(--panel); padding: 8px 16px; }
.huddlebar .hb-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.huddlebar .hb-top b { font-size: 12px; letter-spacing: .05em; color: var(--ink); }
.huddlebar .hb-rec { color: var(--red); }
.huddlebar .hb-top .mono, .huddlebar .hb-line { color: var(--muted); font-size: 10.5px; letter-spacing: .05em; }
.huddlebar .hb-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 6px; }
.huddlebar .hb-actions b { font-size: 11px; letter-spacing: .05em; }
.huddlebar .hb-line { margin-top: 6px; }
.huddlebar .hb-line.bad { color: var(--red); }
/* the card (amended 2026-09-09): AI VOICE MEMO · JHA SUMMARY — the status line and ONE summary, editable. No state colour
   here: the accent-restraint law (crew_drag.test.mjs) keeps amber for warnings; a summary is a fact, not an alarm. */
.huddlecard { padding: 2px 0; }
/* "powered by KEYO" under the TAKE AI VOICE MEMO row (or under the memo heading once the section renders): the card's
   own .foot mono note, pulled up against the row. Nothing upper-cases .foot or .mono (only .cond does, and that is the
   button) — text-transform: none pins the lower-case wording. */
.huddlecard .huddlepow { padding-top: 0; text-transform: none; }
.huddlehead { padding: 8px 16px 0; color: var(--ink); font-size: 11px; letter-spacing: .08em; }
.huddlest { padding: 2px 16px 0; color: var(--muted); font-size: 10.5px; letter-spacing: .05em; }
.huddlest.bad { color: var(--red); }
.huddlesum { padding: 6px 16px 2px; font-size: 14px; color: var(--ink); }
/* the editable summary: the .fbtext law (16px = no iOS zoom, resize vertical, the panel's own hairline), inset to the
   card's 16px gutter; SAVE sits in its own row; SAVED / EDITED BY is the card's .foot mono note */
.huddleedit { display: block; width: calc(100% - 32px); margin: 6px 16px 0; background: var(--bg); border: 1px solid var(--hair);
  color: var(--ink); font-family: inherit; font-size: 16px; line-height: 1.35; padding: 8px; border-radius: 0; min-height: 110px;
  box-sizing: border-box; resize: vertical; }
.huddlesave { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 6px 16px 0; }
.huddlesave .mono { color: var(--muted); font-size: 10.5px; letter-spacing: .05em; }
.huddlecard .huddleedited { padding-top: 4px; }
/* the HUDDLE chip: dashed like .src.ai (a machine's guess stays a guess), and a TAP TARGET — the chip itself stays
   chip-sized beside the words, its hit area is extended to the 44px glove floor by the pseudo-element */
.jhatask .src.huddle { border-style: dashed; }
.jhatask button.src { background: transparent; font-family: inherit; cursor: pointer; position: relative; color: var(--muted); border-radius: 0; }
.jhatask button.src::after { content: ''; position: absolute; left: -8px; right: -8px; top: -13px; bottom: -13px; }
.jhatask button.src[aria-expanded="true"] { color: var(--ink); border-color: var(--line); }
.jhatask .hz.quote { color: var(--ink); margin-top: 4px; }

/* ---- THE MACHINE CARD (David 2026-09-09, Austin's 768 list item 2) — every printed unit number is a tap
   target (.unittap) that opens ONE card. The card itself is the SHARED component since 2026-09-11 (its drawer, rows
   and map are styled by /machine-card.css, GENERATED from lib/machine-card - the ksgmc-* classes; none of the old
   sheet's rules is left here). .unittap: the 44px glove floor via padding pulled back with a matching negative
   margin, so the tap area grows without moving the type; a dotted underline says "this opens something" without a
   button's chrome. */
.unittap { cursor: pointer; text-decoration: underline dotted; text-decoration-color: var(--muted);
  text-underline-offset: 3px; min-height: 44px; box-sizing: border-box; padding: 14px 4px; margin: -14px -4px;
  -webkit-tap-highlight-color: transparent; }
.unittap:active { color: var(--muted); }
/* THE SHARED CARD ON A PHONE — the two field-only rules the shared sheet must not carry (the planner has no insets
   and works a mouse). The drawer is position:fixed inset:0 and its Close button is the ONLY way out (the page
   behind is inert, the backdrop does not close, at <=640px the drawer is 100vw so there is no backdrop to tap, and
   a phone has no Escape key), so both of these are the exit itself:
   1. SAFE AREA. index.html declares viewport-fit=cover and the manifest declares standalone, so on an installed
      iPhone the top ~47-59px is the status bar / Dynamic Island - the bar, the title and Close measured y=10..50,
      entirely under it (red team 2026-09-11). The drawer is box-sizing:border-box at height:100%, so padding the
      insets moves the bar clear of the notch and the scrolled body clear of the home indicator. This is the same
      recipe body and .nav already use above.
   2. THE 44px GLOVE FLOOR. The shared Close is 40px (a mouse target); the sheet it replaces was 44. The floor is a
      standing field law here (.pbtn.sm, .prescheck, .hcbtn, .unittap, docs/DESIGN.md) and this is the one control
      a gloved foreman must hit to get his app back. The WIDTH is restated, not relaxed: the shared sheet's
      own 76px is wider than the floor, and a bare min-width:44px here would have SHRUNK the target (measured 76 -> 64). */
#mcsheet .ksgmc-drawer { padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); }
#mcsheet .ksgmc-close { min-height: 44px; min-width: 76px; }

/* The Home brief stacks ~31px rows, so the 44px floor cannot come from vertical overhang there: the padded box
   reached ~6px into the neighbouring row and a low tap opened the NEXT machine's card (red team 2026-09-09).
   Bound the brief's tap to its own row: padding equal to .ibrow's own 7px, pulled back the same way, no floor. */
.ibrow .unittap { min-height: 0; padding: 7px 4px; margin: -7px -4px; }

/* HOUSING REQUESTS grouping (Stage 4, 0139 follow-on) — one collapsible group per request. The group's own
   markup reuses .row/.secbar/.pbanner/.st verbatim (no new component); these rules only add the collapse
   affordance (cursor + tap feedback, same recipe .row[data-actas] already uses above) and a light nesting cue
   for the body underneath its header. */
.hgrouphead { cursor: pointer; min-height: 44px; box-sizing: border-box; }   /* glove tap target, gloves outdoors */
.hgrouphead:active { background: var(--hair); }
/* the toggle is drawn, not spelled — SHOW/HIDE text becomes a CSS chevron (border-corner + rotate), never a
   glyph character: the glyph guard is repo-wide and a chevron char is how you lose an afternoon (readability
   pass, David's brief). Points down when the body is open, right when it is collapsed. */
.hgrouparrow { flex: none; margin-left: 6px; width: 7px; height: 7px;
  border-right: 2px solid var(--dim); border-bottom: 2px solid var(--dim); transform: rotate(45deg); }
.hgroup.collapsed .hgrouparrow { transform: rotate(-45deg); }
.hgroupbody { border-left: 2px solid var(--line); margin-left: 16px; }
.hgroupbody .row { padding-left: 12px; }
.hgroup.collapsed { border-bottom: 1px solid var(--hair); }
/* the header's own payload line: "Pecan Park - 3 campers" — data in normal case (readability pass), the
   destination + what's on the request, so a foreman reads what matters without opening the group. */
.hgrouphead .t b { font-weight: 600; text-transform: none; letter-spacing: normal; }
.hgrouphead .t small { text-transform: none; }
/* the move date carries urgency: amber for today/tomorrow, red for overdue — the same two semantics the rest
   of the system already uses for WAITING and danger states, never a new color. */
.hgroupdate.amber { color: var(--amber); font-weight: 700; }
.hgroupdate.red { color: var(--red); font-weight: 700; }
/* compact camper list (readability pass): a wrapped chip row instead of one full .row per unit — same content,
   a fraction of the height on a five-camper batch. */
.hchips { display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 16px 12px 12px; }
.hchip { border: 1px solid var(--line); padding: 3px 8px; font-size: 12px; }
/* screen-reader-only label for the toggle's drawn caret — visually nothing, still announced */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0; }

/* TAKE A MAN OFF THE CREW (David 2026-09-13) — the foreman's confirm sheet, mounted at #crewrmsheet outside
   #body. The overlay/card recipe is .fbwrap/.fbcard's verbatim (fixed backdrop, squared panel, 90vh scroll);
   these are its own names because it is its own component. The REMOVE row button is the standard .pbtn.sm on
   the glove floor; danger is the hairline and the word (.pbtn.bad), never a fill. */
.crwrap { position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: 16px; }
.crcard { background: var(--panel); border: 1px solid var(--line); border-radius: 0;
  width: 100%; max-width: 420px; max-height: 90vh; overflow-y: auto; padding: 14px; box-sizing: border-box; }
.crcard .chiprow { padding-left: 0; padding-right: 0; }
.crlab { margin-top: 10px; color: var(--muted); font-size: 10px; letter-spacing: .08em; }
table.crew td .crewrm { margin-left: 8px; vertical-align: middle; }

/* THE DECLINE BADGE on the REQUESTS nav tab (paintRequestsBadge, app.js) — positions the SHARED .st.bad chip
   (steel.css) into the corner of the nav anchor; the chip's own color/border/shape is untouched, reused as-is. */
.nav a { position: relative; }
.nav a .navbadge { position: absolute; top: 3px; right: 10%; padding: 1px 4px; font-size: 9px; line-height: 1.3; }
