/* ============================================================================
   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:#0D0B09;
  --surface-2:#16130F;
  --band:#F2EADC;
  --band-ink:#17130E;
  --band-ink-2:#4A423A;
  --accent-on-band:#8A5A16;
  --ink:#F4F0E8;
  --ink-2:#BCB3A6;
  --ink-3:#9A9082;
  --accent:#F2A65A;
  --accent-2:#FFCB93;
  --accent-ink:#1A1006;
  --line:#2A241D;
  --maxw:1160px;
  --gutter:22px;
  --radius:0px;
  --accent-a12:rgba(242,166,90,.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:#30261d;
  --board-cell:#372e24;
  --board-line:rgba(242,166,90,.34);
  --board-accent:#F2A65A;
  --board-glow:rgba(242,166,90,.20);
  --board-shadow-1:rgba(244,240,232,.08);
  --board-shadow-2:rgba(244,240,232,.60);
  --font-display:'Syne','Helvetica Neue',Helvetica,Arial,sans-serif;
  --font-body:'Public Sans',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:-.03em}
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(242,166,90,.26)}
.field ::placeholder{color:var(--ink-3);opacity:1}
.frow{display:grid;gap:16px}
@media(min-width:680px){.frow{grid-template-columns:1fr 1fr}}
.thanks{display:none;border:1px solid var(--line);border-radius:var(--radius);
  padding:28px;background:var(--surface-2)}
.thanks.show{display:block}
.thanks h3{font-size:24px;margin-bottom:10px}
.formerror{color:#FFCB93;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:66px}
.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)}


/* ============================================================================
   LIGHTBOX — the source is BEHIND the plane.

   LIGHT LOGIC. Nothing on this page reflects light. Things either EMIT it or
   BLOCK it. There is consequently not one offset shadow in this file: every
   box-shadow and every text-shadow begins `0 0`, because light escaping from
   behind an edge has no direction to throw in. Grep it and check.
   Every frame carries a dark inner rim on all four sides — the MOUNT — and a
   soft outward halation in the emit colour. Type sits in the unlit field.

   THE BLEED — the signature. One custom property, --emit, is every glow on the
   page. Four strengths are derived from it with color-mix and NOTHING else in
   this file names a glow colour. Change --emit and the hero's emitting edge,
   the headline, the price apertures, the photo halation, the nav seam, the
   accordion markers, the form panel, the footer mark and the 404 numeral all
   relight together. That is the whole reskin, in one line.

   REGISTER. Dark, cool, quiet — deliberately not 01-nightwork's warm tungsten
   room. 01 lights the leading edge and throws a hard shadow away from the lamp;
   this lights all four edges equally and throws nothing at all.

   THE ORIENTATION RULE. Below the hero there is no aspect-ratio, no
   object-fit:cover and no fixed height on any photographic frame. The asset
   sizes its own box and the column flow absorbs the change. The HERO is exempt
   by name — it is art-directed and it is ours.

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

:root{
  --grade:saturate(.92) contrast(1.06) brightness(1.0);
  --nav-h:66px;
  --nav-gap:30px;
  --hero-floor:#3A3733;
  --lit:#FFF9F1;        /* the emitting ink: the one white on the page */
  --hair:1px solid var(--line);

  /* ---------------------------------------------------------------------
     THE BLEED. One value. Every glow resolves from it.
     --------------------------------------------------------------------- */
  --emit:var(--accent);

  /* the four strengths the light is ever used at, and the only four */
  --rim:      color-mix(in srgb, var(--emit) 46%, transparent);   /* the 1px leak   */
  --wash:     color-mix(in srgb, var(--emit) 8%,  transparent);   /* light behind   */
  --halo:     color-mix(in srgb, var(--emit) 26%, transparent);   /* close halation */
  --halo-far: color-mix(in srgb, var(--emit) 11%, transparent);   /* far halation   */

  /* THE MOUNT — the dark inner rim every frame carries on all four sides.
     Inset, so it prints OVER the photograph rather than under it. */
  --mount:      inset 0 0 0 1px rgba(0,0,0,.88);
  --mount-deep: inset 0 0 38px 8px rgba(3,5,8,.72);

  /* the composed glow, and the lit hairline. Zero offset, always. */
  --edge:     0 0 0 1px var(--rim);
  --bleed:    0 0 24px var(--halo), 0 0 74px var(--halo-far);
  --bleed-lg: 0 0 40px var(--halo), 0 0 150px var(--halo-far);
}

/* A 28-inch display is where this gets looked at. The measure opens once, and
   the light band and the hero do the rest of the work at that size. */
@media(min-width:1700px){:root{--maxw:1320px;--gutter:64px}}

/* ---------------------------------------------------------------- type ----- */
body{background:var(--surface)}
h1{font-size:clamp(44px,5.6vw,66px);line-height:1.03;letter-spacing:-.032em;margin:0}
.h-lg{font-size:clamp(29px,3.5vw,42px);line-height:1.08;letter-spacing:-.028em}
.h-md{font-size:clamp(24px,2.7vw,31px);line-height:1.12;letter-spacing:-.024em}
h3{font-size:18.5px;line-height:1.26;letter-spacing:-.014em;font-weight:700}
.measure{max-width:60ch}
.lead{color:var(--ink-2)}
.eyebrow{font-size:11px;font-weight:700;letter-spacing:.2em;color:var(--emit);margin:0 0 14px}
.eyebrow.onband{color:var(--accent-on-band)}

/* -------------------------------------------------------------- imagery ----
   ONE grade, one rule, every photograph on the page. There is no second filter
   in this file and there must never be one — a second grade is what makes a
   set read as grabbed rather than shot. */
.heroplate img,.slabin img{filter:var(--grade)}

/* THE SLAB — the frame, and it has no shape of its own. The asset sets the box;
   .slabin is only a positioning context for the mount and a carrier for the
   halation. No aspect-ratio, no object-fit, no height. */
.slabin{position:relative;display:block;background:var(--surface-2);
  box-shadow:var(--edge),var(--bleed)}
.slabin::after{content:"";position:absolute;inset:0;pointer-events:none;
  box-shadow:var(--mount),var(--mount-deep)}
.slabin img{width:100%;height:auto;display:block}

/* ------------------------------------------------------------ preview bar -- */
.tplbar{position:fixed;top:0;left:0;right:0;z-index:130;
  background:var(--surface-2);color:var(--ink-3);border-bottom:var(--hair);
  font-size:11.5px;line-height:1.45;letter-spacing:.02em;
  padding:8px var(--gutter);text-align:center}
.tplbar b{color:var(--emit)}
:root:has(.tplbar){--tpl-h:56px}
@media(min-width:760px){:root:has(.tplbar){--tpl-h:34px}}
#nav{padding-top:var(--tpl-h,0px)}

/* -------------------------------------------------------------------- nav --
   The masthead is a BLOCKER: opaque, unlit, sitting on the plate. The only
   thing that ever lights on it is the seam along its bottom edge, and only
   once you have scrolled off the hero. No `top` and no `position` is declared
   here — the shared floor owns both and houserules.py enforces it. */
#nav{background:var(--surface);border-bottom:1px solid transparent}
#nav.scrolled{border-bottom-color:var(--rim);box-shadow:0 0 30px var(--halo-far)}
.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:32px;height:32px;font-size:12px;font-weight:700;letter-spacing:.04em;
  font-family:var(--font-display);color:var(--emit);background:var(--wash);
  box-shadow:var(--edge),0 0 16px var(--halo-far)}
.markimg{width:auto;height:32px;background:none;box-shadow:none}
.brandtype{font-family:var(--font-display);font-size:19px;font-weight:700;
  letter-spacing:-.03em;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-2)}
.navtoggle .bars i:nth-child(2){width:13px;background:var(--emit)}
.nav-links{flex:1 0 100%;flex-direction:column;align-items:stretch;gap:0;
  width:100%;padding:2px 0 14px;margin-top:4px;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(--ink)}
.drawlab{font-size:10.5px;font-weight:700;letter-spacing:.2em;color:var(--emit);margin:12px 0 2px}
.drawhours{font-size:14.5px;color:var(--ink-2);margin:0 0 10px}
.drawtel{font-size:20px!important;color:var(--ink)!important;
  font-family:var(--font-display);letter-spacing:-.02em}
.navcta{background:var(--emit);color:var(--accent-ink)!important;
  justify-content:center!important;border-bottom:0!important;margin-top:14px;
  font-size:12.5px!important;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  box-shadow:0 0 26px var(--halo-far)}
html.menu-open #nav{border-bottom-color:var(--line)}

@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:26px;border-right:var(--hair);font-size:15px;
    color:var(--ink-2);text-decoration:none;letter-spacing:-.01em}
  .navtel:hover{color:var(--ink)}
  .nav-links{flex:0 0 auto;width:auto;padding:0;margin:0;border-top:0}
  .nav-links a{font-size:14.5px;padding:0;min-height:44px;border-bottom:0;color:var(--ink-2)}
  /* SCOPED TO TWO CLASSES ON PURPOSE. `.nav-links a` above is (0,1,1); a bare
     `.drawtel` / `.navcta` is (0,1,0) and LOSES to it even from inside this media
     query — specificity outranks a breakpoint. Unscoped, the drawer's phone link
     stayed visible next to the masthead one (the number printed twice on desktop)
     and `.nav-links a{padding:0}` flattened the CTA's side padding to zero, so its
     label sat flush against both edges of its own box. Both were on the render.
     This is the trap base.css documents for the desktop nav reset. */
  .nav-links .drawlab,.nav-links .drawhours,.nav-links .drawtel{display:none}
  .nav-links .navcta{margin-top:0;padding:0 18px;min-height:42px;font-size:11.5px}
}

/* ------------------------------------------------------------------- hero --
   ARCHETYPE: one full-bleed frame lit from behind. The plate fills the screen;
   the type sits in a VEIL — an unlit field laid over the plate that ends at a
   crisp edge, and that edge is the one place the light gets out. Horizontal on
   a phone (the veil ends across the frame), vertical from 900px (the veil is a
   left column and the light leaks down its right-hand side). One element, two
   configurations, the same physics.
   The hero is exempt from the orientation rule by name: it is art-directed and
   it is ours, never the client's blurry phone picture. */
.hero{position:relative;overflow:hidden;isolation:isolate;
  --hero-pad:calc(var(--nav-h) + var(--tpl-h,0px) + 6px);
  --hero-padb:34px;
  min-height:100svh;display:flex;align-items:flex-start;
  padding-top:var(--hero-pad);padding-bottom:var(--hero-padb)}
.heroplate{position:absolute;inset:0;z-index:0;background:var(--surface-2)}
.heroplate picture{display:block;height:100%}
.heroplate img{width:100%;height:100%;object-fit:cover;object-position:50% 50%}
/* the mount, on the biggest frame on the page: a dark rim on all four sides */
.heromount{position:absolute;inset:0;pointer-events:none;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.9),inset 0 0 190px 46px rgba(3,5,8,.66)}
.herowrap{position:relative;z-index:2;width:100%}
.herosay{position:relative;padding-bottom:6px}
.herosay::before{content:"";position:absolute;z-index:-1;
  /* THE PANEL, not a veil. Jon, 2026-08-14: "instead of making the whole left side
     dark, what about just putting a box around the text, and making it a little less
     dark?" So this is now a CONTAINED box behind the words only -- it starts and ends
     with the type, it has its own rim and its own bloom, and the photograph runs
     unobscured across the entire rest of the hero. Two consequences worth keeping:
     the picture is no longer half-hidden, and the panel is itself a lit object, which
     is the template's whole argument (things emit or block; this one blocks, and its
     edge leaks). Opacity is .84/.79 against the veil's old .965 -- "a little less
     dark", and the hero floor was re-measured underneath it rather than assumed. */
  /* ON A PHONE IT IS NOT A BOX, IT IS A FEATHERED SCRIM. Found by looking at a true
     390 render: a box drawn around the text plus 22px of margin IS the full width at
     390, so it read as the same full-width dark band Jon rejected on desktop — the
     photograph was barely visible behind it. There is no room on a phone for a box to
     read AS a box. So below 560px the edges and the rim come off and it feathers top
     and bottom into the picture, which is what holiday.pgh-it.com does. The alpha is
     unchanged, so the measured hero floor still holds. From 560px it becomes the box. */
  left:-22px;right:-22px;top:-40px;bottom:-40px;
  background:linear-gradient(180deg,
    rgba(6,5,4,0) 0%,rgba(6,5,4,.84) 12%,rgba(6,5,4,.79) 86%,rgba(6,5,4,0) 100%)}
@media(min-width:560px){
  .herosay::before{left:-34px;right:-34px;top:-30px;bottom:-28px;border-radius:3px;
    background:linear-gradient(180deg,rgba(6,5,4,.84) 0%,rgba(6,5,4,.79) 100%);
    box-shadow:inset 0 0 0 1px var(--rim),0 0 44px var(--halo),0 0 150px var(--halo-far)}}
.herosay .eyebrow{margin-bottom:12px}
.herolead{font-size:16.5px;line-height:1.52;max-width:44ch;margin:16px 0 22px}
.hero .btnrow{margin-bottom:6px}
/* The shop's hours and its door, at the foot of the hero column. Hidden below
   900px on purpose: the phone's first screen has no room for it and both strings
   are already in the drawer, the close and the footer, so nothing is lost. From
   900px it is pushed to the bottom of the veil with margin-top:auto, which is what
   stops a full-height dark column ending in 300px of nothing. */
.herostand{display:none}

/* THE DRAWN MOMENT, and it is EMISSION — h1b is the one place on the blocker
   where the light gets through. h1a is the unlit half of the sentence; h1b is
   near-white with a two-stage halation and a soft bloom bleeding out from
   behind the letterforms. No stroke, no bar, no underline: there is no
   decorative rule anywhere in this template. */
h1 .lit{font-style:normal;color:var(--lit);
  text-shadow:0 0 14px var(--halo),0 0 44px var(--halo),0 0 100px var(--halo-far);
  background:radial-gradient(120% 190% at 50% 54%,var(--wash) 0%,transparent 72%);
  -webkit-box-decoration-break:clone;box-decoration-break:clone}
h1{color:var(--ink-2)}

@media(min-width:900px){
  /* The panel is a CONTAINED BOX at every width now, never a full-height column.
     The hero still stretches rather than centring its wrapper: centring put the h1
     at ~309px and DESIGN-FLOOR A3 wants it above 300 at 1440. Top-anchored with a
     vh-scaled inset it lands at ~206. */
  .hero{--hero-padb:56px;align-items:stretch}
  .herowrap{display:flex;align-items:stretch}
  .herosay{flex:1 1 auto;max-width:600px;display:flex;flex-direction:column;
    padding:clamp(40px,7vh,76px) 0 40px}
  .herostand{display:flex;flex-wrap:wrap;gap:4px 20px;margin:26px 0 0;padding-top:20px;border-top:1px solid var(--line);
    font-size:14px;color:var(--ink-2)}
  .herostand b{color:var(--ink-2);font-weight:600}
  /* the box, sized to the words and nothing else — it does NOT reach the top of the
     hero, the bottom, or either edge, so the photograph is continuous around it */
  .herosay::before{left:-40px;right:-40px;top:-34px;bottom:-32px;
    width:auto;transform:none;border-radius:4px;
    background:linear-gradient(180deg,rgba(6,5,4,.84) 0%,rgba(6,5,4,.79) 100%);
    border:0;box-shadow:inset 0 0 0 1px var(--rim),0 0 52px var(--halo),0 0 180px var(--halo-far)}
  .herolead{font-size:19px;margin:20px 0 26px}
}
@media(min-width:1440px){.herosay{max-width:640px}}

/* ------------------------------------------------------------------- rail --
   Dark, dense, no imagery. Six APERTURES. A price is a rectangle with light
   behind it; that is the only place light gets through on the whole section,
   and it is why the eye lands on the figure and not on a box.
   THE EMPTY STATE, and it is the reply hook: a service with no price keeps its
   aperture and keeps its size — the light behind it is simply off. Nothing is
   invented, nothing collapses, and the page has a visible, honest reason to
   ask the owner for the one thing only they have. */
.rail{padding:72px 0 78px}
.shead{margin-bottom:34px}
.shead .lead{margin-top:16px}
.slots{border-top:var(--hair)}
.srow{display:grid;grid-template-columns:auto 1fr;gap:14px 18px;
  align-items:start;padding:22px 0 24px;border-bottom:var(--hair)}
.slot{grid-row:span 2;display:inline-flex;align-items:center;justify-content:center;
  min-width:96px;min-height:56px;padding:0 14px;
  font-family:var(--font-display);font-size:23px;font-weight:700;letter-spacing:-.03em;
  color:var(--lit);background:var(--wash);box-shadow:var(--edge),var(--bleed)}
.slot.unlit{background:rgba(0,0,0,.46);
  box-shadow:inset 0 0 0 1px var(--line),inset 0 0 20px 5px rgba(0,0,0,.72)}
.smain h3{color:var(--ink)}
.smain p{margin:7px 0 0;color:var(--ink-2);font-size:15.5px;line-height:1.55;max-width:52ch}
.stag{grid-column:2;justify-self:start;font-size:10.5px;font-weight:700;
  letter-spacing:.18em;text-transform:uppercase;color:var(--ink-3);
  padding:5px 10px;box-shadow:inset 0 0 0 1px var(--line)}

/* the hook — emitted ABOVE the first unpriced aperture only. It is rendered
   once per unpriced row and every one after the first is removed here with the
   general sibling combinator, so the markup stays a single loop. */
.hook{padding:22px 20px 24px;margin:22px 0 0;background:var(--surface-2);
  box-shadow:var(--edge),var(--bleed)}
.hook ~ .hook{display:none}
.hooklab{margin:0 0 8px;font-size:11px;font-weight:700;letter-spacing:.2em;
  text-transform:uppercase;color:var(--emit)}
.hooktxt{margin:0;color:var(--ink-2);font-size:15.5px;line-height:1.55;max-width:56ch}
.rfoot{margin:26px 0 0;color:var(--ink-3);font-size:15px}

/* extra priced groups, in the same aperture geometry */
.mgroup{margin-top:34px}
.mgh{font-size:13px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3);
  margin:0 0 6px}
.mrow{display:grid;grid-template-columns:1fr auto;gap:4px 16px;padding:12px 0;
  border-bottom:var(--hair)}
.mn{color:var(--ink);font-weight:600}
.mnote{grid-column:1;color:var(--ink-3);font-size:14px}
.mp{grid-row:1;grid-column:2;font-family:var(--font-display);font-weight:700;
  color:var(--emit)}

@media(min-width:760px){
  .rail{padding:104px 0 110px}
  .srow{grid-template-columns:132px 1fr auto;gap:0 26px;align-items:center;
    padding:24px 0 26px}
  .slot{grid-row:auto;min-width:132px;font-size:26px}
  .stag{grid-column:3;justify-self:end}
}

/* ------------------------------------------------------------------- room --
   Dark and open. A COLUMN FLOW, not a grid: the frames are set in CSS columns
   and each one is exactly as tall as the picture inside it. A portrait set and
   a landscape set produce the same page with different proportions, and no row
   can break because there are no rows. */
.shop{padding:66px 0 72px;background:var(--surface-2)}
.slabs{margin:34px 0 0;columns:1;column-gap:26px}
.slab{break-inside:avoid;margin:0 0 26px;display:block}
.slab figcaption{display:flex;justify-content:space-between;gap:14px;
  padding:11px 2px 0;border-top:var(--hair);margin-top:12px}
.slab figcaption b{font-size:14px;font-weight:700;color:var(--ink)}
.slab figcaption span{font-size:13px;color:var(--ink-3);text-align:right}
.crew{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin:6px 0 0}
/* gallery or team absent: the wrapper is removed rather than left as an empty
   box with its own margins. :empty cannot do this — the container still holds a
   whitespace text node after the loop renders nothing — so it is asked whether it
   actually contains a child. Without it the stripped state carries 40px of dead
   space that reads as a section someone forgot to finish. */
.slabs:not(:has(.slab)),.crew:not(:has(.member)){display:none}
.member h3{margin:12px 0 2px}
.member p{margin:0;color:var(--ink-3);font-size:14px}
.facts{display:grid;gap:2px;margin:36px 0 0;border-top:var(--hair)}
.fact{padding:20px 0 22px;border-bottom:var(--hair)}
.fact h3{color:var(--ink)}
.fact p{margin:7px 0 0;color:var(--ink-2);font-size:15.5px;line-height:1.55}

@media(min-width:720px){
  .shop{padding:96px 0 104px}
  .slabs{columns:2}
  .slab:nth-child(2n){margin-top:30px}
  .facts{grid-template-columns:repeat(3,1fr);gap:0 28px;margin-top:48px}
  .fact{border-bottom:0}
  .crew{grid-template-columns:repeat(4,1fr)}
}

/* ------------------------------------------------------ the light table ----
   The breath, and the argument of the whole template made once: this is the
   ONLY unobstructed plane on the page — the lightbox itself, seen with nothing
   on it. So the logic inverts. Nothing here emits; the three reasons and the
   review are BLOCKERS, opaque dark objects laid on the light, each carrying an
   occlusion halo instead of a glow. Same physics, read from the other side. */
.ltable{background:var(--band);color:var(--band-ink);padding:80px 0 86px}
.ltable h2,.ltable h3{color:var(--band-ink)}
.ltable .lead{color:var(--band-ink-2)}
.thead{margin-bottom:34px}
.tmark{height:38px;width:auto;margin:0 0 18px}
.blks{display:grid;gap:2px;margin:0}
.blk{position:relative;background:var(--surface);color:var(--ink);
  padding:26px 22px 28px;box-shadow:0 0 34px rgba(9,13,19,.30)}
.blk h3{color:var(--ink);margin:0 0 8px;padding-right:34px}
.blk p{margin:0;color:var(--ink-2);font-size:15.5px;line-height:1.55}
.bnum{position:absolute;top:22px;right:20px;font-family:var(--font-display);
  font-size:13px;font-weight:700;letter-spacing:.1em;color:var(--emit)}
/* the review slot: the one place on the light plane where nothing gets through */
.cut{margin:2px 0 0;padding:30px 24px 26px;background:var(--band-ink);color:var(--ink);
  box-shadow:0 0 40px rgba(9,13,19,.34)}
.cut blockquote{margin:0;font-family:var(--font-display);font-weight:600;
  font-size:19px;line-height:1.42;letter-spacing:-.018em}
.cut figcaption{margin-top:16px;font-size:12.5px;font-weight:700;letter-spacing:.16em;
  text-transform:uppercase;color:var(--emit)}

@media(min-width:820px){
  .ltable{padding:120px 0 126px}
  .blks{grid-template-columns:repeat(3,1fr);gap:26px}
  .cut{margin-top:26px;padding:40px 38px 34px;max-width:74ch}
  .cut blockquote{font-size:22px}
}

/* ------------------------------------------------------------------- asks --
   Dark and tight. A shut question is an unlit square; an open one lights.
   The marker is the state, so deleting it would cost information. */
.asks{padding:60px 0 66px}
.ahead{margin-bottom:24px}
.acall{margin:14px 0 0;color:var(--ink-3);font-size:15px}
.acall a{color:var(--emit);text-decoration:none;border-bottom:1px solid var(--rim)}
.asklist{border-top:var(--hair)}
.ask{border-bottom:var(--hair)}
.ask summary{display:flex;align-items:center;justify-content:space-between;gap:18px;
  padding:19px 0;min-height:44px;cursor:pointer;list-style:none;
  font-family:var(--font-display);font-size:17px;font-weight:700;letter-spacing:-.018em;
  color:var(--ink)}
.ask summary::-webkit-details-marker{display:none}
.ask summary i{flex:0 0 auto;width:11px;height:11px;
  box-shadow:inset 0 0 0 1px var(--line)}
.ask[open] summary i{background:var(--emit);box-shadow:0 0 16px var(--halo)}
.ask[open] summary{color:var(--lit)}
.ask p{margin:0 0 20px;padding-right:30px;color:var(--ink-2);font-size:15.5px;
  line-height:1.6;max-width:66ch}

@media(min-width:760px){
  .asks{padding:88px 0 94px}
  .ask summary{font-size:19px;padding:22px 0}
}

/* ------------------------------------------------------------------ close --
   The last lit surface on the page is the form. Everything else here is field. */
.close{padding:72px 0 84px;background:var(--surface-2)}
.cgrid{display:grid;gap:38px}
.cleft .lead{margin-top:16px}
.when{margin:28px 0 26px;border-top:var(--hair)}
.wrow{display:flex;justify-content:space-between;gap:18px;padding:12px 0;
  border-bottom:var(--hair)}
.wd{color:var(--ink-2);font-size:15px}
.wt{color:var(--ink);font-weight:600;font-size:15px}
/* hours[] absent — designed, not a hole. The one line a Facebook page does give
   you is set as the aperture instead, lit, and the rest of the week is asked for
   in plain words underneath it. */
.wone{padding:22px 20px 24px;background:var(--surface);box-shadow:var(--edge),var(--bleed)}
.wonelab{margin:0 0 8px;font-size:11px;font-weight:700;letter-spacing:.2em;
  text-transform:uppercase;color:var(--emit)}
.wonebig{margin:0;font-family:var(--font-display);font-size:21px;font-weight:700;
  letter-spacing:-.024em;color:var(--lit);line-height:1.24}
.woneask{margin:12px 0 0;color:var(--ink-3);font-size:14.5px;line-height:1.55}
.crow{display:grid;gap:6px}
.cbig{font-family:var(--font-display);font-size:20px;font-weight:700;
  letter-spacing:-.026em;color:var(--ink);text-decoration:none}
.cbig:hover{color:var(--emit)}
.crow address{font-style:normal;color:var(--ink-3);font-size:15px;margin-top:6px}
.area{margin-top:28px;padding-top:22px;border-top:var(--hair)}
.area h3{color:var(--ink)}
.area p{margin:8px 0 0;color:var(--ink-2);font-size:15.5px;line-height:1.55}
.towns{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.towns span{font-size:12px;font-weight:600;letter-spacing:.06em;color:var(--ink-3);
  padding:5px 10px;box-shadow:inset 0 0 0 1px var(--line)}

.formpanel{padding:24px 20px 26px;background:var(--surface);
  box-shadow:var(--edge),var(--bleed-lg)}
.formpanel .field input,.formpanel .field select,.formpanel .field textarea{
  background:var(--surface-2);border-color:var(--line);border-radius:0}
.formpanel .field input:focus,.formpanel .field select:focus,
.formpanel .field textarea:focus{border-color:var(--emit);
  box-shadow:0 0 0 1px var(--rim),0 0 26px var(--halo)}
.formpanel .field label{color:var(--ink-3)}
.fineprint{margin:14px 0 0;font-size:13px;line-height:1.5;color:var(--ink-3)}
.thanks{background:var(--surface);border:0;box-shadow:var(--edge),var(--bleed)}
.thanks h3{color:var(--lit)}
.thanks p{margin:0;color:var(--ink-2)}

@media(min-width:960px){
  .close{padding:108px 0 116px}
  .cgrid{grid-template-columns:1fr 1fr;gap:56px;align-items:start}
  .formpanel{padding:34px 32px 36px}
}

/* ----------------------------------------------------------------- footer -- */
footer{background:var(--surface);border-top:1px solid var(--rim);
  box-shadow:inset 0 0 90px -34px var(--halo-far);padding:52px 0 34px}
.fgrid{display:grid;gap:30px}
.fmark{display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;font-family:var(--font-display);font-size:15px;font-weight:700;
  letter-spacing:.04em;color:var(--emit);background:var(--wash);
  box-shadow:var(--edge),0 0 26px var(--halo-far)}
.fname{margin:16px 0 6px;font-family:var(--font-display);font-size:20px;font-weight:700;
  letter-spacing:-.028em;color:var(--ink)}
.fabout{margin:0;color:var(--ink-3);font-size:14.5px;line-height:1.55;max-width:42ch}
.fh{font-size:11px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;
  color:var(--ink-3);margin:0 0 12px}
.fcol a,.fplain{display:block;color:var(--ink-2);font-size:15px;text-decoration:none;
  padding:3px 0}
.fcol a:hover{color:var(--emit)}
.fcol address{font-style:normal;color:var(--ink-2);font-size:15px;padding:3px 0}
.fnav{display:flex;flex-wrap:wrap;gap:8px 22px;margin:34px 0 0;padding:22px 0 0;
  border-top:var(--hair)}
.fnav a{color:var(--ink-3);font-size:14px}
.fnav a:hover{color:var(--ink)}
.fbase{display:flex;flex-wrap:wrap;gap:8px 20px;justify-content:space-between;
  margin-top:22px;padding-top:18px;border-top:var(--hair);
  color:var(--ink-3);font-size:13px}
.credit a{color:var(--ink-2)}

@media(min-width:760px){
  footer{padding:72px 0 42px}
  .fgrid{grid-template-columns:1.5fr 1fr 1fr;gap:44px}
}

/* -------------------------------------------------------------------- 404 --
   A designed corner, not an inherited one. The numeral is the only emitting
   thing on the page and the plate beside it is a slab like any other — the
   asset sets its own box there too. */
.nf{padding:70px 0 80px}
.nfgrid{display:grid;gap:34px;align-items:center}
.nfnum{display:block;font-family:var(--font-display);font-size:15px;font-weight:700;
  letter-spacing:.28em;color:var(--emit);margin:0 0 18px;
  text-shadow:0 0 18px var(--halo),0 0 60px var(--halo-far)}
.nfsay .lead{margin-top:16px}
.nfline{display:flex;flex-wrap:wrap;gap:6px 18px;margin:24px 0 0;
  color:var(--ink-3);font-size:14.5px}
.nfline b{color:var(--ink-2);font-weight:600}
.nfline a{color:var(--emit);text-decoration:none}
.nfslab{display:block}

@media(min-width:900px){
  .nf{padding:104px 0 118px}
  .nfgrid{grid-template-columns:1.25fr 1fr;gap:60px}
}


/* =============================================================== THE BOARD ==
   The shared services section (base.css THE BOARD, partials/board.html), lit by
   this template's one light. Only tokens and colour are set here — the grid, the
   cell layout, the edge accent and the hook are inherited and must not be
   re-solved. Jon, 2026-08-14: one services section for every skeleton.

   THE APERTURE SURVIVES AS THE ICON. On this page light comes from behind
   things; the icon plate is the only opening in the section, so the glyph EMITS
   (--emit halation) and the price is the plain figure beside it. That is the
   same idea the old price slot carried, moved to the element that now leads the
   row — the price is optional and the icon is not, so the light belongs on it. */
.slots .board{--bd-gap:12px;--bd-cell-pad:17px;--bd-price:22px;
  --bd-glow:var(--board-glow);
  --bd-edge:linear-gradient(90deg,var(--emit) 0%,var(--halo) 55%,transparent 100%)}
@media(min-width:900px){.slots .board{--bd-icon:54px;--bd-glyph:27px;--bd-price:25px}}
.slots .bicon{background:var(--surface-2);color:var(--lit);
  box-shadow:inset 0 0 0 1px var(--line),0 0 22px var(--halo-far)}
.slots .bname{font-family:var(--font-display);font-weight:600;font-size:19px;
  letter-spacing:-.015em;color:var(--ink)}
.slots .bprice{font-family:var(--font-display);font-weight:700;
  color:var(--lit);text-shadow:0 0 14px var(--halo),0 0 40px var(--halo-far)}
.slots .btag{border-radius:2px;font-size:10px;letter-spacing:.13em;color:var(--ink-3)}
.slots .bdesc{color:var(--ink-2)}
.slots .bhook{box-shadow:0 0 26px var(--halo-far)}
.slots .bhook-l{color:var(--emit)}


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