/* ============================================================================
   BASE — identical on every Pittsburgh IT build. The visitor never sees any of it.
   Token NAMES are fixed; only the VALUES change per client. Everything below the
   token block is structural: the accessibility floor, the form skeleton, tap
   targets, the motion contract. Roughly 20 lines get rewritten per client and
   several hundred lines of quality are inherited.
   MOBILE FIRST — every rule here is the small-screen rule. Breakpoints add.
   ========================================================================== */
:root{
  --surface:#FBF5EA;
  --surface-2:#F1E5D0;
  --band:#3E1236;
  --band-ink:#F7EDE2;
  --band-ink-2:#DBBFD2;
  --accent-on-band:#EFC663;
  --ink:#241021;
  --ink-2:#57394F;
  --ink-3:#67495F;
  --accent:#A63A1B;
  --accent-2:#87290F;
  --accent-ink:#FFF6EE;
  --line:#E2D0B5;
  --maxw:1240px;
  --gutter:22px;
  --radius:8px;
  --accent-a12:rgba(166,58,27,.12);
  /* THE BOARD's surfaces. All four are DERIVED from --surface by an HSL lightness
     step in build.py, so the services section separates from the page by TONE and
     can never be a white card on a grey page. --board-accent is --accent unless it
     cannot hold 4.5:1 on --board, in which case build.py has already swapped it. */
  --board:#f8ecd8;
  --board-cell:#faf3e5;
  --board-line:#f2deb9;
  --board-accent:#A63A1B;
  --board-shadow-1:rgba(36,16,33,.06);
  --board-shadow-2:rgba(36,16,33,.22);
  --font-display:'Newsreader',Georgia,'Iowan Old Style','Times New Roman',serif;
  --font-body:'Public Sans',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{margin:0;background:var(--surface);color:var(--ink);
  font-family:var(--font-body);font-size:17px;line-height:1.62;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  overflow-x:hidden}
img,svg,video{max-width:100%;height:auto;display:block}
h1,h2,h3,h4{font-family:var(--font-display);margin:0;line-height:1.08;
  font-weight:600;letter-spacing:-.014em}
p{margin:0 0 1em}
a{color:inherit}
ul{margin:0;padding:0;list-style:none}

/* --- container ------------------------------------------------------------ */
.container{width:100%;max-width:var(--maxw);margin:0 auto;padding:0 var(--gutter)}
@media(min-width:760px){:root{--gutter:34px}}
@media(min-width:1180px){:root{--gutter:48px}}

/* --- accessibility floor -------------------------------------------------- */
.skip{position:absolute;left:-9999px;top:0;z-index:200;background:var(--accent);
  color:var(--accent-ink);padding:12px 18px;border-radius:0 0 var(--radius) 0;
  font-weight:700;text-decoration:none}
.skip:focus{left:0}
:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:2px}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  clip-path:inset(50%);white-space:nowrap}
/* honeypot: OFF-SCREEN, never display:none — a hidden field some bots skip,
   and a screen reader is told to leave it alone. */
.hp{position:absolute!important;left:-9999px;top:-9999px;width:1px;height:1px;
  opacity:0;pointer-events:none}

/* --- tap targets ---------------------------------------------------------- */
a,button,input,select,textarea,summary{font-family:inherit}
.btn,button,input[type=submit]{min-height:48px}
nav a,footer a{display:inline-block;min-height:24px}

/* --- button skeleton (templates restyle the skin, not the mechanics) ------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.55em;
  padding:14px 26px;border:1px solid transparent;border-radius:var(--radius);
  font-weight:650;font-size:16px;line-height:1.1;text-decoration:none;cursor:pointer;
  transition:transform .18s ease,background .18s ease,color .18s ease,border-color .18s ease,box-shadow .18s ease}
.btn:active{transform:translateY(1px)}
.btn-primary{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
.btn-ghost{background:transparent;color:var(--ink);border-color:var(--line)}

/* --- form skeleton -------------------------------------------------------- */
.form{display:grid;gap:16px}
.field{display:grid;gap:7px}
.field label{font-size:12.5px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-2)}
.field input,.field select,.field textarea{
  width:100%;min-height:50px;padding:13px 15px;border:1px solid var(--line);
  border-radius:var(--radius);background:var(--surface);color:var(--ink);
  font-size:16px;/* 16px stops iOS zooming the page on focus */
  font-family:inherit;transition:border-color .18s ease,box-shadow .18s ease}
.field textarea{min-height:140px;resize:vertical;line-height:1.55}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(166,58,27,.26)}
.field ::placeholder{color:var(--ink-3);opacity:1}
.frow{display:grid;gap:16px}
@media(min-width:680px){.frow{grid-template-columns:1fr 1fr}}
.thanks{display:none;border:1px solid var(--line);border-radius:var(--radius);
  padding:28px;background:var(--surface-2)}
.thanks.show{display:block}
.thanks h3{font-size:24px;margin-bottom:10px}
.formerror{color:#87290F;font-size:15px;font-weight:600;margin-top:10px}

/* --- MOTION CONTRACT ------------------------------------------------------
   Nothing starts hidden unless JavaScript has already run. The `js` class is set
   by an inline <head> script, and ONLY when prefers-reduced-motion is not set.
   No JS, blocked JS, old browser, or reduced motion => the page is simply visible.
   This is the inverse of how most sites do it, and it is why a script failure
   here cannot produce a blank page on a lead-generating site. */
html.js .reveal{opacity:0;transform:translateY(18px);
  transition:opacity .7s cubic-bezier(.22,.61,.36,1),transform .7s cubic-bezier(.22,.61,.36,1)}
html.js .reveal.in{opacity:1;transform:none}
html.js .reveal.d1{transition-delay:.08s}
html.js .reveal.d2{transition-delay:.16s}
html.js .reveal.d3{transition-delay:.24s}
@media(prefers-reduced-motion:reduce){
  html.js .reveal,html.js .reveal.in{opacity:1!important;transform:none!important;transition:none!important}
  *{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important}
}

/* --- nav skeleton --------------------------------------------------------- */
#nav{position:fixed;top:0;left:0;right:0;z-index:100;
  transition:background .25s ease,box-shadow .25s ease,border-color .25s ease}
.nav-inner{display:flex;align-items:center;justify-content:space-between;
  gap:16px;min-height:68px}
.navtoggle{display:inline-flex;align-items:center;justify-content:center;
  width:48px;height:48px;margin-right:-10px;background:none;border:0;
  color:inherit;cursor:pointer}
@media(min-width:940px){.navtoggle{display:none}}
.nav-links{display:none}
.nav-links.open{display:flex}
.nav-links{gap:0}
/* Desktop nav reset. Deliberately scoped to #nav so its specificity (1,1,0) beats any
   later `.nav-links{position:absolute…}` a template writes for its mobile drawer —
   equal specificity later in the file wins over a media query, which silently left the
   drawer stacked at the left edge on six of seven templates before this was scoped.
   It sets ONLY the structural properties that must not leak from mobile to desktop;
   gap, colour and type stay the template's business. */
@media(min-width:940px){
  #nav .nav-links,#nav .nav-links.open{display:flex;position:static;inset:auto;
    flex-direction:row;align-items:center;padding:0;margin:0;border:0;background:none;
    width:auto;height:auto;min-height:0;max-height:none;box-shadow:none;transform:none;
    visibility:visible;opacity:1;overflow:visible;
    gap:var(--nav-gap,32px)}
}
html.menu-open,html.menu-open body{overflow:hidden}

/* --- footer skeleton ------------------------------------------------------ */
footer a{text-decoration:none}
.credit{font-size:13.5px}
.credit a{text-decoration:underline;text-underline-offset:3px}

/* --- button rows ----------------------------------------------------------
   On a phone two CTAs sitting side by side at different intrinsic widths look
   accidental, and neither is a comfortable thumb target. They go full width until
   there is room for a row. Scoped as `.btnrow > .btn` (0,2,0) so a template's own
   `.btn{}` rule later in the file cannot silently undo it — the same cascade trap
   that left the desktop nav stacked. */
.btnrow{display:flex;flex-wrap:wrap;gap:12px}
.btnrow > .btn{flex:1 1 100%}
@media(min-width:560px){.btnrow > .btn{flex:0 0 auto}}

/* --- long-string guard ----------------------------------------------------
   A client email address or a compound word set in a heavy display face at
   20px uppercase does not wrap, and pushes a 390px page sideways. Found on the
   Ironworks contact page: dispatch@vantagemechanical.com forced the document to
   462px. Plumbing, so every template inherits the fix. */
/* Headings are deliberately NOT in this guard. break-word on a display heading
   shatters a word mid-syllable the moment its container is narrower than the word —
   it turned "Everything we make," into "Everyth / ing we / make," on Marquee. A
   heading that does not fit is a LAYOUT bug; fix the column, do not hyphenate it. */
h1,h2,h3,h4{overflow-wrap:normal;word-break:normal}
a[href^="mailto:"],a[href^="tel:"]{overflow-wrap:anywhere}
.crow span,.crow b,.cinfo span,address{overflow-wrap:anywhere}

/* --- utility -------------------------------------------------------------- */
.sec{padding:64px 0}
@media(min-width:760px){.sec{padding:96px 0}}
@media(min-width:1180px){.sec{padding:124px 0}}
.eyebrow{font-size:12px;font-weight:750;letter-spacing:.16em;text-transform:uppercase;
  color:var(--accent);margin:0 0 14px}
.lead{font-size:19px;line-height:1.6;color:var(--ink-2)}
@media(min-width:760px){.lead{font-size:21px}}

/* ============================================================ THE BOARD =====
   THE ONE services section for every one-page build, current and future.
   Jon, 2026-08-14: this replaces the board/menu section on every skeleton.
   Ruleset and worked examples: _system/onepage/SERVICES-SECTION.md.

   It renders BOTH shapes the contract carries, through one loop, because
   build.py normalises them into board.groups before this file ever sees them:
     services[]  4-6 headline offerings          -> one untitled group
     menu[]      a grouped price list            -> one titled group each
   A barber with six services and a restaurant with eight menu groups get the
   same instrument, and the skeleton does not know which it got.

   WHY IT LIFTS BY TONE, NOT BY SHADOW. The page background is the brand's
   neutral base. The container is the SAME neutral at a different value, so it
   separates even before the shadow renders — on a low-contrast screen, in
   forced-colours, and in print, where the shadow is simply gone. A white card
   on a grey page is the default this exists to replace: it is the one move that
   makes a spec site look like a template, because it belongs to no brand.
   --board / --board-cell / --board-line are DERIVED FROM --surface in build.py
   by an HSL lightness step, never hand-typed, so they cannot drift off-palette
   when a client's logo repaints the page.

   THE PRICE IS A COLUMN, NOT THE POINT. A row with no price omits the cell and
   the other two tracks take the space; the section still renders, because the
   reason it exists is the list of services, not the figures beside them. One
   hook note prints above the grid when anything is unpriced — that is the only
   moment the page speaks to its own owner, and it is why a prospect writes back.

   TOKENS a skeleton sets (all have working defaults):
     --bd-cols      columns in the grid          default 1, 2 from 820px
     --bd-gap       gutter between cells         default 10px
     --bd-pad       padding inside the container default 20px, 32px from 820px
     --bd-cell-pad  padding inside a cell        default 16px
     --bd-icon      the icon plate               default 46px, 52px from 820px
     --bd-glyph     the glyph inside it          default 24px, 26px from 820px
     --bd-price     the price size               default 21px, 24px from 820px
     --bd-edge      the accent edge on a cell    default a 2px gradient bar
     --bd-radius    corner radius                default calc(var(--radius)*1.4)
     --bd-accent    the accent that is SAFE here — build.py falls back to
                    --accent-2, then to --ink, if --accent cannot hold 4.5:1 on
                    the container. Small uppercase text in a brand colour on a
                    tonal surface is the exact place a palette goes unreadable.
   Parts to skin: .board .bgrid .bitem .bicon .bname .bdesc .btag .bprice .bgh
   ========================================================================== */
.board{
  --bd-cols:1;
  --bd-gap:10px;
  --bd-pad:20px;
  --bd-cell-pad:16px;
  --bd-icon:46px;
  --bd-glyph:24px;
  --bd-price:21px;
  --bd-radius:calc(var(--radius) * 1.4);
  --bd-accent:var(--board-accent);
  --bd-edge:linear-gradient(90deg,var(--accent) 0%,var(--accent-2) 62%,transparent 100%);
  background:var(--board);
  border:1px solid var(--board-line);
  border-radius:var(--bd-radius);
  padding:var(--bd-pad);
  box-shadow:0 1px 2px var(--board-shadow-1),0 18px 44px -18px var(--board-shadow-2);
}
@media(min-width:820px){.board{--bd-pad:32px;--bd-icon:52px;--bd-glyph:26px;--bd-price:24px}}

/* THE GRID. Two columns from 820px, one below it, and the cell layout is the
   SAME at every width — icon, then the flexible middle, then the price. It does
   not restack on mobile, because a row that reflows into a different shape is a
   second design to maintain and it always reads as the cheaper one. */
.bgrid{display:grid;grid-template-columns:repeat(var(--bd-cols),minmax(0,1fr));
  gap:var(--bd-gap);margin:0;padding:0;list-style:none}
@media(min-width:820px){.board{--bd-cols:2}}

.bitem{position:relative;display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  align-items:start;column-gap:14px;
  padding:var(--bd-cell-pad);
  border-radius:calc(var(--bd-radius) * .7);
  background:var(--board-cell);
  overflow:hidden}
/* The edge accent is a PSEUDO-ELEMENT, never a border-top: a border changes the
   box and a gradient cannot live on one side of it. ::before also means a
   skeleton can move the edge (left rail, full underline) by restyling one rule
   without touching the grid. */
.bitem::before{content:"";position:absolute;inset:0 0 auto 0;height:2px;
  background:var(--bd-edge)}

.bicon{display:flex;align-items:center;justify-content:center;
  width:var(--bd-icon);height:var(--bd-icon);flex:none;
  border-radius:calc(var(--bd-radius) * .55);
  background:var(--accent-a12);color:var(--bd-accent)}
/* The glyph is sized HERE and nowhere else. Every icon on the page is one
   library at one stroke weight (icons.py writes the wrapper), so the only thing
   a template may change is the size and the colour of the whole set. */
.bicon > svg{width:var(--bd-glyph);height:var(--bd-glyph);display:block}

/* the flexible middle. min-width:0 or a long unbroken word (a URL in a blurb,
   an email address) blows the track out and pushes the price off the row. */
.bmain{grid-column:2;min-width:0}
.bname{margin:0;font-family:var(--font-body);font-size:16.5px;font-weight:700;
  line-height:1.3;color:var(--ink)}
.bdesc{margin:5px 0 0;font-size:14.5px;line-height:1.5;color:var(--ink-3)}
/* justify-self:start or the chip fills its grid cell — a grid item stretches by
   default, and "45 MIN" drew a 350px rounded rectangle across the row the first
   time this was built. Found on the render, not in the markup. */
.btag{display:inline-block;justify-self:start;margin:8px 0 0;
  padding:3px 9px;border:1px solid var(--board-line);
  border-radius:999px;font-size:11.5px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-2)}
.bprice{grid-column:3;align-self:center;font-family:var(--font-display);
  font-size:var(--bd-price);font-weight:700;line-height:1.1;
  font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--ink)}

/* A GROUP HEADING IN THE BRAND COLOUR, and it stays with its list. */
.bgroup + .bgroup{margin-top:26px}
.bgh{margin:0 0 10px;line-height:1.2;font-family:var(--font-body);font-size:12.5px;font-weight:750;
  letter-spacing:.14em;text-transform:uppercase;color:var(--bd-accent)}

/* THE REPLY HOOK. Printed once, above the grid, only when something is unpriced.
   It is not an empty state — the cells with no price are already complete — it
   is the page asking its owner for the one thing only they have. */
.bhook{margin:0 0 16px;padding:13px 15px;border:1px dashed var(--board-line);
  border-radius:calc(var(--bd-radius) * .6);background:transparent}
.bhook-l{display:block;margin:0 0 3px;font-size:11.5px;font-weight:750;
  letter-spacing:.14em;text-transform:uppercase;color:var(--bd-accent)}
.bhook-t{display:block;font-size:14px;line-height:1.5;color:var(--ink-2)}


/* ============================================================================
   THE REGISTER — an account of what a business charges, ruled on one axis.

   THE ORGANISING DEVICE: --axis. One custom property holds the x-position of the
   MONEY COLUMN's left edge, and one 1px gold hairline is drawn there by `.led`.
   Everything the page has to line up hangs off it: the hero's headline plate ends
   on it, every price in the register starts on it, every opening time starts on it,
   and every FAQ marker sits on it. Delete the rule and you lose the fact that the
   figure, the hour and the answer are the same column read three ways. It is NOT
   412toring's spine and must not become it: that is a 2px rule on a wrapper around
   the whole document, positioned 96px inside the content column, running unbroken
   top to bottom with chapter numerals hanging in the margin. This one is 1px, is
   drawn per SECTION by the element that actually aligns to it, moves with the
   measure (64% / 72% / 76%), carries nothing in its margin, and is simply absent
   from the two sections that have nothing to align — the photographic run and the
   phone hero. A rule that is everywhere is decoration; this one is a fence post.

   COLOUR — five, each with one job, none decorative. FOLIO #3E1236 is the ground
   (hero, the proof band, the masthead, the footer). CREAM and SAND are the two
   papers the register is printed on. GOLD is MONEY and only money — every figure,
   the axis rule itself, and hero.h1b. CLAY is ACTION and only action — every
   button, every link, and the leader that stands in for a price nobody has sent
   yet, because that blank IS the ask. Nothing here is grey, nothing is
   cool-neutral, and the photographs keep their own colour on top of all of it.

   LIGHT LOGIC: NONE FALLS ON THIS PAGE, IT IS RULED ON IT. There is not one
   box-shadow, not one glow, not one blur and not one gradient used as a surface in
   this file — grep it. Every separation is a 1px rule, a change of paper, or the
   folio ground. The only gradient in the file is the repeating-linear-gradient
   that draws the LEADER in an unfilled money cell, and a leader is an instrument.
   Depth would be a lie here: a register is flat because paper is flat, and the one
   thing on the page with any depth in it is the photograph.

   MOBILE FIRST. Every rule below is the phone rule; breakpoints only ever add.
   ========================================================================== */

:root{
  --grade:saturate(1.04) contrast(1.02) brightness(1.02);
  --navh:68px;
  --nav-gap:30px;
  --gold:#7A5407;
  --gold-lit:#EFC663;
  --rule-soft:rgba(239,198,99,.34);
  --hair:1px solid var(--line);
  --hair-band:1px solid rgba(247,237,226,.16);
  /* THE AXIS. The money column's left edge, as a fraction of the measure. */
  --axis:64%;
}
@media(min-width:760px){:root{--axis:72%}}
@media(min-width:1180px){:root{--axis:76%}}
/* Jon works on a 28-inch display, and a 1240px column centred in 2560px of paper
   reads as a phone layout that stopped growing. The measure opens once. */
@media(min-width:1700px){:root{--maxw:1380px;--gutter:60px}}

/* ---------------------------------------------------------------- type ----- */
body{background:var(--surface)}
h1{font-size:clamp(44px,5.9vw,78px);line-height:1.02;letter-spacing:-.022em;margin:0}
.h-lg{font-size:clamp(30px,3.7vw,46px);line-height:1.06;letter-spacing:-.018em}
.h-md{font-size:clamp(24px,2.7vw,32px);line-height:1.14;letter-spacing:-.012em}
h3{font-size:19px;line-height:1.3;font-weight:600;letter-spacing:-.008em}
.measure{max-width:58ch}
.lead{color:var(--ink-2)}
.eyebrow{font-family:var(--font-body);font-size:11.5px;font-weight:700;
  letter-spacing:.2em;color:var(--accent);margin:0 0 14px}
.eyebrow.onband{color:var(--accent-on-band)}
/* the money face: every figure on this page is set in the display serif with
   tabular figures, so a column of prices lines up on its own digits. */
.lp,.wt,.pnum,.mp{font-family:var(--font-display);font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum" 1}

/* -------------------------------------------------------------- imagery ----
   ONE grade, ONE rule, every photograph on the page — a WHISPER, and weaker on
   every axis than the house whisper measured off pgh-it.com. Saturation moves UP
   because the argument of this skeleton is that a client's photographs keep their
   own colour. There is no greyscale, no sepia, no hue-rotate and no single-hue
   tint anywhere in this file. */
.heroimg,.plate img{filter:var(--grade)}

/* ------------------------------------------------------------ preview bar --
   Demo chrome. Pinned ABOVE the masthead and the masthead padded to match, because
   in normal flow the fixed nav simply covers it and the wordmark prints on top of
   the bar's own words. No `top` and no `position` is declared on #nav anywhere in
   this file — the shared floor owns those and houserules.py enforces it. */
.tplbar{position:fixed;top:0;left:0;right:0;z-index:130;background:var(--ink);
  color:var(--band-ink-2);font-size:11.5px;line-height:1.4;letter-spacing:.03em;
  padding:8px var(--gutter);text-align:center}
.tplbar b{color:var(--band-ink);font-weight:600}
:root:has(.tplbar){--tpl-h:48px}
@media(min-width:760px){:root:has(.tplbar){--tpl-h:30px}}
#nav{padding-top:var(--tpl-h,0px)}

/* -------------------------------------------------------------------- nav --
   The masthead is FOLIO at every scroll position, so it is invisible against the
   hero and becomes a solid coloured band the moment the page moves onto paper.
   Nothing is translucent, blurred or shadowed; scrolling adds a gold hairline and
   nothing else. */
#nav{background:var(--band);border-bottom:1px solid transparent}
#nav.scrolled{border-bottom-color:var(--rule-soft)}
.nav-inner{gap:14px;flex-wrap:wrap;color:var(--band-ink)}
.brand{display:inline-flex;align-items:center;gap:12px;text-decoration:none;
  color:var(--band-ink);min-height:44px}
.mark{display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;border:1px solid var(--gold-lit);color:var(--gold-lit);
  font-family:var(--font-display);font-size:15px;font-weight:600;letter-spacing:.02em}
.markimg{width:auto;height:34px;border:0;object-fit:contain}
.brandtype{font-family:var(--font-display);font-size:21px;font-weight:600;
  letter-spacing:-.018em;white-space:nowrap}
.navtel{display:none}
.navtoggle{color:var(--band-ink)}
.navtoggle .bars{display:block;width:22px}
.navtoggle .bars i{display:block;height:1px;margin:6px 0;background:var(--band-ink)}
.navtoggle .bars i:nth-child(2){width:14px}
.nav-links{flex:1 0 100%;flex-direction:column;align-items:stretch;gap:0;
  width:100%;padding:2px 0 16px;margin-top:4px;border-top:var(--hair-band)}
.nav-links a{color:var(--band-ink);text-decoration:none;font-size:16px;
  font-weight:500;padding:11px 0;min-height:44px;display:flex;align-items:center;
  border-bottom:var(--hair-band)}
.nav-links a:hover{color:var(--gold-lit)}
.drawlab{font-size:10.5px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;
  color:var(--accent-on-band);margin:12px 0 2px}
.drawhours{font-size:14px;color:var(--band-ink-2);margin:0 0 6px}
html.menu-open #nav{border-bottom-color:var(--rule-soft)}

@media(min-width:940px){
  .nav-inner{flex-wrap:nowrap;gap:24px}
  .drawlab,.drawhours{display:none}
  /* SCOPED TO TWO CLASSES on purpose. `.nav-links a` is (0,1,1) and beats a bare
     (0,1,0) even inside a media query — that is what printed the phone number twice
     in one masthead on 04. Both overrides below are (0,2,0) or better. */
  .nav-links .drawtel{display:none}
  .navtel{display:inline-flex;align-items:center;min-height:44px;margin-left:auto;
    padding-right:24px;border-right:var(--hair-band);color:var(--band-ink);
    font-size:15px;font-weight:500;text-decoration:none;letter-spacing:-.005em}
  .navtel:hover{color:var(--gold-lit)}
  .nav-links{flex:0 0 auto;width:auto;padding:0;margin:0;border-top:0}
  .nav-links a{border-bottom:0;padding:0;font-size:15px;color:var(--band-ink-2)}
  .nav-links .navcta{background:var(--gold-lit);color:var(--band);
    padding:0 20px;min-height:44px;border-radius:var(--radius);font-weight:700;
    font-size:13.5px;letter-spacing:.05em}
  .nav-links .navcta:hover{background:var(--band-ink);color:var(--band)}
}

/* ---------------------------------------------------------------- buttons -- */
.btn{font-weight:650;letter-spacing:.005em}
.btn-primary:hover{background:var(--accent-2);border-color:var(--accent-2)}
.btn-ghost{border-color:var(--line);color:var(--ink)}
.btn-ghost:hover{border-color:var(--accent);color:var(--accent)}
/* On the folio ground clay measures 2.41:1 against the paint behind it, so the
   primary button changes ITS OWN skin rather than its meaning: gold fill, folio
   text, 9.61:1. Action is still one idea; it is wearing the ground's own light. */
.hero .btn-primary,.pband .btn-primary{background:var(--gold-lit);
  color:var(--band);border-color:var(--gold-lit)}
.hero .btn-primary:hover,.pband .btn-primary:hover{background:var(--band-ink);
  border-color:var(--band-ink);color:var(--band)}
.hero .btn-ghost,.pband .btn-ghost{color:var(--band-ink);border-color:var(--rule-soft)}
.hero .btn-ghost:hover,.pband .btn-ghost:hover{border-color:var(--gold-lit);
  color:var(--gold-lit)}

/* ================================================================= THE LEDGER
   The axis, and the two-column row that hangs off it. Four different sections use
   this and they are the same instrument each time: the label is left of the rule,
   the figure is right of it and starts ON it. */
.led{position:relative}
.led::before{content:"";position:absolute;left:var(--axis);top:0;bottom:0;
  width:1px;background:var(--gold);opacity:.62;pointer-events:none}
.pband .led::before{background:var(--gold-lit);opacity:.7}
.lhead,.lrow{display:grid;grid-template-columns:var(--axis) auto;align-items:start}
.lhead{padding:0 0 10px}
.lhead span{font-size:10.5px;font-weight:700;letter-spacing:.2em;
  text-transform:uppercase;color:var(--ink-3)}
.lhead span+span{padding-left:18px}
.lrows{border-top:var(--hair)}
.lrow{padding:17px 0;border-bottom:var(--hair)}
.lwhat{padding-right:16px;min-width:0}
.lfig{padding-left:18px;min-width:0}
.ln{margin:0 0 4px}
.lb{margin:0;font-size:14.5px;line-height:1.5;color:var(--ink-2)}
.lp{display:block;font-size:clamp(24px,3vw,30px);line-height:1;font-weight:600;
  color:var(--gold);letter-spacing:-.01em}
.ltag{display:inline-block;margin-top:9px;padding:3px 9px;border:1px solid var(--line);
  color:var(--ink-2);font-size:11px;font-weight:700;letter-spacing:.13em;
  text-transform:uppercase}
/* THE LEADER — the designed empty state, and this page's one repeated instrument.
   A ruled money column with no figure in it is what an unpriced ledger actually
   looks like, so the cell prints the LEADER a figure would have been set against,
   in CLAY, because an empty price is a request for a reply and clay is the colour
   of every other request on this page. It is used twice and only twice: here, and
   for a week nobody has sent. */
.lleader{display:block;width:118px;max-width:100%;height:3px;
  background-image:repeating-linear-gradient(90deg,var(--accent) 0 3px,transparent 3px 9px)}
.lask{grid-template-columns:1fr;background:var(--surface-2);padding:16px 18px;
  border-left:2px solid var(--accent)}
.lask ~ .lask{display:none}
.laskh{margin:0 0 5px;font-size:14px;font-weight:700;color:var(--accent);
  letter-spacing:.01em}
.laskb{margin:0;font-size:14px;line-height:1.55;color:var(--ink-2)}

/* ==================================================================== THE HEAD
   Folio ground, a serif headline, and the plate in the outer margin. There is no
   scrim and no veil anywhere in this composition: the type is on solid folio and
   the picture is beside it, so every ratio in the hero is a token pair that was
   COMPUTED rather than sampled off a photograph. */
.hero{position:relative;background:var(--band);color:var(--band-ink);
  padding:calc(var(--navh) + var(--tpl-h,0px) + 8px) 0 0;overflow:hidden}
.hero-in{position:relative}
/* PHONE — the plate is a full-bleed band and the folio block cuts UP into it by
   108px, so the h1's box sits INSIDE the picture's box (DESIGN-FLOOR A1) without a
   word of type ever landing on a photograph. */
.heroplate{position:relative;width:100vw;margin-left:calc(50% - 50vw);height:210px}
.heroplate picture{display:block;width:100%;height:100%}
.heroimg{width:100%;height:100%;object-fit:cover;object-position:100% 40%}
.herosay{position:relative;z-index:2;background:var(--band);width:100vw;
  margin-left:calc(50% - 50vw);margin-top:-108px;
  padding:20px var(--gutter) 40px;border-top:1px solid var(--gold-lit)}
.herokick{margin:0 0 12px;font-size:11.5px;font-weight:700;letter-spacing:.2em;
  text-transform:uppercase;color:var(--accent-on-band)}
.hero h1{color:var(--band-ink)}
/* THE DRAWN MOMENT — hero.h1b turns GOLD, turns ITALIC, and STEPS UP a size while
   dropping a weight. Three changes on one phrase and not one of them is a stroke:
   05-room owns the underline in this set and this skeleton is explicitly not
   allowed it. Because the emphasis is carried by colour, angle and size rather
   than by a painted mark, a phrase that wraps carries all three down with it and
   there is nothing that could break into two bars or draw a slab (DESIGN-FLOOR D1). */
.h1b{color:var(--gold-lit);font-style:italic;font-weight:400;font-size:1.07em;
  letter-spacing:-.014em}
.herosub{margin:18px 0 26px;font-size:17.5px;line-height:1.58;color:var(--band-ink-2);
  max-width:46ch}
.hero .btnrow{margin-bottom:34px}
/* THE PROOF COLUMN — proof.rows[], on vertical gold rules, the way 412toring runs
   25+ / M.Ed. / 100%. It is the one vertical rule that exists at EVERY width,
   including the phone, so the hero is never without the device the page is built
   on. The rows print once, here, and are not restated further down. */
.heroproof{display:grid;gap:20px;padding-top:22px;border-top:var(--hair-band)}
.hp-row{padding-left:16px;border-left:1px solid var(--gold-lit)}
.hp-t{margin:0 0 5px;font-family:var(--font-display);font-size:19px;font-weight:600;
  line-height:1.2;color:var(--gold-lit);letter-spacing:-.01em}
.hp-d{margin:0;font-size:14px;line-height:1.5;color:var(--band-ink-2)}

@media(min-width:760px){
  .heroplate{height:280px}
  .herosay{margin-top:-124px;padding-top:26px}
  .heroproof{grid-template-columns:repeat(3,1fr);gap:26px}
}
/* DESKTOP — the plate stands in the outer margin, running the full height of the
   hero and bleeding off the right edge of the viewport. The headline block is the
   width of the AXIS, so it overlaps the plate's left edge by 10-14% of the measure
   (A1 again, by construction) and its right edge IS the axis rule, in gold. The
   page's organising line is therefore born in the hero at exactly the x every
   price below it will start from. */
@media(min-width:1000px){
  .hero{padding-bottom:0;min-height:min(86svh,980px);display:flex;align-items:stretch}
  .hero>.container{display:flex;align-items:stretch;width:100%}
  .hero-in{display:flex;align-items:stretch;width:100%}
  .heroplate{position:absolute;top:0;bottom:0;left:62%;right:calc(50% - 50vw);
    width:auto;height:auto;margin:0;z-index:0}
  .heroimg{object-position:100% 46%}
  .herosay{flex:0 0 var(--axis);width:var(--axis);margin:0;
    padding:calc(var(--navh) + 10px) 46px 54px 0;
    border-top:0;border-right:1px solid var(--gold-lit);
    /* TOP-ANCHORED, NOT CENTRED. justify-content:center reads fine at 1440 and
       fails DESIGN-FLOOR A3 on every large display: centring a fixed block inside a
       min(86svh,980px) column pushes the h1 down as the viewport grows, and it
       measured 346px at 1920, 402 at 2200 and 428 at 2560x1440 against a 300 ceiling
       -- with the 2560x1440 case also breaking the 560 floor at the bottom. Jon works
       on a 28in display, so this is the width that matters most. It is now anchored
       to the top with a vh-scaled inset that cannot outrun the ceiling, and the proof
       column takes up the slack at the bottom via margin-top:auto -- the same fix 04
       needed for the same reason. Measured after the change, not assumed. */
    display:flex;flex-direction:column;justify-content:flex-start;
    padding-top:clamp(28px,6.2vh,74px)}
  .herosub{margin:22px 0 30px;font-size:19px}
  .heroproof{gap:30px;margin-top:auto}
}
@media(min-width:1600px){
  .hero{min-height:min(94svh,1180px)}
}

/* ================================================================ THE REGISTER
   The centrepiece, and the reason this skeleton exists. Cream paper, a column
   head, and one ruled row per service with the figure hanging on the axis. No
   imagery at all — this section is an instrument and a photograph in it would be
   the first thing the delete test threw out. */
.reg{background:var(--surface);padding-top:56px}
.shead{margin-bottom:30px}
.shead .lead{margin-bottom:0}
.regfoot{margin:22px 0 0;font-size:14.5px;color:var(--ink-3);max-width:64ch}
.mgroup{margin-top:34px}
.mgh{margin:0 0 12px;font-size:11.5px;font-weight:700;letter-spacing:.2em;
  text-transform:uppercase;color:var(--ink-3)}
.mrow{padding:12px 0}
.mn{display:block;font-size:16px;font-weight:600;color:var(--ink)}
.mnote{display:block;font-size:13.5px;color:var(--ink-3);margin-top:2px}
.mp{display:block;font-size:19px;font-weight:600;color:var(--gold)}

/* ================================================================ THE PREMISES
   Story, three ruled facts, and the photographic run. The run is the SHARED
   justified layout (gallery.css): every plate is given a WIDTH in proportion to
   its own aspect, so a row of mixed portraits and landscapes comes out at one
   height with nothing cropped. No aspect-ratio, no object-fit, no fixed height and
   no width/height attributes go anywhere near it. */
.prem{background:var(--surface)}
.facts{display:grid;gap:0;margin:30px 0 34px;border-top:var(--hair)}
.fact{padding:16px 0;border-bottom:var(--hair)}
.fact h3{margin:0 0 4px}
.fact p{margin:0;font-size:14.5px;line-height:1.55;color:var(--ink-2)}
.plate figcaption{display:flex;flex-wrap:wrap;gap:4px 12px;align-items:baseline;
  margin-top:9px;padding-top:8px;border-top:1px solid var(--gold);
  font-size:12.5px;letter-spacing:.02em}
.plate figcaption b{font-weight:700;color:var(--ink)}
.plate figcaption span{color:var(--ink-3)}
.ourplates{margin:0 0 18px;padding-left:14px;border-left:2px solid var(--accent);
  font-size:14px;line-height:1.55;color:var(--ink-2);max-width:62ch}
@media(min-width:760px){
  .facts{grid-template-columns:repeat(3,1fr);gap:0 26px}
}

/* ================================================================== THE BAND
   The second and last appearance of the folio ground, full bleed. It is here so
   the page does not run cream from the register to the footer, and it carries the
   one thing on the page that is somebody else's words. */
.pband{background:var(--band);color:var(--band-ink)}
.pband h2{color:var(--band-ink)}
.pband .lead{color:var(--band-ink-2)}
.qt{margin:28px 0 0;background:var(--surface);color:var(--ink);
  border-radius:var(--radius);padding:26px 24px}
.qt blockquote{margin:0;font-family:var(--font-display);font-style:italic;
  font-size:clamp(19px,2.2vw,24px);line-height:1.42;color:var(--ink);
  letter-spacing:-.008em}
.qt figcaption{margin-top:16px;padding-top:12px;border-top:1px solid var(--gold);
  font-size:12.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gold)}
@media(min-width:900px){
  .bandgrid{display:grid;grid-template-columns:1fr 1fr;gap:46px;align-items:start}
  .qt{margin:0;padding:32px 30px}
}

/* ================================================================== THE ASKS
   Sand paper, one narrow column beside its own heading, and the marker sits ON the
   axis — the same rule, doing a fourth job. */
.asks{background:var(--surface-2)}
.ask{border-bottom:var(--hair)}
.ask summary{display:grid;grid-template-columns:var(--axis) auto;align-items:start;
  gap:0;padding:16px 0;cursor:pointer;list-style:none;font-size:16.5px;
  font-weight:600;color:var(--ink)}
.ask summary::-webkit-details-marker{display:none}
.ask summary i{justify-self:start;margin-left:18px;position:relative;
  width:15px;height:15px;margin-top:5px}
.ask summary i::before,.ask summary i::after{content:"";position:absolute;
  background:var(--accent);left:0;top:7px;width:15px;height:1px}
.ask summary i::after{transform:rotate(90deg);transition:transform .2s ease}
.ask[open] summary i::after{transform:rotate(0deg)}
.ask[open] summary{color:var(--accent)}
.ask p{margin:0 0 18px;padding-right:18px;font-size:15.5px;line-height:1.6;
  color:var(--ink-2);max-width:64ch}
.askcall{margin:12px 0 0;font-size:14.5px;color:var(--ink-3)}
.askcall a{color:var(--accent);font-weight:600}
@media(min-width:900px){
  .askgrid{display:grid;grid-template-columns:0.72fr 1.28fr;gap:46px;align-items:start}
  .asklist{--axis:78%}
}

/* ================================================================== THE CLOSE
   Cream. The week on the axis, the address, and the form on sand beside it. */
.close{background:var(--surface)}
.wk{margin:28px 0 26px}
.wd{font-size:15.5px;font-weight:600;color:var(--ink)}
.wt{font-size:16px;color:var(--ink-2)}
.wask{margin:16px 0 0;padding-left:14px;border-left:2px solid var(--accent);
  font-size:14px;line-height:1.55;color:var(--ink-2);max-width:60ch}
.crow{display:grid;gap:6px;margin-bottom:22px}
.cbig{font-family:var(--font-display);font-size:clamp(21px,2.4vw,26px);
  font-weight:600;color:var(--accent);text-decoration:none;letter-spacing:-.012em}
.cbig:hover{color:var(--accent-2);text-decoration:underline;text-underline-offset:4px}
.crow address{font-style:normal;font-size:15px;color:var(--ink-2);margin-top:4px}
.area h3{margin:0 0 6px}
.area p{margin:0 0 10px;font-size:14.5px;color:var(--ink-2)}
.towns{display:flex;flex-wrap:wrap;gap:7px}
.towns span{padding:3px 9px;border:1px solid var(--line);font-size:11.5px;
  font-weight:700;letter-spacing:.11em;text-transform:uppercase;color:var(--ink-2)}
.formpanel{background:var(--surface-2);border-radius:var(--radius);padding:22px 20px}
.formpanel .field label{color:var(--ink-2)}
.fineprint{margin:12px 0 0;font-size:13px;line-height:1.5;color:var(--ink-3)}
@media(min-width:900px){
  .cgrid{display:grid;grid-template-columns:1fr 1fr;gap:46px;align-items:start}
  .formpanel{padding:30px 28px}
}

/* ==================================================================== 404 ---- */
.nf{background:var(--band);color:var(--band-ink);
  padding-top:calc(var(--navh) + var(--tpl-h,0px) + 46px)}
.nf h1{color:var(--band-ink);font-size:clamp(38px,4.6vw,58px)}
.nf .lead{color:var(--band-ink-2)}
.nfline{display:flex;flex-wrap:wrap;gap:6px 20px;margin:26px 0 0;
  padding-top:16px;border-top:var(--hair-band);font-size:15px;
  color:var(--band-ink-2);align-items:baseline}
.nfline .cbig{color:var(--gold-lit)}
.nfline .cbig:hover{color:var(--band-ink)}
.nfplate{margin:34px 0 0}
.nfplate figcaption{border-top-color:var(--gold-lit)}
.nfplate figcaption b{color:var(--band-ink)}
.nfplate figcaption span{color:var(--band-ink-2)}
@media(min-width:900px){
  .nfgrid{display:grid;grid-template-columns:1.15fr .85fr;gap:46px;align-items:center}
  .nfplate{margin:0}
}

/* ================================================================== footer -- */
footer{background:var(--band);color:var(--band-ink-2);padding:48px 0 30px;
  border-top:1px solid var(--gold-lit)}
.fgrid{display:grid;gap:28px}
.fname{margin:12px 0 6px;font-family:var(--font-display);font-size:21px;
  font-weight:600;color:var(--band-ink);letter-spacing:-.016em}
.fabout{margin:0;font-size:14.5px;line-height:1.55;max-width:44ch}
.mark.big{width:42px;height:42px;font-size:18px}
.fh{margin:0 0 10px;font-size:10.5px;font-weight:700;letter-spacing:.2em;
  text-transform:uppercase;color:var(--accent-on-band)}
.fcol a,.fplain{display:block;font-size:15px;color:var(--band-ink);
  text-decoration:none;padding:3px 0}
.fcol a:hover{color:var(--gold-lit)}
.fcol address{font-style:normal;font-size:15px;padding:3px 0}
.fnav{display:flex;flex-wrap:wrap;gap:8px 22px;margin-top:30px;padding-top:20px;
  border-top:var(--hair-band)}
.fnav a{font-size:14px;color:var(--band-ink-2);text-decoration:none}
.fnav a:hover{color:var(--gold-lit)}
.fbase{display:flex;flex-wrap:wrap;gap:8px 22px;margin-top:22px;padding-top:18px;
  border-top:var(--hair-band);font-size:13px}
.credit a{color:var(--gold-lit)}
@media(min-width:760px){
  .fgrid{grid-template-columns:1.4fr 1fr 1fr;gap:40px}
  .fbase{justify-content:space-between}
}


/* =============================================================== THE BOARD ==
   The shared services section (base.css THE BOARD, partials/board.html) wearing
   the register's skin. Structure is inherited; what is set here is the ledger's
   own vocabulary. Jon, 2026-08-14: one services section for every skeleton.

   THE AXIS SURVIVES AS THE EDGE. The register's identity was a gold rule running
   name -> figure. There is no leader in this component, so the rule becomes the
   top edge of every cell: the same 1px gold line, now drawn across the whole row
   rather than through it, and the money still lines up because the price column
   is a grid track. Square corners throughout — a ledger has no round corners. */
.led .board{--bd-gap:0;--bd-cell-pad:15px 16px;--bd-price:22px;--bd-radius:0;
  --bd-edge:linear-gradient(90deg,var(--gold-rule,#C6A254) 0 100%)}
@media(min-width:940px){.led .board{--bd-icon:52px;--bd-glyph:26px;--bd-price:25px}}
.led .bitem{border-radius:0;border-right:1px solid var(--board-line);
  border-bottom:1px solid var(--board-line)}
.led .bicon{border-radius:0;color:var(--gold-tx,#7A5407)}
.led .bname{font-family:var(--font-display);font-weight:600;font-size:19px;color:var(--ink)}
.led .bprice{font-family:var(--font-display);font-weight:700;color:var(--gold-tx,#7A5407)}
.led .btag{border-radius:0;font-size:10px;letter-spacing:.13em;color:var(--ink-3)}
.led .bdesc{color:var(--ink-2)}
.led .bgh{color:var(--gold-tx,#7A5407)}
.led .bhook{border-style:solid;border-color:var(--gold-rule,#C6A254);border-radius:0}
.led .bhook-l{color:var(--gold-tx,#7A5407)}


/* ==================================================== THE MOSAIC COMPONENT ===
   One component, ten hardcoded layouts (see mosaic.py). The layout decides every
   tile's column span and row span; nothing is computed from the photographs, and
   nothing is randomised at runtime. Each layout is pre-validated so its tiles fill
   the 12-column grid exactly — no gaps, no orphan hanging below the rest.

   The tile is the shape. The picture fills it (cover), so any source aspect crops
   cleanly and the wall is a rectangle whatever the client sends.

   Row height is a clamp, so a twelve-tile layout is a wall and not a scroll. */

.mos{display:grid;grid-template-columns:repeat(12,1fr);
  grid-auto-rows:clamp(96px,13.5vw,190px);
  grid-template-rows:repeat(var(--mos-rows,3),clamp(96px,13.5vw,190px));
  gap:var(--mos-gap,10px)}

.mos-t{position:relative;overflow:hidden;border-radius:var(--radius,3px);
  background:var(--surface-2,#171512);margin:0;min-width:0}
.mos-t img{display:block;width:100%;height:100%;object-fit:cover}

/* a caption sits on the tile, so it never changes the tile's height */
.mos-cap figcaption{position:absolute;left:0;right:0;bottom:0;padding:26px 14px 11px;
  display:flex;flex-direction:column;gap:1px;pointer-events:none;
  background:linear-gradient(180deg,rgba(9,8,7,0),rgba(9,8,7,.82))}
.mos-cap figcaption b{font-size:13.5px;font-weight:600;color:#F0EAE0;line-height:1.25}
.mos-cap figcaption span{font-size:11px;letter-spacing:.11em;text-transform:uppercase;
  color:rgba(240,234,224,.72)}

/* a text tile is just another tile — same grid maths, no special case */
.mos-text{display:flex;flex-direction:column;justify-content:center;
  padding:clamp(14px,1.6vw,26px);
  background:var(--mos-text-bg,var(--surface-2,#171512))}
.mos-text b{display:block;margin-bottom:7px;font-family:var(--font-display,inherit);
  font-size:clamp(15px,1.5vw,21px);font-weight:600;line-height:1.2;
  color:var(--ink,#F0EAE0)}
.mos-text p{margin:0;font-size:clamp(12.5px,1.05vw,15px);line-height:1.5;
  color:var(--ink-2,#B8AE9E)}

/* THE PHONE. A twelve-column mosaic is unreadable at 390px, but stacking every tile
   full width is not the answer either: it turned an eight-tile wall into 2,071px of
   scroll and pushed whole pages past the height ceiling. It also stops being a mosaic.

   So the phone gets a TWO-column mosaic. Same tiles, same order, square cells - the wall
   is still a wall and the height roughly quarters.

   THE ODD-COUNT ORPHAN, which is the thing Jon actually objected to: with two columns an
   odd number of tiles leaves the last one alone on its row. `:last-child:nth-child(odd)`
   is true only when the tile is BOTH last and at an odd position - i.e. exactly when the
   count is odd - and widening it to both columns closes the block. Even counts are
   already flush, and the rule cannot fire on them. */
@media(max-width:699px){
  .mos{grid-template-columns:repeat(2,1fr);grid-template-rows:none;
    grid-auto-rows:auto;gap:var(--mos-gap-sm,8px)}
  .mos-t{grid-column:auto/span 1 !important;grid-row:auto !important;
    aspect-ratio:var(--mos-sm-ar,1/1)}
  .mos-t:last-child:nth-child(odd){grid-column:auto/span 2 !important;
    aspect-ratio:var(--mos-sm-ar-wide,16/9)}
  .mos-text{aspect-ratio:auto !important;min-height:132px}
  .mos-cap figcaption{padding:20px 10px 9px}
  .mos-cap figcaption b{font-size:12.5px}
}

/* Very narrow phones: two columns of a 12-photo wall get tight, but one column is still
   worse than a small tile, so the columns hold and only the gap tightens. */
@media(max-width:359px){ .mos{gap:6px} }
