/* ============================================================================
   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:#EDF0F1;
  --surface-2:#DFE5E7;
  --band:#18262C;
  --band-ink:#E7EDEF;
  --band-ink-2:#A9BAC1;
  --accent-on-band:#79C7C2;
  --ink:#101619;
  --ink-2:#434F55;
  --ink-3:#55636A;
  --accent:#0B5B63;
  --accent-2:#083F45;
  --accent-ink:#F2F7F8;
  --line:#C3CDD1;
  --maxw:1300px;
  --gutter:22px;
  --radius:0px;
  --font-display:'Bricolage Grotesque','Helvetica Neue',Helvetica,Arial,sans-serif;
  --font-body:'Chivo',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:-.032em}
p{margin:0 0 1em}
a{color:inherit}
ul{margin:0;padding:0;list-style:none}

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

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

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

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

/* --- form skeleton -------------------------------------------------------- */
.form{display:grid;gap:16px}
.field{display:grid;gap:7px}
.field label{font-size:12.5px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-2)}
.field input,.field select,.field textarea{
  width:100%;min-height:50px;padding:13px 15px;border:1px solid var(--line);
  border-radius:var(--radius);background:var(--surface);color:var(--ink);
  font-size:16px;/* 16px stops iOS zooming the page on focus */
  font-family:inherit;transition:border-color .18s ease,box-shadow .18s ease}
.field textarea{min-height:140px;resize:vertical;line-height:1.55}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(11,91,99,.24)}
.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:#8A2417;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}}


/* ============================================================================
   FRONTAGE — the shop's street elevation, seen from the pavement.

   THE APERTURE. --ap is one number and it is the height of every window on this
   page: the hero, the four bays, the SOLID bays (the stall panel in the
   elevation and the review bay on the slate band), the two bays on the 404. A
   photograph appears through that rectangle or it does not appear. The height
   never changes; how many are across, WHAT SITS BETWEEN THEM, whether a bay has
   glass in it or is a solid panel, and how dense the block above and below is —
   those are the only things that vary, and on the phone they vary as much as
   they do on the desktop. Four identical shapes down a phone screen is sameness
   on scroll, which the rubric scores as a 2.

   LIGHT LOGIC: OVERCAST DAYLIGHT, FRONT ON. An overcast sky is a source the size
   of the sky. Nothing has a lit side and nothing casts. So grep this file for
   `box-shadow` and every hit is `inset 0 1px 0` — no offset, no blur, no spread,
   no vignette, no glow, no gradient used as a surface. One hairline catch on the
   TOP edge of seven different component types, because on a grey afternoon the
   only thing that catches light is an upward-facing edge. 01-nightwork has one
   raking lamp; 02-open decided to have no light at all; this has the whole sky.

   And the sky is ON THE GLASS. From the street the inside of a shop is always
   darker than the pavement and the sky is lying across the window, so the grade
   strips the colour and flattens it (--grade) and a flat veil of sky (--haze) is
   screened over every aperture. That is why the pictures are dark on pale paper —
   it is what a shopfront looks like, not a contradiction.

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

:root{
  /* THE APERTURE. 200px on the phone; 20.8vw from 760 up, which is 1/4.81 — so a
     full-bleed aperture is 4.80:1 at 1440, at 1920 and at 2560 and the hero plate
     never drifts from its asset. Clamped at both ends so it can neither collapse
     to a letter-slot on a small tablet nor eat a 4K first screen.
     DO NOT retune the phone value. 390/200 is 1.95:1 against a 2.18:1 bay asset —
     10.6% — and DESIGN-FLOOR D2 allows 15%. 240px would measure 25% and warn. */
  --ap:200px;
  --grade:saturate(.32) contrast(.82) brightness(1.26);
  --haze:rgba(178,205,216,.19);       /* the sky lying on the glass          */
  --haze-flare:rgba(178,205,216,.62); /* the same sky, the instant it opens  */
  --sky:rgba(255,255,255,.80);        /* the catch on a paper top edge       */
  --sky-band:rgba(231,237,239,.26);   /* the same catch on slate             */
  --hair:1px solid var(--line);
  --hair-band:1px solid rgba(231,237,239,.22);
  --nav-h:68px;
  --nav-gap:30px;
}
@media(min-width:760px){:root{--ap:clamp(260px,20.8vw,540px)}}
/* Jon works on a 28-inch display. One 1300px column centred in 2560px of paper
   reads as a phone layout that stopped growing, so the measure opens once and the
   full-bleed elevation does the rest. */
@media(min-width:1700px){:root{--maxw:1440px;--gutter:60px}}

/* ---------------------------------------------------------------- type ----- */
body{background:var(--surface)}
/* 44px floor, not 38: DESIGN-FLOOR C2 wants the largest type at 2.5x body and
   body is 17px, so anything under 42.5 fails at 390 while passing at 1440. */
/* the ceiling is 86, not 92, and it was moved on the render. At 2560 a three-line
   h1 (which is what a real prospect's headline is - "Custom tattoo and piercings, /
   drawn for you") ended 560px into the first screen against DESIGN-FLOOR A3's 560
   limit, once the descender clearance below was added. 86 buys 18px of margin and
   only bites above ~1593px, so 390 and 1440 are byte-identical. C2 still clears:
   86/17 = 5.1x body against the 2.5x floor. */
h1{font-size:clamp(44px,5.4vw,86px);line-height:1.0;letter-spacing:-.035em;margin:0}
.h-lg{font-size:clamp(29px,3.5vw,48px);line-height:1.04;letter-spacing:-.03em}
.h-md{font-size:clamp(23px,2.5vw,32px);line-height:1.12;letter-spacing:-.024em}
h3{font-size:18px;line-height:1.28;letter-spacing:-.014em;font-weight:700}
.measure{max-width:60ch}
.lead{color:var(--ink-2)}
/* the lettering face: everything that behaves like painted lettering on a shop —
   fascia labels, ticket chips, captions, the register numbers, form labels — is
   the body face at small size, uppercase, wide tracking. Nothing else is. */
.fl,.fr,.chip,.bay figcaption span,.kerb span,.kerb a,.fh,.drawlab,.mgh,.towns span,
.field label,.solidbay figcaption,.fplain.lab,.fnav a,.mnote,.kick{
  text-transform:uppercase;letter-spacing:.15em;font-weight:600;font-size:10.5px}

/* -------------------------------------------------------------- imagery ----
   ONE grade, ONE rule, every photograph on the page — and the cool cast is NOT in
   it. --grade desaturates, flattens and lifts; --haze puts the sky back on top in
   screen blend, so the cool arrives in the shadows the way an overcast sky puts it
   there rather than as an even wash, which is what reads as a filter. There is no
   second filter declaration in this file and there must never be one. */
.apf img{filter:var(--grade)}

/* THE APERTURE ITSELF. Fixed height, no radius, no frame line — the only edge it
   has is the 1px of sky along its top, the same catch every other component on the
   page carries. isolation:isolate keeps the screen blend inside the window.
   ::after is the sky lying on the glass and is always there.
   ::before is the same sky at the moment the window opens, and is at opacity 0
   until the shutter runs — see MOTION at the foot of this file. */
.ap{display:block;position:relative}
.apf{display:block;position:relative;height:var(--ap);overflow:hidden;isolation:isolate;
  background:var(--surface-2);box-shadow:inset 0 1px 0 var(--sky)}
.apf img{width:100%;height:100%;object-fit:cover;object-position:50% 50%}
.apf::after{content:"";position:absolute;inset:0;background:var(--haze);
  mix-blend-mode:screen;pointer-events:none}
.apf::before{content:"";position:absolute;inset:0;z-index:2;opacity:0;
  background:var(--haze-flare);mix-blend-mode:screen;pointer-events:none}

/* ------------------------------------------------------------ preview bar --
   Demo-only chrome, and it has to get out of the masthead's way: in normal flow
   the fixed nav simply covers it and the wordmark prints on top of the bar's own
   words (the defect 05-plate shipped). Pinned above the nav, and the nav is given
   a matching padding-top published as --tpl-h. No `top` and no `position` is
   declared on #nav — the shared floor owns those and houserules.py enforces it. */
.tplbar{position:fixed;top:0;left:0;right:0;z-index:130;background:var(--band);
  color:var(--band-ink-2);font-size:11.5px;line-height:1.4;letter-spacing:.02em;
  padding:8px var(--gutter);text-align:center}
.tplbar b{color:var(--band-ink)}
:root:has(.tplbar){--tpl-h:54px}
@media(min-width:760px){:root:has(.tplbar){--tpl-h:32px}}
#nav{padding-top:var(--tpl-h,0px)}

/* -------------------------------------------------- THE MASTHEAD = FASCIA --
   Lettering across the top of a shopfront. Paper, one hairline underneath, the
   name at the left, the number at the right. Nothing translucent, blurred or
   shadowed; scrolling DRAWS the hairline across the shopfront and nothing else
   moves — see MOTION. */
#nav{background:var(--surface);border-bottom:1px solid transparent}
.nav-inner{gap:12px;flex-wrap:wrap}
.brand{display:inline-flex;align-items:center;gap:12px;text-decoration:none;
  color:var(--ink);min-height:44px}
.mark{display:inline-flex;align-items:center;justify-content:center;width:30px;
  height:30px;font-size:11px;font-weight:700;letter-spacing:.04em;color:var(--accent);
  border:1px solid var(--accent);box-shadow:inset 0 1px 0 var(--sky)}
.brandtype{font-family:var(--font-display);font-size:20px;font-weight:700;
  letter-spacing:-.032em;white-space:nowrap}
.navtel{display:none}
.navtoggle .bars{display:block;width:22px}
.navtoggle .bars i{display:block;height:1px;margin:6px 0;background:var(--ink)}
.navtoggle .bars i:nth-child(2){width:14px}
.nav-links{flex:1 0 100%;flex-direction:column;align-items:stretch;gap:0;width:100%;
  padding:4px 0 14px;margin-top:6px;border-top:var(--hair)}
.nav-links a{color:var(--ink-2);text-decoration:none;font-size:16px;font-weight:500;
  padding:11px 0;min-height:44px;display:flex;align-items:center;
  border-bottom:var(--hair)}
.nav-links a:hover{color:var(--accent)}
.drawlab{color:var(--accent);margin:10px 0 2px;text-transform:uppercase;
  letter-spacing:.15em;font-weight:600;font-size:10.5px}
.drawhours{font-size:14px;color:var(--ink-2);margin:0 0 8px}
.drawtel{font-family:var(--font-display)!important;font-size:22px!important;
  font-weight:700!important;letter-spacing:-.02em!important;color:var(--ink)!important}
.navcta{background:var(--accent);color:var(--accent-ink)!important;
  justify-content:center!important;border-bottom:0!important;margin-top:12px;
  font-family:var(--font-display)!important;font-size:15px!important;font-weight:700;
  letter-spacing:0;box-shadow:inset 0 1px 0 var(--sky-band)}

@media(min-width:940px){
  .nav-inner{flex-wrap:nowrap;gap:26px}
  .navtel{display:inline-flex;align-items:center;min-height:44px;margin-left:auto;
    padding-right:24px;border-right:var(--hair);font-family:var(--font-display);
    font-size:16px;font-weight:600;letter-spacing:-.02em;color:var(--ink);
    text-decoration:none}
  .navtel:hover{color:var(--accent)}
  .nav-links{flex:0 0 auto;width:auto;padding:0;margin:0;border:0}
  /* two classes, not one: `.nav-links a` is (0,1,1) and beats a bare `.drawtel`
     (0,1,0), which is how a drawer's 22px telephone number ends up printed in a
     desktop masthead. 02-open found that on the render, not in the file. */
  .nav-links a{width:auto;padding:0;border-bottom:0;font-size:11px;font-weight:600;
    letter-spacing:.15em;text-transform:uppercase;color:var(--ink-2)}
  .nav-links .drawlab,.nav-links .drawhours,.nav-links .drawtel{display:none}
  .navcta{height:40px;margin-top:0;padding:0 18px!important;
    font-size:13px!important;letter-spacing:0}
}

/* ------------------------------------------------------------- THE HERO ----
   One full-bleed aperture, and THE BOARD hanging off it.

   THE PHONE IS ITS OWN COMPOSITION, not the desktop one collapsed. The board's
   top is a constant 174px below the top of the aperture at 390 and a constant
   140px at 820 and up, and those two numbers were arrived at from opposite ends.
   On the desktop the aperture is 260-540px tall and the board is 58% of the width,
   so the photograph frames the sign above AND to the right and 140px is simply
   where the sign hangs. On the phone the board is full-bleed, so every pixel of
   aperture below the board's top edge is a pixel of photograph nobody sees: at
   112px the most photographic of the three skeletons was showing 56% of its own
   hero window. 174 shows 87% of it and still leaves the h1 inside the picture's
   box (DESIGN-FLOOR A1 — the overlap is 8px, deliberate and measured, not
   incidental) with its top at 266 and its bottom at ~400 against A3's 300/480.

   The 30px that bought is the KICKER, which on the phone is set at the FOOT of
   the board over a hairline — the town lettered along the bottom edge of the sign
   — instead of above the headline. Desktop puts it back on top. No scrim, no
   gradient, no shadow: the type is legible because it is painted on a sign. */
.hero{background:var(--surface);padding:calc(var(--nav-h) + var(--tpl-h,0px)) 0 0}
.board{position:relative;background:var(--band);color:var(--band-ink);
  display:flex;flex-direction:column;
  margin:calc(174px - var(--ap)) calc(var(--gutter) * -1) 0;
  padding:18px var(--gutter) 24px;box-shadow:inset 0 1px 0 var(--sky-band)}
.kick{order:9;margin:24px 0 0;padding-top:13px;border-top:var(--hair-band);
  color:var(--accent-on-band)}
/* padding-bottom, not line-height. h1 is set at line-height 1.0 so h1a's block box
   ends ON its last baseline+descent and any descender in the final word overflows it;
   the painted h1b panel below is a background painted LATER and covered the tail of a
   "g" and a comma outright on the first real prospect fixture ("Custom tattoo and
   piercings, / drawn for you"). Opening the gap here rather than raising line-height
   keeps the two-line h1a itself tight, which is the whole look. Measured on the render
   at 390 and 1440, not derived. */
h1 .h1a{display:block;color:var(--band-ink);padding-bottom:.10em}
/* THE DRAWN MOMENT — the painted sign. Same face, same size, printed the other way
   round: ink on a paper rectangle inside the slate board. box-decoration-break so a
   wrap gives two painted panels rather than one broken one. */
h1 .h1b{font-style:normal;color:var(--ink);background:var(--surface);
  padding:.03em .14em;-webkit-box-decoration-break:clone;box-decoration-break:clone}
.board .lead{margin:16px 0 0;font-size:16.5px;line-height:1.55;color:var(--band-ink-2);
  max-width:44ch}
.board .btnrow{margin-top:20px}
/* the street line under the sign: what somebody standing outside actually wants. */
.kerb{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 22px;margin:22px 0 0;
  padding-top:11px;border-top:var(--hair)}
.kerb span,.kerb a{color:var(--ink-3)}
.kerb .kerb-h{color:var(--accent)}
.kerb a{text-decoration:none;min-height:24px;display:inline-block}
@media(min-width:560px){.kerb a{margin-left:auto}}
.kerb a:hover{color:var(--accent)}
.kerb span,.kerb a{overflow-wrap:anywhere}

@media(min-width:560px){.board .lead{font-size:17.5px}}
@media(min-width:820px){
  .board{margin:calc(140px - var(--ap)) 0 0;width:min(700px,58%);
    padding:28px 34px 32px}
  /* the desktop hero composition is unchanged: the kicker goes back above the
     headline, with no rule under it. */
  .board .kick{order:0;margin:0 0 16px;padding-top:0;border-top:0}
  .board .lead{margin-top:20px;font-size:19px;line-height:1.52}
  .board .btnrow{margin-top:26px}
  .kerb{margin-top:30px}
}
@media(min-width:1700px){
  .board{width:min(880px,60%);padding:34px 44px 40px}
}

/* --------------------------------------------------- THE FASCIA + SECTIONS --
   Every section opens on a full-bleed hairline with its name hanging under it at
   the left and a real piece of the shop's data printed at the right — area,
   address, hours, telephone. Delete a fascia and information is lost, which is the
   test it has to pass to be allowed on the page at all. No two data points repeat
   in adjacent rows: the hero kerb prints hours, address and telephone, so the
   fascia 25px beneath it prints the SERVICE AREA and nothing else. */
.sec{padding:0 0 44px;background:var(--surface)}
.secin{padding-top:34px}
@media(min-width:760px){.sec{padding-bottom:84px}.secin{padding-top:56px}}
@media(min-width:1180px){.sec{padding-bottom:104px}.secin{padding-top:66px}}
.fascia{border-top:var(--hair)}
.fascia .container{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 18px;
  padding-top:9px;padding-bottom:0}
.fl{color:var(--accent)}
.fr{color:var(--ink-3);overflow-wrap:anywhere}
@media(min-width:640px){.fr{margin-left:auto;text-align:right}}
.fascia.onband{border-top:var(--hair-band)}
.fascia.onband .fl{color:var(--accent-on-band)}
.fascia.onband .fr{color:var(--band-ink-2)}
.shead,.ahead,.chead{max-width:800px}
.shead .h-lg,.chead .h-lg{margin-bottom:14px}
.shead .lead,.chead .lead{margin:0}

/* THE STANDFIRST. A section head of a headline over a paragraph, both clamped to
   a 60ch measure, leaves the right 38% of a 1300px column empty — which is the
   automatic deduction for restraint that reads as unfinished, not editorial
   restraint. From 1080 the head splits: the headline holds the left column and
   its own paragraph moves into the right one, so the block carries information
   edge to edge of the same measure the priced rows and the elevation align to.
   Scoped by section rather than to `.shead`, because the register's head already
   sits inside a 1.12fr column on the slate band and must NOT split again. */
@media(min-width:1080px){
  .prices .shead,.front .shead,.door .chead{
    display:grid;grid-template-columns:1.04fr 1fr;column-gap:58px;
    align-items:start;max-width:none}
  .prices .shead .h-lg,.front .shead .h-lg,.door .chead .h-lg{margin-bottom:0}
  .prices .shead .lead,.front .shead .lead,.door .chead .lead{
    max-width:48ch;margin-top:10px}
}

/* ---------------------------------------------------- PRICES (paper, dense) --
   Rows on hairlines, no cards, no imagery. The second axis is the TICKET: the
   outlined chip carrying services[].tag sits on the row's top line, before the
   name, because on a shopfront the ticket in the window is what is read first. */
.rows{margin-top:26px;border-top:1px solid var(--ink)}
.prow{display:grid;grid-template-columns:1fr auto;column-gap:16px;row-gap:6px;
  padding:16px 0;border-bottom:var(--hair)}
.chip{grid-column:1;grid-row:1;justify-self:start;color:var(--ink-3);
  padding:5px 9px;border:var(--hair);box-shadow:inset 0 1px 0 var(--sky);
  line-height:1}
.pn{grid-column:1;grid-row:2;color:var(--ink);align-self:center}
.pp{grid-column:2;grid-row:1 / span 2;align-self:center;font-family:var(--font-display);
  font-size:clamp(26px,2.6vw,38px);font-weight:700;letter-spacing:-.035em;
  color:var(--ink);white-space:nowrap;font-variant-numeric:tabular-nums}
.pb{grid-column:1;grid-row:3;margin:0;font-size:14.5px;line-height:1.55;
  color:var(--ink-2);max-width:58ch}
.bfoot{margin:24px 0 0;font-size:14px;color:var(--ink-3);max-width:60ch}

/* ---- THE EMPTY PRICE STATE -------------------------------------------------
   services[].price is optional because a real shop's prices are never invented,
   and this skeleton has exactly one honest way to draw a missing one. A shopfront
   does not hide an unpriced item and it does not put a dash on it: the ticket
   holder stays in the window and the CARD IN IT IS BLANK, waiting for somebody
   with the figures to write on it. So the absence here is a physical object of
   the page's own family — a paper rectangle with the same 1px of sky along its
   top edge as every aperture, every chip and every panel on this page, and one
   ruled line across it to write the number on. Nothing is greyed out, nothing
   collapses, and the row keeps its exact height and its exact rhythm.

   THE PRICE BOARD is what makes that legible, and it is the reply hook. It is
   the same slate as the hero sign and the stall, set at the HEAD of the rows so
   the reader meets the explanation before the first blank ticket rather than
   after six of them. It is emitted inside the services loop on every unpriced
   row and the general sibling combinator keeps the FIRST one only — no :has(),
   no JavaScript and no new field in the contract. On a half-priced list it lands
   immediately above the first row with no figure, which is where the question
   is. It also does real work for the section: the run was fascia + heading + six
   ruled rows, and the board puts one dark dense block into it, which is a change
   of surface inside a section that had none. */
.blankboard{padding:0;border-bottom:var(--hair)}
.blankboard ~ .blankboard{display:none}
.bb{display:grid;gap:16px;background:var(--band);color:var(--band-ink);
  padding:22px 20px 24px;box-shadow:inset 0 1px 0 var(--sky-band)}
.bblab{display:block;color:var(--accent-on-band)}
.bbsay{margin:8px 0 0;font-family:var(--font-display);font-weight:700;
  font-size:clamp(19px,2.2vw,29px);line-height:1.24;letter-spacing:-.028em;
  color:var(--band-ink);max-width:22ch}
.bbtxt{margin:0;font-size:14.5px;line-height:1.6;color:var(--band-ink-2);
  max-width:52ch}
.bbgo{display:inline-flex;align-items:center;min-height:44px;margin-top:6px;
  font-family:var(--font-display);font-size:19px;font-weight:700;
  letter-spacing:-.024em;color:var(--band-ink);text-decoration:none;
  border-bottom:1px solid var(--accent-on-band)}
.bbgo:hover{color:var(--accent-on-band)}

/* the blank ticket. Same width as a painted price at each end, so the column
   does not move when the figures arrive. */
.ptick{display:block;position:relative;width:104px;height:38px;
  background:var(--surface);border:var(--hair);box-shadow:inset 0 1px 0 var(--sky)}
.ptick::after{content:"";position:absolute;left:13px;right:13px;bottom:11px;
  height:1px;background:var(--line)}
/* no ticket means no first line on the phone either — the name moves up into it
   rather than leaving a 0-height row and its gutter behind. */
.prow.notag .pn{grid-row:1}
.prow.notag .pb{grid-row:2}
.mgroup{margin-top:32px}
.mgh{color:var(--accent);padding-bottom:9px;border-bottom:1px solid var(--ink)}
.mrow{display:grid;grid-template-columns:1fr auto;column-gap:16px;padding:11px 0;
  border-bottom:var(--hair)}
.mn{color:var(--ink);font-weight:600;font-size:15px}
.mnote{grid-column:1;color:var(--ink-3)}
.mp{grid-column:2;grid-row:1 / span 2;align-self:center;font-family:var(--font-display);
  font-size:19px;font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums}
@media(min-width:760px){
  .prow{grid-template-columns:118px 1fr auto;column-gap:28px;row-gap:4px;padding:18px 0}
  .chip{grid-column:1;grid-row:1 / span 2;align-self:start;margin-top:5px}
  .pn{grid-column:2;grid-row:1;font-size:21px;align-self:start}
  .pb{grid-column:2;grid-row:2;font-size:15px}
  .pp{grid-column:3;grid-row:1 / span 2}
  /* services[].tag is optional too, and an empty chip is never rendered. With no
     ticket there is no first column, so the row is TWO fields instead of three
     and the register never opens a 118px hole where the ticket used to be. */
  .prow.notag{grid-template-columns:1fr auto}
  .prow.notag .pn{grid-column:1}
  .prow.notag .pb{grid-column:1}
  .prow.notag .pp{grid-column:2}
  .bb{grid-template-columns:1.02fr 1fr;column-gap:52px;align-items:start;
    padding:30px 34px 32px}
  .bbsay{margin-top:12px}
  .bbtxt{font-size:15px}
  .ptick{width:132px;height:46px}
}

/* --------------------------------------------- THE FRONT (the elevation) ----
   A run of windows, edge to edge of the viewport, one hairline of paper between
   them and nothing else — not a gallery of cards. The caption under each is the
   stallriser: what it is at the left, what it costs or where it is at the right.

   AND THE STALL. The elevation is not four of the same thing in a row. A solid
   bay — no glass in it, the same slate as the hero board, carrying the three
   facts about the shop — is set INTO the run: after the second window on the
   phone, and as a full-width band between the two rows of windows on the desktop.
   So the phone reads window / window / dark dense panel / window / window instead
   of four identical letterboxes, the density changes twice inside one section,
   and the section closes on a photograph rather than on a list. It is also what
   proves the aperture is a MODULE and not a picture frame: the same rectangle
   works with a photograph behind it or three sentences in it.

   Ordering is done with `order`, not with DOM position, so the stall stays last
   in the markup and the run still renders correctly when `gallery` is absent,
   when `team` adds three more bays, or when both are. */
.elev{display:grid;grid-template-columns:1fr;background:var(--surface);
  margin-top:30px;border-top:var(--hair)}
.elev > *{border-bottom:var(--hair)}
.bay{margin:0;background:var(--surface);order:9}
.elev .bay:nth-child(1){order:1}
.elev .bay:nth-child(2){order:2}
.bay figcaption{display:flex;justify-content:space-between;align-items:baseline;
  gap:12px;padding:9px 14px 11px}
.bay figcaption b{font-size:13.5px;font-weight:700;color:var(--ink);
  letter-spacing:-.012em}
.bay figcaption span{color:var(--ink-3);white-space:nowrap}
/* THE STALL — the solid bay. Its side padding is the page MEASURE, not the
   viewport, so the slate runs full bleed while the words inside it line up
   exactly with the priced rows above and the register below (DESIGN-FLOOR D3).

   It is deliberately NOT --ap tall, and that is the one place a solid bay is
   allowed to differ from a glazed one. --ap is clamp(260px,20.8vw,540px), so on a
   28-inch display a stall pinned to the aperture height is 532px of slate holding
   three short sentences — a void, which is the restraint-that-reads-as-unfinished
   deduction rather than a module. A stallriser is the panel UNDER the glass and it
   is shorter than the glass on every real shopfront. So the stall takes its height
   from its content, which is what makes it the DENSE beat between two open rows of
   windows: 300 / 300 / 124 / 300 / 300 at 1440. The height change IS the rhythm. */
.stall{order:3;background:var(--band);color:var(--band-ink);
  display:grid;box-shadow:inset 0 1px 0 var(--sky-band);
  padding:20px max(var(--gutter),calc((100% - var(--maxw))/2 + var(--gutter))) 22px}
.stall .fact{padding:13px 0;border-bottom:var(--hair-band)}
.stall .fact:first-child{padding-top:0}
.stall .fact:last-child{padding-bottom:0;border-bottom:0}
.stall h3{color:var(--band-ink)}
.stall p{margin:5px 0 0;font-size:14.5px;line-height:1.55;color:var(--band-ink-2);
  max-width:58ch}
/* the section ends ON the last window: no trailing block, no closing padding. */
.front{padding-bottom:0}
@media(min-width:760px){
  .elev{grid-template-columns:1fr 1fr;margin-top:44px}
  .elev .bay:nth-child(odd){border-right:var(--hair)}
  /* From 760 the stall spans both columns and its three facts become PIERS: the
     hairline between them runs the FULL height of the panel, in line with the
     mullion between the two windows above it and the two below. The grid row
     stretches every pier to the tallest, so all three headings sit on one line and
     all three rules run floor to ceiling — no pier is vertically centred against
     its neighbour, which is what puts a one-line note and a two-line note on
     different baselines and reads as a bug.
     EVERY padding is restated here. The phone's `:first-child{padding-top:0}` and
     `:last-child{padding-bottom:0}` are (0,3,0) and beat a bare `.stall .fact`
     shorthand (0,2,0) even inside this media query — which is exactly how the
     first pier shipped with its heading 24px above the other two. */
  .stall{grid-column:1 / -1;grid-template-columns:repeat(3,1fr);column-gap:0;
    padding-top:0;padding-bottom:0}
  .stall .fact,.stall .fact:first-child,.stall .fact:last-child{
    border-bottom:0;border-right:var(--hair-band);padding:24px 36px 26px 0}
  .stall .fact + .fact,.stall .fact + .fact:last-child{padding-left:36px}
  .stall .fact:last-child{border-right:0;padding-right:0}
  .stall h3{font-size:19px}
}

/* ------------------------------------------------- REGULARS (the slate) -----
   The one full-bleed dark surface and the biggest change of paper. A ruled
   register rather than three cards, and beside it THE SOLID BAY — the same
   rectangle as the stall above and every window on the page, carrying the
   review. */
.slate{background:var(--band);color:var(--band-ink)}
.slate h2,.slate h3{color:var(--band-ink)}
.slate .lead{color:var(--band-ink-2)}
.regwrap{display:grid;gap:30px;margin-top:0}
.regleft .shead{margin-bottom:26px;max-width:none}
.reg{border-top:var(--hair-band)}
.regrow{padding:17px 0;border-bottom:var(--hair-band)}
.regrow p{margin:6px 0 0;font-size:15px;line-height:1.6;color:var(--band-ink-2);
  max-width:64ch}
.regrow:last-child{border-bottom:0}
.solidbay{margin:0;min-height:var(--ap);display:flex;flex-direction:column;
  justify-content:flex-start;gap:20px;padding:20px;border:var(--hair-band);
  box-shadow:inset 0 1px 0 var(--sky-band)}
.solidbay blockquote{margin:0;font-family:var(--font-display);font-weight:600;
  font-size:clamp(17px,1.9vw,27px);line-height:1.38;letter-spacing:-.024em;
  color:var(--band-ink)}
.solidbay figcaption{color:var(--accent-on-band)}
@media(min-width:900px){
  .regwrap{grid-template-columns:1.12fr .88fr;column-gap:52px;align-items:start;
    margin-top:0}
  .regleft .shead{margin-bottom:38px}
  .regrow{display:grid;grid-template-columns:264px 1fr;column-gap:30px;
    align-items:baseline;padding:20px 0}
  .regrow h3{font-size:19px}
  .regrow p{margin:0}
  .solidbay{padding:26px}
}

/* ----------------------------------------------------------- QUESTIONS ------ */
.asklist{margin-top:20px;border-top:1px solid var(--ink)}
.ask{border-bottom:var(--hair)}
.ask summary{display:flex;align-items:center;justify-content:space-between;gap:18px;
  padding:15px 0;min-height:54px;cursor:pointer;list-style:none;font-weight:600;
  font-size:15.5px;color:var(--ink)}
.ask summary::-webkit-details-marker{display:none}
.ask summary:hover{color:var(--accent)}
.ask summary i{flex:0 0 auto;width:11px;height:11px;position:relative}
.ask summary i::before,.ask summary i::after{content:"";position:absolute;
  background:var(--accent)}
.ask summary i::before{left:0;top:5px;width:11px;height:1px}
.ask summary i::after{left:5px;top:0;width:1px;height:11px;
  transition:opacity .22s ease}
.ask[open] summary i::after{opacity:0}
.ask p{margin:0;padding:0 0 18px;font-size:14.5px;line-height:1.62;color:var(--ink-2);
  max-width:64ch}
.askcall{margin:14px 0 0;font-size:14px;line-height:1.6;color:var(--ink-3);max-width:34ch}
.askcall a{display:inline-flex;align-items:center;min-height:44px;
  font-family:var(--font-display);font-size:21px;font-weight:700;letter-spacing:-.02em;
  color:var(--ink);text-decoration:none}
.askcall a:hover{color:var(--accent)}
/* a narrow column beside its own heading from 900. Four short rows run across a
   1320px measure is restraint that reads as unfinished — a D5 deduction, not
   minimalism. */
@media(min-width:900px){
  .asks .secin{display:grid;grid-template-columns:.78fr 1.22fr;column-gap:56px;
    align-items:start}
  .asklist{margin-top:0}
}

/* ------------------------------------------------------------- THE DOOR ----- */
.door{background:var(--surface-2)}
.week{margin-top:0;border-top:1px solid var(--ink)}
/* hours[] is optional — a Facebook page almost never publishes a schedule, and
   the contract keeps hours_line as the one short human string that is always
   there. So the week table is not emitted empty and it is not padded out with
   seven invented rows: it is replaced by the same object the price column uses,
   a card with the sky catch on its top edge, carrying the line the shop DID
   publish and the ask for the rest of it. One language for both absences. */
.weekblank{padding:18px 18px 20px;background:var(--surface);border:var(--hair);
  box-shadow:inset 0 1px 0 var(--sky)}
.weekblank .fh{display:block;color:var(--ink-3)}
.wbline{margin:9px 0 0;font-family:var(--font-display);font-weight:700;
  font-size:clamp(20px,2.1vw,26px);line-height:1.2;letter-spacing:-.028em;
  color:var(--ink);max-width:20ch}
.wbask{margin:12px 0 0;font-size:14px;line-height:1.6;color:var(--ink-2);
  max-width:44ch}
.wrow{display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  padding:11px 0;border-bottom:var(--hair)}
.wd{font-size:15px;font-weight:600;color:var(--ink)}
.wt{font-family:var(--font-display);font-size:15px;font-weight:600;color:var(--ink-2);
  white-space:nowrap;font-variant-numeric:tabular-nums}
.cgrid{display:grid;gap:32px;margin-top:32px}
.crow{display:flex;flex-direction:column;gap:2px;margin-top:24px}
.cbig{display:inline-flex;align-items:center;min-height:44px;align-self:flex-start;
  font-family:var(--font-display);font-size:clamp(19px,2.1vw,25px);font-weight:700;
  letter-spacing:-.03em;color:var(--ink);text-decoration:none;
  border-bottom:1px solid transparent}
.cbig:hover{color:var(--accent);border-bottom-color:var(--accent)}
.crow address{font-style:normal;font-size:14px;color:var(--ink-3);margin-top:6px}
.area{margin-top:24px;padding-top:18px;border-top:var(--hair)}
.area h3{color:var(--ink)}
.area p{margin:7px 0 0;font-size:14.5px;color:var(--ink-2)}
.towns{display:flex;flex-wrap:wrap;gap:6px}
.towns span{color:var(--ink-3);padding:5px 9px;border:var(--hair);
  box-shadow:inset 0 1px 0 var(--sky)}
.formpanel{background:var(--surface);border:var(--hair);padding:22px 20px;
  box-shadow:inset 0 1px 0 var(--sky)}
.field label{color:var(--ink-3)}
.field input,.field select,.field textarea{background:var(--surface);
  border-color:var(--line);color:var(--ink)}
.field input:focus,.field select:focus,.field textarea:focus{border-color:var(--accent)}
.fineprint{margin:14px 0 0;font-size:12.5px;line-height:1.55;color:var(--ink-3)}
.thanks{background:var(--surface);border:var(--hair);border-radius:0;
  box-shadow:inset 0 1px 0 var(--sky)}
.thanks h3{font-family:var(--font-display);font-size:24px;color:var(--ink)}
.thanks p{margin:0;color:var(--ink-2)}
@media(min-width:900px){
  .cgrid{grid-template-columns:.84fr 1.16fr;column-gap:54px;align-items:start}
  .formpanel{padding:30px 28px}
}
@media(min-width:1180px){.cgrid{grid-template-columns:.8fr 1.2fr;column-gap:70px}}

/* ----------------------------------------------------------------- BUTTONS -- */
.btn{border-radius:0;font-family:var(--font-display);font-size:15px;font-weight:700;
  letter-spacing:-.012em;padding:15px 24px}
.btn-primary{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);
  box-shadow:inset 0 1px 0 var(--sky-band)}
.btn-primary:hover{background:var(--accent-2);border-color:var(--accent-2)}
.btn-ghost{background:transparent;color:var(--band-ink);border-color:var(--band-ink-2)}
.btn-ghost:hover{color:var(--band-ink);border-color:var(--band-ink)}
/* the ghost button lives on the slate board in the hero and on the slate 404 board
   and NOWHERE else, so it is coloured for slate and the paper case is a separate
   two-class rule rather than a hopeful override. */
.sec .btn-ghost,.formpanel .btn-ghost{color:var(--ink);border-color:var(--ink)}
.board .btn-ghost{color:var(--band-ink);border-color:var(--band-ink-2)}

/* ------------------------------------------------------------------ FOOTER -- */
footer{background:var(--surface-2);padding:0 0 26px}
.ffascia{border-top:1px solid var(--ink)}
.fgrid{display:grid;gap:28px;padding-top:34px}
.mark.big{width:40px;height:40px;font-size:14px}
.fname{font-family:var(--font-display);font-size:22px;font-weight:700;
  letter-spacing:-.032em;color:var(--ink);margin:14px 0 6px}
.fabout{margin:0;font-size:14px;color:var(--ink-2);max-width:44ch}
.fh{color:var(--accent);margin-bottom:10px;padding-bottom:8px;border-bottom:var(--hair)}
.fcol{display:flex;flex-direction:column;gap:4px}
.fcol a{color:var(--ink-2);font-size:14px;text-decoration:none;
  display:inline-flex;align-items:center;min-height:44px}
.fplain{color:var(--ink-2);font-size:14px;line-height:1.5;margin-top:2px}
.fplain.lab{color:var(--ink-3);line-height:1.9;margin-top:0}
.fcol a:hover{color:var(--accent)}
.fcol address{font-style:normal;font-size:14px;color:var(--ink-3)}
.fnav{display:flex;flex-wrap:wrap;gap:6px 24px;margin-top:30px;padding-top:16px;
  border-top:var(--hair)}
.fnav a{display:inline-flex;align-items:center;min-height:44px;color:var(--ink-3);
  text-decoration:none}
.fnav a:hover{color:var(--accent)}
.fbase{display:flex;flex-wrap:wrap;justify-content:space-between;gap:8px 24px;
  margin-top:16px;font-size:12.5px;color:var(--ink-3)}
.credit a{color:var(--ink-2)}
@media(min-width:760px){
  .fgrid{grid-template-columns:1.6fr 1fr 1fr;gap:40px;padding-top:52px}
  .fcol a,.fnav a{min-height:26px}
  footer{padding-bottom:30px}
}

/* --------------------------------------------------------------------- 404 --
   The 404 IS the hero: same aperture, same board, same kerb line. It needs no
   rules of its own beyond a slightly shorter headline step, because the whole
   composition is already a component. The bays below it are the ordinary run. */
.nfrun{padding-bottom:0}
.nfrun .elev{margin-top:0}
.nf h1{font-size:clamp(44px,4.6vw,72px)}
/* 44px is a floor, not a taste call: body is 17px and DESIGN-FLOOR C2 wants the
   largest type at 2.5x it. A 40px minimum measured 2.35x and FAILED C2 at 390 on
   the deployed 404 while passing at 1440 and 2560. */

/* ------------------------------------------------------------------ MOTION --
   THREE moments, and each one says what the layout says. Nothing here moves a
   layout box: every property is clip-path, opacity or transform, so none of it
   costs a frame of CLS and none of it delays content.

   1. THE HOUSE REVEAL — every section below the hero fades up. base.css owns it.

   2. THE WINDOW OPENS. A window is a thing that opens, so each aperture's
      photograph opens downward out of its own top edge as that window arrives —
      a shopfront shutter going up. The `.reveal` sits on EACH bay rather than on
      the run, so the elevation opens one window at a time as you walk down it
      instead of firing all four the moment the run's top edge appears; where two
      windows sit side by side the right-hand one follows the left by .22s. It is
      a clip on the IMAGE inside an element that has already been revealed, so
      nothing an observer needs to see is ever a zero-area box, and clip-path
      changes no layout box, so DESIGN-FLOOR D2 measures exactly what it measured
      before.

   3. THE GLASS CATCHES. The sky already lies on every aperture as a flat veil
      (--haze, screen blend). At the instant a window opens, a second veil of the
      SAME sky at full strength lifts off it and clears. That is the whole light
      logic in half a second — no gradient, no glow, no shadow, because an
      overcast sky casts none of them. Delete it and the windows open like doors
      instead of like glass.

   The masthead hairline drawing across the shopfront when you leave the first
   screen, and the accordion answer settling open, are the same idea at furniture
   scale.

   The hero is exempt from ALL of it: its aperture and its headline are painted
   the instant the page arrives. Every rule is gated behind html.js, which the
   inline head script sets only when the visitor has not asked for reduced
   motion — and the reduced-motion block below is belt as well as braces. */
@keyframes shutter{from{clip-path:inset(0 0 100% 0)}to{clip-path:inset(0 0 0 0)}}
@keyframes glasscatch{from{opacity:1}to{opacity:0}}
@keyframes askopen{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}

/* the run's cells fade without the house 18px lift: the shutter is the movement,
   and a lifting grid cell would slide its own hairline. */
html.js .elev > .reveal{transform:none}
html.js .reveal.in .apf img{animation:shutter .78s cubic-bezier(.22,.61,.36,1) .14s both}
html.js .reveal.in .apf::before{animation:glasscatch 1.05s cubic-bezier(.22,.61,.36,1) .14s both}
html.js .elev .bay:nth-child(even).reveal.in .apf img,
html.js .elev .bay:nth-child(even).reveal.in .apf::before{animation-delay:.36s}
/* the fascia hairline draws across the shopfront when you step back from it. */
#nav::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:var(--line);transform:scaleX(0);transform-origin:left center}
html.js #nav::after{transition:transform .55s cubic-bezier(.22,.61,.36,1)}
#nav.scrolled::after,html.menu-open #nav::after{transform:scaleX(1)}
html.js .ask[open] p{animation:askopen .34s cubic-bezier(.22,.61,.36,1) both}
@media(prefers-reduced-motion:reduce){
  html.js .reveal.in .apf img{animation:none;clip-path:none}
  html.js .reveal.in .apf::before{animation:none;opacity:0}
  html.js .ask[open] p{animation:none}
  html.js #nav::after{transition:none}
}
