/* ============================================================================
   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:#F6F4F1;
  --surface-2:#FFFFFF;
  --band:#C0451B;
  --band-ink:#FFFFFF;
  --band-ink-2:#FFF1EB;
  --accent-on-band:#FFFFFF;
  --ink:#16171A;
  --ink-2:#4C4E55;
  --ink-3:#63656C;
  --accent:#C0451B;
  --accent-2:#9C3512;
  --accent-ink:#FFFFFF;
  --line:#DFDBD4;
  --maxw:1200px;
  --gutter:22px;
  --radius:0px;
  --accent-a12:rgba(192,69,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:#e3d7ce;
  --board-cell:#efeae4;
  --board-line:rgba(192,69,27,.30);
  --board-accent:#9C3512;
  --board-glow:rgba(192,69,27,.13);
  --board-shadow-1:rgba(22,23,26,.08);
  --board-shadow-2:rgba(22,23,26,.26);
  --font-display:'Bodoni Moda',Georgia,'Times New Roman',serif;
  --font-body:'Manrope',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:700;letter-spacing:-.018em}
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(192,69,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:#9C3512;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-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 AWNING — a shopfront awning.

   ONE SOLID SLAB of the shop's own colour hangs over the edge of the thing
   beneath it, and it is the ONLY element on this page permitted to overlap
   anything. Every other join in the file is a butt join: flat colour meets flat
   colour, zero radius, no shadow, no soft edge. That is why the slab reads as an
   awning instead of as a card.

   THE STRADDLE happens exactly three times and in three different geometries:
     1  the hero slab hangs 72px DOWN past the photograph into the wall
     2  the priced card rises 72px UP out of the accent field
     3  the closing slab hangs 72px UP into the section above it
   --hang is that distance, declared once. Change it and all three move together.

   MOBILE FIRST. Every rule here is the phone rule; breakpoints only add.
   ==========================================================================

   ---------------------------------------------------------------------------
   CLIENT SWAP BLOCK — the only lines that change per client live in
   template.json `defaults.brand` and `design`. base.css turns them into the
   token names below and nothing in this file hard-codes a colour or a face:

     --surface   the ground        off-white   #F6F4F1
     --surface-2 the second surface  white     #FFFFFF
     --ink / --ink-2 / --ink-3      the type
     --accent / --accent-2          the ONE confident colour  #C0451B
     --band / --band-ink / --band-ink-2 / --accent-on-band   the slab and its type
     --line                         every hairline
     --font-display  Bodoni Moda 700   --font-body  Manrope
     --radius 0px    --maxw 1200px

   Swap those thirteen values and the page is the next client's. Everything
   below the token block is structure.
   --------------------------------------------------------------------------- */

:root{
  --hang:56px;              /* the straddle, phone */
  --grade:saturate(1.04) contrast(1.04);
  --nav-gap:30px;
  --rule:1px solid var(--line);
}
@media(min-width:860px){:root{--hang:72px}}

/* ONE grade, one rule, every photograph on the page. It ADDS saturation: nothing
   in the house is desaturated and nothing is duotoned. There is no second filter
   anywhere in this file and there must never be one. */
.heroshot img,.mos-t img,.mframe img{filter:var(--grade)}

/* ---------------------------------------------------------------- type ----- */
body{background:var(--surface)}
h1{font-size:clamp(40px,7.4vw,72px);line-height:1.02;letter-spacing:-.022em;margin:0}
.h-lg{font-size:clamp(29px,4.6vw,46px);line-height:1.06;letter-spacing:-.018em}
.h-md{font-size:clamp(25px,3.6vw,34px);line-height:1.1;letter-spacing:-.014em}
/* Bodoni is a didone: gorgeous at display size, thin and fragile below about
   24px. Every heading under that size drops to the workhorse face on purpose. */
h3{font-family:var(--font-body);font-size:17.5px;line-height:1.3;letter-spacing:-.005em;font-weight:700}
.measure{max-width:58ch}
.elab{font-family:var(--font-body);font-size:11.5px;font-weight:800;letter-spacing:.19em;
  text-transform:uppercase;color:var(--accent);margin:0 0 14px}
.elab.onband{color:var(--accent-on-band)}
.onband{color:var(--band-ink)}
p.lead.onband{color:var(--band-ink-2)}
.mono{font-family:var(--font-display);font-weight:700;letter-spacing:.03em}

/* ------------------------------------------------------------ preview bar -- */
.tplbar{position:fixed;top:0;left:0;right:0;z-index:130;background:var(--ink);
  color:#D9D6D0;font-size:12.5px;line-height:1.45;padding:8px var(--gutter);text-align:center}
.tplbar b{color:#fff}
:root:has(.tplbar){--tpl-h:58px}
@media(min-width:760px){:root:has(.tplbar){--tpl-h:36px}}
#nav{padding-top:var(--tpl-h,0px)}

/* -------------------------------------------------------------------- nav -- */
#nav.scrolled{background:var(--surface);box-shadow:0 1px 0 var(--line)}
.nav-inner{gap:12px;flex-wrap:wrap}
.brand{display:inline-flex;align-items:center;gap:11px;text-decoration:none;color:var(--ink);min-height:44px}
.brand .mono{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;
  font-size:14px;color:var(--band-ink);background:var(--accent)}
.brandmark{width:40px;height:40px;object-fit:contain}
.brandtype{font-family:var(--font-display);font-size:20px;font-weight:700;letter-spacing:-.015em;white-space:nowrap}
.navtoggle .bars{display:block;width:22px}
.navtoggle .bars i{display:block;height:2px;margin:5px 0;background:var(--ink)}
.navtoggle .bars i:nth-child(2){width:14px}
.nav-links{flex:1 0 100%;flex-direction:column;align-items:flex-start;gap:2px;width:100%;
  padding:6px 0 16px;margin-top:6px;border-top:var(--rule)}
.nav-links a{color:var(--ink-2);text-decoration:none;font-size:16px;font-weight:600;
  padding:11px 0;min-height:44px;display:flex;align-items:center;width:100%}
.nav-links a:hover{color:var(--accent)}
.drawhours{color:var(--ink-3);font-size:11.5px;letter-spacing:.16em;text-transform:uppercase;margin:8px 0 4px}
.drawtel{color:var(--accent)!important;font-family:var(--font-display);font-size:21px!important;font-weight:700}
.navcta{background:var(--accent);color:var(--band-ink)!important;padding:0 22px!important;
  justify-content:center!important;font-weight:700;margin-top:8px}
html.menu-open #nav{background:var(--surface)}
/* the masthead sits over the photograph until you scroll, so it starts in the
   slab's light ink and swaps to page ink the moment there is a ground under it */
#nav:not(.scrolled) .brandtype,#nav:not(.scrolled) .navtoggle{color:#fff}
#nav:not(.scrolled) .navtoggle .bars i{background:#fff}
html.menu-open #nav .brandtype,html.menu-open #nav .navtoggle{color:var(--ink)}
html.menu-open #nav .navtoggle .bars i{background:var(--ink)}
/* THE MASTHEAD SITS ON AN UNKNOWN PHOTOGRAPH. A spec site is sent to a prospect
   whose picture we have never seen, so the menu cannot depend on that picture
   having a dark corner. It gets a shadow on the TYPE rather than a scrim over the
   image: the photograph keeps its own exposure, which is the rule on this page,
   and the words stay legible over a bright one. Found on the render - the links
   landed on a chrome highlight and disappeared. */
#nav:not(.scrolled) .brandtype,#nav:not(.scrolled) .nav-links a,
#nav:not(.scrolled) .navtoggle{text-shadow:0 1px 4px rgba(0,0,0,.55)}
#nav:not(.scrolled) .nav-links a.navcta{text-shadow:none}

@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;font-size:14px;font-weight:600;letter-spacing:.01em;color:#fff}
  #nav.scrolled .nav-links a{color:var(--ink-2)}
  /* (0,2,0) DELIBERATELY. `.nav-links a{display:flex}` above is (0,1,1), so a bare
     `.drawtel{display:none}` LOSES to it and the drawer phone number printed in the
     desktop bar, in the accent, over the hero photograph. Found on the render. */
  .nav-links .drawhours,.nav-links .drawtel{display:none}
  .navcta{padding:0 20px!important;min-height:42px;color:var(--band-ink)!important}
  #nav.scrolled .nav-links a.navcta{color:var(--band-ink)!important}
}

/* ------------------------------------------------------------------- hero --
   THE PHOTOGRAPH IS NOT SCRIMMED. There is no veil, no wash and no gradient over
   it anywhere in this file: it stops where the slab starts and keeps its own
   exposure everywhere else. The type never sits on the picture, so nothing here
   depends on how bright the client's photograph happens to be — which is the
   whole reason a spec site can take a photo we have not seen.

   On the phone the slab OVERLAPS UP into the picture as well as down out of it,
   which pulls the headline into the top third of the first screen. */
.hero{position:relative;z-index:2;background:var(--surface)}
.heroshot{position:relative;height:44svh;min-height:270px;overflow:hidden;background:var(--ink)}
.heroshot picture{display:block;height:100%}
.heroshot img{width:100%;height:100%;object-fit:cover;object-position:50% 34%}
.herowrap{position:relative;z-index:2;margin-top:-15svh}
.awn{background:var(--accent);color:var(--band-ink);padding:24px 22px 28px;margin-bottom:calc(var(--hang) * -1)}
.akick{font-family:var(--font-body);font-size:11.5px;font-weight:800;letter-spacing:.19em;
  text-transform:uppercase;color:var(--accent-on-band);margin:0 0 12px}
.awn h1{color:var(--band-ink);line-height:1.24}
/* THE DRAWN MOMENT — THE NOTCH, and it is a subtraction. The accent is removed
   from behind the emphasised phrase down to the page ground and the words are set
   in page ink on it: the slab is a solid thing with an edge, and here you are
   looking through it. box-decoration-break:clone carries the notch line by line,
   so a phrase that wraps to three lines is three notches and never one full-width
   bar under the last line (DESIGN-FLOOR D1).
   It is a subtraction rather than an ink-on-accent inversion because the
   arithmetic forbids the inversion: #C0451B reaches only 4.11:1 against pure
   black, so no dark colour at all can hold 4.5:1 on it. The notch gets 16.33:1. */
/* The line-height on .awn h1 is 1.24 and NOT the 1.02 the rest of the page uses,
   because an inline background box is as tall as the font's own ascent plus descent
   - at 1.02 the notches for line two and line three printed straight through the
   line above them. Vertical padding is zero for the same reason. Found on the
   render at 1440 and at 390; the file looked correct. */
.awn .h1b{font-style:normal;color:var(--ink);background:var(--surface);
  box-decoration-break:clone;-webkit-box-decoration-break:clone;
  padding:0 .14em;margin-left:-.14em}
.asub{margin:16px 0 22px;font-size:17px;line-height:1.55;color:var(--band-ink-2);max-width:46ch}
.awn .btnrow{margin-bottom:20px}
.awn .btn-primary{background:var(--ink);color:#fff;border-color:var(--ink)}
.awn .btn-primary:hover{background:#000;border-color:#000}
.awn .btn-ghost{background:transparent;color:var(--band-ink);border-color:rgba(255,255,255,.55)}
.awn .btn-ghost:hover{background:rgba(255,255,255,.12);border-color:#fff}
.astand{display:flex;flex-wrap:wrap;gap:4px 16px;margin:0;padding-top:16px;
  border-top:1px solid rgba(255,255,255,.28);font-size:13.5px;color:var(--band-ink-2)}
.astand b{color:var(--band-ink);font-weight:700}

@media(min-width:860px){
  .hero{display:flex;align-items:flex-end;min-height:clamp(560px,86svh,820px)}
  .heroshot{position:absolute;inset:0;height:auto;min-height:0}
  .heroshot img{object-position:62% 42%}
  .herowrap{margin-top:0;width:100%}
  .awn{max-width:600px;padding:38px 40px 40px}
  .asub{font-size:18.5px}
}
@media(min-width:1180px){.awn{max-width:640px;padding:44px 48px 46px}}

/* ------------------------------------------------------------------- wall --
   The only element on the page that touches both edges of the viewport. */
.wall{padding-top:calc(var(--hang) + 44px)}
.shead{margin-bottom:26px}
.ourplates{margin:0 0 20px;font-size:14.5px;line-height:1.55;color:var(--ink-3);max-width:64ch}
.wallbleed{width:100%}
/* THE SHARED WALL'S ROW HEIGHT is clamp(96px,13.5vw,190px), which is correct for a wall
   inside a 1180px container and letterboxes the moment the wall is bled to the viewport:
   at 1425 it made a 948x190 tile, five to one. Bleeding it is this template's decision,
   so paying for it is this template's job. The component is untouched; only the row
   height it exposes is re-declared, and only for the bled wall. */
.wallbleed .mos{--mos-gap:6px;--mos-gap-sm:5px;--mos-text-bg:var(--ink);
  grid-auto-rows:clamp(120px,20vw,320px);
  grid-template-rows:repeat(var(--mos-rows,3),clamp(120px,20vw,320px))}
.crew{display:grid;gap:20px;margin-top:34px;grid-template-columns:repeat(2,minmax(0,1fr))}
.mate h3{margin:12px 0 2px}
.mate p{margin:0;font-size:14px;color:var(--ink-3)}
.mframe{display:block;overflow:hidden;background:var(--line)}
.mframe img{display:block;width:100%;height:auto}
@media(min-width:760px){
  .wall{padding-top:calc(var(--hang) + 64px)}
  .shead{margin-bottom:34px}
  .crew{grid-template-columns:repeat(4,minmax(0,1fr));gap:24px}
}

/* ------------------------------------------------------------------ facts --
   The quietest thing in the file, and deliberately the narrowest. It sits
   between the two loudest sections, because a page that never drops its voice
   cannot raise it. */
.facts{padding-top:0}
.factlist{display:grid;gap:0;max-width:820px;border-top:var(--rule)}
.fact{padding:22px 0;border-bottom:var(--rule)}
.fact h2,.fact h3{margin:0 0 5px;font-family:var(--font-body);font-size:17.5px;font-weight:700;
  letter-spacing:-.005em;color:var(--ink)}
.fact p{margin:0;font-size:15.5px;line-height:1.55;color:var(--ink-2)}
@media(min-width:760px){
  .factlist{grid-template-columns:repeat(3,minmax(0,1fr));column-gap:38px;border-bottom:var(--rule)}
  .fact{border-bottom:0;padding:26px 0}
}
/* The 820px cap keeps the strip narrow where narrow is right, and gets out of the way
   where it is not: at 1440 it was making three 248px columns and a five-line paragraph
   in each. Measured on the render. */
@media(min-width:1080px){.factlist{max-width:none;column-gap:56px}}

/* ------------------------------------------------------------------- word --
   Full-bleed accent. The review is set at display size because it is somebody
   else's sentence about them, and that is worth more than ours. This field does
   NOT stop at the section boundary — .counter's slab continues it, and the white
   priced card rises out of the join. STRADDLE 2. */
.word{background:var(--accent);padding-bottom:0}
.whead{max-width:62ch}
.say{margin:34px 0 0;padding:0}
.say blockquote{margin:0;font-family:var(--font-display);font-weight:400;
  font-size:clamp(24px,3.6vw,40px);line-height:1.24;letter-spacing:-.012em;color:var(--band-ink);max-width:20ch}
.say blockquote::before{content:"\201C"}
.say blockquote::after{content:"\201D"}
.say figcaption{margin-top:16px;font-size:11.5px;font-weight:800;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent-on-band)}

/* THE GOOGLE RATING — the shared component, skinned. Never a stat in a card: it
   is the only hard checkable fact on the page, so it gets display scale and air.
   The official Google Maps asset is the WHITE one and it is shipped unmodified,
   which is why the figure sits on the accent field rather than on the ground. */
.rating{margin-top:40px;display:block}
.rating-fig{position:relative;padding:0 0 8px}
.rstars{position:relative;display:inline-block;font-size:clamp(22px,3vw,34px);
  letter-spacing:.14em;line-height:1;white-space:nowrap}
.rstars i{font-style:normal;display:block}
.rs-off{color:rgba(255,255,255,.34)}
.rs-on{position:absolute;inset:0;overflow:hidden;width:var(--fill,100%);color:#fff}
.rnum{display:block;font-family:var(--font-display);font-weight:700;letter-spacing:-.05em;
  font-size:clamp(96px,18vw,220px);line-height:.82;color:var(--band-ink);margin-top:8px}
.rsay{margin:14px 0 0;font-family:var(--font-display);font-size:clamp(19px,2.2vw,28px);
  line-height:1.26;color:var(--band-ink-2);max-width:24ch}
.rsay .rcount{color:var(--band-ink);font-weight:700}
.gmpill{display:inline-flex;align-items:center;gap:9px;margin:18px 0 0;padding:9px 15px 9px 13px;
  text-decoration:none;background:rgba(0,0,0,.34);border:1px solid rgba(255,255,255,.3)}
.gmpill:hover{background:rgba(0,0,0,.5)}
.gmpill img{display:block;height:17px;width:auto}
.gmpill .gmgo{font:700 13px/1 var(--font-body);color:#fff;white-space:nowrap}
.rquotes{display:grid;gap:12px;margin-top:26px;max-width:660px}
.rq{margin:0;padding:20px 22px;background:rgba(0,0,0,.2)}
.rq blockquote{margin:0;font-family:var(--font-display);font-size:18px;line-height:1.42;color:var(--band-ink)}
.rq figcaption{margin-top:9px;font-size:11.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--accent-on-band)}
.rq-empty{background:none;border:1px dashed rgba(255,255,255,.42)}
.rq-empty blockquote{color:var(--band-ink-2);font-size:16px}
.word .rating{padding-bottom:8px}
@media(min-width:900px){
  .rating-fig{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:clamp(36px,5vw,72px);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:16ch}
  .gmpill{grid-column:2;grid-row:3;justify-self:start}
}

/* ---------------------------------------------------------------- counter --
   The accent field carries on through this heading, and the white card rises out
   of it. .pbody is pulled up by --hang and given its own stacking context so the
   card sits over the colour; nothing here is positioned, so nothing here can
   push the document sideways at 390. */
.counter{padding-top:0}
.pslab{background:var(--accent);padding:52px 0 calc(var(--hang) + 30px)}
.pslab .shead{margin-bottom:0}
.pbody{position:relative;z-index:2;margin-top:calc(var(--hang) * -1)}
.pcard{background:var(--surface-2);border:var(--rule)}
/* THE BOARD — the shared services component, skinned with its own tokens and
   never forked. Zero radius, hairline cell joins, and the accent inside the card
   is left to the icon plate: the accent's loud moments on this page are the three
   slabs, and a sixth orange bar in here would take one of them away. */
.pcard .board{--bd-gap:0;--bd-pad:0;--bd-radius:0;--bd-cell-pad:22px 20px;--bd-price:24px;
  background:transparent;border:0;box-shadow:none}
.pcard .bitem{border-radius:0;background:transparent;border-top:var(--rule)}
.pcard .bitem::before{height:1px;background:var(--line)}
.pcard .bname{font-size:17px}
.pcard .bprice{font-family:var(--font-display);font-weight:700;color:var(--ink)}
.pcard .btag{border-radius:0;border-color:var(--line);color:var(--ink-3)}
.pcard .bicon{border-radius:0}
.pcard .bhook{margin:0;border-radius:0;border-style:dashed;padding:18px 20px}
.pcard .bgh{padding:0 20px}
.pfoot{margin:22px 0 0;font-size:14.5px;color:var(--ink-3);max-width:64ch}
@media(min-width:760px){
  .pslab{padding:70px 0 calc(var(--hang) + 40px)}
  .pcard .board{--bd-cell-pad:26px 26px;--bd-price:28px}
}
@media(min-width:820px){
  /* two cells per row, so the hairline join has to run in both directions */
  .pcard .bitem{border-left:var(--rule)}
  .pcard .bitem:nth-child(odd){border-left:0}
}

/* ------------------------------------------------------------------- asks -- */
.asks{padding-top:56px}
.askgrid{display:grid;gap:26px}
.askcall{margin:0;font-size:15px;color:var(--ink-2)}
.askcall a{color:var(--accent);font-weight:700;text-decoration:none;border-bottom:1px solid currentColor}
.asklist{border-top:var(--rule)}
.ask{border-bottom:var(--rule)}
.ask summary{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;
  padding:18px 0;cursor:pointer;list-style:none;font-weight:700;font-size:16.5px;color:var(--ink);min-height:44px}
.ask summary::-webkit-details-marker{display:none}
.ask summary i{flex:none;width:14px;height:14px;margin-top:6px;position:relative}
.ask summary i::before,.ask summary i::after{content:"";position:absolute;background:var(--accent)}
.ask summary i::before{inset:6px 0 auto 0;height:2px}
.ask summary i::after{inset:0 6px 0 auto;width:2px;transition:opacity .2s ease}
.ask[open] summary i::after{opacity:0}
.ask p{margin:0 0 18px;font-size:15.5px;line-height:1.6;color:var(--ink-2);max-width:66ch}
@media(min-width:900px){.askgrid{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);gap:48px}}

/* ------------------------------------------------------------------ close --
   STRADDLE 3, and the last one: the slab hangs UP into the section above it. */
.close{padding-top:0}
.cslab{position:relative;z-index:2;margin-top:calc(var(--hang) * -1);background:var(--accent);
  color:var(--band-ink);padding:32px 24px 34px}
.cgrid{display:grid;gap:34px;margin-top:38px}
.week{border-top:var(--rule)}
.wrow{display:flex;justify-content:space-between;gap:18px;padding:13px 0;border-bottom:var(--rule);font-size:15.5px}
.wd{color:var(--ink-2)}
.wt{color:var(--ink);font-weight:700;font-variant-numeric:tabular-nums}
.card{border:var(--rule);padding:22px}
.cardlab{margin:0 0 8px;font-size:11.5px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:var(--accent)}
.cardline{margin:0 0 12px;font-family:var(--font-display);font-size:24px;font-weight:700;color:var(--ink)}
.cardask{margin:0;padding-top:12px;border-top:var(--rule);font-size:14px;line-height:1.55;color:var(--ink-3)}
.crow{display:grid;gap:6px;margin-top:26px}
.cbig{font-family:var(--font-display);font-size:23px;font-weight:700;color:var(--ink);text-decoration:none}
.cbig:hover{color:var(--accent)}
.crow address{font-style:normal;font-size:15.5px;color:var(--ink-2);margin-top:8px}
.carea{margin:12px 0 0;font-size:15px;color:var(--ink-2)}
.clab{display:block;font-size:11.5px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3);margin-bottom:3px}
.area{margin-top:26px;padding-top:22px;border-top:var(--rule)}
.area h3{margin:0 0 6px}
.area p{margin:0 0 10px;font-size:15.5px;color:var(--ink-2)}
.towns{display:flex;flex-wrap:wrap;gap:7px}
.towns span{border:var(--rule);padding:5px 11px;font-size:12.5px;color:var(--ink-2)}
.formpanel{background:var(--surface-2);border:var(--rule);padding:24px}
.formpanel .field label{color:var(--ink-3)}
.formpanel .field input,.formpanel .field select,.formpanel .field textarea{
  background:var(--surface);border-radius:0}
.formpanel .btn-primary{width:100%}
.fineprint{margin:0;font-size:13px;line-height:1.55;color:var(--ink-3)}
.thanks{border-radius:0;background:var(--surface-2)}
@media(min-width:760px){
  .cslab{padding:40px 44px 42px}
  .formpanel{padding:32px}
}
@media(min-width:960px){
  .cgrid{grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);gap:56px}
}

/* ----------------------------------------------------------------- footer -- */
footer{background:var(--ink);color:#CFCBC4;padding:52px 0 34px}
.fgrid{display:grid;gap:30px}
.fbrand .mono.big{display:inline-flex;align-items:center;justify-content:center;width:46px;height:46px;
  font-size:17px;color:var(--band-ink);background:var(--accent)}
.fname{margin:14px 0 6px;font-family:var(--font-display);font-size:22px;font-weight:700;color:#fff}
.fabout{margin:0;font-size:14.5px;line-height:1.6;max-width:44ch}
.fh{font-family:var(--font-body);font-size:11.5px;font-weight:800;letter-spacing:.16em;
  text-transform:uppercase;color:#8E8A83;margin:0 0 12px}
.fcol a,.fcol .fplain{display:block;font-size:15.5px;color:#CFCBC4;margin-bottom:5px}
.fcol a:hover{color:#fff}
.fcol address{font-style:normal;font-size:15.5px;margin-top:6px}
.fnav{display:flex;flex-wrap:wrap;gap:8px 22px;margin-top:36px;padding-top:22px;border-top:1px solid #2E2C2A}
.fnav a{font-size:14.5px;color:#CFCBC4}
.fnav a:hover{color:var(--accent-on-band)}
.fbase{display:flex;flex-wrap:wrap;gap:8px 22px;margin-top:22px;font-size:13px;color:#8E8A83}
.fbase a{color:#CFCBC4}
@media(min-width:760px){.fgrid{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) minmax(0,1fr);gap:40px}}


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