/* ============================================================================
   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:#FBFAF8;
  --surface-2:#EDE9E3;
  --band:#101010;
  --band-ink:#F6F4F1;
  --band-ink-2:#C9C5BE;
  --accent-on-band:#5FD3A6;
  --ink:#101010;
  --ink-2:#5A554E;
  --ink-3:#6B655C;
  --accent:#12684A;
  --accent-2:#5FD3A6;
  --accent-ink:#FBFAF8;
  --line:#DCD7CE;
  --maxw:1320px;
  --gutter:22px;
  --radius:0;
  --accent-a12:rgba(18,104,74,.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:#e0e0d5;
  --board-cell:#f1f1ec;
  --board-line:rgba(18,104,74,.30);
  --board-accent:#12684A;
  --board-glow:rgba(18,104,74,.13);
  --board-shadow-1:rgba(16,16,16,.08);
  --board-shadow-2:rgba(16,16,16,.26);
  --font-display:'Bricolage Grotesque',system-ui,-apple-system,'Segoe UI',sans-serif;
  --font-body:'Karla',system-ui,-apple-system,'Segoe UI',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:800;letter-spacing:-.032em}
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(18,104,74,.22)}
.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:#8A2115;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:58px}
.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-glow      accent halation around the panel  DEFAULT OFF - opt in only
                    on a skeleton whose idea is light
     --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-glow:transparent;
  --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);
  /* THE GLOW IS OPT-IN AND OFF BY DEFAULT. Jon, 2026-08-14: "glow isn't right
     on all sites, we have a template that does glow... everything else is
     accurate." A halation is a LIGHT effect and it belongs to the skeleton
     whose organising idea is light (04 Lightbox). On a ruled ledger or a flat
     midday page it is a borrowed trick, and a device used everywhere is
     decoration. A skeleton opts in with --bd-glow:var(--board-glow).
     The lift itself does NOT depend on it: the colour step and the accent
     hairline carry it, which is why they are derived and not optional. */
  box-shadow:0 0 46px -6px var(--bd-glow),
             0 1px 2px var(--board-shadow-1),
             0 26px 56px -24px 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 GOOGLE RATING — shared structural CSS for partials/rating.html.
   Canonical source. Skinned per skeleton by declaring these five tokens in the
   template's own site.css; never by editing the rules below.

     --rating-bg      the figure's ground
     --rating-shadow  its cast shadow, in that skeleton's light logic
     --rating-rim     its inner hairline / lit edge
     --rq-bg          the review slot's ground
     --rq-ink         the review slot's body colour

   Jon's standing requirement: the rating is NEVER a stat in a card. It is the
   only hard checkable fact on the page, so it takes display scale and air.
   ========================================================================= */
.rating{margin-top:38px;display:block}
.rating-fig{position:relative;isolation:isolate;overflow:hidden;
  padding:clamp(26px,4vw,54px) clamp(24px,4vw,56px) clamp(28px,4vw,52px);
  border-radius:calc(var(--radius) * 2);
  background:var(--rating-bg, color-mix(in srgb, var(--accent) 8%, var(--surface-2)));
  box-shadow:var(--rating-shadow, 0 30px 64px -32px rgba(0,0,0,.5))}
.rating-fig::after{content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;
  box-shadow:inset 1px 1px 0 var(--rating-rim, rgba(255,255,255,.14))}
.rstars{position:relative;display:inline-block;font-size:clamp(24px,3.6vw,40px);
  letter-spacing:.14em;line-height:1;white-space:nowrap}
.rstars i{font-style:normal;display:block}
.rs-off{color:color-mix(in srgb, var(--accent) 26%, transparent)}
/* filled to the EXACT fraction: a discrete half-star claimed 4.5 while the figure beside
   it said 4.8, and both cannot be true. 4.8 of 5 is 96% of the row. */
.rs-on{position:absolute;inset:0;overflow:hidden;width:var(--fill,100%);color:var(--accent)}
.rnum{display:block;font-family:var(--font-display);font-weight:700;letter-spacing:-.055em;
  font-size:clamp(120px,22vw,300px);line-height:.8;color:var(--ink);margin-top:10px}
.rsay{margin:18px 0 0;font-family:var(--font-display);
  font-size:clamp(19px,2.4vw,31px);line-height:1.24;letter-spacing:-.015em;
  color:var(--ink-2);max-width:24ch}
.rsay .rcount{color:var(--ink);font-weight:700}
/* Google Maps attribution as a PILL, under the sentence and inside the SAME container as
   the rating - Google's placement guidance - and the pill is itself the "visually
   distinguish this content" cue they ask for. Logo AND the words. Min 16dp / max 19dp
   high with Google's clear space. The asset is official and is never restyled or redrawn. */
.gmpill{display:inline-flex;align-items:center;gap:9px;margin:18px 0 0;
  padding:8px 14px 8px 12px;border-radius:99px;text-decoration:none;
  background:rgba(255,255,255,.07);border:1px solid var(--rating-rim, rgba(255,255,255,.16));
  transition:background .2s,border-color .2s}
.gmpill:hover{background:rgba(255,255,255,.12)}
.gmpill img{display:block;height:17px;width:auto}
.gmpill .gmgo{font:600 13px/1 system-ui,-apple-system,'Segoe UI',sans-serif;
  letter-spacing:.01em;color:var(--ink);white-space:nowrap}
.gmpill:hover .gmgo{color:var(--accent)}
.rquotes{display:grid;gap:14px;margin-top:22px;max-width:660px}
.rq{margin:0;padding:20px 22px;border-radius:var(--radius);
  background:var(--rq-bg, var(--surface-2))}
.rq blockquote{margin:0;font-family:var(--font-display);font-size:18px;line-height:1.42;
  color:var(--rq-ink, var(--ink))}
.rq figcaption{margin-top:9px;font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-2)}
.rq-empty{background:none;border:1px dashed var(--line)}
.rq-empty blockquote{color:var(--ink-2);font-size:16px}
@media(min-width:900px){
  .rating-fig{display:grid;grid-template-columns:auto minmax(0,1fr);
    column-gap:clamp(36px,5vw,76px);align-items:center}
  .rstars{grid-column:1;grid-row:1;margin-bottom:2px}
  .rnum{grid-column:1;grid-row:2;margin-top:0}
  .rsay{grid-column:2;grid-row:1 / span 2;align-self:center;margin:0;max-width:15ch}
}


/* ============================================================================
   03 — THE STACK.

   PLATES, NOT SECTIONS. Full-width bands edge to edge with no gutter anywhere,
   and every content plate is pulled UP over the band above it by exactly --lap.
   That overlap is the whole idea: a plate is a physical thing lying on top of
   another physical thing, so it carries a hairline shadow along its top edge and
   its type sits hard against that edge with almost no air above it.

   THE STRUCTURE IS THE MOTION. THE SHUNT: a plate does not fade and does not
   drift. It arrives from BELOW, travels up into its seat and stops dead against
   the plate above — fast in, no easing tail, no overshoot, the way a stack of
   plates settles when you drop it on a bench. Because the plates overlap, you
   watch each arriving plate cover the bottom --lap of the one before it. Lift
   this move onto a page with gutters between its sections and it means nothing;
   there is nothing to land against. See template.js.

   THREE RULES THIS FILE NEVER BREAKS
     1. no gutter, no container, no card, no rounded corner. --radius is 0 and
        every shared component inherits it: board cells, rating figure, form
        fields, mosaic tiles. There is not one override.
     2. every plate is .plate + one of .pl-ink / .pl-pale / .pl-paper, and they
        run ink → pale → paper down the whole document, so no two plates in
        contact share a ground.
     3. the geometry is identical on all nine plates. Only the skin changes.

   MOBILE FIRST. Every rule here is the phone rule; breakpoints only add, and
   there is not a single max-width query in this file.
   ========================================================================== */

:root{
  /* THE MOCKUP'S NAMES, MAPPED ONTO THE HOUSE PALETTE. One literal per colour,
     declared once in template.json and rendered into base.css above this file —
     nothing here re-types a hex.
        --paper       IS --surface      #FBFAF8
        --pale        IS --surface-2    #EDE9E3
        --ink         IS --ink          #101010, and --band is the same hex:
                                        on this template the dark plate IS the ink
        --accent-lit  IS --accent-2     #5FD3A6 */
  --paper:var(--surface);
  --pale:var(--surface-2);
  --accent-lit:var(--accent-2);
  /* the one colour base.css does not carry: the type that sits ON the lit accent,
     i.e. the drawn moment's plate and the ink plate's primary button. */
  --lit-ink:#062A1E;

  --lap:clamp(26px,4.6vw,74px);      /* how far a plate rides over the one above */
  --pad:clamp(18px,4.4vw,66px);      /* the only horizontal measure on the page   */
  --navh:58px;                       /* matches design.nav_h, which base.css puts
                                        on .nav-inner as a min-height             */
  --shunt:clamp(60px,9vh,110px);     /* how far a plate travels to its seat       */
  --band-h:clamp(190px,26vw,330px);  /* the interstitial photographic strips      */
  --measure:1320px;                  /* type stops; the PLATE never does          */
  --nav-gap:22px;

  /* THE SEAM. The shadow a plate throws UP the face of the plate it is lying on.
     It is the only shadow in the design and it is always on the top edge, because
     a stack is lit from above and nothing here floats. */
  --seam:0 -22px 44px -26px color-mix(in srgb, var(--ink) 62%, transparent);
  --seam-2:0 -14px 26px -18px color-mix(in srgb, var(--ink) 48%, transparent);
  --rule:color-mix(in srgb, var(--ink) 14%, transparent);
  --rule-on-ink:color-mix(in srgb, var(--band-ink) 20%, transparent);
}

/* ------------------------------------------------------------------ type ---- */
body{background:var(--paper)}
h1,h2,h3{font-optical-sizing:auto}
h1{font-size:min(clamp(34px,7vw,84px),12.4vh);line-height:1.0;
  letter-spacing:-.035em;margin:0;max-width:16ch}
.h-lg{font-size:clamp(28px,5.4vw,62px);line-height:.98;letter-spacing:-.032em;max-width:17ch}
.h-md{font-size:clamp(23px,3.4vw,36px);line-height:1.06;letter-spacing:-.026em;max-width:20ch}
h3{font-size:18px;line-height:1.2;font-weight:600;letter-spacing:-.018em}
.eyebrow{font-size:11px;font-weight:700;letter-spacing:.28em;
  margin:0 0 clamp(9px,1.2vh,14px)}
.lead{font-size:16px;line-height:1.54;color:var(--ink-2);max-width:54ch;margin:14px 0 0}
.measure{max-width:56ch}
.mono{font-family:var(--font-display);font-weight:800;letter-spacing:-.01em}
@media(min-width:760px){.lead{font-size:17px}}

/* ------------------------------------------------------------ preview bar --
   Demo-only chrome. The fixed nav would print straight on top of it, so the bar
   is pinned above the nav and the nav takes a matching padding-top, published as
   --tpl-h and read again by the hero. No `top` and no `position` is declared on
   #nav: the shared floor owns those and houserules.py enforces it. */
.tplbar{position:fixed;top:0;left:0;right:0;z-index:130;
  background:var(--band);color:var(--band-ink-2);font-size:12.5px;line-height:1.45;
  padding:8px var(--pad);text-align:center}
.tplbar b{color:var(--accent-2);font-weight:700}
:root:has(.tplbar){--tpl-h:58px}
@media(min-width:760px){:root:has(.tplbar){--tpl-h:36px}}
#nav{padding-top:var(--tpl-h,0px)}

/* ------------------------------------------------------------------- nav ----
   THE NAV IS THE TOP PLATE. It is opaque paper from the first pixel, not glass
   over the picture: a plate is opaque, and a translucent bar would be the one
   surface on the page you could see through. On scroll it gains the seam — the
   same shadow every other plate throws — so the page reads as sliding UNDER it. */
.navpad{padding:0 var(--pad)}
#nav{background:var(--paper);border-bottom:1px solid var(--rule)}
#nav.scrolled{box-shadow:0 12px 26px -22px color-mix(in srgb, var(--ink) 70%, transparent)}
.nav-inner{gap:10px;flex-wrap:wrap}
.brand{display:inline-flex;align-items:center;gap:10px;text-decoration:none;
  color:var(--ink);min-height:44px}
.brand .mono{display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;font-size:12.5px;background:var(--band);color:var(--accent-2)}
.brandmark{display:block;width:auto;height:30px;max-width:150px;object-fit:contain}
.brandtype{font-family:var(--font-display);font-size:18px;font-weight:800;
  letter-spacing:-.03em;white-space:nowrap}
.navtoggle .bars{display:block;width:22px}
.navtoggle .bars i{display:block;height:2px;margin:4px 0;background:var(--ink)}
.navtoggle .bars i:nth-child(2){width:14px}
/* THE DRAWER. Unscoped on purpose: base.css resets #nav .nav-links at 940px with
   (1,1,0), and a rule written here as `#nav .nav-links` would tie on specificity,
   sit later in the file and drag the mobile column onto the desktop bar. */
.nav-links{flex:1 0 100%;flex-direction:column;align-items:flex-start;gap:0;
  width:100%;padding:4px 0 14px;margin-top:4px;border-top:1px solid var(--rule)}
.nav-links a{color:var(--ink-2);text-decoration:none;font-size:16px;font-weight:500;
  padding:11px 0;min-height:46px;display:flex;align-items:center;width:100%}
.nav-links a:hover{color:var(--accent)}
.drawhours{margin:8px 0 2px;font-size:11px;font-weight:700;letter-spacing:.2em;
  text-transform:uppercase;color:var(--ink-3)}
/* two classes, because `.drawtel` alone is (0,1,0) and loses to base's
   `.nav-links a{display:flex}` at (0,1,1) — which printed the drawer phone number
   in every desktop masthead across five templates before it was scoped. */
.nav-links .drawtel{font-family:var(--font-display);font-size:21px;font-weight:800;
  letter-spacing:-.03em;color:var(--accent)}
.nav-links .navcta{background:var(--accent);color:var(--accent-ink);font-weight:700;
  justify-content:center;padding:0 20px;margin-top:8px;min-height:48px}
.nav-links .navcta:hover{background:var(--band);color:var(--accent-2)}
html.menu-open #nav{background:var(--paper)}

@media(min-width:940px){
  .nav-inner{flex-wrap:nowrap}
  .nav-links{flex:0 0 auto;width:auto;padding:0;margin:0;border:0;align-items:center}
  .nav-links a{width:auto;padding:0;min-height:34px;font-size:13px;font-weight:700;
    letter-spacing:.16em;text-transform:uppercase}
  .nav-links .drawhours,.nav-links .drawtel{display:none}
  .nav-links .navcta{height:38px;min-height:38px;margin-top:0;font-size:12px;
    letter-spacing:.16em;text-transform:uppercase}
}
@media(min-width:1180px){
  .nav-links .drawtel{display:flex;font-size:16px;letter-spacing:-.02em;
    text-transform:none;font-weight:800}
}

/* =========================================================== THE PLATE =======
   Everything below the nav is one of these. Opaque, full width, no radius, pulled
   up over whatever is above it by --lap, and carrying the seam on its top edge. */
.plate{position:relative;z-index:2;margin-top:calc(var(--lap) * -1);
  box-shadow:var(--seam)}
.pl-ink{background:var(--band);color:var(--band-ink)}
.pl-pale{background:var(--pale);color:var(--ink)}
.pl-paper{background:var(--paper);color:var(--ink)}
/* THE PLATE'S CONTENT. `.pin` and not `.in`: base.css owns `html.js .reveal.in`,
   so an element carrying both `reveal` and `in` would match the REVEALED state
   from first paint and never fade at all. Grep base.css before naming anything. */
.pin{width:100%;max-width:var(--measure);margin:0 auto;
  padding:clamp(14px,1.8vw,24px) var(--pad) clamp(34px,4.6vw,64px)}
/* type hard against the top edge: the eyebrow's own line-box is the only air. */
.pin > .eyebrow:first-child{margin-top:0}
.pl-ink .eyebrow{color:var(--accent-2)}
.pl-ink .lead{color:var(--band-ink-2)}
.pl-ink h1,.pl-ink h2,.pl-ink h3{color:var(--band-ink)}

/* ------------------------------------------------------------------ bands ----
   The photographic strips between plates. Full bleed, no gutter, and the plate
   below always covers their bottom --lap, which is why nothing important may sit
   in the bottom sixth of one of these frames. */
.band{position:relative;z-index:1;overflow:hidden;height:var(--band-h);
  background:var(--band)}
/* ORIENTATION (orientcheck.py, allowance recorded in orientcheck-allow.txt).
   A band is a horizontal SLICE, not a hole of a fixed shape: it never asks the
   asset for a proportion, it takes a strip across the middle of whatever arrives.
   That is the one frame where portrait and landscape both work, which is exactly
   what the orientation rule protects. The client's actual photographs are carried
   by the shared mosaic, which sizes every tile from its own asset. */
.band img{width:100%;height:100%;object-fit:cover;object-position:50% 50%}

/* ------------------------------------------------------------------ buttons -- */
.btn{border-radius:0;font-weight:700;font-size:13px;letter-spacing:.18em;
  text-transform:uppercase;padding:16px 24px;min-height:50px}
.btn-primary{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
.btn-primary:hover{background:var(--band);border-color:var(--band);color:var(--accent-2)}
.btn-ghost{background:transparent;color:var(--ink);border-color:var(--ink)}
.btn-ghost:hover{background:var(--ink);color:var(--paper)}
.pl-ink .btn-primary{background:var(--accent-2);border-color:var(--accent-2);color:var(--lit-ink)}
.pl-ink .btn-primary:hover{background:var(--band-ink);border-color:var(--band-ink);color:var(--band)}
.pl-ink .btn-ghost{color:var(--band-ink);border-color:var(--rule-on-ink)}
.pl-ink .btn-ghost:hover{background:var(--band-ink);color:var(--band);border-color:var(--band-ink)}

/* ================================================================= HERO ======
   ONE BUDGET FOR THE FIRST SCREEN. #top is min-height:100svh less the masthead;
   the words plate is `auto` — its own height, whatever the headline runs to — and
   the band is the `1fr` that absorbs the remainder. So the type can never be
   clipped by the picture and the picture can never be squeezed to nothing, and
   both buttons are above the fold at every viewport. No word is ever set on the
   photograph, which is why this hero needs no scrim and no hero-floor pair. */
#top{display:flex;flex-direction:column;background:var(--paper);
  padding-top:calc(var(--navh) + var(--tpl-h,0px));
  min-height:100vh;min-height:100svh}
.heroband{position:relative;z-index:1;flex:1 1 auto;overflow:hidden;
  min-height:clamp(150px,22svh,340px);background:var(--band)}
.heroband img{width:100%;height:100%;object-fit:cover;object-position:50% 42%}
#top .plate{flex:0 0 auto}

/* THE DRAWN MOMENT — LAMINATION. h1b leaves the run of the headline and is given
   a PLATE of its own: accent-lit ground, --lit-ink type, the same seam along its
   top edge that every plate on the page carries, sitting hard against the line
   above with almost no air. The page's one idea, performed once at headline
   scale. It is a BLOCK and not an inline span deliberately — an inline background
   box is as tall as the font's ascent plus descent and at this leading it would
   overprint the line above it. */
.lamina{display:block;width:fit-content;max-width:100%;
  margin:.05em 0 0;padding:.08em .24em .14em;
  font-style:normal;font-weight:800;line-height:1.0;
  background:var(--accent-2);color:var(--lit-ink);box-shadow:var(--seam-2)}
.herosay .lead{color:var(--band-ink-2);max-width:48ch}
.herosay .btnrow{margin-top:clamp(16px,2.4vw,26px)}
/* the standing line: hours and the door, on the last hairline of the plate. */
.standing{display:flex;flex-direction:column;gap:3px;margin:clamp(18px,2.6vw,28px) 0 0;
  padding-top:13px;border-top:1px solid var(--rule-on-ink);
  font-size:12.5px;line-height:1.5;color:var(--band-ink-2)}
.standing b{color:var(--band-ink);font-weight:700;letter-spacing:.02em}
@media(min-width:760px){
  .standing{flex-direction:row;gap:20px;align-items:baseline;font-size:13px}
}

/* ============================================================== THE RATING ===
   Jon's standing rule, every template: the Google figure is never a statistic in
   a card. Here it is a PAPER SLAB lying on the pale plate — the page's own move,
   at the page's own scale, with the seam on its top edge. The review slots are
   smaller slabs under it so they never compete with the number.
   Structural CSS is shared/rating.css; these five tokens are the whole skin. */
.rated{--rating-bg:var(--paper);
  --rating-shadow:var(--seam);
  --rating-rim:color-mix(in srgb, var(--ink) 9%, transparent);
  --rq-bg:var(--paper);
  --rq-ink:var(--ink)}
.rated .rating{margin-top:clamp(24px,3.4vw,42px)}
.rated .rating-fig{padding:clamp(22px,3.4vw,46px) clamp(20px,3.4vw,48px) clamp(24px,3.4vw,44px)}
.rated .rnum{color:var(--ink)}
.rated .rsay{color:var(--ink-2)}
.rated .rq{box-shadow:var(--seam-2)}
.rated .rq figcaption{color:var(--ink-3)}
.rated .rq-empty{border-color:var(--rule)}
/* THE ATTRIBUTION PILL IS AN INK CHIP. Google's asset ships white and may not be
   recoloured or filtered, so on a paper slab the pill takes the ink ground and the
   logo stays exactly as Google supplies it. Same compliance, light page. */
.rated .gmpill{background:var(--band);border-color:var(--band)}
.rated .gmpill .gmgo{color:var(--band-ink)}
.rated .gmpill:hover{background:color-mix(in srgb, var(--band) 88%, var(--accent))}
.rated .gmpill:hover .gmgo{color:var(--accent-2)}

/* ================================================================ THE BOARD ==
   The shared services component (base.css THE BOARD + partials/board.html). The
   container lifts by TONE — build.py derives --board from --surface — and this
   template only squares it off and puts the seam on it, so the priced list reads
   as one more plate lying on the paper one. */
.prices .board{--bd-gap:1px;--bd-cell-pad:18px 16px;--bd-price:26px;
  --bd-edge:linear-gradient(90deg,var(--accent) 0%,var(--accent) 38%,transparent 100%);
  margin-top:clamp(22px,3.2vw,40px);border:0;padding:0;background:var(--rule);
  box-shadow:var(--seam)}
/* --bd-gap:1px and a --rule ground under the grid: the cells are butted plates
   with a hairline of the tray showing between them, never a grid of cards on a
   background. The board is the page's argument in miniature. */
.prices .bitem{background:var(--paper)}
.prices .bname{font-family:var(--font-display);font-size:18px;font-weight:600;
  letter-spacing:-.022em;color:var(--ink)}
.prices .bdesc{font-size:14.5px;line-height:1.5;color:var(--ink-2)}
.prices .bicon{background:var(--pale);color:var(--accent)}
.prices .btag{border:0;border-radius:0;padding:7px 0 0;margin-top:9px;
  border-top:1px solid var(--rule);font-size:10.5px;letter-spacing:.18em;
  color:var(--ink-3)}
.prices .bprice{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;
  color:var(--accent)}
.prices .bgh{color:var(--accent);letter-spacing:.2em}
.prices .bhook{border-style:solid;border-color:var(--rule);border-radius:0;
  background:var(--pale);margin:0 0 1px}
.prices .bhook-l{color:var(--accent)}
.prices .bhook-t{max-width:58ch;color:var(--ink-2)}
.bfoot{margin:clamp(18px,2.4vw,28px) 0 0;font-size:14px;color:var(--ink-3);max-width:58ch}
@media(min-width:820px){.prices .board{--bd-cell-pad:22px 20px;--bd-price:30px}}

/* ================================================================= THE SHOP ==
   The story, on ink, and then the wall lying on top of it. */
/* THE LEDGER. One object, used twice: proof.rows on the rating plate and
   story.rows on the shop plate. Rows butted against a hairline, no cards, no
   icons, no boxes — the same geometry as the board and the accordion, so all
   three dense blocks on the page are recognisably the same material. It takes
   its rule colour from whichever plate it is lying on and nothing else changes. */
.ledger{margin:clamp(20px,3vw,34px) 0 0;border-top:1px solid var(--rule)}
.led{display:grid;gap:2px;padding:15px 0;border-bottom:1px solid var(--rule)}
.led h3{color:var(--ink)}
.led p{margin:0;font-size:14.5px;line-height:1.55;color:var(--ink-2);max-width:62ch}
.pl-ink .ledger,.pl-ink .led{border-color:var(--rule-on-ink)}
.pl-ink .led h3{color:var(--band-ink)}
.pl-ink .led p{color:var(--band-ink-2)}
@media(min-width:820px){
  .led{grid-template-columns:.9fr 1.5fr;gap:8px 34px;align-items:baseline;padding:17px 0}
}
/* on the rating plate the ledger is the reasons, and it sits BETWEEN the claim and
   the figure: claim, reasons, then the one number that is not ours to write. */
.rated .ledger{margin-bottom:clamp(4px,1vw,10px)}

/* THE WALL. The shared mosaic (20 hardcoded, pre-validated layouts) as a plate in
   its own right: the pin loses its padding entirely so the wall runs edge to edge
   with only its 5px internal gutter, and the plate below laps up over its bottom
   row. The template lays out nothing here and must not try to — the wall is the
   same component on every skeleton, skinned by four tokens. */
.wall{--mos-gap:5px;--mos-gap-sm:4px;--mos-text-bg:var(--paper);--mos-sm-ar:1/1}
.wall .pin{max-width:none;padding:0}

/* ======================================================== WHAT PEOPLE ASK ====
   A paper plate, and the questions are butted rows with a hairline between them —
   the board's geometry at text scale, so the two dense sections rhyme. */
.asklist{margin-top:clamp(18px,2.6vw,30px);border-top:1px solid var(--rule)}
.ask{border-bottom:1px solid var(--rule)}
.ask summary{display:flex;align-items:center;justify-content:space-between;gap:18px;
  padding:16px 0;min-height:56px;cursor:pointer;list-style:none;
  font-family:var(--font-display);font-size:17px;font-weight:600;letter-spacing:-.018em;
  color:var(--ink)}
.ask summary::-webkit-details-marker{display:none}
.ask summary:hover{color:var(--accent)}
.ask summary i{flex:0 0 auto;width:11px;height:11px;position:relative}
.ask summary i::before,.ask summary i::after{content:"";position:absolute;
  background:var(--accent);transition:opacity .18s ease}
.ask summary i::before{left:0;top:5px;width:11px;height:1px}
.ask summary i::after{left:5px;top:0;width:1px;height:11px}
.ask[open] summary i::after{opacity:0}
.ask p{margin:0;padding:0 0 20px;font-size:15px;line-height:1.6;color:var(--ink-2);max-width:68ch}
.askcall{margin:16px 0 0;font-size:14.5px;line-height:1.6;color:var(--ink-3);max-width:36ch}
.askcall a{display:inline-flex;align-items:center;min-height:44px;
  font-family:var(--font-display);font-size:21px;font-weight:800;letter-spacing:-.03em;
  color:var(--accent);text-decoration:none}
.askcall a:hover{color:var(--ink)}
@media(min-width:940px){
  .asks .pin{display:grid;grid-template-columns:.8fr 1.2fr;gap:0 clamp(34px,5vw,72px);
    align-items:start}
  .asklist{margin-top:0}
}

/* ====================================================== HOURS AND THE DOOR ===
   The week as a ruled ledger down the ink plate: day on the left, time on the
   right, a hairline between every row and no box around any of it. */
.hrs{margin:clamp(20px,3vw,34px) 0 0;border-top:1px solid var(--rule-on-ink)}
.hrow{display:flex;justify-content:space-between;align-items:baseline;gap:18px;
  padding:12px 0;border-bottom:1px solid var(--rule-on-ink);font-size:15px}
.hd{color:var(--band-ink-2)}
.ht{color:var(--band-ink);font-variant-numeric:tabular-nums;font-weight:500}
/* hours[] IS OPTIONAL. Absent, this plate does not print a one-row table — the
   line the business DID publish is set at display scale as the plate's own
   statement, and the ask for the rest of the week sits under a hairline beneath
   it. Same object, one row instead of five. */
.hrow.hrow-line{display:block;padding:0 0 18px}
.hrow-line .hd{display:block;font-size:11px;font-weight:700;letter-spacing:.24em;
  text-transform:uppercase;color:var(--accent-2);padding-top:14px}
.hrow-line .ht{display:block;margin-top:8px;font-family:var(--font-display);
  font-size:clamp(21px,3vw,30px);font-weight:800;letter-spacing:-.03em;line-height:1.1}
.hask{display:block;margin-top:12px;padding-top:12px;font-size:14px;line-height:1.55;
  color:var(--band-ink-2);max-width:48ch;border-top:1px solid var(--rule-on-ink)}
.doors{margin:clamp(20px,3vw,32px) 0 0;display:flex;flex-direction:column;gap:5px}
.dbig{display:inline-flex;align-items:center;align-self:flex-start;min-height:44px;
  font-family:var(--font-display);font-size:clamp(19px,2.6vw,26px);font-weight:800;
  letter-spacing:-.03em;color:var(--accent-2);text-decoration:none;
  border-bottom:1px solid transparent}
.dbig:hover{border-bottom-color:var(--accent-2)}
.doors address{font-style:normal;font-size:15px;line-height:1.5;color:var(--band-ink-2);margin-top:6px}
.maplink{display:inline-flex;align-items:center;align-self:flex-start;min-height:44px;
  font-size:12px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;
  color:var(--band-ink);text-decoration:none;border-bottom:1px solid var(--accent-2)}
.maplink:hover{color:var(--accent-2)}
.area{margin-top:clamp(22px,3vw,32px);padding-top:18px;border-top:1px solid var(--rule-on-ink)}
.area h3{color:var(--band-ink)}
.area p{margin:8px 0 0;font-size:14.5px;line-height:1.55;color:var(--band-ink-2);max-width:56ch}
.towns{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px}
.towns span{font-size:12px;letter-spacing:.06em;color:var(--band-ink-2);
  padding:6px 10px;background:color-mix(in srgb, var(--band-ink) 9%, transparent)}
@media(min-width:940px){
  .hours .pin{display:grid;grid-template-columns:1fr 1fr;gap:0 clamp(34px,5vw,72px);
    align-items:start}
  .hours .hhead{grid-column:1 / -1}
  .hrs{margin-top:clamp(22px,3vw,34px)}
  .doors{margin-top:clamp(22px,3vw,34px)}
  .area{grid-column:1 / -1}
}

/* ================================================================ THE CLOSE ==
   A pale plate, and the form is the last thing in the stack. The fields are paper
   set into it — the same relationship as the whole page, one plate down. */
.close .lead{margin-bottom:clamp(20px,3vw,30px)}
.cgrid{display:grid;gap:clamp(24px,3.4vw,44px)}
.close .field label{font-size:11px;letter-spacing:.2em;color:var(--ink-3)}
.close .field input,.close .field select,.close .field textarea{
  background:var(--paper);border-color:var(--rule);color:var(--ink);border-radius:0}
.close .field input:focus,.close .field select:focus,.close .field textarea:focus{
  border-color:var(--accent)}
.close .btn-primary{width:100%}
.fineprint{margin:14px 0 0;font-size:12.5px;line-height:1.55;color:var(--ink-3);max-width:52ch}
.close .thanks{background:var(--paper);border:0;border-radius:0;box-shadow:var(--seam-2);padding:26px 22px}
.close .thanks h3{font-family:var(--font-display);font-size:23px;color:var(--ink)}
.close .thanks p{margin:8px 0 0;color:var(--ink-2)}
.cnote{margin:clamp(18px,2.6vw,26px) 0 0;padding-top:16px;border-top:1px solid var(--rule);
  font-size:14.5px;line-height:1.55;color:var(--ink-2);max-width:44ch}
.cnote a{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--rule)}
.cnote a:hover{border-bottom-color:var(--accent)}
@media(min-width:940px){
  .cgrid{grid-template-columns:1fr 1.05fr;gap:clamp(40px,5vw,80px);align-items:start}
  .close .btn-primary{width:auto}
}

/* =================================================================== FOOTER ==
   The last plate. Paper, lapping the close, and the Pittsburgh IT blurb below it
   laps the footer in turn — so the stack runs all the way off the bottom of the
   document instead of stopping at a rule. */
footer{position:relative;z-index:2;margin-top:calc(var(--lap) * -1);
  background:var(--paper);color:var(--ink);box-shadow:var(--seam)}
.fpad{width:100%;max-width:var(--measure);margin:0 auto;
  padding:clamp(30px,4vw,54px) var(--pad) clamp(24px,3vw,34px)}
.fgrid{display:grid;gap:26px}
.fbrand .mono.big{display:inline-flex;align-items:center;justify-content:center;
  width:42px;height:42px;font-size:16px;background:var(--band);color:var(--accent-2)}
.fname{font-family:var(--font-display);font-size:21px;font-weight:800;
  letter-spacing:-.03em;margin:13px 0 6px}
.fabout{margin:0;font-size:14.5px;line-height:1.55;color:var(--ink-2);max-width:42ch}
.fh{font-family:var(--font-body);font-size:11px;font-weight:700;letter-spacing:.24em;
  text-transform:uppercase;color:var(--accent);margin:0 0 10px}
.fcol{display:flex;flex-direction:column;gap:4px}
.fcol a,.fplain{color:var(--ink-2);font-size:14.5px;text-decoration:none}
.fcol a{display:inline-flex;align-items:center;min-height:44px}
.fcol a:hover{color:var(--accent)}
.fcol address{font-style:normal;font-size:14.5px;color:var(--ink-3)}
.fnav{display:flex;flex-wrap:wrap;gap:4px 22px;margin-top:28px;padding-top:16px;
  border-top:1px solid var(--rule)}
.fnav a{display:inline-flex;align-items:center;min-height:44px;color:var(--ink-3);
  font-size:12px;font-weight:700;letter-spacing:.16em;text-transform:uppercase}
.fnav a:hover{color:var(--ink)}
.fbase{display:flex;flex-wrap:wrap;justify-content:space-between;gap:6px 22px;
  margin-top:18px;font-size:12.5px;color:var(--ink-3)}
.credit a{color:var(--ink-2)}
@media(min-width:760px){
  .fgrid{grid-template-columns:1.5fr 1fr 1fr;gap:36px}
  .fcol a,.fnav a{min-height:28px}
}
/* the shared Pittsburgh IT blurb is the final plate in the stack. Its colours are
   hardcoded in shared/pghnote.css and stay ours; only the lap is ours to add, and
   that sheet loads after this one so it can only be given properties it does not
   set itself. */
.pghblurb{position:relative;z-index:3;margin-top:calc(var(--lap) * -1);
  box-shadow:var(--seam)}

/* ====================================================================== 404 ==
   Short, and in the template's own language: one band, one ink plate lying on it,
   two ways out. */
.nf{display:flex;flex-direction:column;padding-top:calc(var(--navh) + var(--tpl-h,0px))}
.nfnum{display:block;font-family:var(--font-display);font-size:clamp(15px,2vw,20px);
  font-weight:800;letter-spacing:.3em;color:var(--accent-2);margin:0 0 12px}
.nf h1{max-width:14ch}
.nf .lead{margin:16px 0 26px}

/* ================================================================ THE SHUNT ==
   The mechanical signature, and the only motion in this file. Observed elements
   are full-size plates and bands: nothing here is ever scaled or clipped to zero,
   so every observer has real area to fire against.

   Fast in, and the curve has no tail — cubic-bezier(.9,.04,.22,1) is almost all
   of its travel in the last third and then stops dead. A plate that eases out is
   a plate that floats. */
html.js [data-shunt]{transform:translateY(var(--shunt))}
html.js [data-shunt].seated{transform:translateY(0);
  transition:transform 340ms cubic-bezier(.9,.04,.22,1)}
/* THE LAST PLATE SITS ONE TRAVEL LOW UNTIL IT LANDS, and a transform counts toward
   scrollable overflow, so the page carried an extra --shunt of dead scroll at the
   bottom — a second scrollbar's worth of nothing. A margin cannot cancel it
   (transform overflow is measured absolutely, not from the margin box); the stack
   has to CLIP. `clip` and not `hidden`, because `hidden` would make main a scroll
   container and put the second scrollbar straight back. */
main{overflow:clip}
@media(prefers-reduced-motion:reduce){
  html.js [data-shunt],html.js [data-shunt].seated{transform:none;transition:none}
}

/* THE TYPE DOES NOT ALSO SLIDE. base.css's reveal is opacity + an 18px rise, and
   the rise fights the shunt: the plate travels 110px and its contents would travel
   another 18 inside it, which reads as slop rather than as one object landing. So
   inside a plate the reveal keeps its FADE and loses its transform — the plate
   carries all of the travel, the words only arrive. The guarded declaration
   (`html.js .reveal{opacity:0}`) is untouched; this is a more specific selector
   setting a different property. */
html.js .plate .reveal{transform:none}

/* ====================================================== VARIATION AXES ========
   A template is something a build DRAWS FROM, not a stamp: without this, 500
   customers get 500 identical pages. Each axis is independent and is chosen from a
   hash of the client (var_classes), and none of them touches the argument — the
   lap, the seam and the three-cycle of grounds are the same on every build. */
/* how much of the first screen the picture gets */
.v-band-tall .heroband{min-height:clamp(210px,34svh,460px)}
.v-band-slim .heroband{min-height:clamp(120px,15svh,240px)}
/* how far a plate travels to its seat */
.v-shunt-short{--shunt:clamp(34px,5vh,64px)}
.v-shunt-long{--shunt:clamp(80px,13vh,150px)}
/* how deep the overlap reads */
.v-lap-deep{--lap:clamp(34px,6.2vw,96px)}


/* ==================================================== 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} }


/* ============================================ THE PITTSBURGH IT BLURB (bottom) ===
   Jon, 2026-08-14. First the three how-it-works boxes were flagged - "this isn't an
   actual part of their site" - then: "wait, just remove it... maybe just put a blurb
   all the way at the bottom saying what those 3 boxes say, and put it in our branding."

   So the three boxes are GONE from every skeleton, and what they said is now one
   branded block below the footer. It is the only place on the page that is Pittsburgh
   IT speaking rather than the shop, and it looks like it: our gold rule, our name, our
   contact. A prospect can tell in one glance which words are theirs and which are ours.

   Shared, and loaded after every template sheet, so it cannot drift per skeleton. */

.pghblurb{
  /* PITTSBURGH IT GOLD, hardcoded on purpose. Using var(--accent-2) inherited the
     CLIENT's colour, so the block that is supposed to read as US came out in THEIR
     brand - Roberto's printed it orange. Nothing in here may take a client token. */
  border-top:3px solid #e3c05a;
  background:#0E0D0C;color:#B8AE9E;
  padding:26px 0 30px;font-size:14px;line-height:1.62}
.pghblurb .pgb{max-width:min(1160px,92vw);margin:0 auto}
.pghblurb .pgb-mark{
  margin:0 0 7px;font-size:11px;font-weight:700;letter-spacing:.16em;
  text-transform:uppercase;color:#e3c05a}
.pghblurb .pgb-body{margin:0;max-width:64ch;color:#CFC6B8}
.pghblurb .pgb-body b{color:#F0EAE0;font-weight:600}
.pghblurb .pgb-line{
  margin:13px 0 0;font-size:13px;color:#8E8677;
  display:flex;gap:9px;flex-wrap:wrap;align-items:baseline}
.pghblurb .pgb-line a{color:#e3c05a;text-decoration:none;border-bottom:1px solid rgba(227,192,90,.4)}
.pghblurb .pgb-line a:hover{border-bottom-color:#e3c05a}
.pghblurb .pgb-sep{opacity:.45}
@media(max-width:699px){
  .pghblurb{padding:22px 0 26px;font-size:13.5px}
  .pghblurb .pgb-line{gap:6px}
}
