/* OneWay Remodel - design system
   Charcoal #141518 / gold #D89B2C. Inter Tight 800 display, Inter body, self-hosted.
   Everything below sits on one 8px-derived spacing scale and one modular type scale.
   No raw values in components: if a number is needed, it becomes a token first. */

@font-face{font-family:"Playfair Display";src:url("../fonts/playfair-latin.woff2") format("woff2");
  font-weight:400 900;font-display:swap;font-style:normal}
@font-face{font-family:"Source Sans 3";src:url("../fonts/sourcesans-latin.woff2") format("woff2");
  font-weight:200 900;font-display:swap;font-style:normal}

:root{
  /* colour */
  --ink:#141518; --ink-2:#1c1e22; --ink-3:#26292e;
  --gold:#D89B2C; --gold-dim:#8E6212;
  /* gold-dim darkened from #a8741a: the old value measured 3.60:1 on paper, which
     fails AA for the small uppercase text it decorates. #8E6212 measures 4.77:1. */

  /* Timber accent tones (cedar, oak) were trialled here and pulled: client read
     them as a stray second colour. Gold is the only accent. */
  --paper:#F4F1EA; --paper-2:#E9E4D9;
  --on-ink:#EDEAE4; --on-ink-mute:#A8A49C;
  --on-paper:#1a1b1e; --on-paper-mute:#54575e;
  --rule:rgba(237,234,228,.14); --rule-paper:rgba(26,27,30,.13);

  /* spacing scale, 8px derived */
  --s-1:.25rem; --s-2:.5rem;  --s-3:.75rem; --s-4:1rem;  --s-5:1.5rem;
  --s-6:2rem;   --s-7:3rem;   --s-8:4rem;   --s-9:6rem;  --s-10:8rem;

  /* fluid rhythm: one section step, one block step, one gutter. Used everywhere. */
  --sec-y:clamp(2.6rem,4.2vw,4.75rem);         /* section padding block; gaps are 2x this */
  --block:clamp(var(--s-6),3.4vw,var(--s-7));  /* gap between blocks in a section */
  --gut:clamp(var(--s-5),4.4vw,var(--s-8));    /* page gutter */
  --wrap:1320px;
  --measure:780px;                             /* prose measure; px, not ch (see notes) */

  /* type scale */
  --fs-xs:.72rem; --fs-sm:.85rem; --fs-base:clamp(.95rem,.9rem + .2vw,1.05rem);
  --fs-lead:clamp(1.05rem,.98rem + .5vw,1.3rem);
  --fs-h3:clamp(1.2rem,1.05rem + .8vw,1.65rem);
  --fs-h2:clamp(1.9rem,1.1rem + 3.2vw,3.4rem);
  --fs-h1:clamp(2.2rem,1.3rem + 3.6vw,4.2rem);
  --fs-hero:clamp(2.2rem,1.1rem + 3.5vw,4.6rem);    /* hero h1 only */
  --fs-hero-2:clamp(1.9rem,1rem + 3.1vw,4rem);      /* hero h2 */

  --d:"Playfair Display",Georgia,"Times New Roman",serif;
  --b:"Source Sans 3",system-ui,-apple-system,Segoe UI,sans-serif;
  --dur:.22s;
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{margin:0;background:var(--ink);color:var(--on-ink);font-family:var(--b);
  font-size:var(--fs-base);line-height:1.65;-webkit-font-smoothing:antialiased;
  overflow-x:clip}
  /* clip, not hidden: [data-reveal="right"] sits at translate3d(40px) until it
     reveals, which made pages horizontally scrollable at 375px. clip removes the
     scrollable overflow without creating a scroll container, so the sticky hero
     stage keeps pinning. */
img{max-width:100%;height:auto;display:block}
a{color:inherit}
:focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:2px}

h1,h2,h3{font-family:var(--d);font-weight:700;line-height:1.08;letter-spacing:-.012em;
  margin:0;text-wrap:balance}
h1{font-size:var(--fs-h1)}
h2{font-size:var(--fs-h2)}
h3{font-size:var(--fs-h3);letter-spacing:-.004em;line-height:1.24;font-weight:700}
p{margin:0 0 var(--s-4)}
p:last-child{margin-bottom:0}
.hl{color:var(--gold);font-weight:800}
.hero .eyebrow{color:#fff;text-shadow:0 1px 14px rgba(0,0,0,.55)}
.sec-paper .hl{color:var(--gold-dim)}
.lede{font-size:var(--fs-lead);line-height:1.6;color:var(--on-ink-mute);max-width:54ch;font-weight:400}
.eyebrow{font-family:var(--b);font-weight:600;font-size:var(--fs-sm);letter-spacing:.2em;
  text-transform:uppercase;color:var(--gold);margin:0 0 var(--s-5)}
.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:var(--gut)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* stack utility: one rhythm, no ad-hoc margins */
.stack>*+*{margin-top:var(--s-5)}
.stack-lg>*+*{margin-top:var(--block)}
.mt-block{margin-top:var(--block)}

/* ---- buttons ---- */
.btn-row{display:flex;flex-wrap:wrap;gap:var(--s-3)}
.btn{display:inline-flex;align-items:center;gap:var(--s-2);font-family:var(--b);font-weight:600;
  font-size:clamp(.9rem,.85rem + .15vw,1rem);letter-spacing:.01em;padding:var(--s-3) var(--s-5);border-radius:999px;cursor:pointer;
  text-decoration:none;border:1px solid transparent;min-height:44px;
  transition:background var(--dur),color var(--dur),border-color var(--dur)}
.btn-gold{background:var(--gold);color:#17130a}
.btn-gold:hover{background:#e9ad42}
.btn-ghost{border-color:var(--rule);color:var(--on-ink)}
.btn-ghost:hover{border-color:var(--gold);color:var(--gold)}
.btn-dark{background:var(--ink);color:var(--on-ink)}
.btn-dark:hover{background:var(--ink-3)}

/* ---- header ---- */
.hdr{position:absolute;inset:0 0 auto;z-index:20;padding-block:var(--s-6)}
.hdr-in{display:flex;align-items:center;gap:var(--s-5)}
.brand{display:flex;align-items:center;gap:var(--s-4);text-decoration:none;font-family:var(--d);
  font-weight:700;font-size:clamp(1.3rem,1.05rem + .7vw,1.75rem);letter-spacing:-.01em;line-height:1}
.brand img{width:clamp(62px,5vw,84px);height:auto;object-fit:contain;flex:none}
.nav{margin-left:auto;display:flex;align-items:center;gap:clamp(var(--s-4),1.8vw,var(--s-6))}
.nav a{font-family:var(--b);font-weight:600;font-size:clamp(.82rem,.76rem + .18vw,.95rem);
  letter-spacing:.11em;text-transform:uppercase;text-decoration:none;transition:color var(--dur)}
.nav a:hover{color:var(--gold)}
.nav-menu{list-style:none;margin:0;padding:0;display:flex;align-items:center;
  gap:clamp(var(--s-4),1.8vw,var(--s-6))}
.nav-item{position:relative;display:flex;align-items:center}
.nav-toggle,.sub-toggle{font-family:var(--b);background:none;border:0;cursor:pointer;color:inherit}
.nav-toggle{display:none}

/* Header call CTA. Desktop shows the full number. Below 640px the number is dropped and
   the label reads "Call now", because "Call 210-946-9110" wraps to three lines on a phone,
   blowing the button to 113px tall and the header to 177px. The aria-label carries the
   number at every width, so screen readers never lose it. */
.nav-call{white-space:nowrap}
/* The label is one flex item. Nesting the toggling spans inside it matters: direct
   children of an inline-flex button are blockified as flex items, so display:inline
   on them is ignored and the label breaks onto two lines. */
.nav-call-label{display:inline}
.nav-call-short{display:none}
@media(max-width:640px){
  .nav-call-num{display:none}
  .nav-call-short{display:inline}
}
.sub-toggle{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;
  padding:0;margin-inline-start:calc(-1 * var(--s-3));transition:color var(--dur)}
.sub-toggle:hover{color:var(--gold)}
.sub-toggle svg{width:.7rem;height:.7rem;transition:transform var(--dur)}
.sub-toggle[aria-expanded="true"] svg{transform:rotate(180deg)}
/* Dropdown panel. Solid ink so it reads over any hero or paper section below,
   and z-index above the header row so nothing under it bleeds through. */
.sub{list-style:none;margin:0;padding:var(--s-2);display:none;position:absolute;top:100%;
  left:calc(-1 * var(--s-4));z-index:30;min-width:16rem;background:var(--ink-2);
  border:1px solid var(--rule);border-radius:3px;box-shadow:0 var(--s-3) var(--s-6) rgba(0,0,0,.5)}
.has-sub.open>.sub{display:block}
.sub a{display:flex;align-items:center;min-height:44px;padding:var(--s-2) var(--s-4);
  border-radius:2px;white-space:nowrap;color:var(--on-ink)}
.sub a:hover{background:var(--ink-3);color:var(--gold)}
/* JS off: desktop menus open on hover or keyboard focus, dead buttons hidden. */
.no-js .sub-toggle{display:none}
.no-js .has-sub:hover>.sub,.no-js .has-sub:focus-within>.sub{display:block}
@media(max-width:880px){
  .js .nav-toggle{display:inline-flex;align-items:center;justify-content:center;min-height:44px;
    padding:var(--s-2) var(--s-4);border:1px solid var(--rule);border-radius:999px;font-weight:600;
    font-size:clamp(.82rem,.76rem + .18vw,.95rem);letter-spacing:.11em;text-transform:uppercase;
    transition:color var(--dur),border-color var(--dur)}
  .js .nav-toggle:hover,.js .nav-toggle[aria-expanded="true"]{color:var(--gold);border-color:var(--gold)}
  .nav-menu{display:none;position:absolute;top:100%;left:var(--gut);right:var(--gut);z-index:30;
    flex-direction:column;align-items:stretch;gap:0;padding:var(--s-2);background:var(--ink-2);
    border:1px solid var(--rule);border-radius:3px;box-shadow:0 var(--s-3) var(--s-6) rgba(0,0,0,.5);
    max-height:calc(100svh - var(--s-10));overflow:auto}
  .js .nav-menu.open{display:flex}
  .nav-item{flex-wrap:wrap}
  .nav-item+.nav-item{border-top:1px solid var(--rule)}
  .nav-menu a{display:flex;align-items:center;flex:1;min-height:44px;padding:var(--s-2) var(--s-4)}
  .sub-toggle{margin-inline-start:0;flex:none}
  .sub{position:static;flex-basis:100%;min-width:0;padding:0 0 var(--s-2);
    border:0;border-radius:0;box-shadow:none;background:none}
  .sub a{padding-inline-start:var(--s-6);white-space:normal;color:var(--on-ink-mute)}
  .sub a:hover{background:var(--ink-3);color:var(--gold)}
  /* JS off: no working toggle, so the whole menu renders open and stacked. */
  .no-js .nav-menu{display:flex}
  .no-js .sub{display:block}
}

/* ---- Google reviews ---- */
/* Tiles paint no border of their own; the divider is a box-shadow ring on the tile, the
   same convention as .cards, so an unrevealed tile is genuinely invisible. */
.rvws{list-style:none;margin:0;padding:0;display:grid;gap:var(--s-4);grid-template-columns:1fr}
@media(min-width:680px){.rvws{grid-template-columns:1fr 1fr}}
@media(min-width:1080px){.rvws{grid-template-columns:repeat(3,1fr)}}
.rvw{display:flex;flex-direction:column;gap:var(--s-4);padding:var(--s-5);
  border-radius:3px;background:var(--ink-2);box-shadow:0 0 0 1px var(--rule)}
.rvw-stars{margin:0;line-height:1;color:var(--gold);letter-spacing:.16em;font-size:.85rem}
.rvw blockquote{margin:0}
.rvw blockquote p{margin:0;font-size:var(--fs-sm);line-height:1.7;color:var(--on-ink)}
.rvw-by{margin:auto 0 0;padding-top:var(--s-4);border-top:1px solid var(--rule);
  display:flex;flex-direction:column;gap:1px;
  font-family:var(--b);font-size:var(--fs-xs);color:var(--on-ink-mute)}
.rvw-by b{color:var(--on-ink);font-weight:600;letter-spacing:.01em}
.rvws-foot{margin-top:var(--s-6);display:flex;align-items:center;
  gap:var(--s-4) var(--s-5);flex-wrap:wrap}
.rvws-note{margin:0;font-size:var(--fs-xs);color:var(--on-ink-mute)}
/* Visually hidden, still read aloud. The stars are decorative glyphs. */
.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ---- scroll hero ---- */
.hero{position:relative;height:400vh;background:var(--ink)}
.hero-stage{position:sticky;top:0;height:100svh;overflow:hidden}
.hero-media{position:absolute;inset:0}
.hero-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transform:scale(1.06);transition:opacity .9s ease,transform 1.4s ease;will-change:opacity,transform}
.hero-media img.on{opacity:1;transform:scale(1)}
.hero-media::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(20,21,24,.66) 0%,rgba(20,21,24,.30) 30%,rgba(20,21,24,.30) 52%,rgba(20,21,24,.86) 100%)}
.hero-copy{position:relative;z-index:2;height:100%;display:grid}
.hero-copy .wrap{width:100%;height:100%;display:grid;max-width:1600px;
  padding-block:clamp(var(--s-9),16vh,10rem)}
.moment{grid-area:1/1;width:100%;opacity:0;transform:translateY(38px);
  transition:opacity .62s ease,transform .62s ease;pointer-events:none;
  align-self:end;justify-self:start}
.moment.on{opacity:1;transform:none;pointer-events:auto}
.moment>*+*{margin-top:var(--s-5)}
.moment h1{font-size:var(--fs-hero);max-width:24ch;letter-spacing:-.018em;font-weight:700}
.moment h2{font-size:var(--fs-hero-2);max-width:20ch;letter-spacing:-.016em;font-weight:700}
.moment .lede{max-width:44ch;color:#e5e1d9;font-weight:400;font-size:var(--fs-lead)}

/* credential row: turns a wall of small print into a designed element */
.meta-row{display:flex;flex-wrap:wrap;gap:var(--s-2) var(--s-4);align-items:center;
  font-family:var(--b);font-size:var(--fs-sm);font-weight:500;letter-spacing:.06em;
  text-transform:uppercase;color:var(--on-ink-mute)}
.meta-row span{display:inline-flex;align-items:center;gap:var(--s-3)}
.meta-row span+span::before{content:"";width:5px;height:5px;border-radius:50%;
  background:var(--gold);opacity:.85}

.hero-steps{position:absolute;left:50%;transform:translateX(-50%);bottom:var(--s-6);z-index:3;
  display:flex;gap:var(--s-2)}
.hero-steps span{width:38px;height:3px;background:var(--rule);border-radius:2px;transition:background .3s}
.hero-steps span.on{background:var(--gold)}

/* Four distinct zones so the eye is not parked in one corner for the whole scroll. */
@media(min-width:960px){
  .moment{width:min(64%,56rem)}
  .moment.pos-bl{align-self:end;   justify-self:start}
  .moment.pos-tr{align-self:start; justify-self:end;   text-align:right}
  .moment.pos-br{align-self:end;   justify-self:end;   text-align:right}
  .moment.pos-cl{align-self:center;justify-self:start}
  .moment.pos-bl h1{max-width:24ch}
  .pos-tr .lede,.pos-br .lede{margin-left:auto}
  .pos-tr h2,.pos-br h2{margin-left:auto}
  .pos-tr .meta-row,.pos-br .meta-row,.pos-tr .btn-row,.pos-br .btn-row{justify-content:flex-end}
}
/* no-JS and reduced-motion: everything visible, nothing pinned, no fancy placement */
.no-js .hero,html.reduce .hero{height:auto}
.no-js .hero-stage,html.reduce .hero-stage{position:static;height:auto;padding-block:var(--s-10) var(--s-9)}
.no-js .hero-media img:first-child,html.reduce .hero-media img:first-child{opacity:1;transform:none}
.no-js .hero-copy,html.reduce .hero-copy{display:block}
.no-js .hero-copy .wrap,html.reduce .hero-copy .wrap{display:block;height:auto}
.no-js .moment,html.reduce .moment{opacity:1;transform:none;width:100%;margin:0 0 var(--s-9);
  pointer-events:auto;text-align:left}
.no-js .hero-steps,html.reduce .hero-steps{display:none}

/* ---- proof bar ---- */
.proof{background:var(--ink-2);border-block:1px solid var(--rule)}
.proof ul{list-style:none;margin:0;padding:var(--s-6) 0;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:var(--s-4) var(--s-5);
  font-family:var(--b);font-weight:600;font-size:var(--fs-sm);letter-spacing:.1em;
  text-transform:uppercase;color:var(--on-ink-mute);text-align:center}
.proof li{display:flex;align-items:center;justify-content:center;gap:var(--s-2);
  min-height:2.4em;line-height:1.3}
.proof li+li{border-left:1px solid var(--rule)}
@media(max-width:900px){.proof li+li{border-left:0}}
.proof b{color:var(--gold)}

/* ---- sections: one rhythm everywhere ---- */
.sec{padding-block:var(--sec-y)}
.sec-flush-top{padding-top:0}
.sec-paper{background:var(--paper);color:var(--on-paper)}
.sec-paper .lede{color:var(--on-paper-mute)}
.sec-paper .eyebrow{color:var(--gold-dim)}
.sec-head{display:grid;gap:var(--s-5);grid-template-columns:1fr;align-items:start;
  margin-bottom:var(--block)}
.sec-head>*{min-width:0}
.sec-head .eyebrow{margin-bottom:0}
@media(min-width:900px){
  .sec-head{grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);
    grid-template-areas:"eyebrow eyebrow" "title lede";
    column-gap:var(--s-8);row-gap:var(--s-5)}
  .sec-head .eyebrow{grid-area:eyebrow}
  .sec-head h2{grid-area:title}
  /* gold rule divides heading from description. Fallback nudge for browsers without
     text-box-trim; the @supports block below replaces it with real cap alignment. */
  .sec-head .lede{grid-area:lede;padding-top:.3em;padding-left:var(--s-6);
    border-left:2px solid var(--gold)}
  .sec-paper .sec-head .lede{border-left-color:var(--gold-dim)}
}

/* Cap-line alignment, measured fix.
   The old .3em nudge was a constant scaled off the LEDE's font size, so it could not
   track an h2 that fluidly scales 1.9rem to 3.4rem. Measured 15.2px of drop at 1037px
   and it widened as the viewport grew.
   Trimming the START edge to the cap height means grid align-items:start aligns the
   actual letterforms rather than the line boxes, so the heading and the lede share a
   cap line at every viewport with no magic number.
   Start-edge only: trim-both also trimmed below the last baseline, so a two-line
   lede reported a box 10px shorter than its own text and the next section painted
   over the second line. */
@supports (text-box: trim-start cap alphabetic){
  @media(min-width:900px){
    .sec-head h2,
    .sec-head .lede{text-box:trim-start cap alphabetic}
    .sec-head .lede{padding-top:0}
  }
}

/* ---- service cards ---- */
/* Dividers are drawn by each card's own box-shadow ring, never by a container
   background showing through gaps. A card at opacity 0 therefore paints nothing:
   no grey slab can appear while neighbours are still revealing. */
.cards{display:grid;gap:1px}
.card{box-shadow:0 0 0 1px var(--rule-paper)}
/* Equal rows only where cards sit side by side. In the single column stack below
   820px, 1fr rows stretched every card to the tallest one, leaving up to 150px of
   blank paper between a short description and its See the work line. */
@media(min-width:820px){.cards{grid-template-columns:1fr 1fr;grid-auto-rows:1fr}}
.card{display:grid;grid-template-columns:1fr;background:var(--paper);text-decoration:none;color:var(--on-paper);
  min-height:100%}
@media(min-width:520px){.card{grid-template-columns:38% 1fr}}
.card-img{position:relative;overflow:hidden;min-height:200px}
.card-img img{width:100%;height:100%;object-fit:cover;transition:transform .5s ease}
.card:hover .card-img img{transform:scale(1.04)}
.card-num{position:absolute;z-index:2;left:var(--s-3);bottom:var(--s-3);background:var(--gold);color:#17130a;
  font-family:var(--b);font-weight:700;font-size:.7rem;padding:.15rem .5rem;border-radius:3px}
.card-body{padding:var(--s-6);display:flex;flex-direction:column}
.card-body p{margin:var(--s-3) 0 0;color:var(--on-paper-mute);font-size:.95rem}
.card-more{margin-top:auto;padding-top:var(--s-5);font-family:var(--b);font-weight:600;
  font-size:var(--fs-xs);letter-spacing:.12em;text-transform:uppercase;color:var(--gold-dim)}

/* ---- media blocks ---- */
.ba{display:grid;gap:var(--s-5)}
@media(min-width:760px){.ba{grid-template-columns:1fr 1fr}}
.ba figure,.grid3 figure{margin:0}
.ba img,.grid3 img{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:2px}
.ba figcaption{margin-top:var(--s-3);font-family:var(--b);font-weight:600;font-size:var(--fs-xs);
  letter-spacing:.14em;text-transform:uppercase;color:var(--on-ink-mute)}
/* Animated before/after: poster img keeps the layout, video fades in over it
   exactly like the vhero. Reduced motion hides the video entirely. */
.ba-stage{position:relative;overflow:hidden;border-radius:2px}
.ba-stage video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity 1.1s ease}
.ba-stage video.on{opacity:1}
html.reduce .ba-stage video{display:none}

/* About page portrait entrance. Same stage pattern as the before/after, but the video
   carries no loop attribute: it runs once and rests on its own final frame, which is the
   real photograph sitting underneath it. The still is what remains, so with reduced
   motion or no JS the page simply shows the photograph. */
.bio-stage{position:relative;overflow:hidden;border-radius:2px}
.bio-stage video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .5s ease}
.bio-stage video.on{opacity:1}
html.reduce .bio-stage video{display:none}
.ba figcaption::before{content:"";display:inline-block;width:var(--s-5);height:2px;
  background:var(--gold);margin-right:var(--s-3);vertical-align:middle}
.sec-paper .ba figcaption::before{background:var(--gold-dim)}
.sec-paper .ba figcaption,.sec-paper .grid3 figcaption{color:var(--on-paper-mute)}
.grid3{display:grid;gap:var(--s-5);grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
.grid3 figcaption{margin-top:var(--s-3);font-size:var(--fs-sm);color:var(--on-ink-mute)}
.figure-wide img{width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:2px}

/* ---- prose ----
   .prose governs typography and internal rhythm ONLY. It must never set width,
   max-width, or margin-inline: it shares elements with .wrap (class="wrap prose"),
   selectors tie on single-class specificity, and the later rule silently wins.
   That trap has now fired twice: .prose{max-width:none} once beat .wrap's
   max-width, and .prose{margin-inline:0} beat .wrap's margin-inline:auto and
   pinned 14 blocks to the left edge. Box geometry belongs to .wrap or to a
   child-scoped modifier like .prose-mid below. */
.prose p,.prose ul,.prose ol{max-width:var(--measure)}
/* Essay pages (About): a full-wrap prose block pinned left leaves ~540px of dead
   space down the right side. .prose-mid centres the measure inside the wrap while
   the text itself stays left-aligned. Child-scoped, so it cannot collide with
   .wrap on the shared element. */
.prose-mid>*{max-width:var(--measure);margin-inline:auto}
.prose h2{margin-top:var(--s-8);margin-bottom:var(--s-5)}

/* Icons on the four About page statements. Stroked, not filled, so they sit with the
   editorial type instead of competing with it, and sized in em so they track the fluid
   heading scale. Set at .68em: clearly subordinate to the words, which carry the meaning.
   The headings are the accessible label, which is why the SVGs are aria-hidden.
   The h2 becomes a flex row only where an icon is present, so a heading that wraps keeps
   its text in one column rather than tucking under the icon. */
.prose h2:has(> .h2-ico){display:flex;align-items:flex-start;gap:var(--s-3)}
.h2-ico{flex:none;width:.68em;height:.68em;margin-top:.2em;
  fill:none;stroke:var(--gold);stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.sec-paper .h2-ico{stroke:var(--gold-dim)}
.prose > *:first-child{margin-top:0}
.prose h3{margin-top:var(--s-6);margin-bottom:var(--s-4)}
/* Inline heading icons (About page trust section). Decorative line icons that sit
   inside the h2 text flow, so heading margins and the h2 to paragraph rhythm are
   untouched. Colour follows the ground: gold on ink, gold-dim on paper. */
.h2-ico{display:inline-block;width:var(--s-6);height:var(--s-6);margin-right:var(--s-3);
  vertical-align:-.12em;fill:none;stroke:var(--gold);stroke-width:1.75;
  stroke-linecap:round;stroke-linejoin:round}
.sec-paper .h2-ico{stroke:var(--gold-dim)}
.prose ul{padding-left:var(--s-4);margin:0 0 var(--s-4)}
.prose li{margin-bottom:var(--s-2)}
.answer{border-left:3px solid var(--gold);padding-left:var(--s-5);margin:var(--s-5) 0;
  font-size:var(--fs-lead);line-height:1.55;max-width:860px}

/* ---- areas ---- */
/* Centred sections have exactly one job: symmetric content. Today that means
   the town chip lists (home #areas and the areas hub) and the short Related
   work / Nearby outro rows on service and town pages, which read lopsided when
   pinned left inside a 1300px wrap between two centred neighbours. Everything
   else uses the two-column title/lede split. Do not centre a head over
   left-aligned content, and do not left-align one over chips. */
.sec-center .sec-head{grid-template-columns:1fr;grid-template-areas:none;
  justify-items:center;text-align:center}
.sec-center .sec-head>*{grid-area:auto}
.sec-center .sec-head .lede{padding:0;border-left:0;margin-inline:auto}
.sec-center .prose{text-align:center}
.sec-center .prose h2{margin-top:0}
.sec-center .prose p,.sec-center .prose ul,.sec-center .prose ol{margin-inline:auto}
/* Outro band: the Related work / Nearby signposts. Two lines of wayfinding, not a
   content section: h3-scale heading, tightened padding, so it reads as a compact
   band between the FAQ and the CTA instead of a mostly empty full section. */
.sec-outro{padding-block:var(--s-6)}
.sec-outro .prose h2{font-size:var(--fs-h3);margin-bottom:var(--s-3)}
.chips{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--s-3);list-style:none;
  padding:0;margin:0}
.chips a{display:inline-flex;align-items:center;padding:var(--s-3) var(--s-6);border:1px solid var(--rule);
  border-radius:999px;text-decoration:none;font-size:.92rem;min-height:44px;
  transition:border-color var(--dur),color var(--dur)}
.chips a:hover{border-color:var(--gold);color:var(--gold)}
.sec-paper .chips a{border-color:var(--rule-paper)}

/* ---- cta ---- */
.cta{background:var(--gold);color:#17130a;text-align:center}
.cta h2{max-width:17ch;margin-inline:auto}
.cta p{max-width:54ch;margin:var(--s-5) auto 0;color:#3d3216}
.cta .btn-row{justify-content:center;margin-top:var(--block)}

/* ---- footer ---- */
/* Footer breathes on the same section step as everything above it. It used to sit
   on --s-9/--s-7 (96px/48px at 1280 against 53.76px sections), which made the page
   bottom the one place with a different rhythm. The extra visual weight the footer
   needs comes from .ft-base's own internal margin, not from section padding. */
.ft{background:var(--ink-2);border-top:1px solid var(--rule);padding-block:var(--sec-y);
  font-size:.92rem}
.ft-grid{display:grid;gap:var(--s-7) var(--s-8);grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
@media(min-width:900px){.ft-grid{grid-template-columns:1.5fr 1fr 1fr 1fr}}
.ft-grid>div{min-width:0}
.ft h3{font-family:var(--b);font-weight:600;font-size:var(--fs-xs);letter-spacing:.14em;
  text-transform:uppercase;color:var(--gold);margin-bottom:var(--s-4)}
.ft p{color:var(--on-ink-mute);max-width:38ch;margin:0}
.ft ul{list-style:none;padding:0;margin:0}
.ft li{margin-bottom:var(--s-2)}
.ft li:last-child{margin-bottom:0}
.ft a{text-decoration:none;color:var(--on-ink-mute);transition:color var(--dur)}
.ft a:hover{color:var(--on-ink)}
.ft-base{margin-top:var(--s-8);padding-top:var(--s-5);border-top:1px solid var(--rule);
  color:var(--on-ink-mute);font-size:var(--fs-sm);display:flex;flex-wrap:wrap;
  justify-content:center;align-items:center;text-align:center;gap:var(--s-2) var(--s-5)}
.ft-base span+span::before{content:"";display:inline-block;width:4px;height:4px;border-radius:50%;
  background:var(--gold);opacity:.7;margin-right:var(--s-5);vertical-align:middle}

/* ---- breadcrumb ---- */
.crumb{padding-block:var(--s-10) 0;font-size:var(--fs-sm);color:var(--on-ink-mute)}
.crumb a{text-decoration:none}
.crumb a:hover{color:var(--gold)}

@media(prefers-reduced-motion:reduce){
  *{animation-duration:.001ms!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
}

/* ============================================================
   MOTION
   Reveals animate opacity + transform only. Nothing here moves
   layout, and everything is visible by default so JS-off and
   reduced-motion users lose nothing.
   ============================================================ */
[data-reveal]{opacity:1}
.js [data-reveal]{opacity:0;transition:opacity .72s cubic-bezier(.22,.61,.36,1),
  transform .72s cubic-bezier(.22,.61,.36,1);transition-delay:var(--d,0ms);will-change:opacity,transform}
.js [data-reveal="up"]     {transform:translate3d(0,34px,0)}
.js [data-reveal="left"]   {transform:translate3d(-40px,0,0)}
.js [data-reveal="right"]  {transform:translate3d(40px,0,0)}
.js [data-reveal="scale"]  {transform:scale(.965)}
.js [data-reveal="mask"]   {transform:translate3d(0,26px,0);clip-path:inset(12% 0 0 0)}
.js [data-reveal].in{opacity:1;transform:none;clip-path:inset(0 0 0 0);will-change:auto}
html.reduce [data-reveal],html.reduce .js [data-reveal]{opacity:1!important;transform:none!important;
  clip-path:none!important;transition:none!important}

/* headings get a slightly longer, softer arrival than body copy */
.js h1[data-reveal],.js h2[data-reveal]{transition-duration:.86s}

/* ============================================================
   GALLERY
   ============================================================ */
.gal{position:relative;border-radius:3px;overflow:hidden;background:var(--ink-2)}
.gal-stage{position:relative;aspect-ratio:16/10}
.gal-stage img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transform:scale(1.035);transition:opacity .85s ease,transform 1.6s ease}
.gal-stage img.on{opacity:1;transform:scale(1)}
.gal-shade{position:absolute;inset:auto 0 0 0;height:46%;pointer-events:none;
  background:linear-gradient(180deg,transparent,rgba(20,21,24,.82))}
.gal-bar{position:absolute;inset:auto 0 0 0;display:flex;align-items:center;gap:var(--s-4);
  padding:var(--s-5) var(--s-5);color:var(--on-ink)}
.gal-cap{font-family:var(--b);font-size:var(--fs-sm);font-weight:500;letter-spacing:.06em;
  text-transform:uppercase;color:#fff;text-shadow:0 1px 12px rgba(0,0,0,.6);flex:1;min-width:0}
.gal-dots{display:flex;gap:var(--s-2);flex:none}
.gal-dots button{width:26px;height:4px;border:0;padding:0;border-radius:2px;cursor:pointer;
  background:rgba(255,255,255,.34);transition:background .25s,width .25s}
.gal-dots button.on{background:var(--gold);width:38px}
.gal-nav{position:absolute;inset:0;display:flex;align-items:center;justify-content:space-between;
  padding:0 var(--s-4);pointer-events:none}
.gal-nav button{pointer-events:auto;width:46px;height:46px;border-radius:50%;border:1px solid rgba(255,255,255,.28);
  background:rgba(20,21,24,.42);color:#fff;cursor:pointer;font-size:1.1rem;line-height:1;
  backdrop-filter:blur(6px);transition:background .2s,border-color .2s,opacity .2s;opacity:0}
.gal:hover .gal-nav button,.gal:focus-within .gal-nav button{opacity:1}
.gal-nav button:hover{background:var(--gold);border-color:var(--gold);color:#17130a}
@media(max-width:760px){.gal-nav button{opacity:1;width:40px;height:40px}}
@media(max-width:700px){
  /* The bar is an absolute row over a 16:10 stage. On a phone the stage is ~195px
     while the caption, squeezed into a narrow column beside the dots, wrapped to
     ~183px: 94% of the photo. Below this width the caption sits under the image at
     full width, which also costs it fewer lines. The prev/next buttons stay over
     the image; only the caption moves. */
  .gal-bar{position:static;flex-direction:column;align-items:flex-start;
    gap:var(--s-3);padding:var(--s-4) var(--s-5) var(--s-5)}
  .gal-cap{text-shadow:none;flex:none}
  .gal-shade{display:none}
}

/* ============================================================
   FAQ - numbered, with the number as a display element
   ============================================================ */
/* The top rule sits on the first item, not the container, so no border is on
   screen before the item that carries it has revealed. */
.faq{margin-top:var(--block)}
.faq-item:first-child{border-top:1px solid var(--rule)}
.sec-paper .faq-item:first-child{border-top-color:var(--rule-paper)}
.faq-item{border-bottom:1px solid var(--rule);padding:var(--s-6) 0;
  display:grid;grid-template-columns:auto 1fr;gap:var(--s-3) var(--s-5);align-items:start}
.sec-paper .faq-item{border-bottom-color:var(--rule-paper)}
.faq-n{font-family:var(--d);font-weight:700;font-size:clamp(2rem,1.2rem + 2.4vw,3.2rem);
  line-height:.9;color:var(--gold);font-variant-numeric:tabular-nums;min-width:1.6em}
.sec-paper .faq-n{color:var(--gold-dim)}
.faq-q{font-family:var(--d);font-weight:700;font-size:var(--fs-h3);line-height:1.22;margin:0;
  letter-spacing:-.006em}
.faq-a{grid-column:2;margin:0;color:var(--on-ink-mute);max-width:68ch}
.sec-paper .faq-a{color:var(--on-paper-mute)}
@media(max-width:640px){
  .faq-item{grid-template-columns:1fr;gap:var(--s-2)}
  .faq-a{grid-column:1}
  .faq-n{font-size:2rem}
}

/* ============================================================
   FEATURE ROWS - alternating image/text blocks for service pages
   ============================================================ */
.rows{display:grid;gap:var(--sec-y)}
.row{display:grid;gap:var(--s-6);align-items:center}
@media(min-width:900px){
  .row{grid-template-columns:1fr 1fr;gap:var(--s-9)}
  .row.flip .row-media{order:2}
}
.row-media img{width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:3px}
.row-body h3{margin-bottom:var(--s-4)}
.row-body p{color:var(--on-ink-mute);max-width:56ch}
.sec-paper .row-body p{color:var(--on-paper-mute)}
.row-tag{display:inline-block;font-family:var(--b);font-weight:600;font-size:var(--fs-xs);
  letter-spacing:.16em;text-transform:uppercase;color:var(--gold);margin-bottom:var(--s-3)}
.sec-paper .row-tag{color:var(--gold-dim)}

/* ============================================================
   STAT STRIP
   ============================================================ */
/* Same principle as .cards: every line is a box-shadow ring on the stat itself,
   so an unrevealed stat paints no border skeleton. */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:1px}
.stat{background:none;padding:var(--s-6);text-align:center;box-shadow:0 0 0 1px var(--rule)}
.sec-paper .stat{box-shadow:0 0 0 1px var(--rule-paper)}
.stat b{display:block;font-family:var(--d);font-weight:700;color:var(--gold);
  font-size:clamp(2rem,1.2rem + 2.2vw,3rem);line-height:1;margin-bottom:var(--s-3)}
.sec-paper .stat b{color:var(--gold-dim)}
.stat span{font-family:var(--b);font-size:var(--fs-sm);font-weight:500;letter-spacing:.08em;
  text-transform:uppercase;color:var(--on-ink-mute)}
.sec-paper .stat span{color:var(--on-paper-mute)}

/* ============================================================
   ABOUT HAYNE - deliberately unlike the rest of the site.
   Split portrait hero, oversized pull quotes, a dated timeline.
   Same palette and type, different rhythm.
   ============================================================ */
.bio-hero{display:grid;gap:var(--s-7);align-items:center;padding-top:var(--s-6)}
@media(min-width:940px){
  .bio-hero{grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);gap:var(--s-9);
    padding-top:var(--s-7)}
}
.bio-portrait{position:relative}
.bio-portrait img{width:100%;aspect-ratio:4/5;object-fit:cover;border-radius:3px}
.bio-portrait figcaption{margin-top:var(--s-3);font-family:var(--b);font-size:var(--fs-xs);
  letter-spacing:.14em;text-transform:uppercase;color:var(--on-ink-mute)}
.bio-portrait::after{content:"";position:absolute;inset:auto auto -14px -14px;width:64%;height:42%;
  border-left:2px solid var(--gold);border-bottom:2px solid var(--gold);border-radius:0 0 0 3px;
  pointer-events:none;opacity:.85}
.bio-name{font-size:clamp(2.4rem,1.3rem + 3.6vw,4.4rem);line-height:1.02;margin-bottom:var(--s-5)}
.bio-role{font-family:var(--b);font-weight:600;font-size:var(--fs-sm);letter-spacing:.2em;
  text-transform:uppercase;color:var(--gold);margin-bottom:var(--s-5)}
.bio-intro{font-size:clamp(1.1rem,1rem + .6vw,1.42rem);line-height:1.62;color:#e5e1d9;
  max-width:46ch;font-weight:400}

/* oversized pull quote in his voice */
/* About page pull-quote. Thin rule in the same gold used for highlighted keywords,
   gold-dim on paper sections to match how .hl already switches. The decorative glyph is
   2.6em of a clamped heading size, so it grows with the viewport while section padding
   does not: at 1512px its box reached 36px from the section top against 64px of section
   padding and collided with the boundary above. Containment therefore comes from this
   block's own top padding, not the section's. */
.quote{text-align:center;border:1px solid var(--gold);border-radius:3px;
  padding-block:clamp(3.25rem,2rem + 3vw,5.25rem) clamp(2.25rem,1.5rem + 2vw,3.25rem)}
.sec-paper .quote{border-color:var(--gold-dim)}
.quote+.prose{margin-top:var(--block)}
.quote blockquote{margin:0 auto;max-width:22ch;font-family:var(--d);font-weight:700;
  font-size:clamp(1.9rem,1.1rem + 3vw,3.5rem);line-height:1.12;letter-spacing:-.018em;
  text-wrap:balance;position:relative}
.quote blockquote::before{content:"\201C";position:absolute;top:-.26em;left:-.3em;
  font-size:2.6em;line-height:1;color:var(--gold);opacity:.28;pointer-events:none}
.quote cite{display:block;margin-top:var(--s-6);font-family:var(--b);font-style:normal;
  font-weight:600;font-size:var(--fs-sm);letter-spacing:.16em;text-transform:uppercase;
  color:var(--on-ink-mute)}
.sec-paper .quote cite{color:var(--on-paper-mute)}

/* dated timeline */
.tl{display:grid;gap:0;margin-top:var(--block)}
.tl-item{display:grid;grid-template-columns:auto 1fr;gap:var(--s-5) var(--s-6);
  padding:var(--s-6) 0;border-top:1px solid var(--rule);align-items:start}
.sec-paper .tl-item{border-top-color:var(--rule-paper)}
.tl-item:last-child{border-bottom:1px solid var(--rule)}
.sec-paper .tl-item:last-child{border-bottom-color:var(--rule-paper)}
.tl-year{font-family:var(--d);font-weight:700;color:var(--gold);line-height:.92;
  font-size:clamp(1.7rem,1.1rem + 1.8vw,2.6rem);font-variant-numeric:tabular-nums;min-width:3.4em}
.sec-paper .tl-year{color:var(--gold-dim)}
.tl-body h3{margin-bottom:var(--s-4)}
.tl-body p{color:var(--on-ink-mute);max-width:62ch;margin:0}
.sec-paper .tl-body p{color:var(--on-paper-mute)}
@media(max-width:640px){
  .tl-item{grid-template-columns:1fr;gap:var(--s-3)}
  .tl-year{font-size:1.7rem}
}

/* signature sign-off */
.signoff{text-align:center}
.signoff p{max-width:600px;margin:0 auto var(--s-6);font-size:var(--fs-lead);color:var(--on-ink-mute)}
.signoff .sig{font-family:var(--d);font-weight:700;font-size:clamp(1.5rem,1rem + 1.6vw,2.2rem);
  color:var(--gold);letter-spacing:-.01em}
.signoff .sig-role{font-family:var(--b);font-size:var(--fs-xs);letter-spacing:.18em;
  text-transform:uppercase;color:var(--on-ink-mute);margin-top:var(--s-2)}

/* ============================================================
   VIDEO HERO
   The poster is a real <img> and stays the LCP element. The video
   is only attached once the block is in view and reduced motion is
   off, so nothing about this costs us on first paint.
   ============================================================ */
.vhero{position:relative;overflow:hidden;border-radius:3px;background:var(--ink-2)}
.vhero-stage{position:relative;aspect-ratio:16/9}
.vhero-poster,.vhero video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* Fade is deliberately short. The poster IS frame 0 of the video, so there is no jump
   for a long fade to hide, and fading slowly blends a static frame over moving footage,
   which reads as the clip being slow to start. 1.1s left 17% of a 6.27s clip looking
   frozen, which James flagged twice on the Spring Branch hero. */
.vhero video{opacity:0;transition:opacity .3s ease}
.vhero video.on{opacity:1}
.vhero::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(20,21,24,.42) 0%,rgba(20,21,24,.06) 34%,rgba(20,21,24,.80) 100%)}
.vhero-copy{position:absolute;inset:auto 0 0 0;z-index:2;padding:var(--s-7) var(--s-6)}
.vhero-copy .eyebrow{color:#fff;text-shadow:0 1px 14px rgba(0,0,0,.6);margin-bottom:var(--s-3)}
.vhero-cap{font-family:var(--b);font-size:var(--fs-sm);font-weight:500;letter-spacing:.08em;
  text-transform:uppercase;color:#fff;text-shadow:0 1px 12px rgba(0,0,0,.65)}
.vhero-badge{position:absolute;z-index:3;top:var(--s-5);right:var(--s-5);display:inline-flex;
  align-items:center;gap:var(--s-2);padding:.4rem .8rem;border-radius:999px;
  background:rgba(20,21,24,.55);border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(6px);
  font-family:var(--b);font-size:var(--fs-xs);letter-spacing:.12em;text-transform:uppercase;color:#fff}
.vhero-badge b{width:6px;height:6px;border-radius:50%;background:var(--gold);display:block}
@media(max-width:700px){
  .vhero-stage{aspect-ratio:4/3}
  /* Same overlay-on-a-fluid-box problem: the copy held 64% of the stage on a phone.
     Below this width it becomes a caption under the media rather than a layer on it,
     so the shadow that made it legible over video is no longer needed. */
  .vhero-copy{position:static;padding:var(--s-5)}
  .vhero-copy .eyebrow{text-shadow:none}
  .vhero-cap{text-shadow:none}
}
html.reduce .vhero video{display:none}

/* Concept renders. Design visualisations of what OneWay can build, never presented
   as completed OneWay work. The badge is part of the component. Do not remove it. */
.grid3 .concept{position:relative}
.concept-badge{position:absolute;top:var(--s-3);left:var(--s-3);
  font-family:var(--b);font-weight:700;font-size:var(--fs-xs);letter-spacing:.18em;
  text-transform:uppercase;color:var(--ink);background:var(--gold);
  padding:var(--s-1) var(--s-3)}

/* About: personal block. Two real photographs, offset so it reads as designed
   rather than as a grid dump. Gold corner rule echoes .bio-portrait. */
.personal{display:grid;gap:var(--s-6);grid-template-columns:1fr}
@media(min-width:800px){
  .personal{grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
    gap:var(--s-8);align-items:start}
  .personal figure:last-child{margin-top:var(--s-9)}
}
.personal figure{position:relative;margin:0}
.personal img{width:100%;aspect-ratio:4/5;object-fit:cover;border-radius:3px}
.personal figure:last-child img{aspect-ratio:3/4;object-position:center top}
/* the wedding frame is much taller than the box; a centred crop cuts the faces off */
.personal figcaption{margin-top:var(--s-3);font-family:var(--b);font-size:var(--fs-xs);
  letter-spacing:.14em;text-transform:uppercase;color:var(--on-ink-mute)}
.sec-paper .personal figcaption{color:var(--on-paper-mute)}
.personal figure:first-child::after{content:"";position:absolute;
  inset:auto auto -14px -14px;width:52%;height:34%;
  border-left:2px solid var(--gold);border-bottom:2px solid var(--gold);
  border-radius:0 0 0 3px;pointer-events:none;opacity:.85}

/* signoff button row: was an inline style */
.signoff .btn-row{justify-content:center;margin-top:var(--s-6)}

/* ============================================================
   DUO + DRIFT
   Long single-column prose left acres of dead space to the right
   of its measure on wide screens. .duo splits the wrap into prose
   plus a decorative photograph that fills the otherwise empty
   right column, top-aligned with the prose and ending with it, so
   it never touches the section's own edges. The fade is one-sided
   and horizontal only: opaque at the outer edge, dissolving from
   the image's midpoint, fully transparent before the text column.
   Works on both grounds because the mask fades to transparent,
   not to a colour. Drift images are decorative: empty alt,
   aria-hidden, lazy. Hidden below 1100px, where the dead space
   does not exist; with no box they are never fetched there.
   ============================================================ */
.duo{display:grid;gap:var(--block);align-items:start}
.drift{display:none;margin:0}
@media(min-width:1100px){
  .duo{grid-template-columns:minmax(0,1.2fr) minmax(0,.8fr);column-gap:var(--s-9)}
  .drift{display:block;align-self:stretch}
  .drift img{width:100%;height:100%;object-fit:cover;
    -webkit-mask-image:linear-gradient(to left,#000 0,#000 50%,transparent 100%);
    mask-image:linear-gradient(to left,#000 0,#000 50%,transparent 100%)}
}

/* ============================================================
   MAP FACADE - contact page service-area block.
   A styled static map, zero third-party script. The real Google
   embed is injected into [data-map-slot] only after the visitor
   clicks the load button, so nothing here touches Core Web Vitals
   or sets a cookie before consent.
   ============================================================ */
.mapfx{position:relative;background:var(--ink);border:1px solid var(--rule);border-radius:3px;
  overflow:hidden;color:var(--on-ink)}
.mapfx svg{display:block;width:100%;height:auto}
/* An author display:block beats the UA [hidden] rule, so hiding the facade in JS does
   nothing without this. Same trap applies to .mapfx-ui below. */
.mapfx svg[hidden],.mapfx-ui[hidden]{display:none}
.mapfx-ui{position:absolute;inset:auto 0 0 0;z-index:2;display:flex;flex-wrap:wrap;
  align-items:center;justify-content:space-between;gap:var(--s-3) var(--s-5);
  padding:var(--s-5) var(--s-6);
  background:linear-gradient(180deg,transparent,rgba(20,21,24,.88))}
.mapfx-note{margin:0;font-family:var(--b);font-size:var(--fs-sm);letter-spacing:.04em;
  color:var(--on-ink-mute)}
.mapfx-tag{position:absolute;z-index:2;top:var(--s-5);left:var(--s-5);display:inline-flex;
  align-items:center;gap:var(--s-2);padding:var(--s-1) var(--s-3);border-radius:999px;
  background:rgba(20,21,24,.55);border:1px solid var(--rule);
  font-family:var(--b);font-size:var(--fs-xs);letter-spacing:.12em;text-transform:uppercase;
  color:var(--on-ink)}
.mapfx-tag b{width:6px;height:6px;border-radius:50%;background:var(--gold);display:block}
/* SVG parts. Geometry lives in SVG attributes (its own coordinate space, like
   width/height on an img), colour comes from the tokens here. */
.mapfx text{font-family:var(--b);font-weight:600;fill:var(--on-ink);letter-spacing:.08em;
  text-transform:uppercase}
.mapfx .mx-road{stroke:var(--rule);fill:none}
.mapfx .mx-ring{stroke:var(--gold);fill:none;opacity:.4}
.mapfx .mx-dot{fill:var(--on-ink-mute)}
.mapfx .mx-base{fill:var(--gold)}
.mapfx .mx-halo{fill:none;stroke:var(--gold);opacity:.5}
/* The slot keeps the facade's 16:9 so swapping in the iframe does not shift the page.
   :not([hidden]) so the display rule never fights the hidden attribute. */
.mapfx [data-map-slot]:not([hidden]){display:block;aspect-ratio:16/9}
.mapfx [data-map-slot] iframe{display:block;width:100%;height:100%;border:0}
@media(max-width:700px){
  /* The overlay bar is sized for the desktop box. On a phone the illustration is only
     ~185px tall while the note and button still need ~136px, so the bar covered three
     quarters of the map. Below this width the controls sit under the illustration
     instead of on top of it, and the scrim goes with them since there is nothing left
     to scrim. */
  .mapfx-ui{position:static;background:none;flex-direction:column;align-items:stretch;
    justify-content:flex-start;gap:var(--s-3);padding:var(--s-4) var(--s-5) var(--s-5)}
  .mapfx-ui .btn{width:100%;justify-content:center}
  .mapfx text{letter-spacing:.04em}
}

/* CTA reassurance line. Free consultation, no travel charge, verified 2026-08-23. */
.cta-note{margin-top:var(--s-5);font-family:var(--b);font-size:var(--fs-sm);
  letter-spacing:.04em;opacity:.78}
