/* Contractor Handbook. One stylesheet, no framework, no webfont request.
   Speed is the product here: the searcher wants a file in 30 seconds. */
:root {
  color-scheme: light;
  --ink: #0e1319;
  --ink-2: #3d4753;
  --ink-3: #5f6b79;
  --line: #e3e8ed;
  --line-2: #f0f4f7;
  --paper: #ffffff;
  --paper-2: #f7f9fc;
  --paper-3: #eef2f7;
  --accent: #1a56db;
  --accent-dark: #133fa8;
  --accent-soft: #eaf1ff;
  /* The call to action's own paint. It used to be two literal blues and #fff written
     into .btn-primary, which is fine in daylight and wrong in the dark, where --accent
     becomes #6f9bff and white on it is 2.69 to 1. MEASURED: white on #2f6bf5 is 4.62 and
     on #1a56db 6.18; hovering, 6.44 and 9.11. tools/inkcheck.mjs holds these. */
  --cta-top: #2f6bf5;
  --cta-bot: #1a56db;
  --cta-top-h: #2453d4;
  --cta-bot-h: #133fa8;
  --cta-line: #133fa8;
  --on-accent: #ffffff;
  --on-bad: #ffffff;
  --good: #0f7b3d;
  --warn-bg: #fff8e6;
  --warn-line: #f0d48a;
  --radius: 12px;
  --r-sm: 8px;
  --r-lg: 18px;
  --wrap: 1120px;
  --measure: 68ch;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Three shadow steps and nothing else. A page that uses five is a page where
     nothing looks lifted, because the eye reads depth by comparison. */
  --sh-1: 0 1px 2px rgba(14, 19, 25, .05);
  --sh-2: 0 6px 20px -6px rgba(14, 19, 25, .12), 0 2px 6px -2px rgba(14, 19, 25, .05);
  --sh-3: 0 20px 48px -16px rgba(14, 19, 25, .22);
  /* The edge a sideways scrolling container draws to say there is more to the
     right of it. A token and not a literal, because the literal it replaced was
     a dark shadow and a dark shadow on dark paper is nothing at all. See
     .tbl-scroll, where it was invisible to every phone in the dark theme. */
  --sh-edge: rgba(14, 19, 25, .14);

  /* The eight card families. A tint is a pale ground and a saturated mark, and
     the pair is always chosen so the mark clears 4.5:1 on its own ground. The
     card reads its pair from a .t-* class, so nothing outside this block has to
     know a hex value. */
  --t-bg: #eaf1ff; --t-fg: #1a56db;

  /* The eight pairs themselves. They live here rather than in the .t-* rules so
     that a theme only has to restate this block, and the .t-* rules never have to
     be written twice. */
  --tb-blue: #eaf1ff;   --tf-blue: #1a4fd0;
  --tb-indigo: #eeebfd; --tf-indigo: #4a35bd;
  --tb-green: #e6f6ec;  --tf-green: #0d7038;
  --tb-teal: #e0f4f2;   --tf-teal: #0c6f68;
  --tb-red: #fdecea;    --tf-red: #c43428;
  --tb-amber: #fcf2dd;  --tf-amber: #96600a;
  --tb-orange: #fdeee2; --tf-orange: #b3500e;
  --tb-violet: #f5eafb; --tf-violet: #7433a4;

  /* Everything below was a hex literal sitting in a rule until dark mode needed
     it. Each one is a job: the colour a failure is written in, the two grounds a
     diff uses, the frosted header, the highlight behind a search match, the one
     inverted surface. */
  --bad: #b42318;
  --bad-fg: #912018;
  --bad-bg: #fef3f2;
  --add-fg: #05603a;
  --add-bg: #ecfdf3;
  --glass: rgba(255, 255, 255, .86);
  --glass-2: rgba(255, 255, 255, .72);
  --mark: #fdf0b8;
  --lift: #f4f8ff;
  --eyebrow-line: #cfdffb;
  --dot: #17a34a;
  --invert-bg: #0e1319;
  --invert-fg: #ffffff;
}
/* The tools set an element's hidden property to put it away, and the browser's own
   rule for that is [hidden] { display: none } at the weakest weight there is. Any
   class here that sets display, and several do, quietly beat it, which left the
   action bar on every tool page showing before a file had been chosen. This is the
   one place in the stylesheet that earns an !important: nothing should ever style a
   hidden element back into view. */
[hidden] { display: none !important; }

.t-blue   { --t-bg: var(--tb-blue);   --t-fg: var(--tf-blue); }
.t-indigo { --t-bg: var(--tb-indigo); --t-fg: var(--tf-indigo); }
.t-green  { --t-bg: var(--tb-green);  --t-fg: var(--tf-green); }
.t-teal   { --t-bg: var(--tb-teal);   --t-fg: var(--tf-teal); }
.t-red    { --t-bg: var(--tb-red);    --t-fg: var(--tf-red); }
.t-amber  { --t-bg: var(--tb-amber);  --t-fg: var(--tf-amber); }
.t-orange { --t-bg: var(--tb-orange); --t-fg: var(--tf-orange); }
.t-violet { --t-bg: var(--tb-violet); --t-fg: var(--tf-violet); }

/* The sprite is markup, not picture: it is drawn once per page and never seen. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
/* A <use> clones the symbol into a shadow tree at the USE site, so inherited
   properties come from there, not from the sprite the symbol was written in.
   Presentation attributes on the sprite element are therefore ignored and the
   stroke only paths render as solid black blobs. The stroke belongs here. */
.ci, .ni, .brand-mark {
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Keyboard users land here first on every page. It stays off screen until it is
   focused, which is the one state where it has to be visible and on top. */
.skip {
  position: fixed; top: 8px; left: 8px; z-index: 100;
  transform: translateY(-200%); transition: transform .16s;
  background: var(--accent); color: var(--on-accent); padding: 10px 16px;
  border-radius: var(--r-sm); text-decoration: none; font-weight: 620; font-size: 14px;
}
.skip:focus { transform: none; }

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0; background: var(--paper); color: var(--ink);
  font: 16px/1.65 var(--font); letter-spacing: -0.003em;
}
img { max-width: 100%; height: auto; }
a { color: var(--accent); text-underline-offset: 2px; }
a:hover { color: var(--accent-dark); }
.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 20px; }
.measure { max-width: var(--measure); }

/* ---------- header ---------- */
.site-head {
  border-bottom: 1px solid var(--line);
  background: var(--glass);
  position: sticky; top: 0; z-index: 40;
}
@supports (backdrop-filter: blur(1px)) {
  .site-head { backdrop-filter: saturate(1.6) blur(12px); }
}
.site-head .wrap { display: flex; align-items: center; gap: 22px; height: 64px; }
.brand {
  display: inline-flex; align-items: center; gap: 9px;
  font-weight: 700; font-size: 16.5px; color: var(--ink); text-decoration: none;
  letter-spacing: -0.022em; white-space: nowrap;
}
.brand-mark {
  flex: none; color: #fff; background: linear-gradient(150deg, #3b78ff, var(--accent));
  border-radius: 8px; padding: 4px; box-shadow: var(--sh-1);
}
.brand b { color: var(--accent); font-weight: 700; }
.nav { display: flex; gap: 4px; margin-left: auto; align-items: center; }
.nav a {
  color: var(--ink-2); text-decoration: none; font-size: 14.5px; font-weight: 560;
  padding: 7px 11px; border-radius: var(--r-sm); line-height: 1;
  display: inline-flex; align-items: center; gap: 7px;
  transition: background .13s, color .13s;
}
.nav a:hover { color: var(--accent); background: var(--accent-soft); }
.nav .ni { display: none; }

/* The menu toggle is a checkbox and a label, so the header needs no script at
   all and works on a page whose JavaScript has not arrived or has failed. The
   checkbox itself is off screen rather than display:none, because a hidden
   input is not focusable and the menu would be unreachable by keyboard. */
.navtog { position: absolute; opacity: 0; width: 1px; height: 1px; }
.burger {
  display: none; margin-left: auto; cursor: pointer;
  width: 42px; height: 42px; border-radius: var(--r-sm); border: 1px solid var(--line);
  background: var(--paper); align-items: center; justify-content: center;
  flex-direction: column; gap: 4px; flex: none;
}
.burger span { display: block; width: 17px; height: 2px; border-radius: 2px; background: var(--ink-2); transition: transform .18s, opacity .18s; }
.navtog:focus-visible + .burger { outline: 2px solid var(--accent); outline-offset: 2px; }
.navtog:checked + .burger span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.navtog:checked + .burger span:nth-child(2) { opacity: 0; }
.navtog:checked + .burger span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

@media (max-width: 900px) {
  .site-head .wrap { height: 58px; gap: 12px; }
  .burger { display: flex; }
  .nav {
    display: none; position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: 2px;
    background: var(--paper); border-bottom: 1px solid var(--line);
    padding: 10px 14px 16px; box-shadow: var(--sh-2);
    max-height: calc(100dvh - 58px); overflow-y: auto;
  }
  .navtog:checked ~ .nav { display: flex; }
  .nav a { font-size: 15.5px; padding: 12px 12px; gap: 12px; }
  .nav .ni { display: block; width: 20px; height: 20px; color: var(--ink-3); flex: none; }
  .nav a:hover .ni { color: var(--accent); }
  /* The wordmark is the biggest link in the header and it was 26px tall, because an
     inline-flex box is exactly as tall as one line of 16px text. Nothing about it
     looked wrong, which is the point: a target is a rectangle you can hit, not a
     thing you can see. The mark and the text do not move, the box around them grows.
     (.navtheme wants the same treatment and cannot have it here. See below.) */
  .brand { min-height: 44px; }
}

/* The header row is four fixed things: wordmark, search, theme, menu. Added up with
   their gaps they came to 360px of content, which is the width of the commonest
   Android phone exactly, so every page on the site scrolled sideways by a few pixels
   there and by 32 on a 320px screen. Measured in a 360px frame, not guessed: the
   burger was the element hanging off the right edge of all 746 pages.

   So the row gets tighter twice. At 400px the gap and the wordmark give back what
   they can while the name stays readable. At 360px the name goes and the mark stands
   for it, which is what every interface does at this width, and it goes by the screen
   reader recipe rather than display:none, because the only text in that link is its
   accessible name and a link with no name is a worse bug than the one being fixed.

   The gap is 8 and not 10 because the site was renamed. ContractorHandbook is three
   characters longer than the name this block was first measured against, and at 360px
   that put the row back to 363 and the burger back off the edge. Three gaps at two
   pixels each is six, which buys it back twice over without touching the type. Measure
   it again if the name ever changes: document.documentElement.scrollWidth in a 360px
   frame is the whole test. */
@media (max-width: 400px) {
  .site-head .wrap { gap: 8px; }
  .brand { font-size: 15.5px; gap: 7px; }
  .brand-mark { width: 24px; height: 24px; }
}
@media (max-width: 355px) {
  .brand span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  /* With the name gone the link is a 24px square. The height it already has from
     the touch rule above; this gives it the width back too, without the mark
     growing with it. */
  .brand { min-width: 44px; justify-content: center; }
}

/* ---------- page furniture ---------- */
.crumbs { font-size: 13px; color: var(--ink-3); padding: 16px 0 0; }
.crumbs a { color: var(--ink-3); text-decoration: none; }
.crumbs a:hover { color: var(--accent); }

h1 { font-size: clamp(28px, 4.4vw, 40px); line-height: 1.14; letter-spacing: -0.025em; margin: 14px 0 12px; font-weight: 760; }
h2 { font-size: clamp(21px, 2.6vw, 26px); line-height: 1.22; letter-spacing: -0.02em; margin: 40px 0 12px; font-weight: 720; }
h3 { font-size: 17.5px; margin: 26px 0 8px; font-weight: 680; letter-spacing: -0.012em; }
p { margin: 0 0 15px; color: var(--ink-2); }
.lede { font-size: 18.5px; color: var(--ink-2); max-width: var(--measure); }

.layout { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 44px; padding: 0 0 64px; align-items: start; }
/* minmax(0, 1fr), not 1fr. A grid item will not shrink below its min content width
   unless it is told it may, and one long unbreakable string, a file name in the merge
   list or a wide code line, would otherwise push the whole page sideways on a phone. */
@media (max-width: 980px) { .layout { grid-template-columns: minmax(0, 1fr); gap: 32px; } }
.rail { position: sticky; top: 82px; }
@media (max-width: 980px) { .rail { position: static; } }

/* ---------- the download strip, the most important block on the page ---------- */
.dl {
  position: relative; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: linear-gradient(165deg, var(--lift) 0%, var(--paper) 58%);
  padding: 22px 24px; margin: 26px 0 8px; box-shadow: var(--sh-1);
}
/* A soft corner glow, drawn with a radial gradient rather than an image, so the
   strip reads as the one lifted object on the page without a single request. */
.dl::after {
  content: ""; position: absolute; top: -70px; right: -60px; width: 220px; height: 220px;
  background: radial-gradient(circle, rgba(26, 86, 219, .10), transparent 68%);
  pointer-events: none;
}
.dl h2 { margin: 0 0 4px; font-size: 17.5px; letter-spacing: -0.018em; }
/* h2 + p, not p. This was written for the one paragraph under the heading and was
   reaching every paragraph in the block, which includes .no-gate at the bottom. .dl p is
   0,1,1 and .no-gate is 0,1,0, so for 194 pages the reassurance line under the buttons
   has been quiet grey at 14px and its own rule, which asks for --good at 13px, has never
   once painted. MEASURED in the browser, not read off the cascade. There are exactly two
   paragraphs in a .dl: this one, directly after the h2, and that one. */
.dl h2 + p { margin: 0 0 15px; font-size: 14px; color: var(--ink-3); }
.dl-row { display: flex; gap: 10px; flex-wrap: wrap; position: relative; }
/* Buttons carry nowrap, which is right for a label somebody wrote and wrong for
   one the legislature did. A download button is labelled with the form's own
   title, and "Waiver of the List of Subcontractors and Suppliers" sets 396px on
   one line, wider than a 375px phone, so it was being clipped. Lifting nowrap
   here lets a label that cannot fit wrap onto a second line; a label that fits
   still sits on one, because normal only breaks when it has to. */
.dl-row .btn { white-space: normal; max-width: 100%; }

/* The route to the builder, written by build.mjs above the download block and
   only on the 134 pages that have a builder. The page then offers both ways of
   getting the form before either one has been scrolled past: fill it in here, or
   take a blank one from the block underneath.

   A card and not a line of text, because it sits between two cards and a bare
   link between them reads as a caption belonging to one of them. The chevron is
   drawn from two borders rather than set as a glyph: there is not one arrow
   character anywhere else on this site and this is not the place to start. */
.gen-jump,
.doc-jump {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  column-gap: 16px; padding: 14px 18px; margin: 26px 0 0;
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: var(--accent-soft); color: var(--ink); text-decoration: none;
}
.gen-jump:hover,
.doc-jump:hover { border-color: var(--accent); }
.gen-jump b,
.doc-jump b { grid-column: 1; font-size: 15.5px; font-weight: 640; color: var(--accent-dark); }
.gen-jump span,
.doc-jump span { grid-column: 1; font-size: 13.5px; color: var(--ink-2); margin: 2px 0 0; }
.gen-jump::after,
.doc-jump::after {
  content: ""; grid-column: 2; grid-row: 1 / 3; width: 9px; height: 9px;
  border-right: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  /* Pointing down, not right. It is a move further down this same page and a
     right chevron would promise another one. */
  transform: rotate(45deg);
}

/* The same card on a guide page, between the lede and the drawing, pointing at the
   document the guide is about. Everything above is shared; the two differences are
   the whole reason it has a class of its own.

   The chevron turns to point right, because this one really does promise another
   page, which is the distinction the comment above is making.

   And it needs margins on both sides, which the generator's does not. Up there it
   follows a lede rather than a heading, and a lede is already the end of a thought,
   so 20 rather than 26. Underneath it, the drawing has no top margin of its own: the
   15 pixel gap the lede used to have was the lede's own bottom margin, so a card
   dropped in between them ended up touching the drawing, measured at 536 against
   535. 22 pixels, because these are two bordered boxes and 15 between them is the
   spacing for a paragraph. */
.doc-jump { margin: 20px 0 22px; }
.doc-jump::after { transform: rotate(-45deg); }

/* The block underneath it opens with the same promise, so the two must not read
   as a repeated offer. */
.gen-jump + .dl { margin-top: 12px; }
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--paper);
  padding: 11px 17px; font: inherit; font-size: 14.5px; font-weight: 620;
  color: var(--ink); text-decoration: none; cursor: pointer; white-space: nowrap;
  box-shadow: var(--sh-1);
  transition: border-color .13s, background .13s, box-shadow .13s, transform .07s;
}
.btn .ci { width: 18px; height: 18px; flex: none; }
.btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); box-shadow: var(--sh-2); }
.btn:active { transform: translateY(1px); box-shadow: var(--sh-1); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A gradient is also why this went unnoticed for so long: an element painted with one
   has a transparent backgroundColor, so a contrast walk that treats a gradient as a hint
   rather than a surface scores the label against the page behind the button and reports
   it passing. It is a surface. Only the tokens decide the colour now. */
.btn-primary {
  background: linear-gradient(170deg, var(--cta-top), var(--cta-bot));
  border-color: var(--cta-line); color: var(--on-accent);
  box-shadow: 0 1px 2px rgba(14, 19, 25, .12), inset 0 1px 0 rgba(255, 255, 255, .22);
}
.btn-primary:hover {
  background: linear-gradient(170deg, var(--cta-top-h), var(--cta-bot-h));
  border-color: var(--cta-line); color: var(--on-accent);
  box-shadow: 0 8px 20px -8px rgba(26, 86, 219, .55), inset 0 1px 0 rgba(255, 255, 255, .22);
}
.btn .ext { font: 600 11px/1 var(--mono); letter-spacing: .04em; }
.no-gate { font-size: 13px; color: var(--good); margin: 12px 0 0; font-weight: 560; }

/* ---------- generator ---------- */
/* The header is sticky and 59px tall, so an anchor landing at the top of this
   section would put its own heading underneath the header. */
.gen { border: 1px solid var(--line); border-radius: var(--radius); margin: 28px 0; overflow: clip; scroll-margin-top: 76px; }
.gen-head { padding: 16px 20px; border-bottom: 1px solid var(--line); background: var(--paper-2); }
.gen-head h2 { margin: 0; font-size: 17px; }
.gen-head p { margin: 3px 0 0; font-size: 13.5px; color: var(--ink-3); }
.gen-body { padding: 20px; display: grid; gap: 18px; }
fieldset { border: 0; padding: 0; margin: 0; display: grid; gap: 12px; }
legend { font: 620 11px/1 var(--font); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); padding: 0 0 10px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
.grid2 > .span2, .grid3 > .span2 { grid-column: 1 / -1; }
@media (max-width: 640px) { .grid2, .grid3 { grid-template-columns: 1fr; } }
label { display: block; font-size: 12.5px; font-weight: 600; color: var(--ink-2); margin: 0 0 5px; }
input, textarea, select {
  width: 100%; font: inherit; font-size: 14.5px; color: var(--ink);
  padding: 9px 11px; border: 1px solid var(--line); border-radius: 7px; background: var(--paper);
}
input:focus, textarea:focus, select:focus { outline: 2px solid var(--accent-soft); border-color: var(--accent); }
textarea { min-height: 78px; resize: vertical; line-height: 1.5; }

table.items { width: 100%; border-collapse: collapse; }
table.items th {
  text-align: left; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 620; padding: 0 8px 7px 0;
}
table.items td { padding: 0 8px 8px 0; vertical-align: top; }
table.items td:last-child, table.items th:last-child { padding-right: 0; width: 38px; }
.col-n { width: 78px; } .col-r { width: 104px; }
@media (max-width: 640px) {
  table.items, table.items tbody, table.items tr, table.items td { display: block; width: 100%; }
  table.items thead { display: none; }
  table.items tr { border: 1px solid var(--line); border-radius: 8px; padding: 10px; margin: 0 0 10px; }
  table.items td { padding: 0 0 8px; }
  .col-n, .col-r { width: 100%; }
}
.row-x {
  border: 0; background: none; color: var(--ink-3); cursor: pointer;
  font-size: 18px; line-height: 1; padding: 6px 8px; border-radius: 6px;
  position: relative;
}
/* It deletes a line of an invoice and it was 27 by 30, in a table cell, on a phone.
   Growing the button would widen the column on the narrowest screen, so the hit area
   grows instead and the button stays the size it looks. */
.row-x::after { content: ""; position: absolute; inset: -7px -9px; }
.row-x:hover { background: var(--line-2); color: var(--bad); }
.gen-foot {
  padding: 16px 20px; border-top: 1px solid var(--line); background: var(--paper-2);
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
}
.gen-total { margin-left: auto; font-size: 15px; font-weight: 680; }
.gen-total span { color: var(--ink-3); font-weight: 550; font-size: 13px; }
/* Empty until there is something to say, so it takes no room on a fresh form. */
.gen-saved { font-size: 13px; color: var(--ink-3); }

/* ---------- the wording, on the pages that have no builder ----------
   Three statutory notices in this site prescribe a block of words with no
   blanks in it. There is nothing to fill in, so those pages show the wording
   instead of a form and offer to copy it, because the statute's own first
   choice is that the contractor put the words INTO the written contract.

   It borrows the builder's outer box on purpose: same border, same head, same
   place in the page, so a reader moving between two forms in one state does not
   feel like they landed somewhere else. Inside, it is set as paper. */

.words { border: 1px solid var(--line); border-radius: var(--radius); margin: 28px 0; overflow: hidden; }
.words-head {
  padding: 16px 20px; border-bottom: 1px solid var(--line); background: var(--paper-2);
  display: flex; gap: 12px 14px; align-items: center; flex-wrap: wrap;
}
.words-head h2 { margin: 0; font-size: 17px; flex: 1 1 100%; }
.words-head p { margin: 0; font-size: 13.5px; color: var(--ink-3); flex: 1 1 260px; }
.words-said { font-size: 13px; font-weight: 600; color: var(--accent); }

.words-body { padding: 24px 22px; background: #fff; }
.words-body > * { max-width: 62ch; }
.words-body p { margin: 0 0 14px; font-size: 14.5px; line-height: 1.62; }
.words-body > :last-child { margin-bottom: 0; }

/* The statute asks for at least 10-point bold type if printed, or capital
   letters if typewritten. The PDF takes the size floor and the bold face; on
   screen the face is what carries, so a b block is bold here too. */
.words-body .w-b { font-weight: 640; color: var(--ink); }
.words-body .w-h { margin: 0 0 14px; font-size: 15px; letter-spacing: .01em; }
.words-body .w-s { font-weight: 640; }
.words-body .w-c { font-size: 12.5px; font-style: italic; color: var(--ink-3); margin-top: -8px; }

/* A signature rule the sheet draws rather than the statute. Tex. Prop. Code
   53.256(d) requires the owner to sign the waiver and then prints the waiver
   with no line under it, so the sheet supplies one and the page draws the same
   one: the page and the download have to be the same document. The caption sits
   beside the rule, not under it, which is what the PDF does, and the rule is
   capped at 300px because that is the width the PDF gives it. Both carry
   data-nocopy, so the copy button never picks the caption up as statute text. */
.words-body .w-sp { height: 16px; }
.words-body .w-r {
  display: flex; align-items: flex-end; gap: 12px;
  margin: 0 0 16px; max-width: 62ch;
}
.words-body .w-rule {
  flex: 1 1 auto; max-width: 300px; height: 1.5em;
  border-bottom: 1px solid var(--ink-3);
}
.words-body .w-rcap {
  flex: 0 0 auto; font-size: 12px; color: var(--ink-3);
  line-height: 1.4; padding-bottom: 1px;
}

@media (max-width: 560px) {
  .words-body { padding: 18px 16px; }
  /* Under 560px a 300px rule plus its caption stops fitting, so the caption
     drops below the rule. The PDF does the same thing when a caption is too
     long for the space beside it. */
  .words-body .w-r { flex-wrap: wrap; gap: 4px 12px; }
  .words-body .w-rule { flex-basis: 100%; max-width: 100%; }
  .words-head { padding: 14px 16px; }
  .words-head .btn { width: 100%; justify-content: center; }
}

/* The customer book, a row of names at the top of the customer block. Hidden outright
   until one has been saved, because an empty "Recent" label is a promise not kept. The
   name and the forget cross are two buttons inside one pill, so the pill reads as one
   thing and the cross is still its own target. */
.cb-row { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }
.cb-lab { font-size: 12.5px; color: var(--ink-3); }
.cb-one {
  display: inline-flex; align-items: stretch; border: 1px solid var(--line);
  background: var(--paper-2); border-radius: 999px; overflow: hidden;
}
.cb-one:hover { border-color: var(--accent); background: var(--accent-soft); }
.cb-chip, .cb-x {
  font: inherit; font-size: 13px; line-height: 1; border: 0; background: none;
  color: var(--ink-2); cursor: pointer;
}
.cb-chip { padding: 9px 5px 9px 13px; max-width: 15em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cb-chip:hover { color: var(--accent); }
.cb-x { padding: 9px 12px 9px 5px; color: var(--ink-3); font-size: 15px; }
.cb-x:hover { color: var(--ink); }
.cb-chip:focus-visible, .cb-x:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The running number, one line under the number box. It is a suggestion and it is
   dressed as one: muted text, the next number as a small button, then a cross to forget
   that series. Hidden until a number has been downloaded once. */
.nn-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; }
.nn-lab { font-size: 12.5px; color: var(--ink-3); }
.nn-use, .nn-x {
  font: inherit; font-size: 12.5px; line-height: 1; cursor: pointer; border-radius: 999px;
  border: 1px solid var(--line); background: var(--paper-2); color: var(--ink-2); padding: 6px 11px;
}
.nn-use:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.nn-x { padding: 6px 9px; color: var(--ink-3); font-size: 14px; }
.nn-x:hover { color: var(--ink); border-color: var(--ink-3); }
.nn-use:focus-visible, .nn-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The estimate that won, a row of cards at the very top of an invoice form. Two lines
   per card, because a customer name alone does not say which estimate and a number
   alone does not say which customer. The label takes a whole line of its own so the
   cards never get squeezed by it on a phone. */
.es-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 16px; }
.es-lab { flex: 1 1 100%; font-size: 12.5px; color: var(--ink-3); }
.es-one {
  display: inline-flex; align-items: stretch; border: 1px solid var(--line);
  background: var(--paper-2); border-radius: var(--r-sm); overflow: hidden;
}
.es-one:hover { border-color: var(--accent); background: var(--accent-soft); }
.es-chip, .es-x { font: inherit; border: 0; background: none; cursor: pointer; color: var(--ink-2); }
.es-chip {
  display: flex; flex-direction: column; gap: 2px; text-align: left;
  padding: 8px 6px 8px 12px; max-width: 17em; overflow: hidden;
}
.es-chip b {
  font-size: 13px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.es-chip span { font-size: 12px; color: var(--ink-3); }
.es-one:hover .es-chip b { color: var(--accent); }
.es-x { padding: 8px 11px 8px 6px; color: var(--ink-3); font-size: 15px; line-height: 1; }
.es-x:hover { color: var(--ink); }
.es-chip:focus-visible, .es-x:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.privacy-note { font-size: 12.5px; color: var(--ink-3); margin: 10px 0 0; }

/* ---------- content ---------- */
.prose { max-width: var(--measure); }
.prose ul, .prose ol { margin: 0 0 16px; padding-left: 20px; color: var(--ink-2); }
.prose li { margin: 0 0 7px; }
.checklist { list-style: none; padding: 0; }
.checklist li { position: relative; padding-left: 26px; margin: 0 0 9px; }
.checklist li::before {
  content: ""; position: absolute; left: 3px; top: 8px; width: 10px; height: 5px;
  border-left: 2px solid var(--good); border-bottom: 2px solid var(--good);
  transform: rotate(-45deg);
}
.callout {
  border: 1px solid var(--warn-line); background: var(--warn-bg);
  border-radius: var(--radius); padding: 14px 16px; margin: 22px 0; font-size: 14.5px;
}
.callout strong { color: var(--ink); }
.callout p:last-child { margin: 0; }

/* The Spanish glossary. Two columns on a desktop, stacked on a phone, and the
   split is the meaning of the block: on the left is what is PRINTED on the form,
   in English, byte for byte out of the statute; on the right is what it means.
   The left column is set in the mono stack and tinted, because it is a quotation
   from a document and not the page talking. Grid rather than float so a long
   English phrase and a long Spanish sentence stay on the same row.

   Note the class. `.dl` was already taken by the download block, and `dl` as a
   bare element selector would have reached into anything else that ever uses one. */
/* Sits between the header and the article, full bleed so it reads as site
   furniture rather than as part of the page's own content, which is what it is:
   a route to the same page in the other language. Never printed. */
.langbar { border-bottom: 1px solid var(--line); background: var(--accent-soft); }
.langbar .wrap { padding: 9px 20px; }
.langbar a {
  font-size: 13.5px; font-weight: 600; color: var(--accent);
  text-decoration: none; display: inline-block; min-height: 24px;
}
.langbar a:hover { text-decoration: underline; }
@media print { .langbar { display: none; } }

.gloss {
  display: grid; grid-template-columns: minmax(0, 34%) minmax(0, 1fr);
  gap: 0; margin: 18px 0 26px;
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
}
.gloss dt, .gloss dd { margin: 0; padding: 11px 14px; border-top: 1px solid var(--line); }
.gloss dt:first-of-type, .gloss dt:first-of-type + dd { border-top: 0; }
.gloss dt {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px; line-height: 1.5; color: var(--accent);
  background: var(--paper-2); font-weight: 600;
  overflow-wrap: anywhere;
}
.gloss dd { font-size: 14.5px; line-height: 1.6; color: var(--ink-2); }
@media screen and (max-width: 680px) {
  /* A 34% column cannot hold a phrase like CONDITIONAL WAIVER AND RELEASE ON
     PROGRESS PAYMENT at 390px, so below this width the pair stacks and the
     English sits directly above its own translation. */
  .gloss { grid-template-columns: 1fr; }
  .gloss dt { border-top: 1px solid var(--line); padding-bottom: 7px; }
  .gloss dt:first-of-type + dd { border-top: 0; }
  .gloss dd { border-top: 0; padding-top: 0; background: var(--paper-2); }
}

.statute-src { margin: -12px 0 22px; font-size: 13.5px; color: var(--ink-3); }
.statute-src .host {
  margin-left: 8px; padding: 1px 6px; border: 1px solid var(--line);
  border-radius: 5px; background: var(--paper-2); font-size: 12px; color: var(--ink-3);
}

/* The statute quote on a state law page. The wording is the whole point of the
   page, so it is set apart from the prose rather than run into it. */
.statute-quote {
  margin: 22px 0; padding: 15px 18px; border-left: 3px solid var(--accent);
  background: var(--paper-2); border-radius: 0 var(--radius) var(--radius) 0;
}
.statute-quote .q-label {
  display: block; font-size: 12.5px; letter-spacing: .02em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 8px;
}
.statute-quote .q-text { font-size: 16.5px; line-height: 1.55; color: var(--ink); font-style: italic; }
.statute-quote .q-note { margin: 11px 0 0; font-size: 14px; color: var(--ink-2); }

details.faq { border-bottom: 1px solid var(--line); padding: 14px 0; }
details.faq summary { cursor: pointer; font-weight: 620; font-size: 16px; list-style: none; color: var(--ink); padding: 9px 0; }
details.faq summary::-webkit-details-marker { display: none; }
details.faq summary::before { content: "+ "; color: var(--accent); font-weight: 700; }
details.faq[open] summary::before { content: "- "; }
details.faq p { margin: 10px 0 0; font-size: 15px; }

/* ---------- cards and lists ----------
   The card is the unit this whole site is browsed in, so it is the one thing
   worth spending pixels on. Icon tile, title, one line of plain English. The
   icon and the tint are stamped on in build.mjs from the href, so a card added
   later gets both without anyone remembering to ask. */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(248px, 1fr)); gap: 14px; margin: 22px 0; }
.card {
  position: relative; isolation: isolate;
  border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 18px 17px;
  text-decoration: none; color: inherit; display: block; background: var(--paper);
  box-shadow: var(--sh-1);
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
/* The tinted wash is a pseudo element rather than a background on the card,
   because the card already carries a gradient-free white and a wash painted on
   top of it would have to be re-stated for every tint. */
.card::before {
  content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit;
  background: linear-gradient(160deg, var(--t-bg), transparent 62%);
  opacity: 0; transition: opacity .15s;
}
.card:hover { border-color: var(--t-fg); box-shadow: var(--sh-2); transform: translateY(-2px); }
.card:hover::before { opacity: .7; }
.card:focus-visible { outline: 2px solid var(--t-fg); outline-offset: 2px; }
.card .card-ic {
  display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 10px; margin: 0 0 13px;
  background: var(--t-bg); color: var(--t-fg);
  transition: transform .15s;
}
.card .card-ic .ci { width: 21px; height: 21px; }
.card:hover .card-ic { transform: scale(1.06); }
.card b { display: block; font-size: 15.5px; margin: 0 0 4px; letter-spacing: -0.014em; }
.card span { font-size: 13.5px; color: var(--ink-3); line-height: 1.5; display: block; }

/* A dense list of forty state links does not want forty icon tiles, so any grid
   marked compact drops to a single tidy row per card. */
.cards.compact { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; }
.cards.compact .card { padding: 13px 15px; }
.cards.compact .card-ic { width: 28px; height: 28px; border-radius: 8px; margin: 0 0 9px; }
.cards.compact .card-ic .ci { width: 16px; height: 16px; }
.cards.compact .card b { font-size: 14.5px; }
.cards.compact .card span { font-size: 12.5px; }

.pill-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 0; }
.pill {
  border: 1px solid var(--line); border-radius: 100px; padding: 9px 15px;
  font-size: 13.5px; text-decoration: none; color: var(--ink-2); background: var(--paper);
  box-shadow: var(--sh-1); transition: border-color .13s, color .13s, background .13s;
}
.pill:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* The toolbox hub group headings are anchor targets for the rail on every talk page.
   The header is sticky, so without this the jump lands the heading underneath it. Same
   reason and same number as .gen and .ex > h2 above. */
.tg-head { scroll-margin-top: 78px; }

/* ---------- rail ---------- */
.rail-box {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--paper); box-shadow: var(--sh-1);
  padding: 16px 18px; margin: 0 0 16px;
}
.rail-box h3 {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 10px; padding: 0 0 9px; border-bottom: 1px solid var(--line-2);
  font-size: 12px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); font-weight: 640;
}
.rail-box h3::before {
  content: ""; width: 3px; height: 13px; border-radius: 2px; flex: none;
  background: var(--accent);
}
/* Every rail box is a details element. See foldRail in src/layout.mjs for why. With no
   JavaScript they are all open, which is the old behaviour, so this only has to make a
   summary look like the heading it replaced and give it something to show it can close. */
details.rail-box > summary { display: block; cursor: pointer; list-style: none; }
details.rail-box > summary::-webkit-details-marker { display: none; }
details.rail-box:not([open]) > summary > h3 { margin-bottom: 0; border-bottom: 0; }
details.rail-box > summary > h3::after {
  content: ""; margin-left: auto; flex: none; width: 7px; height: 7px;
  border-right: 2px solid var(--ink-3); border-bottom: 2px solid var(--ink-3);
  transform: translateY(-2px) rotate(45deg); transition: transform .15s;
}
details.rail-box[open] > summary > h3::after { transform: translateY(1px) rotate(-135deg); }

.rail-box ul { list-style: none; margin: 0; padding: 0; }
.rail-box li { margin: 0; font-size: 14px; }
.rail-box li + li { border-top: 1px solid var(--line-2); }
.rail-box a {
  display: block; padding: 7px 0; text-decoration: none; color: var(--ink-2);
  transition: color .12s, padding-left .12s;
}
.rail-box a:hover { color: var(--accent); padding-left: 4px; }
/* On a desktop these are a scannable column read with a mouse. Once the rail drops
   under the article they are a phone menu, and 37px tall with no space between them is
   a column of near misses. The measurement that found this found 518 of them across
   twenty one pages, which is the largest single tap target fault on the site and
   invisible from a desktop browser. (It said "twenty nine links deep" when it was
   written. A talk page rail is twelve now, because it stopped listing every talk on
   the site. Do not put a number in this comment again.) */
@media (max-width: 980px) {
  .rail-box a { padding: 11px 0; }
  /* The summary is the only way into a folded box, so it is a tap target like the links
     are, and it gets measured like one. 15px each side of a 12px heading is 44. */
  details.rail-box > summary > h3 { padding-top: 15px; padding-bottom: 15px; }
  /* The box already has 16px of its own padding above the heading. Once the heading
     carries 15 of its own that reads as a gap rather than a margin. */
  details.rail-box { padding-top: 2px; }
}

/* ---------- ads. Banner and in-page only. Never popunder, vignette or push. ---------- */
.ad-slot { margin: 24px 0; text-align: center; min-height: 0; }
.ad-slot:empty { display: none; }
.ad-slot::before {
  content: "Advertisement"; display: block; font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3); margin: 0 0 6px;
}
.ad-slot:empty::before { display: none; }

.pill-more { margin: 14px 0 26px; font-size: 14px; }

/* ---------- calculators ---------- */
.calc { border: 1px solid var(--line); border-radius: var(--radius); margin: 28px 0; overflow: hidden; }
.calc:empty { display: none; }
.calc-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); }
.calc-left { padding: 20px; border-right: 1px solid var(--line); }
.calc-left h2 { margin: 0 0 14px; font-size: 17px; letter-spacing: -0.015em; }
.calc-right { padding: 20px; background: var(--paper-2); display: flex; flex-direction: column; }
.calc-form { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
.cf label { white-space: normal; }
.cf-in { display: flex; align-items: stretch; }
.cf-in input { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.cf-unit {
  display: flex; align-items: center; padding: 0 10px; font-size: 12.5px; color: var(--ink-3);
  border: 1px solid var(--line); border-left: 0; border-radius: 0 7px 7px 0; background: var(--paper-2);
  white-space: nowrap;
}
.calc-note { font-size: 12.5px; color: var(--ink-3); margin: 14px 0 0; }
.calc-out { flex: 1; }
.calc-err { color: var(--bad); font-size: 14px; margin: 0; }

.calc-headline {
  border: 1px solid var(--accent); background: var(--accent-soft); border-radius: var(--radius);
  padding: 14px 16px; margin: 0 0 16px;
}
.ch-label { display: block; font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); font-weight: 640; margin: 0 0 3px; }
.ch-value { font-size: 30px; font-weight: 700; letter-spacing: -0.025em; line-height: 1.1; color: var(--ink); }
.ch-unit { font-size: 14px; color: var(--ink-2); margin-left: 6px; }

/* The pinned copy of the answer. Drawn by calc.js as the last child of the input
   column, and shown only where it is needed, which is the width at which the result
   block stops being beside the fields and goes underneath them. */
.calc-sticky { display: none; }

.calc-warn {
  background: var(--warn-bg); border: 1px solid var(--warn-line); border-radius: 8px;
  padding: 9px 12px; font-size: 13.5px; color: var(--ink-2); margin: 0 0 12px;
}

table.calc-table { width: 100%; border-collapse: collapse; font-size: 14px; }
table.calc-table th {
  text-align: left; font-weight: 550; color: var(--ink-2); padding: 8px 12px 8px 0;
  border-bottom: 1px solid var(--line); vertical-align: top;
}
table.calc-table td {
  text-align: right; font-weight: 650; color: var(--ink); padding: 8px 0;
  border-bottom: 1px solid var(--line); white-space: nowrap; vertical-align: top;
}
table.calc-table tr:last-child th, table.calc-table tr:last-child td { border-bottom: 0; }
.ct-hint { display: block; font-size: 12px; font-weight: 450; color: var(--ink-3); white-space: normal; margin: 2px 0 0; }

table.calc-table-wide { margin: 18px 0 0; }
/* Bottom, because on a phone these headings wrap and the wrapping is not
   optional. The concrete slab chart squeezes "60 lb bags at 4 in" into a 42px
   column and it comes down in five lines, which sets the whole row at 98px; left
   at the default the five short headings beside it hang at the top of that row
   with 80px of nothing under them and the head reads as a ragged column rather
   than a row. Aligned to the bottom every heading lands on the same line, just
   above the rule, and the block reads as one header again.

   Not solved by refusing to wrap. Measured on that page: nowrap takes the head
   from 98px to 25px and the table from 374px to 533px inside a 335px box, which
   trades 73px of height nobody was going to scroll for 160px of sideways scroll
   that hides the last two columns of numbers outright. The numbers are the page.
   The heading can wrap. */
table.calc-table-wide thead th {
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3);
  font-weight: 620; text-align: right; padding: 0 0 7px 12px;
  vertical-align: bottom;
}
table.calc-table-wide thead th:first-child { text-align: left; padding-left: 0; }
table.calc-table-wide td { padding: 7px 0 7px 12px; font-weight: 550; }
table.calc-table-wide td:first-child { text-align: left; padding-left: 0; font-weight: 650; }

.tbl-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.calc-actions { display: flex; gap: 10px; flex-wrap: wrap; margin: 18px 0 0; }

.worked { border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 20px; margin: 0 0 24px; }
.worked h3 { margin: 20px 0 8px; font-size: 14.5px; }
.worked-given { list-style: none; margin: 0 0 16px; padding: 0 0 14px; border-bottom: 1px solid var(--line); display: grid; grid-template-columns: 1fr 1fr; gap: 5px 18px; }
.worked-given li { display: flex; justify-content: space-between; gap: 12px; font-size: 13.5px; margin: 0; min-width: 0; }
.worked-given span { color: var(--ink-3); }
/* The value used to carry nowrap, to stop "1600 sq ft" breaking between the
   number and its unit. That is right for a number and wrong for a sentence, and
   one given here is a sentence: the BTU calculator prints the climate zone as
   "Zone 4, mixed, mid Atlantic and the lower Midwest", which sets 309px on one
   line. Because the row is a grid item, that width became the track's floor and
   pushed the whole document to 427px inside a 375px phone, so ten pages scrolled
   sideways. normal only breaks when it has to, so "8 ft" still never breaks,
   and text-align keeps a value that does break sitting against the right edge
   where the single line already sat. min-width on the row is the guard: a grid
   item may now shrink below its content, so no future long value can widen the
   page again. */
.worked-given b { font-weight: 620; text-align: right; }
.worked-head { margin: 0 0 14px; display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.worked-head span { font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); font-weight: 640; }
.worked-head b { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; }
.worked-head i { font-style: normal; font-size: 14px; color: var(--ink-2); }
.callout-doc { border-color: var(--accent); background: var(--accent-soft); }

/* The preset pages answer the question in the h1 before the calculator loads, so the
   answer has to read as the answer and not as a widget that has not booted yet. */
.answer {
  border: 1px solid var(--accent); border-radius: var(--radius);
  background: var(--accent-soft); padding: 18px 20px; margin: 0 0 22px;
}
.answer-lab { margin: 0; font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); font-weight: 640; }
.answer-val { margin: 4px 0 0; font-size: 38px; line-height: 1.1; font-weight: 720; letter-spacing: -0.03em; color: var(--ink); }
.answer-val i { font-style: normal; font-size: 17px; font-weight: 600; color: var(--ink-2); }
.answer-sub { margin: 8px 0 0; font-size: 14px; color: var(--ink-2); max-width: var(--measure); }
/* The row the page was built for, so the comparison reads against a fixed point. */
table.calc-table-wide tr.is-now td { background: var(--accent-soft); }
table.calc-table-wide tr.is-now td:first-child { color: var(--accent-dark); }

/* A wide chart table is read one way: find your row down the left, then scan
   across it. So the row label leaving the screen is not a cosmetic problem, it
   is the whole failure. Measured at 360px on the air conditioner chart, the
   first table hides 54px and the second 85px, and the moment you scroll either
   one the label column is gone and the numbers under your thumb belong to a row
   you can no longer name.

   Sticky pins it. Three things it needs and each one is load bearing: an opaque
   background, or the scrolling columns show through the label; a z-index, or the
   later cells in source order paint over it; and the hairline on the right drawn
   as a box-shadow rather than a border, because a collapsed border on a sticky
   cell is dropped by the collapse algorithm in Chrome and the column would read
   as a rendering fault instead of a pinned edge.

   The highlighted row keeps its tint without being restated here:
   `table.calc-table-wide tr.is-now td` is (0,2,3) against this rule's (0,2,2),
   so accent-soft already wins and is already opaque. */
@media (max-width: 720px) {
  table.calc-table-wide thead th:first-child,
  table.calc-table-wide td:first-child {
    position: sticky; left: 0; z-index: 1;
    background: var(--paper);
    padding-right: 14px;
    box-shadow: 1px 0 0 var(--line);
  }
}

/* And the table has to look scrollable before it is touched, which at 374px
   inside 320px it does not: it reads as a table that very nearly fits. The
   shadow is attached scroll, so it sits against the container's right edge and
   stays there; the paper cover is attached local, so it travels with the content
   and lands on top of the shadow exactly when the last column arrives. The
   affordance is therefore present only while there is something left to see,
   with no script and no measurement. The cover is listed first because
   with no script and no measurement. The cover is listed first because
   background layers paint front to back.

   The shadow was written as a literal dark ink and that quietly made it a light
   theme affordance only: rgba(14, 19, 25, .14) laid over the dark theme's own
   #0d1117 paper comes back as #0d1117, so on 660 pages a phone in dark mode had
   no affordance at all and the table simply ended. It reads --sh-edge now, which
   is ink in the light theme and a thin white in the dark one, because what the
   eye is being shown is an edge and not the shadow of anything. */
.tbl-scroll {
  background:
    linear-gradient(to left, var(--paper) 45%, rgba(0, 0, 0, 0)) right center / 22px 100% no-repeat local,
    radial-gradient(farthest-side at 100% 50%, var(--sh-edge), rgba(0, 0, 0, 0)) right center / 10px 100% no-repeat scroll;
}

/* A drawing and a filmstrip scroll sideways on a phone for the same reason the
   table above does, and neither of them can use the table's answer. That one is a
   pair of background layers, and a background paints behind the element's own
   content: under a drawing whose panels are filled shapes there is nothing left
   to see, and under a row of page thumbnails there is less.

   So they say it with the bar itself. On a phone a scrollbar is an overlay, drawn
   while a finger is moving and then faded, so what a reader meets is a drawing cut
   down the middle with no reason to believe there is more to the right of it.
   Styling the bar is what takes it out of overlay mode on WebKit, which keeps it
   on screen, and the length of the thumb is the honest answer to how much is
   still off to the side. Measured on the build this went in on, 647 of 892 pages
   carry a drawing. Proved at 375px on /redact-a-pdf/, where the drawing box holds
   305 of 460 units and the thumb comes out at two thirds of the width, sitting on
   screen with nothing touching the page.

   Both spellings on purpose. Chrome 121 and Firefox read scrollbar-width and
   scrollbar-color, older Chrome and every iPhone read the ::-webkit- rules, and
   where both are understood the standard pair wins and comes out the same shape.
   A container whose content fits shows no bar either way, so a desktop pays
   nothing for this. The strip said scrollbar-width on its own before this rule
   existed; it is said here now, once, for both. */
.sc-strip,
.dg-scroll {
  scrollbar-width: thin;
  scrollbar-color: var(--ink-3) var(--line);
}
.sc-strip::-webkit-scrollbar,
.dg-scroll::-webkit-scrollbar { height: 6px; }
.sc-strip::-webkit-scrollbar-track,
.dg-scroll::-webkit-scrollbar-track { background: var(--line); border-radius: 99px; }
.sc-strip::-webkit-scrollbar-thumb,
.dg-scroll::-webkit-scrollbar-thumb { background: var(--ink-3); border-radius: 99px; }

/* The same table with sentences in it. Everything above this point assumes a
   chart of numbers, which is why the cells never wrap, and the terms lane broke
   that assumption: draw-schedule's middle column measured 419px at a 375px
   screen, every sentence was cut mid word, and the column carrying the actual
   percentage was off screen with only a sticky "Draw 1" left to read.

   Above 560px the fix is only that the cells wrap. Below it, a three column
   table of sentences cannot be made to fit at any font size worth reading, so
   each row becomes its own block and every value is printed under its column
   heading, taken from data-label. Nothing scrolls sideways any more, so the
   wrapper's scroll affordance and the sticky first column both have to be shut
   off with it, and the wrapper carries its own class to do that, because the
   alternative is :has() and :has() is not starting here.

   `screen and` deliberately. A4 minus its margins is under 720 CSS pixels, which
   is why the sticky rule above needs a print override. 560 sits below that and
   would not match paper today, but a stacked table is a phone layout and must
   never reach print by accident. */
table.calc-table-prose td { white-space: normal; text-align: left; }
table.calc-table-prose td:last-child,
table.calc-table-prose thead th:last-child { text-align: right; }
table.calc-table-prose thead th { text-align: left; }

@media screen and (max-width: 560px) {
  .tbl-stack { overflow: visible; background: none; }
  table.calc-table-prose thead { display: none; }
  table.calc-table-prose,
  table.calc-table-prose tbody,
  table.calc-table-prose tr,
  table.calc-table-prose td { display: block; width: auto; }
  table.calc-table-prose tr { border-bottom: 1px solid var(--line); padding: 12px 0; }
  table.calc-table-prose tr:last-child { border-bottom: 0; }
  /* Repeated at the same specificity as the sticky rule, and later in the file,
     which is the only thing that beats it. */
  table.calc-table-prose td,
  table.calc-table-prose td:first-child,
  table.calc-table-prose td:last-child {
    position: static; box-shadow: none; border: 0;
    padding: 2px 0 0; text-align: left;
  }
  table.calc-table-prose td:first-child {
    padding: 0; font-weight: 650; font-size: 15.5px;
  }
  table.calc-table-prose td::before {
    content: attr(data-label); display: block;
    font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
    color: var(--ink-3); font-weight: 620; margin: 8px 0 1px;
  }
  table.calc-table-prose td:first-child::before { content: none; }
}

@media (max-width: 560px) {
  .answer-val { font-size: 30px; }
}

@media (max-width: 860px) {
  .calc-grid { grid-template-columns: minmax(0, 1fr); }
  .calc-left { border-right: 0; border-bottom: 1px solid var(--line); }
}

/* The answer follows the fields down the screen. Screen only, and the width is the one
   at which the result block stops sitting beside the fields and goes underneath them.
   A sheet of A4 is 794 CSS pixels wide, so a plain max-width block here would also be a
   print rule and would have printed a result table with the result missing off the top
   of it. */
@media screen and (max-width: 860px) {
  /* overflow: hidden makes .calc a scrolling container, and a scrolling container is
     what position: sticky sticks to. Inside one that cannot scroll, a sticky child
     never moves, so the whole feature silently does nothing. clip does the same corner
     clipping for the rounded border without creating one. */
  .calc { overflow: clip; }

  /* One headline, not two. The pinned bar replaces the card rather than repeating it,
     which is also what keeps the two of them out of each other's way in the moment the
     bottom of the field column scrolls into view. display: none on whichever one is not
     in use takes it out of the accessibility tree with it, so a screen reader hears the
     answer exactly once either way. An embed keeps the card: it is a panel a few hundred
     pixels tall on somebody else's page, with no keyboard covering it and no room to
     spend on a bar. */
  body:not(.is-embed) .calc-headline { display: none; }

  .calc-sticky {
    display: flex; align-items: center; gap: 8px;
    position: sticky; bottom: 0; z-index: 5;
    margin: 16px -20px -20px; padding: 10px 20px;
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--accent); background: var(--accent-soft);
  }
  .calc-sticky[hidden] { display: none; }
  /* The label is the question the number answers, so cutting it off is worse than spending a
     second line on it. Several of these calculators produce labels longer than the 228px the bar
     leaves at 375, and "Days left to record the claim of li" is not an answer. Two lines of 11px
     type fit inside the height the 21px number already takes, so the bar does not grow at all,
     and centering is what keeps the two level once the label stops being a single line sitting
     on the number's baseline. Anything longer than two lines still ends in an ellipsis. */
  .cs-label {
    font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
    color: var(--ink-3); font-weight: 640; line-height: 1.3;
    min-width: 0; overflow: hidden;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  }
  .cs-value {
    margin-left: auto; white-space: nowrap;
    font-size: 21px; font-weight: 700; letter-spacing: -0.02em; color: var(--ink);
  }
  .cs-unit { white-space: nowrap; font-size: 13px; color: var(--ink-2); }
}
@media (max-width: 560px) {
  .calc-form { grid-template-columns: 1fr; }
  .worked-given { grid-template-columns: 1fr; }
  .ch-value { font-size: 26px; }
}

/* ---------- embeds ---------- */
.embed-box { border: 1px dashed var(--line); border-radius: var(--radius); padding: 18px 20px; margin: 28px 0; background: var(--paper-2); }
.embed-box h2 { margin: 0 0 6px; font-size: 16px; }
.embed-box p { margin: 0 0 12px; font-size: 13.5px; color: var(--ink-3); }
.embed-code {
  width: 100%; font: 12px/1.5 var(--mono); color: var(--ink-2); background: var(--paper);
  border: 1px solid var(--line); border-radius: 7px; padding: 10px 12px; margin: 0 0 12px;
  resize: vertical; min-height: 0; white-space: pre; overflow-x: auto;
}
body.is-embed { margin: 0; background: transparent; }
body.is-embed .calc { margin: 0; border-radius: 0; border: 0; }
body.is-embed .calc-left { padding: 16px; }
body.is-embed .calc-right { padding: 16px; }
body.is-embed .calc-sticky { display: none; }
.embed-wrap { padding: 0; }
.embed-credit {
  margin: 0; padding: 9px 16px; font-size: 12px; color: var(--ink-3);
  border-top: 1px solid var(--line); background: var(--paper);
}
.embed-credit a { color: var(--accent); font-weight: 600; text-decoration: none; }

/* ---------- hero ---------- */
/* The background is two radial washes and a grid drawn in CSS, not an image, so the
   homepage still paints with zero extra requests. The grid is masked to fade out
   downward, which is what stops it reading as graph paper. */
.hero {
  position: relative; overflow: hidden;
  padding: 52px 0 18px; border-bottom: 1px solid var(--line-2);
  background:
    radial-gradient(900px 340px at 12% -10%, rgba(26, 86, 219, .09), transparent 62%),
    radial-gradient(700px 320px at 92% 0%, rgba(13, 148, 136, .07), transparent 60%);
}
.hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image:
    linear-gradient(var(--line-2) 1px, transparent 1px),
    linear-gradient(90deg, var(--line-2) 1px, transparent 1px);
  background-size: 34px 34px;
  -webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, .55), transparent 72%);
  mask-image: linear-gradient(180deg, rgba(0, 0, 0, .55), transparent 72%);
  opacity: .8;
}
.hero > * { position: relative; }
.hero h1 { margin: 0 0 12px; max-width: 18ch; font-size: clamp(30px, 5.2vw, 44px); line-height: 1.12; letter-spacing: -0.032em; }
.hero .lede { font-size: 19px; max-width: 60ch; margin: 0 0 22px; }
.eyebrow {
  display: inline-flex; align-items: center; gap: 7px;
  margin: 0 0 14px; padding: 5px 12px 5px 10px;
  border: 1px solid var(--eyebrow-line); border-radius: 999px; background: var(--accent-soft);
  font-size: 12px; font-weight: 640; letter-spacing: .02em; color: var(--accent-dark);
}
.eyebrow::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--dot); }
.hero-cta { display: flex; gap: 10px; flex-wrap: wrap; }
.hero-cta .btn { padding: 12px 20px; font-size: 15px; }

/* The hero split: copy on the left, the paper illustration on the right. It collapses
   to one column at 860px, and on a phone the illustration stays, smaller and centred,
   below the buttons. It used to be dropped entirely on the argument that it pushed the
   first real link below the fold. It does not: it comes after the buttons in the markup,
   so what it pushes down is the second screen. A phone is where most of this traffic
   lands, and a hero with no picture on it is the flattest first impression the site can
   make. */
.hero-split { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); gap: 34px; align-items: center; }
.hero-art { justify-self: end; width: 100%; max-width: 440px; }
.hero-art svg { display: block; width: 100%; height: auto; }

/* The illustration's palette. Not one colour lives in the markup, for the reason
   written at the top of src/heroart.mjs: an SVG presentation attribute cannot read a
   custom property, so a drawing coloured by attribute is a drawing that never goes
   dark. Same arrangement as .dg, and the class names are deliberately the same shape. */
.ha .f-p { fill: var(--paper); }
.ha .f-ln { fill: var(--line); }
.ha .f-ln2 { fill: var(--line-2); }
.ha .f-a { fill: var(--accent); }
.ha .f-soft { fill: var(--accent-soft); }
.ha .f-ink2 { fill: var(--ink-2); }
.ha .f-good { fill: var(--good); }
.ha .s-ln { stroke: var(--line); }
.ha .s-good { stroke: var(--good); }

/* Paper on the page colour, on purpose: the sheet is held by its outline, the way a
   diagram sheet is. .f-on is whatever contrasts with the accent, which is the page
   colour in both themes, near white under a deep blue chip and near black under a
   pale one. */
.ha .f-on { fill: var(--paper); }
.ha .s-on { stroke: var(--paper); }

/* Two steps the token set does not have as their own names: a grey a shade darker
   than a rule, and an accent a shade weaker than the accent. */
.ha .f-mid { fill: var(--ink-3); opacity: .45; }
.ha .s-mid { stroke: var(--ink-3); opacity: .45; }
.ha .f-a2 { fill: var(--accent); opacity: .42; }

/* The sheet's drop shadow, drawn as an offset rectangle because an SVG filter costs a
   raster pass on the largest element above the fold. In daylight it is ink at five
   percent, which is a shadow. At night --ink is near white, so the same five percent
   becomes a faint rim along the two edges the light would catch, which is how depth is
   drawn in a dark interface anyway. One rule, correct twice. */
.ha .f-sh { fill: var(--ink); opacity: .05; }

.ha .g-p1 { stop-color: var(--paper); }
.ha .g-p2 { stop-color: var(--paper-2); }
.ha .g-a1 { stop-color: var(--accent); }
.ha .g-a2 { stop-color: var(--accent-dark); }

.ha .t-mono { font-family: var(--mono); }

.stat-row { display: flex; gap: 10px; flex-wrap: wrap; margin: 24px 0 32px; }
.stat {
  display: flex; flex-direction: column; gap: 1px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--glass-2); padding: 9px 14px; box-shadow: var(--sh-1);
}
.stat b { display: block; font-size: 15px; letter-spacing: -0.01em; }
.stat span { font-size: 12.5px; color: var(--ink-3); }

@media (max-width: 860px) {
  .hero-split { grid-template-columns: 1fr; gap: 26px; }
  .hero-art { justify-self: start; max-width: 380px; }
}
@media (max-width: 620px) {
  .hero { padding: 34px 0 14px; }
  .hero-split { gap: 18px; }
  .hero-art { justify-self: center; max-width: 300px; }
  /* The three proof cards are a wrapping flex row, so on a phone each one takes
     the width of its own sentence and they stack at three different widths, 394,
     474 and 410 at 375px. A row of ragged cards reads as a layout that broke
     rather than one that was decided, and these three are the first thing under
     the buttons, which is the worst place on the site to look accidental. One
     column makes them one width. The flex column keeps the wrap behaviour above
     this breakpoint untouched. */
  .stat-row { flex-direction: column; align-items: stretch; gap: 8px; margin: 22px 0 28px; }
}

/* ---------- footer ---------- */
.site-foot {
  border-top: 1px solid var(--line); background: var(--paper-2);
  padding: 40px 0 30px; margin-top: 28px;
  background-image: linear-gradient(180deg, var(--paper-3), var(--paper-2) 140px);
}
.foot-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 26px; }
.foot-grid h4 { margin: 0 0 11px; font-size: 12px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); font-weight: 640; }
.foot-grid ul { list-style: none; margin: 0; padding: 0; }
.foot-grid li { margin: 0 0 7px; }
.foot-grid a {
  font-size: 14px; color: var(--ink-2); text-decoration: none;
  transition: color .12s;
}
.foot-grid a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.foot-note { margin: 30px 0 0; padding: 20px 0 0; border-top: 1px solid var(--line); font-size: 12.5px; color: var(--ink-3); }
.foot-note p { font-size: 12.5px; margin: 0 0 6px; max-width: 92ch; }
.foot-note strong { color: var(--ink-2); }

/* On a phone the nine footer columns are nine scroll screens of links. Collapsing
   them into two narrow columns keeps the whole footer to about one screen. */
@media (max-width: 620px) {
  .site-foot { padding: 28px 0 24px; }
  .foot-grid { grid-template-columns: 1fr 1fr; gap: 20px 16px; }
  .foot-grid a { font-size: 13.5px; }
}

.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ---------- file tools: sign, fill, job photos to PDF ---------- */
.ft { margin: 26px 0 8px; }
.ft h2 { margin: 0 0 12px; font-size: 20px; }
.ft h3 { margin: 18px 0 6px; font-size: 15px; }
.ft-hint { font-size: 12.5px; color: var(--ink-3); margin: 4px 0 0; }
.ft-note { font-size: 13.5px; color: var(--ink-3); margin: 12px 0 0; min-height: 18px; }
.ft-note.bad { color: var(--bad); }
.ft-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.ft-form { margin: 18px 0 0; }
.ft-drop {
  border: 2px dashed var(--line); border-radius: var(--radius);
  background: var(--paper-2); padding: 28px 18px; text-align: center;
}
.ft-drop.over { border-color: var(--accent); background: var(--accent-soft); }
.ft-drop p { margin: 0 0 6px; }
.ft-drop .btn { margin-top: 12px; }
.ft-bar { display: flex; flex-wrap: wrap; gap: 10px; margin: 16px 0 0; }

/* The dock. packet.js draws it as the action bar's previous sibling and mirrors the
   primary button into it. Shown only at the width where a file list the visitor
   builds themselves can grow taller than the screen. */
.ft-dock { display: none; }
.btn-lg { padding: 11px 18px; font-size: 15px; }

.ft-list { list-style: none; margin: 18px 0 0; padding: 0; }
.ft-photo {
  display: flex; gap: 12px; align-items: center;
  border: 1px solid var(--line); border-radius: 8px; padding: 8px; margin: 0 0 8px; background: var(--paper);
}
.ft-thumb { width: 84px; height: 64px; object-fit: cover; border-radius: 5px; border: 1px solid var(--line); flex: none; }
.ft-cap { flex: 1 1 auto; min-width: 0; }
.ft-cap input { width: 100%; }
.ft-cap .ft-hint { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ft-photo-btns { display: flex; gap: 4px; flex: none; }

.ft-tools { margin: 16px 0 0; }
.ft-btns { display: flex; flex-wrap: wrap; gap: 8px; }
.ft-btns .btn.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.ft-sel {
  margin: 12px 0 0; padding: 10px; border: 1px solid var(--line);
  border-radius: 8px; background: var(--paper-2);
}
.ft-sel-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.ft-sel-row label { margin: 0; font-size: 12.5px; color: var(--ink-3); }
.ft-sel-row input { flex: 1 1 180px; min-width: 0; }

.ft-pages { margin: 18px 0 0; }
.ft-page {
  position: relative; margin: 0 0 16px; border: 1px solid var(--line);
  box-shadow: 0 1px 3px rgba(18, 22, 27, 0.08); background: #fff;
}
.ft-canvas { display: block; width: 100%; height: auto; }
.ft-layer { position: absolute; inset: 0; }
.ft-pages.arming .ft-layer { cursor: crosshair; background: rgba(26, 86, 219, 0.04); }
.ft-item {
  position: absolute; cursor: move; touch-action: none;
  outline: 1px dashed rgba(26, 86, 219, 0.5); outline-offset: 1px;
  color: #0d1259; line-height: 1.25; white-space: pre; font-family: Helvetica, Arial, sans-serif;
}
.ft-item.sel { outline: 2px solid var(--accent); }
.ft-item.empty { color: var(--ink-3); }
.ft-item img { display: block; width: 100%; height: 100%; }
.ft-item .ft-t { pointer-events: none; }

.ft-modal {
  position: fixed; inset: 0; z-index: 50; background: rgba(18, 22, 27, 0.55);
  display: flex; align-items: flex-start; justify-content: center; overflow: auto; padding: 24px 12px;
}
.ft-modal-box {
  background: var(--paper); border-radius: var(--radius); padding: 18px;
  width: 100%; max-width: 560px;
}
.ft-modal-box h3:first-child { margin-top: 0; }
.ft-modal-box label { display: block; font-size: 12.5px; color: var(--ink-3); margin: 0 0 4px; }
.ft-modal-box input { width: 100%; }
.ft-pad {
  display: block; width: 100%; height: auto; margin: 8px 0 0; touch-action: none;
  border: 1px solid var(--line); border-radius: 8px; background: #fff;
}
/* Two classes on purpose. The daylight restore below lists .ft-pad and ends by
   setting background: var(--paper), which at one class and a later line would
   win here and flatten the typed preview to the same white as the pad it sits
   under. The tint is the only thing telling a reader that the lower box is a
   preview of what they typed rather than a second place to draw. */
.ft-pad.ft-pad-prev { background: var(--paper-2); }

@media (max-width: 560px) {
  .ft-photo { flex-wrap: wrap; }
  .ft-cap { flex-basis: 100%; order: 3; }
}

/* ---------- the handoff, one file carried from one tool to the next ----------
   Two pieces. The bar sits at the top of a tool and offers the file that was made
   somewhere else a minute ago. The steps sit under a finished file and say what
   usually happens to it next. Both are real links and real buttons, no overlay and
   no toast: a thing that disappears on its own is a thing somebody in a truck with
   one hand free will miss. */

.ho-bar {
  display: flex; align-items: flex-start; gap: 12px;
  border: 1px solid var(--accent); border-left-width: 4px;
  border-radius: var(--radius); background: var(--accent-soft);
  padding: 14px 16px; margin: 0 0 18px;
}
.ho-ic { flex: none; width: 22px; height: 22px; color: var(--accent); margin-top: 1px; }
.ho-txt { flex: 1 1 220px; min-width: 0; }
.ho-txt b { display: block; font-size: 15px; color: var(--ink); }
.ho-txt span { display: block; font-size: 13px; color: var(--ink-2); margin-top: 3px; }
.ho-act { flex: none; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.ho-no {
  background: transparent; border-color: transparent; color: var(--ink-3);
  padding-left: 8px; padding-right: 8px;
  /* .btn carries a shadow. On the tinted bar that reads as a second filled button
     sitting beside the primary one, and a "no thanks" is not a second offer. */
  box-shadow: none;
}
.ho-no:hover { color: var(--ink); background: var(--paper); box-shadow: none; }

.ho-next {
  margin: 22px 0 0; padding: 18px 0 0; border-top: 1px solid var(--line);
}
.ho-h {
  margin: 0 0 12px; font-size: 13px; font-weight: 700; color: var(--good);
  text-transform: uppercase; letter-spacing: .04em;
}
.ho-row {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.ho-step {
  display: block; text-decoration: none; background: var(--paper);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 13px 14px;
  transition: border-color .15s, background .15s, transform .15s;
}
.ho-step:hover {
  border-color: var(--accent); background: var(--accent-soft); transform: translateY(-1px);
}
.ho-step b { display: block; font-size: 14.5px; color: var(--accent); }
.ho-step span { display: block; font-size: 12.5px; color: var(--ink-2); margin-top: 3px; }
.ho-fine { margin: 10px 0 0; font-size: 12px; color: var(--ink-3); }

@media (max-width: 560px) {
  .ho-bar { flex-wrap: wrap; }
  .ho-act { width: 100%; }
  .ho-act .btn { flex: 1 1 auto; justify-content: center; }
}

/* ---------- page tools: merge, split, delete and reorder, rotate, numbers ----------
   One grid of page thumbnails serves four of the five. The thumbnail is a canvas that
   pdf.js redraws at the new angle whenever a page is rotated, rather than a canvas spun
   with a CSS transform, because a transformed canvas keeps its old box and a landscape
   page would then sit on top of its neighbours. So nothing here may set a fixed height
   on the canvas: the tile has to grow and shrink with the page it is showing. */

.pt-grid {
  display: grid; gap: 14px; margin: 20px 0 0;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
}

.pt-card {
  position: relative; isolation: isolate; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 12px 8px 10px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--paper);
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.pt-card:hover { border-color: var(--accent); box-shadow: var(--sh-2); transform: translateY(-2px); }
.pt-card.on { border-color: var(--accent); background: var(--accent-soft); box-shadow: var(--sh-1); }
.pt-card.on::after {
  content: "";
  position: absolute; top: 8px; right: 8px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4 10-11'/%3E%3C/svg%3E") center / 11px no-repeat;
}

.pt-num {
  position: absolute; top: 8px; left: 8px; z-index: 1;
  min-width: 20px; padding: 1px 5px; border-radius: 999px;
  font-size: 11px; font-weight: 700; line-height: 1.5; text-align: center;
  color: var(--ink-2); background: var(--paper-3); border: 1px solid var(--line);
}
.pt-card.on .pt-num { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }

.pt-thumb {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 120px; padding: 6px 0;
}
.pt-canvas {
  display: block; max-width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: 4px; background: #fff;
  box-shadow: 0 1px 3px rgba(14, 19, 25, .10);
}
.pt-plain { font-size: 13px; color: var(--ink-3); }

.pt-ops { display: flex; gap: 4px; flex-wrap: wrap; justify-content: center; }
.pt-op {
  width: 28px; height: 28px; padding: 0; line-height: 1;
  font-size: 14px; font-family: inherit; color: var(--ink-2);
  border: 1px solid var(--line); border-radius: 7px; background: var(--paper);
  cursor: pointer; transition: background .12s, color .12s, border-color .12s;
}
.pt-op:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.pt-op:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pt-pick { font-size: 12px; font-weight: 600; color: var(--ink-3); }
.pt-card.on .pt-pick { color: var(--accent-dark); }

/* ---------- merge: a list of files, not a grid of pages ---------- */
.pt-files { margin: 20px 0 0; }
.pt-file {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px 10px 40px; margin: 0 0 8px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper);
}
.pt-file .pt-num { top: 50%; left: 10px; transform: translateY(-50%); }
.pt-thumb-sm { width: 52px; min-height: 0; flex: none; padding: 0; }
.pt-meta { flex: 1 1 auto; min-width: 0; }
.pt-meta b { display: block; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pt-meta span { font-size: 12.5px; color: var(--ink-3); }
.pt-file .pt-ops { flex: none; flex-wrap: nowrap; }

/* ---------- the page numbers form ---------- */
.pt-opts {
  display: grid; gap: 12px; margin: 0 0 12px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
/* In the images tool this same block sits under the select-all row rather than on
   its own, so it needs the gap above that the page numbers form gets from the
   fieldset it lives in. */
.ft-tools .pt-opts { margin: 14px 0 0; }
.pt-opts label { display: block; font-size: 12.5px; color: var(--ink-3); margin: 0 0 4px; }
.pt-opts select, .pt-opts input { width: 100%; }

@media (max-width: 560px) {
  .pt-grid { grid-template-columns: repeat(auto-fill, minmax(108px, 1fr)); gap: 10px; }
  .pt-thumb { min-height: 96px; }
  .pt-file { padding-left: 36px; gap: 10px; }
  .pt-file .pt-ops { flex-direction: column; gap: 3px; }
}

/* The select row was written for a text input, which is supposed to grow to fill the
   line. A checkbox is not: left under the same rule it stretches to a hundred and
   eighty pixels and pushes its own label onto the next line, which on a phone reads
   as a broken control. */
.ft-sel-row input[type="checkbox"] {
  flex: none; width: 16px; height: 16px; margin: 0; accent-color: var(--accent);
}
.ft-sel-row input[type="checkbox"] + label { flex: 1 1 auto; cursor: pointer; }

/* Two fieldsets in a row with no margin between them read as one long list of
   controls, so the second legend looks like a label belonging to the control above
   it. Air, not a rule: a border on the fieldset gets notched by its own legend and
   the line ends up running through the text. */
.ft-form fieldset + fieldset { margin-top: 26px; }

/* ---------- the picture of the template, beside the download buttons ----------
   Drawn at build time by src/paper.mjs, which records the real pdf-lib calls and
   replays them as SVG, so what is on screen is the file, not an impression of it.
   It is inline SVG, which means no request, no layout shift and no blurring on a
   retina screen, and it costs a few kilobytes that gzip takes most of back.

   How wide, and why the width is the whole question. A US Letter sheet is 612
   points across, so the width set here divides into 612 to give the scale, and the
   scale multiplies every type size on the sheet. It was 204px on a desktop and
   176px on a phone, which put the document's own title, set at 22 points, on screen
   at 7.3px and 6.3px. Nobody can read a 6px word. The picture was therefore doing
   none of the job the line below claims for it, and the phone, the device with the
   least room and the most need, was getting the smaller of the two.

   300px puts the title at 10.8px at 1280 and 10.2px in a 375 column, against the 7.3px
   and 6.3px it was. Those four numbers are read off the built page with the service
   worker cleared, both ways round: put 204 and 176 back and the old two return, restore
   and the new two return. The body type is still about 3.5px and is meant to be: this
   is a thumbnail, and what a thumbnail has to answer is which document this is and what
   shape it has, not what it says. The worked example sheet further down this file
   already sized itself this way, off .ex-sheet, and the two should not disagree: it
   measures 480px and 335px at those same two widths, and did not move when this did. */

.dl { display: flex; gap: 28px; align-items: flex-start; }
.dl-body { flex: 1 1 auto; min-width: 0; }

.paper {
  display: block; position: relative; z-index: 1; flex: none; width: 300px; margin: 2px 0 0;
  border-radius: 3px; overflow: hidden; background: #fff;
  border: 1px solid var(--line);
  box-shadow: 0 1px 2px rgba(14, 19, 25, .06), 0 10px 22px -12px rgba(14, 19, 25, .32);
  transition: transform .18s, box-shadow .18s;
}
.paper:hover {
  transform: translateY(-3px);
  box-shadow: 0 1px 2px rgba(14, 19, 25, .06), 0 18px 34px -14px rgba(14, 19, 25, .42);
}
/* The sheet is a sheet, so it gets the one highlight a sheet has: a lighter edge
   down the left where the page would lift off the table. */
.paper::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, rgba(255, 255, 255, .55), transparent 22%);
}
.paper-svg { display: block; width: 100%; height: auto; }

/* The wrapper. On a desktop it carries no width at all, so the sheet's width is stated
   in exactly one place, on .paper, which is where tools/papercheck.mjs reads it. What
   makes that work is not flex: none on its own. A flex item with no width is as wide as
   the widest thing inside it, and the caption is inside it, so the caption has to be made
   not to count. That is what contain: inline-size on .paper-cap is for, and the number it
   is worth is in the comment there. On a phone the wrapper does need the cap, because .dl
   becomes a centred column and a shrink to fit parent cannot resolve a percentage child.
   That cap is therefore written twice, here and on .paper, and the two must agree.
   papercheck enforces the agreement rather than trusting this sentence. */
.paper-fig { flex: none; margin: 0; display: flex; flex-direction: column; align-items: stretch; }

/* Nine pages carry one of these, and they are the nine that hand over more than one
   statutory document: four in California, Texas, Nevada, Michigan and Arizona, three in
   Florida, Georgia and Mississippi, two in Utah. The picture shows the first of them and
   the reader cannot read its title to find out which, because at the size a sheet is
   shown that title is about 6.6px. Making the picture bigger does not reach it: the
   measurement in the comment above says a full width overlay on a phone is still only
   about 4.6px of body type. So the page says it, in HTML, at the size text is read at. */
.paper-cap {
  /* Doing real work, not decoration. .paper-fig is a flex item with no width of its own,
     so its width is the widest thing inside it, and without this that is this caption's
     longest unwrapped line. MEASURED on /california-lien-waiver-form/ at 1280 before it
     was added: the figure came out 432.7px beside a 300px sheet, and the reading column
     next to it fell from 358px to 225px. A third of the text column, spent on a line that
     should have wrapped under the picture, on the nine pages the caption exists for.
     Inline size containment makes the caption contribute nothing to that calculation, so
     the figure is the sheet's width and the caption wraps inside it. papercheck fails the
     build if this goes, because nothing else on the page would say so. */
  contain: inline-size;
  margin: 9px 0 0; font-size: .78rem; line-height: 1.4;
  color: var(--ink-3); text-align: center;
}
.paper-cap b { color: var(--ink-2); font-weight: 600; }

/* Below this the two columns stop being two columns. column-reverse rather than a
   wrap, so the sheet comes first: on a phone the picture is the shape of the document
   and it should be above the button, not under it.

   It is the SHAPE and not the words. That sentence used to say the picture is what tells
   you whether the download is the right one, and the twenty-eighth defect measured what
   that was worth: on the 155 sheets with no house header the document's own title lands
   at about 6.6px. WHICH document it is is the page's job, in the heading above the block,
   and on the nine pages that offer more than one, in .paper-cap under the sheet.

   The sheet takes the column, the way .ex-sheet does. It is written as a percentage
   and not as a number of pixels on purpose: a fixed number here can be smaller than
   the desktop number and nothing would say so, which is exactly what happened when
   this rule read 176px against a 204px desktop. A column can never be wider than the
   device, so a percentage cannot make that mistake twice. */
@media (max-width: 700px) {
  .dl { flex-direction: column-reverse; align-items: center; gap: 20px; }
  .dl-body { width: 100%; }
  .paper { width: 100%; max-width: 420px; }
  /* The same cap as .paper, and it has to be: see .paper-fig above. papercheck fails the
     build if these two numbers ever stop matching. */
  .paper-fig { width: 100%; max-width: 420px; }
}

/* ------------------------------------------------------------- worked example
   The blank sheet above a download button answers one question, "is this the
   right document". It does not answer the other one people actually type, which
   is "what does a filled in one look like and what am I supposed to put in each
   part". This block is the same sheet with a real job written into it, above a
   numbered reading of why each part says what it says.

   It is one column, and that is a measurement rather than a preference. The
   first version put the sheet beside the notes with a 820px media query to stack
   them, and at a 1165px viewport it measured:

     doc=1165  sheetW=306  gap=28  notesW=253

   253 pixels is about thirty characters, a paragraph shredded into a column of
   two and three word lines, on a screen wide enough that nothing looked wrong.
   The media query was reading the viewport, and the viewport is not what
   constrains this block: the block lives inside .prose, .prose is capped at
   --measure, 68ch, and it measured 587 at 1165 and stays 587 at any width above
   that. A viewport query can never be correct for anything in here. One column
   has no breakpoint to get wrong.

   The sheet is not a link and gets no hover lift. A .paper is an affordance,
   click it and a file arrives; this one downloads nothing, and a picture that
   looks clickable and is not is a small lie told on every visit. It keeps the
   rest of the sheet styling, the white ground, the hairline and the lighter left
   edge where a page lifts off a table, because it is the same object as the
   preview above it and should not read as a second kind of thing.

   Nobody can read 9.5pt body text at this scale and nobody is meant to. The
   sheet carries the SHAPE, which is what tells you at a glance that an estimate
   and an invoice are not the same document, and every note below names the thing
   it is about by the words printed on it rather than asking you to find it. */

.ex { margin: 36px 0 0; }
.ex > h2 { scroll-margin-top: 78px; }
.ex-row { margin: 18px 0 0; }

.ex-sheet { width: 100%; max-width: 480px; margin: 0 auto 22px; }
.ex-paper {
  display: block; position: relative; border-radius: 3px; overflow: hidden;
  background: #fff; border: 1px solid var(--line);
  box-shadow: 0 1px 2px rgba(14, 19, 25, .06), 0 10px 22px -12px rgba(14, 19, 25, .32);
}
.ex-paper::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, rgba(255, 255, 255, .5), transparent 22%);
}
.ex-sheet figcaption {
  margin: 12px 2px 0; font-size: 12.5px; line-height: 1.55; color: var(--ink-3);
}

/* Numbered by a counter rather than by an <ol> marker, because a note is two
   blocks, a heading line and a paragraph, and a list marker sits against the
   first line of whichever one it feels like. The number is also the only
   ornament in here: this list carries a lot of text and a second decoration
   would compete with it. */
.ex-notes {
  list-style: none; margin: 0; padding: 0; counter-reset: exn;
}
.ex-notes li { position: relative; padding: 0 0 0 38px; margin: 0 0 17px; }
.ex-notes li:last-child { margin-bottom: 0; }
.ex-notes li::before {
  counter-increment: exn; content: counter(exn);
  position: absolute; left: 0; top: 1px;
  width: 25px; height: 25px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent);
  font-size: 12.5px; font-weight: 700; line-height: 25px; text-align: center;
}
.ex-notes b {
  display: block; font-size: 15px; font-weight: 660; color: var(--ink);
  line-height: 1.42; margin: 1px 0 3px;
}
.ex-notes span { display: block; font-size: 14.5px; line-height: 1.62; color: var(--ink-2); }

/* ---------------------------------------------------------------- black out
   One page at a time, big enough to aim at. The canvas is the render and the layer
   above it is where the bars live, both stretched over a stage whose aspect ratio is
   set from the page itself, so the box never lands somewhere other than where it was
   drawn. Bars are positioned in percentages for the same reason: a phone turned on
   its side resizes the stage and must not move a single mark. */
.rd-work { margin: 16px 0 0; }
.rd-nav {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: 10px; margin: 0 0 12px;
}
.rd-count { font-size: 13px; font-weight: 600; color: var(--ink-3); min-width: 120px; text-align: center; }
.rd-stage {
  position: relative; width: 100%; margin: 0 auto;
  border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
  background: var(--paper); box-shadow: var(--sh-1);
}
.rd-canvas { display: block; width: 100%; height: 100%; }
/* touch-action is what stops a drag on a phone scrolling the article instead of
   drawing a bar. Without it the tool is unusable on the device most of these
   documents are read on. */
.rd-layer {
  position: absolute; inset: 0; cursor: crosshair; touch-action: none;
}
.rd-box {
  position: absolute; background: #000; pointer-events: none;
  outline: 1px solid rgba(255, 255, 255, .55); outline-offset: -1px;
}
.rd-ghost { background: rgba(0, 0, 0, .55); outline-style: dashed; }
/* The remove button is the only thing in the layer that answers a pointer, and it
   sits outside its bar's top right corner so a bar drawn tight around one line of
   text is still removable without covering the text next to it. */
.rd-x {
  position: absolute; top: -9px; right: -9px; pointer-events: auto;
  width: 20px; height: 20px; padding: 0; line-height: 18px; text-align: center;
  font-size: 15px; font-weight: 700; cursor: pointer;
  color: var(--ink-3); background: var(--paper);
  border: 1px solid var(--line); border-radius: 50%; box-shadow: var(--sh-1);
}
.rd-x:hover { color: var(--on-bad); background: var(--bad); border-color: var(--bad); }
.rd-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rd-chk { display: flex; align-items: center; gap: 8px; }
.rd-chk input[type="checkbox"] {
  flex: none; width: 16px; height: 16px; margin: 0; accent-color: var(--accent);
}
.rd-chk label { margin: 0; cursor: pointer; }

/* Search sits under the buttons rather than beside them. On a phone the stage is
   already the whole width and the box has to be too, so a row that wraps is the
   only shape that works in both places. */
.rd-find { margin: 14px 0 0; }
.rd-find > label {
  display: block; font-size: 12.5px; color: var(--ink-3); margin: 0 0 6px;
}
.rd-frow { display: flex; flex-wrap: wrap; gap: 8px; }
.rd-frow input[type="text"] { flex: 1 1 220px; min-width: 0; }
.rd-find .ft-hint { margin: 6px 0 0; }

@media (max-width: 560px) {
  .rd-nav { gap: 8px; }
  .rd-nav .btn { flex: 1 1 0; min-width: 0; }
  .rd-count { order: -1; flex: 1 0 100%; min-width: 0; }
  /* A twenty pixel target with nine pixels of it hanging off the corner is a miss on
     a phone, so the X grows and tucks back inside the bar it belongs to. */
  .rd-x { top: -11px; right: -11px; width: 26px; height: 26px; line-height: 24px; font-size: 17px; }
}

/* ------------------------------------------------------------------- the crop
   Same stage, same layer, same reason for the percentages: one box held as a
   fraction of the page means the same thing after a phone is turned on its side.
   What is different is that here the box is the part being kept, so the dimming
   runs the other way. A single enormous spread shadow paints everything outside the
   box in one go and the stage's own overflow clips it, which is a lot less work for
   a phone than four positioned panels that have to be kept in step with the drag. */
.cr-box {
  position: absolute; pointer-events: none;
  border: 1px solid var(--accent);
  box-shadow: 0 0 0 9999px rgba(14, 19, 25, .46);
  outline: 1px solid rgba(255, 255, 255, .5); outline-offset: -2px;
}
/* The corners are the drag handles and they answer no pointer at all. A handle that
   swallowed events would be a handle you could never draw a new box across, and the
   press is worked out from where it landed rather than from what it hit anyway. */
.cr-h {
  position: absolute; width: 13px; height: 13px; pointer-events: none;
  background: var(--paper); border: 1.5px solid var(--accent); border-radius: 3px;
  box-shadow: 0 1px 2px rgba(14, 19, 25, .35);
}
.cr-nw { left: -7px; top: -7px; }
.cr-ne { right: -7px; top: -7px; }
.cr-sw { left: -7px; bottom: -7px; }
.cr-se { right: -7px; bottom: -7px; }
.cr-size {
  margin: 10px 0 0; text-align: center;
  font-family: var(--mono); font-size: 13px; color: var(--ink-3);
}

@media (max-width: 560px) {
  /* A thumb is wider than a mouse pointer and the hit test that goes with these is
     already 24 pixels, so the drawing matches what the finger can actually reach. */
  .cr-h { width: 17px; height: 17px; }
  .cr-nw, .cr-ne { top: -9px; }
  .cr-sw, .cr-se { bottom: -9px; }
  .cr-nw, .cr-sw { left: -9px; }
  .cr-ne, .cr-se { right: -9px; }
  .cr-size { font-size: 12px; }
}

/* -------------------------------------------------- a photograph into a page
   Two panels: the photograph with a quad drawn on it, and the page that would come
   out of it. Side by side is the whole point of the screen, because the only question
   anybody has here is whether the corners are on the corners of the sheet, and that is
   answered by looking at the result rather than at the quad. On a phone they stack,
   photograph first, which is also the order the work happens in. */
.sc-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
.sc-cap { margin: 0 0 8px; font-size: 13px; font-weight: 640; color: var(--ink-2); }
.sc-out { background: var(--paper-2); }
.sc-shot .rd-layer { cursor: move; }
/* The dim outside the quad and the outline of it are one stretched viewBox laid over
   the photograph, so a phone turned on its side re-lays them out with no javascript at
   all. The stroke is told not to stretch with the box or it would come out thick one
   way and thin the other. */
.sc-svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.sc-dim { fill: rgba(14, 19, 25, .5); }
.sc-poly { fill: none; stroke: var(--accent); stroke-width: 2; }
/* Round, because these four are dragged one at a time onto a corner rather than sized
   as a box, and because a circle centred on a point says where the point is. They
   answer no pointer themselves: the press is worked out from where it landed. */
.sc-h {
  position: absolute; width: 20px; height: 20px; margin: -10px 0 0 -10px;
  pointer-events: none; background: var(--paper);
  border: 2px solid var(--accent); border-radius: 50%;
  box-shadow: 0 1px 3px rgba(14, 19, 25, .45);
}
.sc-note { margin: 12px 0 0; font-size: 13px; color: var(--ink-3); text-align: center; }
.sc-note.bad { color: var(--bad); }
.sc-second { margin: 10px 0 0; }

/* The filmstrip is only there once there is more than one page in the packet. */
.sc-strip {
  display: flex; gap: 10px; overflow-x: auto;
  padding: 3px 3px 10px; margin: 0 0 14px;
}
.sc-cell { position: relative; flex: none; }
.sc-tile {
  display: flex; align-items: center; justify-content: center;
  width: 76px; height: 96px; padding: 0; overflow: hidden; cursor: pointer;
  background: var(--paper-2); border: 1px solid var(--line); border-radius: 8px;
}
.sc-tile.on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.sc-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sc-thumb { display: block; max-width: 100%; max-height: 100%; }
.sc-num {
  position: absolute; left: 5px; bottom: 5px;
  min-width: 17px; padding: 0 4px; border-radius: 5px;
  font-size: 11px; font-weight: 700; line-height: 17px; text-align: center;
  color: #fff; background: rgba(14, 19, 25, .66);
}
.sc-x {
  position: absolute; top: -7px; right: -7px;
  width: 22px; height: 22px; padding: 0; line-height: 20px; text-align: center;
  font-size: 15px; font-weight: 700; cursor: pointer;
  color: var(--ink-3); background: var(--paper);
  border: 1px solid var(--line); border-radius: 50%; box-shadow: var(--sh-1);
}
.sc-x:hover { color: var(--on-bad); background: var(--bad); border-color: var(--bad); }
.sc-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (max-width: 760px) {
  .sc-pair { grid-template-columns: 1fr; gap: 14px; }
}
@media (max-width: 560px) {
  /* A thumb is wider than a mouse pointer, and the hit test that goes with these is
     already thirty pixels, so the drawing matches what the finger can reach. */
  .sc-h { width: 26px; height: 26px; margin: -13px 0 0 -13px; }
  .sc-x { top: -9px; right: -9px; width: 26px; height: 26px; line-height: 24px; font-size: 17px; }
  .sc-note { font-size: 12.5px; }
}

/* ---------------------------------------------------- compare two revisions
   Two drop zones side by side, then either a list of what the wording did or the
   two pages side by side with a box round everything that moved. Both grids drop
   to one column on a phone, because two half width pages on a 360 pixel screen are
   two pictures nobody can read rather than a comparison. */
.cm-feeds { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.cm-cap { margin: 0 0 8px; font-size: 13px; font-weight: 640; color: var(--ink-2); }
.cm-feed .ft-drop { margin: 0; height: 100%; }
/* A file that is already open should look settled, so its zone goes quiet and stops
   competing with the one still waiting for a file. */
.cm-got .ft-drop { border-style: solid; border-color: var(--line); background: var(--paper-2); }
.cm-got .cm-cap { color: var(--accent); }

.cm-work { margin: 16px 0 0; }
.cm-head {
  margin: 0 0 14px; padding: 10px 14px; border-radius: var(--r-sm);
  background: var(--paper-2); border: 1px solid var(--line);
  font-size: 14px; font-weight: 620; color: var(--ink);
}

/* One control with two states, so it reads as a choice rather than as two buttons
   that might both be doing something. */
.cm-seg {
  display: inline-flex; margin: 0 0 14px; padding: 3px;
  background: var(--paper-3); border-radius: 10px;
}
.cm-seg-b {
  border: 0; background: transparent; box-shadow: none;
  padding: 7px 14px; border-radius: var(--r-sm); font-size: 13.5px; color: var(--ink-2);
}
.cm-seg-b:hover { background: var(--paper-2); color: var(--ink); }
.cm-seg-b.is-on { background: var(--paper); color: var(--ink); box-shadow: var(--sh-1); }

.cm-changes { display: grid; gap: 10px; }
.cm-change {
  padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--paper);
}
.cm-n {
  margin: 0 0 8px; font-size: 11.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3);
}
.cm-ctx { margin: 0 0 9px; font-size: 13px; line-height: 1.55; }
.cm-dim { color: var(--ink-3); }
.cm-here {
  padding: 1px 7px; border-radius: 20px; font-size: 11.5px; font-weight: 700;
  background: var(--paper-3); color: var(--ink-2);
}
/* The tag sits in a column of its own so Removed and Added line up under each other
   and the eye can run straight down a page of changes. */
.cm-row {
  display: grid; grid-template-columns: 74px 1fr; gap: 10px;
  margin: 0 0 6px; font-size: 13.5px; line-height: 1.6;
}
.cm-row:last-child { margin-bottom: 0; }
.cm-tag { font-size: 11.5px; font-weight: 700; letter-spacing: .03em; padding-top: 3px; }
.cm-tag-del { color: var(--bad); }
.cm-tag-add { color: var(--add-fg); }
.cm-del { color: var(--bad-fg); background: var(--bad-bg); border-radius: 5px; padding: 2px 6px; text-decoration: line-through; }
.cm-add { color: var(--add-fg); background: var(--add-bg); border-radius: 5px; padding: 2px 6px; }
.cm-rewrite { margin: 0; font-size: 14px; color: var(--ink-3); }
.cm-none {
  margin: 0; padding: 18px 14px; text-align: center; font-size: 14px; color: var(--ink-3);
  border: 1px dashed var(--line); border-radius: var(--r-sm); background: var(--paper-2);
}

.cm-panes { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.cm-stage {
  position: relative; width: 100%;
  border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
  background: var(--paper); box-shadow: var(--sh-1);
}
.cm-canvas { display: block; width: 100%; height: 100%; }
.cm-layer { position: absolute; inset: 0; pointer-events: none; }
/* A box, not a fill. The whole point is to see what changed, which means still being
   able to read the thing the box is drawn around. */
.cm-box {
  position: absolute; border: 2px solid var(--accent); border-radius: 3px;
  background: rgba(26, 86, 219, .10);
}

@media (max-width: 720px) {
  .cm-feeds, .cm-panes { grid-template-columns: 1fr; }
  .cm-seg { display: flex; width: 100%; }
  .cm-seg-b { flex: 1 1 0; min-width: 0; padding: 8px 6px; }
  .cm-row { grid-template-columns: 1fr; gap: 3px; }
  .cm-tag { padding-top: 0; }
}

/* ---------------------------------------------------------- make a PDF smaller
   Three figures and one sentence about what is actually inside the file, then the
   same patch of the biggest picture in it twice at the same size on screen. A
   whole page shrunk to thumbnail size always looks fine, which is exactly why
   nobody else shows you one, so both sides here are a crop at the size you would
   really see. */
.sk-facts {
  margin: 18px 0 0; padding: 16px 18px;
  background: var(--paper-2); border: 1px solid var(--line); border-radius: var(--radius);
}
.sk-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.sk-stat { text-align: center; }
.sk-stat b {
  display: block; font-size: 22px; font-weight: 700; color: var(--ink);
  letter-spacing: -.01em; font-variant-numeric: tabular-nums;
}
.sk-stat span { display: block; margin-top: 3px; font-size: 12.5px; line-height: 1.35; color: var(--ink-3); }
.sk-verdict {
  margin: 14px 0 0; padding-top: 13px; border-top: 1px solid var(--line);
  font-size: 14px; line-height: 1.55; color: var(--ink-2);
}

.sk-prev { margin: 20px 0 0; }
.sk-prev h3 { margin: 0 0 10px; font-size: 15px; }
.sk-prev-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.sk-cap {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  margin: 0 0 6px; font-size: 13px; font-weight: 640; color: var(--ink-2);
}
.sk-sz { font-size: 12.5px; font-weight: 600; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.sk-frame {
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--paper-2); overflow: hidden;
}
.sk-shot { display: block; width: 100%; height: auto; }
.sk-fine { margin: 10px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-3); }

.sk-prog {
  height: 6px; margin: 16px 0 0; border-radius: 999px;
  background: var(--line); overflow: hidden;
}
.sk-prog i {
  display: block; height: 100%; width: 0; border-radius: 999px;
  background: var(--accent); transition: width .18s ease;
}

@media (max-width: 560px) {
  .sk-row { grid-template-columns: 1fr; gap: 10px; }
  .sk-stat { display: flex; align-items: baseline; gap: 8px; text-align: left; }
  .sk-stat b { font-size: 18px; }
  .sk-stat span { margin-top: 0; }
  .sk-prev-row { grid-template-columns: 1fr; }
}

/* --------------------------------------------- the mark this browser decided to keep
   Drawing the same signature with a mouse every visit is why people give up and go
   back to print, sign and scan, so a mark drawn once is kept. The rule that comes with
   keeping it: show the visitor exactly what is stored, at the size it will be stamped,
   and put the way out beside it every single time. */
.ft-saved {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 12px 0 0; padding: 12px 14px;
  background: var(--paper-2); border: 1px solid var(--line); border-radius: var(--radius);
}
.ft-saved-marks { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ft-saved-img {
  display: block; height: 34px; width: auto; max-width: 148px;
  padding: 4px 8px; background: var(--paper);
  border: 1px solid var(--line); border-radius: 8px;
}
.ft-saved-txt { flex: 1 1 230px; min-width: 0; }
.ft-saved-txt b { display: block; font-size: 13.5px; line-height: 1.4; color: var(--ink); }
.ft-saved-txt span { display: block; margin-top: 2px; font-size: 12.5px; line-height: 1.45; color: var(--ink-3); }

@media (max-width: 560px) {
  .ft-saved { gap: 10px; padding: 12px; }
  .ft-saved-txt { flex-basis: 100%; }
  .ft-saved .btn { width: 100%; }
}

/* ------------------------------------------------------- a folder full at once
   The list that stands in for whatever a tool shows about one file, when more
   than one is dropped on it. One line per file, its weight on the right, and
   after the run that same spot says what it became. Shared by every tool that
   takes a batch, which is why it is fl- and not named after one of them. */
.fl-wrap {
  margin: 18px 0 0; padding: 14px 18px;
  background: var(--paper-2); border: 1px solid var(--line); border-radius: var(--radius);
}
.fl-list { margin: 0; padding: 0; list-style: none; }
.fl-list li {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 14px;
}
.fl-list li:first-child { padding-top: 0; }
.fl-list li:last-child { border-bottom: 0; }
.fl-nm { color: var(--ink); overflow-wrap: anywhere; }
.fl-st {
  flex: 0 0 auto; font-size: 12.5px; font-weight: 600;
  color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.fl-st.ok { color: var(--good); }
.fl-total {
  margin: 12px 0 0; padding-top: 11px; border-top: 1px solid var(--line);
  font-size: 14px; line-height: 1.55; color: var(--ink-2);
}
.fl-note { margin: 10px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-3); }

@media (max-width: 560px) {
  .fl-list li { flex-wrap: wrap; gap: 2px 12px; }
  .fl-st { flex: 1 0 100%; }
}

/* ---------------------------------------------- works with no signal
   The chip is the only fixed element on the site. It appears when the
   connection actually goes, because a page that keeps working without
   saying so reads as a page that has not noticed yet. */
.pwa-chip {
  position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%);
  z-index: 60; max-width: calc(100% - 32px);
  background: var(--invert-bg); color: var(--invert-fg); cursor: pointer;
  font-size: .85rem; line-height: 1.35; padding: 10px 16px;
  border-radius: 999px; box-shadow: 0 6px 24px rgba(14, 19, 25, .28);
}
.pw-panel {
  border: 1px solid var(--line); background: var(--paper-2);
  border-radius: var(--radius); padding: 18px 20px; margin: 22px 0;
}
.pw-panel h2 { margin: 0 0 8px; font-size: 1.12rem; }
.pw-state { margin: 0 0 14px; color: var(--ink-2); font-size: .95rem; }
.pw-btns { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.pw-bar {
  height: 8px; margin: 16px 0 6px; border-radius: 999px;
  background: var(--paper-3); overflow: hidden;
}
.pw-fill { height: 100%; width: 0; background: var(--accent); transition: width .18s linear; }
.pw-note { margin: 10px 0 0; font-size: .88rem; color: var(--ink-3); }
.pw-note:empty { margin: 0; }
@media (max-width: 560px) {
  .pw-panel { padding: 16px; }
  .pw-btns .btn { width: 100%; justify-content: center; }
}

/* ------------------------------------------------------------- the job packet
   A tray that follows you across the site, and the page that turns what is in it
   into one document. The chip is the second and last fixed element here, and it
   earns that because it is also the disclosure: while anything is being held, the
   corner of every page says how much. It sits bottom right, and the offline chip
   is pushed up off it by a class on <html> rather than by :has(), which appears
   nowhere in this file and is not starting here. */
.pk-chip {
  position: fixed; right: 16px; bottom: 16px; z-index: 60;
  display: flex; align-items: center; gap: 8px;
  max-width: calc(100% - 32px);
  background: var(--accent); color: var(--on-accent); text-decoration: none;
  font-size: .85rem; line-height: 1.3; padding: 10px 16px;
  border-radius: 999px; box-shadow: 0 6px 24px rgba(26, 86, 219, .34);
  transition: background .15s, transform .15s;
}
.pk-chip:hover { background: var(--accent-dark); transform: translateY(-1px); }
.pk-chip b { font-weight: 700; }
.pk-chip span:last-child { opacity: 1; }
.pk-dot {
  flex: none; width: 8px; height: 8px; border-radius: 50%;
  background: #fff; opacity: .9;
}
html.has-packet .pwa-chip { bottom: 66px; }

@media screen and (max-width: 860px) {
  .ft-dock {
    display: block;
    position: sticky; bottom: 0; z-index: 5;
    margin: 16px -20px 0; padding: 10px 20px;
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--line); background: var(--paper);
  }
  .ft-dock[hidden] { display: none; }
  .ft-dock .btn { width: 100%; justify-content: center; }

  /* The tool's own status line, mirrored above the button. It is hidden when there
     is nothing to say, so a dock with no message is the same height it always was,
     and it wraps rather than truncating because these messages are sentences and a
     half sentence is worse than none. Reason and measurement in packet.js. */
  .ft-dock-note {
    margin: 0 0 8px; font-size: 13px; line-height: 1.35; color: var(--ink-3);
  }
  .ft-dock-note.bad { color: var(--bad); }
  .ft-dock-note[hidden] { display: none; }

  /* Exactly one primary button at any width. Above 860px the dock is display: none
     and the real button is in the bar; below it, the other way round. display: none
     takes whichever is unused out of the accessibility tree too, so a screen reader
     meets the action once. */
  .ft > .ft-bar > .btn-primary,
  .pk-body > .ft-bar > .btn-primary { display: none; }

  /* The builder's dock. Same element, different neighbours. .gen has no padding
     of its own, so this one does not bleed out to the page edge the way the file
     tool's does, and it takes the foot's own background so that when it stops
     being sticky it has simply become the foot. */
  .ft-dock-gen {
    display: flex; align-items: center; gap: 12px;
    margin: 0; padding: 10px 20px;
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    background: var(--paper-2);
  }
  .ft-dock-gen .btn { flex: 1 1 auto; width: auto; }
  .ft-dock-gen .gen-total { flex: 0 0 auto; margin-left: 0; font-size: 14px; }
  .ft-dock-gen .gen-total[hidden] { display: none; }
  .gen > .gen-foot > .btn-primary { display: none; }

  /* Both chips are fixed to the bottom edge and would land on the dock. */
  html.has-dock .pk-chip,
  html.has-dock .pwa-chip { bottom: 80px; }
  html.has-dock.has-packet .pwa-chip { bottom: 130px; }
}

/* The offer under a finished file. It is a second thought, not a second call to
   action, so it sits below the row of next steps rather than inside it. */
.ho-pkrow {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
  margin: 14px 0 0; padding: 14px 0 0; border-top: 1px dashed var(--line);
}
.ho-pkn { font-size: 13px; color: var(--ink-2); }
.ho-pkn b { color: var(--good); }
.ho-pkrow .ho-fine { flex: 1 0 100%; margin: 0; }

/* Where the finished file went. Same shape as the packet offer and directly above
   it, because saving the file comes before collecting it, and both are answers to
   "and now what" rather than new calls to action. It is
   absent entirely on a browser that cannot hand a page a real folder, which is
   every phone and everything that is not Chrome or Edge. */
.ho-dirrow {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
  margin: 14px 0 0; padding: 14px 0 0; border-top: 1px dashed var(--line);
}
.ho-dir { border-color: var(--accent); color: var(--accent); }
.ho-dir:hover { background: var(--accent-soft); }
.ho-dirn { font-size: 13px; color: var(--ink-2); overflow-wrap: anywhere; }
.ho-dirn b { color: var(--good); }
.ho-diroff { margin-left: 6px; font-size: 13px; padding: 4px 8px; }
.ho-dirrow .ho-fine { flex: 1 0 100%; margin: 0; }

/* The list on /job-packet/. One row per document, its printed name editable in
   place, because "merged.pdf" is a filename and a contents page wants a title. */
.pk-list { margin: 0; padding: 0; list-style: none; }
.pk-list li {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line);
}
.pk-list li:first-child { padding-top: 0; }
.pk-num {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--paper-3); color: var(--ink-2);
  font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.pk-fields { flex: 1 1 auto; min-width: 0; }
.pk-lbl {
  display: block; width: 100%; box-sizing: border-box;
  padding: 7px 10px; font: inherit; font-size: 14.5px; color: var(--ink);
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-sm);
}
.pk-lbl:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
.pk-meta { font-size: 12.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.pk-act { flex: none; display: flex; align-items: center; gap: 10px; }
.pk-mv {
  width: 34px; height: 34px; flex: none; cursor: pointer;
  background: var(--paper); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  font-size: 15px; line-height: 1;
}
.pk-mv:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.pk-mv:disabled { opacity: .35; cursor: default; }
.pk-rm {
  cursor: pointer; background: transparent; border: 0;
  padding: 6px 4px; font: inherit; font-size: 13px; color: var(--ink-3);
}
.pk-rm:hover { color: var(--bad); text-decoration: underline; }
.pk-sum {
  border-bottom: 0; padding-top: 12px;
  font-size: 13.5px; font-weight: 600; color: var(--ink-2);
}
.pk-sum .pk-fields { font-variant-numeric: tabular-nums; }

.pk-empty {
  border: 1px solid var(--line); background: var(--paper-2);
  border-radius: var(--radius); padding: 20px; margin: 22px 0;
  font-size: .95rem; color: var(--ink-2);
}
.pk-eh { margin: 0 0 6px; font-size: 1.05rem; font-weight: 700; color: var(--ink); }
.pk-empty p { margin: 0 0 14px; }
.pk-empty .ho-row { margin-top: 0; }

.pk-cover {
  margin: 20px 0 0; padding: 14px 18px 16px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper-2);
}
.pk-cover legend {
  padding: 0 6px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: var(--ink-3);
}
.pk-crow { margin: 8px 0 0; }
.pk-crow label { display: block; font-size: 12.5px; font-weight: 600; color: var(--ink-2); margin: 0 0 5px; }
.pk-crow input[type="text"] {
  display: block; width: 100%; max-width: 340px; box-sizing: border-box;
  padding: 8px 10px; font: inherit; font-size: 14.5px; color: var(--ink);
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-sm);
}
/* The checkbox is 17px and it is never the target: the label wraps it, so the whole
   row toggles it. That only helps if the row is tall enough to hit, and at 24px it
   was not. The box grows a little and the row grows to a thumb. */
.pk-crow label.pk-check {
  display: flex; align-items: center; gap: 9px; cursor: pointer; margin: 0;
  min-height: 44px; font-size: 14.5px; font-weight: 500; color: var(--ink);
}
.pk-crow .pk-check span { font-size: 14.5px; font-weight: 500; color: var(--ink); }
.pk-crow .pk-check input { width: 20px; height: 20px; flex: none; margin: 0; accent-color: var(--accent); }
.pk-fine { margin: 12px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-3); }

@media (max-width: 560px) {
  .pk-chip { right: 12px; bottom: 12px; padding: 9px 14px; }
  .pk-chip span:last-child { display: none; }
  .pk-list li { flex-wrap: wrap; row-gap: 10px; }
  .pk-act { flex: 1 0 100%; justify-content: space-between; }
  .pk-cover { padding: 14px; }
  #packet .ft-bar .btn { width: 100%; justify-content: center; }
}

/* Send it, and the page a shared in file lands on. The send row sits above the
   tool links inside .ho-next, because on a phone sending the finished thing is
   the commoner next step than opening another tool with it. */
.ho-sendrow {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
  margin: 0 0 14px;
}
.ho-send { border-color: var(--accent); color: var(--accent); }
.ho-send:hover { background: var(--accent-soft); }
.ho-sendn { font-size: 13px; color: var(--ink-2); }
.ho-sendrow .ho-fine { flex: 1 0 100%; margin: 0; }

.si-box {
  margin: 18px 0 26px; padding: 18px 20px;
  background: var(--accent-soft); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.si-box.si-idle { background: var(--paper-2); }
.si-head {
  margin: 0; font-size: 17px; font-weight: 700; color: var(--ink);
  overflow-wrap: anywhere;
}
.si-sub { margin: 6px 0 0; font-size: 14px; color: var(--ink-2); }
.si-act { margin: 14px 0 0; display: flex; flex-wrap: wrap; gap: 8px; }
.si-no { font-size: 14px; }

@media (max-width: 560px) {
  .ho-sendrow .btn { width: 100%; justify-content: center; }
  .ho-dirrow .ho-dir { width: 100%; justify-content: center; }
  .ho-diroff { margin-left: 0; }
  .si-box { padding: 16px; }
  .si-act .btn { width: 100%; justify-content: center; }
}

/* Dropping anywhere and pasting. The veil is only a picture: the listeners are on the
   window, so pointer-events none keeps it out of the way of the drop it is announcing. */
.dz-veil {
  position: fixed; inset: 0; z-index: 70; pointer-events: none;
  display: flex; align-items: center; justify-content: center;
  background: rgba(26, 86, 219, 0.10);
}
.dz-card {
  background: var(--paper); border: 2px dashed var(--accent);
  border-radius: var(--r-lg); padding: 22px 26px; text-align: center;
  box-shadow: 0 18px 40px rgba(14, 19, 25, 0.16); max-width: min(420px, 86vw);
}
.dz-card b { display: block; font-size: 1.05rem; color: var(--ink); }
.dz-card span { display: block; margin: 6px 0 0; font-size: 0.92rem; color: var(--ink-3); }

.dz-toast {
  position: fixed; left: 50%; bottom: 22px; z-index: 72; pointer-events: none;
  transform: translate(-50%, 8px); opacity: 0; transition: opacity 0.18s, transform 0.18s;
  background: var(--invert-bg); color: var(--invert-fg); padding: 10px 16px; border-radius: 999px;
  font-size: 0.92rem; box-shadow: 0 10px 24px rgba(14, 19, 25, 0.22);
  max-width: calc(100% - 32px); text-align: center;
}
.dz-toast.on { opacity: 1; transform: translate(-50%, 0); }

/* Paste is a keyboard on a desk, not a thumb on a phone. */
.dz-tip { display: none; margin: 8px 0 0; }
@media (hover: hover) and (pointer: fine) {
  .dz-tip { display: block; }
}

/* ---------- site search ---------- */

/* The trigger is a button dressed as a field, which is the one pattern people
   already know from every documentation site, and the real input lives in the
   panel. Two inputs for one search would be two places to type the same thing.

   It is invisible rather than absent until search.js runs, so it can never be a
   control that does nothing, and because the box still occupies its space the
   header does not move when the script arrives. */
.navsearch {
  visibility: hidden;
  display: inline-flex; align-items: center; gap: 9px;
  min-width: 216px; height: 36px; padding: 0 9px 0 11px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--paper-2); color: var(--ink-3);
  font: inherit; font-size: 14px; cursor: pointer; flex: none;
  transition: border-color .13s, background .13s, color .13s;
}
.has-search .navsearch { visibility: visible; }
.navsearch:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.navsearch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.navsearch .ci { width: 18px; height: 18px; flex: none; }
.navsearch > span { flex: 1; text-align: left; }

kbd {
  font: inherit; font-size: 11.5px; font-family: var(--mono); line-height: 1;
  color: var(--ink-3); background: var(--paper); border: 1px solid var(--line);
  border-bottom-width: 2px; border-radius: 5px; padding: 3px 5px; flex: none;
}

.srch {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 9vh 16px 24px;
}
.srch-back { position: absolute; inset: 0; background: rgba(14, 19, 25, .44); }
@supports (backdrop-filter: blur(1px)) {
  .srch-back { backdrop-filter: blur(2px); }
}
.srch-box {
  position: relative; width: 100%; max-width: 640px; max-height: 78vh;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--sh-3);
  animation: srch-in .16s ease-out;
}
@keyframes srch-in { from { opacity: 0; transform: translateY(-10px) scale(.985); } }

.srch-top {
  display: flex; align-items: center; gap: 11px;
  padding: 13px 14px; border-bottom: 1px solid var(--line);
}
.srch-mag { width: 20px; height: 20px; color: var(--ink-3); flex: none; }
.srch-in {
  flex: 1; min-width: 0; border: 0; outline: 0; background: transparent;
  font: inherit; font-size: 16.5px; color: var(--ink); padding: 5px 0;
  letter-spacing: -0.012em;
}
.srch-in::placeholder { color: var(--ink-3); }
.srch-x {
  flex: none; font: inherit; font-size: 11.5px; font-family: var(--mono);
  color: var(--ink-3); background: var(--paper-2); cursor: pointer;
  border: 1px solid var(--line); border-radius: 6px; padding: 5px 8px; line-height: 1;
}
.srch-x:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

.srch-list { overflow-y: auto; overscroll-behavior: contain; padding: 6px; }
.srch-row {
  display: flex; align-items: center; gap: 12px;
  padding: 9px 10px; border-radius: 10px; text-decoration: none; color: inherit;
}
.srch-row[aria-selected="true"] { background: var(--paper-3); }
.srch-ic {
  width: 34px; height: 34px; border-radius: 9px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--t-bg); color: var(--t-fg);
}
.srch-ic .ci { width: 19px; height: 19px; }
.srch-txt { min-width: 0; }
.srch-txt b {
  display: block; font-size: 14.8px; font-weight: 600; color: var(--ink);
  letter-spacing: -0.012em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.srch-txt > span { display: block; font-size: 12.5px; color: var(--ink-3); margin-top: 2px; }
/* The match is marked rather than bolded, because the title is already bold and a
   bolder bold is not a difference anybody sees. */
/* The mark takes --ink rather than inheriting. Inheriting means it takes whatever the
   line around it is, which under .srch-txt > span is --ink-3, and --ink-3 on --mark in
   the dark is 2.11 to 1. It is also simply what a highlight is for: the matched run
   should be the most readable thing in the row, not the least. */
.srch-row mark { background: var(--mark); color: var(--ink); border-radius: 3px; padding: 0 1px; }
.srch-none { margin: 0; padding: 26px 18px; color: var(--ink-2); font-size: 14.5px; line-height: 1.65; }

.srch-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0; padding: 9px 14px; border-top: 1px solid var(--line);
  background: var(--paper-2); color: var(--ink-3); font-size: 12.5px;
}
.srch-keys { display: flex; align-items: center; gap: 5px; white-space: nowrap; }

/* On a phone the panel is the screen. A centred card with the keyboard over it
   leaves about four results visible, which is not a search, it is a peephole. */
@media (max-width: 600px) {
  .srch { padding: 0; }
  .srch-box { max-width: none; max-height: none; height: 100dvh; border: 0; border-radius: 0; animation: none; }
  .srch-top { padding: 11px 12px; }
  .srch-keys { display: none; }
}

@media (max-width: 900px) {
  .navsearch { min-width: 0; width: 42px; height: 42px; padding: 0; justify-content: center; margin-left: auto; }
  .navsearch > span, .navsearch kbd { display: none; }
  .navsearch .ci { width: 20px; height: 20px; }
  .burger { margin-left: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .srch-box { animation: none; }
  /* The jump to the builder is a 1,500 pixel scroll on a phone, which is the
     first distance on this site long enough for smooth to be a problem rather
     than a nicety. */
  html { scroll-behavior: auto; }
}

/* ---------- change the paper size ----------
   One line per distinct page size in the file: how many pages, what size they are,
   what percentage they come out at, and the only part that matters, whether that
   percentage is a scale a rule can read. The verdict is a pill rather than more
   prose because it is the one thing on the panel somebody has to see without
   reading. */
.rz-map { margin: 14px 0 0; padding: 0; list-style: none; border-top: 1px solid var(--line); }
.rz-map li {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px;
  padding: 10px 0; border-bottom: 1px solid var(--line-2); font-size: 13.5px;
}
.rz-map li:last-child { border-bottom: 0; }
.rz-map b { color: var(--ink); font-weight: 700; white-space: nowrap; }
.rz-map span { color: var(--ink-3); }
.rz-map em {
  font-style: normal; font-weight: 700; color: var(--accent);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.rz-more { color: var(--ink-3); font-size: 12.5px; }

/* Scoped under .rz-map on purpose: the pill is a span, so the plain `.rz-flag`
   selector loses to `.rz-map span` above it, which is one class plus one element,
   and the verdict would quietly come out in muted grey. `.rz-flag.ok` happened to
   win on its two classes, which is exactly the kind of half working that is worse
   than either. Warning is the default and good is the exception, which is the right way round
   here: almost no reduction is a ratio anybody can measure, and the two that are
   deserve to be the thing that stands out. */
.rz-map .rz-flag {
  margin-left: auto; padding: 3px 9px; border-radius: 999px;
  background: var(--warn-bg); border: 1px solid var(--warn-line);
  /* --ink rather than the --ink-2 the other warn boxes use, because this pill is
     12px and it is the one thing on the panel that has to be read. --ink-2 on
     --warn-bg measures 4.36 to 1, which is under the bar at this size. */
  color: var(--ink); font-size: 12px; font-weight: 640; line-height: 1.35;
}
.rz-map .rz-flag.ok { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-dark); }

.rz-prev { margin: 20px 0 0; }
.rz-prev h3 { margin: 0 0 10px; font-size: 15px; }
/* The paper is white in both themes, because paper is white. A preview of a print
   that comes out dark grey at midnight is a preview of nothing. */
.rz-sheet { display: flex; justify-content: center; padding: 18px; background: var(--paper-3); border-radius: var(--radius); }
.rz-paper { display: block; background: #ffffff; box-shadow: var(--sh-2); }

/* Three sheet names across a third of a phone row would wrap mid word at the size
   the other tools use for a page count. */
.sk-facts.rz .sk-stat b { font-size: 19px; }
@media (max-width: 560px) {
  .rz-map li { font-size: 13px; }
  .rz-map .rz-flag { margin-left: 0; flex-basis: 100%; text-align: center; }
  .rz-sheet { padding: 12px; }
}

/* ---------- calculator diagrams ---------- */

/* Every colour in a diagram is a class, never a fill attribute, because a
   presentation attribute cannot read a custom property: fill="var(--line)" is
   simply dead. Classing it means the drawings follow dark mode for free and the
   whole palette lives in one place. */
.diagram {
  margin: 0 0 30px; padding: 20px 20px 4px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--paper-2);
}
.dg { display: block; width: 100%; height: auto; max-width: 560px; margin: 0 auto; }
.dg path, .dg line { stroke-linejoin: round; stroke-linecap: round; fill: none; }
.dg rect, .dg ellipse, .dg path { stroke-width: 1.4; vector-effect: non-scaling-stroke; }

.dg .f-1 { fill: var(--paper); }
.dg .f-2 { fill: var(--paper-3); }
.dg .f-t { fill: var(--accent-soft); }
.dg .f-a { fill: var(--accent); }
.dg .f-w { fill: var(--warn-bg); }
/* A solid block of ink, for the one drawing that has to show a redaction bar. It
   follows --ink rather than being black, so at night it is a pale block on a dark
   page instead of a hole: still obviously a bar laid over a line of type, which is
   the only thing the drawing is saying. */
.dg .f-k { fill: var(--ink); }
.dg .s-1 { stroke: var(--line); }
.dg .s-2 { stroke: var(--ink-3); }
.dg .s-a { stroke: var(--accent); }
.dg .s-w { stroke: var(--warn-line); }

.dg text { font-family: inherit; }
.dg .d-lbl { fill: var(--ink-2); font-size: 13px; font-weight: 640; }
.dg .d-mut { fill: var(--ink-3); font-size: 11.5px; }
.dg .d-key { fill: var(--accent); font-size: 13.5px; font-weight: 700; }
/* Text sitting on an accent fill. Paper rather than white, because --accent is a
   pale blue at midnight and white on it would be unreadable. */
.dg .d-on { fill: var(--paper); font-size: 13px; font-weight: 640; }

/* The one oversized word on the site, the stamp across the watermark drawing. It
   has to be a class and not a font-size attribute on the element: a presentation
   attribute loses to any CSS rule at all, so .d-key's 13.5px was quietly winning
   and the stamp came out the size of a caption. */
.dg .d-stamp { fill: var(--accent); font-size: 30px; font-weight: 700; letter-spacing: 2.5px; }

.diagram figcaption {
  margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--line);
  color: var(--ink-2); font-size: .94rem; line-height: 1.55; max-width: var(--measure);
}

/* A drawing 520 units wide carries 11.5px text, and scaled into a 330px phone
   column that is about 7px on glass, which is not a label any more. So on a small
   screen the drawing alone scrolls sideways at a readable size, inside its own
   box. The caption does not scroll with it: that is ordinary prose and it wraps
   like the rest of the page. One drawing per engine either way. */
.dg-scroll { overflow-x: auto; }
@media (max-width: 560px) {
  .diagram { padding: 16px 14px 4px; }
  .dg { min-width: 460px; margin: 0; }
}

/* ---------- page head ---------- */

/* The badge beside a heading. The tint class sits on the wrapper rather than on
   the badge so that anything else added to this band later inherits the same two
   variables, and the badge itself only has to say background and colour once.

   The vertical rhythm is deliberately unchanged: the margins that used to live on
   the h1 now live on the wrapper, and the h1 inside it has none, so every page
   below the heading sits exactly where it sat before. */
/* Top aligned, not centred. 49 of these headings run to three lines, and a badge
   centred against three lines floats in the middle of the block with nothing to
   sit against. Aligned to the top it lands beside the first line on every page,
   and the badge's own padding puts the icon's cap at the heading's cap without a
   nudge, because a 30px icon centred in a 56px square starts 13px down and a 40px
   line puts its cap about 13px down too. */
.pg-head { display: flex; align-items: flex-start; gap: 16px; margin: 14px 0 12px; }
.pg-head h1 { margin: 0; }
.pg-ic {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 56px; height: 56px; border-radius: var(--r-lg);
  background: var(--t-bg); color: var(--t-fg);
}
.pg-ic .ci { width: 30px; height: 30px; }

/* On a phone the badge goes above the heading rather than beside it. A 56px
   square and a 28px heading side by side leave about 14 characters of line, which
   sets a four line title on a five word heading. */
@media (max-width: 620px) {
  .pg-head { display: block; }
  .pg-ic { width: 46px; height: 46px; margin: 0 0 12px; }
  .pg-ic .ci { width: 25px; height: 25px; }
}

/* ---------- dark ---------- */

/* A theme here is a swap of the token block and nothing else. Every rule above
   this line reads its colour from a variable, which is why there is no second
   copy of the stylesheet below and no rule that has to know which theme is on.

   Three states, not two. An explicit choice stamps data-theme on <html> and wins
   in both directions; with nothing stamped the system setting decides. That is
   why the dark values appear twice: CSS has no way to hand one block to two
   selectors, and a preprocessor would be a build step this site does not have.
   If you change one list, change the other. */

:root[data-theme="dark"] {
  color-scheme: dark;
  --ink: #e6ecf4;
  --ink-2: #aab5c3;
  --ink-3: #808d9d;
  --line: #242e3c;
  --line-2: #1a212c;
  --paper: #0d1117;
  --paper-2: #141b25;
  --paper-3: #1c2532;
  --accent: #6f9bff;
  --accent-dark: #a9c4ff;
  --accent-soft: #16243c;
  /* In the dark the button goes bright and the label goes dark, because a dark label on
     a light blue is the only way this reads at 4.5. MEASURED: #0d1117 on #8fb4ff is 9.14
     and on #6f9bff 7.04; hovering, 10.84 and 9.14. */
  --cta-top: #8fb4ff;
  --cta-bot: #6f9bff;
  --cta-top-h: #a9c4ff;
  --cta-bot-h: #8fb4ff;
  --cta-line: #6f9bff;
  --on-accent: #0d1117;
  --on-bad: #2c1714;
  --good: #46c37b;
  --warn-bg: #2b2412;
  --warn-line: #5a4a1e;
  --sh-1: 0 1px 2px rgba(0, 0, 0, .4);
  --sh-2: 0 6px 20px -6px rgba(0, 0, 0, .55), 0 2px 6px -2px rgba(0, 0, 0, .4);
  --sh-3: 0 20px 48px -16px rgba(0, 0, 0, .7);
  --sh-edge: rgba(255, 255, 255, .18);
  --t-bg: #16243c; --t-fg: #8fb2ff;
  --tb-blue: #16243c;   --tf-blue: #8fb2ff;
  --tb-indigo: #1e1b3a; --tf-indigo: #b3a4ff;
  --tb-green: #10281a;  --tf-green: #5fd48d;
  --tb-teal: #0e2723;   --tf-teal: #4fd0c4;
  --tb-red: #2c1714;    --tf-red: #ff9a8c;
  --tb-amber: #2a2211;  --tf-amber: #e8bc5e;
  --tb-orange: #2b1c11; --tf-orange: #ffa76b;
  --tb-violet: #251a30; --tf-violet: #d59bf0;
  --bad: #ff8a7a;
  --bad-fg: #ffb3a8;
  --bad-bg: #2c1714;
  --add-fg: #6ee7a8;
  --add-bg: #0f2a1c;
  --glass: rgba(13, 17, 23, .86);
  --glass-2: rgba(20, 27, 37, .72);
  --mark: #6b551a;
  --lift: #16202e;
  --eyebrow-line: #2c3f63;
  --dot: #34d07a;
  --invert-bg: #e6ecf4;
  --invert-fg: #0d1117;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ink: #e6ecf4;
    --ink-2: #aab5c3;
    --ink-3: #808d9d;
    --line: #242e3c;
    --line-2: #1a212c;
    --paper: #0d1117;
    --paper-2: #141b25;
    --paper-3: #1c2532;
    --accent: #6f9bff;
    --accent-dark: #a9c4ff;
    --accent-soft: #16243c;
    --cta-top: #8fb4ff;
    --cta-bot: #6f9bff;
    --cta-top-h: #a9c4ff;
    --cta-bot-h: #8fb4ff;
    --cta-line: #6f9bff;
    --on-accent: #0d1117;
    --on-bad: #2c1714;
    --good: #46c37b;
    --warn-bg: #2b2412;
    --warn-line: #5a4a1e;
    --sh-1: 0 1px 2px rgba(0, 0, 0, .4);
    --sh-2: 0 6px 20px -6px rgba(0, 0, 0, .55), 0 2px 6px -2px rgba(0, 0, 0, .4);
    --sh-3: 0 20px 48px -16px rgba(0, 0, 0, .7);
    --sh-edge: rgba(255, 255, 255, .18);
    --t-bg: #16243c; --t-fg: #8fb2ff;
    --tb-blue: #16243c;   --tf-blue: #8fb2ff;
    --tb-indigo: #1e1b3a; --tf-indigo: #b3a4ff;
    --tb-green: #10281a;  --tf-green: #5fd48d;
    --tb-teal: #0e2723;   --tf-teal: #4fd0c4;
    --tb-red: #2c1714;    --tf-red: #ff9a8c;
    --tb-amber: #2a2211;  --tf-amber: #e8bc5e;
    --tb-orange: #2b1c11; --tf-orange: #ffa76b;
    --tb-violet: #251a30; --tf-violet: #d59bf0;
    --bad: #ff8a7a;
    --bad-fg: #ffb3a8;
    --bad-bg: #2c1714;
    --add-fg: #6ee7a8;
    --add-bg: #0f2a1c;
    --glass: rgba(13, 17, 23, .86);
    --glass-2: rgba(20, 27, 37, .72);
    --mark: #6b551a;
    --lift: #16202e;
    --eyebrow-line: #2c3f63;
    --dot: #34d07a;
    --invert-bg: #e6ecf4;
    --invert-fg: #0d1117;
  }
}

/* A document preview is a sheet of paper, and paper is white at midnight too.
   These surfaces put the daylight palette back for themselves and everything
   inside them, which is one rule instead of an override for every label, rule and
   caption that a preview contains. It is written unconditionally because in
   daylight it restates what is already true and costs nothing.

   .ft-pad was missing from this list and it was not a cosmetic miss. The signature
   pad is a white canvas and the ink is drawn on it at a fixed #101418, so the pad
   itself was right; but the typed preview beside it, .ft-pad-prev, is a
   TRANSPARENT canvas over a --paper-2 background, and after dark that background
   was #141b25. Near black lettering on a near black panel is about 1.07 to 1,
   which is not low contrast, it is nothing on the screen. A tool whose whole
   question is "does this signature look right to you" was showing an empty box to
   anybody using it at night. Listing the pad restores --paper-2 to #f7f9fc inside
   it and the same ink reads at about 18 to 1.

   The rule this belongs to: a surface that draws its own content with a FIXED
   colour has to be on this list, because the fixed colour is a daylight decision
   and everything around it has to agree with it. */
.words-body, .ft-page, .ft-pad, .paper, .ex-paper, .pt-canvas, .rd-stage {
  --ink: #0e1319;
  --ink-2: #3d4753;
  --ink-3: #5f6b79;
  --line: #e3e8ed;
  --line-2: #f0f4f7;
  --paper: #ffffff;
  --paper-2: #f7f9fc;
  --paper-3: #eef2f7;
  /* The accent has to come back with the rest of it. These containers are paper and
     paper is white at midnight too, so the night time accent, which is a pale blue
     picked to sit on a near black page, lands on white here at about 2.7 to 1 and
     reads as a mark somebody forgot to finish. Restoring it fixes the line under a
     crop box, the focus ring on a redaction, and the small blue label on a document
     preview, all of which were quietly pale after dark. */
  --accent: #1a56db;
  --accent-dark: #133fa8;
  --accent-soft: #eaf1ff;
  background: var(--paper); color: var(--ink);
}

/* The switch. Same rules as the search trigger beside it: hidden until the script
   that makes it work has run, so it is never a control that does nothing, and the
   space is reserved either way so the header cannot shift. */
.navtheme {
  visibility: hidden;
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; margin-left: 8px; padding: 0; flex: none;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--paper-2); color: var(--ink-3);
  font: inherit; cursor: pointer;
  transition: border-color .13s, background .13s, color .13s;
}
.has-theme .navtheme { visibility: visible; }
.navtheme:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.navtheme:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.navtheme .ci { width: 18px; height: 18px; }

/* 36px, sitting between a 42px search button and a 42px burger, which reads as a
   mistake before it reads as a small target and is both. This override has to live
   here rather than up with the rest of the mobile header, because both rules are a
   single class and the cascade then decides on source order alone: written up there
   it lost to the block above and measured 36px on a phone after the "fix" landed.
   .navsearch's mobile rule already sits after its own block for the same reason. */
@media (max-width: 900px) {
  .navtheme { width: 42px; height: 42px; margin-left: 0; }
}

/* The button shows where it goes, not where it is: a moon in daylight, a sun at
   night. Both icons are in the markup and CSS picks, so pressing it changes the
   picture with no second line of script. */
.navtheme .ic-sun { display: none; }
:root[data-theme="dark"] .navtheme .ic-sun { display: block; }
:root[data-theme="dark"] .navtheme .ic-moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .navtheme .ic-sun { display: block; }
  :root:not([data-theme="light"]) .navtheme .ic-moon { display: none; }
}

/* ---------- print ---------- */

/* There was no print stylesheet on this site at all until now, and this site gets
   printed more than most sites do. A toolbox talk is read out on a job site and
   then signed at the bottom by everyone standing there. A load chart gets taped
   inside a van. A statutory notice goes on paper because the statute says
   certified mail. Printing any of those before this block put the header, the
   burger menu, the search button, the theme toggle, the Monetag slot, a column of
   related links and a four column footer on the sheet, and a reader who had dark
   mode on got a page of near black paper.

   The line itself is written into the markup by src/layout.mjs, hidden here on
   screen and shown again inside the print block below. A printed sheet with no
   address on it cannot be found a second time, and a job site sheet is exactly
   the kind of paper somebody wants to reprint next month. */
.print-url { display: none; }

/* The attendance sheet on a toolbox talk page, written by talkSignBlock in build.mjs
   and off the screen entirely. It exists so that Ctrl+P on a talk page produces the
   same document the PDF produces, the talk and the lines to sign it on one sheet,
   rather than a talk with nowhere to sign. It reuses the .words-body rule vocabulary
   rather than inventing a second one. */
.print-sign { display: none; }

@media print {

  /* Dark mode does not survive contact with a printer. The dark tokens are
     declared in two places, once behind prefers-color-scheme and once behind
     [data-theme="dark"], and a print run still matches the first of those, so
     both are restated here at equal specificity and later in the file, which is
     the whole of why this wins. Nothing else in the sheet has to learn about
     printing: every component already paints itself out of these tokens, so
     setting them once turns the entire site black on white.

     The accent goes to a dark navy rather than staying #1a56db. A mid blue is a
     light grey on a black and white laser, which is what a contractor prints on,
     and the accent is carrying the links, the key lines on every diagram and the
     highlighted column on every chart. Navy still reads as blue in colour and
     reads as near black in greyscale, so neither printer loses it. The warn line
     moves off pale gold for the same reason: gold prints as almost nothing. */
  :root,
  :root[data-theme="dark"],
  :root:not([data-theme="light"]) {
    --ink: #000;
    --ink-2: #1c2530;
    --ink-3: #4a5462;
    --line: #b9c1ca;
    --line-2: #dfe4ea;
    --paper: #fff;
    --paper-2: #fff;
    --paper-3: #f2f4f7;
    --accent: #12325f;
    --accent-dark: #12325f;
    --accent-soft: #eef2f7;
    --good: #14532d;
    --warn-bg: #fff;
    --warn-line: #9aa3ad;
  }

  @page { margin: 14mm 12mm; }

  html, body { background: #fff; }

  /* Every shadow on the site is there to lift a card off the page. On paper
     there is nothing to lift it off, and a printer renders a soft shadow as a
     band of grey dither around the box, which looks like a fault. */
  * { box-shadow: none !important; text-shadow: none !important; }

  /* Transitions and animations are for a screen somebody is pointing at. On paper
     they are at best nothing and at worst a component caught half way through a
     state change at the moment the print snapshot is taken.

     There is a second reason this line is here, and it cost an hour to learn. With
     the Browser pane hidden a CSS transition never advances, because it is driven
     by the same animation frame clock that rAF is, and the clock does not run.
     getComputedStyle then reports the value the property had BEFORE the change,
     for as long as you keep asking, and no amount of waiting or !important moves
     it. Probing .card for box-shadow: none looked exactly like a cascade failure
     and was not one: padding, which .card does not transition, changed instantly
     in the same call, and setting transition: none made border-colour and
     box-shadow both jump to their new values at once. Any probe of a transitioned
     property has to turn transitions off first, and inside this block they are
     already off. */
  *, *::before, *::after { transition: none !important; animation: none !important; }

  /* The chrome. Everything in this list is either a control that cannot be
     operated on paper, a navigation aid that leads nowhere on paper, or the ad
     slot, which must never be on a sheet a person is holding in front of a crew. */
  .skip,
  .site-head,
  .crumbs,
  .ad-slot,
  .rail,
  .site-foot,
  .sprite,
  .srch,
  .srch-back,
  .pwa-chip,
  .pk-chip,
  .dz-veil,
  .dz-toast,
  .dz-tip,
  .pill-row,
  .pill-more,
  .btn,
  .calc-actions,
  .calc-sticky,
  .embed-box,
  .dl,
  .ho-bar,
  .ho-next,
  .si-box,
  .ft-form,
  .gen-jump,
  .doc-jump,
  .ft-drop,
  .ft-bar,
  .ft-dock,
  .ft-tools,
  .ft-btns,
  .ft-modal,
  .ft-saved,
  .rd-nav,
  .rd-chk,
  .cm-head,
  .sk-prog {
    display: none !important;
  }

  /* With the rail gone the grid has one column and no reason to still be a grid,
     and the page gutter belongs to a browser window, not to a sheet that already
     has an @page margin. */
  .layout { display: block; padding-bottom: 0; }
  .wrap { max-width: none; padding: 0; }

  /* overflow: hidden is what rounds the corners of these three blocks on screen.
     A block that spans a page break with overflow: hidden is clipped at the break
     by some print engines, and a toolbox talk is longer than one page almost
     every time. */
  .gen,
  .words,
  .calc { overflow: visible; }

  /* A wide chart is the single hardest thing on this site to print. On screen it
     lives in a horizontal scroller with a sticky first column, so printing it
     untouched clips it at whatever the scroller happened to be showing and leaves
     a sticky cell floating. Both go. The sticky rule sits inside a max-width: 720px
     query, and A4 minus its margins is under 720 CSS pixels, so that query really
     does match on paper and really does have to be turned off here. The selectors
     are repeated at the same specificity because that is the only thing that beats
     the original. */
  .tbl-scroll { overflow: visible; background: none; }
  table.calc-table-wide thead th:first-child,
  table.calc-table-wide td:first-child {
    position: static;
    box-shadow: none;
    padding-right: 8px;
  }
  table.calc-table,
  table.calc-table-wide { width: 100%; font-size: 10.5px; }
  table.calc-table td,
  table.calc-table th { padding: 5px 7px; }

  /* A chart is read a row at a time, so a row that straddles a page break is
     worse than a short page, and the header has to come back at the top of the
     second sheet or the columns stop meaning anything. */
  thead { display: table-header-group; }
  tr { break-inside: avoid; }

  /* Same argument as the table, for the drawings: the container scrolls sideways
     on a narrow screen and paper cannot scroll. */
  .dg-scroll { overflow: visible; }
  .dg { min-width: 0; max-width: 100%; }

  h1, h2, h3, h4 { break-after: avoid; }
  p, li { orphans: 3; widows: 3; }
  .diagram,
  .callout,
  .statute-quote,
  .statute-src,
  .worked,
  .answer,
  .card,
  .words-head,
  .words-body .w-r,
  .ex-notes li,
  .ex-paper,
  .paper { break-inside: avoid; }

  /* Underlining every link on a printed page is noise, because none of them are
     clickable. An external link is different: it is the only route back to the
     source, and a statutory notice is the page where somebody most needs to check
     the statute for themselves, so those print their address. Internal links on
     this site are all root relative, so ^="http" catches exactly the outside ones. */
  a { text-decoration: none; color: inherit; }
  .prose a[href^="http"]::after,
  .statute-src a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 10.5px;
    color: var(--ink-3);
    word-break: break-all;
  }

  /* The download strip is the most important block on the page on a screen and the
     least useful one on paper: it is a call to action for a file, and a call to
     action cannot be actioned on a sheet somebody is holding. Hiding it is what
     makes the print-only attendance sheet below worth writing, because the talk
     page then prints the whole record instead of printing an advert for it. */
  .print-sign {
    display: block;
    padding: 0;
    margin-top: 26px;
    break-before: page;
  }
  .print-sign .w-rule { border-bottom-color: #000; }

  /* Related link cards are a grid of bordered boxes with an icon in each, which on
     paper is six boxes where six lines would do. They are not hidden outright,
     because on a hub page the cards ARE the page and printing one should still give
     a usable index, so they flatten into a list instead of disappearing. */
  .cards { display: block; margin: 14px 0; }
  .card { border: 0; padding: 0 0 7px; background: none; }
  .card .card-ic { display: none; }

  .print-url {
    display: block;
    margin: 20px 0 0;
    padding-top: 9px;
    border-top: 1px solid var(--line);
    font-size: 10.5px;
    color: var(--ink-3);
  }
}
