/* print.css — the printed work-order traveler.

   These styles are deliberately NOT wrapped in @media print. The sheet renders
   identically on screen and on paper, which is what makes the on-screen preview
   trustworthy and lets the design be reviewed from a screenshot. @media print in
   index.html only decides which of #app / #printroot is visible.

   Design constraints (set with Simon, 2026-07-21):
   - Shop-traveler look: ruled boxes, initial+date per step, everything griddable.
   - B&W laser first. Every distinction must survive grayscale, so meaning is
     carried by rule weight, hatching and layout — never by hue alone. Berkeley
     blue/gold appear only as enhancement on a colour printer.
   - Boxes are sized to be WRITTEN IN. Minimum cell heights below are floors for
     a ballpoint pen in a cold shop, not typographic preferences. Don't shrink
     them to save paper.
   - Every list section ends in blank ruled rows so work nobody anticipated at
     the desk can be added at the bench.

   Units are pt/in throughout: this is a paper document, not a screen. */

.wsheet {
  --ink: #000;
  --hair: 0.75pt solid #000;   /* ordinary cell rule            */
  --heavy: 1.75pt solid #000;  /* section + blocker emphasis    */
  --gold: #FDB515;
  --shade: #e9e9e9;            /* table headers; ~8% grey prints clean */
  font: 9.5pt/1.28 Arial, Helvetica, sans-serif;
  color: var(--ink);
  background: #fff;
}
.wsheet * { box-sizing: border-box; }

/* Pagination spacers for a FLOWING sheet (the traveler spans two physical
   pages as one element). @page prints with margin 0 so the browser has no
   band to draw its URL/date chrome into; a table's header and footer groups
   repeat on every page it spans, so these spacer rows are the vertical page
   margins on every page. Zero on screen: the .ws-page padding draws the
   preview margins there. */
.pgflow { width: 100%; border-collapse: collapse; }
.pgflow > thead > tr > td, .pgflow > tfoot > tr > td { height: 0; padding: 0; }
.pgflow > tbody > tr > td { padding: 0; vertical-align: top; }
@media print {
  .pgflow > thead > tr > td, .pgflow > tfoot > tr > td { height: 0.45in; }
}

/* Page shell. On screen this draws a Letter page; in print the element's own
   padding is the page margin (@page prints at margin 0 — see index.html). */
.ws-page {
  width: 8.5in;
  min-height: 11in;
  padding: 0.45in;
  margin: 0 auto 18px;
  background: #fff;
  position: relative;
}

/* ---------- masthead ---------- */
/* Solid black bar rather than a gold one: a laser renders it crisply, and the
   gold underrule below carries the team colour when colour is available. */
.ws-head { display: flex; align-items: stretch; border: var(--heavy); }
.ws-head .brand {
  background: #000; color: #fff;
  padding: 5pt 9pt; font-weight: 700; font-size: 11pt;
  letter-spacing: .06em; white-space: nowrap;
  display: flex; align-items: center; gap: 6pt;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.ws-head .brand .sub { font-weight: 400; font-size: 7.5pt; letter-spacing: .14em; opacity: .85; }
.ws-head .idblock { margin-left: auto; display: flex; }
.ws-head .idcell {
  border-left: var(--hair); padding: 3pt 8pt; min-width: 0.85in;
  display: flex; flex-direction: column; justify-content: center;
}
.ws-head .idcell .lab { font-size: 6pt; letter-spacing: .1em; text-transform: uppercase; }
.ws-head .idcell .val { font-size: 12pt; font-weight: 700; line-height: 1.1; }
.ws-head .idcell.wide .val { font-size: 9pt; font-weight: 700; }
.ws-rule {
  height: 3.5pt; background: var(--gold);
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.ws-sheetkind {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 7pt; letter-spacing: .09em; text-transform: uppercase;
  padding: 2.5pt 1pt 0;
}

/* ---------- section headings ---------- */
.ws-h {
  margin: 11pt 0 0; padding: 2pt 0 1.5pt;
  border-bottom: var(--heavy);
  font-size: 8.5pt; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  display: flex; align-items: baseline; gap: 7pt;
  break-after: avoid;
}
.ws-h .hint { font-weight: 400; letter-spacing: .02em; text-transform: none; font-size: 7.5pt; }
.ws-h .hint::before { content: "· "; }

/* ---------- labelled field boxes ---------- */
/* Label sits ABOVE the writing area so the full cell width stays writable and a
   printed value can be struck through and rewritten beside it. */
.ws-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border-top: var(--hair); border-left: var(--hair);
}
.ws-grid.c3 { grid-template-columns: repeat(3, 1fr); }
.ws-f {
  border-right: var(--hair); border-bottom: var(--hair);
  padding: 2pt 5pt 0; min-height: 0.38in;
  display: flex; flex-direction: column;
}
.ws-f.span2 { grid-column: span 2; }
.ws-f .lab { font-size: 6pt; letter-spacing: .09em; text-transform: uppercase; }
.ws-f .val {
  font-size: 10pt; line-height: 1.15; flex: 1;
  display: flex; align-items: center; min-height: 0.2in;
  word-break: break-word;
}
/* A date the app doesn't know: pre-print the separators so it's obvious how to
   write it and the format stays consistent across the team. */
.ws-f .val.date::before { content: "     /        /"; letter-spacing: .06em; color: #777; }
.ws-f .val.filled.date::before { content: none; }

/* ---------- tables ---------- */
.ws-t { width: 100%; border-collapse: collapse; margin-top: 0; }
.ws-t th {
  border: var(--hair); background: var(--shade);
  padding: 2.5pt 5pt; text-align: left;
  font-size: 6.5pt; letter-spacing: .08em; text-transform: uppercase;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.ws-t td { border: var(--hair); padding: 3pt 5pt; vertical-align: top; font-size: 9pt; }
.ws-t tr { break-inside: avoid; }
.ws-t thead { display: table-header-group; }   /* repeat header across pages */
.ws-t td.num, .ws-t th.num { text-align: center; width: 0.3in; }
.ws-t .initial { width: 0.72in; }
.ws-t .datec { width: 0.95in; }
.ws-t .idc { width: 1.05in; font-variant-numeric: tabular-nums; }

/* ---------- the weekly status board ----------
   A report rather than a form, so it borrows the traveler's furniture and
   changes two things: a count is the biggest thing in its box, and the R&D
   mark is an outline rather than a fill so it survives a laser and a
   photocopy. */
.ws-f .val.big { font-size: 15pt; font-weight: 700; line-height: 1.15; }
.ws-rnd {
  font-size: 6.5pt; font-weight: 700; letter-spacing: .08em;
  border: var(--hair); padding: 0 2.5pt; vertical-align: 1pt;
}
/* Said once, in the place where a table would otherwise be. Not a muted grey:
   on a laser at 7.5pt, grey is the first thing to disappear. */
.ws-none { margin: 4pt 0 0; font-size: 8.5pt; font-style: italic; }
/* Ruled lines for the meeting. The board is read standing up with somebody
   writing on it, and a printout with nowhere to write gets notes in the margin
   or not at all. */
.ws-notes td.ws-write { height: 0.3in; }

/* Row heights are the whole point — these are hand-fill targets. */
.ws-t.rows td { height: 0.34in; }
.ws-t.steps td { height: 0.40in; }
.ws-t tr.blank td { height: 0.34in; }

/* Faint separators pre-printed inside empty date cells. */
.ws-t td.datec.empty::before { content: "    /       /"; color: #888; font-size: 8.5pt; }

/* ---------- steps & blockers ---------- */
.ws-t.steps td.seq { font-weight: 700; font-size: 10pt; }
.ws-t.steps .cs { font-size: 7pt; letter-spacing: .04em; white-space: nowrap; }
.ws-t.steps .stitle { font-size: 9pt; }

/* Blocker rows must read as "stop" with zero colour. Heavy rules top and bottom
   plus a hatched gutter down the sequence cell; the word BLOCKER is spelled out
   because a symbol alone doesn't survive a photocopy. */
tr.blk td { border-top: var(--heavy); border-bottom: var(--heavy); }
tr.blk td.seq {
  padding-left: 11pt;
  background:
    repeating-linear-gradient(45deg, #000 0 1.3pt, #fff 1.3pt 4pt) left / 7pt 100% no-repeat,
    #fff;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.blkflag { font-size: 6.5pt; font-weight: 700; letter-spacing: .1em; white-space: nowrap; }
.blkflag::before {
  content: ""; display: inline-block; vertical-align: -0.5pt;
  width: 6pt; height: 6pt; margin-right: 3pt; background: #000;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}

/* A hold prints as an instruction with blanks in it, not as a warning: by the
   time this sheet is on the bench the wait is just part of the job. Lighter
   than .blkflag on purpose — a blocker is someone withholding a signature, a
   hold is the clock, and the sheet shouldn't shout the same way about both.
   Wraps, unlike the nowrap flags above, because the resin name plus two
   timestamps is longer than a step title. */
.holdflag { font-size: 7pt; letter-spacing: .02em; margin-top: 1pt; }

/* Recorded buy-off: printed small so a correction can be written beside it. */
.ws-t .signed { font-size: 8pt; }
.ws-t .signed .nm { font-weight: 700; }

/* Empty checkbox for "done" — always drawn, even on signed rows, so a reprint
   mid-job can be re-ticked. */
.ws-cb {
  display: inline-block; width: 9pt; height: 9pt;
  border: 1pt solid #000; vertical-align: -1pt; margin-right: 4pt;
}

/* ---------- ply stack ---------- */
/* The screen's coloured plybar carries its meaning purely in hue, so print uses
   a table instead. The tint is kept as a colour-printer bonus only. */
.ws-t.stack td.mat { font-weight: 600; }
.ws-t.stack tr.cf td.sw { background: #3b4653; }
.ws-t.stack tr.spread td.sw { background: #6b7684; }
.ws-t.stack tr.core td.sw { background: var(--gold); }
.ws-t.stack tr.mesh td.sw { background: #c98f4a; }
.ws-t.stack tr.other td.sw { background: #dfe4ea; }
.ws-t.stack td.sw {
  width: 0.16in; padding: 0;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}

/* ---------- ruled free-text ---------- */
/* Real writing lines, not blank white space: people write straighter and the
   transcription back into the app is legible. */
.ws-lines {
  border: var(--hair); border-top: none;
  background-image: repeating-linear-gradient(
    to bottom, transparent 0 0.29in, #9a9a9a 0.29in calc(0.29in + 0.4pt));
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
/* min-height, not height: a work order with long notes grows its own box rather
   than printing the text over the bottom rule. The fit loop measures the result,
   so growth here just costs writing space elsewhere. */
.ws-lines.h2 { min-height: 0.58in; }
.ws-lines.h3 { min-height: 0.87in; }
.ws-lines.h4 { min-height: 1.16in; }
.ws-lines.h6 { min-height: 1.74in; }
.ws-lines.h8 { min-height: 2.32in; }
.ws-lines .prefill { padding: 1pt 5pt; font-size: 9pt; line-height: 0.29in; }

/* ---------- sign-off ---------- */
.ws-sign { margin-top: 12pt; break-inside: avoid; }
.ws-signgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 0; border-top: var(--hair); border-left: var(--hair); }
.ws-sigbox { border-right: var(--hair); border-bottom: var(--hair); padding: 3pt 6pt 5pt; }
.ws-sigbox .role { font-size: 6.5pt; letter-spacing: .09em; text-transform: uppercase; font-weight: 700; }
.ws-sigbox .who { font-size: 7pt; margin-bottom: 12pt; }
.ws-sigrow { display: flex; gap: 10pt; align-items: flex-end; }
.ws-sigrow .sigline { flex: 1; border-bottom: var(--hair); height: 0.30in; }
.ws-sigrow .dateline { width: 1.05in; border-bottom: var(--hair); height: 0.30in; }
.ws-sigrow .cap { font-size: 6pt; letter-spacing: .08em; text-transform: uppercase; }
.ws-caps { display: flex; gap: 10pt; margin-top: 1.5pt; }
.ws-caps .c1 { flex: 1; } .ws-caps .c2 { width: 1.05in; }

/* ---------- footer ---------- */
/* position:fixed inside paged media repeats this on every sheet, so a page that
   gets separated from its staple can still be traced back. Page numbering is
   hand-written: Chrome doesn't support @page margin-box counters. */
.ws-foot {
  border-top: var(--hair); margin-top: 10pt; padding-top: 2.5pt;
  display: flex; gap: 10pt; align-items: baseline;
  font-size: 6.5pt; letter-spacing: .05em; text-transform: uppercase;
}
.ws-foot .sp { margin-left: auto; }
.ws-foot .pg { border-bottom: var(--hair); display: inline-block; width: 0.34in; }

/* ---------- retro / draft / blank stamp ---------- */
/* Sits in the masthead's empty middle rather than floating over it — an absolute
   stamp collided with the process cell, and a rotated stamp over live data is
   exactly the kind of thing that gets misread on a photocopy. */
.ws-stamp {
  align-self: center; margin-left: 12pt;
  border: 1.75pt solid #000; padding: 2pt 8pt;
  font-size: 8pt; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  white-space: nowrap;
}
/* The R&D stamp rides ALONGSIDE the kind stamp, never instead of it: one is
   about the sheet and the other about the part, and both can be true.
   It has to be tellable from "Draft" at a glance on a photocopy of a photocopy,
   so it differs in BORDER STRUCTURE and in word count — never in colour, since
   this file prints on a monochrome laser and tokens.css flattens everything to
   ink anyway. A double rule needs >= 3pt of border-width to resolve as two
   lines rather than one thick one, which is why 3pt and not 1.75pt.
   Deliberately NOT reverse video: at 8pt on a shop laser with a tired cartridge
   a knockout fills in and becomes an illegible black lozenge, whereas a double
   rule degrades to a single rule, which is still a stamp. */
.ws-stamp.rnd { border-style: double; border-width: 3pt; padding: 2pt 7pt; letter-spacing: .1em; }

/* ---------- compact mode (fit-loop floor) ---------- */
/* Last resort before a third page. Rows stay tall enough to write in; the sheet
   loses breathing room, not legibility. */
.wsheet.compact { font-size: 8.8pt; }
.wsheet.compact .ws-t.steps td { height: 0.31in; }
.wsheet.compact .ws-t.rows td,
.wsheet.compact .ws-t tr.blank td { height: 0.27in; }
.wsheet.compact .ws-f { min-height: 0.32in; }
.wsheet.compact .ws-h { margin-top: 7pt; }
.wsheet.compact .ws-t td { padding: 2pt 5pt; }

/* ---------- off-screen measuring ---------- */
/* The fit loop renders candidate layouts here to measure them. It has to be laid
   out for scrollHeight to mean anything, so it is positioned away rather than
   hidden, and the on-screen page furniture is neutralised so the measurement is
   of the paper, not of the preview. */
#printroot.measuring {
  position: absolute; left: -10000px; top: 0;
  width: 8.5in; display: block !important;
  background: #fff; padding: 0;
}
#printroot.measuring .ws-page { min-height: 0; margin: 0; box-shadow: none; }

/* ---------- screen-only preview furniture ---------- */
/* Wraps the sheet on screen so it reads as paper. All of this disappears in
   print via index.html's @media print block. */
/* No TOP padding: the toolbar must start flush at y=0 so its background
   fills the safe-area inset. The 22px gap it used to create now lives on
   .pv-bar's margin-bottom. */
#printroot.preview { background: #6b7079; padding: 0 0 calc(40px + var(--sa-b, 0px)); }
#printroot.preview .ws-page { box-shadow: 0 3px 14px rgba(0,0,0,.4); }

/* ---------- fitting a Letter sheet onto a phone ----------

   A sheet is 8.5in — 816 CSS px — and always will be, because "this is exactly
   what prints" is the entire promise of the preview. On a 390px phone that
   overflowed by more than double: the browser blew the layout viewport out to
   816px to contain it, so the traveler's Initial and Date columns sat off the
   right-hand edge with no way to reach them, and the app around it went with it.

   So the sheet is ZOOMED to fit, never reflowed. Reflowing would make the
   preview a different document from the paper, which is worse than not having
   one. `zoom` rather than `transform: scale()` on purpose: zoom shrinks the
   element's LAYOUT box, so the page stops overflowing and the scroll height
   comes out right, where a transform leaves an 816px-wide box behind it and a
   tall gap underneath. print.js sets --pv-zoom; it is 1 on any screen with room. */
#printroot.preview .ws-page { zoom: var(--pv-zoom, 1); }

/* Reported from a phone: the Close / Save / Print row sat under the Dynamic
   Island. `body.previewing #app { display: none }` hands the whole viewport to
   #printroot, so this bar is the only chrome on screen and it is sticky at
   top:0 — there is nothing above it to have absorbed the inset.

   The bar's own BACKGROUND grows up into the inset rather than the bar being
   pushed below it: the island then sits on solid #22262c and reads as
   deliberate, where a bar starting below it leaves a strip of the grey paper
   backdrop above that looks like a rendering fault. The gap that used to come
   from #printroot.preview's top padding moves to this bar's margin-bottom, or
   the bar would start 22px down the page and the island would cover that strip.

   var(--sa-t, 0px) with the fallback spelled out, because print.js's
   sheetFileHtml() inlines this stylesheet into a standalone saved file that has
   no :root token block from index.html. */
#printroot .pv-bar {
  position: sticky; top: 0; z-index: 3;
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  padding: calc(9px + var(--sa-t, 0px)) 14px 9px;
  margin-bottom: 22px;
  background: #22262c; color: #fff; font: 13px/1.25 Arial, sans-serif;
  padding-left: max(14px, var(--sa-l, 0px));
  padding-right: max(14px, var(--sa-r, 0px));
}
#printroot .pv-bar .t { font-weight: 700; }
#printroot .pv-bar .sp { margin-left: auto; }
#printroot .pv-bar button { font: inherit; padding: 6px 11px; border-radius: 4px; border: 1px solid #4a515b; background: #333941; color: #fff; cursor: pointer; }
#printroot .pv-bar button.primary { background: #FDB515; border-color: #FDB515; color: #1c2733; font-weight: 700; }
#printroot .pv-bar label { display: flex; align-items: center; gap: 5px; cursor: pointer; }

/* Narrow: strip the toolbar back to the three things you can DO with the sheet.
   The title and the caption describe a document that is right there on screen,
   and the B&W proof is a desk task — it verifies the grayscale constraint while
   the sheet is being designed, which is not what anyone is doing on a phone at
   the bench. Left in, they wrapped the bar onto three rows and took a tenth of
   the screen; part of "printing breaks the UI" was just this. */
@media (max-width: 640px) {
  #printroot.preview { padding: 0 0 calc(24px + var(--sa-b, 0px)); }
  #printroot .pv-bar { gap: 6px; padding-top: calc(7px + var(--sa-t, 0px)); padding-bottom: 7px; margin-bottom: 10px; }
  #printroot .pv-bar .cap,
  #printroot .pv-bar .t,
  #printroot .pv-bar label { display: none; }
}
/* Touch: tap targets, keyed on the INPUT and not on the width. An iPad in
   portrait is 768px wide and every bit as much a finger as a phone is; sizing
   these by a width breakpoint left its controls 19px tall. */
@media (pointer: coarse) {
  #printroot .pv-bar button { padding: 9px 13px; min-height: 40px; }
  #printroot .pv-bar label { min-height: 40px; }
}
/* Grayscale proof: verifies the B&W-laser constraint without a printer. */
#printroot.gray .ws-page { filter: grayscale(1) contrast(1.04); }

/* ==========================================================================
   drawings.js — the mold stack drawing set
   ==========================================================================
   Each sheet is a .ws-page, so @page, the #app/#printroot print swap and the
   B&W proof toggle all apply with no second set of rules to maintain. What
   differs is that a traveler is ONE document that happens to run to two pages,
   while this is a SET of sheets: each one breaks, and each one carries its own
   title block rather than a repeating fixed footer.

   Same B&W-laser constraint as the rest of the printed output. Nothing here
   depends on colour; the drawings carry meaning in rule weight and dash. */

/* ISO 3098 lettering — the drawing standard: single-stroke, uniform weight,
   vertical, no serifs. Self-hosted and subset (9 KB); see fonts/osifont-LICENSE.md
   for why it is bundled rather than named in a stack, and for the one character
   it does not have. Declared here but only fetched when a .dwg sheet is on
   screen, since CSS fetches a face lazily on first use. */
@font-face {
  font-family: "ISO3098";
  src: url("fonts/osifont-iso3098.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}

.dwg {
  --ink: #000;
  --surface: #fff;   /* stackSvg fills top faces with var(--surface) — in dark
                        theme that resolves to a dark navy, so the reprinted
                        layer key would come out with black block faces. */
  color: #000;
  background: #fff;
  font: 8.5pt/1.3 "ISO3098", "ISOCPEUR", "Arial Narrow", Arial, sans-serif;
}
/* The geometry is drawn as SVG <text>, which inherits `font` from an HTML
   ancestor for family but is sized per element in drawings.js. */
.dwg svg text { font-family: "ISO3098", "ISOCPEUR", "Arial Narrow", Arial, sans-serif; }
.dwg, .dwg * { box-sizing: border-box; }   /* .wsheet's reset doesn't reach here */

.dwg-page {
  display: flex; flex-direction: column;
  break-after: page; page-break-after: always;
  border: 0.9pt solid #000;                /* the drawing frame */
}
.dwg-page:last-child { break-after: auto; page-break-after: auto; }
.dwg-page > svg { display: block; }

.dwg-top {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 6.5pt; letter-spacing: .11em; text-transform: uppercase;
  border-bottom: 0.6pt solid #000; padding-bottom: 2pt; margin-bottom: 5pt;
}

/* The drawing itself. currentColor is what every stroke in drawings.js uses, so
   this one declaration is what makes the geometry black on paper regardless of
   the app theme behind it. */
.dwg-view { width: 100%; height: auto; color: #000; }

/* Takes the slack on a sheet whose drawing doesn't need the whole page, so the
   views sit in the middle of the sheet instead of against the top rule. */
.dwg-fill { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center; }
.dwg-fill > svg { width: 100%; }

.dwg-cols { display: flex; gap: 10pt; align-items: flex-start; margin-top: 6pt; }
.dwg-cols > * { min-width: 0; }
.dwg-key { flex: 0 0 2.5in; }
/* Capped, or a tall stack's exploded key prints BIGGER than the assembled view
   it is a key to. preserveAspectRatio letterboxes it — the drawing shrinks and
   centres, it never squashes. */
.dwg-key svg { width: 100%; height: auto; max-height: 2.9in; color: #000; }
/* The as-machined mold inset on sheet 1. Narrower than the key: it is a
   picture, not a keyed diagram, and the cut-list table needs the width more. */
.dwg-inset { flex: 0 0 1.85in; }
.dwg-inset svg { width: 100%; height: auto; max-height: 2.9in; color: #000; }
.dwg-cols > .dwg-tabwrap { flex: 1 1 auto; }
.dwg-cap {
  font-size: 6.5pt; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  border-bottom: 1.2pt solid #000; padding-bottom: 1.5pt; margin-bottom: 3pt;
}
/* A reference table, not a hand-fill one: .ws-t.rows sizes its cells for a
   ballpoint in a cold shop, which is right for the traveler and wasteful here. */
.dwg-t td, .dwg-t th { font-size: 7.4pt; padding: 1.6pt 4pt; }
.dwg-t.rows td { height: auto; }
/* A row is 7.4pt x 1.3 plus 3.2pt of padding plus a hairline: 13.3pt, or 17.7px.
   The cut sheets PAGINATE on that number — how many blanks fit beside a nest
   diagram, where a table breaks to a continuation sheet — and they work it out
   without measuring anything, because measuring would mean a DOM and these
   builders are pure. The arithmetic holds only while a cell is one line high,
   so every column that can carry a long value (a dimension, a cut description,
   a board id) is marked .nw and refuses to wrap. A wrapped cell is 27px and
   quietly overflows the sheet, which the harness then fails somewhere else. */
.dwg-t td.nw, .dwg-t th.nw { white-space: nowrap; }
.dwg-t td.num, .dwg-t th.num { width: 0.5in; }
.dwg-notes {
  margin-top: 5pt; border: 1.2pt solid #000; padding: 3pt 6pt;
  font-size: 7.4pt; font-weight: 700;
}
.dwg-notes div + div { margin-top: 2pt; }

/* ---------- title block ---------- */
/* Bottom of every sheet, because a sheet separated from the set has to be able
   to say which mold, which plan, which sheet and at what scale. margin-top:auto
   floats it to the foot of the page rather than leaving it under the drawing. */
/* Four content columns, not three: the block carries eight cells (mold, plan,
   part, work order / sheet, title, scale, planned) and they have to fill
   exactly TWO rows. The brand spans two rows, so a ninth cell would start a
   third row underneath it — which grows the block, shrinks the drawing area
   above it and crowds the layer labels. If you add a field here, remove one. */
.dwg-tb {
  margin-top: auto; display: grid;
  grid-template-columns: 0.62in repeat(4, 1fr);
  border-top: 1.75pt solid #000; border-left: 0.75pt solid #000;
}
.dwg-tb .tb-brand {
  grid-row: span 2; background: #000; color: #fff;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  font-weight: 700; font-size: 9pt; letter-spacing: .04em;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.dwg-tb .tb-brand span { font-weight: 400; font-size: 6pt; letter-spacing: .14em; }
.dwg-tb .tb-c {
  border-right: 0.75pt solid #000; border-bottom: 0.75pt solid #000;
  padding: 2pt 5pt; min-height: 0.34in;
  display: flex; flex-direction: column; justify-content: center;
}
.dwg-tb .tb-c .lab { font-size: 5.8pt; letter-spacing: .1em; text-transform: uppercase; }
.dwg-tb .tb-c .val { font-size: 9pt; font-weight: 700; line-height: 1.15; word-break: break-word; }
.dwg-tb .tb-c .val.sm { font-size: 7pt; }
.dwg-tb .tb-note {
  grid-column: 1 / -1;
  border-right: 0.75pt solid #000; border-bottom: 0.75pt solid #000;
  padding: 2.5pt 5pt; font-size: 6.6pt; line-height: 1.32;
}

/* ================================================================
   LABELS — 4 x 1 inch, Avery 5161 (20 up) and 5522 (14 up)
   ================================================================

   Lives in print.css, NOT in index.html's <style>, because downloadSheet()
   (print.js) fetches print.css and inlines it into the saved standalone HTML.
   Anything in index.html vanishes from every saved sheet, and a saved label
   sheet on a phone at RFS with no wifi is exactly the case this has to survive.

   Why a named @page: Avery's left margin is 0.15625in, well inside the global
   @page margin of 0.45in that index.html sets for travelers. Positioning cells
   from the physical page corner needs margin:0, and margin can only be changed
   per-page-context. Named pages are Chrome 85+ / Safari 16.4+. If a browser
   ignores it the calibration cell's corner mark lands off-square, which is the
   signal to set margins to "None" in the print dialog by hand.

   The whole label is built to be read WITHOUT scanning it: PP-09 records the
   question "what layup stack was used for seat last year" being unanswerable,
   so the stack line (.lbl-r2) is the point of the exercise and is the only
   body line set bold. */

@page label { size: letter; margin: 0; }

.wsheet.labels .label-sheet {
  page: label;
  width: 8.5in; height: 11in; min-height: 0;
  padding: 0; margin: 0 auto 18px;
  position: relative; overflow: hidden;
}
.label-cell { position: absolute; overflow: hidden; }

/* One label. The name leads: the label's primary use is being read, the QR is
   secondary, so the name is the top row at the largest size that fits
   (nameTier in labels.js picks the class) and the ID is a 9.5pt row of its
   own. Vertical budget at 1in: 25.4mm less 2mm of margin top and bottom
   leaves 21.4mm; a one-line name runs 5.4+4.0+3.6+2.9+2.9 = 18.8mm and a
   two-line name drops the mid row (merged into the footer by labelHtml) for
   10.1+4.0+3.6+2.9 = 20.6mm. There is no room for a masthead bar at this
   height, so the FEB mark is a 6.5pt tag riding the last line. */
.lbl {
  width: 100%; height: 100%;
  display: flex; align-items: center; gap: 2mm;
  padding: 2mm 2mm 2mm 2.5mm;
  font: 7pt/1.18 Arial, Helvetica, sans-serif;
  color: #000; background: #fff;
  overflow: hidden;
}
/* min-width:0 is load-bearing: without it a grid/flex track sizes from
   min-content and a long mold name pushes the QR off the label instead of
   clipping. Paired with overflow:hidden on .lbl-txt, a 60-character name
   truncates rather than destroying the layout. */
.lbl-txt { flex: 1 1 auto; min-width: 0; overflow: hidden; }
.lbl-code { flex: 0 0 auto; display: flex; align-items: center; }
.lbl-qr { display: block; }

/* The name row. One-line tier keeps nowrap+ellipsis as a belt-and-braces
   guard; the two-line tiers use a line clamp so a runaway name can never push
   the key row (the point of the label) off the bottom. Tier thresholds are
   char counts chosen in labels.js so the ellipsis should never actually fire
   below the n2d tier. */
.lbl-name { font-weight: 700; letter-spacing: -.01em; line-height: 1.1;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lbl-name.n1  { font-size: 14pt; }
.lbl-name.n2a, .lbl-name.n2b, .lbl-name.n2c, .lbl-name.n2d {
  white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.lbl-name.n2a { font-size: 13pt; }
.lbl-name.n2b { font-size: 11pt; }
.lbl-name.n2c { font-size: 9pt; }
.lbl-name.n2d { font-size: 8pt; }
/* A flex row for the same reason .lbl-r4 is one (see the note below it): the
   ellipsis has to live on the TEXT span and never on the row, or a tag at the
   end gets clipped mid-glyph instead of the text truncating before it.
   Here that lesson is inverted and deliberate — flex:0 0 auto on the R&D tag
   makes it the one element on this label that can never be truncated away. */
.lbl-rid { display: flex; align-items: baseline; gap: 1.6mm;
           font-size: 9.5pt; font-weight: 700; letter-spacing: -.01em; line-height: 1.2;
           margin-top: .3mm; white-space: nowrap; }
.lbl-rid > span:first-child { min-width: 0; flex: 1 1 auto;
                              overflow: hidden; text-overflow: ellipsis; }

/* R&D: real carbon, a real cost, not a season deliverable.
   COSTS ZERO VERTICAL MILLIMETRES, which is the whole reason it is on this row
   and not a new one — the budget above is spent. The row is 9.5pt x 1.2 =
   4.02mm; the tag is 7.5pt x 1.1 = 2.91mm of text box, plus 2 x 0.75pt of rule
   (0.53mm) and 0.2mm of padding = 3.64mm. A flex row is as tall as its tallest
   item and 3.64 < 4.02, so the row cannot grow and the 18.8/20.6mm arithmetic
   is unchanged.
   Horizontally there is 26mm of slack even at the 14-character id cap, against
   the narrow 5522 track. A rule and not a fill: this prints on the office laser
   and then gets resin wiped off it, and an 8pt knockout fills in solid the first
   time toner runs low. */
.lbl-rnd { flex: 0 0 auto;
           font-size: 7.5pt; font-weight: 700; letter-spacing: .08em; line-height: 1.1;
           border: 0.75pt solid #000; border-radius: 0.6mm; padding: 0.1mm 1.0mm; }

/* The line the system exists for: layup stack on a part or panel, sealing
   record on a mold. Bold at 8.5pt rather than dropped to the 7pt tier. */
.lbl-r2 { font-size: 8.5pt; font-weight: 700; line-height: 1.2; margin-top: .4mm;
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lbl-r3 { font-size: 7pt; line-height: 1.2;
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The ellipsis has to live on the TEXT span, not on the flex row. With
   overflow:hidden on the row itself, a long footer clipped the FEB tag mid-
   glyph instead of truncating the text before it — visible on a mold label
   whose footer carries board, location and the keep-out reminder. */
.lbl-r4 { display: flex; justify-content: space-between; align-items: baseline;
          gap: 2mm; font-size: 7pt; line-height: 1.2; }
.lbl-r4 > span:first-child { min-width: 0; flex: 1 1 auto;
                             white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lbl-feb { font-size: 6.5pt; font-weight: 700; letter-spacing: .08em; flex: 0 0 auto; }
.lbl-r2:empty, .lbl-r3:empty { display: none; }

/* Calibration cell. Browsers silently apply "Fit to page" scaling, which shifts
   every label on the sheet; $30 of polyester is worth ten seconds with a steel
   rule. The corner mark doubles as the registration check for the named-page
   note above. */
/* The bar is 100mm and the cell is 101.6mm, so this one label cannot afford
   .lbl's 2mm/2.5mm side padding — 100 + 4.5 > 101.6 and the bar overflows the
   die-cut. The bar's width is the whole point and is not negotiable, so the
   padding gives way instead. */
.lbl-cal { flex-direction: column; align-items: flex-start; justify-content: center; gap: 1mm;
           padding-left: 0.6mm; padding-right: 0.6mm; }
.lbl-calbar { display: flex; width: 100mm; height: 3mm; border: 0.75pt solid #000; border-right: none; }
.lbl-caltick { flex: 1 1 10mm; border-right: 0.75pt solid #000; }
.lbl-calnote { font-size: 6.5pt; line-height: 1.25; }

/* Screen-only preview aid: show where the die-cut edges are, so the preview is
   readable as a sheet of labels rather than floating text. Hidden on paper —
   printing a border on an Avery sheet is how you get ink on the backing. */
.wsheet.labels .label-cell { outline: 0.4pt dashed #bbb; outline-offset: -0.4pt; }
@media print { .wsheet.labels .label-cell { outline: none; } }

/* A label sheet is a fixed grid whose cells are positioned from the physical
   page corner, so it must keep its exact 8.5 x 11in box on paper. index.html's
   `#printroot .ws-page { width:auto; min-height:0 }` exists to undo the
   fit-to-screen shrink on a traveler and would collapse that box, and it wins
   on specificity (#id + .class). Re-assert with the id in the selector.
   One sheet per physical page; the last one must not emit a trailing blank. */
@media print {
  #printroot .wsheet.labels .label-sheet {
    width: 8.5in !important; height: 11in !important;
    padding: 0 !important; margin: 0 !important; zoom: 1 !important;
    break-after: page; page-break-after: always;
  }
  #printroot .wsheet.labels .label-sheet:last-child { break-after: auto; page-break-after: auto; }
}

/* ================================================================
   LABELS ON A ROLL — Brother QL, one label at a time
   ================================================================

   Same .lbl markup as the Avery sheet, a different page. On a QL the roll's
   width runs ACROSS the print head and the length along the feed is whatever
   you cut, so a 29mm continuous roll cut at 101.6mm IS the 5161 cell to within
   a millimetre — which is why nothing above this comment had to change.

   ONE NAMED PAGE PER MEDIA, because @page carries the `size` and the two rolls
   are different sizes. The names are matched by the `page` property on the
   element, so a media added to LABEL_ROLLS in labels.js needs its @page here
   or the label prints on whatever the default page is (US Letter) with one
   label marooned in the corner of a sheet.

   `.roll-page` and NOT `.ws-page`: print.js sheetFileHtml() injects
   `@media print { .ws-page { padding: 0 0.45in } }` into every saved standalone
   sheet, and 0.45in either side of a 101.6mm label leaves nothing. The preview
   would look right and the file that actually gets printed at the bench would
   not, which is the worst shape a bug can have here. */

@page roll2210 { size: 101.6mm 25.4mm; margin: 0; }
@page roll1201 { size: 86.6mm 26.4mm; margin: 0; }

.wsheet.rolls .roll-page {
  position: relative; overflow: hidden;
  padding: 0; margin: 0 auto 8px;
  background: #fff;
}
.wsheet.rolls .roll-page[data-media="dk2210"] { page: roll2210; width: 101.6mm; height: 25.4mm; }
.wsheet.rolls .roll-page[data-media="dk1201"] { page: roll1201; width: 86.6mm; height: 26.4mm; }

/* Screen-only: the preview is a stack of small white rectangles on a grey
   background, so without an edge it reads as floating text rather than as
   labels. Hidden on paper for the same reason the sheet's cell outlines are —
   a printed border is how you get ink on the backing. */
#printroot.preview .wsheet.rolls .roll-page { box-shadow: 0 3px 14px rgba(0,0,0,.4); }
.wsheet.rolls .roll-page { outline: 0.4pt dashed #bbb; outline-offset: -0.4pt; }
@media print { .wsheet.rolls .roll-page { outline: none; } }

/* index.html's `#printroot .ws-page { width:auto; min-height:0 }` does not
   reach .roll-page, but the id-in-selector form is kept anyway to match what
   the sheet rules above had to do, and because one page per label means a
   trailing blank page is the visible failure if break-after leaks. */
@media print {
  #printroot .wsheet.rolls .roll-page {
    padding: 0 !important; margin: 0 !important; zoom: 1 !important;
    box-shadow: none;
    break-after: page; page-break-after: always;
  }
  #printroot .wsheet.rolls .roll-page[data-media="dk2210"] { width: 101.6mm !important; height: 25.4mm !important; }
  #printroot .wsheet.rolls .roll-page[data-media="dk1201"] { width: 86.6mm !important; height: 26.4mm !important; }
  #printroot .wsheet.rolls .roll-page:last-child { break-after: auto; page-break-after: auto; }
}

/* ---------- the R&D study report ----------
   Added 2026-08-28 with the R&D bench. Here and NOT in index.html's <style>,
   because downloadSheet() fetches and inlines THIS file and nothing else — a
   rule written next to the screen styles renders correctly in the preview and
   then vanishes from every saved sheet, which is the failure mode the label
   rules are already grouped here to avoid.

   Its own table class rather than table.sub: that one is screen vocabulary and
   print.css has never styled it, so a report leaning on it would print as
   unruled rows. */
.rdrep-page { break-after: page; page-break-after: always; }
.rdrep-page:last-child { break-after: auto; page-break-after: auto; }
.rdrep-page h1 { font-size: 15pt; margin: 0 0 2pt; }
.rdrep-meta { font-size: 7.5pt; letter-spacing: .06em; text-transform: uppercase; margin-bottom: 6pt; }
.rdrep-q { font-size: 9.5pt; margin: 0 0 8pt; }
.rdrep-mat { display: flex; flex-wrap: wrap; gap: 2pt 14pt; font-size: 8pt; margin-bottom: 8pt; }

.rdrep-tbl { width: 100%; border-collapse: collapse; font-size: 8.5pt; margin-bottom: 10pt; }
.rdrep-tbl th { text-align: left; font-size: 6.5pt; letter-spacing: .09em; text-transform: uppercase;
  border-bottom: 0.9pt solid #000; padding: 2pt 4pt; }
.rdrep-tbl td { padding: 2.5pt 4pt; border-bottom: 0.3pt solid #999; vertical-align: top; }
/* Measurements line up on the decimal or they cannot be scanned down a column,
   which is the only reason to print the table rather than the summary. */
.rdrep-tbl td { font-variant-numeric: tabular-nums; }
.rdrep-tbl tr { break-inside: avoid; page-break-inside: avoid; }

.rdrep-photos { display: flex; flex-wrap: wrap; gap: 8pt; margin-top: 8pt; }
.rdrep-photos figure { margin: 0; width: 132pt; break-inside: avoid; page-break-inside: avoid; }
.rdrep-photos img { width: 132pt; height: 94pt; object-fit: cover; border: 0.6pt solid #000; display: block; }
.rdrep-photos figcaption { font-size: 6.5pt; margin-top: 1.5pt; }
