/* ============================================================================
   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:#0B0B0B;
  --surface-2:#151513;
  --band:#FF3B14;
  --band-ink:#0B0B0B;
  --band-ink-2:#2B0C03;
  --accent-on-band:#000000;
  --ink:#FFFFFF;
  --ink-2:#D8D4D0;
  --ink-3:#9A958F;
  --accent:#FF3B14;
  --accent-2:#FF6A45;
  --accent-ink:#0B0B0B;
  --line:#2C2C2A;
  --maxw:1680px;
  --gutter:22px;
  --radius:0px;
  --accent-a12:rgba(255,59,20,.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:#0B0B0B;
  --board-cell:#0B0B0B;
  --board-line:rgba(255,255,255,.22);
  --board-accent:#FF3B14;
  --board-glow:transparent;
  --board-shadow-1:transparent;
  --board-shadow-2:transparent;
  --font-display:'Archivo Black',Impact,'Haettenschweiler','Arial Narrow',sans-serif;
  --font-body:'Archivo',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{margin:0;background:var(--surface);color:var(--ink);
  font-family:var(--font-body);font-size:17px;line-height:1.62;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  overflow-x:hidden}
img,svg,video{max-width:100%;height:auto;display:block}
h1,h2,h3,h4{font-family:var(--font-display);margin:0;line-height:1.08;
  font-weight:400;letter-spacing:-.035em}
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(255,59,20,.42)}
.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:#FF6A45;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:76px}
.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}
}


/* ============================================================================
   02 — THE POSTER.

   A printed sheet, not a web page. The photograph runs edge to edge and the type
   is set DIRECTLY on it. There is NO container, NO card, NO rounded corner and NO
   shadow anywhere in this file — grep it: `border-radius` appears only to remove
   one, and `box-shadow` only as `none`, as an inset rule, or as a printed
   underline. Where a section has no photograph it becomes a solid field of
   vermilion or of near-black, still edge to edge.

   THE GRID IS THE VIEWPORT. --gut is the only horizontal measurement in the
   template; every line on the page begins on it, from the masthead to the last
   form field. Nothing is centred and nothing is boxed. Text columns are set with
   a max-width in ch or in per-cent of the sheet, which is a MEASURE, not a box.

   THE SHUTTER is the signature: two opaque bars meeting at the horizon of a
   full-bleed plate, retracting to the top and bottom edges. Its failsafe is the
   keyframe itself (a 4.2s delay under html.js), so template.js can only ever
   bring the opening forward and a dead script cannot leave a bar shut.

   EVERY SCRIM IS MEASURED — and so is where the type STARTS. The five gradients
   below (hero phone, hero desktop, masthead, bed phone, bed desktop) were computed
   on the real plates against the real geometry: the hero at seven phone and eight
   desktop viewports crossed with four block heights, the masthead with and without
   the demo bar, the beds at fifteen phone and twelve desktop section sizes. Worst
   composited pixel under hero type #67443A, under bed type #4B4B4C, under the
   masthead #2F2F2C. --hero-floor (#6B5148) and --plate-floor (#545454) are declared
   one step brighter than those and proved in contrast_pairs. Change a gradient, a
   padding-top, a type size or a column width and the measurement is void.

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

:root{
  --gut:clamp(18px,4.4vw,64px);
  --nav-h:76px;
  --nav-gap:26px;
  --rule:3px;                     /* the printed rule: over every list, under the footer */
  --hairline:rgba(255,255,255,.20);
  /* the two measured floors. Nothing else on this page may be used as a ground
     for a contrast claim over a photograph. */
  --hero-floor:#6B5148;
  --plate-floor:#545454;
}

/* -------------------------------------------------------------------- type -- */
body{background:var(--surface);color:var(--ink);font-size:16px;line-height:1.55}
h1,h2,h3{text-transform:uppercase;font-weight:400}
/* The headline is sized so the whole hero block still fits a 720px-tall laptop and
   an 844px phone: on the desktop plate the type column is capped at 58% of the
   sheet (the left half is the dark half) and a 10vw headline needed four lines in
   it, which put the buttons 400px below the fold. */
h1{font-size:min(clamp(38px,10vw,72px),5.2vh);line-height:.86;letter-spacing:-.035em;
   margin:0;max-width:15ch}
.h-xl{font-size:clamp(32px,6.4vw,84px);line-height:.92;letter-spacing:-.03em;max-width:16ch}
h3{font-size:clamp(16px,2vw,24px);line-height:1.05;letter-spacing:-.02em}
.lead{font-size:16px;line-height:1.55;color:var(--ink-2)}
.measure{max-width:54ch}

/* THE CHIP. base.css's .eyebrow is small uppercase text in the brand colour; here
   it is a solid rectangle of ink with the words knocked out of it, because
   vermilion TYPE over a photograph cannot hold 4.5:1 and an opaque slab always
   can. Same reason the primary button and the map link are slabs. */
.eyebrow{display:inline-block;margin:0 0 clamp(12px,1.9vh,26px);
  padding:6px 10px 5px;background:var(--accent);color:var(--accent-ink);
  font-size:11px;font-weight:700;letter-spacing:.26em;line-height:1;
  text-transform:uppercase}

/* ------------------------------------------------------------------ layout -- */
/* THE GUTTER. Longhand, never the shorthand: `padding:0 var(--gut)` would reset
   the vertical padding of anything that also carries this class, and the bed's
   type block does. */
.gut{padding-left:var(--gut);padding-right:var(--gut)}

/* ------------------------------------------------------------ preview bar --
   Demo-only chrome. Pinned above the masthead, which is given a matching
   padding-top published as --tpl-h. No `top` and no `position` is declared on
   #nav anywhere in this file: the shared floor owns those. */
.tplbar{position:fixed;top:0;left:0;right:0;z-index:130;
  background:var(--accent);color:var(--accent-ink);
  font-size:12px;font-weight:600;line-height:1.45;
  padding:8px var(--gut);text-align:center;letter-spacing:.01em}
.tplbar b{font-weight:700}
:root:has(.tplbar){--tpl-h:56px}
#nav{padding-top:var(--tpl-h,0px)}

/* --------------------------------------------------------------- masthead -- */
/* Printed on the raw plate. What makes it legible is a ramp on the masthead's own
   box, MEASURED like every other scrim here: the wordmark and the links sit on the
   lit face of hero-tall, which is the brightest passage on either hero plate
   (99.9th percentile 0.34 raw). The first draft of this ramp left the links at
   #6D7473, i.e. 4.8:1 and outside the declared --hero-floor. Composited with the
   hero's own top scrim over the real type row at twelve geometries, and with and
   without the demo bar shifting the row down 56px, this one lands the worst pixel
   at #2F2F2C. It falls to fully transparent at its own bottom edge, so it is a
   ramp and never a bar with a line under it. */
#nav::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(180deg,rgba(0,0,0,.94) 0%,rgba(0,0,0,.90) 70%,
    rgba(0,0,0,.66) 88%,rgba(0,0,0,0) 100%);
  transition:opacity .25s ease}
#nav.scrolled{background:var(--surface);box-shadow:inset 0 -3px 0 0 var(--accent)}
#nav.scrolled::before{opacity:0}
/* base's .nav-inner is a nowrap flex row and the drawer is its fourth child, so
   without wrap it opens as a column squeezed beside the wordmark. */
.nav-inner{gap:14px;flex-wrap:wrap}
.brand{display:inline-flex;align-items:center;min-height:44px;
  text-decoration:none;color:var(--ink);margin-right:auto}
.pmark{font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(15px,1.7vw,21px);letter-spacing:-.02em;line-height:1}
/* A client mark is never cropped: contain, never cover, and it keeps the
   wordmark's footprint so nothing in the bar reflows. */
.navlogo{display:block;width:auto;height:34px;max-width:190px;object-fit:contain}
.navtoggle .bars{display:block;width:24px}
.navtoggle .bars i{display:block;height:2px;margin:6px 0;background:var(--ink)}
.nav-links{flex:1 0 100%;flex-direction:column;align-items:stretch;gap:0;
  width:100%;padding:6px 0 16px;margin-top:8px;border-top:var(--rule) solid var(--accent)}
.nav-links a{display:flex;align-items:center;width:100%;min-height:48px;padding:13px 0;
  color:var(--ink);text-decoration:none;font-family:var(--font-body);font-weight:700;
  font-size:12px;letter-spacing:.22em;text-transform:uppercase}
/* A vermilion UNDERLINE, not vermilion text: the bar sits over photography and
   #FF3B14 on the hero floor is 3.2:1. An inset shadow is a printed rule, not a box. */
.nav-links a:hover{box-shadow:inset 0 -2px 0 0 var(--accent)}
.navcta{justify-content:center;margin-top:10px;padding:0 22px;min-height:52px;
  background:var(--accent);color:var(--accent-ink)}
.navcta:hover{background:var(--accent-2);box-shadow:none}
html.menu-open #nav{background:var(--surface)}

/* ------------------------------------------------------------------- hero -- */
/* min-height, never height: a long headline on a short phone must be able to grow
   the sheet instead of being clipped by overflow:hidden. */
.hero{position:relative;min-height:100vh;min-height:100svh;
  display:grid;align-content:end;overflow:hidden;isolation:isolate;
  background:var(--surface)}
.heroshot{position:absolute;inset:0;z-index:0}
.heroshot picture{display:block;width:100%;height:100%}
.heroshot img{width:100%;height:100%;object-fit:cover;object-position:50% 50%}
/* MEASURED. Two ramps: a short one under the masthead, where hero-tall's lit face
   sits, and a long one up from the trim edge, where every line of type sits.
   Neither is a flat panel and both are fully transparent across the middle of the
   frame, so the subject keeps its exposure at both widths. */
.heroshade{position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    linear-gradient(to bottom,rgba(0,0,0,.883) 0%,rgba(0,0,0,.578) 13.2%,rgba(0,0,0,0) 24%),
    linear-gradient(to bottom,rgba(0,0,0,0) 24%,rgba(0,0,0,.426) 37.7%,
                    rgba(0,0,0,.805) 57.4%,rgba(0,0,0,.934) 78.7%,rgba(0,0,0,.964) 100%)}
/* WHERE THE TYPE STARTS IS A FRACTION OF THE SCREEN, NOT A PIXEL COUNT — and that
   is what makes the measured scrim hold. .hero is a grid with align-content:end, so
   the block is trimmed to the bottom edge and its top lands at heroH - block. With a
   FIXED padding-top a 700px phone put the headline at 16% of the frame, straight
   through the transparent window in the middle of the scrim and onto the lit face:
   #A78574, 3.3:1, and no gradient could have saved it because the gradient is
   deliberately clear there. Expressed in svh the padding grows the hero instead, so
   the type top is pt/(pt+block) of the frame and can never climb: 43svh holds it at
   or below 38.5% on every phone geometry measured, 30svh at or below 25.4% on every
   desktop one. Both variation axes only ever INCREASE it.

   43svh, NOT 52svh — AND THE SECOND CTA IS WHY. At 52svh the block cleared 100svh on
   every phone and the hero grew past the fold with it: at 390x844 the hero measured
   1007px and "See the prices" ended at 881, 37px below the screen. Re-measured after
   the cut, on the real page, at 360x780 / 390x667 / 390x750 / 390x844 / 390x932 /
   414x896 and all three placement axes: the hero is 714-933px, both CTAs are above
   the fold at every one, and the type top never rises above 38.5%. At 390x844 the
   hero is now exactly one screen (844) and the CTAs end at 738. Note that once the
   hero is AT its min-height the padding no longer positions the type at all —
   align-content:end pins the block to the trim edge, so the block's own height is
   what holds the CTAs up, and that is why the rhythm below was cut with it. */
.heroin{position:relative;z-index:2;width:100%;
  padding-top:39vh;padding-top:43svh;padding-bottom:clamp(22px,3.4vh,58px)}
.herolead{margin:clamp(13px,2vh,28px) 0 0;max-width:48ch}
.heroin .btnrow{margin-top:clamp(16px,2.4vh,34px);gap:10px}
/* THE TWO HERO CTAs SHARE A ROW ON THE PHONE. base.css gives every .btnrow > .btn
   flex:1 1 100% below 560px, which is right for a stacked pair in a section but
   wrong here: it put the second hero button 38px BELOW the fold at 390x844, and a
   primary call to action a visitor never sees is not a call to action. Scoped to
   .heroin (0,2,1) so every other button row in the template keeps the floor's
   behaviour, and flex-wrap on .btnrow is the fallback if a long label pair cannot
   make the width. */
.heroin .btnrow > .btn{flex:0 1 auto;padding-left:18px;padding-right:18px}

/* THE DRAWN MOMENT — REVERSAL. h1a is printed. h1b is the hole in the ink.
   box-decoration-break:clone gives each LINE its own slab, ragged right, instead
   of one rectangle around a wrapped phrase. An inline background box is as tall
   as the font's ascent plus descent and NOT its line-height, so at .86 two
   painted lines overprint: h1b therefore carries its own leading, and the change
   of rhythm between the printed half and the knocked-out half is part of the move. */
.h1a{display:block;color:var(--ink)}
.h1b{display:inline;line-height:1.14;padding:.02em .15em .06em;
  background:var(--accent);color:var(--accent-ink);
  -webkit-box-decoration-break:clone;box-decoration-break:clone}

/* THE STANDING LINE — hours and address on the trim edge, over a printed rule. */
.stand{display:flex;flex-direction:column;gap:5px;
  margin:clamp(18px,2.6vh,36px) 0 0;padding-top:12px;
  border-top:1px solid rgba(255,255,255,.34);
  font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-2)}
.stand b{color:var(--ink);font-weight:700}

/* THE SHUTTER — the signature. Opaque bars, transform only, and the picture
   behind them never moves. The keyframe carries its own 4.2s failsafe delay so a
   bar can never be left shut over a photograph by a script that did not run;
   template.js only brings the opening forward. */
@keyframes shutopen{from{transform:scaleY(1)}to{transform:scaleY(0)}}
.shut{position:absolute;inset:0;z-index:4;pointer-events:none;overflow:hidden}
.shut i{position:absolute;left:0;right:0;height:50.5%;display:block;
  background:#000;transform:scaleY(0)}
.shut .t{top:0;transform-origin:top}
.shut .b{bottom:0;transform-origin:bottom}
html.js .shut i{transform:scaleY(1);
  animation:shutopen 1000ms cubic-bezier(.16,.84,.3,1) 4200ms both}
html.js .hero .shut i{animation-delay:120ms}
html.js .shut.open i{animation-delay:0ms}
html:not(.js) .shut{display:none}

/* ------------------------------------------------------- 2. THE FIGURE ------
   Solid vermilion, edge to edge. The Google rating is the one hard checkable fact
   a shop has, so it is never a statistic in a card: here it is a whole printed
   sheet and the figure is knocked out of the ink at display scale. The shared
   component (shared/rating.css) is SKINNED through its five tokens plus scoped
   colour rules; the markup is never forked. */
.score{background:var(--band);color:var(--band-ink);
  padding:clamp(48px,6.6vh,132px) 0 clamp(44px,5.8vh,120px);
  --rating-bg:transparent;
  --rating-shadow:none;
  --rating-rim:transparent;
  --rq-bg:transparent;
  --rq-ink:var(--band-ink)}
.score .eyebrow{background:var(--accent-on-band);color:var(--accent)}
.score h2{color:var(--band-ink)}
.scorelead{color:var(--band-ink-2);max-width:44ch;margin:0}
.score .rating{margin-top:clamp(24px,3.2vh,60px)}
.score .rating-fig{padding:clamp(16px,2.2vh,38px) 0 0;border-radius:0;
  border-top:var(--rule) solid var(--band-ink)}
.score .rating-fig::after{box-shadow:none}
.score .rstars{font-size:clamp(22px,3.4vw,38px);letter-spacing:.1em}
.score .rs-off{color:rgba(11,11,11,.26)}
.score .rs-on{color:var(--band-ink)}
.score .rnum{font-weight:400;letter-spacing:-.06em;line-height:.78;
  font-size:clamp(126px,24vw,300px);color:var(--band-ink)}
.score .rsay{font-weight:400;text-transform:uppercase;line-height:1.04;
  letter-spacing:-.02em;font-size:clamp(19px,2.5vw,32px);color:var(--band-ink-2)}
.score .rsay .rcount{font-weight:400;color:var(--band-ink)}
/* Google's asset is official and unmodified; it is given a black chip to sit on
   rather than being recoloured for the vermilion field. */
.score .gmpill{border:0;border-radius:0;padding:11px 15px;
  background:var(--accent-on-band)}
.score .gmpill:hover{background:var(--band-ink-2)}
.score .gmgo{font-family:var(--font-body);font-weight:700;font-size:11px;
  letter-spacing:.18em;text-transform:uppercase;color:var(--ink)}
.score .gmpill:hover .gmgo{color:var(--accent)}
.score .rquotes{max-width:none;gap:0;margin-top:clamp(24px,3.4vh,62px)}
.score .rq{padding:clamp(12px,1.6vh,26px) 0 0;border-radius:0;
  border-top:1px solid rgba(11,11,11,.32)}
.score .rq blockquote{font-family:var(--font-body);font-weight:500;font-size:15.5px;
  line-height:1.5;color:var(--band-ink)}
.score .rq figcaption{color:var(--band-ink-2);letter-spacing:.18em}
.score .rq-empty{border:0;border-top:1px dashed rgba(11,11,11,.45)}
.score .rq-empty blockquote{color:var(--band-ink-2)}

/* --------------------------------------------------- 3. THE PRICE SHEET -----
   The shared board (base.css THE BOARD, partials/board.html) loses every surface
   it owns — tray, cell, radius, shadow, chip — and becomes a ruled list. Every
   rule below is SCOPED to .prices: a bare .board / .bitem / .bname here would
   redefine the services section for every build in the library. */
.prices{background:var(--surface);padding:clamp(48px,6.6vh,132px) 0 clamp(44px,5.8vh,120px)}
.prices .board{--bd-cols:1;--bd-gap:0;--bd-cell-pad:0;--bd-radius:0;--bd-edge:none;
  --bd-glow:transparent;
  background:none;border:0;border-radius:0;box-shadow:none;padding:0;
  margin-top:clamp(22px,3.2vh,54px);border-top:var(--rule) solid var(--accent)}
/* the gutter between the two columns at 1024 comes from --bd-gap and nowhere else:
   a `.prices .bgrid{gap:0}` here would out-specify base's `gap:var(--bd-gap)` and
   the 2-up would silently render with its columns touching. */
.prices .bitem{background:none;border-radius:0;overflow:visible;
  align-items:baseline;column-gap:clamp(13px,1.8vw,24px);
  padding:clamp(10px,1.3vh,25px) 0;
  border-bottom:1px solid var(--hairline);
  transition:background .2s ease}
.prices .bitem::before{display:none}
.prices .bitem:hover{background:rgba(255,255,255,.045)}
/* ONE mark per row, one library, one weight, one size, one colour — the same
   vermilion as everything else that is not in a photograph. */
.prices .bicon{width:22px;height:22px;border-radius:0;background:none;
  align-self:center;color:var(--accent)}
.prices .bicon > svg{width:22px;height:22px}
.prices .bname{font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(19px,2.7vw,34px);line-height:1;letter-spacing:-.025em;color:var(--ink)}
.prices .bdesc{margin:7px 0 0;font-size:14px;line-height:1.55;
  color:var(--ink-2);max-width:52ch}
.prices .btag{display:block;justify-self:start;margin:8px 0 0;padding:0;
  border:0;border-radius:0;font-size:10.5px;font-weight:700;letter-spacing:.24em;
  color:var(--ink-3)}
.prices .bprice{align-self:baseline;font-family:var(--font-display);font-weight:400;
  font-size:clamp(21px,2.7vw,34px);line-height:1;letter-spacing:-.03em;
  color:var(--accent)}
.prices .bgh{font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(14px,1.5vw,19px);letter-spacing:0;color:var(--accent);margin:0 0 8px}
.prices .bgroup + .bgroup{margin-top:clamp(26px,4vh,44px)}
/* THE REPLY HOOK, printed only when something is unpriced — and made the loudest
   thing in the section on purpose. It is the page asking its owner for the one
   thing only they have, and it is why a prospect writes back. */
.prices .bhook{margin:0;padding:clamp(16px,2.4vh,22px) clamp(16px,2.4vw,22px);
  border:0;border-radius:0;background:var(--accent)}
.prices .bhook-l{color:var(--accent-ink);letter-spacing:.24em;font-size:11px}
.prices .bhook-t{color:var(--accent-ink);max-width:58ch;font-size:14.5px}
.pfoot{margin:clamp(18px,2.5vh,36px) 0 0;font-size:12px;font-weight:700;
  letter-spacing:.18em;text-transform:uppercase;color:var(--ink-3);max-width:46ch}

/* ------------------------------------------------------ 4/7/8. THE BEDS -----
   A full-bleed plate with the type set straight on it. The scrim is MEASURED and
   it deepens shadow the plate already carries; on the phone it opens at the top
   so the picture is the first thing you see, and from 860px it runs left to right
   so the picture keeps its exposure on the side with nothing printed on it.
   Only --ink and --ink-2 are ever used over a bed: --ink-3 holds 2.4:1 on
   --plate-floor and is confined to solid grounds. */
.bed{position:relative;overflow:hidden;isolation:isolate;background:var(--surface)}
.bleed{position:absolute;inset:0;z-index:0}
.bleed img{width:100%;height:100%;object-fit:cover;object-position:50% 50%}
/* THE PHONE RAMP IS IN PIXELS, NOT PER CENT, and that is not a detail. A bed's
   height is set by its own content, so the close (heading, paragraph and a whole
   contact form) is roughly twice the door's. Written in per cent the same ramp
   stretched over the taller section and the form's first labels landed at 19% of
   the frame on #707070 - 3.6:1 - while the identical rule was fine on the short
   one. In pixels the ramp is deep by 196px on every bed, and .bedin never starts
   its type above 200px, so the guarantee is the same in all three.

   196/200, NOT 275/280: three beds at 280px of clear plate each is 840px of a phone
   page, and the page measured 8,522px against CONTRACT.md's 7,500px ceiling. The ramp
   and the floor moved TOGETHER, which is the only way the measurement survives — the
   type still starts where the scrim is at .945 or deeper. Re-proved by render, not by
   argument: type hidden, page screenshotted at 390, worst composited pixel inside
   every bed type box is #010101 against a --plate-floor of #545454. Then the same
   check with all three plates replaced by PURE WHITE, which is the worst photograph
   that could ever be dropped into this template: #0E0E0E, still far under the floor.
   With the scrim removed the same check reads #FFFFFF and fails, so it can fail. */
.scrim{position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(to bottom,rgba(0,0,0,.12) 0px,rgba(0,0,0,.48) 86px,
    rgba(0,0,0,.80) 142px,rgba(0,0,0,.945) 196px,rgba(0,0,0,.965) 100%)}
.bedin{position:relative;z-index:2;
  padding-top:clamp(200px,24svh,300px);padding-bottom:clamp(38px,5vh,104px)}
.bedin .lead{margin:clamp(11px,1.6vh,24px) 0 0}

/* the story's three facts: one printed rule each, never a card and never a grid
   of boxes. NOT named .frow — base.css owns that, and it is the FORM ROW. */
.shoprows{margin:clamp(22px,3.2vh,50px) 0 0;display:grid;gap:0}
.shoprow{padding:clamp(11px,1.5vh,20px) 0;border-top:1px solid var(--hairline)}
.shoprow:last-child{border-bottom:1px solid var(--hairline)}
.shoprow h3{color:var(--ink)}
.shoprow p{margin:7px 0 0;font-size:14.5px;line-height:1.55;color:var(--ink-2);max-width:44ch}

/* ---------------------------------------------------------- 5. THE WALL -----
   The shared mosaic, bled to all four edges. This template lays out NOTHING here:
   it emits the token and pays for the frame. The field behind the wall is
   vermilion and the gap between tiles is 3px, so what shows between the prints is
   ink rather than background. */
.wall{background:var(--accent);padding:0}
.wallwrap{padding:var(--rule) 0;
  --mos-gap:3px;--mos-gap-sm:2px;--mos-text-bg:var(--accent-on-band);--mos-sm-ar:1/1}
.wallstrip{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 16px;margin:0;
  padding:14px var(--gut) 18px;background:var(--accent);color:var(--accent-ink)}
.wallstrip b{font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(15px,2vw,26px);line-height:1;letter-spacing:-.02em}
.wallstrip span{font-size:11px;font-weight:700;letter-spacing:.24em;
  text-transform:uppercase;color:var(--band-ink-2)}

/* -------------------------------------------------- 6. WHAT PEOPLE ASK ------ */
.asks{background:var(--surface);padding:clamp(44px,6vh,124px) 0}
.askcall{margin:clamp(12px,1.8vh,24px) 0 0;font-size:14.5px;line-height:1.6;
  color:var(--ink-3);max-width:38ch}
.askcall a{display:inline-flex;align-items:center;min-height:44px;text-decoration:none;
  font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(19px,2.2vw,26px);letter-spacing:-.02em;color:var(--accent)}
.askcall a:hover{color:var(--accent-2)}
.asklist{margin-top:clamp(20px,2.8vh,48px);border-top:var(--rule) solid var(--accent)}
.ask{border-bottom:1px solid var(--hairline)}
.ask summary{display:flex;align-items:center;justify-content:space-between;gap:20px;
  min-height:48px;padding:clamp(12px,1.7vh,23px) 0;cursor:pointer;list-style:none;
  font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(15px,1.9vw,24px);line-height:1.05;letter-spacing:-.02em;color:var(--ink)}
.ask summary::-webkit-details-marker{display:none}
.ask summary i{position:relative;flex:0 0 auto;width:13px;height:13px}
.ask summary i::before,.ask summary i::after{content:"";position:absolute;
  background:var(--accent);transition:opacity .2s ease}
.ask summary i::before{left:0;top:5.5px;width:13px;height:2px}
.ask summary i::after{left:5.5px;top:0;width:2px;height:13px}
.ask[open] summary i::after{opacity:0}
.ask p{margin:0;padding:0 0 clamp(18px,2.6vh,26px);max-width:66ch;
  font-size:15px;line-height:1.62;color:var(--ink-2)}

/* ---------------------------------------------------------- 7. THE DOOR ----- */
.hours{margin-top:clamp(20px,2.8vh,46px);border-top:var(--rule) solid var(--accent)}
.hrow{display:flex;justify-content:space-between;gap:20px;padding:13px 0;
  border-bottom:1px solid rgba(255,255,255,.26);font-size:15px}
.hd{color:var(--ink-2)}
.ht{color:var(--ink);font-variant-numeric:tabular-nums}
/* hours[] is optional, and one ruled row where a week should be is tolerance, not
   design. Absent, the published line is set at display scale on the same rule the
   day rows would have used, and the ask is printed under a vermilion one. */
.hrow-open{display:block}
.hrow-open .hd{display:block;font-size:11px;font-weight:700;letter-spacing:.24em;
  text-transform:uppercase}
.hrow-open .ht{display:block;margin-top:9px;font-family:var(--font-display);
  font-weight:400;text-transform:uppercase;font-size:clamp(20px,2.6vw,32px);
  line-height:1;letter-spacing:-.025em}
.hask{display:block;margin-top:13px;padding-top:13px;max-width:44ch;
  border-top:2px solid var(--accent);font-size:14.5px;line-height:1.55;color:var(--ink-2)}
.doorat{display:flex;flex-direction:column;align-items:flex-start;gap:6px;
  margin-top:clamp(18px,2.6vh,44px)}
.dbig{display:inline-flex;align-items:center;min-height:48px;text-decoration:none;
  font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(24px,3.4vw,46px);line-height:1;letter-spacing:-.03em;color:var(--ink)}
.dbig:hover{box-shadow:inset 0 -3px 0 0 var(--accent)}
.doorat address{font-style:normal;font-size:15px;color:var(--ink-2);max-width:36ch}
.dmap{display:inline-flex;align-items:center;min-height:48px;margin-top:12px;
  padding:0 18px;background:var(--accent);color:var(--accent-ink);text-decoration:none;
  font-size:11px;font-weight:700;letter-spacing:.2em;text-transform:uppercase}
.dmap:hover{background:var(--accent-2)}
.area{margin-top:clamp(18px,2.6vh,42px);padding-top:18px;
  border-top:1px solid var(--hairline)}
.area h3{color:var(--ink)}
.area p{margin:8px 0 0;font-size:14.5px;line-height:1.55;color:var(--ink-2);max-width:44ch}
.towns{display:flex;flex-wrap:wrap;gap:7px;margin-top:12px}
.towns span{padding:6px 11px;border:1px solid rgba(255,255,255,.32);
  font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2)}

/* --------------------------------------------------------- 8. THE CLOSE -----
   The form is set STRAIGHT ON THE PHOTOGRAPH. There is no panel behind these
   fields and there must never be one: every input is a rule under a line of type,
   so the contact form is the same object as the price list and the hours. */
.formwrap{margin-top:clamp(22px,3.2vh,52px);max-width:640px}
.close .form{gap:clamp(12px,1.6vh,24px)}
.close .field label{color:var(--ink-2);font-size:11px;letter-spacing:.22em}
.close .field input,.close .field select,.close .field textarea{
  min-height:48px;padding:12px 0;border:0;border-radius:0;
  border-bottom:2px solid rgba(255,255,255,.45);
  background:transparent;color:var(--ink);font-size:16px}
.close .field textarea{min-height:88px;padding:12px 0}
.close .field input:focus,.close .field select:focus,.close .field textarea:focus{
  outline:none;box-shadow:none;border-bottom-color:var(--accent)}
.close .field ::placeholder{color:var(--ink-2);opacity:1}
.close .form .btn{justify-self:start;margin-top:6px}
.close .formerror{margin-top:8px;padding:12px 15px;background:var(--accent);
  color:var(--accent-ink);font-size:13px;font-weight:700}
.close .fineprint{margin:14px 0 0;max-width:52ch;font-size:12.5px;line-height:1.55;
  color:var(--ink-2)}
.close .thanks{border:0;border-radius:0;padding:clamp(24px,4vh,40px);
  background:var(--accent);color:var(--accent-ink)}
.close .thanks h3{font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(22px,3vw,34px);line-height:1;letter-spacing:-.03em;color:var(--accent-ink)}
.close .thanks p{margin:12px 0 0;color:var(--band-ink-2);font-size:15px}

/* ---------------------------------------------------------------- buttons -- */
.btn{border-radius:0;border-width:2px;padding:16px 28px;min-height:52px;
  font-family:var(--font-body);font-weight:700;font-size:11.5px;
  letter-spacing:.2em;text-transform:uppercase;line-height:1}
.btn:active{transform:none}
.btn-primary{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
.btn-primary:hover{background:var(--accent-2);border-color:var(--accent-2)}
.btn-ghost{background:transparent;color:var(--ink);border-color:rgba(255,255,255,.55)}
.btn-ghost:hover{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}

/* ----------------------------------------------------------------- footer -- */
footer{background:var(--surface);border-top:var(--rule) solid var(--accent);
  padding:clamp(36px,5vh,92px) 0 clamp(22px,3vh,44px)}
.fname{margin:0;font-family:var(--font-display);font-weight:400;text-transform:uppercase;
  font-size:clamp(28px,5.4vw,66px);line-height:.92;letter-spacing:-.035em;color:var(--ink)}
.fline{margin:14px 0 0;font-size:14.5px;line-height:1.55;color:var(--ink-2);max-width:46ch}
.fgrid{display:grid;gap:20px;margin-top:clamp(24px,3.4vh,54px)}
.fcol{display:flex;flex-direction:column;gap:2px}
.fh{margin:0 0 10px;font-family:var(--font-body);font-weight:700;font-size:11px;
  letter-spacing:.22em;text-transform:uppercase;color:var(--accent)}
.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(--ink)}
.fcol address{font-style:normal;font-size:14.5px;color:var(--ink-3)}
.fnav{display:flex;flex-wrap:wrap;gap:0 26px;margin-top:clamp(24px,3.4vh,52px);
  padding-top:16px;border-top:1px solid var(--line)}
.fnav a{display:inline-flex;align-items:center;min-height:44px;color:var(--ink-3);
  font-size:11px;font-weight:700;letter-spacing:.2em;text-transform:uppercase}
.fnav a:hover{color:var(--accent)}
.fbase{display:flex;flex-wrap:wrap;justify-content:space-between;gap:8px 24px;
  margin-top:14px;font-size:12.5px;color:var(--ink-3)}
.credit a{color:var(--ink-2)}

/* -------------------------------------------------------------------- 404 --
   The one page in the template with no photograph on it: a solid vermilion sheet.
   The reversal runs the other way here — the slab is near-black and the phrase is
   knocked out of it in ink, which is the same move seen from the back. */
.nf{background:var(--accent);color:var(--accent-ink);
  padding:calc(var(--nav-h) + var(--tpl-h,0px) + clamp(64px,11vh,132px)) 0 clamp(64px,11vh,140px)}
.nf .eyebrow{background:var(--accent-on-band);color:var(--accent)}
.nf .h1a{color:var(--band-ink)}
.nf .h1b{background:var(--band-ink);color:var(--accent)}
.nf .lead{color:var(--band-ink-2)}
.nf .btn-primary{background:var(--band-ink);color:var(--accent);border-color:var(--band-ink)}
.nf .btn-primary:hover{background:var(--accent-on-band);border-color:var(--accent-on-band)}
.nf .btn-ghost{color:var(--band-ink);border-color:rgba(11,11,11,.5)}
.nf .btn-ghost:hover{background:var(--band-ink);color:var(--accent);border-color:var(--band-ink)}
.nf .stand{border-top-color:rgba(11,11,11,.42);color:var(--band-ink-2)}
.nf .stand b{color:var(--band-ink)}

/* ================================================== VARIATION AXES ===========
   A template is something a build DRAWS FROM. Each axis is independent and chosen
   from a hash of the client (variations.py), so the combination is what stops 500
   customers getting 500 identical pages. Nothing here touches the argument: the
   gutter, the one ink and the measured scrims are untouched.
   HERO PLACEMENT IS A DESKTOP-ONLY AXIS, deliberately. The phone plate is lit
   across its top third and the phone scrim is measured for type trimmed to the
   bottom edge; lifting the block there would move the headline into the one bright
   passage on that plate. The desktop scrim was measured with the lift already
   applied (type region from 0.24 of the frame), so the axis is proved, not hoped. */
main{display:flex;flex-direction:column}
.hero{order:-10}
.v-order-proof #prices{order:-9}
/* HERO PLACEMENT moves the type DOWN the plate and never up: more padding-top means
   a taller hero and a lower type block, so every setting of this axis is safer than
   the one the scrims were measured against. That is deliberate — an axis that could
   move type into a lit passage would void the measurement on one build in three. */
.v-hero-mid .heroin{padding-top:42vh;padding-top:46svh}
.v-hero-low .heroin{padding-top:45vh;padding-top:49svh}

.v-accent-wide .prices .board,.v-accent-wide .asklist,.v-accent-wide .hours,
.v-accent-wide .nav-links,.v-accent-wide footer{border-top-width:6px}
.v-accent-wide .prices .bitem,.v-accent-wide .ask{border-bottom-color:rgba(255,59,20,.45)}
.v-accent-wide .wallwrap{--mos-gap:6px;--mos-gap-sm:4px}
.v-accent-wide .stand{border-top-color:var(--accent)}

/* ------------------------------------------------------------ breakpoints -- */
@media(min-width:700px){
  .stand{flex-direction:row;flex-wrap:wrap;gap:8px 28px;align-items:baseline}
  .doorat{gap:8px}
}

@media(min-width:760px){
  body{font-size:17px}
  .lead{font-size:18px}
  .fgrid{grid-template-columns:1fr 1fr;gap:40px;max-width:840px}
  :root:has(.tplbar){--tpl-h:34px}
}

@media(min-width:860px){
  /* the desktop plate is a different photograph from the phone one, so it gets its
     own measured pair of ramps rather than a scaled version of theirs */
  .heroshade{background:
    linear-gradient(to bottom,rgba(0,0,0,.907) 0%,rgba(0,0,0,.611) 13.2%,rgba(0,0,0,0) 24%),
    linear-gradient(to bottom,rgba(0,0,0,0) 16%,rgba(0,0,0,.449) 29.4%,
                    rgba(0,0,0,.837) 53%,rgba(0,0,0,.95) 74.8%,rgba(0,0,0,.973) 100%)}
  .heroin{max-width:min(58%,980px);padding-top:26vh;padding-top:30svh;
    padding-bottom:clamp(26px,4.5vh,58px)}
  .herolead{margin-top:clamp(16px,2.6vh,28px)}
  .heroin .btnrow{margin-top:clamp(20px,3.4vh,34px);gap:12px}
  .heroin .btnrow > .btn{padding-left:28px;padding-right:28px}
  .stand{margin-top:clamp(22px,3.6vh,36px);padding-top:14px}
  h1{font-size:clamp(56px,5.6vw,96px)}
  /* the beds turn their scrim through 90 degrees: type on the left, the picture
     keeping its exposure on the right, where nothing is printed */
  .scrim{background:linear-gradient(to right,rgba(0,0,0,.896) 0%,rgba(0,0,0,.843) 40%,
    rgba(0,0,0,.611) 64%,rgba(0,0,0,.292) 86%,rgba(0,0,0,.159) 100%)}
  .bed{min-height:88svh;display:grid;align-content:center}
  /* 56%, not 58%: the bed scrim was measured over x 0-0.56 and the hours row is a
     space-between flex, so its right-hand column really does reach this edge. */
  .bedin{max-width:min(56%,880px);
    padding-top:clamp(110px,14vh,180px);padding-bottom:clamp(110px,14vh,180px)}
  .v-hero-mid .heroin{padding-top:31vh;padding-top:35svh}
  .v-hero-low .heroin{padding-top:36vh;padding-top:40svh}

  /* ---- PHONE RHYTHM, RESTORED ------------------------------------------------
     Every vertical figure above this line is now the PHONE's, cut to hold the
     7,500px ceiling CONTRACT.md sets at 390 (the page measured 8,522). vh cannot
     tell the two apart — 10vh is 84px at 390x844 and 90px at 1440x900 — so the
     WIDTH does, and these are the figures the desktop composition was measured
     with. Mobile-first is intact: the base rules are the phone's and this
     breakpoint only adds. Change a phone figure above and change its pair here,
     or the desktop rhythm silently follows the phone down. */
  .eyebrow{margin-bottom:clamp(14px,2.4vh,26px);padding:7px 11px 6px}
  .score,.prices{padding:clamp(64px,10vh,132px) 0 clamp(56px,9vh,120px)}
  .score .rating{margin-top:clamp(30px,5vh,60px)}
  .score .rating-fig{padding-top:clamp(20px,3vh,38px)}
  .score .rquotes{margin-top:clamp(32px,5.5vh,62px)}
  .score .rq{padding-top:clamp(18px,2.6vh,26px)}
  .prices .board{margin-top:clamp(28px,4.6vh,54px)}
  .prices .bitem{padding:clamp(15px,2.1vh,25px) 0}
  .prices .bdesc{margin-top:9px}
  .prices .btag{margin-top:10px}
  .pfoot{margin-top:clamp(22px,3.4vh,36px)}
  .bedin .lead{margin-top:clamp(14px,2.2vh,24px)}
  .shoprows{margin-top:clamp(28px,4.4vh,50px)}
  .shoprow{padding:clamp(14px,2vh,20px) 0}
  .asks{padding:clamp(60px,9vh,124px) 0}
  .askcall{margin-top:clamp(16px,2.4vh,24px)}
  .asklist{margin-top:clamp(26px,4vh,48px)}
  .ask summary{min-height:56px;padding:clamp(15px,2.1vh,23px) 0}
  .hours{margin-top:clamp(26px,4vh,46px)}
  .doorat{margin-top:clamp(24px,3.8vh,44px)}
  .area{margin-top:clamp(24px,3.8vh,42px)}
  .formwrap{margin-top:clamp(28px,4.4vh,52px)}
  .close .form{gap:clamp(17px,2.4vh,24px)}
  .close .field textarea{min-height:130px}
  footer{padding:clamp(48px,7vh,92px) 0 clamp(28px,4vh,44px)}
  .fgrid{gap:26px;margin-top:clamp(30px,5vh,54px)}
  .fnav{margin-top:clamp(30px,5vh,52px)}
}

@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;min-height:44px;padding:0;font-size:11px;letter-spacing:.2em}
  .navcta{min-height:46px;margin-top:0;padding:0 22px}
}

@media(min-width:900px){
  .score .rquotes{grid-template-columns:repeat(3,1fr);gap:0 clamp(22px,3vw,44px)}
  .shoprows{grid-template-columns:repeat(3,1fr);gap:0 clamp(22px,3vw,44px)}
  .shoprow:last-child{border-bottom:0}
}

@media(min-width:1024px){
  .prices .board{--bd-cols:2;--bd-gap:0 clamp(38px,5vw,92px)}
}

/* ----------------------------------------------------------------- motion -- */
/* base.css neutralises .reveal under prefers-reduced-motion and html.js is never
   set in that case, so the shutter never runs either. This block is the belt to
   that brace: nothing here animates, transitions or hides when motion is refused. */
@media(prefers-reduced-motion:reduce){
  .shut{display:none}
  .prices .bitem,#nav::before,.ask summary i::before,.ask summary i::after{transition:none}
  .prices .bitem:hover{background:none}
}


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