/* Datum Shop Connect — Forestdale's shop floor app.
 *
 * Designed for a phone or tablet held in one gloved hand in a fabrication shop, which
 * drives every choice here: large hit targets (44px is the accessibility floor and
 * everything tappable clears it), high contrast for a bright bay, dark ground so the
 * screen is not a torch, and no hover states because there is no pointer.
 *
 * Deliberately NOT sharing Datum's stylesheet. Datum is a dense desktop board tuned
 * for information per square inch; this is the opposite problem. One stylesheet
 * serving both would end up serving neither.
 *
 * 🎨 BUT THE COLOURS ARE SHARED, AND THEY ARE NOT DEFINED HERE. /brand/theme.css (the
 * repo-root `brand` package) is loaded first and defines every colour token in three
 * themes — Light, Dark, Black — chosen per device on the account page. This file says
 * where a colour goes; theme.css says what it is. ⚠️ A hex literal here ignores two of
 * the three themes — add a token there instead. (Renamed 2026-09-30 to the shared
 * names: `--ground` is `--bg`, `--ink-dim` is `--dim`, `--ok` is `--good`.)
 * The old "dark ground so the screen is not a torch" default is now the device's own
 * setting, and Black is the mode for a phone in a dark bay.
 */

:root {
  --tap:      48px;          /* every interactive thing clears the 44px floor */

  /* The app bar's height, as the three numbers it is actually made of. Named here
     because a SECOND sticky element (the agenda's day header) has to sit exactly
     below it, and a hardcoded guess there would slide under the bar the moment
     either padding changed — or on any phone with a notch, where the bar grows by
     the safe-area inset and a fixed offset does not. */
  --bar-pad:  .55rem;
  --bar-body: 2.75rem;       /* the tallest thing in the bar: the Menu button (44px) */
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.45 var(--font-sans);
  font-variant-numeric: tabular-nums;
  /* 16px minimum on inputs, or iOS zooms the whole page when one is focused. */
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--accent); }
button, input, select, textarea { font-family: inherit; }
/* 🔢 Chivo Mono for job numbers, part numbers, POs and quantities — the brand type
   guide's one rule for it: "so every digit lines up and nothing gets misread", which on
   a floor means 8 vs B and 0 vs O read off a phone at arm's length. */
.mono, .qty, .capture-qty, code { font-family: var(--font-mono); letter-spacing: -.01em; }

.wrap {
  max-width: 44rem;
  margin: 0 auto;
  padding: 1rem 1rem 4rem;
  /* Reach under the rounded corners, then put the padding back. */
  padding-left:  max(1rem, env(safe-area-inset-left));
  padding-right: max(1rem, env(safe-area-inset-right));
  padding-bottom: max(4rem, env(safe-area-inset-bottom));
}

h1 { font-size: 1.55rem; margin: .4rem 0 .2rem; font-weight: 700; letter-spacing: -.01em;
     line-height: 1.2; }
/* The mockup's section label: small, capitalised, widely spaced, quiet — it names a
   block without taking a line's worth of height from it. */
h2 { font-size: .74rem; margin: 1.4rem 0 .5rem; color: var(--dim); font-weight: 700;
     text-transform: uppercase; letter-spacing: .12em; }
h3 { font-weight: 700; }
.sub { color: var(--dim); margin: 0 0 1rem; }
.hint { color: var(--dim); font-size: .88rem; margin: .3rem 0 .9rem; }
.fineprint { color: var(--dim); font-size: .82rem; border-left: 2px solid var(--line);
             padding-left: .6rem; margin: .8rem 0 1rem; }
.count { color: var(--dim); margin: .2rem 0 .6rem; }
.empty { background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
         padding: 1.2rem; color: var(--dim); }

/* --- top bar ------------------------------------------------------------- */
/* 🔑 THE BAR IS MIDNIGHT IN EVERY THEME — the brand bar, the same as Datum's rail and
   the mockup's header — so everything inside it reads the constant `--rail-*` tokens,
   never the page's `--ink`/`--panel`, which in Light are near-black on white. */
.bar {
  display: flex; align-items: center; gap: .75rem;
  background: var(--rail-bg); color: var(--rail-ink);
  border-bottom: 1px solid var(--rail-line);
  padding: var(--bar-pad) 1rem;
  padding-top: max(var(--bar-pad), env(safe-area-inset-top));
  position: sticky; top: 0; z-index: 5;
}
.bar-home { color: var(--rail-strong); font-weight: 650; text-decoration: none;
            flex: 0 0 auto; }

/* --- weight ---------------------------------------------------------------
   The shipping total reads like a fact somebody writes down, so it gets the size of
   one. The per-line piece weight is quieter: it is scanned, not transcribed. */
.shipwt { margin: .2rem 0 .8rem; font-size: 1.15rem; line-height: 1.35; }
.shipwt strong { font-variant-numeric: tabular-nums; }
.shipwt-note { display: block; font-size: .78rem; color: var(--dim);
               font-weight: 500; margin-top: .15rem; }
.linewt strong { color: var(--ink); font-variant-numeric: tabular-nums; }

/* --- flat size, above the outline -----------------------------------------
   Big enough to read at arm's length on a tablet clamped to a machine, which is the
   whole reason it is a measurement and not a caption. */
.flatsize { margin: .2rem 0 .6rem; font-size: 1.25rem; line-height: 1.3; }
.flatsize strong { font-variant-numeric: tabular-nums; }
.flatsize-note { display: block; font-size: .8rem; color: var(--dim);
                 font-weight: 500; }
/* A skipped or unlabelled measurement is dimmed, never hidden: "not measured" and
   "measured as nothing" are different facts. */
.flatsize-none { font-size: .88rem; color: var(--dim); }

/* --- the name lockup ------------------------------------------------------
   "Datum" full size with "shop connect" small underneath — Brodie 2026-08-21. The
   floor says "Datum"; the long form is there to tell this app from the office one in
   writing, not to be read out.

   ⚠️ BOTH LINES ARE line-height: 1 ON PURPOSE. A two-line lockup in a STICKY header
   costs vertical room on a phone that is already competing with the notch — the .tabs
   comment below is the same budget. Tight leading keeps the whole lockup to ~25px
   against the old single line's ~16px; at default leading it would be ~38px. */
.lockup      { display: inline-flex; flex-direction: column; justify-content: center;
               gap: 3px; text-decoration: none; }
/* The name is the DATUM wordmark itself (brand/templates/brand/_logo.html), drawn in
   currentColor, so the lockup's colour is set once here. Height sets it; the wordmark is
   ~6.4:1, so 15px tall is ~96px wide. */
.lockup-name { color: var(--rail-strong); line-height: 1; display: block; }
.lockup-name .datum-logo { display: block; height: 15px; width: auto; }
/* 🎀 The Pink easter egg's script logo, sized for the bar (theme.css shows it). */
.bar .lockup-name .logo-pink { height: 24px; }
/* Spaced out to the wordmark's own width, as in the mockup. */
.lockup-sub  { color: var(--rail-accent); font-weight: 500; font-size: .6rem;
               line-height: 1; letter-spacing: .34em; margin-right: -.34em; }
/* The sign-in page has the room the header does not — and sits on the THEME's ground,
   not the Midnight bar, so it takes the page's ink. */
.lockup-lg              { gap: .45rem; margin: 0 0 1.4rem; }
.lockup-lg .lockup-name { color: var(--ink); }
.lockup-lg .lockup-name .datum-logo { height: 30px; }
.lockup-lg .lockup-name .logo-pink { height: 64px; }
.lockup-lg .lockup-sub  { color: var(--dim); font-size: .82rem; letter-spacing: .5em; margin-right: -.5em; }

/* --- the pop-out menu (2026-10-01) ------------------------------------------
   Brodie: "can we move the look up, receiving, my shift, account and sign out buttons to
   a pop out menu? they are very crowded right now". The bar is now the lockup, the name
   of the section you are in, and one Menu button; the tabs, your account and Sign out
   open from it. A native <details>, so it needs no JavaScript to work.
   ⚠️ The tab classes inside it (`.tab`, `.tab-on`) are UNCHANGED — the floor suites find
   the lit tab by them — and only their layout moved. (`.tab-off` went 2026-10-01: a tab
   you cannot open is now left out of the menu rather than greyed.) */
.bar-here { color: var(--rail-ink); font-weight: 600; font-size: .95rem; min-width: 0;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
            padding-left: .7rem; border-left: 1px solid var(--rail-line); }
.navmenu { position: relative; margin-left: auto; flex: 0 0 auto; }
.navbtn {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: .55rem; min-height: var(--bar-body);
  padding: 0 .85rem; border: 1px solid var(--rail-line); border-radius: 8px;
  color: var(--rail-ink); font-weight: 600; font-size: .95rem;
}
.navbtn::-webkit-details-marker { display: none; }
.navbtn::marker { content: ""; }
.navmenu[open] > .navbtn { background: var(--rail-active); border-color: var(--rail-accent); }
/* Three bars drawn in CSS, so the icon is the text colour in every theme. */
.navicon { position: relative; width: 18px; height: 2px; border-radius: 1px;
           background: currentColor; box-shadow: 0 -6px 0 currentColor, 0 6px 0 currentColor; }
.navmenu[open] .navicon { box-shadow: none; transform: rotate(45deg); }
.navmenu[open] .navicon::after { content: ""; position: absolute; inset: 0; border-radius: 1px;
                                 background: currentColor; transform: rotate(90deg); }
.navpanel {
  position: absolute; right: 0; top: calc(100% + .5rem); z-index: 6;
  width: min(19rem, calc(100vw - 1.5rem)); padding: .45rem;
  background: var(--rail-panel); border: 1px solid var(--rail-line); border-radius: 12px;
  box-shadow: var(--shadow);
}
/* Scan traveller: the menu's one primary action, so it wears the primary button colours. */
.navscan { display: flex; align-items: center; gap: .65rem; min-height: var(--tap);
           margin-bottom: .45rem; padding: .5rem .85rem; border-radius: 8px;
           background: var(--btn-bg); color: var(--btn-ink); font-weight: 650; font-size: 1.02rem;
           text-decoration: none; }
/* A little barcode, drawn in the text colour so it follows every theme. */
.navscan-icon { width: 20px; height: 15px; flex: 0 0 auto;
                background: repeating-linear-gradient(90deg, currentColor 0 2px, transparent 2px 3px,
                            currentColor 3px 4px, transparent 4px 6px, currentColor 6px 9px, transparent 9px 10px); }
.tabs { display: flex; flex-direction: column; gap: .2rem; }
/* Full-width rows past the tap floor: these get hit by a gloved thumb. */
.tab {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  min-height: var(--tap); padding: .5rem .8rem; border-radius: 8px; text-decoration: none;
  color: var(--rail-ink); font-size: 1.02rem; white-space: nowrap;
}
.tab-on  { color: var(--rail-strong); background: var(--rail-active); font-weight: 650;
           box-shadow: inset 3px 0 0 var(--rail-accent); }
.navfoot { margin-top: .45rem; padding-top: .45rem; border-top: 1px solid var(--rail-line);
           display: grid; gap: .35rem; }
/* The name is the way into your own account (/password: password and screen). */
.bar-who { display: flex; flex-direction: column; justify-content: center;
           min-height: var(--tap); padding: .4rem .8rem; border-radius: 8px;
           color: var(--rail-ink); font-weight: 600; text-decoration: none;
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-who:hover, .bar-who:focus-visible { background: var(--rail-hover); }
.bar-who-sub { color: var(--rail-dim); font-size: .8rem; font-weight: 500; }
.bar-out { width: 100%; background: none; border: 1px solid var(--rail-line); color: var(--rail-ink);
           border-radius: 8px; font-size: .95rem; white-space: nowrap; }

/* --- flashes ------------------------------------------------------------- */
.flash { border-radius: 8px; padding: .75rem 1rem; margin: .6rem 0; font-weight: 550;
         color: var(--ink); border-left-width: 4px; }
.flash-ok    { background: var(--good-bg); border: 1px solid var(--good-line); border-left: 4px solid var(--good); }
.flash-error { background: var(--bad-bg);  border: 1px solid var(--bad-line);  border-left: 4px solid var(--bad); }
/* Something worth doing, not something that failed — "you're running without a first off". */
.flash-warn  { background: var(--warn-bg); border: 1px solid var(--warn-line); border-left: 4px solid var(--warn); }
.flash-warn a { color: inherit; }
/* 🙋 "You have not been marked present" — who to ask, those here today first. */
.presence p { margin: 0 0 .35rem; }
.presence-who { font-weight: 500; }
.presence-tag { display: inline-block; min-width: 6.5rem; color: var(--dim); font-size: .8rem;
                text-transform: uppercase; letter-spacing: .04em; }
.absenceform [hidden], .timeoffform [hidden] { display: none; }
.timeoffform select { min-height: var(--tap); font-size: 1rem; width: 100%; }
/* 🌴 A request on the supervisor's review page. */
.tor-reason { margin: .3rem 0 0; white-space: pre-wrap; }
.tor-decide textarea { min-height: 4rem; }
.tor-decide .absence-acts button { flex: 1 1 8rem; }
/* Edit (a link) and Withdraw (a form) side by side on one of your own reports, the link
   drawn exactly like the plain `button` rule above so the two read as a pair. */
.absence-acts { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .5rem; }
.absence-acts form { margin: 0; }
.absence-acts a.button { display: inline-flex; align-items: center; min-height: var(--tap);
                         border-radius: 8px; border: 1px solid var(--line-2);
                         font-size: 1rem; font-weight: 600; background: var(--panel-2);
                         color: var(--ink); padding: .7rem 1rem; text-decoration: none; }

/* --- login --------------------------------------------------------------- */
.login { max-width: 22rem; margin: 3rem auto; }
.login h1 { font-size: 1.8rem; margin-bottom: 0; }

label, .field { display: block; margin: 1rem 0 .35rem; font-weight: 550; }
input[type=text], input[type=email], input[type=password], input[type=search],
input[type=number], input[type=file] {
  width: 100%; min-height: var(--tap);
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: 8px;
  padding: .7rem .8rem;
  font-size: 1rem;              /* never below 16px — see the iOS note above */
}
input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

button, .big {
  min-height: var(--tap);
  border-radius: 8px; border: 1px solid var(--line-2);
  font-size: 1rem; font-weight: 600;
  background: var(--panel-2); color: var(--ink);
  padding: .7rem 1rem;
}
/* The one primary action on a screen: Midnight in Light (like the docket's rules), the
   mockup's steel-blue in the dark modes. `--btn-*` carries both. */
.big { display: block; width: 100%; margin-top: 1.4rem; border-color: transparent;
       background: var(--btn-bg); color: var(--btn-ink); font-size: 1.1rem; }
a.big { text-align: center; text-decoration: none; }

/* --- home tiles ---------------------------------------------------------- */
/* Sized by what is IN a tile, not by a floor on its height: a label and one line of
   blurb is ~3.9rem at this padding, comfortably past the tap floor, and the old 5rem
   minimum spent the difference on nothing — two fewer tiles per phone screen. */
.tiles { display: grid; gap: .6rem; margin-top: .6rem; }
.tile {
  display: block; text-decoration: none; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 8px; padding: .8rem .95rem; min-height: var(--tap);
}
.tile-label { display: block; font-size: 1.15rem; font-weight: 650; }
.tile-blurb { display: block; color: var(--dim); font-size: .9rem; margin-top: .2rem; }
.tile-off   { opacity: .5; }
.tile-tag   { display: inline-block; margin-top: .5rem; font-size: .75rem;
              text-transform: uppercase; letter-spacing: .05em; color: var(--warn); }

/* --- search + queue ------------------------------------------------------ */
.search { display: flex; gap: .5rem; align-items: stretch; margin: .6rem 0; }
.search input { flex: 1; }
.search button { flex: 0 0 auto; }
.clear { align-self: center; color: var(--dim); font-size: .9rem; }

.lines, .recent, .capture { list-style: none; margin: 0; padding: 0; }
.lines li { margin-bottom: .6rem; }
.lines a {
  display: block; text-decoration: none; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 8px; padding: .85rem;
}
.line-top { display: flex; justify-content: space-between; gap: .6rem; }
.line-mid { display: block; color: var(--dim); font-size: .92rem; margin: .25rem 0; }
.line-bot { display: flex; flex-wrap: wrap; gap: .5rem; font-size: .85rem; }
.vendor { color: var(--dim); text-align: right; }
.qty    { color: var(--ink); }
.due    { color: var(--dim); }
.overdue{ color: var(--warn); font-weight: 600; }

/* Floor confirmation is WEAKER evidence than a JobBoss receipt — nothing posted,
   nothing inspected, possibly uncounted. It gets its own colour so the two can never
   be read as the same fact. */
.seen { color: var(--accent); }

/* --- lookup: search, part detail, work-order detail ----------------------- */

.crumb { margin: 0 0 .4rem; }
.crumb a { color: var(--dim); text-decoration: none; }
.desc { color: var(--dim); margin: 0 0 1rem; }

/* Search hits. A whole card is the tap target, not the words in it — a gloved
   fingertip is nearer 15mm than the 44px floor a stylus assumes. */
.hits { list-style: none; margin: 0; padding: 0; }
.hits li { margin-bottom: .5rem; }
.hits a, .hit-flat {
  display: block; text-decoration: none; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 8px; padding: .8rem; min-height: var(--tap);
}
/* A result with no detail page is deliberately NOT styled as tappable: no border
   highlight, dimmer ground. Nothing here should look like a link that does nothing. */
.hit-flat { background: transparent; border-style: dashed; }
.hit-title { display: block; font-size: 1.05rem; }
.hit-sub   { display: block; color: var(--dim); font-size: .9rem; margin-top: .15rem;
             white-space: pre-line; }
.hit-meta  { display: block; color: var(--dim); font-size: .8rem; margin-top: .3rem; }
/* A PO hit that leads to the capture screen is an ACTION, not navigation, so it is
   marked as one — the accent edge says "this does something" where the other hits just
   go somewhere. Only drawn when capture() would accept the PO; see find(). */
.hits a.hit-act { border-left: 3px solid var(--accent); }
.hit-cta { display: block; margin-top: .35rem; font-size: .85rem; font-weight: 600;
           color: var(--accent); }

/* Drawings. The file name is the tap target and it wraps — customer drawing numbers
   run long and truncating one hides the revision suffix, which is the part that
   matters most on a shop floor. */
.docs { list-style: none; margin: 0; padding: 0; }
.docs li { background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
           padding: .8rem; margin-bottom: .5rem; }
.docs a { color: var(--accent); text-decoration: none; word-break: break-word; }
.doc-meta { display: block; color: var(--dim); font-size: .82rem; margin-top: .35rem; }

/* Provenance, never blended: a vetted JobBoss revision and an engineering-only file
   are different claims, and showing the second AS RELEASED would put a stale revision
   on the floor. */
.prov { display: inline-block; padding: 0 .4rem; border-radius: 4px; font-size: .75rem;
        border: 1px solid currentColor; }
.prov-ok   { color: var(--good); }
.prov-warn { color: var(--warn); }
.prov-dim  { color: var(--dim); }

.facts { display: grid; grid-template-columns: auto 1fr; gap: .3rem .9rem;
         margin: 0 0 1rem; }
.facts dt { color: var(--dim); }
.facts dd { margin: 0; }

.warn { color: var(--warn); background: var(--panel); border: 1px solid var(--warn);
        border-radius: 8px; padding: .8rem; }
.warn-inline { color: var(--warn); }
.ok { color: var(--good); }

/* 3D. A fixed height rather than an aspect ratio: three.js sizes its canvas from the
   container's clientHeight at construction, so a container that is 0px tall when the
   module runs renders nothing at all and looks like a failed load. */
.viewer3d { height: 60vh; min-height: 260px; background: var(--panel-2);
            border: 1px solid var(--line); border-radius: 8px; touch-action: none; }
.viewbar { display: flex; gap: .4rem; margin-top: .5rem; flex-wrap: wrap; }
.viewbar button { flex: 1 1 auto; min-height: var(--tap); }
.viewer-error { padding: 1rem; color: var(--dim); }

/* Work-order material rows. Not `.lines` — that card lives on `.lines a`. */
.matlines { list-style: none; margin: 0; padding: 0; }
.matlines li { background: var(--panel); border: 1px solid var(--line);
               border-radius: 8px; padding: .8rem; margin-bottom: .5rem; }
.line-part  { color: var(--accent); text-decoration: none; font-size: 1.02rem; }
/* The work order's Jobs list: the JOB is the headline (it is what the traveller is
   printed against) and the part rides small in the line below it. */
.line-job   { color: var(--accent); text-decoration: none; font-size: 1.2rem;
              font-weight: 600; font-family: var(--font-mono); }
.line-partref { color: var(--accent); text-decoration: none; font-family: var(--font-mono); }
.jobmeta > span + span::before { content: " · "; }
/* The ops you can clock on to, at the foot of a job card (templates/_job_ops.html).
   Small, as asked, but each link is padded to a fingertip — these are tapped on a tablet
   at a bench, and a .78rem word is not a target. */
.line-ops { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 .9rem;
            margin-top: .3rem; font-size: .78rem; color: var(--dim); }
.line-ops a { color: var(--accent); text-decoration: none; padding: .35rem 0;
              font-family: var(--font-mono); }
/* A sub-job is a COMPONENT of the line above it, not another thing the customer
   bought. Indented so the tree structure is visible without drawing one. */
.matlines li.subjob { margin-left: 1rem; border-left-width: 3px;
                      border-left-color: var(--panel-2); }

/* The lines inside a purchase order card. Nested, so a slip covering several lines of
   one PO reads as one delivery rather than several. */
/* A DXF outline, rendered server-side, standing in for the 3D viewer on a flat.
 *
 * White ground because ezdxf draws in #212830 — near-black — which on this app's dark
 * panel would be invisible: the same "renders but shows nothing" failure as the
 * kilometre-scale 3D model.
 *
 * ⚠️ NO object-fit AND NO max-height. The first version had width:100% with both, and
 * the SVG's intrinsic size is ~10mm (about 40px) with a 1,000,000-unit viewBox — the
 * combination scaled it unpredictably, which is what "rendering weirdly" was. Given a
 * viewBox the browser already knows the aspect ratio, so width plus height:auto is
 * enough and is the one thing that cannot fight it. Height is bounded by aspect, not
 * by a cap that would letterbox a long panel. */
.outline {
  display: block; width: 100%; height: auto;
  background: var(--paper); border: 1px solid var(--line); border-radius: 8px;
  margin: .3rem 0 .2rem; padding: .5rem;
}

/* The BOM tree. Nesting is shown by an indent and a rule rather than by connector
   lines: on a phone the indent is the only thing that survives a narrow column. */
.bomtree { list-style: none; margin: .6rem 0 0 .8rem; padding: 0 0 0 .7rem;
           border-left: 2px solid var(--line); }
.bomtree li { background: var(--panel-2); border: 1px solid var(--line);
              border-radius: 8px; padding: .7rem; margin-bottom: .45rem; }

/* Drawings behind a tap, not printed inline. A weldment with eight flats was eight
   prints deep before its quantities — the tree is for reading structure. */
/* The root of the tree is the part you asked for: named, not linked to itself. */
.line-root { color: var(--ink); }
.bomtree-root > li { background: var(--panel); }

/* Smaller than the tree's own text: a file name is a label you scan, not prose. */
.docs-sm { font-size: .82rem; }
.docs-sm li { padding: .3rem 0; }

/* The engineering-file preference. Quiet — it is a view setting, not a warning. */
.engtoggle { color: var(--dim); font-size: .85rem; margin: .2rem 0 .6rem; }
.engtoggle a { color: var(--accent); }

.docdrop { margin-top: .45rem; }
.docdrop > summary {
  cursor: pointer; color: var(--accent); font-size: .88rem;
  min-height: 2rem; display: flex; align-items: center;
}
.docdrop[open] > summary { margin-bottom: .2rem; }

.polines { list-style: none; margin: .5rem 0 0; padding: 0; }
.polines li { padding: .4rem 0; border-top: 1px solid var(--line); font-size: .9rem; }
.polines a { color: var(--accent); text-decoration: none; }
.polines .qty { float: right; color: var(--ink); }
.polines .line-meta { display: block; clear: both; }

/* The action offered where the question is asked. Full width and past the tap floor:
   this is pressed by someone standing at a skid, not sitting at a desk. */
.pobtn {
  display: block; margin-top: .7rem; min-height: var(--tap);
  display: flex; align-items: center; justify-content: center;
  background: var(--btn-bg); color: var(--btn-ink); font-weight: 600;
  border-radius: 8px; text-decoration: none; padding: .6rem;
}
.line-stage { color: var(--dim); font-size: .85rem; text-align: right; }
.line-desc  { color: var(--dim); font-size: .9rem; margin-top: .2rem; }
.line-meta  { color: var(--dim); font-size: .8rem; margin-top: .3rem; }
/* The floor's own claim, indented behind a rule so it reads as a SECOND source rather
   than more detail about the JobBoss stage above it. They can disagree, and that
   disagreement is the whole point of capturing on the floor. */
.line-floor { margin-top: .45rem; padding-left: .55rem;
              border-left: 2px solid var(--line); font-size: .85rem; }
.pill { display: inline-block; background: var(--panel-2); border: 1px solid var(--line);
        border-radius: 999px; padding: .05rem .5rem; margin-left: .3rem;
        font-size: .78rem; color: var(--dim); }

.recent li { border-bottom: 1px solid var(--line); padding: .55rem 0;
             font-size: .9rem; color: var(--dim); }
.recent strong { color: var(--ink); }
.when { display: block; font-size: .78rem; }
.tag  { display: inline-block; border: 1px solid var(--line); border-radius: 6px;
        padding: 0 .35rem; font-size: .72rem; text-transform: uppercase; }
.tag-counted { border-color: var(--good); color: var(--good); }

/* --- capture: the photo control ------------------------------------------ */
/* The native file input is a small grey "Choose File" — the wrong target for a
   gloved thumb, and it gives no clue a camera is involved. So the input is hidden
   and a <label for> is styled as the button; clicking a label activates its input,
   which is plain HTML and needs no JavaScript to work.
   ⚠️ `visually-hidden` rather than display:none — a display:none input is skipped by
   some browsers' form handling and unreachable to a screen reader, and this way the
   label still drives it. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.shotbtn {
  display: flex; align-items: center; justify-content: center; gap: .7rem;
  min-height: 5.5rem;                     /* far above the 48px floor: this is the
                                             one control everyone touches, outdoors,
                                             in gloves, often in a hurry */
  margin: .4rem 0 0;
  border: 2px dashed var(--accent); border-radius: 8px;
  background: var(--panel);
  color: var(--accent); font-size: 1.15rem; font-weight: 650;
  cursor: pointer;
}
.shotbtn:active { background: var(--panel-2); }
.shotbtn-icon { font-size: 1.7rem; line-height: 1; }

.shotalt {
  display: block; margin: .6rem 0 0; text-align: center;
  color: var(--dim); font-size: .9rem; font-weight: 400;
  text-decoration: underline; cursor: pointer; min-height: var(--tap);
  line-height: var(--tap);
}

/* Nothing on screen changes when the camera closes, so a taken photo and a cancelled
   one look identical until submit. This is the difference. */
.chosen {
  margin: .5rem 0 0; padding: .55rem .7rem;
  background: var(--panel-2); border: 1px solid var(--good); border-radius: 8px;
  color: var(--good); font-size: .9rem; word-break: break-all;
}

/* The same control used as a link on the queue and scan screens. */
a.shotbtn { text-decoration: none; }
/* Once a scan has supplied the photo, retaking it is optional — so the button stops
   competing with the confirm button for attention. */
.shotbtn-quiet {
  min-height: var(--tap); border-style: solid; border-color: var(--line);
  color: var(--dim); font-size: .95rem; font-weight: 500;
}
.shotbtn-quiet .shotbtn-icon { font-size: 1.1rem; }

/* "Read the slip for me" on the capture screen. Full width so it reads as the next
   step after the photo, but panel-coloured rather than accent — Confirm is the only
   accent button on the page and it should stay the one thing that finishes the job. */
.readbtn {
  display: block; width: 100%; margin-top: .8rem;
  border-color: var(--line); color: var(--ink);
}

/* --- what the scan read --------------------------------------------------- */
/* 🔑 Coloured BY CONFIDENCE, and confidence comes from corroboration (is the PO open,
   does the vendor match, are the parts outstanding) rather than from the model saying
   it is sure. A low-confidence match still gets shown — it is a lead worth checking,
   not an answer — but it must not look like a high one. */
.matched {
  border-radius: 8px; padding: .85rem 1rem; margin: .2rem 0 1rem;
  border: 1px solid var(--line); background: var(--panel);
  border-left-width: 5px;
}
.matched-high   { border-left-color: var(--good); }
.matched-medium { border-left-color: var(--accent); }
.matched-low    { border-left-color: var(--warn); }
/* Nothing corroborated the slip against the PO a person chose, so nothing was filled
   in. Warn-coloured like `low`, because an unscored read and a weak one both mean
   "trust the paper, not this box" — and an uncoloured panel would read as neutral. */
.matched-none   { border-left-color: var(--warn); }
.matched-head { margin: 0 0 .3rem; font-size: .82rem; text-transform: uppercase;
                letter-spacing: .05em; color: var(--dim); }
.matched-body { margin: 0 0 .4rem; }
.matched-why  { margin: .3rem 0 .5rem; padding-left: 1.1rem; font-size: .86rem;
                color: var(--dim); }
.matched-why li { margin: .15rem 0; }
.matched-alt  { margin: 0; font-size: .88rem; color: var(--dim); }

/* A quantity the slip actually stated, as opposed to the outstanding default. The
   two are different claims and the person is checking them against paper. */
.capture-qty-read { border-color: var(--accent); }

/* The slip stated a quantity and we refused to convert it. Left EMPTY on purpose —
   an outstanding-quantity default here is a claim the app has no evidence for. */
.capture-qty-blocked { border-color: var(--warn); background: var(--panel-2); }

/* 🔴 The line is not ordered in pieces. Steel is bought by weight and delivered by
   the sheet, so this is where a piece count would silently go into a pounds column. */
.unitwarn {
  display: block; margin-top: .3rem; font-size: .8rem; font-weight: 600;
  color: var(--warn);
}

/* Already received. The one no-match branch where the right action is to STOP, so it
   is coloured like a stop rather than like an apology. */
.warn-head { color: var(--warn); }

.readout { background: var(--panel); border: 1px solid var(--line);
           border-radius: 8px; padding: .9rem 1rem; margin-bottom: .8rem; }
.readout h2 { margin-top: 0; }
.readout dl { margin: 0; display: grid; grid-template-columns: auto 1fr;
              gap: .35rem .8rem; }
.readout dt { color: var(--dim); font-size: .85rem; }
.readout dd { margin: 0; word-break: break-word; }
.why p { margin: .3rem 0; color: var(--dim); font-size: .9rem; }

/* --- capture: the rest ---------------------------------------------------- */
.back { color: var(--dim); text-decoration: none; font-size: .9rem; }
.toggle { display: flex; align-items: center; gap: .65rem; min-height: var(--tap);
          margin-top: 1.1rem; font-weight: 550; }
.toggle input { width: 1.4rem; height: 1.4rem; accent-color: var(--good); }

.capture li {
  display: flex; gap: .8rem; align-items: center;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 8px; padding: .8rem; margin-bottom: .6rem;
}
.capture-part { flex: 1; min-width: 0; }
.capture-part span { display: block; color: var(--dim); font-size: .88rem; }
.capture-part .meta { font-size: .8rem; margin-top: .2rem; }
/* The quantity box and the PO's own unit, together. The unit used to live only in the
   text above; a person about to type is looking at the FIELD, so it belongs beside it.
   The box shrinks to make room rather than the unit wrapping underneath. */
.qtybox { flex: 0 0 auto; display: flex; align-items: center; gap: .4rem; }
.capture-qty { flex: 0 1 5.5rem; width: 5.5rem; text-align: right; font-size: 1.15rem;
               font-variant-numeric: tabular-nums; }
.qtyunit { color: var(--dim); font-size: .85rem; min-width: 1.6rem; }

.denied { text-align: center; margin-top: 2.5rem; }

/* (The `prefers-color-scheme: light` override that lived here is gone: Light, Dark and
   Black are all defined in /brand/theme.css, and "follow the device" is its default.) */

/* --- receiving as an AGENDA, not a month grid -----------------------------
 * Brodie 2026-09-01: the calendar view makes more sense for receiving, but a 7x5
 * grid on a phone gives ~50px cells that fit a number and nothing else — and it
 * cannot show a late line at all, because a late line has no future square to sit
 * on. Half this queue is late (130 of 261 lines, 75 POs), so that is not a corner
 * case, it is the main thing somebody is looking for.
 *
 * So: one vertical scroll, a sticky header per day. The date structure is the
 * headers rather than a grid, and nothing needs tapping to be read.
 */
.agenda { margin: 0 0 1.4rem; }

/* 🔑 `top` MUST CLEAR THE APP BAR, which is itself sticky at top:0 (see .bar). Two
   sticky elements at the same offset overlap, and the day header would slide under
   the bar and vanish exactly when it is doing its job.
   ⚠️ SO THE OFFSET IS THE BAR'S OWN ARITHMETIC, NOT A MEASURED CONSTANT — the same
   max(padding, safe-area-inset) the bar itself uses, plus its body and its bottom
   padding. A fixed 3.4rem was the first draft and it is wrong on every notched
   phone, where the bar grows by the inset and a constant cannot follow it.
   z-index sits BELOW the bar's 5, so if they ever do meet the bar wins. */
.dayhead {
  position: sticky; z-index: 4;
  top: calc(max(var(--bar-pad), env(safe-area-inset-top, 0px))
            + var(--bar-body) + var(--bar-pad));
  display: flex; justify-content: space-between; align-items: baseline; gap: .6rem;
  margin: 0 0 .5rem; padding: .45rem .6rem;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
  font-size: .95rem;
}
.dayname  { color: var(--ink); font-weight: 600; }
.daycount { color: var(--dim); font-size: .85rem; white-space: nowrap; }

/* Overdue is the one section that is not a date, and it reads as a warning rather
   than an error: a late delivery is normal here, and the receiving backlog this app
   exists to fix means "unreceived" does not even reliably mean "not here". */
.agenda-late .dayhead { border-color: var(--warn); }
.agenda-late .dayname { color: var(--warn); }
.dayhint { color: var(--dim); font-size: .82rem; margin: -.2rem 0 .55rem .2rem; }

/* A PO's lines stack inside its card; the description is secondary to the part
   number and the quantity, so it dims and truncates rather than wrapping to three
   lines on a phone. */
.partdesc { color: var(--dim); display: block; font-size: .82rem;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The rest of the overdue block. A disclosure, so it costs no JavaScript — and its
   summary is a full-width tap target like everything else on this screen. */
.moredue > summary {
  display: block; min-height: var(--tap); line-height: var(--tap);
  text-align: center; color: var(--accent);
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  cursor: pointer; list-style: none;
}
.moredue > summary::-webkit-details-marker { display: none; }
.moredue[open] > summary { margin-bottom: .6rem; }

/* --- scanning a Job Traveler ---------------------------------------------
 *
 * The live camera preview. A fixed height rather than an aspect ratio for the same
 * reason .viewer3d has one: the element is sized before the stream arrives, and a
 * 0px-tall box shows nothing while looking like a failed load.
 *
 * ⚠️ object-fit: cover, because a phone hands back whatever aspect its sensor likes and
 * a letterboxed preview makes people hold the barcode in the black part of the frame. */
.camview { width: 100%; height: 46vh; min-height: 220px; background: #000;
           border: 1px solid var(--line); border-radius: 8px; object-fit: cover; }

/* --- the routing on a job page -------------------------------------------
 *
 * 🔑 THE SCANNED STEP IS MARKED, NEVER ISOLATED. An operator needs to see what happens
 * before and after them — on a real job the row after the one they scanned is the paint
 * that has not been bought yet. So this is an accent edge on one card in a full list,
 * not a filter.
 *
 * ⚠️ A LEFT BORDER RATHER THAN A BACKGROUND FILL. `.matlines li.subjob` already uses the
 * left edge to mean "component of", and those two can land on one page; a fill would
 * compete with the pills instead of with the indent. */
.routing li.step-scanned { border-color: var(--accent);
                           box-shadow: inset 3px 0 0 var(--accent); }
/* 🔑 THE WHOLE ROUTING CARD OPENS ITS STEP (templates/job.html, `step-link`). The link's
   ::after is stretched over the card, so a tap anywhere on it lands on the step page;
   the note's expander is lifted above that layer so it still opens the note instead. */
.routing li { position: relative; }
.routing .step-link::after { content: ""; position: absolute; inset: 0; border-radius: 8px; }
.routing .notedrop { position: relative; z-index: 1; }

/* Pills carry a STATE and its evidence. Deliberately quiet by default — the base .pill
 * is already dim — because most of what JobBoss knows about a routing step is a pointer
 * rather than a fact, and colour would read as progress. Only the two that ARE facts
 * (something came back; something is on the job) get the ok colour. */
.pill-back, .pill-done { border-color: var(--good); color: var(--good); }
.pill-ok   { border-color: var(--good); color: var(--good); }
.pill-warn { border-color: var(--warn); color: var(--warn); }
/* The pointer is the one that most looks like progress and least is one: JobBoss moves
 * exactly one per job and 0 of 518 open ones carry a start date. Dim, and it says what
 * it is in words. */
.pill-pointer { border-color: var(--line); color: var(--dim); }
.pill-scanned { border-color: var(--accent); color: var(--accent); }

/* --- an expandable routing note ------------------------------------------
 *
 * 🔴 A ROUTING NOTE IS A WORK INSTRUCTION. It used to be truncated at 160 characters,
 * which on 262599-01's weld step cut "they are grade A surfaces. this means no ding|s
 * or burn threw." — keeping the generic half and losing the quality requirement.
 * Measured: 695 of 13,602 notes are longer than that and 670 contain line breaks.
 *
 * ⚠️ THE SUMMARY IS ONE LINE, ELLIPSISED BY CSS AS WELL AS BY THE PREVIEW. The preview
 * is already capped at 150 characters, but a narrow phone still wraps that to three
 * lines and the collapsed state stops looking collapsed. Clipping here keeps every
 * closed row the same height, which is what makes a list of steps scannable.
 *
 * ⚠️ The marker is kept (no `list-style: none`), because the disclosure triangle is the
 * only thing telling somebody there IS more. A summary that looks like ordinary text is
 * a control nobody finds. */
.notedrop { margin: .25rem 0; }
.notedrop summary {
  color: var(--dim); font-size: .92rem; cursor: pointer;
  /* Comfortably past the 44px tap floor once the row's own padding is counted. */
  padding: .3rem 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.notedrop[open] summary { white-space: normal; color: var(--ink); }

/* 🔑 pre-wrap, NOT pre: honour the author's line breaks AND wrap long lines to the
 * tablet. `pre` would give a 3,297-character assembly procedure a horizontal scrollbar
 * on a device nobody scrolls sideways on. */
.notefull {
  white-space: pre-wrap; overflow-wrap: anywhere;
  color: var(--ink); font-size: .92rem; line-height: 1.45;
  margin: .35rem 0 .2rem; padding: .55rem .7rem;
  background: var(--panel-2); border-radius: 8px;
}

/* --- quality: the operation picker and the report form -------------------
 *
 * A checkbox with its own body beside it, so the whole ROW is the tap target rather
 * than a 20px box. These get tapped with gloves on.
 * ⚠️ align-items: flex-start, not center: a row can wrap to three lines on a phone and
 * a centred checkbox then floats away from the label it belongs to. */
.pickrow { display: flex; gap: .7rem; align-items: flex-start;
           padding: .2rem 0; cursor: pointer; }
.pickrow input[type="checkbox"] { width: 26px; height: 26px; flex: 0 0 auto;
                                  margin-top: .15rem; }
.pickbody { flex: 1 1 auto; min-width: 0; }

/* The type / method / result groups. 🔑 A GRID RATHER THAN A LIST, because these are
 * short mutually-visible options and a person should be able to see all four kinds of
 * check at once — the whole reason the type is now mandatory is that it used to be
 * inferred from what somebody did NOT tick. */
.checkgrid { list-style: none; margin: .3rem 0 .6rem; padding: 0;
             display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
             gap: .4rem; }
.checkgrid label { display: flex; align-items: center; gap: .55rem;
                   min-height: var(--tap); padding: .35rem .6rem;
                   background: var(--panel); border: 1px solid var(--line);
                   border-radius: 8px; cursor: pointer; }
.checkgrid input { width: 24px; height: 24px; }

/* The operator roster: 55 names, filtered, ticked one at a time.
 *
 * This replaced a native <select multiple>, whose comment here claimed it was "the only
 * control that works reliably for multi-pick on both Android and iOS without shipping a
 * widget". Reliable at REGISTERING a tap, yes - but one stray tap replaces the whole
 * selection instead of adding to it, so on a tablet it silently discards work. Ticking
 * 55 checkboxes needs no widget either.
 * The scroll cap is deliberate: the list must not push Notes and Photos off the screen,
 * because a form people never scroll to the bottom of is a form with empty fields. */
.rosterpick { border: 1px solid var(--line); border-radius: 8px;
              background: var(--panel); padding: .45rem; }
.rosterfind { display: flex; gap: .4rem; align-items: center; }
.rosterfind input[type="search"] {
    flex: 1 1 auto; min-width: 0; min-height: var(--tap);
    background: var(--panel-2); color: var(--ink); border: 1px solid var(--line);
    border-radius: 8px; padding: .45rem .6rem; font-size: 1rem; font-family: inherit; }
/* A tap target, not a glyph: this sits beside a text box on a touch screen. */
.rosterclear { min-width: var(--tap); min-height: var(--tap); flex: 0 0 auto;
               background: var(--panel-2); color: var(--ink); border: 1px solid var(--line);
               border-radius: 8px; font-size: 1.3rem; line-height: 1; cursor: pointer; }
.rostercount { margin: .4rem .2rem .1rem; }
.rosterlist { list-style: none; margin: .35rem 0 0; padding: 0;
              max-height: 15rem; overflow-y: auto;
              -webkit-overflow-scrolling: touch; }
.rosterlist li + li { margin-top: .3rem; }
.rosterlist .chk { display: flex; align-items: center; gap: .55rem;
                   min-height: var(--tap); padding: .3rem .5rem;
                   background: var(--panel-2); border: 1px solid var(--line);
                   border-radius: 8px; cursor: pointer; }
.rosterlist .chk input { width: 24px; height: 24px; flex: 0 0 auto; }
/* A ticked name is highlighted, because it stays visible while the filter hides the
 * rest - without this it reads as a search result that did not match.
 * ⚠️ Driven by a CLASS the script sets, not by :has(input:checked). The highlight is the
 * only thing distinguishing "you picked this" from "this happens to match your search",
 * so it must not depend on a selector an older shop tablet's WebView may not support -
 * and :has() fails SILENTLY, leaving a correct-looking list that means something else. */
.rosterlist li.on .chk { border-color: var(--accent); background: var(--panel); }
textarea { width: 100%; background: var(--panel); color: var(--ink);
           border: 1px solid var(--line); border-radius: 8px; padding: .6rem;
           font-size: 1rem; font-family: inherit; }
input[type="datetime-local"], input[type="time"], input[type="date"] {
           background: var(--panel); color: var(--ink);
           border: 1px solid var(--line); border-radius: 8px; padding: .6rem;
           font-size: 1rem; min-height: var(--tap); }
/* 🕐 A reminder's own time on Settings, indented under its tick box so it reads as part of
   that row. `label` is display:block site-wide, hence the reset. */
.remindtime { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
              margin: 0 0 .6rem 2.05rem; }
.remindtime label { display: inline; margin: 0; font-weight: 500; }
.remindtime .hint { margin: 0; }

/* --- "Something wrong on this page?" ------------------------------------- */
/* In the foot of every page, because a report is worth most from the screen the
   problem is on and /feedback prefills the page you came from. Deliberately quiet:
   dim, small, and separated by space rather than a border — it is the least important
   thing on the page until it is the only thing that matters. */
.foot-report { margin: 2.5rem 0 .5rem; text-align: center; font-size: .82rem; }
.foot-report a { color: var(--faint); text-decoration: none; }
.foot-report a:hover, .foot-report a:focus-visible {
  color: var(--dim); text-decoration: underline;
}

/* --- quality requests, operator checks, per-operation report sections (2026-09-30) --- */
/* A plain boxed note. `.warn` already existed for the amber version; this is the neutral
   one the request and answering notes use. */
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
         padding: .8rem; margin: .6rem 0; }
/* The count on the Quality tab: requests waiting on YOU. Amber rather than red — a
   request is work to pick up, not a fault. */
.tabbadge { display: inline-block; min-width: 1.3em; margin-left: .35rem;
            padding: 0 .35rem; border-radius: 999px; background: var(--rail-badge-bg);
            color: var(--rail-badge-ink); font-size: .78rem; font-weight: 700; text-align: center;
            line-height: 1.35; }
/* One section per operation on Joram's report. The border is what makes "which
   operation am I answering for" visible while scrolling past the roster lists. */
.opsection { border: 1px solid var(--line); border-left: 4px solid var(--accent);
             border-radius: 8px; padding: .2rem .9rem .9rem; margin: 1.1rem 0;
             background: color-mix(in srgb, var(--panel) 45%, transparent); }
.opsection-head { margin-top: .7rem; }
.opsection h3 { font-size: 1rem; margin: 1rem 0 .3rem; }
.big.secondary { background: var(--panel-2); color: var(--ink);
                 border: 1px solid var(--line); }
/* A small action that must not look like the primary button beside it. */
button.linkish { background: none; border: 0; padding: .3rem 0; min-height: 0;
                 color: var(--accent); font-weight: 500; text-decoration: underline;
                 cursor: pointer; }
.reqlist { list-style: none; margin: 0; padding: 0; }
.reqlist li { margin-bottom: .6rem; }
.reqlist a { display: block; text-decoration: none; color: var(--ink);
             background: var(--panel); border: 1px solid var(--line);
             border-left: 4px solid var(--warn); border-radius: 8px; padding: .7rem .8rem; }
.reqlist a.mine { border-left-color: var(--accent); }

/* --- time on operations (2026-09-30) --------------------------------------------- */
/* The clock sits at the top of the step page: it is the first thing an operator does
   after scanning the docket's step QR. */
.clockpanel { background: var(--panel); border: 1px solid var(--line);
              border-left: 4px solid var(--good); border-radius: 8px;
              padding: .6rem .9rem .9rem; margin: .8rem 0 1rem; }
.clockpanel .big { margin-top: .7rem; }
.clocknow { margin: .2rem 0; }
/* Setup and Run side by side: two equal, obviously different choices. */
.clockbtns { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; }
/* Clocking off: "back to it later" or "step finished" — stacked, because the second label
   is long and a glove needs the whole width. */
.clockoffbtns { display: grid; gap: .6rem; }
/* ⏸️ Pausing (2026-10-05): folded away until wanted, so the clock-off stays the obvious
   action; open, the reasons sit two across — five one-tap buttons a glove can hit. A paused
   job turns the whole panel amber, so "you are paused" cannot be missed on the way back. */
.pausepanel { margin-top: .8rem; border-top: 1px solid var(--line); padding-top: .6rem; }
.pausepanel summary { cursor: pointer; font-weight: 600; color: var(--dim); }
.pausepanel[open] summary { color: var(--ink); }
.pausebtns { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; margin-top: .5rem; }
.pausebtns .big { margin-top: 0; }
.pausepanel.is-paused { border-top: 0; padding: .6rem .8rem .8rem; border-radius: 8px;
                        background: var(--warn-bg); border: 1px solid var(--warn-line); }
/* ⏸️↪ A job left WAITING while you are on something else (2026-10-07): the clock panel's
   shape, edged amber like a pause, so it reads as "come back to this", not "you are on it". */
.clockpanel.is-waiting { border-left-color: var(--warn); }
.pieces { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; margin-top: .6rem; }
.pieces label { display: flex; flex-direction: column; gap: .25rem; font-size: .9rem;
                color: var(--dim); }
/* "So far on this step", who else is on it and the warning line span both boxes (2026-10-07). */
.pieces > p { grid-column: 1 / -1; margin: 0; }
/* 🏆 The leaderboard (2026-10-07): your own row edged in the accent, like the lit theme
   swatch — marked, not filled, so it reads as "you" rather than as first place. */
.board .board-me { border-left: 3px solid var(--accent); padding-left: .5rem; }

/* --- the theme picker and the sign-in foot (2026-09-30) -------------------------- */
/* Three equal buttons, each a swatch of that mode's ground plus its name — big enough for
   a glove, like every other control here. The lit one is edged in the accent rather than
   filled, so it cannot be mistaken for the page's primary action. */
.themepick { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .5rem; }
.themepick a {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  min-height: var(--tap); padding: .4rem; border-radius: 8px;
  border: 1px solid var(--line-2); background: var(--panel); color: var(--ink);
  font-weight: 600; text-decoration: none;
}
.themepick a.on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.themepick .swatch { width: 14px; height: 14px; }
.login-foot { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--line);
              display: flex; justify-content: center; }
.login-foot img { display: block; width: 160px; height: auto; opacity: .9; }

/* --- page tabs: a work order, a job, a part (2026-10-01) -------------------------
   Johith via Brodie: "pages for WOs/jobs/parts are very dense vertically and pages can be
   long. it would be helpful to split the page into tabs". Each tab is a real link that
   loads ONLY its own data (lookup.WO_TABS / JOB_TABS / PART_TABS).
   Sticky under the bar, so on a long routing the other tabs are still one tap away; the
   offset is the bar's own arithmetic, the same as the agenda's day header. */
.pagetabs {
  position: sticky; z-index: 3;
  top: calc(max(var(--bar-pad), env(safe-area-inset-top, 0px)) + var(--bar-body) + var(--bar-pad));
  display: flex; gap: .15rem; margin: .5rem -1rem 1rem; padding: 0 1rem;
  background: var(--bg); border-bottom: 1px solid var(--line);
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.pagetabs::-webkit-scrollbar { display: none; }
.pagetab {
  flex: 0 0 auto; display: flex; align-items: center; min-height: var(--tap);
  padding: 0 .85rem; margin-bottom: -1px; border-bottom: 3px solid transparent;
  color: var(--dim); font-weight: 600; text-decoration: none; white-space: nowrap;
}
.pagetab.on { color: var(--ink); border-bottom-color: var(--accent); }

/* The work order as a tree: indented by depth (lookup._wo_lines), the current job marked. */
.wotree li.subjob { margin-left: calc(var(--depth, 1) * .9rem); }
.wotree li.tree-here { border-color: var(--accent); box-shadow: inset 4px 0 0 var(--accent); }

/* --- the loading indicator (2026-10-01) -------------------------------------------
   Johith: "the app can take a second to load, adding a loading spinner thing would make
   the page not look unresponsive after the first click". Shown by the script in base.html
   only if the next page has not arrived after 150 ms, so a fast tap never flickers: a slim
   bar sweeping under the top edge, and a small "Loading…" pill. */
.navloading {
  position: fixed; left: 0; right: 0; top: 0; z-index: 50; height: 3px;
  pointer-events: none; opacity: 0; transition: opacity .15s;
  background: linear-gradient(90deg, transparent, var(--rail-accent), transparent)
              no-repeat; background-size: 40% 100%;
}
.navspinner {
  position: fixed; left: 50%; z-index: 50; transform: translateX(-50%);
  top: calc(max(var(--bar-pad), env(safe-area-inset-top, 0px)) + var(--bar-body) + var(--bar-pad) + .6rem);
  display: none; align-items: center; gap: .55rem; padding: .45rem .95rem;
  border-radius: 999px; background: var(--rail-panel); color: var(--rail-ink);
  box-shadow: var(--shadow); font-weight: 600; font-size: .9rem; pointer-events: none;
}
.navspinner::before {
  content: ""; width: 16px; height: 16px; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid var(--rail-line); border-top-color: var(--rail-accent);
  animation: navspin .7s linear infinite;
}
html.is-loading .navloading { opacity: 1; animation: navsweep 1s linear infinite; }
html.is-loading .navspinner { display: flex; }
@keyframes navsweep { from { background-position: -40% 0; } to { background-position: 140% 0; } }
@keyframes navspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  html.is-loading .navloading, .navspinner::before { animation: none; }
}



/* ============================================================ chat + tasks (the dock), 2026-10-09
   The pill bottom-right, exactly as Datum draws it (ui/static/datum.css) - Brodie: "a bubble on
   the bottom right, just like in datum". It sat in the top bar for an hour and crowded the page
   title. The panels own rules are dock/static/chat.css, fetched on first open, and already
   anchor the panel and the toasts above a bottom-right pill, so nothing here overrides them.
   Midnight like the bar, in every theme - a piece of the brand bar floating over the page. */
.chatdock {
  position: fixed; z-index: 58;
  right: max(16px, env(safe-area-inset-right, 0px)); bottom: max(16px, env(safe-area-inset-bottom, 0px));
  display: flex; align-items: stretch; height: 44px; border-radius: 22px; overflow: hidden;
  background: var(--rail-bg); border: 1px solid var(--rail-line); box-shadow: var(--shadow);
}
.dock-btn {
  width: 52px; border: 0; background: none; color: var(--rail-accent); cursor: pointer;
  display: grid; place-items: center; position: relative; padding: 0;
  /* This file's own `button` rule gives every button a 48 px minimum height; inside the 44 px
     pill that pushed each icon two pixels low (Brodie: off centre). The pill is the height. */
  min-height: 0; height: 100%;
}
.dock-btn + .dock-btn { border-left: 1px solid var(--rail-line); }
.dock-btn:hover, .dock-btn[aria-expanded="true"] { background: var(--rail-active); color: var(--rail-strong); }
.dock-btn.loading { opacity: .6; }
.dock-badge {
  position: absolute; top: 7px; right: 9px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; display: grid; place-items: center;
  background: var(--rail-badge-bg); color: var(--rail-badge-ink);
  font-family: var(--font-mono); font-size: 10px; font-weight: 700; line-height: 1;
  border: 2px solid var(--rail-bg);
}
.dock-badge[hidden] { display: none; }
/* "N tasks about this page" / "N messages about this page" - chips beside the pill (chat.js). */
.dockchips {
  position: fixed; z-index: 58; display: flex; gap: 8px;
  right: calc(max(16px, env(safe-area-inset-right, 0px)) + 120px);
  bottom: calc(max(16px, env(safe-area-inset-bottom, 0px)) + 6px);
}
.dockchips:empty { display: none; }
.dock-about {
  height: 32px; padding: 0 12px 0 10px; border-radius: 16px; border: 1px solid var(--rail-line);
  background: var(--rail-bg); color: var(--rail-ink); font: inherit; font-size: 12px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px; box-shadow: var(--shadow); white-space: nowrap;
}
.dock-about b { font-family: var(--font-mono); color: var(--rail-badge-bg); }
.dock-about[hidden] { display: none; }
/* A chat, task or tasks page inside this app's frame. */
.dock-page { margin-top: 1rem; }
.tk-pagelist, .tk-pagedetail { margin-top: .8rem; }
h1 small.hint { font-size: .55em; font-weight: 500; margin-left: .4rem; }
@media print { .chatdock, .chatpanel, .chattoasts, .dockchips { display: none !important; } }
