@import url("https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Manrope:wght@400;500;600;700&display=swap");

/* =========================================================================
   Omega Lithograph - theme v7  -  "IMPRESSION"
   =========================================================================
   THE CONCEPT, in one line: the site as a well-set catalog - the kind of
   printed object a good shop would produce for itself. An asymmetric
   editorial grid, generous air, a fine rule system, and typography that is
   distinctive without being loud. The one signature device is the OUTLINED
   DISPLAY NUMERAL: a section number set very large in the display serif with
   a hairline stroke and no fill, so it gives the page scale while weighing
   almost nothing. Eye-catching, and the opposite of heavy.

   WHY THIS PASS EXISTS. v6 (Broadside) was rejected on WEIGHT - "it's too
   bold." Anton is a heavy condensed face and it was set to 200px; at that
   mass every page shouts and there is no quiet for anything to stand out
   AGAINST. "Eye-catching but not overpowering" is a request for contrast
   through REFINEMENT: form, detail and space doing the work that weight and
   scale were doing. So this is a fresh design, not a reskin of Broadside and
   not a return to any earlier pass. BRIEF section 15 is the direction, and it
   supersedes section 14 entirely.

   ------------------------------------------------------------------------
   TYPE - and the structural answer to "too bold"
   ------------------------------------------------------------------------
     Instrument Serif   400 + 400 italic   display, pull quote, the numerals
     Manrope            400/500/600/700    everything else, every figure

   INSTRUMENT SERIF SHIPS ONE WEIGHT AND AN ITALIC. It cannot be set bold,
   which makes "too bold" structurally impossible rather than a matter of
   restraint. Its italic is the only emphasis device in the display face.

   That one-weight fact is also a trap, and it has bitten this project twice:
   every display selector below lands on an h1/h2/h3/b whose UA or inherited
   weight is 600-900, and a bold request against a face that ships only 400
   makes the browser SYNTHESIZE a fake bold - smeared stems, closed counters,
   the exact defect that was rejected. So `font-weight: 400` appears on every
   display selector, and it is not a weight CHOICE - it REMOVES one. NOTHING
   in this file ever sets a weight above 400 on an element in var(--display).
   There is no exception anywhere below.

   SCALE IS MODERATE, NOT POSTER.
     home h1        clamp(40px, 5.2vw, 76px)   Instrument Serif
     interior h1    clamp(36px, 4.4vw, 62px)   Instrument Serif
     section head   clamp(30px, 3.2vw, 46px)   Instrument Serif
     sub head       clamp(28px, 2.2vw, 32px)   Instrument Serif
     pull quote     clamp(28px, 2.9vw, 40px)   Instrument Serif italic
     outline num    clamp(64px,   7vw, 120px)  hairline stroke, no fill
     lead           19-21px    Manrope 400
     body           18px       Manrope 400
     floor          17px       Manrope 400
     caption        15px       Manrope 400
     label          12px       Manrope 600 caps +.14em

   Display line-height is 1.05-1.12, never .9 - the tight leading was half of
   what made v6 feel compressed. Display tracking runs -0.005em to 0 and never
   below -0.01em. (Section 13.2's "nothing negative" rule was written against
   Anton, a condensed face already tightly fitted; Instrument Serif is a
   normal-width serif and a hair of negative tracking at 60px+ is correct
   typography, which section 15 states in as many words.)

   ------------------------------------------------------------------------
   THE PALETTE - two colors, ONE ACTION COLOR, and a tint scale
   ------------------------------------------------------------------------
   The sheet and the ink are settled (section 13.1). What changes in this pass
   is the PROPORTION: this is a predominantly light site. AT MOST ONE INK
   FLOOD PER PAGE, and on most pages that is the closing band - which runs
   straight into the footer, so the two read as a single field of ink at the
   foot of the sheet rather than as two separate floods. Depth comes from the
   tint scale, not from flooding.

   SECTION 16.1 ADDS ONE FUNCTIONAL COLOR and does not reopen the palette.
   It is the magenta already inside assets/logo.png, and it is RESERVED FOR
   CONTROLS: if an element is not clickable it is not this color. No
   heading, no rule, no eyebrow, no numeral, no non-control border, no
   non-control hover tint. Every other value on the site stays monochrome,
   the sheet stays #F6F7F8 and the ink stays #1A1614. The measured table for
   it sits beside the tokens in :root.

   RULES ARE 1px. A 2px rule appears at most ONCE per page: it closes the
   hero, directly under the h1. Exactly two selectors in this file draw one -
   .split (its border-bottom) and .page-hero (a grid item in its .wrap) - and
   they are the two alternative page openers. A PAGE USES ONE OR THE OTHER,
   NEVER BOTH; putting a .split and a .page-hero on the same page is the only
   way to get two 2px rules, and it is the one thing a copy module must not
   do. Nothing else in this file draws a rule heavier than 1px.

   ------------------------------------------------------------------------
   CONTRAST - measured from sRGB relative luminance, not estimated
   ------------------------------------------------------------------------
   Every text role computed on all three grounds. Floors: 4.5:1 body text,
   3:1 for a rule or edge that carries meaning, 17px minimum body size.

   ON THE SHEET  #F6F7F8
     token       value                       composites to   ratio   floor
     --ink       #1A1614                                     16.75   4.5  body
     --ink-2     #54585C                                      6.69   4.5  body
     --ink-3     #7B8085                                      3.72   3.0  rule
     --line      #7B8085                                      3.72   3.0  edge
     --rule      rgba(26,22,20,.14)          #D7D8D8          1.33    -   decor
     --rule-2    rgba(26,22,20,.28)          #B8B8B8          1.85    -   decor
     --focus     #1A1614                                     16.75   3.0  ring
     --act-bg    #A8006C  primary fill, edge                  6.79   3.0  edge
     --act-fg    #FFFFFF  on that fill                        7.29   4.5  label
     hover fill  #8A0059  edge                                8.80   3.0  edge
     hover label #FFFFFF  on that fill                        9.44   4.5  label
     --act-text  #A8006C  .link--action                       6.79   4.5  text
     ghost edge  #1A1614  .btn--ghost border                 16.75   3.0  edge

   ON THE PANEL TINT  #EFF1F2   (1.06:1 against the sheet - a tint, not an
   edge: a .panel is separated by its tone plus its padding, never by a
   border, and nothing depends on seeing exactly where it starts)
     --ink       #1A1614                                     15.86   4.5  body
     --ink-2     #54585C                                      6.33   4.5  body
     --ink-3     #7B8085                                      3.52   3.0  rule
     --rule      rgba(26,22,20,.14)          #D1D2D3          1.34    -   decor
     --rule-2    rgba(26,22,20,.28)          #B3B4B4          1.83    -   decor
     --act-bg    #A8006C  primary fill, edge                  6.43   3.0  edge
     --act-fg    #FFFFFF  on that fill                        7.29   4.5  label

   ON THE CLOSING FLOOD  #1A1614   (the closing band and the footer)
     --fg        #F6F7F8                                     16.75   4.5  body
     --ink-2     #B9BDC1                                      9.51   4.5  body
     --ink-3     #8A8F94                                      5.51   3.0  rule
     --line      #7E8388                                      4.70   3.0  edge
     --rule      rgba(246,247,248,.18)       #423E3D          1.70    -   decor
     --rule-2    rgba(246,247,248,.32)       #605E5D          2.79    -   decor
     --focus     #F6F7F8                                     16.75   3.0  ring
     --act-bg    #CE1B86  primary fill, edge                  3.53   3.0  edge
     --act-fg    #FFFFFF  on that fill                        5.09   4.5  label
     hover fill  #E88BC2  edge                                7.62   3.0  edge
     hover label #1A1614  on that fill                        7.62   4.5  label
     --act-text  #E88BC2  .link--action                       7.62   4.5  text
     ghost edge  #F6F7F8  .btn--ghost border                 16.75   3.0  edge

   THE TWO COMBINATIONS THAT ARE FORBIDDEN, measured so nobody re-derives
   them: #A8006C on the flood is 2.47:1, below the 3:1 control-edge floor -
   which is the entire reason --action-ink exists - and #E88BC2 on the sheet
   is 2.20:1. Neither is reachable, because every control below reads the
   ground-resolved pair (--act-bg / --act-fg / --act-text) declared in each
   bundle, and never a raw --action.

   HOW --ink-3 IS ASSIGNED, and why 3.72:1 is safe here.
   3.72:1 clears the 3:1 rule floor and the 3:1 large-text floor, and it does
   NOT clear the 4.5:1 body floor. So --ink-3 is confined, by assignment, to:
     - the .outline numeral's hairline stroke (ornament: aria-hidden, and
       never the only carrier of a number that matters)
     - the .c-num outline in the contents list (the same - the row's name and
       description carry the meaning, the numeral only orders it)
     - the .c-leader once it solidifies on hover
     - rule and edge duty
   It carries NO body copy, NO caption, NO label and nothing under 18px
   anywhere in this file. Every small role - .lede, figcaption, .listing .k,
   .c-desc, form labels, .crumbs - takes --ink-2 at 6.69:1 instead. Section
   15's token table nominates --ink-3 for "captions, labels" but also states
   "never body"; at 11-15px a caption IS body-sized text, so those roles are
   set in --ink-2. That is the enforcement the assignment asks for, not a
   drift from the brief.

   WHY --rule AND --rule-2 SIT BELOW 3:1, deliberately. They are typographic
   hairlines: they separate one row of type from the next inside a block, the
   way rules do on a printed page. They carry nothing the type they separate
   does not already carry, and deleting them would change no meaning. WCAG
   1.4.11 governs boundaries REQUIRED to identify a control or understand
   content; every such boundary on this site - each input, select and
   textarea, the drop zone, every button edge, the focus ring - is drawn in
   --line (3.72:1 / 4.70:1), --fg (16.75:1) or --act-bg (6.79:1 on the
   sheet, 3.53:1 on the ink) and never in --rule. That split is the entire
   reason both tokens exist.

   ------------------------------------------------------------------------
   THE COMPONENT SYSTEM - what a copy module may use
   ------------------------------------------------------------------------
     .ed         the asymmetric grid: a left label column and an offset
                 reading column, with the last columns left as air
     .lede       the small-caps Manrope label
     .split      the 5/7 hero - text left, one native-size plate right
     .factrule   one thin band of four facts, hairline separated
     .contents   THE CENTERPIECE - the capabilities as a book's table of
                 contents: outline numeral, serif name, dotted leader running
                 across the gap, right-aligned Manrope description
     .outline    the signature: a display numeral in hairline stroke only
     .plates     a deliberately uneven gallery of captioned duotone plates
     .listing    a two-column hairline list - products, spec excerpts
     .quote      one centered italic serif line. The ONLY centered element.
     .closing    the single ink flood
     .panel      a --sheet-2 block, separated by tone rather than a border
     .cta-inline a mid-page lockup: one sentence, a primary button and the
                 phone, on the .ed grid, with a hairline above and no fill,
                 no border and no card
     .link .btn  the three-level control hierarchy: a filled primary in the
                 action color, a --fg outline secondary, and a body-flow
                 text link with a 1px underline growing to 2px

   The token-pair architecture (--bg/--fg/--ink-2/--rule/--line bundles, plus
   .ink-black and .ink-stock) is the v4-v6 mechanic and it stays: every
   component reads pair tokens and never a raw hex, so a block inverts by
   swapping one bundle and its measured ratios travel with it.

   ------------------------------------------------------------------------
   MOTION - four behaviors, and nothing else
   ------------------------------------------------------------------------
     .reveal        fade up 12px over 600ms (site.js adds .in)
     links          a 1px underline grows to 2px over 180ms
     .contents-row  the leader solidifies, the name shifts 4px, 200ms
     .btn           primary swaps to its hover fill, ghost inverts; 180ms
   No marquee, no sticky columns, no inverting rows - those were v6. Every one
   of the four is a no-op under prefers-reduced-motion.

   Contract with the shared assets/site.js - do not rename:
     .reveal -> .reveal.in       scroll reveal
     nav.nav-menu-open           mobile menu (the burger toggles it)
     .dropdown.open              nav dropdowns
     .burger, #navLinks          the toggle and the menu it controls
     .site-header.scrolled       set past 12px of scroll
     .form-status.ok / .err      shared contact-form status element
   The nav collapse point stays at 1140px.

   The @import above loads the identical Google Fonts URL that FONTS_HREF in
   clients/omegalith/__init__.py already carries, so the two requests collapse
   into one and the stylesheet is still correct on its own.
   ====================================================================== */

:root {
  /* --- The two colors ---------------------------------------------------- */
  --ink:     #1A1614;   /* the ink   - warm near-black */
  --sheet:   #F6F7F8;   /* the sheet - super-light cool gray */
  --sheet-2: #EFF1F2;   /* the panel tint - 1.06:1 against the sheet */

  /* --- THE ACTION COLOR (section 16.1) ----------------------------------
     ONE functional color on a monochrome page. It is the magenta already
     inside assets/logo.png, so it is not a new hue - it is on every page
     already. IT IS RESERVED FOR CONTROLS. If an element is not clickable it
     is not this color: no heading, no rule, no eyebrow, no numeral, no
     border on a non-control, no hover tint on a non-control. That single
     restriction is the whole reason one accent does not become a theme.

     Two fills, because the two grounds need different ones. Ratios are
     measured from sRGB relative luminance, not estimated. */
  --action:       #A8006C;  /* PRIMARY FILL ON THE SHEET.  #FFF label 7.29:1,
                               edge on #F6F7F8 6.79:1, edge on #EFF1F2 6.43:1 */
  --action-hover: #8A0059;  /* hover / pressed on the sheet. #FFF label 9.44:1,
                               edge on #F6F7F8 8.80:1                          */
  /* v7.3 (2026-09-08): ONE PURPLE. Luke: "use the same purple everything (I
     like the purple in the request a quote in the header for everything),
     including phone numbers." The lighter flood variants (#CE1B86 fill,
     #E88BC2 text) are retired: a filled control is #A8006C on BOTH grounds
     (its white label is 7.29:1 either way; the fill's edge on the ink is
     2.47:1, accepted by that decision), and action-colored TEXT exists only
     on light ground - on the flood, action text is the sheet color, because
     #A8006C on #1A1614 is unreadable as text. The two legacy names resolve
     to those values so nothing older needs rewriting. */
  --action-ink:   #A8006C;
  --action-link:  #F6F7F8;

  /* THE TWO ILLEGAL COMBINATIONS, measured, so nobody re-derives them:
       --action      on #1A1614 = 2.47:1  - BELOW the 3:1 control-edge floor.
                                            Never on the flood. --action-ink
                                            is what exists instead.
       --action-link on #F6F7F8 = 2.20:1  - never on light ground.
     Neither is reachable through a token below: every control reads the
     ground-resolved pair (--act-bg / --act-text), never a raw --action. */

  /* Legacy names for the same two colors. The engine, 404.html and older copy
     write these by hand; they have to resolve. */
  --black: #1A1614;
  --stock: #F6F7F8;

  /* --- THE SHEET BUNDLE, resolved ---------------------------------------
     16.75 / 6.69 / 3.72 - see the table in the header. */
  --sheet--bg:     #F6F7F8;
  --sheet--fg:     #1A1614;
  --sheet--fg-2:   #54585C;
  --sheet--fg-3:   #7B8085;
  --sheet--rule:   rgba(26, 22, 20, .14);
  --sheet--rule-2: rgba(26, 22, 20, .28);
  --sheet--line:   #7B8085;
  --sheet--duo: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter%20id='d'%20color-interpolation-filters='sRGB'%3E%3CfeColorMatrix%20type='matrix'%20values='.2126%20.7152%20.0722%200%200%20.2126%20.7152%20.0722%200%200%20.2126%20.7152%20.0722%200%200%200%200%200%201%200'/%3E%3CfeComponentTransfer%3E%3CfeFuncR%20type='table'%20tableValues='0.102%200.965'/%3E%3CfeFuncG%20type='table'%20tableValues='0.086%200.969'/%3E%3CfeFuncB%20type='table'%20tableValues='0.078%200.973'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3C/svg%3E#d");

  /* --- THE FLOOD BUNDLE, resolved ---------------------------------------
     16.75 / 9.51 / 5.51, edges at 4.70. The tints are COOL, re-derived
     against the cool sheet; the warm ones carried over from the v4 tan read
     dirty on this ground. */
  --flood--bg:     #1A1614;
  --flood--fg:     #F6F7F8;
  --flood--fg-2:   #B9BDC1;
  --flood--fg-3:   #8A8F94;
  --flood--rule:   rgba(246, 247, 248, .18);
  --flood--rule-2: rgba(246, 247, 248, .32);
  --flood--line:   #7E8388;
  /* A CUTOUT on the flood: its white ground dissolves into the ink. */
  --flood--duo: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter%20id='d'%20color-interpolation-filters='sRGB'%3E%3CfeColorMatrix%20type='matrix'%20values='.2126%20.7152%20.0722%200%200%20.2126%20.7152%20.0722%200%200%20.2126%20.7152%20.0722%200%200%200%200%200%201%200'/%3E%3CfeComponentTransfer%3E%3CfeFuncR%20type='table'%20tableValues='0.965%200.102'/%3E%3CfeFuncG%20type='table'%20tableValues='0.969%200.086'/%3E%3CfeFuncB%20type='table'%20tableValues='0.973%200.078'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3C/svg%3E#d");
  /* A PHOTOGRAPH on the flood: dark stays ink, light goes to the cool
     secondary tint, so a press or a building reads as a positive. */
  --flood--duo-photo: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter%20id='d'%20color-interpolation-filters='sRGB'%3E%3CfeColorMatrix%20type='matrix'%20values='.2126%20.7152%20.0722%200%200%20.2126%20.7152%20.0722%200%200%20.2126%20.7152%20.0722%200%200%200%200%200%201%200'/%3E%3CfeComponentTransfer%3E%3CfeFuncR%20type='table'%20tableValues='0.102%200.725'/%3E%3CfeFuncG%20type='table'%20tableValues='0.086%200.741'/%3E%3CfeFuncB%20type='table'%20tableValues='0.078%200.757'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3C/svg%3E#d");

  /* --- Type ---------------------------------------------------------------
     Instrument Serif falls back to the high-contrast text serifs a system
     actually ships, at roughly its own width, so a 76px line does not reflow
     half a column wider mid-swap. Manrope falls back to modern humanist
     grotesques of similar width. */
  --display: "Instrument Serif", "Iowan Old Style", "Times New Roman", Times,
             "Liberation Serif", Georgia, "Nimbus Roman", serif;
  --text:    "Manrope", "Segoe UI Variable Text", "Segoe UI", -apple-system,
             BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial,
             ui-sans-serif, system-ui, sans-serif;

  /* --- The type scale ----------------------------------------------------
     Real daylight between the steps, and every reading value at 17px or
     above. Nothing here approaches poster scale. */
  --t-hero:    clamp(40px, 5.2vw, 76px);   /* .split-text h1, .h-display    */
  --t-title:   clamp(36px, 4.4vw, 62px);   /* .page-hero h1                 */
  --t-section: clamp(30px, 3.2vw, 46px);   /* .h-section, .closing h2       */
  --t-sub:     clamp(28px, 2.2vw, 32px);   /* .c-name, prose h2, .f .v      */
  --t-quote:   clamp(28px, 2.9vw, 40px);   /* .quote                        */
  --t-outline: clamp(64px,   7vw, 120px);  /* .outline                      */
  --t-block:   clamp(21px, 1.6vw, 24px);   /* block heads, summary  Manrope */
  --t-lead:    clamp(19px, 1.4vw, 21px);   /* .lead                         */
  --t-body:    18px;                       /* body                          */
  --t-min:     17px;                       /* the floor                     */
  --t-cap:     15px;                       /* figcaption, foot-bottom       */
  --t-label:   12px;                       /* .lede and every caps label    */
  --t-nav:     13px;                       /* nav, .btn                     */

  /* Legacy scale names, kept so any rule or inline style still reaching for
     one resolves to a value on the new scale rather than to nothing. */
  --t-1: var(--t-hero);    --t-2: var(--t-title);  --t-3: var(--t-section);
  --t-4: var(--t-section); --t-5: var(--t-sub);    --t-6: var(--t-sub);
  --t-7: var(--t-block);   --t-8: var(--t-lead);   --t-9: var(--t-body);
  --t-label-lg: var(--t-nav);
  --t-poster:   var(--t-hero);

  /* --- Metrics -------------------------------------------------------------
     Every radius is zero and stays zero. The engine reaches for --r-* by
     hand; all of them point at the same nothing. */
  --card-radius: 0;
  --r-card: 0; --r-cta: 0; --r-pill: 0; --r: 0;

  --inset:   clamp(24px, 5vw, 96px);   /* the grid's own margin  */
  --gutter:  clamp(16px, 1.8vw, 26px); /* between columns        */
  --wrap:    1420px;                   /* the widest it ever runs */
  --head-h:  80px;                     /* masthead height        */
  --hair:    1px;                      /* THE rule weight. It is 1px. */
  --measure: 60ch;                     /* ~65 characters against Manrope */

  --ease: cubic-bezier(.22, .61, .36, 1);
  --snap: .18s;   /* links, buttons */
  --row:  .2s;    /* contents rows  */
  --rise: .6s;    /* .reveal        */

  /* --- Defaults: the sheet ---------------------------------------------- */
  --bg:     var(--sheet--bg);
  --fg:     var(--sheet--fg);
  --fg-2:   var(--sheet--fg-2);
  --ink-2:  var(--sheet--fg-2);
  --ink-3:  var(--sheet--fg-3);
  --rule:   var(--sheet--rule);
  --rule-2: var(--sheet--rule-2);
  --line:   var(--sheet--line);
  --duo:       var(--sheet--duo);
  --duo-photo: var(--sheet--duo);
  --btn-bg: var(--sheet--fg);
  --btn-fg: var(--sheet--bg);
  --focus:  var(--sheet--fg);
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);

  /* --- Aliases that do NOT depend on a bundle ---------------------------- */
  --paper:       var(--sheet);
  --white:       var(--sheet);
  --ink-band:    var(--ink);
  --accent-soft: transparent;
  --brass-soft:  transparent;
  --mag-soft:    transparent;
  --body:        var(--text);
  --mono:        var(--text);
}

/* Aliases that point at a BUNDLE token are declared on every element rather
   than once on :root: a var() inside a custom property resolves where the
   property is DECLARED, so on :root --accent would freeze at the sheet's ink
   and inherit that value into the flood. Declared on * each element resolves
   the alias against its OWN bundle. */
* {
  --accent:      var(--fg);
  --accent-dn:   var(--fg);
  --accent-hi:   var(--fg);
  --accent-lt:   var(--fg);
  --cyan:        var(--fg);
  --moss:        var(--fg);
  --moss-deep:   var(--fg);
  --brass:       var(--fg);
  --mag:         var(--fg);
  --tx:          var(--fg);
  --tx-2:        var(--ink-2);
  --tx-3:        var(--ink-2);
  --ink-2-lt:    var(--ink-2);
  --rule-strong: var(--line);
  --line-2:      var(--line);
  --line-3:      var(--line);
  --ground:      var(--bg);
  --surface:     var(--bg);
  --surface-2:   var(--bg);
  --stone-900:   var(--bg);
}

/* =======================================================================
   THE BUNDLES
   =======================================================================
   Each is a set of tokens and nothing else, so it can be dropped on a
   section, a figure or the footer without carrying any other opinion with
   it. .closing and .site-footer are the flood; everything else is the sheet.

   NOTE the one deliberate change from v6: a bare .band is now the SHEET.
   Only .band--ink and .flood - names that say "ink" out loud - take the
   flood bundle. Under v6 any .band was a full-bleed ink field, which is how
   pages ended up carrying two or three of them; the "at most one flood per
   page" rule is easier to keep if flooding has to be asked for by name.
   ==================================================================== */
.ink-black, .ink-stock-r, .flood, .band--ink, .site-footer {
  --bg: var(--flood--bg); --fg: var(--flood--fg); --fg-2: var(--flood--fg-2);
  --ink-2: var(--flood--fg-2); --ink-3: var(--flood--fg-3);
  --rule: var(--flood--rule); --rule-2: var(--flood--rule-2);
  --line: var(--flood--line);
  --duo: var(--flood--duo); --duo-photo: var(--flood--duo-photo);
  --btn-bg: var(--flood--fg); --btn-fg: var(--flood--bg);
  --focus: var(--flood--fg);
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--flood--fg);
}
.ink-black-r, .ink-stock, .sheet, .band, .band--white {
  --bg: var(--sheet--bg); --fg: var(--sheet--fg); --fg-2: var(--sheet--fg-2);
  --ink-2: var(--sheet--fg-2); --ink-3: var(--sheet--fg-3);
  --rule: var(--sheet--rule); --rule-2: var(--sheet--rule-2);
  --line: var(--sheet--line);
  --duo: var(--sheet--duo); --duo-photo: var(--sheet--duo);
  --btn-bg: var(--sheet--fg); --btn-fg: var(--sheet--bg);
  --focus: var(--sheet--fg);
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
}
/* The retired inks survive as aliases only, so a section still carrying one
   gets a complete bundle rather than no --bg at all. */
.ink-green, .ink-reflex, .ink-warm-red, .ink-kraft {
  --bg: var(--flood--bg); --fg: var(--flood--fg); --fg-2: var(--flood--fg-2);
  --ink-2: var(--flood--fg-2); --ink-3: var(--flood--fg-3);
  --rule: var(--flood--rule); --rule-2: var(--flood--rule-2);
  --line: var(--flood--line);
  --duo: var(--flood--duo); --duo-photo: var(--flood--duo-photo);
  --btn-bg: var(--flood--fg); --btn-fg: var(--flood--bg);
  --focus: var(--flood--fg);
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--flood--fg);
}
.ink-green-r, .ink-reflex-r, .ink-warm-red-r, .ink-kraft-r {
  --bg: var(--sheet--bg); --fg: var(--sheet--fg); --fg-2: var(--sheet--fg-2);
  --ink-2: var(--sheet--fg-2); --ink-3: var(--sheet--fg-3);
  --rule: var(--sheet--rule); --rule-2: var(--sheet--rule-2);
  --line: var(--sheet--line);
  --duo: var(--sheet--duo); --duo-photo: var(--sheet--duo);
  --btn-bg: var(--sheet--fg); --btn-fg: var(--sheet--bg);
  --focus: var(--sheet--fg);
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
}

/* Anything carrying a bundle class outside a <section> still has to paint. */
.ink-black, .ink-black-r, .ink-stock, .ink-stock-r,
.ink-green, .ink-green-r, .ink-reflex, .ink-reflex-r,
.ink-warm-red, .ink-warm-red-r, .ink-kraft, .ink-kraft-r,
.flood, .band, .band--ink, .band--white, .sheet {
  background-color: var(--bg);
  color: var(--fg);
}

/* =======================================================================
   THE DEFAULT DEAL - zero specificity, any hand-placed class wins
   =======================================================================
   A paper-first site: the sheet is the default everywhere, and the ONE flood
   is the closing band running into the footer. Note what is NOT here any
   more - v6 flooded `main > section:last-of-type` whether or not the page
   asked for it, which is how pages ended up carrying two dark fields. A page
   now gets a flood only where it places .closing (or the engine's .cta).
   ==================================================================== */
:where(.site-header, .site-header.scrolled, .split, .page-hero, .hero,
       main > section) {
  --bg: var(--sheet--bg); --fg: var(--sheet--fg); --fg-2: var(--sheet--fg-2);
  --ink-2: var(--sheet--fg-2); --ink-3: var(--sheet--fg-3);
  --rule: var(--sheet--rule); --rule-2: var(--sheet--rule-2);
  --line: var(--sheet--line);
  --duo: var(--sheet--duo); --duo-photo: var(--sheet--duo);
  --btn-bg: var(--sheet--fg); --btn-fg: var(--sheet--bg);
  --focus: var(--sheet--fg);
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
}
:where(.cta, .site-footer) {
  --bg: var(--flood--bg); --fg: var(--flood--fg); --fg-2: var(--flood--fg-2);
  --ink-2: var(--flood--fg-2); --ink-3: var(--flood--fg-3);
  --rule: var(--flood--rule); --rule-2: var(--flood--rule-2);
  --line: var(--flood--line);
  --duo: var(--flood--duo); --duo-photo: var(--flood--duo-photo);
  --btn-bg: var(--flood--fg); --btn-fg: var(--flood--bg);
  --focus: var(--flood--fg);
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--flood--fg);
}

/* =======================================================================
   BASE
   ==================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  background: var(--sheet);
}

body {
  margin: 0;
  background: var(--sheet);
  color: var(--ink);
  font-family: var(--text);
  font-size: var(--t-body);
  line-height: 1.62;
  font-weight: 400;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg { max-width: 100%; }
a { color: inherit; text-decoration: none; }
p { margin: 0 0 1em; }
ul { margin: 0 0 1em; padding-left: 1.1em; }
::selection { background: var(--fg); color: var(--bg); }

/* Every heading defaults to MANROPE. The display serif is applied by hand to
   the selectors under "THE DISPLAY SELECTORS" and nowhere else, so a heading
   a copy module adds later cannot inherit Instrument Serif at a size it was
   never drawn for - or, worse, inherit it together with a bold weight. */
h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--text);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -.005em;
  color: var(--fg);
}

/* Every figure on the site in Manrope's real tabular numerals. */
.factrule, .listing, .listing-row, .contents-row, .c-num, .num, .crumbs,
.foot-bottom, .contact-card .v, .contact-card a, .file-list .v, .nearby,
.foot-col a, .foot-col .v, .plate figcaption, .fig figcaption, .spec,
.spec-row, .spec-list, .chip, .step .n, .hero-meta, .outline {
  font-variant-numeric: tabular-nums;
}

/* =======================================================================
   THE CONTAINER
   =======================================================================
   .wrap is the engine's only container: a 12-column grid inset by
   clamp(24px, 5vw, 96px). Everything inside spans all twelve unless it says
   otherwise - .ed is what says otherwise.
   ==================================================================== */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--inset);
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter);
}
.wrap > * { grid-column: 1 / -1; min-width: 0; }

/* =======================================================================
   SECTIONS
   ==================================================================== */
main > section, .site-footer {
  position: relative;
  isolation: isolate;
  width: 100%;
  background-color: var(--bg);
  color: var(--fg);
}

/* Sized by content, generously. The space around a block is the other half
   of the rhythm, and in a light design it is most of the design. */
.section        { padding-block: clamp(72px, 8.5vw, 136px); }
.section--tight { padding-block: clamp(52px, 6vw, 96px); }

/* The masthead overlays the first section (build.py writes
   data-sticky="overlay" and the header pulls itself up by its own height),
   so whatever opens a page pays that height back. */
main > section:first-child { padding-top: calc(var(--head-h) + clamp(48px, 7vw, 96px)); }

/* =======================================================================
   THE RULE SYSTEM
   =======================================================================
   ONE weight: 1px. Which TOKEN a 1px rule takes is the whole decision:

     --rule     a typographic hairline between rows of type
     --rule-2   the rule that opens a block - a section rule
     --line     an EDGE that has to be seen to be understood: a field, the
                drop zone, a control. 3.72:1 on the sheet, 4.70:1 on the flood

   The single 2px rule allowed per page is drawn under the hero h1, by .split
   and .page-hero, and by nothing else in this file.
   ==================================================================== */
.factrule, .contents, .listing, .foot-cta, .spec-group {
  border-top: var(--hair) solid var(--rule-2);
}
.plate figcaption, .fig figcaption, .nearby,
.foot-bottom, .spec-row, .spec-list li, .contact-card .row,
.faq-list details, .file-list li, .form-msg, .form-status,
.city-card, .step, .prod, .svc, .feature {
  border-top: var(--hair) solid var(--rule);
}
.spec-list li:first-child, .contact-card .row:first-of-type { border-top: 0; }

/* Controls draw in --line, never in --rule. */
input, select, textarea, .chip, .drop-zone, .burger {
  border: var(--hair) solid var(--line);
}

/* =======================================================================
   THE DISPLAY SELECTORS - the only places Instrument Serif is used
   =======================================================================
   READ THIS BEFORE ADDING A SELECTOR HERE.

   font-weight: 400 on this block is not a weight CHOICE - it REMOVES one.
   These selectors land on h1/h2/h3/b elements whose UA or inherited weight
   is 600-900. Instrument Serif ships 400 and a 400 italic and nothing else,
   so a bold request against it makes the browser synthesize a fake bold:
   stems thicken, counters close, and the line reads as a smear. That defect
   is exactly what got two previous passes rejected. Naming 400 hands the
   face back its real outline.

   NOTHING else in this file sets font-weight on any of these selectors, and
   no selector below ever raises one above 400. The italic is the emphasis
   device; there is no bold in the display face anywhere on this site.

   Line-height 1.08 sits inside the 1.05-1.12 window. Tracking is -.005em -
   a hair, correct at 40px and up on a normal-width serif, and well inside
   the -0.01em floor.
   ==================================================================== */
.h-display, .split-text h1, .page-hero h1, .hero h1, .h-section,
.c-name, .factrule .f .v, .quote, .outline, .c-num, .closing h2,
.cta h2, .foot-cta-h, .prose h2, .panel > h2, .plate-title {
  font-family: var(--display);
  font-weight: 400;
  font-style: normal;
  line-height: 1.08;
  letter-spacing: -.005em;
  text-wrap: balance;
  color: var(--fg);
}

.h-display, .split-text h1 { font-size: var(--t-hero); }
.page-hero h1, .hero h1    { font-size: var(--t-title); }
.h-section, .closing h2, .cta h2 { font-size: var(--t-section); }
.foot-cta-h                { font-size: clamp(28px, 2.6vw, 38px); }
.prose h2, .panel > h2     { font-size: var(--t-sub); line-height: 1.12; }

/* The one emphasis device in the display face. An .it inside a display line
   is the italic; in running text it is Manrope 700. Both are declared here
   so a copy module can use one class in either place. */
.h-display .it, .split-text h1 .it, .page-hero h1 .it, .hero h1 .it,
.h-section .it, .closing h2 .it, .cta h2 .it, .foot-cta-h .it,
.c-name .it, .quote .it, .prose h2 .it {
  font-style: italic;
  font-weight: 400;   /* never above 400 on the display face */
  color: inherit;
  text-decoration: none;
}
.it { font-style: italic; font-weight: 700; color: var(--fg); }

/* THE GUARD. A copy module that writes <b>, <strong> or <em> inside a display
   line would otherwise pick up a 600/700 from .prose b, from the UA, or from
   an inherited rule - and a bold request against a face that ships only 400
   is exactly the fake bold this pass exists to make impossible. :is() takes
   the specificity of its most specific argument, so this outranks .prose b. */
:is(.h-display, .split-text h1, .page-hero h1, .hero h1, .h-section, .c-name,
    .quote, .closing h2, .cta h2, .foot-cta-h, .prose h2, .panel > h2,
    .aside-card h2, .factrule .f .v, .plate-title) :is(b, strong, em, i, span, a) {
  font-weight: 400;
}

/* =======================================================================
   THE READING VOICE
   ==================================================================== */
.lead, .prose, .split-text > p, .page-hero > .wrap > p, .closing > .wrap > p {
  color: var(--ink-2);
  font-size: var(--t-body);
  line-height: 1.66;
  max-width: var(--measure);
}
.lead, .split-text > p { font-size: var(--t-lead); line-height: 1.58; }

/* .lede - the small-caps Manrope label. 12px in --ink-2 at 6.69:1, NOT in
   --ink-3: at this size it is body-sized text and 3.72:1 would fail it. */
.lede, .eyebrow, .hero-eyebrow, .kicker {
  display: block;
  font-family: var(--text);
  font-size: var(--t-label);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.eyebrow, .hero-eyebrow, .kicker { display: inline-flex; align-items: center; gap: 9px; }
.lede + *, .eyebrow + * { margin-top: 14px; }
.h-section + p, .h-section + ul, .h-section + .chips { margin-top: 22px; }

/* =======================================================================
   1. THE GRID  -  .ed
   =======================================================================
   The spine of the whole site. Twelve columns: a LABEL column at 1-2 that
   holds .lede and .outline, a READING column at 4-10 that holds everything
   a person actually reads, and columns 11-12 left as air. The offset is the
   point - nothing on this site starts at the left edge of the container
   except the label that names it.

   Auto-placement does the rest: a .lede sits in the label column of the row
   it opens, and the block that follows it lands beside it in the reading
   column. An .outline placed later in the source drops into the margin
   beside whatever block it introduces.

   Wide components - the contents list, the fact rule, a listing, a plate
   gallery - run from column 4 to the right edge, so they use the air the
   reading measure deliberately leaves. The .split hero spans all twelve.

   COLUMN 3 IS EMPTY ON PURPOSE. The label column ends at 2 and the reading
   column starts at 4, so the whole of column 3 plus two gutters stands
   between them. That gap is what guarantees an .outline numeral can never
   overlap the reading column: measured at every stop, "06" at the clamp's
   value is 62px at 900 (label track 110px), 70px at 1024 (130px) and 97px
   at 1440 (157px) - inside its own track at all three, with column 3 in
   reserve if a copy module ever sets a wider number.

   Markup:
     <div class="ed">
       <span class="lede">Since 1987</span>
       <h2 class="h-section">Section head</h2>
       <p>One paragraph on the reading measure.</p>
       <span class="outline" aria-hidden="true">02</span>
       <div class="contents"> ... </div>
     </div>
   ==================================================================== */
.ed {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter);
  row-gap: clamp(24px, 2.6vw, 40px);
  align-items: start;
}
.ed > * { grid-column: 4 / span 7; min-width: 0; }
.ed > .lede, .ed > .outline, .ed > .ed-label { grid-column: 1 / span 2; }
.ed > .contents, .ed > .factrule, .ed > .listing, .ed > .plates,
.ed > .spec, .ed > .panel { grid-column: 4 / -1; }
.ed > .split, .ed > .quote { grid-column: 1 / -1; }
/* A run of prose keeps its own tighter rhythm inside the reading column. */
.ed > p + p { margin-top: -.4em; }
.ed > .lede { padding-top: .45em; }

/* =======================================================================
   2. THE HERO  -  .split / .split-text / .split-plate
   =======================================================================
   The 5/7 division: the reading side takes the seven columns and the plate
   the five. That way the text lands at ~58 characters with air to its right
   and the plate sits in a column comfortably wider than its own native
   width, so the photograph is never asked to scale up to fill anything.

   Sized to its content. NEVER 100vh, never a full-bleed image, never a
   photograph behind type.

   THE ONE 2px RULE ON THE PAGE closes this block, directly under the h1 and
   its paragraph. No other selector in this file draws a 2px rule.

   Markup:
     <section class="section split-section">
       <div class="wrap"><div class="split">
         <div class="split-text">
           <span class="lede">Commercial printing</span>
           <h1>Printed, bound <span class="it">and mailed</span>.</h1>
           <p>One paragraph.</p>
           <p class="split-links"><a class="link" href="...">Get a quote</a>
              <a class="link" href="...">See the equipment</a></p>
         </div>
         <figure class="split-plate">
           <img src="..." width="400" height="250" alt="...">
           <figcaption>One line.</figcaption>
         </figure>
       </div></div>
     </section>
   ==================================================================== */

/* =======================================================================
   THE HOME OPENER  -  .hero
   =======================================================================
   One full-bleed photograph with the page's title, its paragraph and its
   two actions sitting ON it. The section carries .flood, so every token
   inside - the lede, the h1, the paragraph, .btn and .btn--ghost - resolves
   for a dark ground exactly as it does in the closing band: the primary
   button is --action-ink (#CE1B86, 5.09:1 label), the ghost is a sheet
   outline, the paragraph is --flood--fg-2 at 9.51:1. Nothing here names a
   color.

   THE PHOTO IS TEMPORARY, BY EXPLICIT CLIENT DECISION (2026-09-06). BRIEF
   Sec.15 bars a full-bleed photo hero for a real reason: the only source is
   omega-lithograph-building.jpg at 400x250px, and at a desktop width that
   is a 3-5x upscale (3.6x at 1440px, worse on retina). Told this plainly,
   the client chose to run it anyway and swap in real photography later
   ("I'll go and get a new picture at some point"). Two legitimate print
   techniques make the accepted softness read as a choice:
     1. --flood--duo-photo, the duotone every photo on a flood already
        takes - dark stays ink, light goes to the cool secondary tint. A
        two-tone image loses far less to upscaling than full color does.
     2. A halftone dot screen (::after). That is what offset printing IS,
        so laying one over a printer's own photograph is on-brand, and it
        breaks up the soft edges an upscale produces.
   The scrim (::before) is the third layer and the one that matters for
   the type: a ground-up gradient in the ink to .96 at the baseline, where
   the h1 and the buttons sit, and a left-weighted one across the reading
   side, so the measured ratios above hold on the photo and not only on a
   flat flood.

   TO SWAP THE PHOTO LATER: replace assets/photos/omega-lithograph-
   building.jpg with a real photograph (1800px+ wide) and update its
   width/height in assets/photos/manifest.py. Nothing else changes.

   The masthead sits on this section transparent (.site-header--over, see
   THE MASTHEAD below) and paints the sheet back in once the page scrolls.
   ======================================================================= */
.hero, main > section.hero:first-child {
  position: relative; isolation: isolate;
  display: grid; align-content: end;
  min-height: clamp(580px, 88vh, 820px);
  padding-top: calc(var(--head-h) + clamp(56px, 8vw, 120px));
  padding-bottom: clamp(40px, 5vw, 72px);
  background: var(--ink);
  overflow: hidden;
}
.hero-img {
  position: absolute; inset: 0; z-index: -3;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center 55%;
  filter: contrast(1.18) saturate(1.05) var(--flood--duo-photo);
}
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: -2;
  background:
    /* top-down: the masthead's links and the top of the h1 sit on ink,
       not on the lightest part of the stonework */
    linear-gradient(to bottom, rgba(26,22,20,.72) 0%, rgba(26,22,20,.40) 22%,
                    rgba(26,22,20,0) 48%),
    linear-gradient(to top, rgba(26,22,20,.96) 0%, rgba(26,22,20,.80) 32%,
                    rgba(26,22,20,.34) 66%, rgba(26,22,20,.14) 100%),
    linear-gradient(to right, rgba(26,22,20,.50) 0%, rgba(26,22,20,0) 62%);
}
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background-image: radial-gradient(var(--sheet) 1px, transparent 1.6px);
  background-size: 5px 5px;
  opacity: .13;
  mix-blend-mode: multiply;
  pointer-events: none;
}
.hero > .wrap { align-items: end; row-gap: 28px; }
.hero-body { grid-column: 1 / span 8; min-width: 0; }
.hero h1 {
  margin: 18px 0 0;
  font-size: var(--t-hero);
  max-width: 20ch;
}
.hero-body > p:not(.hero-actions) {
  margin-top: clamp(20px, 2vw, 30px);
  max-width: 50ch;
  color: var(--ink-2);
  font-size: var(--t-lead); line-height: 1.58;
}
.hero .hero-actions { margin-top: clamp(28px, 3.2vw, 44px); }
/* The caption: bottom right, on the same baseline as the buttons. */
.hero-caption {
  grid-column: 10 / -1; margin: 0; padding-bottom: 8px;
  font-size: var(--t-cap); line-height: 1.5; color: var(--ink-2);
}
@media (max-width: 900px) {
  .hero-body, .hero-caption { grid-column: 1 / -1; }
  .hero-caption { padding-bottom: 0; }
}
@media (max-width: 640px) {
  .hero, main > section.hero:first-child {
    min-height: 0;
    padding-top: calc(var(--head-h) + 88px);
    padding-bottom: 36px;
  }
  .hero h1 { max-width: none; }
  .hero-caption { display: none; }
}

.split {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  column-gap: clamp(28px, 4vw, 72px);
  row-gap: clamp(36px, 4vw, 56px);
  align-items: end;
  padding-bottom: clamp(32px, 4vw, 56px);
  /* The single 2px rule per page. */
  border-bottom: 2px solid var(--fg);
}
.split-text { min-width: 0; }
.split-text h1 { margin: 16px 0 0; }
.split-text > p { margin-top: clamp(20px, 2vw, 30px); max-width: 58ch; }
.split-links, .split-text .hero-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 14px clamp(22px, 2.6vw, 36px);
  margin-top: clamp(24px, 2.6vw, 36px);
}
.split-plate { margin: 0; justify-self: start; min-width: 0; }
.split-plate img { display: block; height: auto; max-width: 100%; }

/* A fact rule immediately under the hero must not draw a second rule on top
   of the hero's own. */
.split + .factrule, .split-section + .section > .wrap > .factrule:first-child {
  border-top: 0;
}

/* =======================================================================
   3. THE FACT RULE  -  .factrule / .f / .k / .v
   =======================================================================
   One thin band of four facts, hairline separated. A label in caps over a
   value in the display serif. Quiet - this is a line of type, not a row of
   stat tiles, and nothing in it is boxed, filled or enlarged.

   Markup:
     <div class="factrule">
       <div class="f"><span class="k">Since</span><span class="v">1987</span></div>
       x4
     </div>
   ==================================================================== */
.factrule {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
}
.factrule .f {
  min-width: 0;
  padding: clamp(20px, 2vw, 28px) clamp(14px, 1.6vw, 24px) clamp(18px, 2vw, 26px) 0;
}
.factrule .f + .f {
  padding-left: clamp(14px, 1.6vw, 24px);
  border-left: var(--hair) solid var(--rule);
}
.factrule .f .k {
  display: block;
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.factrule .f .v {
  display: block;
  margin-top: 10px;
  font-size: var(--t-sub);
  color: var(--fg);
}
.factrule .f .d {
  display: block;
  margin-top: 8px;
  font-size: var(--t-min);
  line-height: 1.5;
  color: var(--ink-2);
}

/* =======================================================================
   4. THE CONTENTS  -  .contents / .contents-row  -  THE CENTERPIECE
   =======================================================================
   The capabilities set the way a book sets its table of contents: an
   outlined numeral, the name in Instrument Serif, a DOTTED LEADER running
   across the gap to a right-aligned description in Manrope. It has to read
   as a printed contents page, not as a nav list - which is why the leader
   is the widest thing in the row and why the row is generous rather than
   compact.

   THE LEADER, mechanically. The row is baseline-aligned. .c-leader is an
   empty grid item, so its height is 0 and the baseline it aligns by is its
   own bottom edge - which puts its 1px dotted background exactly on the
   row's text baseline, where a leader's dots belong. The dots are drawn by
   a repeating-linear-gradient rather than `border-bottom: dotted`, because
   dotted-border spacing is a UA decision and the rhythm has to be the same
   in every browser: a 1px dot every 6px.

   On hover or keyboard focus the leader goes solid --ink-3 (3.72:1) and the
   name shifts 4px, over 200ms. Nothing inverts, nothing floods.

   Markup:
     <div class="contents">
       <a class="contents-row" href="/offset-printing">
         <span class="c-num" aria-hidden="true">01</span>
         <span class="c-name">Offset printing</span>
         <span class="c-leader" aria-hidden="true"></span>
         <span class="c-desc">One to five color on metal plates</span>
       </a>
     </div>
   ==================================================================== */
.contents { display: block; }
.contents-row {
  display: grid;
  grid-template-columns: auto minmax(0, auto) minmax(28px, 1fr) minmax(0, auto);
  align-items: baseline;
  column-gap: clamp(14px, 1.6vw, 24px);
  padding: clamp(22px, 2.4vw, 30px) 0;
  min-height: 76px;
  color: var(--fg);
  border-bottom: var(--hair) solid var(--rule);
}
.contents-row:first-child { border-top: 0; }
a.contents-row { cursor: pointer; }

/* The numeral: the .outline treatment at contents scale. It orders the list
   and nothing more - aria-hidden in the markup, and the name beside it is
   what a screen reader and a search engine read. */
.c-num {
  font-size: clamp(28px, 2.4vw, 34px);
  line-height: 1;
  /* The action color, by the client's call (BRIEF Sec.17) - the one
     exception to "controls only". --act-text resolves per ground. */
  color: var(--act-text);
  -webkit-text-fill-color: transparent;
  -webkit-text-stroke: 1px currentColor;
  user-select: none;
}
.c-name {
  font-size: var(--t-sub);
  color: var(--fg);
  min-width: 0;
  overflow-wrap: break-word;
  transition: transform var(--row) var(--ease);
}
.c-leader {
  /* A 1px-tall box, baseline-aligned: an empty grid item has no baseline of
     its own, so it is aligned by its bottom edge - which lands the dots one
     pixel under the row's text baseline, where a leader's dots belong. The
     height is explicit rather than 0 so the background has a box to paint
     in; a zero-height box paints nothing in some engines. */
  align-self: baseline;
  height: 1px;
  min-width: 28px;
  background-color: transparent;
  background-image: repeating-linear-gradient(to right,
                     var(--rule-2) 0 1px, transparent 1px 6px);
  transition: background-color var(--row) var(--ease);
}
.c-desc {
  font-family: var(--text);
  font-size: var(--t-min);
  line-height: 1.45;
  color: var(--ink-2);
  text-align: right;
  min-width: 0;
  text-wrap: balance;
}
.contents-row:hover .c-leader,
.contents-row:focus-visible .c-leader,
.contents-row:focus-within .c-leader {
  background-image: none;
  background-color: var(--ink-3);
}
.contents-row:hover .c-name,
.contents-row:focus-visible .c-name,
.contents-row:focus-within .c-name { transform: translateX(4px); }
.contents-row:focus-visible { outline-offset: -4px; }

/* =======================================================================
   5. THE SIGNATURE  -  .outline
   =======================================================================
   A display numeral drawn as a hairline outline with no fill: large, airy
   and weightless. It is the device that gives a page scale without giving
   it mass - the direct answer to "too bold".

   THE COLOR MECHANIC. The obvious recipe - color: transparent plus
   -webkit-text-stroke: 1px currentColor - draws nothing, because
   currentColor is then transparent too. The correct form is below: color
   carries the stroke, -webkit-text-fill-color knocks the fill out, and the
   stroke reads currentColor from the color property.

   The @supports fallback matters. A browser without -webkit-text-stroke
   also has no -webkit-text-fill-color, so it renders a SOLID --ink-3
   numeral. That is the intended degradation, and it is why the block below
   restates color explicitly rather than relying on it.

   It is ORNAMENT: aria-hidden in the markup, pointer-events off, and never
   the only carrier of a number that matters.
   ==================================================================== */
.outline {
  display: block;
  font-size: var(--t-outline);
  line-height: 1;
  letter-spacing: 0;
  color: var(--act-text);   /* BRIEF Sec.17: the numerals carry the accent */
  -webkit-text-fill-color: transparent;
  -webkit-text-stroke: 1px currentColor;
  user-select: none;
  pointer-events: none;
  margin: 0;
}
@supports not (-webkit-text-stroke: 1px currentColor) {
  .outline { color: var(--act-text); -webkit-text-fill-color: currentColor; }
}

/* =======================================================================
   6. THE PLATES  -  .plates / .plate
   =======================================================================
   A deliberately uneven gallery: different spans on a twelve-column grid,
   every photograph at NATIVE SIZE, every one captioned. Not a grid of equal
   cards. The variants change the SPAN, never the image - width:auto plus
   max-width:100% means a plate can only ever be shown smaller than it was
   published, never larger.

   Markup:
     <div class="plates">
       <figure class="plate plate--wide">
         <img src="..." width="400" height="250" alt="...">
         <figcaption>One line about the photograph.</figcaption>
       </figure>
     </div>
   ==================================================================== */
.plates {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter);
  row-gap: clamp(32px, 4vw, 64px);
  align-items: start;
}
.plates > .plate { grid-column: span 4; }
.plates > .plate--wide { grid-column: span 6; }
.plates > .plate--tall { grid-column: span 3; }
/* The unevenness: every second plate drops, so the row never reads as a
   row of cards. */
.plates > .plate:nth-child(3n + 2) { margin-top: clamp(20px, 3vw, 56px); }

/* THE FIGURE IS AS WIDE AS ITS PICTURE. --pw is the photograph's published
   width, written onto the <figure> by the _plate() helpers straight out of the
   manifest, so the caption wraps at the image's edge and the hairline under it
   ends where the picture ends. Before this the caption was capped at 44ch -
   a count of characters, which has nothing to do with how wide the picture is:
   down /equipment, /prepress and /bindery the rule ran 103px past the right
   edge of every 300px plate, four times on one page.

   min(--pw, 100%) and not width:min-content, which looks like it should do the
   same job and does not: the image inside carries max-width:100%, so asking
   the figure to size to its contents makes the percentage resolve against a
   width that is still being computed. Browsers break that cycle by treating
   the image as contributing nothing, and the figure collapses to the longest
   word in the caption - measured at 47-100px against a 300px photograph. A
   real length from the manifest has no cycle in it. */
.plate, .fig {
  margin: 0;
  width: min(var(--pw, 100%), 100%);
  color: var(--fg);
  min-width: 0;
}
/* NO width:auto. The width/height attributes on every <img> in the build are
   presentational hints, and an author `width: auto` beats them - which left an
   unloaded photograph with an intrinsic size of 0x0, so the figure above had
   nothing to size itself to until the bytes arrived. Letting the attribute
   stand gives the plate its real box immediately: no reflow when the image
   lands, and max-width:100% still means a photograph can only ever be shown
   SMALLER than it was published, never larger. */
.plate img, .fig img {
  display: block;
  height: auto; max-width: 100%;
  filter: contrast(1.1) var(--duo-photo);
}
/* The manufacturer product shots are light gray machines on pure white;
   once the white has dissolved into the ground they need one more contrast
   step to stand off it. */
.fig--plate img, .plate--cut img { filter: contrast(1.16) var(--duo); }

.plate figcaption, .fig figcaption {
  margin-top: 14px;
  padding-top: 12px;
  font-size: var(--t-cap);
  line-height: 1.5;
  color: var(--ink-2);
}
.plate figcaption b, .fig figcaption b { color: var(--fg); font-weight: 600; }
.prose .plate, .prose .fig { margin: 40px 0; }

/* =======================================================================
   7. THE LISTING  -  .listing / .listing-row
   =======================================================================
   A two-column hairline list: the thirteen products, and the equipment spec
   excerpt. Manrope throughout, tabular figures, no tiles and no icons.

   Markup:
     <div class="listing">
       <div class="listing-row"><span class="k">Saddle stitcher</span>
         <span class="v">Up to 7,000 books per hour</span></div>
     </div>
   ==================================================================== */
.listing { display: block; }
.listing-row {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  column-gap: var(--gutter);
  align-items: baseline;
  padding: 14px 0;
  font-size: var(--t-min);
  line-height: 1.5;
  border-top: 0;
  border-bottom: var(--hair) solid var(--rule);
}
.listing-row .k { color: var(--ink-2); font-weight: 600; letter-spacing: .01em; min-width: 0; }
.listing-row .v { color: var(--fg); min-width: 0; overflow-wrap: break-word; }
.listing-row .v b, .listing-row .v strong { font-weight: 600; color: var(--fg); }
.listing-row > :only-child { grid-column: 1 / -1; }
a.listing-row { transition: none; }
a.listing-row:hover .v { text-decoration: underline; text-underline-offset: .22em; text-decoration-thickness: 1px; }
/* A .listing that follows a .panel's calculator inputs (the five /tools
   pages) needs air above it the same way every other spacing relationship
   on the site is a class rule, not a hand-set inline style; a second
   .listing stacked directly under the first (fold-calculator's live panel
   list) needs less, since it is already inside the same result block. */
.panel .listing { margin-top: 24px; }
.listing + .listing { margin-top: 8px; }

/* =======================================================================
   8. THE PULL QUOTE  -  .quote
   =======================================================================
   One italic Instrument Serif line, centered, with generous air above and
   below. THE ONLY CENTERED ELEMENT ON THE SITE - one per page, maximum.
   ==================================================================== */
.quote {
  font-size: var(--t-quote);
  font-style: italic;
  line-height: 1.24;
  text-align: center;
  text-wrap: balance;
  max-width: 26ch;
  margin: clamp(48px, 7vw, 112px) auto;
  color: var(--fg);
}
.quote cite {
  display: block;
  margin-top: 22px;
  font-family: var(--text);
  font-size: var(--t-label);
  font-style: normal;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-2);
}

/* =======================================================================
   9. THE CLOSING BAND  -  .closing
   =======================================================================
   The single ink flood on the page. A serif heading, one line, two links -
   and then the footer, which carries the same bundle, so the two read as
   one field of ink at the foot of the sheet rather than as two floods.
   ==================================================================== */
/* v7.3: THE CLOSING BAND IS A LIGHT PANEL, not the flood. Luke asked for
   the bottom CTA to look nicer and for one purple everywhere including the
   phone number - and purple text does not read on the ink. So the band is
   --sheet-2 (the panel bundle, re-derived against #EFF1F2), ruled at the
   top, and the footer's ink starts under it. Layout: heading and line in
   columns 1-8, the two buttons stacked in columns 10-12 on the same
   baseline. Every module's _closing() renders into this - lede/h2/p/links
   directly in .wrap, or inside one .reveal - so both containers are the
   grid. */
.closing {
  --bg: var(--sheet-2); --fg: #1A1614; --fg-2: #54585C;
  --ink-2: #54585C; --ink-3: #7B8085;
  --rule: rgba(26, 22, 20, .14); --rule-2: rgba(26, 22, 20, .28); --line: #7B8085;
  --btn-bg: #1A1614; --btn-fg: #EFF1F2; --focus: #1A1614;
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
  background-color: var(--bg); color: var(--fg);
  display: grid; align-content: center;
  padding-block: clamp(80px, 9vw, 136px);
  border-top: var(--hair) solid var(--rule-2);
}
.closing > .wrap > .reveal { grid-column: 1 / -1; }
:is(.closing > .wrap, .closing > .wrap > .reveal) {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter); align-items: end;
}
/* :not(.reveal): the wrapper is a child of .wrap too, and must keep the
   full twelve columns or its own grid is only eight wide. */
:is(.closing > .wrap, .closing > .wrap > .reveal) > :not(.reveal) { min-width: 0; grid-column: 1 / span 8; }
.closing .lede { color: var(--ink-2); margin-bottom: 4px; }
.closing h2 { margin: 0; max-width: 18ch; font-size: var(--t-title); }
.closing p:not(.closing-links) {
  margin-top: 22px;
  color: var(--ink-2);
  font-size: var(--t-lead); line-height: 1.55;
  max-width: 46ch;
}
/* Placed with the same :is() prefix as the child rule above, so it outranks
   it - a bare .closing-links (0,1,0) loses to that (0,3,0) rule and the
   buttons land in column 1, row 1, above the heading. */
:is(.closing > .wrap, .closing > .wrap > .reveal) > :is(.closing-links, .hero-actions, .cta-actions) {
  grid-column: 10 / -1; grid-row: 1 / span 4; align-self: end;
  display: flex; flex-direction: column; align-items: stretch;
  gap: 12px; margin: 0; max-width: none;
}
.closing-links .btn { width: 100%; justify-content: center; padding: 19px 28px; }
.closing-links .btn:not(.btn--ghost)::after { content: "\2197"; font-size: .85em; }
@media (max-width: 900px) {
  :is(.closing > .wrap, .closing > .wrap > .reveal) > *,
  :is(.closing > .wrap, .closing > .wrap > .reveal) > :is(.closing-links, .hero-actions, .cta-actions) {
    grid-column: 1 / -1; grid-row: auto;
  }
  :is(.closing > .wrap, .closing > .wrap > .reveal) > :is(.closing-links, .hero-actions, .cta-actions) {
    flex-direction: row; flex-wrap: wrap; margin-top: clamp(28px, 4vw, 40px);
  }
  .closing-links .btn { width: auto; }
}
@media (max-width: 560px) {
  :is(.closing > .wrap, .closing > .wrap > .reveal) > :is(.closing-links, .hero-actions, .cta-actions) { flex-direction: column; }
}

/* =======================================================================
   10. THE PANEL  -  .panel
   =======================================================================
   A --sheet-2 block for the one or two things that need separating without
   a border. It carries its own bundle, so the rules and secondary text
   inside it are re-derived against #EFF1F2 rather than inherited from the
   sheet - see the second block of the contrast table in the header.
   ==================================================================== */
.panel {
  --bg: var(--sheet-2);
  --fg: #1A1614;
  --fg-2: #54585C;
  --ink-2: #54585C;
  --ink-3: #7B8085;
  --rule: rgba(26, 22, 20, .14);
  --rule-2: rgba(26, 22, 20, .28);
  --line: #7B8085;
  --btn-bg: #1A1614;
  --btn-fg: #EFF1F2;
  --focus: #1A1614;
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
  background-color: var(--bg);
  color: var(--fg);
  padding: clamp(24px, 3vw, 44px);
}
.panel > :last-child { margin-bottom: 0; }
.panel > h2 { margin: 0 0 16px; }
/* On the measure like every other reading block. Without the cap a panel
   paragraph ran to ~109 characters at 1440 against the 84 the same page's
   prose reads at - 25 characters longer, on the same screen, and long enough
   to lose the return sweep. BRIEF 12 keeps ~65ch binding. */
.panel p { color: var(--ink-2); max-width: var(--measure); }

/* =======================================================================
   11. LINKS AND BUTTONS  -  the hierarchy (section 16.2)
   =======================================================================
   WHAT WAS WRONG. Under v7 `.btn` was authored `background: transparent`
   with a hairline border, so every button on the site was a ghost outline
   and the homepage's primary action was a bare `<a class="link">`. On a
   monochrome page that left nothing saying CLICK THIS rather than READ
   THIS. Refinement is for the page; a control is allowed to be obvious.

   THREE LEVELS, and each one looks like what it is:

     .btn          PRIMARY. A filled slab in the action color with a white
                   label, 0 radius, 16/32 padding, Manrope 600 in sentence
                   case at a modest .01em - a button, not a letterspaced
                   label. Its 1px border is the SAME color as its fill, so
                   the control's box is unambiguous at its own edge rather
                   than only where the fill happens to stop.
     .btn--ghost   SECONDARY. Transparent, 1px --fg, ink label; fills to
                   --fg with a sheet label on hover. It inverts with the
                   ground because both values are bundle tokens.
     .btn--lg      A SIZE MODIFIER ONLY. Padding. No color of its own.
     .link         TERTIARY, and body-flow only: 1px underline growing to
                   2px, in INK on the sheet, so a paragraph carrying four
                   links does not turn into a field of color. A standalone
                   action link takes .link--action and the action color.

   THE GROUND DECIDES THE FILL, and it does it through tokens rather than
   through a list of context selectors, exactly like every other component
   here: --act-bg / --act-fg / --act-text are declared in each bundle, so
   the same .btn is #A8006C inside a section and #CE1B86 inside .closing,
   .site-footer or any .band--ink, and each is measured on the ground it
   actually lands on. Nothing below writes --action by hand.

   MEASURED, every combination this block ships:

     PRIMARY .btn ON THE SHEET  (ground #F6F7F8)
       fill #A8006C edge on ground        6.79   floor 3.0   PASS
       fill #A8006C edge on panel #EFF1F2 6.43   floor 3.0   PASS
       label #FFFFFF on fill              7.29   floor 4.5   PASS
       hover fill #8A0059 edge on ground  8.80   floor 3.0   PASS
       hover label #FFFFFF on hover fill  9.44   floor 4.5   PASS
     PRIMARY .btn ON THE FLOOD  (ground #1A1614)
       fill #CE1B86 edge on ground        3.53   floor 3.0   PASS
       label #FFFFFF on fill              5.09   floor 4.5   PASS
       hover fill #E88BC2 edge on ground  7.62   floor 3.0   PASS
       hover label #1A1614 on hover fill  7.62   floor 4.5   PASS
     SECONDARY .btn--ghost
       edge #1A1614 on the sheet         16.75   floor 3.0   PASS
       edge #F6F7F8 on the flood         16.75   floor 3.0   PASS
       label, either ground              16.75   floor 4.5   PASS
       hover swaps fill and label - the same 16.75 in both directions
     TERTIARY
       .link #1A1614 on the sheet        16.75   floor 4.5   PASS
       .link #F6F7F8 on the flood        16.75   floor 4.5   PASS
       .link--action #A8006C on sheet     6.79   floor 4.5   PASS
       .link--action #E88BC2 on flood     7.62   floor 4.5   PASS
     FOCUS RING  (the FOCUS block below carries the rest of the working)
       #1A1614 ring on the sheet         16.75   floor 3.0   PASS
       #F6F7F8 ring on the flood         16.75   floor 3.0   PASS

   THE HOVER ON THE FLOOD GOES LIGHTER, not darker, and that is forced
   rather than chosen: every value dark enough to read as "pressed" against
   #1A1614 drops the control edge under 3:1 (#A8006C itself is 2.47:1
   there). So on the ink the button brightens to --action-link and its
   label flips to ink - the same inversion .btn--ghost already performs on
   hover - at 7.62:1 on both the edge and the label, which is more headroom
   than the resting state has.
   ==================================================================== */
.link, .link-arrow, .more {
  font-family: var(--text);
  font-size: var(--t-min);
  font-weight: 600;
  letter-spacing: .01em;
  text-transform: none;
  color: var(--fg);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .28em;
  transition: text-decoration-thickness var(--snap) var(--ease);
}
.link:hover, .link-arrow:hover, .more:hover, a:hover > .more {
  text-decoration-thickness: 2px;
}

/* PRIMARY. 15px is deliberate, and it is the one size here that is not on
   the reading scale: at --t-nav (13px) a sentence-case label inside 16/32
   padding reads as a caption someone drew a box around, and at --t-min
   (17px) the masthead button outgrows the masthead. 15px at 600 is a
   button. The uppercase .1em tracking this rule used to carry went with the
   outline - section 16.2 asks for a button, not a letterspaced label. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 16px 32px;
  font-family: var(--text);
  font-size: 15px;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: .01em;
  text-transform: none;
  white-space: nowrap;
  min-width: 0;
  background: var(--act-bg);
  color: var(--act-fg);
  /* The same color as the fill. This border is not decoration - it is the
     edge that says where the control ends, and drawing it in the fill color
     makes the box unambiguous without introducing a second value. */
  border: var(--hair) solid var(--act-bg);
  border-radius: 0;
  cursor: pointer;
  transition: background var(--snap) var(--ease), color var(--snap) var(--ease),
              border-color var(--snap) var(--ease);
}
.btn:hover {
  background: var(--act-bg-hover);
  color: var(--act-fg-hover);
  border-color: var(--act-bg-hover);
}
/* .btn--moss is emitted by cta_block() and by the engine's service_page()
   hero, ALWAYS in the primary slot and always alongside .btn. Under v7 it
   was a ghost override, which is exactly how the engine's primary CTA lost
   its fill on every page built through those helpers. It is now a no-op:
   the element keeps .btn's filled primary and the alias declares nothing. */

/* SECONDARY. Its border is its ONLY boundary, so WCAG 1.4.11 governs it and
   it has to clear 3:1 against the ground behind it. Section 16.2 puts that
   edge in --fg rather than in --line: this is the visual partner of a
   filled primary now, not the quieter of two outlines, and --line (3.72:1
   on the sheet) sitting beside a 6.79:1 fill reads as disabled. --fg is
   16.75:1 on both grounds, and the pair still separates cleanly, because
   one of them is filled and the other is not. */
.btn--ghost {
  background: transparent;
  /* v7.3: the ghost is the PHONE, and the phone is purple - so on light
     ground it is a purple outline with a purple label (6.79:1 edge, 6.79:1
     text), and on the flood, where --act-text is the sheet, a sheet outline.
     Hover fills it with the same purple the primary carries. */
  color: var(--act-text);
  border-color: var(--act-text);
  font-weight: 600;
}
.btn--ghost:hover { background: var(--act-bg); color: var(--act-fg); border-color: var(--act-bg); }
/* A SIZE MODIFIER ONLY - padding, and nothing that carries a color. */
.btn--lg { padding: 19px 38px; }
.btn[disabled] { opacity: .5; cursor: not-allowed; }
.arrow { font-size: .85em; }

/* .link--action - a standalone action link: the third item in a .cta-inline,
   or the one line of a block that IS the action. Body-flow .link stays ink
   for the reason given at the head of this block. It is declared once, at
   the foot of the running-text rules below, where the specificity it has to
   clear is stated alongside it. */

/* =======================================================================
   THE INLINE CALL TO ACTION  -  .cta-inline
   =======================================================================
   Section 16.3 wants a way to act roughly every screen and a half, and the
   places that need one - after the contents list, after the spec excerpt,
   after the products listing - are mid-page, between two reading blocks.
   A card there would be a second .panel competing with the prose, so this
   is NOT a card: no fill, no border, no radius, no shadow. It is a single
   hairline above a line of type, the way a printed page rules off a note
   at the foot of a column, and then the two controls on the same baseline.

   It sits on the .ed grid in the reading columns (4 / -1), so it lines up
   with the .contents, .listing and .spec blocks it follows rather than
   floating free of them, and it folds with everything else at 900px.

   Markup:
     <div class="cta-inline">
       <p>One short sentence - what the reader can do next.</p>
       <a href="/request-a-quote" class="btn">Request a quote</a>
       <a href="tel:+17634751826" class="link link--action">Call (763) 475-1826</a>
     </div>

   The phone number is the third slot on purpose: primary button first,
   because that is the action being asked for, and the call as the tertiary
   alternative rather than as a second button competing with it.
   ==================================================================== */
.cta-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px clamp(20px, 2.4vw, 34px);
  margin-top: clamp(30px, 3.4vw, 48px);
  /* v7.3: a --sheet-2 panel rather than a bare ruled row - Luke asked for
     the quote prompts to look nicer. It carries the panel bundle so the
     purple, the rules and the secondary text re-derive against #EFF1F2. */
  --bg: var(--sheet-2); --fg: #1A1614; --fg-2: #54585C; --ink-2: #54585C; --ink-3: #7B8085;
  --rule: rgba(26, 22, 20, .14); --rule-2: rgba(26, 22, 20, .28); --line: #7B8085;
  --btn-bg: #1A1614; --btn-fg: #EFF1F2; --focus: #1A1614;
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
  padding: clamp(22px, 2.6vw, 32px) clamp(22px, 2.8vw, 36px);
  background: var(--bg); color: var(--fg);
  border: 0;
}
.cta-inline > .link--action { font-weight: 600; white-space: nowrap; }
/* Set at the body floor, in --fg rather than --ink-2: this line is the
   sentence that earns the click, not a caption under it. */
.cta-inline > p {
  margin: 0;
  flex: 1 1 24ch;
  min-width: 0;
  max-width: 46ch;
  font-family: var(--text);
  font-size: var(--t-min);
  line-height: 1.5;
  color: var(--fg);
}
.cta-inline > .btn, .cta-inline > .link { flex: none; }
.ed > .cta-inline { grid-column: 4 / -1; }

/* Links inside running text. Same 1px-to-2px behavior, sized to the copy.

   EVERY ONE OF THESE IS :not(.btn), and that is load-bearing rather than
   tidy. These are descendant selectors - `.closing p a` scores (0,1,2) and
   beats `.btn` at (0,1,0) - and the engine writes .closing-links as a <p>,
   so before the exclusion the closing band's primary button came out
   underlined and in --fg instead of filled: the running-text rule quietly
   un-styled the single most important control on the page. A .btn inside a
   paragraph is still a button. */
.answer a:not(.btn), .prose a:not(.btn), .lead a:not(.btn),
.form-note a:not(.btn), .nearby a:not(.btn), .spec-row a:not(.btn),
.plate figcaption a:not(.btn), .contact-card a:not(.btn),
.closing p a:not(.btn), .split-text p a:not(.btn) {
  color: var(--fg);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .22em;
  transition: text-decoration-thickness var(--snap) var(--ease);
}
.answer a:not(.btn):hover, .prose a:not(.btn):hover, .lead a:not(.btn):hover,
.form-note a:not(.btn):hover, .nearby a:not(.btn):hover,
.spec-row a:not(.btn):hover, .plate figcaption a:not(.btn):hover,
.contact-card a:not(.btn):hover, .closing p a:not(.btn):hover,
.split-text p a:not(.btn):hover {
  text-decoration-thickness: 2px;
}
/* A .link--action placed inside one of those runs keeps the action color.
   The rule above hands every in-prose link --fg, which is right for prose
   and wrong for the one link that IS the action, and it outranks a bare
   .link--action - `.closing p a:not(.btn)` scores (0,2,2). So the override
   is restated at (0,3,1), which clears every host in the list above. Write
   it as `class="link link--action"`: .link supplies the underline, the
   modifier supplies the color. */
.link--action,
a.link.link--action:not(.btn) { color: var(--act-text); }

/* =======================================================================
   FOCUS
   =======================================================================
   THE RING IS NEVER THE ACTION COLOR. A magenta ring around a magenta fill
   is invisible: #1A1614 against #A8006C measures 2.47:1 and #F6F7F8 against
   #CE1B86 measures 4.75:1, and neither is a number you want to be relying
   on. So the ring stays --focus, which is the bundle's own extreme - ink on
   the sheet, sheet on the flood - and it is measured against the GROUND,
   not against the fill, because the offset guarantees a band of ground
   between the two:

     ring #1A1614 on the sheet #F6F7F8    16.75   floor 3.0   PASS
     ring #F6F7F8 on the flood #1A1614    16.75   floor 3.0   PASS

   THE OFFSET IS LOAD-BEARING, not a nicety. outline-offset paints the gap
   in whatever is behind the element, so at 3px (4px on .btn) the ring never
   touches the fill and the 2.47:1 adjacency above is never drawn. That is
   also why .btn keeps the larger offset and the thicker stroke: it is the
   one control that has a saturated fill of its own to clear.

   Computed, not assumed: on a .btn the cascade lands .btn:focus-visible
   (0,2,0) over a:focus-visible / button:focus-visible (0,1,1), so a primary
   button resolves to a 3px var(--focus) ring at 4px offset on BOTH grounds
   - 16.75:1 either way. No selector below ever sets outline-color to
   --act-bg, --action or any of its siblings. */
:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
a:focus-visible, button:focus-visible, summary:focus-visible,
label:focus-visible, [tabindex]:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}
.btn:focus-visible, .btn--ghost:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 4px;
}

.skip-link {
  position: absolute; left: 0; top: 0; z-index: 200;
  transform: translateY(-200%);
  background: var(--ink); color: var(--sheet);
  padding: 13px 20px;
  font-size: var(--t-nav);
  font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
}
.skip-link:focus { transform: none; }

/* =======================================================================
   MASTHEAD
   =======================================================================
   A thin bar on the sheet, overlaid on the first section. Once the page has
   scrolled it draws a 1px rule along its foot, so the masthead reads as the
   top edge of the sheet rather than as a floating bar. The rule is a
   pseudo-element, so it adds no height and nothing shifts.

   Everything site.js touches is unchanged: #header, .site-header.scrolled,
   #nav, .nav.nav-menu-open, #navLinks, .burger, .dropdown.open.
   ==================================================================== */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--bg);
  color: var(--fg);
  margin-bottom: calc(var(--head-h) * -1);
  transition: background var(--snap) var(--ease), color var(--snap) var(--ease);
}
.site-header::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: var(--hair); background: var(--rule-2);
  opacity: 0; transition: opacity var(--snap) var(--ease);
}
.site-header.scrolled::after { opacity: 1; }

.nav {
  display: flex; align-items: center; gap: 28px;
  height: var(--head-h);
  grid-column: 1 / -1;
}

/* THE OVERLAY STATE. build.py::header() adds .site-header--over on the
   paths the profile lists in OVERLAY_HEADER_PATHS - Omega's home page,
   which opens on the photograph. Until site.js adds .scrolled the masthead
   is transparent and takes the flood bundle, so the links go to the sheet
   color and the button to --action-ink over the picture; at 12px of scroll
   the sheet paints back in (the .site-header transition above animates it)
   and every token returns to the sheet bundle through the :where() rule.
   The open mobile menu outranks both - .site-header:has(.nav.nav-menu-open)
   scores (0,3,0) against this rule's (0,2,0) - and stays a sheet. */
.site-header--over:not(.scrolled) {
  --bg: var(--flood--bg); --fg: var(--flood--fg); --fg-2: var(--flood--fg-2);
  --ink-2: var(--flood--fg-2); --ink-3: var(--flood--fg-3);
  --rule: var(--flood--rule); --rule-2: var(--flood--rule-2);
  --line: var(--flood--line);
  --btn-bg: var(--flood--fg); --btn-fg: var(--flood--bg);
  --focus: var(--flood--fg);
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--flood--fg);
  background: transparent;
  color: var(--fg);
}

/* The lockup: black letterforms with a magenta stroke on a transparent
   ground - the one place any color survives, and it is inside the artwork.
   It sits on a swatch of the sheet so it survives an ink masthead; on the
   sheet the swatch is invisible. Never set a height - the intrinsic
   width/height attributes reserve the box. */
.brand {
  display: inline-flex; align-items: center; gap: 12px;
  min-width: 0; color: var(--fg);
}
.brand span:empty { display: none; }
.brand b { font-weight: 600; color: inherit; }
.brand-logo {
  display: block;
  width: 128px; height: auto; flex: none;
}
/* THE BACKING IS THE HEADER'S, AND ONLY THE HEADER'S. assets/logo.png is dark
   artwork on transparency, so it needs the sheet behind it - and in the header
   the sheet is what is behind it anyway, which is why nobody noticed the rule
   was unscoped. In the ink footer it painted a 150x64 light rectangle in the
   middle of the flood: the brightest object on the page, with the shop's mark
   sitting on it like a sticker. The footer now carries assets/logo-foot.png,
   the same artwork reversed out (make-brand.py::logo_foot), which needs no
   backing at all - see .brand-logo--foot below. */
.site-header .brand-logo { background: none; padding: 0; }
.brand-logo--foot { background: none; padding: 0; }
/* THE TWO LOCKUPS. header() emits the dark artwork AND the reversed file
   (MARK_HEAD_ALT); the sheet masthead shows the first, the transparent
   masthead over the photograph shows the second, and the open menu - a
   sheet again - shows the first. Only one is ever displayed, so the brand
   link's box never changes width between states. */
.site-header .brand-logo--foot { display: none; }
.site-header--over:not(.scrolled) .brand-logo:not(.brand-logo--foot) { display: none; }
.site-header--over:not(.scrolled) .brand-logo--foot { display: block; }
.site-header:has(.nav.nav-menu-open) .brand-logo--foot { display: none; }
.site-header:has(.nav.nav-menu-open) .brand-logo:not(.brand-logo--foot) { display: block; }

.nav-links { display: flex; align-items: center; gap: 2px; margin-left: auto; }
.nav-links > a, .drop-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 10px 9px;
  /* Sentence case, Manrope 500 at 15px, no tracking. The uppercase
     letterspaced label this row used to carry is the one every printer's
     site carries; a nav set like running text reads as a publication's
     masthead instead, and it is narrower - the row clears the 1141px
     collapse point with the phone link hidden and the fallback face mid-
     swap. */
  font-family: var(--text); font-size: 15px; font-weight: 500;
  letter-spacing: 0; text-transform: none; white-space: nowrap;
  color: var(--fg); background: none; border: 0; cursor: pointer;
  text-decoration: underline transparent;
  text-underline-offset: 8px; text-decoration-thickness: 1px;
  transition: text-decoration-color var(--snap) var(--ease),
              text-decoration-thickness var(--snap) var(--ease),
              color var(--snap) var(--ease);
}
.nav-links > a:hover, .drop-trigger:hover {
  text-decoration-color: currentColor; text-decoration-thickness: 1px;
}
.nav-links > a[aria-current], .drop-trigger[aria-current] {
  text-decoration-color: currentColor; text-decoration-thickness: 2px;
}
.drop-caret { font-size: .6em; }

/* A menu is a sheet laid on the masthead, ruled at its edge. It takes the
   sheet bundle so everything inside it resolves against the sheet. */
.has-dropdown { position: relative; }
.dropdown {
  --bg: var(--sheet--bg); --fg: var(--sheet--fg); --fg-2: var(--sheet--fg-2);
  --ink-2: var(--sheet--fg-2); --ink-3: var(--sheet--fg-3);
  --rule: var(--sheet--rule); --rule-2: var(--sheet--rule-2);
  --line: var(--sheet--line);
  --btn-bg: var(--sheet--fg); --btn-fg: var(--sheet--bg);
  --focus: var(--sheet--fg);
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
  position: absolute; top: 100%; left: 0; z-index: 60;
  min-width: 300px; padding: 10px;
  background: var(--bg); color: var(--fg);
  border: var(--hair) solid var(--line);
  opacity: 0; visibility: hidden;
  transition: opacity var(--snap) var(--ease), visibility var(--snap);
}
.has-dropdown:hover .dropdown,
.has-dropdown:focus-within .dropdown,
.dropdown.open { opacity: 1; visibility: visible; }
.dropdown a {
  display: block; padding: 10px 12px;
  transition: background var(--snap) var(--ease), color var(--snap) var(--ease);
}
.dropdown a:hover { background: var(--fg); color: var(--bg); }
.dropdown a:hover b, .dropdown a:hover span { color: inherit; }
.dropdown b {
  display: block; font-family: var(--text); font-size: var(--t-min);
  font-weight: 600; letter-spacing: -.005em; color: var(--fg);
}
.dropdown span { display: block; font-size: var(--t-cap); color: var(--ink-2); margin-top: 3px; }
.dropdown--mega { min-width: 300px; }
.drop-all { margin-bottom: 6px; padding-bottom: 12px; }
.drop-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 2px; }
.drop-col { min-width: 0; }
.drop-col:empty { display: none; }
.drop-county {
  margin: 10px 0 2px; padding: 0 12px;
  font-family: var(--text); font-size: var(--t-label); font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
}

/* THE ONE PERSISTENT ACTION IN THE CHROME. build.py::header() emits it as
   a bare `<a class="btn">` inside .nav-cta, so it takes the primary fill
   with no extra selector - all this rule does is trim the padding so a
   16/32 button fits a 74px masthead. The bundle on .site-header is the
   sheet, so it resolves to #A8006C: 6.79:1 edge, 7.29:1 label. */
.nav-cta { display: flex; align-items: center; gap: 22px; }
.nav-cta .btn { padding: 13px 22px; }
/* The phone, as text, beside the button: the second door on every page,
   in --fg because it is not the primary and the action color is the
   button's. Hidden below 1300px, where the row has no room for it - the
   number is still one tap away in the open menu's pinned button and in
   every closing band. */
.nav-tel {
  font-family: var(--text); font-size: 15px; font-weight: 600;
  letter-spacing: 0; white-space: nowrap; color: var(--act-text);
  font-variant-numeric: tabular-nums;
  text-decoration: underline transparent;
  text-underline-offset: 8px; text-decoration-thickness: 1px;
  transition: text-decoration-color var(--snap) var(--ease);
}
.nav-tel:hover { text-decoration-color: currentColor; }
@media (max-width: 1300px) { .nav-tel { display: none; } }
.burger {
  display: none; width: 44px; height: 40px; padding: 10px 9px;
  background: none; cursor: pointer;
}
.burger span { display: block; height: 1px; background: var(--fg); margin: 4px 0; }

/* =======================================================================
   INTERIOR PAGE OPENER  -  .page-hero
   =======================================================================
   Where a page opens with a title rather than with the .split hero. Same
   register: label, serif h1 at the interior step, one paragraph on the
   reading measure, and the page's ONE 2px rule closing it.
   ==================================================================== */
.page-hero, main > section.page-hero {
  display: grid; align-content: end;
  padding-top: calc(var(--head-h) + clamp(44px, 6vw, 88px));
  padding-bottom: 0;
}
.page-hero > .wrap > * { position: relative; z-index: 1; }
/* 24ch, not 18ch. The cap is here so an interior h1 cannot run the full
   twelve columns, but at 18ch it computed to 513px against a 62px title and
   broke /about's authored two lines into three - "Printing out / of Plymouth
   / since 1987." - splitting a phrase in a place nobody chose. 24ch is 685px
   at the same step, still inside the 734px reading column, and every interior
   h1 in the build now renders the number of lines its author wrote. */
.page-hero h1 { margin: 16px 0 0; max-width: 24ch; }
.page-hero > .wrap > p { margin-top: clamp(20px, 2vw, 30px); max-width: 58ch; }
.page-hero .eyebrow { margin-top: 0; }
/* The one 2px rule on an interior page. Drawn as a grid item inside .wrap
   so it lines up with the columns exactly the way .split's rule does, rather
   than running to the container edge. */
.page-hero > .wrap::after {
  content: ""; grid-column: 1 / -1;
  height: 2px; background: var(--fg);
  margin-top: clamp(28px, 3.4vw, 48px);
}
.page-hero + .section { padding-top: clamp(56px, 6.5vw, 104px); }

.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-bottom: 24px;
  font-size: var(--t-label); font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-2);
}
.crumbs a { text-decoration: underline transparent; text-underline-offset: 5px; text-decoration-thickness: 1px; }
.crumbs a:hover { text-decoration-color: currentColor; }
/* The separator is a glyph, not a rule: it has to clear the text floor,
   which --ink-2 does at 6.69:1 and --rule does not. */
.crumbs .sep { color: var(--ink-2); }

.hero-actions, .cta-actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 14px clamp(20px, 2.4vw, 32px);
  margin-top: clamp(26px, 3vw, 40px);
}

/* =======================================================================
   SECTION HEADS, PROSE, THE TWO-COLUMN LAYOUT
   ==================================================================== */
.svc-head {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter); row-gap: 22px; align-items: end;
}
.svc-head > * { grid-column: 4 / span 7; min-width: 0; }
.svc-head > *:first-child { grid-column: 4 / span 6; }
.svc-head > *:last-child  { grid-column: 10 / -1; }
.svc-head > *:only-child  { grid-column: 4 / span 7; }

.layout {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter); row-gap: 52px; align-items: start;
}
.layout > * { grid-column: 1 / -1; min-width: 0; }
.layout > *:first-child { grid-column: 3 / span 6; }
.layout > *:nth-child(2) { grid-column: 9 / -1; }

.prose { max-width: var(--measure); font-size: var(--t-body); line-height: 1.68; color: var(--ink-2); }
.prose h2 { margin: 56px 0 16px; color: var(--fg); }
.prose h2:first-child { margin-top: 0; }
.prose h3 {
  font-family: var(--text); font-size: var(--t-block); font-weight: 600;
  line-height: 1.24; margin: 36px 0 12px; color: var(--fg);
}
.prose p, .prose li { color: var(--ink-2); }
.prose b, .prose strong { color: var(--fg); font-weight: 600; }
.prose li { margin-bottom: 9px; }
.prose li::marker { color: var(--ink-3); }

/* A marginal instruction on a job bag: a hairline in the margin, not a box. */
.callout {
  margin: 36px 0; padding: 2px 0 2px 24px;
  border-left: var(--hair) solid var(--line); color: var(--fg);
}
.callout p { color: var(--ink-2); font-size: var(--t-min); max-width: 46ch; }
.callout p:last-child { margin-bottom: 0; }
.callout b, .callout strong { color: var(--fg); font-weight: 600; }

/* The aside the engine's service_page() emits. A panel, not a card. */
.aside-card {
  --bg: var(--sheet-2);
  --fg: #1A1614; --fg-2: #54585C;
  --ink-2: #54585C; --ink-3: #7B8085;
  --rule: rgba(26, 22, 20, .14); --rule-2: rgba(26, 22, 20, .28);
  --line: #7B8085; --focus: #1A1614;
  --act-bg: var(--action); --act-bg-hover: var(--action-hover);
  --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
  background: var(--bg); color: var(--fg);
  padding: clamp(24px, 2.6vw, 36px);
}
.aside-card .inner { display: block; }
.aside-card h2 {
  font-family: var(--display); font-weight: 400; font-size: var(--t-sub);
  line-height: 1.1; letter-spacing: -.005em; margin: 14px 0 12px; color: var(--fg);
}
.aside-card p { color: var(--ink-2); font-size: var(--t-min); margin-bottom: 22px; }
.aside-card .meta { margin-top: 24px; padding-top: 18px; border-top: var(--hair) solid var(--rule); }
.aside-card .meta a { display: flex; justify-content: space-between; gap: 16px; padding: 7px 0; font-size: var(--t-min); }
.aside-card .meta .k { color: var(--ink-2); font-size: var(--t-label); font-weight: 600; letter-spacing: .14em; text-transform: uppercase; }
.aside-card .meta .v { color: var(--fg); }

/* =======================================================================
   FAQ
   ==================================================================== */
.faq-grid {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter); row-gap: 40px; align-items: start;
}
.faq-grid > * { grid-column: 1 / -1; min-width: 0; }
.faq-grid > *:first-child { grid-column: 1 / span 3; }
.faq-grid > *:last-child  { grid-column: 5 / -1; }
.faq-list { display: flex; flex-direction: column; }
.faq-list details { color: var(--fg); }
.faq-list details:last-child { border-bottom: var(--hair) solid var(--rule); }
summary {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 22px 0; cursor: pointer; list-style: none;
  font-family: var(--text); font-size: var(--t-block); font-weight: 600;
  letter-spacing: -.005em; line-height: 1.28; color: var(--fg);
}
summary::-webkit-details-marker { display: none; }
summary:hover { text-decoration: underline; text-underline-offset: 6px; text-decoration-thickness: 1px; }
.faq-list summary:focus-visible { outline-offset: -3px; }
.faq-icon { position: relative; flex: none; width: 16px; height: 16px; }
.faq-icon::before, .faq-icon::after {
  content: ""; position: absolute; inset: 50% 0 auto; height: 1px;
  background: var(--fg); transform: translateY(-50%);
  transition: transform .22s var(--ease), opacity .22s;
}
.faq-icon::after { transform: translateY(-50%) rotate(90deg); }
details[open] .faq-icon::after { transform: translateY(-50%) rotate(0); opacity: 0; }
.answer { padding: 0 0 24px; color: var(--ink-2); font-size: var(--t-min); line-height: 1.66; max-width: 58ch; }
.answer p { margin: 0; }

/* =======================================================================
   THE ENGINE CTA AND THE CONTACT CARD
   =======================================================================
   cta_block() is the engine's closing section and it carries the flood
   bundle, exactly like .closing - so a page that uses it still has exactly
   one ink field, running into the footer.
   ==================================================================== */
.cta {
  display: grid; align-content: center;
  padding-block: clamp(72px, 9vw, 128px);
}
.cta-grid {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter); row-gap: 44px; align-items: start;
}
.cta-grid > * { grid-column: 1 / -1; min-width: 0; }
.cta-grid > *:first-child { grid-column: 1 / span 6; }
.cta-grid > *:last-child  { grid-column: 8 / -1; }
.cta h2 { max-width: 16ch; }
.cta p { color: var(--ink-2); font-size: var(--t-lead); margin-top: 22px; max-width: 50ch; }

.contact-card, .contact-card--panel { padding: 0; color: var(--fg); }
.contact-card .row {
  display: flex; justify-content: space-between; gap: 20px;
  padding: 15px 0; font-size: var(--t-min);
}
.contact-card .k {
  font-size: var(--t-label); font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-2); padding-top: 4px;
}
.contact-card .v { text-align: right; color: var(--fg); }
.contact-card a { text-align: right; }

/* =======================================================================
   FORMS
   =======================================================================
   Fields are ruled boxes on the ground itself with a letterspaced label
   above - a form you would fill in with a pen. Field text is 17px, above
   the 16px threshold below which iOS zooms the page on focus. Every field
   edge is --line (3.72:1 / 4.70:1), never --rule: a control boundary has to
   clear 3:1.
   ==================================================================== */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px 26px; }
.field { display: flex; flex-direction: column; gap: 9px; min-width: 0; }
.field--wide { grid-column: 1 / -1; }
.field > label, .field > span {
  font-family: var(--text); font-size: var(--t-label); font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
}

input, select, textarea {
  width: 100%; padding: 14px 15px;
  font-family: var(--text); font-size: var(--t-min); line-height: 1.45;
  color: var(--fg); background: transparent;
  transition: border-color var(--snap) var(--ease), background var(--snap) var(--ease);
}
textarea { min-height: 172px; resize: vertical; }
select {
  appearance: none;
  /* The caret in currentColor. Two hard-stop gradients at 50% draw two
     triangles - there is no blend, so this is not a gradient in the sense
     the brief forbids, and it is not a shadow. */
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-size: 6px 6px, 6px 6px;
  background-position: right 18px center, right 12px center;
  background-repeat: no-repeat;
  padding-right: 36px;
}
select option { color: var(--ink); background: var(--sheet); }
input::placeholder, textarea::placeholder { color: var(--ink-2); opacity: 1; }
input:hover, select:hover, textarea:hover { border-color: var(--fg); }
/* Focus doubles the field's tone rather than casting a shadow: the edge goes
   full-strength and the well takes the panel tint. No box-shadow anywhere. */
input:focus, select:focus, textarea:focus { border-color: var(--fg); background-color: var(--sheet-2); }
input:focus:not(:focus-visible),
select:focus:not(:focus-visible),
textarea:focus:not(:focus-visible) { outline: none; }
input[type="checkbox"], input[type="radio"] { width: auto; padding: 0; accent-color: var(--fg); }
input[type="file"] { padding: 12px 13px; font-size: var(--t-min); }
input[type="file"]::file-selector-button {
  margin-right: 14px; padding: 10px 17px; cursor: pointer;
  font-family: var(--text); font-size: var(--t-label); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--fg); background: transparent; border: var(--hair) solid var(--line);
}

/* Same reason as .panel p: the quote and upload notes are 208 and 300
   characters, so uncapped they really do set 100+ character lines. */
.form-note { margin: 20px 0 0; font-size: var(--t-min); color: var(--ink-2); max-width: var(--measure); }
.form-msg, .form-status {
  margin-top: 24px; padding: 18px 0 0;
  font-size: var(--t-min); color: var(--ink-2);
}
.form-msg[hidden], .form-status[hidden] { display: none; }
.form-msg.ok, .form-status.ok, .form-msg.err, .form-status.err { color: var(--fg); }
.form-msg.err, .form-status.err { font-weight: 600; }

/* ---------------------------------------------------------- send a file */
.drop-zone {
  display: grid; place-items: center; gap: 12px;
  padding: 60px 26px; text-align: center;
  background: transparent; border-style: dashed;
  color: var(--fg);
  transition: border-color var(--snap) var(--ease), background var(--snap) var(--ease);
}
.drop-zone svg { width: 34px; height: 34px; color: var(--ink-2); }
.drop-zone p { margin: 0; color: var(--ink-2); font-size: var(--t-min); }
.drop-zone b { color: var(--fg); font-weight: 600; }
.drop-zone .form-note { margin-top: 4px; padding-top: 0; }
.drop-zone label {
  font: inherit; font-size: var(--t-min); letter-spacing: normal; text-transform: none;
  color: var(--fg); font-weight: 600;
  text-decoration: underline; text-underline-offset: .22em; text-decoration-thickness: 1px;
  cursor: pointer;
}
.drop-zone.drag { border-style: solid; border-color: var(--fg); background: var(--sheet-2); }

.file-list { list-style: none; margin: 26px 0 0; padding: 0; display: flex; flex-direction: column; }
.file-list li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px 18px;
  align-items: center; padding: 15px 0;
}
.file-list .k { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--t-min); color: var(--fg); }
.file-list .v { font-size: var(--t-cap); letter-spacing: .04em; color: var(--ink-2); }

.bar { grid-column: 1 / -1; height: 2px; background: var(--rule-2); overflow: hidden; }
.bar > span, .bar > i, .bar > b { display: block; height: 100%; width: 0; background: var(--fg); transition: width .25s linear; }
.bar:empty::after { content: ""; display: block; height: 100%; width: var(--p, 0%); background: var(--fg); transition: width .25s linear; }

/* =======================================================================
   FOOTER
   =======================================================================
   The flood bundle, running straight out of the closing band so the two
   read as ONE field of ink - the page's only one.
   ==================================================================== */
.site-footer {
  padding: 0 0 40px; overflow: hidden;
  /* The closing band above it is the same ink; this hairline is where the
     page's last word ends and the directory begins. */
  border-top: var(--hair) solid var(--rule);
}
/* No .foot-cta above it (the profile sets FOOTER_CTA = None - every page
   already closes on a .closing band), so .foot-top opens the footer and takes
   the top padding the CTA block used to carry. */
.site-footer > .wrap > .foot-top:first-child { padding-top: clamp(56px, 6.5vw, 96px); }
.foot-cta {
  display: flex; justify-content: space-between; align-items: center;
  gap: 40px; flex-wrap: wrap;
  margin: 0 0 72px; padding: clamp(44px, 5.5vw, 80px) 0 0;
  border-top: 0;   /* the closing band above it already closed the field */
}
.foot-cta .btn { flex: none; }
.foot-cta .eyebrow { margin-bottom: 18px; }
.foot-cta-h { margin: 0; max-width: 18ch; }

/* Brand and contact in columns 1-4, four link columns in 6-12. The contact
   block is the footer's real content: the reversed lockup, one line about
   the shop, the phone at display size, the email and the full address -
   the one place on every page the address is written out. */
.foot-top {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gutter); row-gap: 48px;
}
.foot-brand { grid-column: 1 / span 4; }
.foot-cols  {
  grid-column: 6 / -1; display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 36px var(--gutter);
}
.foot-brand .brand { align-items: flex-start; }
.foot-brand .brand-logo { width: 156px; }
.foot-tagline { color: var(--ink-2); font-size: var(--t-min); line-height: 1.6; margin: 22px 0 0; max-width: 34ch; }
.foot-contact {
  display: grid; gap: 8px; justify-items: start;
  margin-top: 28px; padding-top: 26px;
  border-top: var(--hair) solid var(--rule);
}
/* The phone in the display face, at the floor the face is allowed (28px),
   400 - the same rule as every other serif line on the site. It is the
   largest thing in the footer because it is the thing people scroll down
   here for. */
.foot-tel {
  font-family: var(--display); font-weight: 400; font-style: normal;
  font-size: clamp(28px, 2.4vw, 34px); line-height: 1.1;
  letter-spacing: -.005em; color: var(--fg);
  text-decoration: underline transparent; text-underline-offset: .14em;
  text-decoration-thickness: 1px;
  transition: text-decoration-color var(--snap) var(--ease);
}
.foot-tel:hover { text-decoration-color: currentColor; }
.foot-mail {
  font-size: var(--t-min); color: var(--fg);
  text-decoration: underline transparent; text-underline-offset: .22em;
  text-decoration-thickness: 1px;
  transition: text-decoration-color var(--snap) var(--ease);
}
.foot-mail:hover { text-decoration-color: currentColor; }
.foot-addr { font-style: normal; font-size: var(--t-min); line-height: 1.6; color: var(--ink-2); margin-top: 4px; }

.foot-col h3 {
  font-family: var(--text); font-size: var(--t-label); font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--fg);
  margin-bottom: 14px; padding-bottom: 12px;
  border-bottom: var(--hair) solid var(--rule);
}
.foot-col a, .foot-col .v { display: block; padding: 5px 0; font-size: var(--t-min); line-height: 1.45; color: var(--ink-2); }
.foot-col a { text-decoration: underline transparent; text-underline-offset: .22em; text-decoration-thickness: 1px; transition: color var(--snap) var(--ease), text-decoration-color var(--snap) var(--ease); }
.foot-col a:hover { color: var(--fg); text-decoration-color: currentColor; }
/* The "all ..." link that closes a column is the column's action and takes
   the action color through .link--action (a.link.link--action:not(.btn)
   outranks .foot-col a). A little air above it separates it from the list. */
.foot-col a.link--action { margin-top: 8px; }

.foot-bottom {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center;
  gap: 14px 32px;
  margin-top: clamp(48px, 5vw, 72px); padding-top: 22px;
  font-size: var(--t-cap); color: var(--ink-2);
}
.foot-bottom a { color: var(--ink-2); }
.foot-bottom a:hover { color: var(--fg); text-decoration: underline; text-underline-offset: .22em; text-decoration-thickness: 1px; }
.foot-nav { display: flex; flex-wrap: wrap; gap: 6px 22px; }
.foot-nav a { color: var(--ink-2); text-decoration: underline transparent; text-underline-offset: .22em; text-decoration-thickness: 1px; }
.foot-nav a:hover { color: var(--fg); text-decoration-color: currentColor; }
.foot-motto { white-space: nowrap; }
/* The builder's credit: last in the bar, on its own line, in --ink-3 - the
   quietest text the flood allows (5.51:1), because it is not the client's. */
/* width, not flex-basis: below 560px the bar is a column flex container,
   where a 100% basis is measured against HEIGHT and pushed the credit into a
   second column off the right edge of the phone. */
.foot-credit { width: 100%; flex: none; margin-top: 4px; color: var(--ink-3); }
.foot-credit a { color: var(--ink-3); text-decoration: underline transparent; text-underline-offset: .22em; text-decoration-thickness: 1px; }
.foot-credit a:hover { color: var(--fg); text-decoration-color: currentColor; }
.foot-giant { display: none; }

/* =======================================================================
   CHAT WIDGET - CHAT is False in the profile; kept on the two colors so
   turning it on is one flag, not a restyle.
   ==================================================================== */
.sk-chat { position: fixed; right: 24px; bottom: 24px; z-index: 120; }
.sk-fab {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 14px 22px; border: var(--hair) solid var(--ink);
  background: var(--ink); color: var(--sheet); cursor: pointer;
  font-family: var(--text); font-size: var(--t-nav); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
}
.sk-fab:hover { background: var(--sheet); color: var(--ink); }
.sk-fab .mk { width: 18px; height: 21px; display: block; }
.fab-dot { width: 6px; height: 6px; background: currentColor; }
.sk-panel {
  position: absolute; right: 0; bottom: 0; width: min(380px, calc(100vw - 48px));
  display: flex; flex-direction: column; max-height: min(560px, calc(100vh - 48px));
  background: var(--sheet); color: var(--ink); border: var(--hair) solid var(--ink);
  overflow: hidden;
}
.sk-panel[hidden] { display: none; }
.sk-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 16px; border-bottom: var(--hair) solid var(--ink); }
.sk-ident { display: flex; align-items: center; gap: 12px; }
.sk-avatar { width: 32px; height: 32px; display: grid; place-items: center; }
.sk-avatar .mk { width: 16px; height: 19px; }
.sk-headtext { display: flex; flex-direction: column; }
.sk-title { font-weight: 600; font-size: var(--t-min); }
.sk-status { display: flex; align-items: center; gap: 7px; font-size: var(--t-label); }
.sk-status .live { width: 6px; height: 6px; background: currentColor; }
.sk-x { background: none; border: 0; color: inherit; cursor: pointer; padding: 4px; }
.sk-x svg { width: 18px; height: 18px; }
.sk-log { flex: 1; overflow-y: auto; padding: 18px; display: flex; flex-direction: column; gap: 12px; font-size: var(--t-min); }
.sk-input { display: flex; gap: 10px; padding: 14px; border-top: var(--hair) solid var(--ink); }
.sk-input textarea { flex: 1; min-height: 0; resize: none; padding: 12px; border-color: var(--ink); color: var(--ink); }
.sk-send { flex: none; width: 44px; background: var(--ink); color: var(--sheet); border: 0; cursor: pointer; }
.sk-send svg { width: 18px; height: 18px; }
.sk-foot { margin: 0; padding: 0 16px 14px; font-size: var(--t-label); }
.hp { position: absolute !important; left: -9999px !important; }

/* =======================================================================
   MOTION
   =======================================================================
   .reveal is the only scroll-driven behavior on the site: fade up, 12px of
   travel, 600ms. site.js adds .in. The other three - the link underline,
   the contents leader and the button invert - are declared on their own
   components above. There is nothing else.
   ==================================================================== */
.reveal { opacity: 1; transform: none; }
html.js .reveal {
  opacity: 0; transform: translateY(12px);
  transition: opacity var(--rise) var(--ease), transform var(--rise) var(--ease);
}
html.js .reveal.in { opacity: 1; transform: none; }

/* =======================================================================
   LEGACY - the compositions this pass retires
   =======================================================================
   The Broadside components (.bs-hero, .marquee, .index, .sticky, .tiles,
   .numeral) and the older card and icon grids (.svc, .feature, .prod,
   .city-card, .step) are gone from the design. Nothing should emit them
   once the copy modules are rebuilt, but a stray one must degrade to a
   ruled column of type rather than to an unstyled block.

   Everything here is :where() - ZERO specificity - so any rule above wins
   over it without a fight, and so a copy module that happens to reuse one
   of these class names gets sane defaults rather than v6's poster scale.
   ==================================================================== */

/* The Broadside furniture that must not come back at all. */
:where(.marquee, .marquee-track) { display: block; overflow: hidden; white-space: normal; animation: none; }
:where(.marquee-track) { display: flex; flex-wrap: wrap; gap: 0 18px; }
:where(.marquee-track span) { font-family: var(--text); font-size: var(--t-min); text-transform: none; color: var(--ink-2); }
:where(.marquee-track span)::after { content: none; }
:where(.numeral, .sticky-label .num) {
  display: block; font-family: var(--display); font-weight: 400;
  font-size: var(--t-outline); line-height: 1; color: var(--ink-3);
  -webkit-text-fill-color: transparent; -webkit-text-stroke: 1px currentColor;
  user-select: none; pointer-events: none; opacity: 1;
}
:where(.stroke) { display: none; }

/* The old openers degrade to the interior opener's shape. */
:where(.bs-hero, .hero) {
  display: grid; align-content: end;
  padding-top: calc(var(--head-h) + clamp(44px, 6vw, 88px));
  padding-bottom: clamp(32px, 4vw, 56px);
}
:where(.bs-hero h1) { font-size: var(--t-hero); font-family: var(--display); font-weight: 400; line-height: 1.08; letter-spacing: -.005em; margin: 0; }
:where(.bs-hero .kicker, .hero .eyebrow) { margin-bottom: 18px; }
:where(.bs-hero .meta, .hero-meta) {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-top: clamp(36px, 4vw, 64px); border-top: var(--hair) solid var(--rule-2);
}
:where(.bs-hero .meta .item) { padding: 20px 20px 4px 0; }
:where(.bs-hero .meta .item + .item) { padding-left: 20px; border-left: var(--hair) solid var(--rule); }
:where(.bs-hero .meta .k) { display: block; font-size: var(--t-label); font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2); }
:where(.bs-hero .meta .v) { display: block; margin-top: 10px; font-family: var(--display); font-weight: 400; font-size: var(--t-sub); line-height: 1.08; color: var(--fg); }
:where(.bs-hero .meta .d) { display: block; margin-top: 8px; font-size: var(--t-min); color: var(--ink-2); }

/* The index and the sticky column become plain ruled rows and a plain
   two-column block - no inverting, no position:sticky. */
:where(.index) { display: block; border-top: var(--hair) solid var(--rule-2); }
:where(.index-row) {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline; column-gap: 20px; padding: 24px 0;
  border-bottom: var(--hair) solid var(--rule); color: var(--fg);
}
:where(.index-row .num) { font-family: var(--display); font-weight: 400; font-size: clamp(28px, 2.4vw, 34px); line-height: 1; color: var(--ink-3); -webkit-text-fill-color: transparent; -webkit-text-stroke: 1px currentColor; }
:where(.index-row .name) { font-family: var(--display); font-weight: 400; font-size: var(--t-sub); line-height: 1.08; letter-spacing: -.005em; color: var(--fg); }
:where(.index-row .desc) { font-size: var(--t-min); color: var(--ink-2); text-align: right; }
:where(.index-row .plate) { display: none; }
:where(.index-row .more) { display: none; }
:where(.sticky) { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 7fr); column-gap: clamp(28px, 4vw, 72px); row-gap: 40px; align-items: start; }
:where(.sticky-label) { position: static; display: flex; flex-direction: column; gap: 10px; min-width: 0; }
:where(.sticky-label p) { margin: 0; font-size: var(--t-min); color: var(--ink-2); max-width: 30ch; }
:where(.sticky-body) { min-width: 0; }
:where(.sticky-body > * + *) { margin-top: clamp(32px, 4vw, 60px); }

/* The tiles become a hairline listing. */
:where(.tiles) { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0; border-top: var(--hair) solid var(--rule-2); }
:where(.tile) { display: block; padding: 18px 20px 18px 0; border-bottom: var(--hair) solid var(--rule); color: var(--fg); background: transparent; }
:where(.tile b) { display: block; font-family: var(--text); font-size: var(--t-min); font-weight: 600; color: inherit; }
:where(.tile span) { display: block; margin-top: 6px; font-size: var(--t-cap); color: var(--ink-2); }

/* The v6 spec ticket, restated at the new weight - 1px rules, tabular
   figures, no floats and no poster heads. */
:where(.spec) { color: var(--fg); }
:where(.spec-group) { display: block; padding: 22px 0 32px; }
:where(.spec-group h2, .spec-group h3) {
  font-family: var(--display); font-weight: 400; font-size: var(--t-sub);
  line-height: 1.1; letter-spacing: -.005em; margin: 0 0 16px; color: var(--fg);
}
:where(.spec-group > p) { color: var(--ink-2); font-size: var(--t-min); max-width: 46ch; }
:where(.spec-row) {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  column-gap: var(--gutter); align-items: baseline; padding: 13px 0;
  font-size: var(--t-min); line-height: 1.5;
}
:where(.spec-row .k) { font-weight: 600; color: var(--ink-2); min-width: 0; }
:where(.spec-row .v) { color: var(--fg); min-width: 0; overflow-wrap: break-word; }
:where(.spec-row > :only-child) { grid-column: 1 / -1; }
:where(.spec + .spec) { margin-top: 40px; }

/* The card and icon grids. Icons are retired outright - an icon grid is one
   of the things this pass exists to remove. */
:where(.feature-grid, .prod-grid, .city-grid, .steps) { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 var(--gutter); }
:where(.svc, .feature, .prod, .city-card, .step) { position: relative; display: block; padding: 26px 0 38px; color: var(--fg); }
:where(.svc h3, .feature h3, .prod h3, .city-card h3, .step h3) { font-family: var(--text); font-size: var(--t-block); font-weight: 600; line-height: 1.24; margin: 0 0 12px; }
:where(.svc p, .feature p, .prod p, .city-card p, .step p) { color: var(--ink-2); font-size: var(--t-min); line-height: 1.6; margin: 0 0 18px; }
:where(.svc:hover h3, .prod:hover h3, .city-card:hover h3) { text-decoration: underline; text-underline-offset: .22em; text-decoration-thickness: 1px; }
:where(.svc .num, .prod .num, .step .n) { display: block; margin-bottom: 16px; font-size: var(--t-label); font-weight: 600; letter-spacing: .14em; color: var(--ink-2); }
:where(.svc-ic, .fic) { display: none; }
:where(.svc ul) { display: flex; flex-wrap: wrap; list-style: none; padding: 0; margin: 0 0 18px; }
:where(.svc li) { font-size: var(--t-label); font-weight: 600; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-2); }
:where(.svc li + li)::before { content: "/"; margin: 0 .6em; color: var(--ink-3); }

/* =======================================================================
   RESPONSIVE - 320 / 375 / 768 / 900 / 1024 / 1440
   =======================================================================
   Four stops. 1140: the nav collapses and every placed column folds (the
   collapse point is unchanged from v6 and is measured against the nav row).
   900: .ed loses its label column, .split stacks with the plate second, and
   the two-column layouts fold. 700: the contents row drops its description
   under the name and hides the leader. 560: phone.

   Type never shrinks below its floors: every reading size is 17px or above
   at every width, and the display clamps bottom out at 40 / 36 / 30 / 25.
   ==================================================================== */

@media (max-width: 1140px) {
  .burger { display: block; }
  .nav-links {
    --bg: var(--sheet--bg); --fg: var(--sheet--fg); --fg-2: var(--sheet--fg-2);
    --ink-2: var(--sheet--fg-2); --ink-3: var(--sheet--fg-3);
    --rule: var(--sheet--rule); --rule-2: var(--sheet--rule-2);
    --line: var(--sheet--line);
    --btn-bg: var(--sheet--fg); --btn-fg: var(--sheet--bg);
    --focus: var(--sheet--fg);
    --act-bg: var(--action); --act-bg-hover: var(--action-hover);
    --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
    position: fixed; top: var(--head-h); left: 0; right: 0;
    height: calc(100vh - var(--head-h));
    height: calc(100dvh - var(--head-h));
    flex-direction: column; align-items: stretch;
    gap: 0; padding: 18px var(--inset) 26px;
    background: var(--bg); color: var(--fg);
    border-top: var(--hair) solid var(--rule-2);
    display: none; overflow-y: auto; overscroll-behavior: contain;
  }
  /* The contract with site.js - the class it toggles opens the menu. */
  .nav.nav-menu-open .nav-links { display: flex; }
  .site-header:has(.nav.nav-menu-open) {
    --bg: var(--sheet--bg); --fg: var(--sheet--fg); --fg-2: var(--sheet--fg-2);
    --ink-2: var(--sheet--fg-2); --ink-3: var(--sheet--fg-3);
    --rule: var(--sheet--rule); --rule-2: var(--sheet--rule-2);
    --line: var(--sheet--line);
    --btn-bg: var(--sheet--fg); --btn-fg: var(--sheet--bg);
    --focus: var(--sheet--fg);
    --act-bg: var(--action); --act-bg-hover: var(--action-hover);
    --act-fg: #FFFFFF; --act-fg-hover: #FFFFFF; --act-text: var(--action);
    background: var(--bg); color: var(--fg);
  }
  .nav-links > a, .drop-trigger {
    padding: 15px 0; font-size: var(--t-min); letter-spacing: .08em;
    border-bottom: var(--hair) solid var(--rule);
    text-decoration: none;
  }
  .nav-links > a:hover, .drop-trigger:hover,
  .nav-links > a[aria-current], .drop-trigger[aria-current] {
    text-decoration: underline; text-underline-offset: 6px; text-decoration-thickness: 1px;
  }
  .has-dropdown { position: static; }
  .dropdown {
    position: static; opacity: 1; visibility: visible;
    background: none; border: 0; padding: 0 0 10px 16px; display: none;
  }
  /* Both submenus are closed accordions in the open menu, and only .open -
     which site.js toggles on tap, for the link trigger too - reveals one.
     Service Areas used to be forced open here because its trigger is a
     link, which put fifteen towns between "Capabilities" and "Products" on
     every phone. The caret turns to say which state it is in. */
  .dropdown.open { display: block; }
  .drop-trigger .drop-caret { transition: transform var(--snap) var(--ease); }
  .drop-trigger[aria-expanded="true"] .drop-caret { transform: rotate(180deg); }
  .dropdown--mega { min-width: 0; }
  .dropdown a { padding: 9px 0; }
  .dropdown a:hover { background: none; color: var(--fg); }
  .dropdown a:hover b { text-decoration: underline; text-underline-offset: .22em; text-decoration-thickness: 1px; }
  .drop-cols { grid-template-columns: 1fr; }
  /* The quote button is a sibling of .nav-links in the engine markup, so it
     cannot move into the menu. It USED to be hidden in the bar and only
     pinned across the foot of the open menu - which meant that below 1140px
     the header was a logo and a hamburger and nothing else, and section
     16.2's "one persistent action in the chrome" held on desktop only. On a
     phone that left runs of seven and eight screens with no control on
     screen at all. It stays in the bar now, sized down so the row still fits
     at 320px, and the menu-open rule below pins the same control across the
     foot when the menu is open. */
  .nav-cta .btn { padding: 10px 14px; font-size: 13px; }
  /* The `margin-left: auto` that pushes the right-hand end of the masthead
     across lives on .nav-links, and .nav-links is display:none here - so it
     contributes no layout and the auto margin does nothing. That was
     invisible while .nav-cta held only a hamburger a few pixels wide; with a
     filled button in it, the pair floated in the middle of the row at tablet
     widths. The auto margin moves to the group that is actually in the flow. */
  .nav-cta { margin-left: auto; }
  .nav.nav-menu-open .nav-links { padding-bottom: 104px; }
  body.menu-open { overflow: hidden; }
  /* Pinned across the foot of the open menu. It stays PRIMARY here - this
     is the same control, and the menu is the one place a visitor is most
     obviously looking for it. The menu carries the sheet bundle (set on
     .nav-links and on .site-header:has(.nav.nav-menu-open) above), so
     --act-bg resolves to #A8006C over the sheet: 6.79:1 edge, 7.29:1
     label. It was ink-on-sheet before this pass, which is exactly the
     "nothing says click this" defect, in the one spot that persists. */
  .nav.nav-menu-open .nav-cta .btn {
    display: inline-flex; position: fixed;
    left: var(--inset); right: var(--inset); bottom: 24px; z-index: 101;
    padding: 18px 24px;
    background: var(--act-bg); color: var(--act-fg); border-color: var(--act-bg);
  }

  /* Every placed column folds. The child selectors repeat their
     :first-child / :nth-child forms because a media query adds no
     specificity of its own. */
  .layout > *:first-child,
  .layout > *:nth-child(2),
  .svc-head > *, .svc-head > *:first-child, .svc-head > *:last-child,
  .svc-head > *:only-child,
  .faq-grid > *:first-child, .faq-grid > *:last-child,
  .cta-grid > *:first-child, .cta-grid > *:last-child,
  .foot-brand, .foot-cols { grid-column: 1 / -1; }
  .svc-head, .faq-grid, .cta-grid { row-gap: 28px; }
  .foot-cols { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  :where(.feature-grid, .prod-grid, .city-grid) { grid-template-columns: repeat(2, 1fr); }
  .plates > .plate { grid-column: span 6; }
  .plates > .plate--wide { grid-column: span 6; }
  .plates > .plate--tall { grid-column: span 6; }
}

@media (max-width: 900px) {
  /* .ed collapses to one column and the label column becomes a stacked
     header: the .lede sits directly above the block it names, and the
     .outline sits above that. Nothing overlaps the reading column at any
     width because the label column stops existing rather than narrowing. */
  .ed { grid-template-columns: minmax(0, 1fr); row-gap: clamp(20px, 3vw, 32px); }
  .ed > *, .ed > .lede, .ed > .outline, .ed > .ed-label,
  .ed > .contents, .ed > .factrule, .ed > .listing, .ed > .plates,
  .ed > .spec, .ed > .panel, .ed > .split, .ed > .quote,
  /* .cta-inline is placed 4 / -1 at full width like every block above it,
     and it has to be named here for the same reason they are: a media query
     adds no specificity, so `.ed > .cta-inline` (0,2,0) outranks `.ed > *`
     (0,1,0) and would keep its explicit start line in a grid that only has
     one column. Line 4 does not exist there, so the browser swapped the
     start and end lines, created two implicit tracks, and collapsed the
     single explicit `1fr` to 0px - taking every sibling placed `1 / -1`
     down with it. The reading column measured 0px wide on 15 pages below
     900px and the home page ran 47,929px tall. */
  .ed > .cta-inline { grid-column: 1 / -1; }
  .ed > .lede { padding-top: 0; }
  /* order:-1 puts the numeral above the label, which is what the note above
     says the collapsed layout does - but the numeral is usually authored
     AFTER the paragraph (it belongs to the label column, and source order
     there is irrelevant at full width). Collapsed, that dropped it directly
     on top of the block it introduces: on the home page an outlined 01 landed
     62px above the contents list's own outlined 01, same face, same hairline
     stroke, and read as a rendering fault rather than a device. Reordered it
     is a section number again, with the heading and the lead between it and
     the list. */
  .ed > .outline { font-size: clamp(56px, 12vw, 88px); order: -1; }

  /* The hero stacks, plate second. */
  .split { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .split-text { order: 1; }
  .split-plate { order: 2; }

  .layout { row-gap: 40px; }
  .factrule { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .factrule .f:nth-child(odd) { padding-left: 0; border-left: 0; }
  .factrule .f:nth-child(n + 3) { border-top: var(--hair) solid var(--rule); }
  .form-grid { grid-template-columns: 1fr; }
  .plates > .plate, .plates > .plate--wide, .plates > .plate--tall { grid-column: span 6; }
  .prose h2 { margin-top: 44px; }
  .brand-logo { width: 106px; }
  .foot-brand .brand-logo { width: 136px; }
  .foot-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
  /* The contents row: the description drops under the name and the leader
     goes away rather than being squeezed into a stub. The numeral spans
     both rows so the row still reads as one entry in a list. */
  .contents-row {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    row-gap: 6px;
    column-gap: 16px;
    min-height: 0;
    padding: 20px 0;
  }
  .c-leader { display: none; }
  .c-num { grid-row: 1 / span 2; font-size: 28px; }
  .c-name { grid-column: 2; }
  .c-desc { grid-column: 2; text-align: left; }
  .contents-row:hover .c-name, .contents-row:focus-within .c-name { transform: none; }

  .listing-row { grid-template-columns: minmax(0, 1fr); row-gap: 3px; padding: 13px 0; }
  /* .k KEEPS ITS DESKTOP TREATMENT. Stacked, it used to drop to 12px tracked
     uppercase - but .k is not chrome here, it is the row's subject, and 88 of
     the 193 in the build are content phrases ("Metal plates that get reused",
     "Two perfecting presses", the product names that carry the page anchors).
     Setting them at 12px gray caps while their glosses stayed 17px black
     inverted the row on the one width where reading is hardest, and put them
     under BRIEF 12's 17px floor. Weight 600 in --ink-2 against .v's 400 in
     --fg is enough to keep the two apart. */
  .listing-row .k { font-size: var(--t-min); letter-spacing: .01em; text-transform: none; }
  :where(.spec-row) { grid-template-columns: 1fr; row-gap: 3px; padding: 12px 0; }
  :where(.index-row) { grid-template-columns: auto minmax(0, 1fr); row-gap: 6px; }
  :where(.index-row .desc) { grid-column: 2; text-align: left; }
  :where(.feature-grid, .prod-grid, .city-grid) { grid-template-columns: 1fr; }
  .foot-bottom { flex-direction: column; align-items: flex-start; }
  .foot-motto { white-space: normal; }
  .plates { grid-template-columns: minmax(0, 1fr); }
  .plates > .plate, .plates > .plate--wide, .plates > .plate--tall { grid-column: 1 / -1; }
  .plates > .plate:nth-child(3n + 2) { margin-top: 0; }
}

@media (max-width: 560px) {
  .factrule { grid-template-columns: minmax(0, 1fr); }
  .factrule .f, .factrule .f + .f {
    padding: 16px 0 14px; border-left: 0; border-top: var(--hair) solid var(--rule);
  }
  .factrule .f:first-child { border-top: 0; }
  .foot-cta { flex-direction: column; align-items: flex-start; gap: 24px; }
  .contact-card .row { flex-direction: column; gap: 4px; }
  .contact-card .v, .contact-card a { text-align: left; }
  .aside-card .meta a { flex-direction: column; gap: 3px; }
  .file-list li { grid-template-columns: 1fr; }
  /* Two 44px tap targets will not sit side by side at this width, and a
     letterspaced "Call (763) 475-1826" is wider than a 320px sheet, so the
     label is allowed to wrap. */
  .hero-actions .btn, .cta-actions .btn, .aside-card .btn,
  .foot-cta .btn, .field--wide .btn, .closing-links .btn,
  .split-links .btn, .cta-inline .btn { width: 100%; }
  /* The tracking stays at the button's own .01em. The old .07em here was
     the uppercase label's fit; on a sentence-case 15px button it just
     spreads the words apart at the width where there is least room. */
  .btn { white-space: normal; text-align: center; letter-spacing: .01em; }
  /* Except the masthead's, which sits beside the logo and the burger and
     has no second line to go to. */
  .nav-cta .btn { white-space: nowrap; }
  .split-links, .hero-actions, .cta-actions, .closing-links { gap: 12px 20px; }
  .cta-inline { gap: 14px 20px; }
  .cta-inline > p { flex-basis: 100%; }
  .quote { margin-block: clamp(40px, 12vw, 64px); }
  :where(.svc, .feature, .prod, .city-card, .step) { padding-bottom: 34px; }
}

@media (max-width: 380px) {
  :root { --inset: 20px; }
  .brand-logo { width: 96px; }
  .site-header .brand-logo { padding: 6px 9px; }
  /* The narrowest sheet, measured rather than left to the clamps. The
     display floors are 40 / 36 / 30 / 25 and every one of them clears the
     28px display minimum with room; body stays at 17-18px. */
  .h-display, .split-text h1 { font-size: 40px; }
  .page-hero h1 { font-size: 34px; }
  .h-section, .closing h2, .cta h2 { font-size: 30px; }
  .c-name, .prose h2, .factrule .f .v { font-size: 28px; }
  .quote { font-size: 28px; max-width: none; }
  .outline, .ed > .outline { font-size: 56px; }
  .c-num { font-size: 28px; }
}

/* =======================================================================
   REDUCED MOTION
   =======================================================================
   All four behaviors are no-ops here: the reveal shows everything at once,
   the contents leader and name change state instantly, and links and
   buttons swap without a transition. .reveal's !important is load-bearing -
   html.js .reveal is the more specific selector.
   ==================================================================== */
@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal.in, html.js .reveal, html.js .reveal.in {
    opacity: 1 !important; transform: none !important; transition: none !important;
  }
  .contents-row:hover .c-name, .contents-row:focus-within .c-name,
  .contents-row:focus-visible .c-name { transform: none !important; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
}
