/* Montana Builders — site stylesheet
 *
 * This was candidate 2 of three. The client chose it on 26 Aug 2026: "Design #2 is
 * definitely our preferred direction. We really like the overall layout, style, and feel."
 * The other two candidates are parked in Backup Asset/Montana Builder/candidates-not-chosen.
 *
 * WHAT CHANGED AND WHY. The first version read dry, and the causes were measurable rather
 * than a matter of taste:
 *   - three light bands ran in a row before the first dark one, so most of the page's length
 *     was beige on beige;
 *   - the services block carried zero photographs, leaving the whole middle of the page as
 *     type on a flat ground;
 *   - the hero used the dullest photograph in the set — hazy, flat light — cut to a strip;
 *   - the rust accent only ever appeared in small eyebrows and buttons.
 *
 * TYPOGRAPHY. Rokkitt stays: this is the candidate that inherits the client's display face,
 * and dropping it would leave no candidate showing their existing brand modernised. Ubuntu
 * is gone, replaced by Figtree as a neutral body face — that pairing was what felt dated.
 *
 * SECTION DESIGNS PORTED FROM OUR 21st.dev CATALOGUE, hand-built in vanilla CSS/JS because
 * these sites are static (no React, no Tailwind). Composition follows each catalogue entry:
 *   proof/number-ticker         @danielpetho id 20459
 *   explainer/accordion-switch  @shadcnblockscom id 688
 *   gallery/grid-lightbox       @moumensoliman id 10596
 *   testimonials/columns-marquee ported on Best Value Locksmith
 *   process/scroll-timeline     @manuarora700 id 857
 */

/* CLIENT REVISION, round 1 — "reduce some of the bright white throughout the background…
 * possibly a soft gray, warm gray, or similar warmer neutral tone. Nothing overly dark, but
 * just enough to give the website a warmer, more premium feel."
 *
 * Measured before changing anything, because the old paper was already a warm off-white and
 * darkening the wrong token would have missed the complaint. Where the whiteness actually
 * came from:
 *   - five light bands on --paper at 88.2% lightness, about 6,100px of page height between
 *     them, the largest being the 3,560px process timeline;
 *   - eighteen review cards at pure #FFFFFF, the only true white on the page.
 *
 * So both moved: the bands step down and the cards come off pure white.
 *
 * SECOND PASS, same note. The first attempt delivered the darkness but kept the page's beige
 * hue family — --paper landed at chroma 10 and --paper-2 at chroma 13, which read as greige
 * rather than grey, and --paper was actually a shade MORE saturated than the colour it
 * replaced. "Soft gray, warm gray" is a hue instruction as much as a brightness one, so the
 * neutrals now sit at chroma 5-7 at the same perceived lightness. There was no brand reason to
 * keep the beige: the old site was effectively greyscale, so that warm cast was our own choice
 * from the inspiration sites, not something inherited.
 *
 * The rust accent is untouched and now carries more. With neutral grounds the warmth reads as a
 * deliberate accent rather than a tint over the whole page, which is nearer to "warmer, more
 * premium" than a beige ground ever was.
 *
 * --line and --muted had to move with them. A hairline of #E4DFD6 is invisible on a 76%
 * ground, and --muted was already marginal for contrast at 4.27:1 — leaving it alone would
 * have pushed it to 3.74:1, so it darkened to hold 4.5:1 on every ground it lands on.
 */
:root{
  --paper:#E3E1DD;        /* warm grey, chroma 6, 75.4% lightness. Was #F4F1EC at 88.2%.  */
  --paper-2:#D7D5D0;      /* the services band, chroma 7, 66.6% — one clear step below.   */
  --ink:#191B19;
  --ink-2:#3F4442;
  --ink-deep:#101211;
  --muted:#565B57;        /* was #6E736F. Two steps down, not one: #5E635F cleared --paper */
                          /* at 4.74:1 but only reached 4.19:1 on the darker --paper-2 band */
  --line:#C4C2BC;         /* chroma 8. The beige #C6C0B4 was chroma 18 and showed as tan  */
  --line-soft:#D0CEC8;    /* chroma 8, for the same reason — a tan hairline reads as dirt */
  --rust:#B5502A;

  /* link colour per ground, from Skills/tokens.css. On both grounds the link is
     the same colour as the prose it sits in — the underline is what marks it. */
  --link-on-light:  var(--ink);
  --link-on-dark:   #ffffff;
  --link-underline: 1px solid currentColor;
  --rust-lift:#CB6034;
  --rust-dark:#96401F;
  --pine:#333E35;
  --white:#F0EFEB;        /* the card surface. Off-white at chroma 5, not stark, not tan. */
  /* A little more depth than before: on a darker ground a faint shadow disappears, and the
     lift is what carries the "premium" half of the request. */
  --shadow:0 1px 2px rgba(25,27,25,.05), 0 18px 40px -26px rgba(25,27,25,.38);
  --serif:'Rokkitt', Georgia, serif;
  --sans:'Figtree', system-ui, -apple-system, sans-serif;
}

*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0; background:var(--paper); color:var(--ink-2);
  font:400 17px/1.68 var(--sans); -webkit-font-smoothing:antialiased}
img{max-width:100%; display:block}
a{color:inherit; text-decoration:none}

/* …but a link inside body copy has to be visible as one. Scoped to <main> so nav,
   footer and the sticky bar keep their own treatment, and to paragraphs so that
   link lists and card grids are not turned into a wall of rules. */
main p a:not(.btn):not([class*="card"]){
  color:var(--link-on-light); border-bottom:var(--link-underline);
  text-underline-offset:2px}
main p a:not(.btn):not([class*="card"]):hover{border-bottom-color:var(--rust); color:var(--rust)}
/* on a dark band the prose is white, so the link is too */
.fbleed p a:not(.btn), .ctamid p a:not(.btn), .end p a:not(.btn){color:var(--link-on-dark)}
button{font:inherit; color:inherit}
h1,h2,h3,h4{font-family:var(--serif); color:var(--ink); margin:0; letter-spacing:-.012em;
  text-wrap:balance}
h1{font-size:clamp(46px,6.6vw,92px); line-height:.96; font-weight:700}
h2{font-size:clamp(32px,4vw,54px); line-height:1.04; font-weight:700}
h3{font-size:23px; line-height:1.2; font-weight:700}
p{margin:0}
.wrap{width:92%; max-width:1240px; margin:0 auto}

/* The accent carries a rule now, not just colour — it reads as a mark rather than a tint. */
/* --rust-dark, not --rust: at 11.5px the brighter rust measured 3.92:1 on the new paper and
   3.46:1 on the services band. The 2px rule below keeps the brighter rust, because a block
   of colour has no contrast threshold to meet. */
.eyebrow{font:700 11.5px/1 var(--sans); letter-spacing:.2em; text-transform:uppercase;
  color:var(--rust-dark); margin:0 0 16px; display:flex; align-items:center; gap:11px}
.eyebrow::before{content:""; width:30px; height:2px; background:var(--rust); flex:none}
.eyebrow.center{justify-content:center}
/* #E9A585 measured 2.93:1 over a dusk sky. A paler tint keeps the warmth and clears 4.5:1. */
.eyebrow.light{color:#FFD2BC}
.eyebrow.light::before{background:var(--rust-lift)}

.btn{display:inline-flex; align-items:center; gap:10px; padding:17px 28px;
  font:700 15px/1 var(--sans); border:1px solid var(--rust); background:var(--rust);
  color:#fff; transition:background .18s, border-color .18s, transform .18s}
.btn:hover{background:var(--rust-dark); border-color:var(--rust-dark); transform:translateY(-2px)}
.btn.ghost{background:transparent; border-color:rgba(255,255,255,.5); color:#fff}
.btn.ghost:hover{background:rgba(255,255,255,.12); border-color:#fff}
.btn.dark{background:var(--ink); border-color:var(--ink)}
.btn.dark:hover{background:#000; border-color:#000}

/* ---------------------------------------------------------------- emergency strip */
.strip{background:var(--ink-deep); color:#E4E3DF; font:500 13.5px/1 var(--sans)}
.strip .wrap{display:flex; align-items:center; justify-content:space-between; gap:18px; padding:11px 0}
.strip .dot{width:7px; height:7px; border-radius:50%; background:#7FBF6A; flex:none;
  margin-right:9px; box-shadow:0 0 0 3px rgba(127,191,106,.22)}
.strip .left{display:flex; align-items:center}
.strip a{color:#fff; border-bottom:1px solid rgba(255,255,255,.4); padding-bottom:2px}
.strip a:hover{border-color:var(--rust-lift)}
@media(max-width:820px){ .strip .right{display:none} }

/* ---------------------------------------------------------------- nav */
/* Matches --paper. Hard-coded because it needs an alpha for the blur, which is exactly why
   it got missed when the palette moved: a token change cannot reach an rgba literal. */
.nav{position:sticky; top:0; z-index:40; background:rgba(227,225,221,.93);
  border-bottom:1px solid var(--line-soft)}
/* the blur sits on a layer behind the header, not on the header itself: a backdrop-filter on .nav makes it the
   containing block of the fixed phone menu, which then shrank to a 49px strip and Services opened out of sight
   (client, iPhone, 7 Oct 2026) */
.nav::before{content:""; position:absolute; inset:0; z-index:-1; backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px)}
.nav .wrap{display:flex; align-items:center; gap:30px; padding:15px 0}
.brand{display:flex; align-items:center; gap:12px; font:700 19px/1 var(--serif);
  color:var(--ink); letter-spacing:.02em; text-transform:uppercase}
.brand img{width:34px; height:34px}
.nav .call{font:700 15.5px/1 var(--sans); color:var(--ink)}

/* ---------------------------------------------------------------- hero */
/* A golden-hour photograph, and the height back. The previous hero used a hazy, flat-light
   frame cut to 470px, which is most of why the page looked drained. */
.hero{position:relative; min-height:min(74vh,640px); display:flex; flex-direction:column;
  justify-content:flex-end; overflow:hidden; background:var(--ink)}
.hero-bg{position:absolute; inset:0; background-image:url("../images/mb-54-1200.webp");
  background-size:cover; background-position:50% 62%; transform:scale(1.04);
  animation:heroDrift 26s ease-in-out infinite alternate}
@keyframes heroDrift{ to{ transform:scale(1.12) translateX(-1.4%) } }
/* Measured against the real photograph, the headline's worst pixel was 2.67:1 and the
   eyebrow 2.93:1 — the gradient thinned to .26 exactly where the widest line of the
   headline reaches. Raised across the middle and right. */
.hero::after{content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(96deg, rgba(16,18,17,.92) 0%, rgba(16,18,17,.78) 45%,
    rgba(16,18,17,.48) 76%, rgba(16,18,17,.58) 100%)}
.hero .wrap{position:relative; z-index:2; padding:132px 0 62px}
.hero h1{color:#FBFAF7; max-width:17ch}
.hero h1 em{font-style:normal; color:#E58A5F}
.hero .sub{margin:24px 0 0; max-width:52ch; color:rgba(255,255,255,.86); font-size:18.5px}
.hero .acts{display:flex; flex-wrap:wrap; gap:14px; margin-top:36px}

/* ---------------------------------------------------------------- proof: number ticker */
/* proof/number-ticker (@danielpetho id 20459). The catalogue note on that entry is that its
   value is the tween rather than a layout, so what is reused is the counting behaviour —
   driven here by one IntersectionObserver and rAF instead of a React ref. Placed as a DARK
   band directly under the hero: this is what breaks the run of beige. */
/* Sized down. At clamp(40px,5vw,68px) on a band with 86px of padding top and bottom, four
   figures ate a third of a screen and read as a splash page rather than a proof strip. */
.figs{background:var(--ink-deep); color:rgba(255,255,255,.72); padding:clamp(34px,3.6vw,52px) 0}
.figs .wrap{display:grid; grid-template-columns:repeat(4,1fr); gap:1px;
  background:rgba(255,255,255,.13)}
.figs .f{background:var(--ink-deep); padding:4px 28px 6px}
.figs b{display:block; font:700 clamp(29px,3vw,42px)/1 var(--serif); color:#fff;
  letter-spacing:-.025em; font-variant-numeric:tabular-nums}
.figs b i{font-style:normal; color:var(--rust-lift)}
.figs .k{display:block; margin-top:9px; font:700 11px/1 var(--sans); letter-spacing:.13em;
  text-transform:uppercase; color:#E9A585}
.figs .d{display:block; margin-top:7px; font-size:13.5px; line-height:1.5;
  color:rgba(255,255,255,.6); max-width:26ch}
@media(max-width:900px){
  .figs .wrap{grid-template-columns:1fr 1fr}
  .figs .f{padding:22px 24px}
}
@media(max-width:520px){ .figs .wrap{grid-template-columns:1fr} }

/* ---------------------------------------------------------------- lede */
.lede{padding:clamp(70px,8vw,112px) 0 clamp(34px,4vw,54px)}
.lede .grid{display:grid; grid-template-columns:1.2fr 1fr; gap:clamp(32px,6vw,84px);
  align-items:start}
.lede .big{font:400 clamp(21px,2.3vw,29px)/1.46 var(--sans); color:var(--ink); max-width:32ch}
.lede .big b{font-family:var(--serif); font-weight:700; color:var(--rust)}
.lede .side p{font-size:17.5px; line-height:1.75; color:var(--muted); max-width:46ch}
.lede .side p + p{margin-top:16px}
@media(max-width:900px){ .lede .grid{grid-template-columns:1fr; gap:26px} }


/* ---------------------------------------------------------------- services: accordion switch */
/* explainer/accordion-switch (@shadcnblockscom id 688): a column of headings where opening
   one swaps the photograph beside it. Chosen precisely because the previous services block
   had no imagery at all — this puts a photograph in the middle of the page and makes the
   section something you operate rather than read. */
.svc{background:var(--paper-2); padding:clamp(72px,8.5vw,116px) 0;
  border-top:1px solid var(--line); border-bottom:1px solid var(--line)}
.svc header{max-width:56ch; margin-bottom:44px}
.svc header p{margin-top:16px; color:var(--muted)}
/* align-items:stretch, not start. With the art on a fixed 4/5 aspect it stood far taller
   than the list beside it — the copy sat at the top and the photograph hung well below the
   last row, which is the "not proportionate" look. Stretched, the grid row is set by the
   list and the photograph matches it exactly. */
.acc{display:grid; grid-template-columns:1fr 1.02fr; gap:clamp(28px,4.5vw,64px);
  align-items:stretch}
.acc-list{border-top:1px solid var(--line)}
.acc-row{border-bottom:1px solid var(--line)}
.acc-btn{width:100%; display:flex; align-items:center; gap:16px; padding:22px 4px;
  background:none; border:0; text-align:left; cursor:pointer;
  transition:padding .2s ease}
.acc-btn:hover{padding-left:10px}
.acc-btn .n{font:700 11.5px/1 var(--sans); letter-spacing:.16em; color:var(--rust-dark); flex:none}
.acc-btn h3{flex:1; transition:color .2s}
.acc-row.on .acc-btn h3{color:var(--rust)}
.acc-btn .pm{position:relative; width:15px; height:15px; flex:none}
.acc-btn .pm::before,.acc-btn .pm::after{content:""; position:absolute; background:var(--ink);
  transition:transform .25s ease, background .2s}
.acc-btn .pm::before{left:0; top:6.5px; width:15px; height:2px}
.acc-btn .pm::after{left:6.5px; top:0; width:2px; height:15px}
.acc-row.on .acc-btn .pm::before{background:var(--rust)}
.acc-row.on .acc-btn .pm::after{background:var(--rust); transform:rotate(90deg)}
.acc-btn:focus-visible{outline:2px solid var(--rust); outline-offset:2px}
/* Height animates via grid-template-rows, so nothing has to be measured in JS. */
.acc-panel{display:grid; grid-template-rows:0fr; transition:grid-template-rows .32s ease}
.acc-row.on .acc-panel{grid-template-rows:1fr}
.acc-panel > div{overflow:hidden}
.acc-panel p{padding:0 4px 20px; color:var(--muted); font-size:16px; max-width:44ch}
/* The photograph panel: every frame stacked, the active one faded in. */
/* No aspect-ratio: the grid row (set by the accordion list) decides the height, so the two
   columns always end level. min-height only stops it collapsing if the list is ever short. */
.acc-art{position:relative; min-height:440px; overflow:hidden; background:var(--line-soft);
  box-shadow:var(--shadow)}
.acc-art figure{position:absolute; inset:0; margin:0; background-size:cover;
  background-position:center; opacity:0; transform:scale(1.045);
  transition:opacity .5s ease, transform 1.2s cubic-bezier(.16,1,.3,1)}
.acc-art figure.on{opacity:1; transform:scale(1)}
.acc-art figcaption{position:absolute; left:0; right:0; bottom:0; padding:44px 22px 18px;
  font:700 13px/1.3 var(--sans); letter-spacing:.04em; color:#fff;
  background:linear-gradient(transparent, rgba(16,18,17,.86))}
@media(max-width:900px){
  .acc{grid-template-columns:1fr}
  .acc-art{aspect-ratio:4/3; min-height:0; order:-1; margin-bottom:26px}
}

/* ---------------------------------------------------------------- process: scroll timeline */
/* process/scroll-timeline — @manuarora700 id 857 ("sticky header and scroll beam follow").
 * Framer Motion's useScroll/useTransform is the whole original component and drops out
 * entirely: the beam height is a scroll-progress ratio, four lines of arithmetic against
 * getBoundingClientRect, and the sticky stage titles are position:sticky rather than JS.
 * The beam tracks scroll BOTH ways so a visitor can go back up and re-read a stage without
 * the progress lying to them.
 */
.tl{padding:clamp(70px,8vw,110px) 0 clamp(40px,5vw,70px); background:var(--paper)}
.tl header{max-width:52ch; margin-bottom:18px}
.tl header p{margin-top:16px; color:var(--muted)}
.tl-body{position:relative; padding-left:64px}
/* the spine, and the beam that fills down it */
.tl-spine{position:absolute; left:19px; top:8px; bottom:8px; width:2px;
  background:linear-gradient(var(--line) 0%, var(--line) 92%, transparent 100%)}
.tl-beam{position:absolute; left:0; top:0; width:2px; height:0;
  background:linear-gradient(var(--rust-lift), var(--rust)); border-radius:2px}
.tl-step{position:relative; display:grid; grid-template-columns:.82fr 1.18fr;
  gap:clamp(24px,3.6vw,54px); padding:0 0 clamp(56px,7vw,96px)}
.tl-step:last-child{padding-bottom:12px}
.tl-dot{position:absolute; left:-53px; top:4px; width:18px; height:18px; border-radius:50%;
  background:var(--paper); border:2px solid var(--line); display:grid; place-items:center;
  transition:border-color .35s ease, background .35s ease}
.tl-dot::after{content:""; width:7px; height:7px; border-radius:50%; background:var(--line);
  transition:background .35s ease}
.tl-step.reached .tl-dot{border-color:var(--rust)}
.tl-step.reached .tl-dot::after{background:var(--rust)}
/* The stage title sticks while its content scrolls past — the behaviour the entry is named for. */
.tl-head{position:sticky; top:96px; align-self:start}
.tl-head .n{display:block; font:700 11.5px/1 var(--sans); letter-spacing:.18em;
  text-transform:uppercase; color:var(--rust-dark); margin-bottom:11px}
.tl-head h3{font-size:clamp(23px,2.5vw,33px); line-height:1.08}
.tl-copy p{color:var(--muted); font-size:16px; max-width:46ch}
.tl-copy p + p{margin-top:13px}
.tl-copy .shot{margin-top:20px; overflow:hidden; box-shadow:var(--shadow)}
.tl-copy .shot img{width:100%; aspect-ratio:16/10; object-fit:cover}
.tl-copy .tag{display:inline-block; margin-top:14px; padding:7px 12px;
  background:var(--paper-2); font:700 11.5px/1 var(--sans); letter-spacing:.08em;
  text-transform:uppercase; color:var(--ink-2)}
@media(max-width:900px){
  .tl-body{padding-left:44px}
  .tl-spine{left:11px}
  .tl-dot{left:-41px}
  .tl-step{grid-template-columns:1fr; gap:14px}
  .tl-head{position:static}
}

/* ---------------------------------------------------------------- gallery: grid + lightbox */
/* gallery/grid-lightbox (@moumensoliman id 10596). A dark band, so it is the second place
   the page stops being beige. */
.work{background:var(--ink-deep); padding:clamp(72px,8.5vw,116px) 0}
.work header{display:flex; justify-content:space-between; align-items:flex-end; gap:26px;
  flex-wrap:wrap; margin-bottom:40px}
.work h2{color:#FBFAF7}
.work header p{color:rgba(255,255,255,.6); max-width:38ch; font-size:16px}
.tiles{display:grid; grid-template-columns:repeat(4,1fr); gap:12px}
/* background-color matters here even though every tile carries a photograph: a <button> with
   no background-color falls back to the UA's buttonface, which is a cold #F0F0F0. On this
   near-black band that meant a bright grey block for as long as the image took to paint — and
   permanently, if one ever failed to load. */
.tile{position:relative; display:block; border:0; padding:0; cursor:zoom-in;
  aspect-ratio:4/3; background-color:var(--ink-deep);
  background-size:cover; background-position:center; overflow:hidden;
  transition:transform .35s cubic-bezier(.16,1,.3,1)}
.tile::after{content:""; position:absolute; inset:0; background:rgba(16,18,17,.26);
  transition:opacity .3s; opacity:1}
.tile:hover{transform:scale(1.015)}
.tile:hover::after{opacity:0}
.tile span{position:absolute; left:0; right:0; bottom:0; z-index:2; padding:34px 16px 14px;
  font:700 12.5px/1.3 var(--sans); letter-spacing:.04em; color:#fff; text-align:left;
  background:linear-gradient(transparent, rgba(16,18,17,.9));
  opacity:0; transform:translateY(6px); transition:opacity .25s, transform .25s}
.tile:hover span,.tile:focus-visible span{opacity:1; transform:none}
.tile:focus-visible{outline:2px solid var(--rust-lift); outline-offset:3px}
@media(max-width:900px){ .tiles{grid-template-columns:1fr 1fr} }

.lb{position:fixed; inset:0; z-index:300; display:none; place-items:center;
  background:rgba(10,12,11,.94); padding:5vw}
.lb.open{display:grid}
.lb figure{margin:0; display:grid; place-items:center}
.lb img{max-width:min(1100px,90vw); max-height:80vh; width:auto;
  box-shadow:0 30px 90px rgba(0,0,0,.6)}
.lb figcaption{margin-top:16px; text-align:center; font:700 13px/1 var(--sans);
  letter-spacing:.09em; text-transform:uppercase; color:rgba(255,255,255,.72)}
.lb button{position:absolute; cursor:pointer; background:rgba(255,255,255,.1);
  border:1px solid rgba(255,255,255,.24); color:#fff; line-height:1; border-radius:50%}
.lb button:hover{background:rgba(255,255,255,.22)}
.lb button:focus-visible{outline:2px solid var(--rust-lift); outline-offset:3px}
.lb-close{top:20px; right:22px; width:44px; height:44px; font-size:19px}
.lb-prev,.lb-next{top:50%; transform:translateY(-50%); width:46px; height:46px; font-size:18px}
.lb-prev{left:18px} .lb-next{right:18px}
@media(max-width:640px){ .lb-prev,.lb-next{top:auto; bottom:18px; transform:none} }

/* ---------------------------------------------------------------- testimonials: columns marquee */
/* testimonials/columns-marquee ("Scrolling columns"), ported from 21st.dev on Best Value
 * Locksmith. Each column drifts at its own rate behind a masked fade, so reviews of very
 * uneven length stop looking like a ragged table.
 *
 * The catalogue entry's avoid note is honoured literally: motion costs readability, so hover
 * pauses every column and below 700px the animation comes off entirely and the reviews are
 * simply a stack. The set inside each column is duplicated once, which is what lets the
 * wrap land on an exact -50% with no visible seam.
 */
.says{padding:clamp(72px,8.5vw,116px) 0; background:var(--paper); overflow:hidden}
.says header{max-width:44ch; margin:0 auto 44px; text-align:center}
.says header .eyebrow{justify-content:center}
.marq{position:relative; height:min(66vh,560px); display:grid;
  grid-template-columns:repeat(3,1fr); gap:18px}
/* the fade: top and bottom, so cards enter and leave rather than being cut off */
.marq::before,.marq::after{content:""; position:absolute; left:0; right:0; height:110px;
  z-index:3; pointer-events:none}
.marq::before{top:0; background:linear-gradient(var(--paper), transparent)}
.marq::after{bottom:0; background:linear-gradient(transparent, var(--paper))}
.mcol{overflow:hidden}
/* No gap: with 2N items there are 2N-1 gaps, so translateY(-50%) lands half a gap short of
   one full set and the loop jumps every cycle. margin-bottom on EVERY card (including the
   last) makes the track exactly 2 x (set + trailing margin), so -50% is one exact set. */
.mtrack{display:flex; flex-direction:column; gap:0; will-change:transform;
  animation:rise var(--dur,22s) linear infinite}
.mtrack .q{margin-bottom:18px}
/* nth-OF-TYPE, not nth-child: the pause button is .marq's first child, so nth-child(2)
   was selecting the first column and nth-child(3) the second — every duration landed one
   column off, and removing the button would silently shift them again. */
.mcol:nth-of-type(2) .mtrack{animation-duration:var(--dur2,26s)}
.mcol:nth-of-type(3) .mtrack{animation-duration:var(--dur3,24s)}
/* Paused by click, not by hover. Hover-to-pause fought the reader: moving the mouse across
   the block to read a card stopped it, and moving away started it again mid-sentence. A click
   is deliberate and stays put until clicked again. */
.marq{cursor:pointer}
.marq.is-paused .mtrack{animation-play-state:paused}
@keyframes rise{ to{ transform:translateY(-50%) } }

/* No visible pause button — the click target is the block itself. The control below is
   hidden until it receives keyboard focus, so moving content still has a stop mechanism
   someone tabbing can reach and hear announced, without a button sitting on the page. */
.pausebtn{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap; border:0; background:none}
.pausebtn:focus-visible{position:static; width:auto; height:auto; margin:0 auto 16px;
  clip-path:none; overflow:visible; display:block; cursor:pointer;
  padding:10px 16px; background:var(--white); color:var(--ink); border:1px solid var(--line);
  font:700 12px/1 var(--sans); letter-spacing:.09em; text-transform:uppercase;
  box-shadow:var(--shadow); outline:2px solid var(--rust); outline-offset:2px}
.q{position:relative; background:var(--white); border:1px solid var(--line-soft);
  padding:26px 26px 24px; box-shadow:var(--shadow)}
.q::before{content:"\201C"; position:absolute; top:8px; right:18px;
  font:700 58px/1 var(--serif); color:var(--paper-2)}
.q .stars{display:flex; gap:3px; margin-bottom:12px; position:relative}
.q .stars i{width:12px; height:12px; background:var(--rust); display:block;
  clip-path:polygon(50% 0,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%)}
.q blockquote{margin:0; position:relative; font-size:15.5px; line-height:1.62; color:var(--ink-2)}
.q cite{display:block; margin-top:14px; font-style:normal;
  font:700 12.5px/1 var(--sans); letter-spacing:.05em; color:var(--muted)}
@media(max-width:1000px){ .marq{grid-template-columns:1fr 1fr} .mcol:nth-of-type(3){display:none} }
@media(max-width:700px){
  /* the entry says the animation comes off entirely here, so it does */
  .marq{grid-template-columns:1fr; height:auto}
  .marq::before,.marq::after{display:none}
  .mcol{overflow:visible}
  .mcol:nth-of-type(2),.mcol:nth-of-type(3){display:none}
  .mtrack{animation:none}
  .mtrack .q:nth-child(n+4){display:none}
}

/* ---------------------------------------------------------------- closing cta */
.cta{position:relative; overflow:hidden; background:var(--pine); color:#fff;
  padding:clamp(74px,9vw,118px) 0}
.cta-bg{position:absolute; inset:0; background-image:url("../images/mb-71-1200.webp");
  background-size:cover; background-position:center; opacity:.28}
.cta::after{content:""; position:absolute; inset:0;
  background:linear-gradient(rgba(30,38,31,.58), rgba(22,28,23,.88))}
.cta .wrap{position:relative; z-index:2; text-align:center}
.cta h2{color:#fff; max-width:23ch; margin:0 auto}
.cta p{margin:20px auto 0; max-width:50ch; color:rgba(255,255,255,.84); font-size:17px}
.cta .acts{display:flex; gap:14px; justify-content:center; margin-top:36px; flex-wrap:wrap}

/* ---------------------------------------------------------------- footer */
footer{background:var(--ink-deep); color:rgba(255,255,255,.6); padding:70px 0 28px; font-size:15px}
footer .grid{display:grid; grid-template-columns:1.6fr repeat(3,1fr); gap:44px}
footer h4{color:#fff; font:700 12px/1 var(--sans); letter-spacing:.14em;
  text-transform:uppercase; margin:0 0 18px}
footer .brand{color:#fff; margin-bottom:16px}
footer p{max-width:34ch; line-height:1.7}
footer ul{list-style:none; padding:0; margin:0; display:grid; gap:11px}
footer a:hover{color:#fff}
footer .base{margin-top:54px; padding-top:22px; border-top:1px solid rgba(255,255,255,.13);
  display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap; font-size:12.5px}
@media(max-width:860px){ footer .grid{grid-template-columns:1fr 1fr; gap:32px} }

/* ---------------------------------------------------------------- reveal */
[data-reveal]{opacity:0; transform:translateY(20px);
  transition:opacity .7s ease, transform .7s cubic-bezier(.16,1,.3,1)}
[data-reveal].in{opacity:1; transform:none}
@media(prefers-reduced-motion:reduce){
  *{animation:none !important; transition:none !important}
  [data-reveal]{opacity:1; transform:none}
}

/* ---------------------------------------------------------------- lede, single column */
/* The document supplies one overview paragraph for the band under the hero, so the
   two-column lede grid would leave an empty second column. Solo runs it full width at
   a 62ch measure — long enough to read as a lede, short enough not to sprawl. */
.lede .lede-solo{max-width:62ch}


/* ---------------------------------------------------------------- faq/accordion-centred */
/* From the section design table: "a centred column with real auto margins, heading centred
   over it, rows 68–89px tall". The avoid-note is explicit — a max-width with no auto
   margins is what once left an 880px column hard against the left edge with the whole
   gutter on the right, so margin-inline:auto is not optional here.
   The section note sets the other two numbers: 28–32px of row padding and a 74ch measure,
   below which the client reads the rows as suffocating. */
.faq{background:var(--paper-2); padding:clamp(72px,8.5vw,116px) 0;
  border-top:1px solid var(--line); border-bottom:1px solid var(--line)}
.faq header{max-width:60ch; margin:0 auto 40px; text-align:center}
.faq-list{width:100%; max-width:920px; margin-inline:auto; border-top:1px solid var(--line)}
.faq-row{border-bottom:1px solid var(--line)}
.faq-btn{width:100%; display:flex; align-items:center; gap:18px; padding:29px 4px;
  background:none; border:0; text-align:left; cursor:pointer; transition:padding .2s ease}
.faq-btn:hover{padding-left:10px}
.faq-btn h3{flex:1; font-size:19px; line-height:1.35; transition:color .2s}
.faq-row.on .faq-btn h3{color:var(--rust)}
.faq-btn .pm{position:relative; width:15px; height:15px; flex:none}
.faq-btn .pm::before,.faq-btn .pm::after{content:""; position:absolute; background:var(--ink);
  transition:transform .25s ease, background .2s}
.faq-btn .pm::before{left:0; top:6.5px; width:15px; height:2px}
.faq-btn .pm::after{left:6.5px; top:0; width:2px; height:15px}
.faq-row.on .faq-btn .pm::before{background:var(--rust)}
.faq-row.on .faq-btn .pm::after{background:var(--rust); transform:rotate(90deg)}
.faq-btn:focus-visible{outline:2px solid var(--rust); outline-offset:2px}
/* Same grid-rows trick as the services switch, so no height has to be measured in JS. */
.faq-panel{display:grid; grid-template-rows:0fr; transition:grid-template-rows .32s ease}
.faq-row.on .faq-panel{grid-template-rows:1fr}
.faq-panel > div{overflow:hidden}
.faq-panel p{padding:0 4px 26px; color:var(--muted); font-size:17.5px; line-height:1.75;
  max-width:74ch}
@media(max-width:640px){
  .faq-btn{padding:24px 4px; gap:14px}
  .faq-btn h3{font-size:17.5px}
}

/* ---------------------------------------------------------------- inner hero */
/* Shorter than the homepage hero: an inner page earns its height from content,
   not from arrival. Same treatment otherwise so the pages read as one site. */
.hero-inner{min-height:0}
.hero-inner .wrap{padding:104px 0 54px}
.hero-inner h1{max-width:20ch}

/* ---------------------------------------------------------------- text/photo split */
/* Standing rule: a body section that is only a heading and paragraphs carries a
   photograph beside it, alternating sides down the page, at full container width.
   .flip swaps the order so the eye zig-zags rather than marching down one edge. */
.split{padding:clamp(56px,6.5vw,92px) 0}
.split + .split{padding-top:0}
.split-grid{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);
  gap:clamp(28px,4.5vw,64px); align-items:stretch}
.split.flip .split-media{order:2}
.split-media{position:relative; overflow:hidden; box-shadow:var(--shadow);
  background:var(--line-soft); min-height:19rem; max-height:32rem}
.split-media img{position:absolute; inset:0; display:block; width:100%; height:100%;
  object-fit:cover}
.split-copy{align-self:center; min-width:0}
.split-copy h2{margin-bottom:18px}
.split-copy p{font-size:17.5px; line-height:1.75; color:var(--muted); max-width:62ch}
.split-copy p + p{margin-top:16px}
@media(max-width:900px){
  .split-grid{grid-template-columns:1fr; gap:24px}
  .split.flip .split-media{order:0}
  .split-media{min-height:15rem; max-height:22rem}
}

/* ---------------------------------------------------------------- about/masthead-two-col */
/* Long copy that cannot be cut, made scannable without moving a word.

   SEPARATION. The lede band above and this one were both bare --paper, so they
   read as a single 1,800px slab of prose. The band still sits on --paper; the
   content now sits on a raised --white slab with a hairline and the site shadow,
   which reads as a distinct object rather than more of the same ground. A second
   full-width band was not an option: --paper-2 is the services band directly
   below, so tinting this one would just have merged it downwards instead.

   HEIGHT. Two paragraphs stacked in a five-column measure ran to 1,090px. Side by
   side in six columns each they come to about thirteen lines apiece.

   THE PHOTOGRAPH. It is boxed at a fixed 3:2 in the masthead row and never asked
   to match the height of the copy. That was the previous failure: a 675px-wide
   source stretched down a 1,090px column is visibly soft, and lengthening the
   image to close a gap only makes the section longer. Fixing the aspect ratio
   means the crop is the crop at every width. */
.abt{padding:clamp(70px,8vw,112px) 0}
.abt-slab{background:var(--white); border:1px solid var(--line-soft);
  box-shadow:var(--shadow); padding:clamp(30px,4vw,58px)}

/* masthead: title and call to action left, photograph right, on one line */
.abt-head{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,.92fr);
  gap:clamp(28px,4vw,60px); align-items:center}
.abt-title{min-width:0}
.abt-h{margin:0; font:700 clamp(27px,3.1vw,41px)/1.14 var(--serif); color:var(--ink);
  letter-spacing:-.02em; max-width:19ch}
.abt-acts{display:flex; align-items:center; gap:22px; flex-wrap:wrap; margin-top:26px}
.abt-tel{font:600 16.5px/1 var(--sans); color:var(--ink); text-decoration:none;
  white-space:nowrap; border-bottom:1px solid var(--line); padding-bottom:3px}
.abt-tel:hover{color:var(--rust); border-color:var(--rust)}
.abt-shot{margin:0; position:relative; aspect-ratio:758 / 380; overflow:hidden;
  background:var(--line-soft)}
.abt-shot img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}

/* the two paragraphs, in document order, left to right */
.abt-cols{display:grid; grid-template-columns:1fr 1fr; gap:clamp(28px,4vw,62px);
  margin-top:clamp(30px,3.6vw,50px); padding-top:clamp(28px,3.4vw,46px);
  border-top:1px solid var(--line-soft)}
.abt-col{min-width:0}
.abt-col p{margin:0; font-size:17.5px; line-height:1.75; color:var(--muted)}
/* The opening sentence, set larger and in --ink. The split is the paragraph's own
   first full stop, so nothing is rewritten — it just gives the eye a place to land
   instead of two identical grey rectangles. */
.abt-lead{font:400 clamp(18px,1.5vw,20.5px)/1.5 var(--sans) !important;
  color:var(--ink) !important; margin:0 0 15px !important}
/* Terms already present in the copy, weighted so a skimmer catches the services
   without reading the sentence around them. */
.abt-col .k{font-weight:700; color:var(--ink-2)}

@media(max-width:1000px){
  .abt-head{grid-template-columns:1fr; gap:30px}
  .abt-h{max-width:none}
  .abt-shot{order:-1; max-width:758px; margin-inline:auto; width:100%}
  .abt-cols{grid-template-columns:1fr; gap:30px}
  .abt-col + .abt-col{padding-top:28px; border-top:1px solid var(--line-soft)}
}
@media(max-width:640px){
  .abt-slab{padding:24px 20px}
  .abt-acts{gap:16px}
  .abt-acts .btn{width:100%; justify-content:center}
}

/* ================================================================ inner-page sections
   Designs selected from the Section Design Table for the section types the
   classifier produces on inner pages. The homepage already carries its own
   versions of several of these; these are the variants that work without the
   homepage's scroll and lightbox scripts, so an inner page stays static.
   Each block names the catalogue id it implements. */

/* ---------------------------------------------------------------- post-list/photo-cards */
/* 21st.dev @shadcnblockscom/blog7. Three up, whole card a link. The photograph is
   decoration chosen for subject relevance, never a claim that it depicts that
   article — the alt text says what it shows, not what the article is about. */
.plist{padding:clamp(66px,8vw,104px) 0}
.plist header{margin:0 0 clamp(30px,3.6vw,44px)}
.plist h2{margin:0; font:700 clamp(25px,2.9vw,38px)/1.14 var(--serif); color:var(--ink);
  letter-spacing:-.02em; text-wrap:balance}
.plist-g{display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
  gap:clamp(20px,2.4vw,32px)}
.pcard{display:flex; flex-direction:column; background:var(--white);
  border:1px solid var(--line-soft); box-shadow:var(--shadow); text-decoration:none;
  color:inherit; transition:transform .22s cubic-bezier(.16,1,.3,1), box-shadow .22s}
.pcard:hover{transform:translateY(-3px);
  box-shadow:0 2px 4px rgba(25,27,25,.06), 0 24px 46px -26px rgba(25,27,25,.5)}
.pcard-shot{position:relative; aspect-ratio:3 / 2; overflow:hidden; background:var(--line-soft)}
.pcard-shot img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.pcard-b{padding:20px 22px 24px; display:flex; flex-direction:column; gap:9px}
.pcard-d{font:600 11px/1 var(--sans); letter-spacing:.13em; text-transform:uppercase;
  color:var(--rust-dark)}
.pcard h3{margin:0; font:700 19px/1.28 var(--serif); color:var(--ink); text-wrap:balance}
.pcard p{margin:0; font-size:15px; line-height:1.65; color:var(--muted)}
.pcard-m{margin-top:4px; font:600 13.5px/1 var(--sans); color:var(--ink)}
.pcard:hover .pcard-m{color:var(--rust)}

/* ---------------------------------------------------------------- gallery/grid-lightbox */
/* Table note: the registry lightbox usually skips focus trapping and Escape. This
   is the static half only — tiles at a fixed ratio, no lightbox — because an
   inner page carries no script. The homepage keeps the interactive version. */
.pgal{background:var(--ink-deep); padding:clamp(66px,8vw,104px) 0; color:#fff}
.pgal header{margin:0 0 clamp(28px,3.4vw,42px)}
.pgal h2{margin:0; font:700 clamp(25px,2.9vw,38px)/1.14 var(--serif); color:#fff;
  letter-spacing:-.02em; text-wrap:balance}
.pgal header p{margin:12px 0 0; color:rgba(255,255,255,.66); max-width:60ch}
.pgal-g{display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); gap:2px}
.pgal figure{position:relative; margin:0; aspect-ratio:4 / 3; overflow:hidden;
  background:#1b1f1d}
.pgal img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  transition:transform .7s cubic-bezier(.16,1,.3,1)}
.pgal figure:hover img{transform:scale(1.05)}
.pgal figcaption{position:absolute; left:0; right:0; bottom:0; padding:38px 16px 14px;
  font:600 12.5px/1.3 var(--sans); color:#fff;
  background:linear-gradient(rgba(12,14,13,0) 0%, rgba(12,14,13,.66) 40%, rgba(12,14,13,.95) 100%)}

/* ---------------------------------------------------------------- process/numbered-row */
/* The catalogue's script-free process variant. The homepage uses offset-ladder,
   which needs a scroll-driven beam; these pages carry no script, so the number
   itself has to do the sequencing work. */
.pstep{padding:clamp(66px,8vw,104px) 0}
.pstep header{margin:0 0 clamp(30px,3.6vw,44px); max-width:60ch}
.pstep h2{margin:0; font:700 clamp(25px,2.9vw,38px)/1.14 var(--serif); color:var(--ink);
  letter-spacing:-.02em; text-wrap:balance}
.pstep-g{display:grid; gap:1px; background:var(--line); border:1px solid var(--line)}
.pstep-r{background:var(--paper); display:grid;
  grid-template-columns:auto minmax(0,1fr); gap:clamp(18px,3vw,40px);
  padding:clamp(22px,2.6vw,34px) clamp(20px,2.4vw,32px)}
.pstep-n{font:700 clamp(30px,3.4vw,46px)/1 var(--serif); color:var(--line);
  font-variant-numeric:tabular-nums; letter-spacing:-.03em}
.pstep-r h3{margin:0 0 10px; font:700 clamp(18px,1.9vw,23px)/1.25 var(--serif);
  color:var(--ink)}
.pstep-r p{margin:0 0 12px; font-size:16px; line-height:1.7; color:var(--muted); max-width:66ch}
.pstep-r p:last-child{margin-bottom:0}
.pstep-r ul{margin:12px 0 0; padding-left:20px; color:var(--muted); font-size:15.5px;
  line-height:1.7}

/* ---------------------------------------------------------------- faq/accordion-centred */
/* Native <details> so the accordion works with no script at all. */
.pfaq{background:var(--paper-2); padding:clamp(66px,8vw,104px) 0}
.pfaq-w{max-width:820px; margin:0 auto}
.pfaq h2{margin:0 0 clamp(26px,3vw,38px); text-align:center;
  font:700 clamp(25px,2.9vw,38px)/1.14 var(--serif); color:var(--ink);
  letter-spacing:-.02em; text-wrap:balance}
.pfaq details{border-top:1px solid var(--line)}
.pfaq details:last-of-type{border-bottom:1px solid var(--line)}
.pfaq summary{cursor:pointer; list-style:none; padding:20px 40px 20px 0; position:relative;
  font:700 17px/1.4 var(--serif); color:var(--ink)}
.pfaq summary::-webkit-details-marker{display:none}
.pfaq summary::after{content:""; position:absolute; right:6px; top:50%; width:11px; height:2px;
  background:var(--rust); transform:translateY(-50%)}
.pfaq summary::before{content:""; position:absolute; right:10.5px; top:50%; width:2px; height:11px;
  background:var(--rust); transform:translateY(-50%); transition:transform .2s}
.pfaq details[open] summary::before{transform:translateY(-50%) rotate(90deg)}
.pfaq summary:focus-visible{outline:2px solid var(--rust); outline-offset:3px}
.pfaq details > div{padding:0 40px 22px 0}
.pfaq details p{margin:0 0 12px; font-size:16px; line-height:1.72; color:var(--muted)}
.pfaq details p:last-child{margin-bottom:0}

/* ---------------------------------------------------------------- service-area/town-list */
.parea{padding:clamp(60px,7vw,92px) 0}
.parea-w{border:1px solid var(--line); background:var(--white); box-shadow:var(--shadow);
  padding:clamp(26px,3.4vw,44px)}
.parea h2{margin:0 0 8px; font:700 clamp(22px,2.5vw,31px)/1.15 var(--serif); color:var(--ink);
  letter-spacing:-.02em}
.parea p{margin:0 0 20px; font-size:16px; line-height:1.7; color:var(--muted); max-width:66ch}
.parea ul{list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:8px}
.parea li{border:1px solid var(--line); padding:7px 14px;
  font:600 14px/1 var(--sans); color:var(--ink-2); background:var(--paper)}

/* ---------------------------------------------------------------- team/magazine-staggered */
.pteam{padding:clamp(66px,8vw,104px) 0}
.pteam h2{margin:0 0 clamp(28px,3.4vw,42px); font:700 clamp(25px,2.9vw,38px)/1.14 var(--serif);
  color:var(--ink); letter-spacing:-.02em}
.pteam-r{display:grid; grid-template-columns:minmax(0,.8fr) minmax(0,1fr);
  gap:clamp(26px,4vw,58px); align-items:start}
.pteam-shot{position:relative; margin:0; aspect-ratio:4 / 5; overflow:hidden;
  background:var(--line-soft); box-shadow:var(--shadow)}
.pteam-shot img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.pteam h3{margin:0; font:700 clamp(22px,2.4vw,30px)/1.15 var(--serif); color:var(--ink)}
.pteam-role{margin:7px 0 18px; font:600 12px/1 var(--sans); letter-spacing:.14em;
  text-transform:uppercase; color:var(--rust-dark)}
.pteam p{margin:0 0 14px; font-size:16px; line-height:1.75; color:var(--muted); max-width:62ch}

@media(max-width:820px){
  .pteam-r{grid-template-columns:1fr; gap:24px}
  .pstep-r{grid-template-columns:1fr; gap:6px}
  .pstep-n{font-size:26px}
}

/* ---------------------------------------------------------------- explainer/stacked-wide */
/* From the table: "Where a candidate wants to break the alternating rhythm on
   purpose." A service page can run to thirteen prose sections, and thirteen
   identical splits is the one-component mistake at a smaller scale. */
.stackw{padding:clamp(56px,6.5vw,92px) 0}
.stackw + .split, .split + .stackw{padding-top:0}
.stackw-w{max-width:940px}   /* prose measure — deliberate, not a truncated list */
.stackw-w .taglist, .stackw-w ul{max-width:none}
.stackw h2{margin:0 0 16px; font:700 clamp(27px,3.5vw,48px)/1.1 var(--serif);
  color:var(--ink); letter-spacing:-.02em; text-wrap:balance}
.stackw p{font-size:17.5px; line-height:1.75; color:var(--muted); max-width:68ch}
.stackw p + p{margin-top:15px}
.stackw ul{margin-top:15px}
.stackw ul{color:var(--muted); font-size:16px; line-height:1.75; max-width:66ch;
  padding-left:20px}
/* THREE LEFT EDGES IN ONE SECTION. `margin:auto` centred the photograph inside the 1256px
   wrap while .stackw-w sits at the wrap's left edge, so the heading, the prose and the photo
   resolved to three different left edges — the fault the design's own avoid-when calls "using
   it by accident". The photo now shares the prose block's edge and its measure.
   NOT widened to the full 1256px container, which the design's name implies: 56 of the 57
   assets are under 1000px native, so the container width would upscale them 2.18x against
   1.63x today, and this catalogue already records 1.6x as visibly soft. The left edge is the
   defect; the width is the asset pool. */
.stackw-shot{position:relative; margin:28px 0 0; max-width:940px; aspect-ratio:21 / 8; overflow:hidden;
  box-shadow:var(--shadow); background:var(--line-soft)}
.stackw-shot img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
/* The caption sits ON the photograph, because the image is absolutely positioned
   to fill the figure. Inheriting --ink-2 put dark grey over a dark photo at 1:1.
   It gets the light treatment and its own gradient, like any other type on an
   image. */
.stackw-shot{display:flex; align-items:flex-end}
.stackw-shot figcaption{position:relative; z-index:1; width:100%; margin:0;
  padding:26px 20px 14px; color:#fff; font:600 15px/1.4 var(--sans);
  /* Measured: the first gradient reached only about .5 alpha where the words
     actually sit, giving 2.85:1 over a bright frame. Weighted so the scrim is
     already strong at the text baseline rather than only at the very bottom. */
  background:linear-gradient(rgba(16,18,17,0) 0%, rgba(16,18,17,.62) 42%, rgba(16,18,17,.94) 100%)}

/* ---------------------------------------------------------------- explainer/full-bleed-band */
/* The table's avoid-note is a hard limit: "Using it more than once. Two full-bleed
   bands on one page and neither is a moment any more." The renderer allows exactly
   one per page and only where a page has enough sections to need a change of pace. */
.fbleed{position:relative; min-height:clamp(340px,42vw,520px); display:grid;
  align-items:center; overflow:hidden; background:var(--ink-deep)}
.fbleed img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.fbleed::after{content:""; position:absolute; inset:0;
  background:linear-gradient(95deg, rgba(11,14,12,.93), rgba(11,14,12,.62) 48%, rgba(11,14,12,.28))}
.fbleed .wrap{position:relative; z-index:2; padding:clamp(46px,6vw,80px) 0}
.fbleed-c{max-width:60ch}
/* A dark band sets its colour once, on itself, and every child inherits it.
   Enumerating children left h3 at --ink — near-black on a photograph, measured
   at rgb(25,27,25) beside its own white h2 — because nobody adds a selector for
   an element type until it appears. */
.fbleed, .fbleed .wrap{color:#fff}
.fbleed h3, .fbleed h4{color:#fff}
.fbleed h2{margin:0 0 16px; font:700 clamp(25px,3vw,40px)/1.14 var(--serif); color:#fff;
  letter-spacing:-.02em; text-wrap:balance}
/* .82 measured 4.31:1 against the band's own scrim — under AA for body copy by
   a margin too small to see and too real to keep. */
.fbleed p{font-size:17.5px; line-height:1.75; color:rgba(255,255,255,.92); max-width:60ch}
.fbleed p + p{margin-top:15px}
.fbleed ul{color:rgba(255,255,255,.82); font-size:16px; line-height:1.75; padding-left:20px}

/* ================================================================ revised section designs */

/* ---------------------------------------------------------------- testimonials/quote-columns
   Replaces testimonials/grid-aligned here. Its avoid-note called this exactly:
   "Wildly uneven review lengths — a 500-word review beside a 25-word one sets a
   punishing row height." These reviews run 148 to 1,289 characters, and four
   aligned columns gave a 20-character measure with the text in a ribbon.

   Two columns, cards sized to their own content (align-items:start, never
   stretched), a 45-character measure, and the quote at 17px in the serif. The
   oversized quotation mark is the only ornament and it sits behind the text
   rather than pushing it down. */
.tcol{background:var(--paper-2); padding:clamp(66px,8vw,104px) 0}
.tcol header{max-width:52ch; margin:0 auto clamp(36px,4.4vw,56px); text-align:center}
.tcol h2{margin:0; font:700 clamp(26px,3vw,40px)/1.12 var(--serif); color:var(--ink);
  letter-spacing:-.022em; text-wrap:balance}
.tcol-g{column-count:2; column-gap:clamp(18px,2.2vw,30px)}
.tcol-q{break-inside:avoid; margin:0 0 clamp(18px,2.2vw,30px);
  position:relative; background:var(--white); border:1px solid var(--line-soft);
  box-shadow:var(--shadow); padding:clamp(28px,3vw,40px) clamp(26px,3vw,38px)
  clamp(24px,2.6vw,32px); overflow:hidden}
/* Rokkitt has no curly quotation mark at 700, so the ornament rendered as two
   .notdef boxes. Georgia always has it, and this is decoration rather than type
   the brand needs to own. */
/* The card clips its overflow, so a glyph hung above the top edge showed as two
   half-blobs rather than a quotation mark. Sit it fully inside. */
.tcol-q::before{content:"\201C"; position:absolute; top:2px; right:18px;
  font:700 108px/1 Georgia,'Times New Roman',serif; color:var(--rust);
  opacity:.12; pointer-events:none}
.tcol-q blockquote{position:relative; margin:0; font:400 17px/1.68 var(--sans);
  color:var(--ink-2); max-width:46ch}
.tcol-q cite{display:block; margin-top:18px; padding-top:15px;
  border-top:1px solid var(--line-soft); font:600 12px/1 var(--sans);
  letter-spacing:.13em; text-transform:uppercase; color:var(--muted); font-style:normal}
@media(max-width:820px){ .tcol-g{column-count:1} }

/* ---------------------------------------------------------------- nav-teaser/split-link
   The fix for copy that invites you somewhere and then goes nowhere. "Explore our
   diverse portfolio" and "Dive into expert advice" were both rendered as prose
   beside a photograph, so the invitation was the whole section and there was
   nothing to accept. A teaser is only a teaser if it carries the link. */
.tease{padding:clamp(56px,6.5vw,88px) 0}
.tease-g{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(28px,4.5vw,64px); align-items:center}
.tease.flip .tease-shot{order:2}
.tease-shot{position:relative; margin:0; aspect-ratio:3 / 2; overflow:hidden;
  box-shadow:var(--shadow); background:var(--line-soft)}
.tease-shot img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  transition:transform .7s cubic-bezier(.16,1,.3,1)}
.tease:hover .tease-shot img{transform:scale(1.04)}
.tease h2{margin:0 0 14px; font:700 clamp(24px,2.8vw,37px)/1.14 var(--serif);
  color:var(--ink); letter-spacing:-.02em; text-wrap:balance}
.tease p{margin:0 0 24px; font-size:16.5px; line-height:1.72; color:var(--muted); max-width:52ch}
.tease .btn{gap:12px}
.tease .btn::after{content:"\2192"; font-size:17px; line-height:1;
  transition:transform .22s cubic-bezier(.16,1,.3,1)}
.tease .btn:hover::after{transform:translateX(4px)}
@media(max-width:860px){
  .tease-g{grid-template-columns:1fr; gap:24px}
  .tease.flip .tease-shot{order:0}
}

/* ---------------------------------------------------------------- post-list/featured-bento
   Replaces post-list/photo-cards on the index. Three cards to a row, all the same
   size, read as a contact sheet rather than an invitation — the client's word for
   it was "so normal".

   What makes it move: the newest post takes a wide lead card at twice the type
   size; the rest run in a grid where every third card spans two columns, so the
   eye never settles into one rhythm; each card lifts, its photograph scales and
   its arrow travels on hover; and the whole grid enters on scroll with a per-card
   delay set from --i, so the cards arrive in sequence rather than as one block. */
.pfeed{padding:clamp(66px,8vw,104px) 0}
.pfeed header{display:flex; align-items:baseline; justify-content:space-between;
  gap:20px; flex-wrap:wrap; margin:0 0 clamp(28px,3.4vw,42px);
  padding-bottom:16px; border-bottom:2px solid var(--ink)}
.pfeed h2{margin:0; font:700 clamp(26px,3vw,40px)/1.12 var(--serif); color:var(--ink);
  letter-spacing:-.022em}
.pfeed-count{font:600 12.5px/1 var(--sans); letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted)}

.pfeed-lead{display:grid; grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
  gap:clamp(26px,3.6vw,52px); align-items:center; text-decoration:none; color:inherit;
  background:var(--white); border:1px solid var(--line-soft); box-shadow:var(--shadow);
  margin:0 0 clamp(20px,2.4vw,32px); overflow:hidden;
  transition:box-shadow .25s, transform .25s cubic-bezier(.16,1,.3,1)}
.pfeed-lead:hover{transform:translateY(-3px);
  box-shadow:0 2px 5px rgba(25,27,25,.07), 0 30px 54px -28px rgba(25,27,25,.55)}
.pfeed-lead .shot{position:relative; margin:0; aspect-ratio:16 / 11; overflow:hidden;
  background:var(--line-soft)}
.pfeed-lead .shot img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  transition:transform .8s cubic-bezier(.16,1,.3,1)}
.pfeed-lead:hover .shot img{transform:scale(1.05)}
.pfeed-lead .body{padding:clamp(22px,2.6vw,36px) clamp(26px,3vw,44px) clamp(22px,2.6vw,36px) 0}
.pfeed-tag{display:inline-block; margin-bottom:14px; padding:4px 11px;
  background:var(--rust); color:#fff; font:700 10.5px/1 var(--sans);
  letter-spacing:.15em; text-transform:uppercase}
.pfeed-lead h3{margin:0 0 12px; font:700 clamp(23px,2.5vw,33px)/1.18 var(--serif);
  color:var(--ink); letter-spacing:-.018em; text-wrap:balance}
.pfeed-lead p{margin:0; font-size:16.5px; line-height:1.7; color:var(--muted); max-width:52ch}

.pfeed-g{display:grid; grid-template-columns:repeat(6,1fr); gap:clamp(16px,2vw,26px)}
.pfeed-c{grid-column:span 2; display:flex; flex-direction:column; text-decoration:none;
  color:inherit; background:var(--white); border:1px solid var(--line-soft);
  box-shadow:var(--shadow); overflow:hidden;
  transition:box-shadow .25s, transform .25s cubic-bezier(.16,1,.3,1);
  opacity:0; transform:translateY(22px)}
.pfeed-c.in{opacity:1; transform:none;
  transition:opacity .6s ease var(--d,0s), transform .6s cubic-bezier(.16,1,.3,1) var(--d,0s),
    box-shadow .25s}
.pfeed-c.wide{grid-column:span 3}
.pfeed-c:hover{transform:translateY(-4px);
  box-shadow:0 2px 5px rgba(25,27,25,.07), 0 28px 50px -28px rgba(25,27,25,.52)}
.pfeed-c .shot{position:relative; margin:0; aspect-ratio:3 / 2; overflow:hidden;
  background:var(--line-soft)}
.pfeed-c.wide .shot{aspect-ratio:21 / 9}
.pfeed-c .shot img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  transition:transform .8s cubic-bezier(.16,1,.3,1)}
.pfeed-c:hover .shot img{transform:scale(1.06)}
.pfeed-c .body{padding:20px 22px 22px; display:flex; flex-direction:column; gap:9px; flex:1}
.pfeed-d{font:600 10.5px/1 var(--sans); letter-spacing:.14em; text-transform:uppercase;
  color:var(--rust-dark)}
.pfeed-c h3{margin:0; font:700 18.5px/1.3 var(--serif); color:var(--ink); text-wrap:balance}
.pfeed-c p{margin:0; font-size:15px; line-height:1.62; color:var(--muted)}
.pfeed-go{margin-top:auto; padding-top:6px; display:flex; align-items:center; gap:9px;
  font:600 13.5px/1 var(--sans); color:var(--ink)}
.pfeed-go::after{content:"\2192"; transition:transform .24s cubic-bezier(.16,1,.3,1)}
.pfeed-c:hover .pfeed-go{color:var(--rust)}
.pfeed-c:hover .pfeed-go::after{transform:translateX(5px)}

@media(max-width:980px){
  .pfeed-lead{grid-template-columns:1fr}
  .pfeed-lead .body{padding:0 26px 28px}
  .pfeed-g{grid-template-columns:repeat(4,1fr)}
  .pfeed-c, .pfeed-c.wide{grid-column:span 2}
}
@media(max-width:620px){
  .pfeed-g{grid-template-columns:1fr}
  .pfeed-c, .pfeed-c.wide{grid-column:span 1}
  .pfeed-c.wide .shot{aspect-ratio:3 / 2}
}
@media(prefers-reduced-motion:reduce){
  .pfeed-c{opacity:1; transform:none}
}

/* ================================================================ navigation
   The site has 81 pages and the menu listed six of them. Below 900px it listed
   none at all — `.nav nav{display:none}` with no hamburger behind it, so a phone
   visitor could reach nothing but the logo.

   Structure comes from the client's own taxonomy: the live site nests services
   under category hubs, and the content document groups them under its own
   headings. Five columns, every service reachable in one hover or one tap. */

.nav .wrap{position:relative}
.navlist{margin-left:auto; display:flex; align-items:center; gap:26px;
  font:500 15.5px/1 var(--sans)}
.navlist > li{list-style:none; position:relative}
.navlist{padding:0}
.navtop{display:flex; align-items:center; gap:7px; padding:8px 0;
  border-bottom:2px solid transparent; color:var(--ink-2); background:none;
  border-top:0; border-left:0; border-right:0; cursor:pointer; white-space:nowrap}
.navtop:hover, .navtop[aria-expanded="true"]{color:var(--ink); border-bottom-color:var(--rust)}
.navtop[aria-haspopup]::after{content:""; width:6px; height:6px; flex:none;
  border-right:1.5px solid currentColor; border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translate(-1px,-1px); transition:transform .2s}
.navtop[aria-expanded="true"]::after{transform:rotate(-135deg) translate(-2px,-2px)}
.navtop:focus-visible{outline:2px solid var(--rust); outline-offset:3px}

/* the panels */
.navpanel{position:absolute; top:calc(100% + 1px); z-index:60;
  background:var(--white); border:1px solid var(--line); box-shadow:var(--shadow);
  opacity:0; visibility:hidden; transform:translateY(-6px);
  transition:opacity .18s, transform .18s, visibility .18s}
.navpanel[data-open="true"]{opacity:1; visibility:visible; transform:none}
.navpanel ul{margin:0; padding:0; list-style:none}
/* a .btn inside the panel is a button, not a panel link: without this the panel
   rule wins on specificity and paints white button text muted grey on the rust
   ground, measured at 1.96:1 against a 4.5 requirement. */
.navpanel a.btn{color:#fff}
.navpanel a:not(.btn){display:block; padding:8px 12px; font-size:14.5px; color:var(--ink-2);
  border-radius:2px}
.navpanel a:not(.btn):hover{background:var(--paper); color:var(--rust-dark)}
.navpanel a:focus-visible{outline:2px solid var(--rust); outline-offset:-2px}

/* simple dropdown */
.navpanel.simple{left:0; min-width:230px; padding:10px}

/* the five-column services menu, pinned to the page gutter rather than the button */
.navlist > li.has-mega{position:static}
.navpanel.mega{left:0; right:0; width:auto; padding:26px 28px 24px;
  display:grid; grid-template-columns:repeat(5,1fr); gap:22px 26px}
.megacol > p{margin:0 0 10px; padding-bottom:9px; border-bottom:1px solid var(--line);
  font:700 11px/1.3 var(--sans); letter-spacing:.14em; text-transform:uppercase;
  color:var(--rust-dark)}
.megacol .hub{font-weight:700; color:var(--ink)}
.megafoot{grid-column:1 / -1; margin-top:6px; padding-top:16px;
  border-top:1px solid var(--line); display:flex; align-items:center;
  justify-content:space-between; gap:18px; flex-wrap:wrap}
.megafoot p{margin:0; font-size:14px; color:var(--muted)}

/* the phone toggle */
.navburger{display:none; margin-left:auto; width:42px; height:42px; padding:0;
  align-items:center; justify-content:center; background:none; border:1px solid var(--line);
  cursor:pointer}
.navburger span, .navburger span::before, .navburger span::after{
  content:""; display:block; width:19px; height:2px; background:var(--ink);
  transition:transform .2s, opacity .2s}
.navburger span::before{transform:translateY(-6px)}
.navburger span::after{transform:translateY(4px)}
.navburger[aria-expanded="true"] span{background:transparent}
.navburger[aria-expanded="true"] span::before{transform:rotate(45deg)}
.navburger[aria-expanded="true"] span::after{transform:rotate(-45deg) translateY(-2px)}
.navburger:focus-visible{outline:2px solid var(--rust); outline-offset:2px}

@media(max-width:1000px){
  .navburger{display:flex}
  .nav .call{display:none}
  .navlist{position:fixed; inset:auto 0 0 0; top:var(--navb,var(--navh,64px));
    margin:0; padding:8px 5% 40px; flex-direction:column; align-items:stretch; gap:0;
    background:var(--paper); border-top:1px solid var(--line);
    overflow-y:auto; max-height:calc(100vh - var(--navb,var(--navh,64px)));
    transform:translateY(-8px); opacity:0; visibility:hidden;
    transition:opacity .2s, transform .2s, visibility .2s}
  .navlist[data-open="true"]{opacity:1; visibility:visible; transform:none}
  .navtop{width:100%; justify-content:space-between; padding:15px 2px;
    border-bottom:1px solid var(--line-soft); font-size:17px; color:var(--ink)}
  .navtop[aria-expanded="true"]{border-bottom-color:var(--line-soft)}
  .navpanel, .navpanel.mega, .navpanel.simple{position:static; width:auto; opacity:1;
    visibility:visible; transform:none; display:none; grid-template-columns:1fr;
    border:0; box-shadow:none; background:transparent; padding:4px 0 12px}
  .navpanel[data-open="true"]{display:block}
  .navpanel.mega[data-open="true"]{display:grid}
  .megacol > p{margin-top:12px}
  .navpanel a{padding:11px 10px; font-size:15.5px}
  .megafoot{display:none}
}

/* ---------------------------------------------------------------- breadcrumbs */
/* 81 pages three levels deep needs a way back up that is not the browser button. */
.crumbs{background:var(--paper-2); border-bottom:1px solid var(--line-soft)}
.crumbs ol{margin:0; padding:11px 0; list-style:none; display:flex; flex-wrap:wrap;
  align-items:center; gap:8px; font:500 13px/1.5 var(--sans); color:var(--muted)}
.crumbs li{display:flex; align-items:center; gap:8px}
.crumbs li + li::before{content:"/"; color:var(--line); font-weight:400}
.crumbs a{color:var(--muted)}
.crumbs a:hover{color:var(--rust-dark); text-decoration:underline}
.crumbs [aria-current]{color:var(--ink); font-weight:600}

/* ---------------------------------------------------------------- explainer/list-columns */
/* A service page can carry a list of thirty-four items. Beside a photograph in a
   half-width column that is a single ribbon of text running far past the image,
   and the two columns end nowhere near each other — the client's word for it was
   "not proportionate".
   A long list is not prose with an illustration; it is an index. It goes full
   width in columns, with the photograph as a band above it where one is wanted. */
.lcols{padding:clamp(56px,6.5vw,92px) 0}
.lcols + .split, .split + .lcols, .lcols + .lcols{padding-top:0}
/* was max-width:1080px, which .wrap then centred inside the 1240 container and
   put every heading in this section 80px right of every other one */
.lcols > .wrap{max-width:var(--container, 1240px)}
.lcols h2{margin:0 0 16px; font:700 clamp(27px,3.5vw,48px)/1.1 var(--serif);
  color:var(--ink); letter-spacing:-.02em; max-width:24ch; text-wrap:balance}
.lcols > .wrap > p, .lcols-intro p{font-size:17.5px; line-height:1.75;
  color:var(--muted); max-width:68ch; margin:0 0 14px}
.lcols > .wrap > p + p{margin-top:14px}
.lcols-band{position:relative; margin:0 0 clamp(24px,3vw,38px); aspect-ratio:21 / 7;
  overflow:hidden; box-shadow:var(--shadow); background:var(--line-soft)}
.lcols-band img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.lcols ul{list-style:none; margin:clamp(18px,2.2vw,28px) 0 0; padding:0;
  column-count:3; column-gap:clamp(24px,3vw,46px)}
.lcols li{break-inside:avoid; position:relative; padding:0 0 9px 20px;
  font-size:15.5px; line-height:1.55; color:var(--ink-2)}
.lcols li::before{content:""; position:absolute; left:0; top:9px; width:7px; height:7px;
  background:var(--rust); flex:none}
@media(max-width:900px){ .lcols ul{column-count:2} }
@media(max-width:560px){ .lcols ul{column-count:1} }

/* ---------------------------------------------------------------- services/capability-tiles */
/* 21st.dev @sshahaider/grid-feature-cards, adapted for title-only items.
   The client's live pages list what they build as a run of bare lines — "Built-in
   BBQ grills", "Outdoor refrigerators and ice makers", nineteen of them. That is a
   set of capabilities, and set is the operative word: each item is a peer of the
   others and none has a description, so the right form is a grid of equal tiles,
   not a paragraph and not a bulleted column.
   The registry version pairs an icon with a title and a description. There are no
   icons and no descriptions here, so the tile carries the name alone with a rule
   in the accent for rhythm — inventing an icon per item would be inventing
   meaning the copy does not have. */
.caps{padding:clamp(56px,6.5vw,92px) 0}
.caps + .split, .split + .caps, .caps + .caps, .caps + .lcols{padding-top:0}
.caps h2{margin:0 0 14px; font:700 clamp(27px,3.5vw,48px)/1.1 var(--serif);
  color:var(--ink); letter-spacing:-.02em; max-width:26ch; text-wrap:balance}
.caps > .wrap > p{font-size:17.5px; line-height:1.75; color:var(--muted);
  max-width:68ch; margin:0 0 clamp(22px,2.6vw,32px)}
.caps > .wrap > p + p{margin-top:14px}
.caps-g{display:grid; grid-template-columns:repeat(auto-fill,minmax(250px,1fr));
  gap:10px}
.caps-i{background:var(--white); border:1px solid var(--line-soft);
  padding:18px 20px 20px; display:flex; flex-direction:column; gap:11px;
  min-height:96px; transition:background .2s, border-color .2s}
.caps-i:hover{background:var(--paper); border-color:var(--line)}
.caps-i::before{content:""; width:22px; height:2px; background:var(--rust); flex:none}
.caps-i span{font:600 15.5px/1.45 var(--sans); color:var(--ink)}
@media(max-width:560px){
  .caps-g{grid-template-columns:1fr}
  .caps-i{min-height:0; padding:15px 18px 16px}
}

/* ---------------------------------------------------------------- service-area/interactive-map */
/* "Areas We Serve" was a paragraph with twenty-five town names buried in it. The
   catalogue's standing note on this section is that the town names are what local
   searches match, so they must stay real text rather than being baked into a map
   image — which rules out an image, and a tile service would need an API key and an
   outside request from a static site.
   So the map is inline SVG at true coordinates: an equirectangular projection with
   longitude corrected by cos(latitude), which at 47°N compresses east-west by about
   a third. The names exist twice, as SVG labels and as a real list beside the map,
   and the two are linked in both directions. Distances are straight-line, computed
   from the coordinates — never drive times, which would be invented. */
.amap{padding:clamp(56px,6.5vw,92px) 0}
.amap-w{background:var(--white); border:1px solid var(--line-soft); box-shadow:var(--shadow);
  padding:clamp(26px,3.2vw,44px)}
.amap h2{margin:0 0 12px; font:700 clamp(27px,3.5vw,48px)/1.1 var(--serif);
  color:var(--ink); letter-spacing:-.02em}
.amap-lede{font-size:16px; line-height:1.72; color:var(--muted); margin:0}
.amap-g{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.02fr);
  gap:clamp(24px,3.4vw,48px); align-items:start}

.amap-fig{margin:0; position:relative; overflow:hidden; background:var(--paper);
  border:1px solid var(--line); box-shadow:var(--shadow)}
.amap-fig svg{display:block; width:100%; height:auto}

.amap-pt{cursor:pointer}
.amap-pt circle{fill:var(--rust); stroke:var(--white); stroke-width:2;
  transition:r .18s, fill .18s}
.amap-pt.t2 circle{fill:var(--pine); r:5}
.amap-pt.t3 circle{fill:var(--muted); r:4}
.amap-pt.base circle{fill:var(--ink); r:9}
.amap-pt text{font:700 11.5px/1 var(--sans); fill:var(--ink); pointer-events:none;
  paint-order:stroke; stroke:rgba(247,246,243,.92); stroke-width:3.5px; stroke-linejoin:round}
.amap-pt.t2 text, .amap-pt.t3 text{display:none}
.amap-pt:hover circle, .amap-pt.on circle{fill:var(--rust-lift); r:10}
/* a marker whose label was dropped for collision still shows it when active */
.amap-pt.on text{display:block}
.amap-pt.on text, .amap-pt:hover text{fill:var(--ink); font-weight:700}
.amap-pt:focus{outline:none}
.amap-pt:focus-visible circle{stroke:var(--ink); stroke-width:3}

.amap-side{display:flex; flex-direction:column; gap:16px}
.amap-read{min-height:74px; padding:15px 17px; background:var(--paper);
  border-left:3px solid var(--rust)}
.amap-read b{display:block; font:700 18px/1.2 var(--serif); color:var(--ink)}
.amap-read span{display:block; margin-top:5px; font-size:13.5px; color:var(--muted)}
.amap-list{list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:7px}
.amap-list button{padding:7px 13px; border:1px solid var(--line); background:var(--paper);
  font:600 13.5px/1 var(--sans); color:var(--ink-2); cursor:pointer;
  transition:background .16s, border-color .16s, color .16s}
.amap-list button:hover, .amap-list button.on{background:var(--rust); border-color:var(--rust);
  color:#fff}
.amap-list button:focus-visible{outline:2px solid var(--rust); outline-offset:2px}
.amap-note{margin:0; font-size:13px; line-height:1.6; color:var(--muted)}

@media(max-width:900px){
  .amap-g{grid-template-columns:1fr; gap:24px}
  .amap-pt.t2 text, .amap-pt.t3 text{display:none}
  .amap-pt.on text{display:block}
}

/* prose and lists inside a capability section share one measure so the band reads
   as composed rather than hard left */
/* The measure belongs to the prose and the heading. .caps-plain is a list and
   was inheriting a reading measure it has no use for. */
.caps > .wrap > p, .caps > .wrap > h2{max-width:72ch}
.caps-plain{margin-bottom:26px}
.caps-plain{list-style:none; margin:4px 0 6px; padding:0}
.caps-plain li{position:relative; padding:0 0 8px 20px; font-size:16px; line-height:1.7;
  color:var(--muted)}
.caps-plain li::before{content:""; position:absolute; left:0; top:10px; width:6px; height:6px;
  background:var(--rust); flex:none}
.caps-g{margin:6px 0 8px}

/* Square project cards: the client photographs the work as 1:1 cards with a
   caption band top and bottom, so a 4:3 tile would crop their own captions off. */
/* auto-fit, not a fixed four: these galleries hold four or five cards, and a
   fixed grid leaves the fifth stranded alone on a second row with three empty
   cells beside it. auto-fit collapses the empty tracks, so four cards make four
   columns and five make five, with no ragged tail either way. */
.tiles-sq{grid-template-columns:repeat(auto-fit,minmax(210px,1fr))}
.tiles-sq .tile{aspect-ratio:1 / 1}
/* a gallery of one photograph (Missoula Then & Now, 2 Oct 2026): the tile keeps the photograph’s own
   proportion and stops at a reading width instead of filling the grid as a giant square */
.tiles-one{grid-template-columns:minmax(0,820px)}
.tiles-one .tile{aspect-ratio:1200 / 863}
/* the historic composites are 4:3 with a caption panel in the picture: three across, their own proportion */
.tiles-43{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
.tiles-43 .tile{aspect-ratio:4 / 3}
/* a second heading inside a prose panel needs air above it (the merged Missoula page) */
.spanel-c h3{margin-top:1.1em}
@media(max-width:560px){ .tiles{grid-template-columns:1fr} }

/* ---------------------------------------------------------------- contact-form/stacked-panel */
/* The site had no form styling at all — these are built from the existing tokens
   so the fields read as native: paper ground, hairline rules, rust focus ring,
   serif heading. Inputs are 16px on purpose; anything smaller makes iOS Safari
   zoom the page on focus, which feels like a bug to the visitor. */
.fband{padding:clamp(66px,8vw,104px) 0; background:var(--paper)}
.fband-g{display:grid; grid-template-columns:.85fr 1fr; gap:clamp(34px,5vw,72px); align-items:start}
.fband h2{margin:0 0 16px; font:700 clamp(25px,2.9vw,38px)/1.14 var(--serif); color:var(--ink);
  letter-spacing:-.02em; text-wrap:balance}
.fband-intro p{margin:0 0 14px; color:var(--muted); line-height:1.75; max-width:46ch}
.fband-intro .fband-alt{margin-top:26px; padding-top:22px; border-top:1px solid var(--line)}
.fband-intro .fband-alt b{display:block; font:600 12px/1 var(--sans); letter-spacing:.12em;
  text-transform:uppercase; color:var(--rust-dark); margin-bottom:9px}
.fband-intro .fband-alt a{color:var(--ink); font-weight:600}

.fform{background:var(--white); border:1px solid var(--line-soft); box-shadow:var(--shadow);
  padding:clamp(22px,2.6vw,34px)}
.frow{display:grid; grid-template-columns:1fr 1fr; gap:16px}
.fld{display:flex; flex-direction:column; gap:7px; margin-bottom:16px}
.fld label{font:600 12.5px/1 var(--sans); letter-spacing:.04em; color:var(--ink)}
.fld label .req{color:var(--rust)}
.fld input,.fld select,.fld textarea{
  font:400 16px/1.5 var(--sans); color:var(--ink); background:var(--paper);
  border:1px solid var(--line); border-radius:0; padding:12px 13px; width:100%;
  transition:border-color .18s, box-shadow .18s}
.fld textarea{min-height:132px; resize:vertical}
.fld input:focus,.fld select:focus,.fld textarea:focus{outline:none; border-color:var(--rust);
  box-shadow:0 0 0 3px rgba(176,86,44,.16)}
/* :user-invalid, not :invalid — a required field is invalid from the moment the
   page loads, so plain :invalid paints every empty field red before the visitor
   has typed anything. :user-invalid waits until they have actually interacted.
   (The earlier :not(:placeholder-shown) guard did not work here because these
   inputs carry no placeholder, so the negation was always true.) */
.fld input:user-invalid,.fld select:user-invalid,.fld textarea:user-invalid{border-color:#a8412c}
.fld-note{font-size:12.5px; color:var(--muted)}

/* the honeypot must be unreachable, not merely invisible: display:none would let
   some bots skip it, so it is taken out of the layout and out of the tab order */
.fpot{position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden}

.fconsent{display:flex; gap:11px; align-items:flex-start; margin:4px 0 20px}
.fconsent input{width:17px; height:17px; margin-top:2px; flex:none; accent-color:var(--rust)}
.fconsent label{font:400 13.5px/1.6 var(--sans); color:var(--muted)}
.fconsent a{color:var(--ink); font-weight:600; text-decoration:underline}

.fform .btn{width:100%; justify-content:center}
.fform .btn[disabled]{opacity:.6; cursor:progress}
.fmsg{margin:16px 0 0; padding:13px 15px; font-size:14.5px; line-height:1.6; display:none}
.fmsg.show{display:block}
.fmsg.ok{background:#eef4ee; border-left:3px solid var(--pine); color:var(--ink)}
.fmsg.err{background:#fbeeea; border-left:3px solid #a8412c; color:var(--ink)}

@media(max-width:900px){
  .fband-g{grid-template-columns:1fr; gap:34px}
  .frow{grid-template-columns:1fr; gap:0}
}

/* ---------------------------------------------------------------- legal prose */
/* A policy page is read in sequence rather than scanned, so it gets a single
   narrow column and generous leading — the opposite of the rest of the site. */
.legal-body{max-width:74ch}
.legal-body .big{font-size:clamp(17px,1.5vw,20px); line-height:1.7; color:var(--ink); margin:0 0 34px}
.legal-body h2{margin:38px 0 12px; font:700 clamp(19px,1.9vw,24px)/1.25 var(--serif);
  color:var(--ink); letter-spacing:-.01em}
.legal-body p{margin:0 0 15px; color:var(--muted); line-height:1.8}
.legal-body ul{margin:0 0 18px; padding-left:20px; color:var(--muted); line-height:1.8}
.legal-body li{margin-bottom:7px}
.legal-body a{color:var(--ink); font-weight:600; border-bottom:1px solid var(--line)}
.legal-body a:hover{border-color:var(--rust); color:var(--rust)}

/* ---------------------------------------------------------------- capability list beside a photograph */
/* A list of things a trade OFFERS reads as a claim about work, so it wants to be
   seen, not just read. The client has no photograph of any individual finish, and
   captioning a generic interior "Traditional Venetian Plaster" would say something
   untrue — so one real interior sits beside the list rather than fourteen captioned
   cards. Swap to a per-item grid the day the client supplies finish photography. */
.capshot{padding:clamp(56px,6.5vw,92px) 0}
.capshot-g{display:grid; grid-template-columns:1fr .92fr; gap:clamp(30px,4.4vw,64px);
  align-items:center}
.capshot h2{margin:0 0 14px; font:700 clamp(27px,3.5vw,48px)/1.1 var(--serif); color:var(--ink);
  letter-spacing:-.02em; text-wrap:balance}
.capshot > .wrap > .capshot-g > div > p{font-size:17.5px; line-height:1.75; color:var(--muted);
  max-width:58ch; margin:0 0 24px}
.capshot-list{list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:1fr 1fr; gap:0 26px}
.capshot-list li{position:relative; padding:11px 0 11px 20px; font-size:16px; line-height:1.5;
  color:var(--ink); border-bottom:1px solid var(--line-soft)}
.capshot-list li::before{content:""; position:absolute; left:0; top:19px; width:9px; height:1px;
  background:var(--rust)}
.capshot-photo{position:relative; aspect-ratio:4/5; overflow:hidden}
.capshot-photo img{width:100%; height:100%; object-fit:cover; display:block;
  transition:transform .8s cubic-bezier(.16,1,.3,1)}
.capshot-photo:hover img{transform:scale(1.04)}
.capshot-photo figcaption{position:absolute; left:0; right:0; bottom:0; padding:34px 16px 13px;
  font:600 12px/1.35 var(--sans); letter-spacing:.04em; color:#fff;
  background:linear-gradient(rgba(12,14,13,0) 0%, rgba(12,14,13,.66) 40%, rgba(12,14,13,.95) 100%)}
@media(max-width:900px){
  .capshot-g{grid-template-columns:1fr; gap:34px}
  .capshot-photo{order:-1; aspect-ratio:3/2}
}
@media(max-width:560px){ .capshot-list{grid-template-columns:1fr} }

/* ---------------------------------------------------------------- short-item list */
/* Card chrome on a two-word item is a card wearing a label. Measured on this site:
   real card grids carry a median of 26 words per item, the promise strip 12; the
   chip grids that read as broken carry 2-3. Anything under 8 words uses this
   instead — a two-column rule-separated list, which also has no column count to
   leave an orphan row against. */
/* No max-width: .wrap is already the container, and a list stopping 300px short
   of it reads as truncated. Prose keeps its measure below; a list has none. */
.taglist{list-style:none; margin:22px 0 26px; padding:0;
  display:grid; grid-template-columns:1fr 1fr; gap:0 40px}
.taglist li{position:relative; padding:11px 0 11px 20px; font-size:16px; line-height:1.5;
  color:var(--ink); border-bottom:1px solid var(--line-soft)}
.taglist li::before{content:""; position:absolute; left:0; top:19px; width:9px; height:1px;
  background:var(--rust)}
/* a list past a dozen items runs three columns: the rule is against one column
   pair carrying a long list, not against the length */
.taglist.cols-3{grid-template-columns:repeat(3,1fr); gap:0 26px; max-width:none}
.caps-plain.cols-3{display:grid; grid-template-columns:repeat(3,1fr); gap:0 26px}
@media(max-width:900px){ .taglist.cols-3, .caps-plain.cols-3{grid-template-columns:1fr 1fr} }
@media(max-width:560px){ .taglist{grid-template-columns:1fr}
  .taglist.cols-3, .caps-plain.cols-3{grid-template-columns:1fr} }

/* ---------------------------------------------------------------- cta/mid-band */
/* A mid-page CTA needs its OWN ground. The one this replaced was an orphaned .caps
   wrapper — a capabilities band left holding a heading and two buttons after the
   explainer copy correctly moved out — sitting on the same paper as both of its
   neighbours. Measured: five transparent bands in a row, which is exactly the
   failure the Homestead band-rhythm note records.
   Quieter than the closing CTA by design: one primary control plus the phone as a
   text link, because the page already carried three near-identical button pairs.
   Repeating the call to action is the rule; repeating the identical pair is not. */
.ctamid{background:var(--ground-cta, #333e35); color:var(--on-cta, #fff);
  padding:clamp(28px,3.2vw,44px) 0}
/* stacked, matching the catalogue preview: the heading gets the full container
   measure instead of a 22ch cap that wrapped it to two lines with 178px empty
   beside the controls */
.ctamid .wrap{display:block}
.ctamid .eyebrow{margin:0 0 12px}
.ctamid h2{margin:0; font:700 clamp(25px,3.9vw,var(--fs-cta,54px))/1.06 var(--serif);
  color:var(--on-cta,#fff); letter-spacing:-.02em; text-wrap:balance; max-width:none}
.ctamid-act{display:flex; align-items:center; gap:clamp(18px,2.4vw,30px);
  flex-wrap:wrap; margin-top:clamp(18px,2.2vw,28px)}
.ctamid-tel{color:var(--on-cta,#fff); font:600 15px/1 var(--sans); white-space:nowrap;
  border-bottom:1px solid rgba(255,255,255,.45); padding-bottom:3px}
.ctamid-tel:hover{border-color:#fff}
@media(max-width:760px){
  .ctamid-act{gap:18px}
}


/* ---------------------------------------------------------------- services/card-slider */
/* Hand-ported from the client's 21st.dev reference. The track scrolls natively —
   scroll-snap, not a script — so a trackpad, a swipe, the keyboard and a screen
   reader all work before assets/js/ui.js loads. The arrows are an affordance. */
.svcslide{margin:26px 0 30px}
.svcslide-h{display:flex; align-items:flex-end; justify-content:space-between;
  gap:20px; margin:0 0 16px}
.svcslide-lead{margin:0; font-size:17.5px; line-height:1.75; color:var(--muted); max-width:62ch}
.svcslide-nav{display:flex; gap:8px; flex:none}
.svcslide--static .svcslide-nav{display:none}   /* the row already fits */

.svcslide-arrow{width:40px; height:40px; flex:none; display:grid; place-items:center;
  border:1px solid var(--line); background:var(--paper); color:var(--ink);
  cursor:pointer; transition:background .16s, border-color .16s, opacity .16s}
.svcslide-arrow:hover:not(:disabled){background:var(--rust); border-color:var(--rust); color:#fff}
.svcslide-arrow:disabled{opacity:.32; cursor:default}
.svcslide-arrow:focus-visible{outline:2px solid var(--rust); outline-offset:2px}
.svcslide-arrow svg{width:17px; height:17px; display:block}

.svcslide-track{list-style:none; margin:0; padding:2px; display:grid;
  grid-auto-flow:column; grid-auto-columns:minmax(268px, 1fr);
  gap:18px; overflow-x:auto; overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory; scrollbar-width:none}
.svcslide-track::-webkit-scrollbar{display:none}
.svcslide-track:focus-visible{outline:2px solid var(--rust); outline-offset:4px}

.svccard{position:relative; height:260px; overflow:hidden; scroll-snap-align:start;
  display:flex; flex-direction:column; justify-content:space-between;
  padding:20px; background:var(--ink-deep); isolation:isolate}
/* --crop and --scrim are set per card by the renderer. Eight photographs serve
   406 cards and no more exist in the source material, so a repeat is unavoidable;
   varying the focal point and the scrim weight is what stops a repeat inside one
   track reading as a tile that failed to load. */
.svccard img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  object-position:var(--crop, 50% 50%); z-index:-2; transition:transform .5s ease}
/* The scrim is what makes the type legible over a photograph, and it is heavier
   at the foot because that is where the name sits. */
/* Two fixed gradients where the type is, and the varied scrim only in between.
   The per-card variation is what stops eight photographs reading as a loading
   error, but varying the scrim under the TEXT took the lightest cards to 1.15:1
   over a bright sky. For white to clear 4.5:1 the pixel behind it needs relative
   luminance <= 0.183, which over ink needs roughly 0.6 alpha against the
   brightest photograph in the pool — so the top and bottom bands are fixed at
   more than that and only the middle varies. */
.svccard::after{content:""; position:absolute; inset:0; z-index:-1;
  /* One gradient, four stops. The three-layer version parsed but did not paint
     the top band, and a scrim that silently does nothing is worse than a flat
     one. Stops chosen from the measurement: white needs the pixel behind it at
     luminance <= 0.183 for 4.5:1, which over ink is about 0.6 alpha against the
     brightest sky in the pool. The middle stays light and carries the per-card
     variation, so the photographs still read as different. */
  background:linear-gradient(rgba(16,18,17,.78) 0%, rgba(16,18,17,var(--scrim, .34)) 34%,
    rgba(16,18,17,var(--scrim, .34)) 52%, rgba(16,18,17,.90) 100%)}
.svccard:hover img{transform:scale(1.04)}

.svccard-n{font:600 12px/1 var(--sans); letter-spacing:.16em; color:#fff; opacity:.62}
.svccard h3{margin:0; font:700 clamp(18px,1.5vw,21px)/1.22 var(--serif); color:#fff;
  text-wrap:balance}

@media(prefers-reduced-motion:reduce){
  .svcslide-track{scroll-behavior:auto}
  .svccard img, .svccard:hover img{transition:none; transform:none}
}
@media(max-width:900px){ .svcslide-track{grid-auto-columns:minmax(240px, 1fr)} }
@media(max-width:560px){
  .svcslide-track{grid-auto-columns:minmax(210px, 1fr)}
  .svccard{height:220px}
  .svcslide-h{flex-direction:column; align-items:flex-start}
}



/* ---- list measure -----------------------------------------------------------
   Every list in the content region reads as prose and takes prose's measure.
   The exceptions are the lists that DIVIDE the container into columns — those
   are a closed set, because a list only becomes multi-column by taking one of
   these classes, so the exemption list cannot fall behind the way a list of
   parent selectors did (it left 115 single-column lists at up to 155ch).

   Composed components are exempt outright: they own their own layout. */
main ul, main ol{max-width:var(--measure-wide, 68ch)}
main .taglist, main .cols-3, main .capshot-list, main .amap-list,
main .navlist, main [data-composed] ul, main [data-composed] ol,
main .plist-g, main .caps-g, main .tiles, main .pgal-g{max-width:none}

/* ---- a composed component owns its internals -------------------------------
   .lcols li::before and its siblings match any descendant <li>, so a section's
   bullet style painted a 7x7 rust square onto every card of a slider that
   happened to sit inside it. A component declares itself with data-composed;
   section-level list decoration stops at that boundary, the same way the checks
   do. */
/* Only UNCLASSED list items. A component's own classed elements carry their own
   decoration — .caps-i draws a rust rule and killing it here would strip the
   design the tiles are made of. The bleed this stops is a SECTION rule reaching
   a bare <li> inside a component. */
[data-composed] li:not([class])::before,
[data-composed] li:not([class])::after{content:none}
[data-composed] li:not([class]){padding-left:0}

/* ---- services/capability-tiles, as a converted slider ----------------------
   The lead-in keeps the measure and spacing it had inside the slider, so the
   sentence still reads as introducing what follows it. */
.caps-block{margin:22px 0 28px}
.caps-lead{margin:0 0 14px; font-size:17.5px; line-height:1.75; color:var(--muted); max-width:62ch}
.lcols-block{margin:22px 0 28px}
.lcols-list{columns:2; column-gap:40px; margin:0; padding-left:20px}

/* ---- capability tiles, sized to their content -------------------------------
   .caps-i was authored with min-height:96px for tiles that carried a line of
   description. These carry a service NAME and nothing else, so 96px left half
   the tile empty and the grid read as a wall of blank boxes. Sized to the label,
   with the rust rule above it as the catalogue preview shows. */
.caps-block .caps-g{gap:8px}
.caps-block .caps-i{min-height:0; padding:13px 16px 15px; gap:9px;
  flex-direction:column; align-items:flex-start}
.caps-block .caps-i::before{width:20px; height:2px}
.caps-block .caps-i span{font-size:15px; line-height:1.4}
@media(max-width:900px){ .caps-block .caps-g{grid-template-columns:repeat(2,1fr)} }
@media(max-width:560px){ .caps-block .caps-g{grid-template-columns:1fr} }

/* ---- services/grouped-index -------------------------------------------------
   The grouping is the design: 39 links flat is a wall, the same 39 under five
   named families is five short scannable lists. Families come from the site's
   own navigation, so a visitor meets the same taxonomy twice. */
.sindex{padding:clamp(56px,6.5vw,92px) 0; background:var(--paper-2)}
.sindex header{max-width:62ch; margin:0 0 30px}
.sindex h2{margin:0 0 12px; font:700 clamp(27px,3.5vw,48px)/1.1 var(--serif);
  color:var(--ink); letter-spacing:-.02em}
.sindex header p{margin:0; font-size:17.5px; line-height:1.75; color:var(--muted)}
.sindex-g{display:grid; grid-template-columns:repeat(auto-fit,minmax(258px,1fr)); gap:30px 34px}
.sindex-fam h3{margin:0 0 12px; padding-bottom:9px; border-bottom:1px solid var(--line);
  font:600 12.5px/1 var(--sans); letter-spacing:.14em; text-transform:uppercase;
  color:var(--rust-dark)}
.sindex-fam ul{list-style:none; margin:0; padding:0; max-width:none}
.sindex-fam li + li{margin-top:2px}
.sindex-fam a{display:block; padding:7px 0; font-size:15.5px; line-height:1.45;
  color:var(--ink); border-bottom:1px solid transparent; transition:color .15s, border-color .15s}
.sindex-fam a:hover{color:var(--rust-dark); border-bottom-color:var(--rust)}

/* ---- team/pair-portraits ----------------------------------------------------
   Two people, so the portraits can be large — large is what makes them read as
   people rather than thumbnails. */
.pteam2{padding:clamp(56px,6.5vw,92px) 0}
.pteam2 header{margin:0 0 32px}
.pteam2 h2{margin:0; font:700 clamp(27px,3.5vw,48px)/1.1 var(--serif); color:var(--ink);
  letter-spacing:-.02em}
.pteam2-g{display:grid; grid-template-columns:repeat(auto-fit,minmax(340px,1fr)); gap:clamp(28px,4vw,54px)}
.pteam2-p{display:grid; grid-template-columns:minmax(0,.82fr) minmax(0,1fr); gap:22px;
  align-items:start}
.pteam2-shot{position:relative; display:block; aspect-ratio:4/5; overflow:hidden;
  background:var(--line-soft); box-shadow:var(--shadow)}
.pteam2-shot img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
.pteam2-p h3{margin:0; font:700 clamp(20px,2.1vw,26px)/1.15 var(--serif); color:var(--ink)}
.pteam2-p h3 a{color:inherit}
.pteam2-role{margin:7px 0 14px; font:600 12px/1 var(--sans); letter-spacing:.14em;
  text-transform:uppercase; color:var(--rust-dark)}
.pteam2-p p{margin:0 0 14px; font-size:16px; line-height:1.7; color:var(--muted); max-width:46ch}
.pteam2-more{font:600 14.5px/1 var(--sans); color:var(--ink); border-bottom:1px solid var(--rust);
  padding-bottom:3px}
.pteam2-more:hover{color:var(--rust-dark)}
@media(max-width:760px){ .pteam2-p{grid-template-columns:1fr; gap:16px} }

/* Adjacent prose bands collapse their shared padding.
   Each of these sections carries a full pad top and bottom, which is right when a
   band sits between two different kinds of section and wrong when two prose bands
   meet: the two pads add and open a gap about 240px tall that reads as a missing
   section rather than a break. The existing rules covered every pairing with
   .split but not the prose bands against each other, which is the adjacency the
   content document's H2 layer creates. */
.caps + .stackw, .stackw + .caps,
.stackw + .stackw, .stackw + .lcols,
.lcols + .stackw, .lcols + .caps,
.caps + .capshot, .capshot + .caps,
.stackw + .capshot, .capshot + .stackw{padding-top:0}
/* The overview band is followed by the document's first H2 section on every page
   the content pack touches, so it needs the same collapse. */
.lede + .caps, .lede + .stackw, .lede + .lcols{padding-top:0}

/* ---- paper band: the light ground -------------------------------------------
   The workhorse ground for prose sections with no photograph. It takes the paper
   tone and a hairline top and bottom so it reads as a band without raising its
   voice — which is exactly what lets the dark full-bleed band stay rare enough to
   remain a moment. Type matches the other prose bands so the ground is the only
   thing that changes. */
/* --paper IS the body background, so a band painted with it is the page colour and
   changes nothing — all 45 of these read as dry, correctly. --paper-2 is the real
   step down, the tone the services band already uses. */
.pband{padding:clamp(56px,6.5vw,92px) 0; background:var(--paper-2);
  border-top:1px solid var(--line-soft); border-bottom:1px solid var(--line-soft)}
.pband h2{margin:0 0 16px; font:700 clamp(27px,3.5vw,48px)/1.1 var(--serif);
  color:var(--ink); letter-spacing:-.02em; max-width:26ch; text-wrap:balance}
.pband > .wrap > p{font-size:17.5px; line-height:1.75; color:var(--muted); max-width:68ch}
.pband > .wrap > p + p{margin-top:15px}
.pband ul{color:var(--muted); font-size:16px; line-height:1.75; max-width:66ch; padding-left:20px}
/* two adjacent paper bands read as one taller band, so they never share an edge —
   the placement rule forbids the adjacency, this is the belt to that braces */
.pband + .pband{border-top:0; padding-top:0}

/* ---- surface panel: the raised slab -----------------------------------------
   For detail that qualifies or supports the claim above it. The copy sits on its
   own surface with an edge and a soft shadow, so it reads as an object laid on the
   page rather than more of the same column — which is what says "this is a note on
   the thing above" without a heading having to say it. */
.spanel{padding:clamp(48px,5.5vw,78px) 0}
.spanel-c{background:var(--white,#fff); border:1px solid var(--line-soft);
  box-shadow:0 2px 10px rgba(16,18,17,.06);
  padding:clamp(30px,3.6vw,52px) clamp(28px,3.4vw,56px)}
.spanel h2{margin:0 0 14px; font:700 clamp(24px,3vw,40px)/1.12 var(--serif);
  color:var(--ink); letter-spacing:-.02em; max-width:28ch; text-wrap:balance}
.spanel p{font-size:17px; line-height:1.75; color:var(--muted); max-width:64ch}
.spanel p + p{margin-top:14px}
.spanel ul{color:var(--muted); font-size:16px; line-height:1.75; padding-left:20px}
.pband + .spanel, .spanel + .pband, .spanel + .spanel{padding-top:0}
.caps + .pband, .pband + .caps, .stackw + .pband, .pband + .stackw,
.lcols + .pband, .pband + .lcols, .fbleed + .pband{padding-top:0}

/* ---- sidehead: the structural treatment --------------------------------------
   Heading in a narrow left column, body right, hairline between. The fixed left
   edge is the device: it gives the eye somewhere to return to and gives the copy a
   reading measure instead of the full container width. No imagery required. */
.sidehead{padding:clamp(52px,6vw,84px) 0}
.sidehead-g{display:grid; grid-template-columns:minmax(200px,30%) 1fr;
  gap:clamp(24px,3.4vw,52px); align-items:start}
.sidehead-h{border-right:1px solid var(--line); padding-right:clamp(18px,2.4vw,36px)}
/* A sidehead heading is a LABEL, not a band headline, so it is set well below the
   other section headings. At display size the client SEO headings — median 11 words
   — ran to five and six lines in the narrow column, which is the fault the design own
   avoid-note names. At label size the median fits in three. */
.sidehead h2{margin:0; font:700 clamp(18px,1.55vw,22px)/1.28 var(--serif);
  color:var(--ink); letter-spacing:-.02em; text-wrap:balance}
.sidehead-c p{font-size:17.5px; line-height:1.75; color:var(--muted); max-width:64ch}
.sidehead-c p + p{margin-top:15px}
.sidehead-c ul{color:var(--muted); font-size:16px; line-height:1.75; padding-left:20px; margin-top:14px}
.sidehead + .sidehead, .pband + .sidehead, .sidehead + .pband,
.spanel + .sidehead, .sidehead + .spanel, .caps + .sidehead, .sidehead + .caps{padding-top:0}
@media (max-width:860px){
  .sidehead-g{grid-template-columns:1fr; gap:16px}
  /* the rule moves under the heading — a border on the right of a full-width block
     is a line down the middle of nothing */
  .sidehead-h{border-right:0; border-bottom:1px solid var(--line);
    padding-right:0; padding-bottom:14px}
}

/* ---- on-dark link colour, at a specificity that actually wins -----------------
   `.fbleed p a:not(.btn)` (0,2,2) was being outranked by
   `main p a:not(.btn):not([class*="card"])` (0,2,3), so links on the dark bands
   kept the near-black body link colour: measured 1.1:1 against the band. The rule
   existed and had simply never fired, because until the content pack landed no dark
   band on these pages contained a link. */
main .fbleed p a:not(.btn):not([class*="card"]),
main .ctamid p a:not(.btn):not([class*="card"]),
main .end p a:not(.btn):not([class*="card"]){
  color:var(--link-on-dark); border-bottom:0;
  /* text-decoration, not a border: the underline then follows currentColor, so it
     is genuinely part of the glyph run rather than a bright rule sitting behind it.
     As a border it also defeated contrast sampling, which hides glyphs by setting
     colour transparent and then read the white rule as the background. */
  text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:3px;
  text-decoration-color:rgba(255,255,255,.5)}
main .fbleed p a:not(.btn):not([class*="card"]):hover,
main .ctamid p a:not(.btn):not([class*="card"]):hover,
main .end p a:not(.btn):not([class*="card"]):hover{
  color:#fff; border-bottom-color:#fff}

/* ---- the lede's ground -------------------------------------------------------
   --white is the card surface: one clear step LIGHTER than the body paper, where
   --paper-2 is a step darker. Lighter is right here because the hero above is a dark
   photographic band, and it also guarantees a lede and a paper-band body section
   below it can never read as a single band. */
.ledeband{background:var(--white); border-bottom:1px solid var(--line-soft)}
.ledeband + .pband, .ledeband + .spanel, .ledeband + .sidehead{padding-top:clamp(56px,6.5vw,92px)}

/* lede/two-column-rule: only where the paragraph is long enough to earn it */
.ledecols{background:var(--white); border-bottom:1px solid var(--line-soft)}
.ledecols-g{column-count:2; column-gap:clamp(32px,4vw,64px); column-rule:1px solid var(--line)}
.ledecols-g p{margin:0}
@media (max-width:820px){ .ledecols-g{column-count:1; column-rule:0} }

/* ---- team/monogram-grid-modal -------------------------------------------------
   Built so a monogram is the intended state, not a gap: the initials sit in a square
   of their own ground at the footprint a portrait would take, so a grid of them reads
   as a deliberate set. When real headshots arrive the photograph replaces the
   monogram in the same square at the same aspect and nothing else changes. */
.tgrid{padding:clamp(56px,6.5vw,92px) 0; background:var(--paper-2);
  border-top:1px solid var(--line-soft); border-bottom:1px solid var(--line-soft)}
.tgrid h2{margin:0 0 6px; font:700 clamp(27px,3.5vw,44px)/1.1 var(--serif); color:var(--ink)}
.tgrid-g{display:grid; gap:clamp(18px,2.4vw,30px); margin-top:clamp(24px,3vw,40px);
  /* two members: two wide and large, which is what stops a pair looking sparse.
     five: three wide. past six: four wide. */
  grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
.tgrid-i{background:var(--white); border:1px solid var(--line-soft);
  padding:clamp(22px,2.6vw,32px); display:flex; flex-direction:column; align-items:flex-start;
  gap:10px; box-shadow:0 2px 10px rgba(16,18,17,.05)}
.tgrid-shot{width:clamp(88px,9vw,116px); aspect-ratio:1/1; display:grid; place-items:center;
  background:var(--ink-deep); flex:none}
.tgrid-shot span{font:700 clamp(26px,2.8vw,38px)/1 var(--serif); color:#fff; letter-spacing:.02em}
.tgrid-i h3{margin:6px 0 0; font:700 clamp(21px,2.2vw,28px)/1.15 var(--serif); color:var(--ink)}
.tgrid-role{margin:0; font-size:13px; font-weight:600; letter-spacing:.06em;
  text-transform:uppercase; color:var(--rust-dark)}
.tgrid-note{margin:0; font-size:16.5px; line-height:1.7; color:var(--muted)}
.tgrid-more{margin-top:6px; background:none; border:0; padding:0; cursor:pointer;
  font:600 15.5px/1.4 var(--sans); color:var(--link-on-light);
  border-bottom:1px solid currentColor}
.tgrid-more:hover{color:var(--rust-dark)}
.tgrid-more:focus-visible, .tmodal-x:focus-visible{outline:2px solid var(--rust); outline-offset:3px}

.tmodal[hidden]{display:none}
.tmodal{position:fixed; inset:0; z-index:80; display:grid; place-items:center; padding:20px}
.tmodal-back{position:absolute; inset:0; background:rgba(11,14,12,.62)}
.tmodal-card{position:relative; background:var(--white); max-width:660px; width:100%;
  max-height:86vh; overflow:auto; padding:clamp(26px,3.4vw,44px);
  box-shadow:0 18px 60px rgba(11,14,12,.34);
  /* touch: momentum scrolling inside the card, and the card never exceeds the viewport */
  -webkit-overflow-scrolling:touch}
.tmodal-card h3{margin:0 0 4px; font:700 clamp(24px,2.8vw,34px)/1.14 var(--serif); color:var(--ink)}
.tmodal-card h4{margin:22px 0 8px; font:700 17.5px/1.3 var(--serif); color:var(--ink)}
.tmodal-card p{font-size:16.5px; line-height:1.75; color:var(--muted); margin:0 0 12px}
.tmodal-x{position:absolute; top:10px; right:12px; background:none; border:0; cursor:pointer;
  font:400 30px/1 var(--sans); color:var(--muted); padding:6px 10px}
.tmodal-x:hover{color:var(--ink)}
body.tmodal-lock{position:fixed; width:100%; overflow:hidden}
@media (prefers-reduced-motion:reduce){ .tmodal, .tmodal-card{animation:none; transition:none} }

/* ---- a ground under a tile grid ----------------------------------------------
   A capability-tile grid is already a device; what it lacked was anything behind it,
   so the tiles floated on the page's own paper and the band read as undesigned. This
   adds only the ground — the tiles themselves are untouched. */
.caps.on-paper, .lcols.on-paper{background:var(--paper-2);
  border-top:1px solid var(--line-soft); border-bottom:1px solid var(--line-soft)}
.on-paper + .on-paper{border-top:0; padding-top:0}

/* ---------------------------------------------------------------- related-services/sibling-links */
/* Name-only links to sibling service pages. The quiet paper band, because this is an index
   rather than one more argument — it sits below the page's closing copy and above the
   closing call to action, and it must not compete with either. Two columns at 1440 and one
   below 720: the names run 2-5 words, so three columns would leave a ragged last row that
   grid-check would rightly object to. */
/* --paper-2 was wrong here: pband already uses it, so on two pages this band landed
   between two paper-2 neighbours and contract-check reported the middle one as having no
   ground of its own. The white surface is the third light tone and nothing else uses it as
   a full band. */
.sibling{padding:clamp(48px,5.5vw,78px) 0; background:var(--white,#fff);
  border-top:1px solid var(--line-soft); border-bottom:1px solid var(--line-soft)}
.sibling h2{margin:0 0 14px; font:700 clamp(23px,2.6vw,34px)/1.14 var(--serif); color:var(--ink)}
.sibling p{margin:0 0 20px; max-width:70ch; color:var(--ink-2)}
.sibling-g{list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:1fr 1fr; gap:0 clamp(24px,3vw,48px)}
.sibling-g li{border-top:1px solid var(--line)}
.sibling-g li:first-child, .sibling-g li:nth-child(2){border-top:0}
.sibling-g a{display:block; padding:13px 0; color:var(--ink); font:600 16.5px/1.35 var(--sans);
  text-decoration:none; transition:color .18s}
.sibling-g a:hover{color:var(--rust)}
.sibling-g a:focus-visible{outline:2px solid var(--rust); outline-offset:3px}
@media(max-width:720px){
  .sibling-g{grid-template-columns:1fr}
  .sibling-g li:nth-child(2){border-top:1px solid var(--line)}
}

/* ---------------------------------------------------------------- ground boundaries do not collapse
 * The padding-top:0 rules above turn two adjacent 92px paddings into a single gap. That is
 * right when the two bands SHARE a ground — they read as one continuous surface and one gap
 * is what you want. It is wrong across a ground boundary: the upper band's padding sits
 * INSIDE its own colour, so the lower band starts hard against a visible edge with no space
 * of its own, and its heading reads as spill rather than as the start of a band.
 *
 * 72 bands across 34 pages were in that state, the most common being pband + sidehead (14).
 * Nothing caught it because every check measures what is inside a section and none measures
 * the space around one.
 *
 * .pband and .caps/.lcols.on-paper are the only body treatments that paint a ground, and all
 * three use the same --paper-2, so they collapse against each other correctly. Everything
 * else is transparent. These rules restore the band's own top padding wherever exactly one
 * side of the pair paints. Grouped by the TARGET's padding, since the rule sets the second
 * element's value, not the pair's.
 */
:is(.pband, .on-paper) + :is(.split, .stackw, .caps, .lcols, .capshot):not(.on-paper),
:is(.split, .stackw, .caps, .lcols, .capshot, .sidehead, .spanel):not(.on-paper) + :is(.pband, .caps.on-paper, .lcols.on-paper),
.fbleed + :is(.pband, .caps.on-paper, .lcols.on-paper){padding-top:clamp(56px,6.5vw,92px)}

:is(.pband, .on-paper) + .sidehead{padding-top:clamp(52px,6vw,84px)}
:is(.pband, .on-paper) + .spanel{padding-top:clamp(48px,5.5vw,78px)}

/* ---------------------------------------------------------------- card slider: pointer drag
 * Cosmetics only — the drag itself is in ui.js and the strip scrolls natively without either.
 * `grab` is set on a pointer device only, so a touch user is not told they can drag something
 * they should be flicking instead. During a drag, snapping is suspended so the track follows
 * the cursor exactly rather than fighting it, and text selection is off so a pull across two
 * cards does not highlight their headings. */
/* The CURSOR is an affordance and belongs behind the pointer query — a touch user should not
   be told they can grab something they should be flicking. Suspending the snap and the text
   selection is BEHAVIOUR, and must not be: the class is only ever added by the pointer handler,
   which already ignores touch, and putting these behind the media query meant that on any
   device where it did not match the drag set scrollLeft and the snap immediately pulled it
   back — the drag appeared to do nothing at all. */
.svcslide-track.is-dragging{scroll-snap-type:none; user-select:none}
.svcslide-track.is-dragging a{pointer-events:none}
@media (hover: hover) and (pointer: fine){
  .svcslide-track{cursor:grab}
  .svcslide-track.is-dragging{cursor:grabbing}
}

/* ---------------------------------------------------------------- mega panel: family labels
 * The column heading is now a link to that family's routing page — hover exposes the family,
 * clicking the label goes to the page that lists it. It has to READ as a link without turning
 * a quiet heading into a loud one, so it keeps the label's own type and takes an underline on
 * hover and focus rather than a colour change. */
.megacol p a.megatop{color:inherit; text-decoration:none; border-bottom:1px solid transparent}
.megacol p a.megatop:hover{border-bottom-color:currentColor}
.megacol p a.megatop:focus-visible{outline:2px solid var(--rust); outline-offset:3px}

/* ---------------------------------------------------------------- pricing/guided-ranges
 * Two radio groups over the client's own published ranges. EVERY RANGE IS IN THE DOCUMENT
 * from first paint; the script only hides the scopes that do not belong to the chosen project.
 * Without it the section is three labelled lists of prices — usable, and indexable, which
 * matters here more than anywhere because this is the page named after the question. */
.est{padding:clamp(56px,6.5vw,92px) 0; background:var(--paper-2);
  border-top:1px solid var(--line-soft); border-bottom:1px solid var(--line-soft)}
.est h2{margin:0 0 14px; font:700 clamp(27px,3.5vw,48px)/1.1 var(--serif); color:var(--ink)}
.est-lead{margin:0 0 26px; max-width:62ch; font-size:17.5px; line-height:1.7; color:var(--muted)}
.est fieldset{border:0; margin:0 0 22px; padding:0; min-width:0}
.est legend{padding:0; margin:0 0 12px; font:600 13px/1.3 var(--sans);
  letter-spacing:.06em; text-transform:uppercase; color:var(--rust-dark)}
.est ul{list-style:none; margin:0; padding:0; display:grid; gap:10px}
.est-kinds{grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}
.est-tiers{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}
/* the whole option is the target, never a 16px radio on its own */
.est-opt{display:flex; flex-direction:column; gap:4px; min-height:56px; padding:14px 16px;
  background:var(--white,#fff); border:1px solid var(--line-soft); border-radius:2px;
  cursor:pointer}
.est-opt:hover{border-color:var(--rust)}
.est-opt input{position:absolute; opacity:0; width:1px; height:1px}
.est-opt:has(input:checked){border-color:var(--rust); box-shadow:inset 0 0 0 1px var(--rust)}
.est-opt:has(input:focus-visible){outline:2px solid var(--rust); outline-offset:2px}
.est-name{font:600 16.5px/1.35 var(--sans); color:var(--ink)}
.est-range{font:700 17px/1.35 var(--serif); color:var(--rust-dark)}
.est-note{font-size:14.5px; line-height:1.5; color:var(--muted)}
.est-src{margin:0; font-size:14.5px; color:var(--muted)}
.est-ask{display:flex; flex-wrap:wrap; align-items:center; gap:16px; margin:26px 0 0}
.est-tel{font:700 17px/1 var(--serif); color:var(--ink)}
/* `main ul, main ol{max-width:var(--measure-wide,68ch)}` is a READING measure, correct for a
   bulleted list in prose and wrong for a grid of controls — it was capping both option grids
   at 741px inside a 1163px band, so the estimator sat in the left two-thirds of its own
   section. A control grid is not prose. */
.est ul{max-width:none}
/* The live region restates the option already shown as selected, so it is visual duplication —
   but it still has to be ANNOUNCED, because the scope group swaps away from the control that
   caused it and a screen reader user would otherwise hear nothing happen. Hidden from sight,
   not from the accessibility tree: display:none would remove it from both. */
.est-live{position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0}

/* ---- the booking call to action in the nav bar -----------------------------
   Replaces the telephone number that used to sit here. The site's .btn is sized for a hero —
   17px of vertical padding makes a 51px control, which will not sit in a 64px bar — so this is
   the same button at bar scale, borrowing every colour and the font from .btn itself.

   It carries its own class rather than being written as `.nav .btn`, because the Services mega
   panel already contains a full-size .btn and that selector would shrink it too.

   Hidden below 1000px exactly where .call was hidden: at that width the bar holds the brand and
   the burger, and booking is still reachable one tap in, from the Services panel's own button
   and from every hero. */
.navbook{padding:11px 18px; font:700 14px/1 var(--sans); white-space:nowrap}
@media(max-width:1000px){ .navbook{display:none} }

/* ---- booking popup: Jobber's request form in the profile-dialog frame -------------------------
   The frame, backdrop, close control and focus behaviour are the team dialog's. Only the card
   differs: wider and taller, because the form is about 870px and steps through pages. The embed
   host keeps a minimum height so Jobber's own spinner has room while the form loads. */
.tmodal.book .tmodal-card{max-width:720px; max-height:calc(100vh - 40px); padding:clamp(22px,3vw,36px) clamp(18px,2.6vw,36px) clamp(18px,2.6vw,28px)}
.tmodal.book .tmodal-card h3{margin-bottom:8px}
.book-embed{position:relative; min-height:280px; margin:6px 0 4px}
.book-embed iframe{width:100%; border:0; display:block}
.tmodal.book .book-alt{margin:10px 0 0; font-size:15px}
@media (max-width:640px){
  .tmodal.book{padding:0; place-items:end center}
  .tmodal.book .tmodal-card{max-height:100vh; max-height:100dvh; box-shadow:none}
}

/* ---- our why: the manifesto stack (about/manifesto-stack) ----
   The client's creed on the About page. Dark ground so the band reads as a statement rather than
   another paragraph band; a diptych of the slogan's two halves under the headline; then each part
   as a hairline row with its name at the left, the way .sidehead sets a label beside its copy.
   Written phone-up: 390 first, widening at the site's 900px breakpoint. The check marks are drawn
   here, not typed into the copy, so the words stay exactly the client's. */
.why{background:var(--ink-deep); color:rgba(255,255,255,.84); padding:clamp(56px,6.5vw,92px) 0}
.why h2, .why h3, .why strong{color:#fff}
.why h2{max-width:14ch}
.why-head{display:grid; gap:28px}
.why-intro .eyebrow{margin-bottom:16px}
.why-pair{display:grid; grid-template-columns:1fr 1fr; gap:12px; margin:0}
.why-pair figure{margin:0; min-width:0}
.why-pair img{width:100%; height:auto; aspect-ratio:4/3; object-fit:cover; display:block}
.why-pair figcaption{margin-top:10px; font:700 11.5px/1.3 var(--sans); letter-spacing:.16em;
  text-transform:uppercase; color:rgba(255,255,255,.66)}
.why-rows{margin-top:clamp(36px,4.5vw,64px); border-top:1px solid rgba(255,255,255,.18)}
.why-row{display:grid; gap:14px; padding:clamp(26px,3.2vw,40px) 0; border-bottom:1px solid rgba(255,255,255,.18)}
.why-row h3{font:700 clamp(18px,1.55vw,22px)/1.28 var(--serif); letter-spacing:-.006em}
.why-copy p{font-size:17.5px; line-height:1.75; max-width:62ch}
.why-copy p + p, .why-copy ul + p{margin-top:15px}
.why-copy strong{font-weight:600}
main .why-std{list-style:none; padding:0; margin:0 0 18px; display:grid; gap:10px; max-width:62ch}
.why-std li{position:relative; padding-left:36px; color:#fff; font-weight:600; font-size:18px; line-height:1.5}
.why-std li::before{content:""; position:absolute; left:0; top:.14em; width:22px; height:22px;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4.5 12.5l4.8 4.8L19.5 7' fill='none' stroke='%23CB6034' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/contain no-repeat}
/* The closing line is set in the serif at the row-label size — the same size as the h3 beside it
   and never larger — so the page's type hierarchy holds: no paragraph on the site may render
   bigger than its smallest section heading, and here that heading is the 22px sidehead label. */
.why-copy .why-final{font:700 clamp(18px,1.55vw,22px)/1.3 var(--serif); letter-spacing:-.006em; color:#fff;
  max-width:36ch; margin-top:22px; text-wrap:balance}
@media(min-width:900px){
  .why-head{grid-template-columns:minmax(0,1fr) minmax(0,1.05fr); align-items:end; gap:clamp(28px,4vw,64px)}
  .why-row{grid-template-columns:minmax(200px,30%) 1fr; gap:clamp(24px,3vw,48px)}
  .why-row h3{padding-right:clamp(18px,2.4vw,36px)}
}
/* THE STACK. The rows pin under the nav one after another and each next row slides up over the
   one before it, which recedes a little as it is covered (ui.js sets the transform). Every row is
   a panel of the same minimum height so each has its moment before the next arrives, with an
   opaque ground, a hairline on its top edge and a shadow above it so it reads as a surface
   sliding over. The class is put on by ui.js only when every row fits the screen below the nav —
   a panel that covered text before it could be read would be worse than no motion — so on a
   phone the rows usually stay in plain flow. Never under prefers-reduced-motion. */
.why-stack .why-rows{border-top:0}
.why-stack .why-row{position:sticky; top:calc(var(--navh,72px) + 20px); background:var(--ink-deep);
  min-height:min(58vh,600px); border-top:1px solid rgba(255,255,255,.18); border-bottom:0;
  box-shadow:0 -22px 44px -26px rgba(0,0,0,.85); transform-origin:50% 0; will-change:transform}
.why-stack .why-row:last-child{border-bottom:1px solid rgba(255,255,255,.18)}
/* The covered row dims under a veil of its own ground rather than by opacity: a translucent row
   would let the row pinned beneath it show through its text. */
.why-stack .why-row::after{content:""; position:absolute; inset:-1px 0 0 0; background:var(--ink-deep);
  opacity:var(--veil,0); pointer-events:none}
@media(prefers-reduced-motion:reduce){
  .why-stack .why-row{position:static; min-height:0; box-shadow:none; transform:none!important}
  .why-stack .why-row::after{display:none}
}
/* ---- end: our why ---- */

/* ------------------------------------------------- carried-over legal pages — 19 Sep 2026
   Four pages the live site has that the SEO document never mentions (legal notice, merchant
   policies, terms, refunds) were carried across rather than dropped at cutover. Two new shapes
   came with them, and both are scoped so nothing already on the page moves.

   NOTE for whoever reads this next: the footer's fine-print row is marked up as .fine while the
   stylesheet above styles footer .base, so those rules have never applied. That is pre-existing
   and is NOT fixed here — the strip below is written against .fine, the class the markup
   actually carries, so it stands on its own either way. */
footer .fine .legal{display:flex; flex-wrap:wrap; gap:8px 0; margin:14px 0 0; font-size:12.5px}
footer .fine .legal li{display:flex; align-items:center}
/* the separator trails its own item rather than leading the next one, so a line that wraps never
   opens with a stray middot — which is what five links did at 390px */
footer .fine .legal li:not(:last-child)::after{content:"\00B7"; opacity:.45; padding:0 9px}
footer .fine .legal a{color:rgba(255,255,255,.6)}
footer .fine .legal a:hover{color:#fff}

/* the legal notice's registered details come through the sidehead renderer as
   <p><strong>label</strong> value</p>, so the label is set apart by weight and case rather than by
   a definition list — the <dl> the source stub carried is never emitted. */
[data-section="legal-notice"] .sidehead-c p{margin-top:18px}
[data-section="legal-notice"] .sidehead-c p:first-child{margin-top:0}
[data-section="legal-notice"] .sidehead-c p strong{display:block; font:600 13px/1.2 var(--sans);
  letter-spacing:.09em; text-transform:uppercase; color:var(--ink); margin-bottom:5px}
[data-section="merchant-policies"] .sidehead-c p strong{display:block; font:600 13px/1.2 var(--sans);
  letter-spacing:.09em; text-transform:uppercase; color:var(--ink); margin-bottom:5px}

/* ---------------------------------------------------------------- gallery/timeline-slider: "Missoula, then"
   Written by then-slider.mjs — 2 Oct 2026. One stage, the caption and count under it, a strip of all 21.
   Moving content rules: advances on its own only while on screen, stops for good on any click, no
   visible pause control (the real one appears on keyboard focus), nothing moves under reduced motion. */
.then{display:grid; gap:16px}
/* full width of the band, a controlled height: the 4:3 photograph sits centred on black with the arrows
   in the margins either side; on a phone the stage takes the photograph’s own proportion */
.then-stage{position:relative; width:100%; height:clamp(420px,52vw,720px); background:#000; overflow:hidden}
.then-shot{position:absolute; inset:0; width:100%; height:100%; border:0; padding:0; margin:0; background:#000; cursor:zoom-in; display:block}
.then-shot:focus-visible{outline:2px solid var(--rust-lift); outline-offset:-3px}
.then-img{position:absolute; inset:0; width:100%; height:100%; object-fit:contain; opacity:0;
  transition:opacity .7s ease, transform 7s linear; transform:scale(1)}
.then-img.is-on{opacity:1}
.then.is-playing .then-img.is-on{transform:scale(1.035)}
.then-arrow{position:absolute; top:50%; transform:translateY(-50%); width:50px; height:50px; border-radius:50%;
  border:1px solid rgba(255,255,255,.3); background:rgba(16,18,17,.6); color:#fff; cursor:pointer;
  display:grid; place-items:center; padding:0; transition:background .2s, border-color .2s}
.then-arrow[data-dir=prev]{left:14px} .then-arrow[data-dir=next]{right:14px}
.then-arrow:hover{background:var(--rust); border-color:var(--rust)}
.then-arrow:focus-visible{outline:2px solid var(--rust-lift); outline-offset:3px}
.then-bar{position:absolute; left:0; right:0; bottom:0; height:3px; background:rgba(255,255,255,.14)}
.then-bar i{display:block; height:100%; width:0; background:var(--rust-lift)}
.then.is-playing .then-bar i{width:100%; transition:width 7s linear}
.then-meta{position:relative; display:flex; justify-content:space-between; align-items:baseline; gap:20px}
.then-cap{margin:0; color:#FBFAF7; font-family:var(--serif); font-size:clamp(17px,1.7vw,21px); line-height:1.35}
.then-count{margin:0; color:rgba(255,255,255,.55); font:700 12.5px/1 var(--sans); letter-spacing:.06em; white-space:nowrap}
/* the pause control: present for keyboard and screen-reader users, invisible until it has focus */
.then-pause{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; padding:0; margin:-1px}
.then-pause:focus-visible{position:static; width:auto; height:auto; clip:auto; clip-path:none; margin:0; padding:8px 14px;
  background:transparent; border:1px solid rgba(255,255,255,.5); color:#fff; font:700 12.5px/1 var(--sans); letter-spacing:.06em; cursor:pointer}
/* the strip is an <ol> inside main, so it has to shed the prose list measure; the native scrollbar is
   hidden (the strip follows the current photograph, and it still scrolls by wheel, touch and drag),
   and the edges fade so a cut-off thumbnail reads as "more this way" */
main .then-strip{max-width:none}
.then-strip{list-style:none; margin:0; padding:4px 2px; display:flex; gap:8px; overflow-x:auto; scroll-snap-type:x proximity;
  scrollbar-width:none; -ms-overflow-style:none; cursor:grab;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 28px,#000 calc(100% - 28px),transparent);
  mask-image:linear-gradient(90deg,transparent,#000 28px,#000 calc(100% - 28px),transparent)}
.then-strip::-webkit-scrollbar{display:none}
.then-strip.is-dragging{cursor:grabbing; scroll-snap-type:none}
.then-strip.is-dragging .then-thumb{pointer-events:none}
.then-strip li{flex:none}
.then-thumb{position:relative; width:112px; aspect-ratio:4/3; border:0; padding:0; background:#000; cursor:pointer; display:block;
  opacity:.45; transition:opacity .2s; scroll-snap-align:center; outline-offset:-2px}
.then-thumb img{width:100%; height:100%; object-fit:cover; display:block}
.then-thumb:hover{opacity:.85}
.then-thumb.is-on{opacity:1; outline:2px solid var(--rust-lift)}
.then-thumb:focus-visible{opacity:1; outline:2px solid #fff}
.then-thumb-cap{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap}
/* the full-size view reuses the gallery lightbox styles (.lb) */
@media(max-width:700px){
  .then-stage{height:auto; aspect-ratio:4/3}
  .then-arrow{width:42px; height:42px} .then-arrow[data-dir=prev]{left:8px} .then-arrow[data-dir=next]{right:8px}
  .then-thumb{width:84px} .then-meta{flex-direction:column; align-items:flex-start; gap:6px}
}
@media(prefers-reduced-motion:reduce){ .then-img{transition:opacity .2s} .then.is-playing .then-img.is-on{transform:none} }

/* ---------------------------------------------------------------- gallery/collection + home missoula-feature
   (client email 7 Oct 2026). The collection's tiles are drawn by ui.js from assets/data/missoula-collection.json;
   the homepage band pairs one historic and one modern photograph from it. */
.cc-more{margin-top:28px; display:flex; flex-wrap:wrap; align-items:center; gap:14px 20px}
.cc-more .btn[hidden]{display:none}
.cc-count{font-size:15px; color:rgba(255,255,255,.72)}
.tnow-pair{display:grid; grid-template-columns:1fr 1fr; gap:10px; box-shadow:none; background:transparent; min-height:0; max-height:none}
.tnow-pair figure{position:relative; margin:0; overflow:hidden; aspect-ratio:3/4; box-shadow:var(--shadow)}
.tnow-pair img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; transition:transform .6s ease}
.tnow-pair figure:first-child img{filter:sepia(.25) contrast(1.05)}
.tnow-pair figure:hover img{transform:scale(1.04)}
.tnow-pair figcaption{position:absolute; left:12px; bottom:12px; padding:6px 12px; background:rgba(20,18,16,.78); color:#fff;
  font:700 11.5px/1 var(--sans); letter-spacing:.2em; text-transform:uppercase}
@media(prefers-reduced-motion:reduce){ .tnow-pair img{transition:none} .tnow-pair figure:hover img{transform:none} }
@media(max-width:900px){ .tnow-pair figure{aspect-ratio:4/5} }
/* a six-photograph job gallery: three across, so two full rows (deck page, 8 Oct 2026) */
.tiles-3{grid-template-columns:repeat(3,1fr)}
.tiles-3 .tile{aspect-ratio:4 / 3}
@media(max-width:900px){ .tiles-3{grid-template-columns:1fr 1fr} }
@media(max-width:560px){ .tiles-3{grid-template-columns:1fr} }
