/* ============================================================
   STANDALONE ADDITIONS — spliced onto direction 01's stylesheet
   by build/site/build-site.mjs. Two things live here: the
   self-hosted faces that replace the Google Fonts request, and
   the furniture for the two section landing pages, which only
   the standalone site carries.
   ============================================================ */

/* Both faces are variable, latin subset, one file per family for
   every weight the site uses — 75KB total, first-party, no
   render-blocking third-party stylesheet in front of them. */
@font-face{
  font-family:'Archivo';
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url(fonts/archivo-latin-var.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,
    U+FEFF,U+FFFD;
}
@font-face{
  font-family:'JetBrains Mono';
  font-style:normal;
  font-weight:100 800;
  font-display:swap;
  src:url(fonts/jetbrains-mono-latin-var.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,
    U+FEFF,U+FFFD;
}


/* ============================================================
   DIRECTION 1 — "SITE PLAN"  (dimensional rebuild)
   Cream hero, mostly dark below. Depth is built in LAYERS:
   grid pattern -> ghost watermark -> angular colour shape ->
   photo card breaking its edge -> floating badge. Rounded, soft
   shadows, angular accent shapes. Deliberately NOT brutalist.
   ============================================================ */
:root{
  /* section rhythm — one value per step, was 7 one-offs */
  --sp-1:clamp(26px,3vw,40px);
  --sp-2:clamp(24px,4vw,56px);
  --sp-3:clamp(40px,5vw,64px);
  --sp-4:clamp(36px,5vw,74px);
  --sp-5:clamp(50px,6vw,86px);
  --sp-6:clamp(72px,10vw,140px);

  --acc:#D07C42;              /* Burnt Orange — the chosen accent, baked in */
  --on-acc:#1C1208;
  --acc-dim:#9A4E1E;
  --acc-on-dark:#D07C42;   /* accent as TEXT on a near-black band */

  --cream:#FAF6EC;
  --cream-2:#F2ECDD;
  --dark:#141413;
  --dark-2:#1D1D1B;
  --dark-3:#272724;
  --ink:#15140F;
  --ink-2:#4A4740;
  --mute:#8C877C;           /* dark grounds only */
  --mute-lt:#6E6A5E;        /* the same role on cream or white, at 5:1 */
  /* The one semantic colour outside the brand: a field that will not submit.
     Rust rather than a signal red so it belongs beside the accent, and dark
     enough to clear 7:1 on white and 6.8:1 on cream, because an error message
     is the text a visitor most needs to be able to read. */
  --bad:#A3261C;
  --on-dark:#F3EFE6;
  --on-dark-2:#A8A399;

  --r:16px;                   /* card radius */
  --r-lg:30px;                /* big block radius */
  --sh:0 20px 50px rgba(20,19,15,.10);
  --sh-lg:0 34px 80px rgba(20,19,15,.16);
  --sh-dark:0 26px 60px rgba(0,0,0,.42);
  --m:clamp(22px,4vw,60px);
  --maxw:1400px;

  /* The sticky header's height, in one place: the drawer hangs off the bottom
     of it and every anchor jump has to clear it. It was three hardcoded
     numbers that had already drifted apart — 82, 83 in a comment, 96 in the
     scroll-margin — so the drawer sat a few pixels wrong at every width. */
  --navh:82px;
  /* The trade strip's height, for the same reason: the hero is sized to stop
     short of it so the strip is on screen when the page lands, and that sum
     needs a number it cannot drift from. The strip sets its own height from
     this rather than falling out of padding plus a line box. */
  --striph:56px;
}
*{box-sizing:border-box}
/* iOS Safari inflates the text of a wide block when a phone is turned to
   landscape, on its own initiative and by a factor it picks. The site is
   otherwise sized in clamps against the viewport, so an inflated body face is
   the one thing that can push a laid-out line past its box on a device this
   sweep cannot open: Chrome does not do it, so no width in the ladder below
   would ever catch it. Pinning it to 100% turns the behaviour off and changes
   nothing anywhere else. */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
body{margin:0;background:var(--cream);color:var(--ink);
  font:400 16px/1.62 'Archivo','Helvetica Neue',Arial,sans-serif;overflow-x:hidden}
img{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
h1,h2,h3,h4{font-family:'Archivo',sans-serif;font-weight:800;letter-spacing:-.035em;line-height:1.03;margin:0}
/* Text for a screen reader and not for the eye. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}
.mono{font-family:'JetBrains Mono',ui-monospace,Consolas,monospace;text-transform:uppercase;letter-spacing:.13em;font-size:11px;line-height:1.4}
/* The phone number is the conversion action on a contractor site, and it is
   the one string on the page a visitor has to read digit by digit and carry to
   a keypad.
   It was set like a headline once — Archivo 900 at -.04em — and that was the
   real bug: negative tracking closes up 3/9/6/8 exactly where they need to
   stay apart, and proportional figures let a 1 sit narrower than its
   neighbours. Both are fixed here, and neither fix needs a different typeface:
   tabular figures give every digit the same advance, and the tracking is
   positive now instead of negative.
   It went to JetBrains Mono in between, and that was wrong. Its zero is dotted
   by design — a code-editor convention for telling 0 apart from a capital O.
   A phone number contains no letters, so that dot solves a problem this string
   does not have, and at 12 to 14px it merges into the oval and reads as an 8.
   Trading 0/O clarity for 0/8 confusion is a bad trade on a number someone is
   about to dial. Archivo's zero is a plain oval with nothing inside it. */
.telnum{font-variant-numeric:tabular-nums;font-feature-settings:'tnum' 1;
  letter-spacing:.02em;font-weight:800}
.wrap{max-width:var(--maxw);margin:0 auto;padding-inline:var(--m)}
.acc{color:var(--acc-dim)}

/* ---------- shared depth furniture ---------- */
/* faint engineering grid */
.grid-bg{
  position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:linear-gradient(rgba(21,20,15,.055) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(21,20,15,.055) 1px,transparent 1px);
  background-size:46px 46px;
  -webkit-mask-image:radial-gradient(ellipse 80% 70% at 50% 40%,#000 40%,transparent 100%);
          mask-image:radial-gradient(ellipse 80% 70% at 50% 40%,#000 40%,transparent 100%);
}
.grid-bg.on-dark{background-image:linear-gradient(rgba(243,239,230,.05) 1px,transparent 1px),linear-gradient(90deg,rgba(243,239,230,.05) 1px,transparent 1px)}
/* big ghosted machine watermark */
.wm{position:absolute;z-index:0;pointer-events:none;opacity:.055;filter:grayscale(1) contrast(1.3)}
/* the angular accent slab that sits BEHIND photography */
.shape{
  position:absolute;z-index:1;background:var(--acc);
  clip-path:polygon(12% 0,100% 0,88% 100%,0 100%);
}
.shape.soft{background:linear-gradient(150deg,var(--acc) 0%,var(--acc-dim) 100%)}
/* stacked diagonal bars — EQUIRT's corner motif */
.bars{display:flex;gap:6px;pointer-events:none}
.bars i{display:block;width:14px;height:34px;background:var(--acc);transform:skewX(-18deg);border-radius:3px}
.bars i:nth-child(1){opacity:.3}.bars i:nth-child(2){opacity:.55}.bars i:nth-child(3){opacity:.8}
.bars.lg i{width:20px;height:52px}

/* buttons — pill, soft, layered
   Every pill carries the same four-part elevation the accent bar does, and for
   the same reason: one wide soft shadow is a button drawn on a page, and it
   takes an edge to make it a button lying on one.

     inset 0 1px 0    a hairline of light along the top, where a raised edge
                      would catch it
     inset 0 -1px 0   and one of shade along the foot, where it would not
     0 2px 5px        the contact shadow — short and dark, the one that says
                      how far off the page it is
     0 10px 26px      and the ambient one it already had

   The two hairlines are one pixel at the very top and bottom of a 46px pill.
   No glyph is within fifteen pixels of either, so none of this is spent out
   of a contrast budget — which matters because .btn.acc is the same trap the
   accent bar was: --on-acc is near-black on gold and orange and WHITE on
   clay, so anything that lightens the field is a gift to two palettes and a
   tax on the third. shots/btncheck.mjs measures the field behind every label
   on every palette rather than trusting that reasoning. */
.btn{display:inline-flex;align-items:center;gap:10px;border-radius:999px;padding:15px 26px;
  font-size:13.5px;font-weight:700;letter-spacing:.01em;cursor:pointer;border:0;
  background:var(--ink);color:var(--cream);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.15),
    inset 0 -1px 0 rgba(0,0,0,.28),
    0 2px 5px rgba(20,19,15,.20),
    0 10px 26px rgba(20,19,15,.22);
  transition:transform .24s cubic-bezier(.2,.8,.2,1),box-shadow .24s ease,background .22s ease}
@media (hover:hover) and (pointer:fine){
  .btn:hover{transform:translateY(-3px);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.18),
      inset 0 -1px 0 rgba(0,0,0,.28),
      0 3px 7px rgba(20,19,15,.22),
      0 18px 38px rgba(20,19,15,.3)}}
/* Pressed, the shadow has to come down with it. A pill that shrinks and keeps
   its full shadow reads as a picture of a button being pressed rather than as
   one being pressed — the light says it is still held up while the shape says
   it has been pushed in. */
.btn:active{box-shadow:inset 0 1px 0 rgba(255,255,255,.10),
  inset 0 -1px 0 rgba(0,0,0,.28),
  0 1px 2px rgba(20,19,15,.24),
  0 3px 8px rgba(20,19,15,.18)}
.btn .pip{width:26px;height:26px;border-radius:50%;background:var(--acc);color:var(--on-acc);
  display:grid;place-items:center;font-size:12px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.28),0 1px 3px rgba(0,0,0,.34);
  transition:transform .3s ease}
/* The call button carries a handset for the phone header, where the digits
   fold away and a bare dot would say nothing. Wide, the digits are there. */
.btn .pip svg{display:none}
@media (hover:hover) and (pointer:fine){.btn:hover .pip{transform:rotate(45deg)}}
/* The accent pill throws an accent-tinted shadow rather than a grey one —
   a coloured object bounces its own colour onto what is under it — but it
   still needs the neutral contact shadow under that, or it glows without
   ever touching down. */
.btn.acc{background:var(--acc);color:var(--on-acc);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.22),
    inset 0 -1px 0 color-mix(in srgb,var(--ink) 22%,transparent),
    0 2px 5px rgba(20,19,15,.22),
    0 12px 30px color-mix(in srgb,var(--acc) 40%,transparent)}
@media (hover:hover) and (pointer:fine){
  .btn.acc:hover{box-shadow:inset 0 1px 0 rgba(255,255,255,.26),
    inset 0 -1px 0 color-mix(in srgb,var(--ink) 22%,transparent),
    0 3px 7px rgba(20,19,15,.24),
    0 20px 42px color-mix(in srgb,var(--acc) 48%,transparent)}}
.btn.acc:active{box-shadow:inset 0 1px 0 rgba(255,255,255,.14),
  inset 0 -1px 0 color-mix(in srgb,var(--ink) 22%,transparent),
  0 1px 2px rgba(20,19,15,.24),
  0 4px 10px color-mix(in srgb,var(--acc) 34%,transparent)}
.btn.acc .pip{background:var(--ink);color:var(--acc)}
/* The outlined pill is the one that cannot be lifted — it has no body to
   throw a shadow. So it is cut in instead: a dark hairline along its top edge
   and a lit one along its foot is what a groove looks like under light coming
   from above, which is the same light the solid pills are lit by. This used
   to be scoped to the accent bar; it belongs to the variant. */
.btn.ghost{background:transparent;color:var(--on-acc);
  border:1.5px solid color-mix(in srgb,var(--on-acc) 68%,transparent);
  box-shadow:inset 0 1px 0 color-mix(in srgb,var(--ink) 20%,transparent),
    0 1px 0 color-mix(in srgb,#fff 22%,transparent)}
/* Without this the dot is the same colour as the plane it sits on. */
.btn.ghost .pip{background:var(--on-acc);color:var(--acc)}
@media (hover:hover) and (pointer:fine){.btn.ghost:hover{background:color-mix(in srgb,var(--on-acc) 16%,transparent);box-shadow:none}}
/* Cream on a dark ground: the shadow it casts has to be black rather than
   the ink-tinted one the dark pills use, or it disappears into the band. */
.btn.on-dark{background:var(--on-dark);color:var(--ink);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.5),
    inset 0 -1px 0 rgba(0,0,0,.16),
    0 2px 6px rgba(0,0,0,.34),
    0 12px 30px rgba(0,0,0,.4)}
@media (hover:hover) and (pointer:fine){
  .btn.on-dark:hover{box-shadow:inset 0 1px 0 rgba(255,255,255,.5),
    inset 0 -1px 0 rgba(0,0,0,.16),
    0 3px 8px rgba(0,0,0,.36),
    0 20px 42px rgba(0,0,0,.46)}}

/* pill label */
.pill{display:inline-flex;align-items:center;gap:9px;background:color-mix(in srgb,var(--acc) 18%,transparent);
  color:var(--ink);border-radius:999px;padding:8px 16px;font-family:'JetBrains Mono',monospace;
  font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;font-weight:700}
.pill .dot{width:7px;height:7px;border-radius:50%;background:var(--acc-dim)}
.pill.on-dark{background:color-mix(in srgb,var(--acc) 14%,transparent);color:var(--acc)}
.pill.on-acc{background:color-mix(in srgb,var(--on-acc) 15%,transparent);color:var(--on-acc)}
.pill.on-acc .dot{background:var(--on-acc)}

/* ---------- 01 NAV ---------- */
.nav{position:sticky;top:0;z-index:200;background:rgba(250,246,236,.86);backdrop-filter:blur(14px);
  border-bottom:1px solid rgba(21,20,15,.07)}
.nav .wrap{display:flex;align-items:center;gap:clamp(16px,3vw,44px);height:var(--navh)}
.brand{display:flex;align-items:center;gap:11px;font-weight:900;font-size:20px;letter-spacing:-.05em;text-transform:uppercase}
.brand .mk{width:34px;height:34px;border-radius:9px;background:var(--acc);display:grid;place-items:center;
  box-shadow:0 8px 20px color-mix(in srgb,var(--acc) 40%,transparent)}
.brand .mk::before{content:"";width:13px;height:13px;background:var(--ink);clip-path:polygon(0 0,100% 0,58% 50%,100% 100%,0 100%)}
.nav nav{display:flex;gap:clamp(14px,2.2vw,34px);margin-left:auto;font-size:14.5px;font-weight:500}
.nav nav a{color:var(--ink-2);transition:color .2s}
@media (hover:hover) and (pointer:fine){.nav nav a:hover{color:var(--ink)}}
@media(max-width:940px){.nav nav{display:none}}

/* ---------- 02 HERO — the banner construction, at hero scale ----------
   The hero used to be its own idea: a hard vertical colour boundary with a
   frameless object straddling it, over a photograph the accent plane tinted.
   It worked, and it was the only composition on the site that had nothing to
   do with the rest of it — every other page opens on a photograph running edge
   to edge under a directional scrim, and the front page opened on an orange
   plane. It also carried the one image in the tree whose rights were never
   verified, which is now out of the tree entirely.

   So the hero is built the way the banners are: the frame full bleed, a scrim
   that is heavy where the copy sits and lets go where it does not, the beam of
   daylight across it, and the type read off the dark. What it does NOT share
   is the proportion — this band is twice the height and carries an eyebrow, a
   two-tier headline, a lede, two buttons and four figures, so the scrim has to
   hold a whole column down rather than two lines. */
/* The height is the fold, expressed rather than guessed. The hero used to be
   clamp(540px,64vw,760px), which on a wide window resolved to a flat 760px —
   add the 83px header and the 56px strip and the trade names landed just past
   the bottom edge of a laptop, so the first thing a visitor saw was a band
   that gave no sign anything moved below it.
   So the middle term is now the room actually left: the viewport, less the
   header, less its 1px rule, less the strip. 760px stays as the cap so a tall
   window does not stretch the band, and the floor keeps it from collapsing on
   a short one. svh, not vh, because on a phone vh is the URL-bar-hidden height
   and would put the strip under the browser's own furniture on landing.
   The vertical padding takes the same treatment. --sp-4 and --sp-5 are
   clamp(36px,5vw,74px) and clamp(50px,6vw,86px) — they scale with WIDTH, so a
   wide-but-short window was holding 160px of padding it had no room for. The
   vh terms are the same 5 and 6 read against the other axis, so this is the
   existing scale applied to the axis that is actually short, not a pair of
   numbers picked to clear a particular laptop. They only bind where the window
   is short: on anything tall enough the content fits inside min-height and the
   flex centring places it, so the padding never enters the sum. */
.hero{position:relative;overflow:hidden;
  padding:min(var(--sp-4),5vh) 0 min(var(--sp-5),6vh);
  min-height:clamp(520px,calc(100vh - var(--navh) - 1px - var(--striph)),760px);
  min-height:clamp(520px,calc(100svh - var(--navh) - 1px - var(--striph)),760px);
  display:flex;align-items:center;
  background:var(--dark);color:var(--on-dark);isolation:isolate}
.hero-shot{position:absolute;inset:0;z-index:0;pointer-events:none}
/* The subject is a horizontal strip of building across the middle of a 16:9
   frame, and the band is close to square on a narrow window. Sliding the crop
   window alone cannot fix that: whichever way it goes, one side of the band
   fills with the empty lot the building sits on, and the visitor is looking at
   dirt instead of at work.
   So it does two things. object-position picks WHICH part of the frame is in
   the band — lowering that number slides the window left, which pushes the
   subject right, the opposite of what it looks like it does. The scale then
   tightens in on that same point, cropping the street at the top and the bare
   lot at the right off the edges of the band, so what is left is mostly
   structure. Origin and position are the same coordinates on purpose: the
   zoom has to pull toward the thing being framed, not away from it.
   Cost, stated plainly: the source is 1200px wide, and scaling it makes a
   soft image slightly softer. Quest's full-resolution drone file would buy
   that back and is a one-file swap. */
.hero-shot img{width:100%;height:100%;object-fit:cover;
  object-position:22% 46%;transform:scale(1.16);transform-origin:22% 46%;
  filter:saturate(.85) contrast(1.04)}
/* The scrim. The copy column runs to 46% of the width, so the dark holds to
   about there and then lets go — the picture is legible on the right of the
   band and the type is legible on the left of it, which is the whole trick the
   banners are doing at half this height. The last stop lifts again rather than
   fading to nothing: a photograph that reaches the right edge at full strength
   reads as a second image butted onto the first. */
.hero-shot::after{content:"";position:absolute;inset:0;
  background:linear-gradient(96deg,var(--dark) 0%,
    color-mix(in srgb,var(--dark) 96%,transparent) 46%,
    color-mix(in srgb,var(--dark) 56%,transparent) 68%,
    color-mix(in srgb,var(--dark) 70%,transparent) 100%)}
/* A second pass down the vertical, because the figures sit low in the band
   where the frame is at its brightest — and a vignette, because an evenly lit
   rectangle reads as a backdrop no matter what stands in front of it. The
   bright centre is put where the object is, not in the middle of the band, so
   the light in the picture and the light on the object agree. */
.hero-shot::before{content:"";position:absolute;inset:0;z-index:1;
  background:
    radial-gradient(122% 96% at 64% 44%,transparent 34%,
      color-mix(in srgb,var(--dark) 30%,transparent) 72%,
      color-mix(in srgb,var(--dark) 52%,transparent) 100%),
    linear-gradient(180deg,transparent 40%,
      color-mix(in srgb,var(--dark) 34%,transparent) 100%)}
/* The banner's light beam was here and is not any more. It is a skewed band of
   cream at 20%, which on a band with no object in it is atmosphere — but this
   band has an object, so the only place left for the beam was the narrow gap
   between the copy column and the object, and a skewed element does not stay
   in a narrow gap: the -5deg throws its top and bottom ±31px either way. At
   1200 that put it across "REACH US" at 3.48:1 against a 4.5 bar, and it
   passed at 1440 and 1120, which is how a width-dependent overlap looks.
   Nothing was lost by removing it. The depth here comes from the vignette, the
   pool the object stands in and the three shadows it casts — the beam was
   sitting behind the object doing almost none of it. */
.hero .grid-bg{z-index:1}
.hero .wrap{width:100%}
.hero-copy,.hero-trust{position:relative;z-index:4}
.hero-copy{max-width:min(500px,42%)}
/* --on-dark, not --acc-on-dark, and this is the third time this line has taught
   the same lesson. The accent's dark-band ink is what every other dark section
   sets a small mono label in — but those sit on a flat band, and this one sits
   on a photograph. Measured: 4.77:1 at 1440 against a 4.5 bar, and 2.56:1 at
   390 on the clay palette, where the accent is a mid-tone terracotta and the
   narrow layout puts the label in the lightest part of the scrim. Buying that
   back means scrimming the picture to near-solid exactly where it is most
   visible, which is paying for an 11px label with the whole image.
   The colour goes where it can afford to be instead: one word of the 61px
   headline, which needs 3:1 and clears it at 4.55 on the worst palette. */
.hero-copy .eyebrow{color:var(--on-dark);margin:0 0 clamp(14px,1.6vw,22px)}
/* Two tiers out of one string. The name line is set at roughly half the
   display size, which stops the promise from being the fourth line of a
   four-line block at one weight — and keeps the colon where it belongs,
   closing the name rather than dangling off a line of its own. */
.hero-copy h1{font-size:clamp(32px,4.3vw,62px);font-weight:900;color:var(--on-dark);
  line-height:1.02;letter-spacing:-.03em;text-shadow:0 2px 30px rgba(0,0,0,.45)}
.h1-name{display:block;font-size:.46em;font-weight:800;letter-spacing:-.02em;
  line-height:1.1;margin-bottom:.28em;color:var(--on-dark)}
/* the one word in accent — the banners' move, and the only colour up here */
.hero-copy h1 .hl{color:var(--acc-on-dark)}
.hero-copy .lede{margin:22px 0 30px;font-size:clamp(14.5px,1.35vw,16.5px);
  color:var(--on-dark);max-width:36ch;display:flex;gap:14px}
/* square bullet marker, straight off the reference */
.hero-copy .lede::before{content:"";width:11px;height:11px;background:var(--acc);
  flex:0 0 auto;margin-top:.45em}

/* The hero is the photograph, full bleed, with nothing standing in front of
   it. The materials cut-out that used to sit here took a pool of warm light,
   three drop-shadows and its own settle animation to stop reading as pasted;
   all of that went with it. */

.hero-acts{display:flex;flex-wrap:wrap;gap:14px;align-items:center}
/* The object settles in on load and everything below the fold reveals on
   scroll; the copy was the one thing that simply appeared. It arrives in the
   order it is read now.
   The headline carries no delay of its own: it is the largest thing painted
   above the fold and a stagger in front of it is a stagger in front of the
   LCP. The rungs either side of it wait, not it. */
@keyframes heroin{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
.hero-copy>*,.hero-trust{animation:heroin .55s cubic-bezier(.2,.8,.2,1) both}
.hero-copy .eyebrow{animation-delay:.04s}
.hero-copy h1{animation-delay:0s}
.hero-copy .lede{animation-delay:.14s}
.hero-acts{animation-delay:.22s}
.hero-trust{animation-delay:.30s}
@media(prefers-reduced-motion:reduce){.hero-copy>*,.hero-trust{animation:none}}
/* Figures with nothing between them read as one run-on line. A hairline
   between each turns them into three things, which is what they are, and the
   rule above now runs the width of the row rather than stopping wherever the
   last label happened to end. */
.hero-trust{max-width:min(500px,42%);flex:1 1 auto;
  display:flex;flex-wrap:wrap;gap:14px 0;
  padding-top:24px;border-top:1px solid color-mix(in srgb,var(--on-dark) 26%,transparent)}
.hero-trust div{padding-right:clamp(16px,2vw,26px);margin-right:clamp(16px,2vw,26px);
  border-right:1px solid color-mix(in srgb,var(--on-dark) 18%,transparent)}
.hero-trust div:last-child{border-right:0;padding-right:0;margin-right:0}
.hero-trust div b{display:block;font-size:clamp(20px,2.2vw,28px);font-weight:900;
  letter-spacing:-.04em;line-height:1;color:var(--on-dark)}
.hero-trust div b i{font-style:normal;opacity:.55}
/* Likewise, and worse: these were --on-acc at 68%, which failed on every
   palette and had done since the hero was drawn — 3.49:1 on orange and 3.33:1
   on clay even before there was a photograph under the plane. A 10px mono
   uppercase label under a 28px 900-weight figure does not need to be faded to
   read as the label. */
.hero-trust div span{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--on-dark-2);display:block;margin-top:7px}

/* floating badge card — the topmost layer */
.badge{
  position:absolute;z-index:6;background:var(--dark);color:var(--on-dark);border-radius:14px;
  padding:14px 18px;box-shadow:var(--sh-lg);display:flex;align-items:center;gap:12px;
}
.badge .ic{width:34px;height:34px;border-radius:9px;background:var(--acc);display:grid;place-items:center;flex:0 0 auto}
.badge .ic svg{width:17px;height:17px;stroke:var(--on-acc);fill:none;stroke-width:2.4}
/* The button's own 13.5px label size is set for words; the digits need a
   touch less to sit in the same pill. */
.btn.telnum,.navtel-num{font-size:13.5px}
.badge b{display:block;font-size:14px;line-height:1.15}
.badge span{font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:.11em;
  text-transform:uppercase;color:var(--on-dark-2)}
/* The badge sits in a row with the figures rather than floating at an offset
   of its own. It was absolutely positioned at bottom:6%, then 13% — both of
   them numbers picked to look right, and neither of them tracking the thing
   they were meant to line up with. Quest asked for it level with the founding
   year, and the way to make that true at every width is to put the two in the
   same flex row and centre them, not to guess a percentage that happens to
   match at one viewport.
   Right rather than left of the figures: the masthead already carries a phone
   button top right, and this anchors the opposite corner of the band. */
.hero-foot{position:relative;z-index:4;display:flex;align-items:center;
  justify-content:space-between;gap:clamp(20px,3vw,48px);
  margin-top:clamp(26px,3.4vw,42px)}
.badge-float{position:relative;flex:0 0 auto;z-index:7}

@media(max-width:1080px){
  .hero{min-height:0;padding:clamp(34px,8vw,52px) 0 clamp(28px,6vw,40px);display:block}
  /* The scrim turns with the layout. Wide, the copy is a column on the left
     and the dark runs across; narrow, the copy is the full width and the dark
     has to run DOWN — heaviest under the type and easing off at the top, where
     the frame gets the only clear air it has at this width.
     There is no cut-out to lead with any more and no plane for it to straddle:
     the photograph is the whole band, so it is behind the words from the first
     pixel rather than stacked above them. */
  .hero-shot::after{background:linear-gradient(180deg,
    color-mix(in srgb,var(--dark) 74%,transparent) 0%,
    color-mix(in srgb,var(--dark) 89%,transparent) 26%,
    color-mix(in srgb,var(--dark) 93%,transparent) 55%,
    color-mix(in srgb,var(--dark) 93%,transparent) 100%)}
  .hero-shot::before{display:none}
  .hero-copy,.hero-trust{max-width:none}
  .hero-trust{margin-top:clamp(18px,4vw,28px)}
  .badge-float{display:none}
  .hero .wrap{display:flex;flex-direction:column}
  /* Three figures, three columns. It was four across four, and before that
     four across two, because four divides badly into the widths a hero has to
     survive. Three divides evenly into every one of them, so this is the last
     rule the row needs — there is no fold below it. */
  .hero-trust{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}}
/* The gutter, not the column count, is what a phone runs out of. At 390 the
   three tracks are 112px each and the separator was eating 32 of them, which
   set "Building since" on two lines under a one-line figure. Halving it buys
   the label its line back on all but the narrowest handsets, and where it
   still wraps the grid keeps the three figures on one baseline. */
@media(max-width:620px){
  .hero-trust div{padding-right:11px;margin-right:11px}
  .badge-float{display:none}}
/* A phone-width bleed used to live here, drawn for a cut-out object that has
   been through three replacements and is now gone: the hero is a full-bleed
   photograph, so there is no object to bleed and the frame already reaches
   both edges at every width. */

/* ---------- 03 MARQUEE STRIP ---------- */
.strip{background:var(--dark);color:var(--on-dark-2);overflow:hidden;
  height:var(--striph);display:grid;align-content:center}
.strip-in{display:flex;align-items:center;width:max-content;animation:slide 36s linear infinite}
@keyframes slide{to{transform:translateX(-50%)}}
.strip .strip-i,.strip .d{font-family:'JetBrains Mono',monospace;font-size:12px;
  letter-spacing:.16em;text-transform:uppercase;padding:0 24px;white-space:nowrap}
.strip .d{color:var(--acc-on-dark)}
/* The strip is 56px tall and the link inside it was 19px — a line box centred
   in a band with 37px of dead space around it. The row already stops moving
   under a pointer so that these can be clicked; this is the other half of
   that, and it costs nothing, because the 44px sits inside the 56px the strip
   already occupies. */
.strip-i{color:inherit;text-decoration:none;transition:color .25s ease;
  display:inline-flex;align-items:center;min-height:44px}
.strip-i:hover,.strip-i:focus-visible{color:var(--acc-on-dark);
  text-decoration:underline;text-underline-offset:5px}
.strip-i:focus-visible{outline:2px solid var(--acc-on-dark);outline-offset:3px;border-radius:2px}
/* A link that is sliding cannot be clicked. The row holds still while a pointer
   is over it or the keyboard is inside it, which is what makes the strip a
   navigation control rather than a decoration that happens to have hrefs. */
.strip:hover .strip-in,.strip:focus-within .strip-in{animation-play-state:paused}
@media(prefers-reduced-motion:reduce){.strip-in{animation:none}}

/* ---------- SECTIONS ---------- */
.sec{position:relative;padding:var(--sp-6) 0;overflow:hidden}
.sec.dark{background:var(--dark);color:var(--on-dark)}
.sec.dark h2{color:var(--on-dark)}
.sec.cream{background:var(--cream)}
/* The head is one column. Two columns left the right half of the row empty on
   every section whose second cell carries nothing, and gave the ones that do
   carry a lede a column sized for four lines to hold two. The heading runs the
   full measure on one line and the lede sits under it, held short enough to set
   at two or three lines. 66px is the size at which the longest single line
   heading fills 91 percent of a 1286px row, measured in Archivo 900 at the
   real letter spacing rather than guessed. */
.shead{position:relative;z-index:3;margin-bottom:clamp(36px,5vw,66px)}
.shead h2{font-size:clamp(30px,4.86vw,70px);font-weight:900;text-wrap:balance}
.shead h2 span{color:var(--acc)}
.sec.cream .shead h2 span{color:var(--acc-dim)}
.shead .lede{margin:clamp(16px,1.8vw,26px) 0 0;color:var(--ink-2);
  font-size:clamp(14.5px,1.4vw,17px);max-width:clamp(320px,44vw,640px);text-wrap:pretty}
.sec.dark .shead .lede{color:var(--on-dark-2)}

/* ---------- 04 SERVICE CARDS (dark) ---------- */
.svcs{position:relative;z-index:3;display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(14px,1.8vw,24px)}
.svc{position:relative;background:var(--dark-2);border:1px solid rgba(243,239,230,.09);
  border-radius:var(--r-lg);padding:var(--sp-1);display:flex;flex-direction:column;
  min-height:clamp(320px,32vw,400px);overflow:hidden;
  transition:transform .38s cubic-bezier(.2,.8,.2,1),box-shadow .38s ease,background .38s ease}
@media (hover:hover) and (pointer:fine){.svc:hover{transform:translateY(-10px);box-shadow:var(--sh-dark);background:var(--dark-3)}}
/* The photograph plate. It cancels the card's padding on three sides so it
   bleeds to the border and takes the card's own top radius — a photo inset
   inside a padded card reads as a box in a box, which is the one thing this
   direction never does. `--card` is the colour the scrim resolves to, so the
   accent tile's plate fades into orange and the rest fade into ink. */
.svc{--card:var(--dark-2)}
.svc--acc{--card:var(--acc)}
.svcshot{display:block;position:relative;overflow:hidden;
  margin:calc(var(--sp-1) * -1) calc(var(--sp-1) * -1) 0;
  border-radius:calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0;
  background:var(--dark-3)}
.svcshot img{display:block;width:100%;height:100%;object-fit:cover;aspect-ratio:3/2;
  transition:transform .9s cubic-bezier(.2,.8,.2,1)}
/* Two speeds on one gesture: the card lifts, the photograph settles into it. */
@media (hover:hover) and (pointer:fine){.svc:hover .svcshot img{transform:scale(1.07)}}
.svcshot::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,
    color-mix(in srgb,var(--card) 22%,transparent) 0%,
    transparent 34%,
    color-mix(in srgb,var(--card) 82%,transparent) 100%)}
/* The chip breaks the plate's lower edge — the same overlap the phone badge
   makes on the hero photograph, and what turns two rectangles into a stack. */
.svc .ic{width:56px;height:56px;border-radius:16px;background:var(--acc);display:grid;place-items:center;
  position:relative;z-index:2;margin:-28px 0 auto;
  box-shadow:0 12px 28px color-mix(in srgb,var(--acc) 28%,transparent),
    0 0 0 5px var(--card)}
.svc .ic svg{width:26px;height:26px;stroke:var(--on-acc);fill:none;stroke-width:2.1}
.svc h3{font-size:clamp(19px,2.1vw,26px);margin:22px 0 12px;font-weight:800}
.svc p{margin:0 0 24px;font-size:14.5px;color:var(--on-dark-2)}
.svc .go{display:inline-flex;align-items:center;gap:10px;font-family:'JetBrains Mono',monospace;
  font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;font-weight:700;color:var(--acc-on-dark)}
.svc .go i{font-style:normal;transition:transform .28s ease}
@media (hover:hover) and (pointer:fine){.svc:hover .go i{transform:translate(5px,-5px)}}
/* ghost numeral for depth */
.svc .n{position:absolute;right:-10px;bottom:-32px;font-size:clamp(90px,11vw,150px);font-weight:900;
  letter-spacing:-.06em;color:rgba(243,239,230,.05);line-height:1;pointer-events:none}
.svc--acc{background:var(--acc);border-color:var(--acc)}
@media (hover:hover) and (pointer:fine){.svc--acc:hover{background:var(--acc)}}
.svc--acc h3,.svc--acc .go{color:var(--on-acc)}
.svc--acc p{color:rgba(20,19,16,.76)}
.svc--acc .ic{background:var(--ink);
  box-shadow:0 12px 28px rgba(20,19,15,.3),0 0 0 5px var(--card)}
/* The accent tile is the one card whose body is not ink, and fading a
   photograph into burnt orange stains it. So this plate keeps a hard bottom
   edge and lets the colour start where the picture stops — the same move the
   hero makes with its accent plane. */
.svc--acc .svcshot::after{background:linear-gradient(180deg,
  color-mix(in srgb,var(--ink) 12%,transparent) 0%,transparent 38%)}
.svc--acc .ic svg{stroke:var(--acc)}
.svc--acc .n{color:rgba(20,19,16,.1)}
@media(max-width:940px){.svcs{grid-template-columns:1fr 1fr}}
/* Single file, and no floor under the card. The 320px minimum is what gives
   the three-up grid its even rhythm; stacked, it only pushed the icon and the
   heading 150px apart and made the fourteen-card list twice as long to scroll
   as it needed to be. */
@media(max-width:640px){
  .svcs{grid-template-columns:1fr}
  .svc{min-height:0;padding:26px 24px}
  /* One card per row is a wide plate; 3:2 would push the words off the fold. */
  .svcshot{margin:-26px -24px 0}
  .svcshot img{aspect-ratio:2/1}
  .svc .ic{width:48px;height:48px;border-radius:14px;margin:-24px 0 0}
  .svc .ic svg{width:23px;height:23px}
  .svc h3{margin:18px 0 10px}
  .svc p{margin-bottom:18px}
  .svc .n{bottom:-24px;font-size:96px}}

/* ---------- 05 FIGURES BAND (accent, angular) ---------- */
.figs{position:relative;background:var(--acc);color:var(--on-acc);overflow:hidden}
.figs .wm{left:-4%;bottom:-30%;width:min(620px,58vw);opacity:.09}
.figs .wrap{position:relative;z-index:3;padding-block:clamp(50px,6.5vw,84px);
  display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(18px,3vw,44px)}
.figs .f b{display:block;font-size:clamp(34px,4.8vw,66px);font-weight:900;letter-spacing:-.05em;line-height:1}
.figs .f span{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;display:block;margin-top:10px;color:rgba(20,19,16,.62)}
.figs .bars{position:absolute;right:var(--m);top:20px;z-index:4}
.figs .bars i{background:var(--ink);opacity:.14}
.figs .bars i:nth-child(2){opacity:.2}.figs .bars i:nth-child(3){opacity:.28}
@media(max-width:860px){.figs .wrap{grid-template-columns:1fr 1fr;gap:28px}}

/* ---------- 06 WORK (dark, layered cards) ---------- */
.work{position:relative;z-index:3;display:grid;grid-template-columns:repeat(6,1fr);
  grid-auto-rows:clamp(150px,15vw,200px);gap:clamp(12px,1.5vw,20px)}
.pj{position:relative;overflow:hidden;border-radius:var(--r-lg);box-shadow:var(--sh-dark)}
.pj img{width:100%;height:100%;object-fit:cover;transition:transform .8s cubic-bezier(.2,.8,.2,1)}
@media (hover:hover) and (pointer:fine){.pj:hover img{transform:scale(1.07)}}
.pj::after{content:"";position:absolute;inset:0;background:linear-gradient(0deg,rgba(10,10,9,.88) 0%,rgba(10,10,9,.1) 52%,transparent 78%)}
.pj .cap{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:clamp(18px,2vw,28px);
  display:flex;justify-content:space-between;align-items:flex-end;gap:14px;
  background:linear-gradient(0deg,rgba(10,10,9,.94) 0%,rgba(10,10,9,.88) 55%,rgba(10,10,9,.72) 100%)}
.pj .cap b{font-size:clamp(15px,1.6vw,21px);font-weight:800;letter-spacing:-.03em;display:block;
  color:var(--on-dark)}
.pj .cap span{font-family:'JetBrains Mono',monospace;font-size:11.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--on-dark);display:block;margin-top:6px;opacity:.92}
.pj .cap .go{width:40px;height:40px;border-radius:50%;background:var(--acc);color:var(--on-acc);
  display:grid;place-items:center;flex:0 0 auto;font-size:15px;transition:transform .3s ease}
@media (hover:hover) and (pointer:fine){.pj:hover .cap .go{transform:rotate(45deg)}}
.pj.a{grid-column:span 4;grid-row:span 2}
.pj.b{grid-column:span 2}
.pj.c{grid-column:span 2}
.pj.d{grid-column:span 3}
.pj.e{grid-column:span 3}
/* Five tiles fill the six columns exactly: 4+2, then 2 beside the tall one,
   then 3+3. Four do not — the last would sit three columns short of its row
   with a hole beside it — so at four it takes the whole width instead. */
.work.four .pj.d{grid-column:span 6}
@media(max-width:900px){
  .work{grid-template-columns:repeat(2,1fr)}
  .pj.a{grid-column:span 2;grid-row:span 2}
  .pj.b,.pj.c,.pj.d,.pj.e{grid-column:span 1}}
/* One column on a phone. Two left each tile 175px wide, and the caption —
   a title, a sentence and a 40px arrow, side by side — could not fit: the
   text overflowed its own tile and ran back up over the photograph. */
@media(max-width:620px){
  .work{grid-template-columns:1fr;grid-auto-rows:minmax(190px,auto)}
  .pj.a,.pj.b,.pj.c,.pj.d,.pj.e{grid-column:span 1;grid-row:span 1}
  .pj.a{grid-row:span 2}}

/* ---------- 07 PROCESS (cream, connected) ---------- */
.steps{position:relative;z-index:3;display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(16px,2.2vw,30px)}
.steps::before{content:"";position:absolute;left:6%;right:6%;top:32px;height:2px;
  background:repeating-linear-gradient(90deg,rgba(21,20,15,.2) 0 8px,transparent 8px 16px);z-index:0}
.step{position:relative;z-index:2}
.step .n{width:66px;height:66px;border-radius:20px;background:var(--cream);border:2px solid var(--acc);
  display:grid;place-items:center;font-weight:900;font-size:20px;letter-spacing:-.04em;
  margin-bottom:24px;box-shadow:var(--sh);transition:background .3s ease,transform .3s ease}
@media (hover:hover) and (pointer:fine){.step:hover .n{background:var(--acc);transform:translateY(-5px)}}
.step h3{font-size:clamp(17px,1.8vw,22px);margin-bottom:11px;font-weight:800}
.step p{margin:0;font-size:14.5px;color:var(--ink-2)}
@media(max-width:900px){.steps{grid-template-columns:1fr 1fr}.steps::before{display:none}}
@media(max-width:540px){.steps{grid-template-columns:1fr}}

/* ---------- 08 CTA ----------
   The plate a page ends on. The photograph is the whole of it, under a scrim
   heavy enough on the left for the headline and light enough on the right to
   leave the work visible; the accent runs as a bar across the foot with the
   ask on one end and the two ways to act on it at the other. */
.cta{position:relative;background:var(--dark);color:var(--on-dark);overflow:hidden;
  padding:var(--sp-2) 0}
.cta-in{position:relative;z-index:3;padding-inline:0;background:var(--dark-2);
  border-radius:var(--r-lg);overflow:hidden;box-shadow:0 40px 90px rgba(0,0,0,.5);
  isolation:isolate}
.cta-shot{position:absolute;inset:0;z-index:0;pointer-events:none}
.cta-shot img{width:100%;height:100%;object-fit:cover;
  transition:transform 1.4s cubic-bezier(.2,.8,.2,1)}
@media (hover:hover) and (pointer:fine){.cta-in:hover .cta-shot img{transform:scale(1.04)}}
/* Two scrims, not one: a horizontal wash carries the type, and a light vertical
   floor keeps the accent bar from sitting on a bright patch of sky. */
.cta-shot::after{content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,var(--dark) 4%,
    color-mix(in srgb,var(--dark) 76%,transparent) 46%,
    color-mix(in srgb,var(--dark) 22%,transparent) 100%),
    linear-gradient(180deg,transparent 55%,color-mix(in srgb,var(--dark) 55%,transparent) 100%)}

.cta-head{position:relative;z-index:2;display:grid;grid-template-columns:1fr auto;
  gap:clamp(24px,4vw,60px);align-items:end;
  padding:var(--sp-4) var(--sp-4) var(--sp-3)}
.cta-copy h2{font-size:clamp(30px,4.6vw,62px);font-weight:900;color:var(--on-dark);
  max-width:16ch}
.cta-copy p{margin:20px 0 0;color:var(--on-dark-2);max-width:42ch;
  font-size:clamp(14.5px,1.4vw,16.5px)}
/* The four trades, right-aligned — the reference's service list, and a way out
   of the plate for a visitor who is not ready to call yet. */
.cta-trades{list-style:none;margin:0;padding:0;text-align:right;
  color:color-mix(in srgb,var(--on-dark) 82%,transparent);display:grid;gap:11px;
  font-size:11.5px}
.cta-trades a{transition:color .2s}
@media (hover:hover) and (pointer:fine){.cta-trades a:hover{color:var(--acc-on-dark)}}

/* ---- the accent bar ----
   It was one flat rectangle of --acc laid on the plate, and it read as a
   swatch rather than as a thing sitting on top of anything.

   The obvious fix is a light-to-dark wash down the face, and it is the one
   thing that cannot be done here: --on-acc is near-black on the gold and
   orange palettes and WHITE on clay, so a wash that lifts the top for one
   sinks the other below AA. Measured — clay falls from 5.26:1 to 4.17:1 at a
   12% lift, and orange to 4.95:1 at a 10% darken.

   So none of the depth is in the field. It is all at the edges, feathered in
   pixels rather than percentages so the band the words actually sit on stays
   exactly --acc at every bar height and on every palette:

     - a cast shadow underneath, which is what says "lifted" and was the one
       thing missing entirely
     - a lit hairline along the top edge and a shaded one along the foot
     - light rolling ten pixels off the top, shade rolling twelve up from the
       bottom, both as inset shadows so the middle is untouched
     - the engineering grid the rest of the page is drawn on, ghosted into the
       surface, so the wide empty middle is a material and not a gap
     - one raking beam, the inner-page banner's move, parked in that same
       middle where nothing is read */
.cta-bar{position:relative;z-index:2;isolation:isolate;
  background:var(--acc);color:var(--on-acc);
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:18px clamp(20px,3vw,40px);padding:20px var(--sp-1);
  border-radius:var(--r-lg);margin:0 14px 14px;
  box-shadow:
    /* the light, and the shade, at the two edges only */
    inset 0 1px 0 color-mix(in srgb,#fff 34%,transparent),
    inset 0 10px 14px -12px color-mix(in srgb,#fff 55%,transparent),
    inset 0 -1px 0 color-mix(in srgb,var(--ink) 26%,transparent),
    inset 0 -12px 15px -17px color-mix(in srgb,var(--ink) 52%,transparent),
    /* and the two shadows it casts on the plate: one wide and soft for the
       height it is held at, one tight and dark for where it meets */
    0 22px 44px -16px rgba(0,0,0,.58),
    0 3px 8px rgba(0,0,0,.26)}
/* Both of these are drawn in --ink and never in --on-acc, and that is not a
   style choice. Contrast is the distance between the ink and the field, so any
   decoration has to move the field AWAY from the ink — and --on-acc is white
   on clay, so tinting with it walks the field toward the type. Measured: the
   grid and beam drawn in --on-acc cost clay 5.26:1 down to 3.60:1, and still
   only reached 4.17:1 with the beam cut to a twentieth. Drawn in --ink, every
   palette moves the right way at once, because the two light palettes have
   ratios of 8.5:1 and 5.8:1 to spend and clay gains rather than pays.
   It is also what the rest of the page already does: .grid-bg is ruled dark on
   cream and only goes light in its .on-dark variant. The accent is a light
   surface. The ruling on it is dark.

   The grid, masked to the middle, so the empty span between the question and
   the buttons is a material rather than a gap. */
.cta-bar::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  border-radius:inherit;
  background-image:linear-gradient(color-mix(in srgb,var(--ink) 8%,transparent) 1px,transparent 1px),
    linear-gradient(90deg,color-mix(in srgb,var(--ink) 8%,transparent) 1px,transparent 1px);
  background-size:46px 46px;
  -webkit-mask-image:linear-gradient(90deg,transparent 0 240px,#000 330px,
    #000 calc(100% - 350px),transparent calc(100% - 265px));
          mask-image:linear-gradient(90deg,transparent 0 240px,#000 330px,
    #000 calc(100% - 350px),transparent calc(100% - 265px))}
/* And in the middle, the hatch: the .bars motif — three skewed bars on a
   rising opacity ramp — laid as a field rather than as three marks, which is
   what a fifty-percent-wide gap wants. Crisp geometry rather than the soft
   raking beam that was here first: at this size a gradient smear reads as a
   printing fault, and hard stripes read as a machined surface. It is also the
   only place in the direction this mark still appears — the CTA carried it
   until the plate was rebuilt around the photograph, and nothing else does.

   Skewed -18deg, which is the angle .bars is drawn at, so it lines up with
   the leaning accent slab and the hero's clip-path rather than sitting at an
   angle of its own. */
.cta-bar::after{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  border-radius:inherit;
  background:repeating-linear-gradient(108deg,
    color-mix(in srgb,var(--ink) 11%,transparent) 0 13px,
    transparent 13px 32px);
  -webkit-mask-image:linear-gradient(90deg,transparent 0 300px,#000 380px,
    #000 calc(100% - 400px),transparent calc(100% - 330px));
          mask-image:linear-gradient(90deg,transparent 0 300px,#000 380px,
    #000 calc(100% - 400px),transparent calc(100% - 330px))}
.cta-bar>*{position:relative;z-index:1}
.cta-bar .mono{font-size:12px;font-weight:700;max-width:26ch;color:var(--on-acc);
  text-wrap:balance}
.cta-acts{display:flex;flex-wrap:wrap;gap:12px;align-items:center}
/* Both pills sit ON the bar now rather than in it. The solid one throws a
   real shadow onto the accent; the outlined one is engraved instead — a dark
   hairline along its top edge and a lit one along its foot, which is what a
   groove looks like under light coming from above. */
/* On the accent, the solid pill's cast shadow is black rather than the
   ink-tinted one it uses on cream: the ground is a saturated colour and a
   grey shadow on it reads as dirt. The outlined pill needs nothing here any
   more — being engraved is part of the variant now. */
.cta-bar .btn{box-shadow:inset 0 1px 0 rgba(255,255,255,.15),
  inset 0 -1px 0 rgba(0,0,0,.28),
  0 2px 5px rgba(0,0,0,.26),
  0 14px 26px -8px rgba(0,0,0,.5)}
@media (hover:hover) and (pointer:fine){
  .cta-bar .btn:hover{box-shadow:inset 0 1px 0 rgba(255,255,255,.18),
    inset 0 -1px 0 rgba(0,0,0,.28),
    0 3px 7px rgba(0,0,0,.3),
    0 20px 34px -8px rgba(0,0,0,.55)}}
/* On the accent the default outline colour is the accent. */
.cta-bar :focus-visible{outline:2px solid var(--on-acc);outline-offset:3px}
@media(prefers-contrast:more){
  /* Every one of these is a decoration over a field that is already at
     ratio. Asked for more contrast, the field is what should be left. */
  .cta-bar::before,.cta-bar::after{display:none}
}

@media(max-width:860px){
  .cta-head{grid-template-columns:1fr;align-items:start;
    padding:var(--sp-3) var(--sp-2) var(--sp-2)}
  .cta-trades{text-align:left;grid-template-columns:1fr 1fr;gap:9px 18px}
  .cta-shot::after{background:linear-gradient(180deg,
    color-mix(in srgb,var(--dark) 82%,transparent) 0%,
    color-mix(in srgb,var(--dark) 62%,transparent) 100%)}
}
@media(max-width:620px){
  .cta-bar{padding:16px 20px;margin:0 10px 10px;border-radius:var(--r);
    box-shadow:
      inset 0 1px 0 color-mix(in srgb,#fff 34%,transparent),
      inset 0 8px 11px -10px color-mix(in srgb,#fff 55%,transparent),
      inset 0 -1px 0 color-mix(in srgb,var(--ink) 26%,transparent),
      inset 0 -9px 11px -13px color-mix(in srgb,var(--ink) 52%,transparent),
      0 14px 28px -12px rgba(0,0,0,.55),
      0 2px 6px rgba(0,0,0,.24)}
  /* At this width the buttons go full-bleed and the middle is gone, so the
     hatch would fall behind them rather than beside them. */
  .cta-bar::after{display:none}
  .cta-acts{width:100%}
  .cta-acts .btn{flex:1 1 auto;justify-content:center}
}

/* ---------- 09 FOOTER ---------- */
footer{background:var(--dark);color:var(--on-dark-2);padding-top:clamp(50px,7vw,90px)}
/* Company is five links; Services is fifteen and Areas thirty-four. Giving all
   three the same track ran the footer to half a screen of one link per row, so
   the two long ones get double the width and split it into columns.
   Areas takes the widest track of the four: it is the list that sets the
   footer's height, and every 60px it gains off the others buys a whole column
   back. The three it borrows from all finish inside eight rows, so the width
   comes out of space that was standing empty. */
.fg{display:grid;grid-template-columns:1.25fr .62fr 1.75fr 1.05fr;gap:clamp(24px,4vw,56px);
  padding-bottom:var(--sp-3)}
.fg ul{list-style:none;padding:0;margin:0}
.fg h2{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--mute);margin:0 0 18px;font-weight:700;line-height:1.4}
.fg a{display:block;padding:9px 0;font-size:14.5px;color:var(--on-dark-2);transition:color .2s}
@media (hover:hover) and (pointer:fine){.fg a:hover{color:var(--acc)}}
.fg .about p{font-size:14.5px;max-width:36ch;margin:16px 0 0}

/* ---- the grouped block: the trades ----
   Multicol rather than a grid: a grid row is as tall as its tallest cell, so
   the two-trade Structure block would leave a hole under the five-trade
   Exterior one. Multicol keeps document order, balances the height, and
   `break-inside` stops a group splitting across a column boundary.

   A column width, not a column count: the block is a third of the footer at
   1440 and the whole row at 900, and the count that suits one is wrong for
   the other. */
.fgrps{columns:120px;column-gap:clamp(14px,1.6vw,24px)}
/* The twelve principal cities, two-up. A flat list rather than a grouped one:
   five valley headings over a partial list would read as the whole coverage,
   and it is not. The full thirty-four are one link away and already in the
   nav panel on this page. */
.fcities ul{columns:2;column-gap:clamp(14px,1.6vw,24px)}
.fcities li{break-inside:avoid}
.fcities a{padding:6px 0;line-height:1.35}
/* The trades need a wider column than the cities: "Residential Development"
   is twice the length of "Apache Junction", and a 120px track would wrap
   half the list. Two columns of four blocks, which is what the width gives. */
.ftrades .fgrps{columns:150px}
.fgrp{break-inside:avoid;margin:0 0 14px}
.fgrp li{break-inside:avoid}
/* Brighter and bolder than the links under it — on a dark ground the heading
   has to out-read the list, and `--mute` at 10px reads as a footnote. */
.fgrp-h{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.14em;
  text-transform:uppercase;font-weight:700;color:var(--on-dark);margin:0 0 4px;line-height:1.4}
.fgrp a,.fall{padding:6px 0;line-height:1.35}
.fall{margin-bottom:14px;color:var(--on-dark)}
.fbar{border-top:1px solid rgba(243,239,230,.1);padding:20px 0 30px;display:flex;flex-wrap:wrap;
  gap:12px 26px;justify-content:space-between;font-family:'JetBrains Mono',monospace;
  font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--mute)}
/* Two across: the blurb and Company pair up, then the two long lists, each
   still two-up inside its own half. */
/* Two across — and the cities take the whole width rather than half of it.
   In one of two tracks the block is 230px, which is one column of thirty-nine
   names; across both it is five columns at 860 and three at 560. It costs a
   row of its own and saves several screens of scrolling. */
/* Folded, it is two clean rows: the blurb beside Company, then the trades
   beside the cities. Nothing spans any more — the cities were the block that
   needed a row to itself, and twelve of them two-up do not. */
@media(max-width:900px){.fg{grid-template-columns:1fr 1fr}}
@media(max-width:540px){.fg{grid-template-columns:1fr}}

/* reveal */
/* Animate `translate`, not `transform`. A `transform:none` on the revealed
   state wipes out any transform the element needs for itself — with `transform`
   here, `.rv.in` outranked `.svc:hover` and silently killed every card's hover
   lift. The independent property composes instead of replacing. */
.rv{opacity:0;translate:0 28px;transition:opacity .8s ease,translate .8s cubic-bezier(.2,.8,.2,1)}
.rv.in{opacity:1;translate:none}
@media(prefers-reduced-motion:reduce){.rv{opacity:1;translate:none;transition:none}}

/* skip link — keyboard users reach the content without tabbing the whole nav */
.skip-link{position:fixed;top:10px;left:10px;z-index:9999;transform:translateY(-300%);
  padding:11px 18px;border-radius:6px;background:#141310;color:#fff;text-decoration:none;
  font:600 14px/1 system-ui,-apple-system,sans-serif;box-shadow:0 6px 20px rgba(0,0,0,.35)}
.skip-link:focus{transform:translateY(0)}
/* ---------- press feedback (punch-list 01) ----------
   Appended last so :active wins over the :hover transforms above.
   0.97 reads as a press; anything lower reads as a bounce. */
.btn:active{transform:scale(.97);transition-duration:.12s}
@media (prefers-reduced-motion:reduce){.btn:active{transform:none}}

/* ================================================================
   MULTI-PAGE FURNITURE
   The mockup was a single anchor-linked page. Everything below is new:
   a real dropdown nav, the offers band, the story split, and the inner
   page types. Written in 01's idiom — radius, layered shadow, cream.
   ================================================================ */

/* ---- brand mark: the real Quest logo replaces the drawn chevron ---- */
.brand img{height:34px;width:auto;display:block}
/* The logo is a light wordmark drawn for dark grounds; the nav is cream, so it
   is darkened here. invert() alone would turn the orange mark blue — the hue is
   rotated back and re-saturated so only the grey moves. */
.nav .brand img{filter:invert(1) hue-rotate(180deg) saturate(1.5)}
footer .brand img{height:38px}

/* ---- dropdown navigation ---- */
.nav .wrap{position:relative}
.nav nav a,.drop>button{color:var(--ink-2);transition:color .2s}
.drop{position:relative}
.drop>button{background:none;border:0;padding:0;font:inherit;font-size:14.5px;
  font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:7px}
.drop>button::after{content:"";width:6px;height:6px;border-right:1.6px solid currentColor;
  border-bottom:1.6px solid currentColor;transform:rotate(45deg) translateY(-2px);
  transition:transform .25s ease}
.drop.open>button::after{transform:rotate(-135deg) translateY(-2px)}
@media (hover:hover) and (pointer:fine){
  .nav nav a:hover,.drop>button:hover{color:var(--ink)}
}
.dropmenu{position:absolute;top:calc(100% + 20px);left:50%;translate:-50% 0;display:none;z-index:220;
  background:var(--cream);border:1px solid rgba(21,20,15,.09);border-radius:var(--r-lg);
  padding:18px;box-shadow:var(--sh-lg)}
.drop.open .dropmenu{display:grid}
@media (hover:hover) and (pointer:fine){
  .drop:hover .dropmenu{display:grid}
  /* The panel floats 20px clear of the button, and that 20px belonged to
     neither of them: moving the pointer down to click an item left `.drop`,
     `:hover` went false, and the menu shut before it could be reached. This
     bridges the gap — transparent, part of the panel, so the hover holds all
     the way down. Absolutely positioned, so it stays out of the grid rather
     than taking a cell of its own. */
  .dropmenu::before{content:"";position:absolute;left:0;right:0;top:-22px;height:22px}
}
/* The services panel is a grouped menu now, the same construction as the
   areas one: one headed block per group, packed by multicol. Two columns
   rather than the areas panel's three, because four blocks split three ways
   leaves one column carrying a single group and looking like an accident.
   The widest label is "Residential Development"; at 230px a column holds it
   on one line, and nothing here reaches the 422px the old full-remodel name
   did — the parenthetical that forced it is prose on the page now. */
.dropmenu--svc{grid-template-columns:1fr;gap:8px;width:min(500px,calc(100vw - 34px))}
.dropmenu--svc .dropgrps{columns:2}
/* The areas panel is a grouped menu rather than a run of links: one headed
   block per valley, and the blocks are packed by CSS multicol. A three-track
   grid would set every row to its tallest block, and the West Valley's
   thirteen cities would leave a hole under the Central Valley's four.
   Multicol keeps document order, balances the height and breaks between
   blocks rather than through one. */
.dropmenu--area{grid-template-columns:1fr;gap:8px;width:min(600px,calc(100vw - 34px))}
.dropgrps{columns:3;column-gap:12px}
.dropgrp{break-inside:avoid;margin:0 0 12px}
/* Bold and inked: at 11px mono the region name was reading as a caption on
   the city under it rather than as the heading over the block. Both families
   are variable, so 700 costs no extra file. */
.dropgrp-h{margin:0 0 3px;padding:0 13px;color:var(--ink);font-weight:700}
/* The panel is centred on its button, so its width is what decides whether it
   clears the left edge. Between the burger breakpoint and 1080px the Services
   button sits close enough to the brand that a 532px panel hangs off-screen;
   the tracks give up the width instead, and the two longest trades wrap. */
@media(max-width:1080px){
  .dropmenu--svc{width:min(430px,calc(100vw - 34px))}
}
/* The areas panel keeps its three columns all the way down to the burger
   breakpoint. It is centred on a button that sits mid-nav rather than beside
   the brand, so 600px still clears both edges at 940px — and folding to two
   columns instead made the panel 940px tall, which runs off the bottom of a
   laptop screen and takes the last cities with it. */
.dropmenu a{padding:10px 13px;border-radius:11px;font-size:14px;line-height:1.35;
  color:var(--ink-2);transition:background .2s,color .2s}
/* After `.dropmenu a`, not before it: same specificity, so the later rule is
   the one that lands. The grouped rows are tighter than a flat menu's because
   the heading above them is already doing the separating. */
.dropgrp a{display:block;padding:5px 13px}
.dropall{font-weight:600;color:var(--ink);border-bottom:1px solid rgba(21,20,15,.08);
  border-radius:11px 11px 0 0}
@media (hover:hover) and (pointer:fine){
  .dropmenu a:hover{background:rgba(21,20,15,.06);color:var(--ink)}
}

/* The call block at the foot of the phone sheet. The wide nav has the number
   in the header already, so it only exists inside the drawer. */
.navcall{display:none}

/* ---- burger ---- */
.navtoggle{display:none;margin-left:auto;width:44px;height:40px;border-radius:12px;
  border:1px solid rgba(21,20,15,.16);background:none;cursor:pointer;
  flex-direction:column;justify-content:center;align-items:center;gap:4px}
.navtoggle span{display:block;width:18px;height:2px;background:var(--ink);
  transition:transform .28s ease,opacity .2s ease}
.navtoggle.on span:nth-child(1){transform:translateY(6px) rotate(45deg)}
.navtoggle.on span:nth-child(2){opacity:0}
.navtoggle.on span:nth-child(3){transform:translateY(-6px) rotate(-45deg)}

/* ---- the phone drawer ----
   This used to wrap onto a second flex line inside `.nav .wrap`, which is a
   fixed `height` — so the drawer overflowed the header box, the cream band
   never grew behind it, and twenty-six menu items rendered transparent on top
   of the hero. It is a sheet now: fixed to the bottom edge of the header,
   opaque, filling the rest of the viewport and scrolling inside itself. */
@media(max-width:940px){
  :root{--navh:64px}
  .navtoggle{display:flex}
  .nav nav{display:none}
  .nav nav.open{
    /* Absolute against the header, not fixed to the viewport: `.nav` sets a
       backdrop-filter, and that makes it the containing block for any fixed
       descendant — a fixed sheet collapsed to the header's own 64px. Sticky
       positioning on `.nav` means absolute tracks the viewport anyway. */
    display:block;position:absolute;left:0;right:0;top:100%;z-index:210;
    height:calc(100vh - var(--navh));height:calc(100dvh - var(--navh));
    background:var(--cream);
    /* The sheet covers the page, so it carries its own gutter rather than
       inheriting the header's — and the bottom one clears the home indicator. */
    padding:8px var(--m) calc(28px + env(safe-area-inset-bottom));
    overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
    border-top:1px solid rgba(21,20,15,.08);
    animation:sheet .22s cubic-bezier(.2,.8,.2,1) both}
  @keyframes sheet{from{opacity:0;translate:0 -8px}to{opacity:1;translate:none}}
  /* 48px rows: the drawer is the whole navigation on a phone, and an 11px
     padding on a 17px line left every one of them a 39px target. */
  .nav nav.open>a{display:flex;align-items:center;min-height:48px;padding:2px;
    font-size:16.5px;font-weight:600;color:var(--ink);
    border-bottom:1px solid rgba(21,20,15,.07)}
  .drop{display:block;border-bottom:1px solid rgba(21,20,15,.07)}
  .drop>button{width:100%;justify-content:space-between;min-height:48px;padding:2px;
    font-size:16.5px;font-weight:600;color:var(--ink)}
  .drop>button::after{width:8px;height:8px;border-width:2px}
  .dropmenu{position:static;translate:none;display:none;box-shadow:none;border:0;
    background:none;padding:0 0 10px}
  .drop.open .dropmenu{display:grid;grid-template-columns:1fr}
  .dropmenu a{display:flex;align-items:center;min-height:44px;white-space:normal;
    padding:4px 14px;border-left:2px solid rgba(21,20,15,.1);border-radius:0;font-size:15px}
  .dropmenu--area,.dropmenu--svc{width:auto}
  .dropgrps{columns:1}
  .dropgrp{margin:0}
  .dropgrp-h{margin:14px 0 4px;padding:0}
  .dropall{border-left:2px solid rgba(21,20,15,.1)}
  /* The header's call button is the conversion action on a contractor site and
     it was the one thing display:none'd at exactly the width where tapping a
     number is easiest. It keeps its place; only the digits fold away. */
  .navtel{margin-left:10px;padding:0;width:44px;height:44px;justify-content:center;
    gap:0;flex:0 0 auto;box-shadow:0 6px 16px color-mix(in srgb,var(--acc) 40%,transparent)}
  .navtel .navtel-num{display:none}
  /* `.btn.acc .pip` fills the dot with ink, which at 44px is the whole
     button — the accent pill would vanish under its own dot. */
  .navtel.btn.acc .pip{width:44px;height:44px;background:transparent;color:var(--on-acc)}
  .navtel .pip svg{display:block;width:19px;height:19px;fill:currentColor}
  .navcall{display:block;margin-top:26px;padding-top:22px;
    border-top:1px solid rgba(21,20,15,.1)}
  .navcall .mono{display:block;color:var(--mute-lt);margin-bottom:8px}
  .navcall>a{display:inline-flex;align-items:center;min-height:44px;
    font-size:clamp(25px,6.8vw,31px);color:var(--acc-dim)}
  .navtoggle{margin-left:0}
  .nav .wrap{gap:0}
  .brand{margin-right:auto;min-width:0}
  .brand img,.nav .brand img{height:30px}
  /* The sheet takes the scroll; the page behind it must not also move.
     Locking it is also what broke it. `overflow:hidden` on the document stops
     `position:sticky` from sticking, so the header snapped back to its static
     position at the top of the DOCUMENT — and the sheet, which hangs off the
     header's bottom edge, went with it. Open the menu anywhere below the fold
     and it opened that far above the viewport; what you saw was whatever you
     were scrolled to, with no way back because the close button had left too.
     So while the drawer is open the header is fixed rather than sticky, which
     needs no scrollport to resolve against. Taking it out of the flow would
     let the page ride up by the header's own height, so the padding puts that
     back — the border is part of the height it vacated. */
  html.nav-open,html.nav-open body{overflow:hidden}
  html.nav-open .nav{position:fixed;top:0;left:0;right:0}
  html.nav-open body{padding-top:calc(var(--navh) + 1px)}
}
@media(max-width:940px) and (prefers-reduced-motion:reduce){
  .nav nav.open{animation:none}
}

/* ---- eyebrow, shared by every inner page ---- */
.eyebrow{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--acc-dim);margin:0 0 14px;font-weight:700}
.sec.dark .eyebrow{color:var(--acc-on-dark)}

/* ---- story split ---- */
.split{position:relative;z-index:3;display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(26px,4.5vw,72px);align-items:center}
.split h2{font-size:clamp(28px,3.8vw,50px);font-weight:900;margin-bottom:20px}
.split p{color:var(--ink-2);margin-bottom:18px;font-size:clamp(14.5px,1.4vw,16.5px)}
.story-shot{border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--sh-lg)}
.story-shot img{display:block;width:100%;height:100%;object-fit:cover;aspect-ratio:4/3}
@media(max-width:880px){.split{grid-template-columns:1fr}}

/* ---- offers band ---- */
/* ---- the offer vouchers ----
   They were two --dark-2 rectangles on --dark with a hairline border: the same
   value as the band behind them, no shadow, nothing saying which was in front.
   The first fix lit them, and the light was the wrong answer — a glow is a
   thing done TO a flat component rather than a component that has a shape.

   So the component changed instead. An offer is a voucher: an amount, its
   terms, and a code you take away. This one is built like one — a band of the
   material across the head, a torn edge under it with a notch punched out of
   each side, and the offer on the stub below. The depth is the shape and the
   elevation now, which is why nothing has to glow. */
.offers{position:relative;z-index:3;display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(16px,2.2vw,28px)}
/* No overflow:hidden. The notches are drawn outside the card's own box, and
   clipping them turns the torn edge back into a straight one. */
.offer{position:relative;background:var(--dark-2);
  border:1px solid rgba(243,239,230,.09);border-radius:var(--r-lg);
  display:grid;grid-template-rows:auto 0 1fr;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08),
    inset 0 -1px 0 rgba(0,0,0,.4),
    0 2px 6px rgba(0,0,0,.36),
    0 24px 48px -14px rgba(0,0,0,.55);
  transition:transform .3s cubic-bezier(.2,.8,.2,1),box-shadow .3s ease}
.offer-shot{height:clamp(118px,14vw,164px);overflow:hidden;
  border-radius:calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0;
  background:var(--dark-3)}
.offer-shot img{width:100%;height:100%;object-fit:cover;
  transition:transform 1.1s cubic-bezier(.2,.8,.2,1)}
@media (hover:hover) and (pointer:fine){.offer:hover .offer-shot img{transform:scale(1.05)}}

/* The tear. Two circles of the band's own colour bitten out of the sides, and
   a dashed rule running between them — a zero-height row, so the seam sits on
   the boundary rather than taking space from either side of it. */
.offer-seam{position:relative}
.offer-seam::before,.offer-seam::after{content:"";position:absolute;top:-12px;
  width:24px;height:24px;border-radius:50%;background:var(--dark)}
.offer-seam::before{left:-13px}
.offer-seam::after{right:-13px}
/* Punched holes rather than a dashed rule. The dashes were --on-dark at a
   quarter strength and lay along the bottom row of the photograph, where a
   pale line on a pale board is no line at all. These are holes in the band's
   own colour bitten straight through the seam — which is what a perforation
   actually is, and it reads the same whatever photograph is above it. */
.offer-seam i{position:absolute;left:14px;right:14px;top:-3px;height:6px;display:block;
  background:radial-gradient(circle at 3px 3px,var(--dark) 2.6px,transparent 3px);
  background-size:15px 6px}

/* ---- the stub, which is the half that was flat ----
   Above the seam the voucher has a photograph, a torn edge and holes punched
   through it. Below the seam it was --dark-2 with an amount, a heading, a
   paragraph and a button sitting on it, and nothing else — no light, no
   material, no depth. Three layers go under the content now, all of them
   behind z-index 2 so nothing lands on the type. */
.offer-body{position:relative;padding:var(--sp-1);
  border-radius:0 0 calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px);
  overflow:hidden}
/* 1. THE LIGHT, and it comes from the RIGHT, which is a contrast decision
      before it is a compositional one.
      The amount is set in --acc — the raw accent, not --acc-on-dark — and on
      the clay palette the raw accent is a dark terracotta. Measured on the
      flat card, before any of this existed, it sat at 3.21:1 against a 3:1
      bar: a seven percent margin. Every way of lighting a surface that puts
      light where the amount is spends that margin, because lifting the field
      toward a dark numeral's own hue is a ratio going the wrong way. A four
      percent white highlight across the head was enough on its own to take it
      to 2.88.
      So the light is thrown from beyond the right edge, where the only thing
      it falls on is the ghost numeral, and the head of the card is pooled in
      shadow instead — which BUYS margin on the amount rather than spending it,
      and gives the stub a light direction at the same time. Measured after:
      3.44:1 on clay, 4.7 on orange. */
.offer-body::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:
    radial-gradient(66% 112% at 118% 56%,
      color-mix(in srgb,var(--acc) 18%,transparent) 0%,
      color-mix(in srgb,var(--acc) 6%,transparent) 38%,transparent 68%),
    linear-gradient(180deg,rgba(0,0,0,.36) 0%,rgba(0,0,0,.1) 34%,
      transparent 52%,rgba(0,0,0,.18) 100%)}
/* 2. THE MATERIAL. The same raking hatch the CTA bar carries, at a fifth of
      its strength and a tighter pitch — enough that the surface has a weave
      under the light and not enough to read as a pattern. Masked off toward
      the foot so it never competes with the paragraph. */
.offer-body::after{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:repeating-linear-gradient(108deg,
    rgba(243,239,230,.03) 0 1px,transparent 1px 8px);
  /* Masked AWAY from the head, not toward it. The hatch is white at three
     percent, which is a lift, and the amount is the one line on this card that
     cannot afford one. It reads on the lit half, which is where a raking
     texture would show anyway. */
  -webkit-mask-image:radial-gradient(96% 108% at 104% 62%,#000 0 34%,transparent 82%);
          mask-image:radial-gradient(96% 108% at 104% 62%,#000 0 34%,transparent 82%)}
.offer-body>*{position:relative;z-index:2}
/* 3. THE AMOUNT, PRINTED TWICE. Once at reading size and once enormous behind
      it, bleeding off the right edge — the eye reads two copies of one number
      at two scales as near and far rather than as a repeat, which is depth for
      the price of one span. It is aria-hidden: the number is already in the
      DOM once and a screen reader does not need it twice.
      Held to the head of the card and faded out downward, because the only
      thing it may sit behind is the amount itself, which is 86px and needs
      3:1 — never the 14.5px paragraph, which needs 4.5. */
.offer-ghost{position:absolute;z-index:1;top:-.19em;right:-.24em;
  font-size:clamp(112px,15vw,220px);font-weight:900;letter-spacing:-.06em;
  line-height:.78;white-space:nowrap;pointer-events:none;user-select:none;
  color:rgba(243,239,230,.042);
  -webkit-mask-image:linear-gradient(174deg,#000 0 34%,transparent 74%);
          mask-image:linear-gradient(174deg,#000 0 34%,transparent 74%)}
.offer b{display:block;font-size:clamp(44px,6vw,86px);font-weight:900;letter-spacing:-.05em;
  line-height:1;color:var(--acc);
  /* Pure black, no accent glow. A glow in the numeral's own hue lifts the
     field immediately around it toward the numeral's colour, which is a
     contrast ratio going the wrong way; black under it darkens the field and
     the ratio improves. It is a shadow the numeral casts on the stub, which is
     also what it looks like. */
  text-shadow:0 2px 0 rgba(0,0,0,.5),0 16px 34px rgba(0,0,0,.55)}
.offer h3{font-size:clamp(19px,2.1vw,26px);margin:14px 0 12px;font-weight:800}
.offer p{margin:0 0 24px;font-size:14.5px;color:var(--on-dark-2);max-width:46ch}
/* focus-within as well as hover: the card is not the control — the button
   inside it is — so a keyboard tabbing to that button should see the voucher
   come forward the same way a pointer does. */
@media (hover:hover) and (pointer:fine){.offer:hover{transform:translateY(-4px)}}
.offer:hover,.offer:focus-within{
  box-shadow:inset 0 1px 0 rgba(255,255,255,.11),
    inset 0 -1px 0 rgba(0,0,0,.4),
    0 3px 9px rgba(0,0,0,.38),
    0 34px 64px -16px rgba(0,0,0,.62)}
.offer:focus-within{transform:translateY(-4px)}
@media(prefers-reduced-motion:reduce){
  .offer,.offer:hover,.offer:focus-within{transform:none}
  .offer:hover .offer-shot img{transform:none}}
@media(max-width:780px){.offers{grid-template-columns:1fr}}

/* ---- inner-page banner ----
   It used to be a flat orange plane with the photograph beside it. It is the
   photograph now: full bleed across the band, under a scrim that is heavy on
   the left where the words are and thin on the right where the work is, with a
   light beam cutting down through it. Turned dark so an inner page reads as a
   chapter rather than a repeat of the front door. */
.subhero{position:relative;overflow:hidden;background:var(--dark);color:var(--on-dark);
  padding:var(--sp-4) 0 var(--sp-4);isolation:isolate}
.subhero-shot{position:absolute;inset:0;z-index:0;pointer-events:none}
.subhero-shot img{width:100%;height:100%;object-fit:cover;object-position:center 45%;
  filter:saturate(.85) contrast(1.04)}
/* The band is 3.3:1 and the library is phone frames, so this crop is severe by
   construction. The scrim is heavy enough that the photograph reads as ground
   rather than as a subject that has had its head cut off — which is also what
   the reference does, for the same reason. */
.subhero-shot::after{content:"";position:absolute;inset:0;
  background:linear-gradient(96deg,var(--dark) 2%,
    color-mix(in srgb,var(--dark) 94%,transparent) 30%,
    color-mix(in srgb,var(--dark) 70%,transparent) 62%,
    color-mix(in srgb,var(--dark) 82%,transparent) 100%)}
/* the light beam — a vertical band of daylight the photograph is read through */
.subhero-beam{position:absolute;z-index:2;top:-10%;bottom:-10%;left:50%;width:20%;
  pointer-events:none;transform:skewX(-5deg);
  background:linear-gradient(90deg,transparent,
    color-mix(in srgb,var(--cream) 26%,transparent) 30%,
    color-mix(in srgb,var(--cream) 26%,transparent) 70%,transparent)}
.subhero .grid-bg{z-index:1}
.subhero .wrap{position:relative;z-index:4}
.subhero-in{max-width:min(660px,58%)}
.subhero h1{font-size:clamp(30px,4.4vw,58px);font-weight:900;color:var(--on-dark);
  letter-spacing:-.03em;margin-top:22px;text-shadow:0 2px 30px rgba(0,0,0,.45)}
/* the one word in accent — the reference's move, and the only colour up here */
.subhero h1 .hl{color:var(--acc-on-dark)}
.subhero .lede{margin:20px 0 28px;font-size:clamp(14.5px,1.4vw,17px);max-width:42ch;
  color:var(--on-dark-2)}
/* .btn.ghost is drawn for an accent ground, where "on-acc" is near-black. Here
   the ground is the photograph, so it borrows the dark-ground ink instead. */
.subhero .btn.ghost,.hero .btn.ghost{color:var(--on-dark);
  border-color:color-mix(in srgb,var(--on-dark) 46%,transparent)}
.subhero .btn.ghost .pip,.hero .btn.ghost .pip{background:var(--on-dark);color:var(--ink)}
@media (hover:hover) and (pointer:fine){
  .subhero .btn.ghost:hover,.hero .btn.ghost:hover{
    background:color-mix(in srgb,var(--on-dark) 14%,transparent)}}
@media(max-width:1080px){
  .subhero-in{max-width:none}
  /* Narrow, the beam falls behind the words instead of beside them. */
  .subhero-beam{display:none}
  .subhero-shot::after{background:linear-gradient(180deg,
    color-mix(in srgb,var(--dark) 90%,transparent) 0%,
    color-mix(in srgb,var(--dark) 62%,transparent) 100%)}
}

/* ---- breadcrumb ---- */
.crumbs{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;color:color-mix(in srgb,var(--on-dark) 72%,transparent);
  display:flex;flex-wrap:wrap;gap:9px;align-items:center}
.crumbs a{color:inherit;text-decoration:underline;text-underline-offset:3px}
.crumbs a:hover{color:var(--acc-on-dark)}
.crumbs b{color:var(--on-dark);font-weight:700}

/* ---- service tab rail ---- */
.svctabs{background:var(--dark);padding:16px 0;overflow:hidden}
.svctabs .wrap{display:flex;flex-wrap:wrap;gap:8px}
.svctabs a{padding:9px 15px;border-radius:999px;font-size:13px;font-weight:600;
  color:var(--on-dark-2);border:1px solid rgba(243,239,230,.14);
  transition:color .22s ease,border-color .22s ease,background .22s ease}
@media (hover:hover) and (pointer:fine){
  .svctabs a:hover{color:var(--on-dark);border-color:rgba(243,239,230,.4)}
}
.svctabs a.on{background:var(--acc);color:var(--on-acc);border-color:transparent}

/* ---- prose + why-choose ---- */
.prose-wrap{position:relative;z-index:3;display:grid;grid-template-columns:1.15fr .85fr;
  gap:clamp(28px,4.5vw,70px);align-items:start}
.prose h2{font-size:clamp(26px,3.4vw,44px);font-weight:900;margin-bottom:22px}
.prose h2 span{color:var(--acc-dim)}
.prose p{color:var(--ink-2);margin-bottom:18px;font-size:clamp(14.5px,1.4vw,16.5px)}
/* On a dark band, which is where the service pages run this block now. Only
   the two colours that were set against cream need restating — the heading
   takes --on-dark from .sec.dark, and the why-choose cards are white either
   way, so their ink and their numeral are already right. --acc-dim is the
   accent mixed toward the ink for a cream ground and it goes muddy on dark;
   --acc-on-dark is the same accent lifted the other way. */
.sec.dark .prose h2 span{color:var(--acc-on-dark)}
.sec.dark .prose p{color:var(--on-dark-2)}
.wcs h3{font-size:clamp(18px,2vw,23px);font-weight:800;margin-bottom:20px}
.wcgrid{display:grid;gap:12px}
.wc{position:relative;background:#fff;border-radius:var(--r);padding:20px 22px;
  box-shadow:var(--sh);display:flex;gap:16px;align-items:flex-start}
.wc .n{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:700;
  color:var(--acc-dim);flex:0 0 auto;padding-top:2px}
.wc p{margin:0;font-size:14.5px;color:var(--ink-2)}
@media(max-width:900px){.prose-wrap{grid-template-columns:1fr}}

/* ---------- the blog ----------
   The one part of this site with a date on it, which is the only thing it
   needs that nothing else here has: a meta line. Everything else is furniture
   already in this stylesheet — the card is the project card with an anchor
   round it, the article is .prose, and the aside is the trade-by-city notes
   panel. */
.postmeta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin:0 0 16px;
  font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;font-weight:700;
  color:var(--mute-lt)}
.postmeta .topic{color:var(--acc-dim)}
/* Between the three items rather than around them, so the row still reads as
   one line when it wraps on a phone. */
.postmeta>*+*{position:relative;padding-left:15px}
.postmeta>*+*::before{content:"";position:absolute;left:0;top:50%;width:5px;height:1px;
  background:currentColor;opacity:.55}
.postmeta.on-dark{color:var(--on-dark-2);margin:22px 0 0}
.postmeta.on-dark .topic{color:var(--acc-on-dark)}

.posts{position:relative;z-index:3;display:grid;gap:clamp(16px,2.2vw,26px)}
.postcard{position:relative;display:grid;grid-template-columns:.8fr 1.2fr;background:#fff;
  border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--sh-lg);
  transition:transform .3s cubic-bezier(.2,.8,.2,1)}
/* The link is on the heading, for a screen reader, and stretched over the card,
   for a mouse. One link, one accessible name, one click target — and the global
   :focus-visible ring is left on the heading itself rather than moved to the
   card, because moving it needs :has() and a ring around the words is a
   perfectly good indicator that works everywhere. */
.posth3 a::after{content:"";position:absolute;inset:0;border-radius:var(--r-lg)}
.postshot{overflow:hidden;display:block}
.postshot img{width:100%;height:100%;object-fit:cover;aspect-ratio:4/3;display:block;
  transition:transform .8s cubic-bezier(.2,.8,.2,1)}
.postbody{padding:var(--sp-2);display:flex;flex-direction:column;justify-content:center}
.posth3{font-size:clamp(20px,2.4vw,31px);font-weight:900;letter-spacing:-.02em;
  line-height:1.15;margin:0 0 12px;text-wrap:balance}
.postdek{color:var(--ink-2);font-size:clamp(14.5px,1.4vw,16.5px);
  margin:0 0 22px;text-wrap:pretty}
.postcard .go{display:inline-flex;align-items:center;gap:10px;min-height:24px;
  font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;font-weight:700;color:var(--acc-dim)}
.postcard .go i{font-style:normal;transition:transform .28s ease}
@media (hover:hover) and (pointer:fine){
  .postcard:hover{transform:translateY(-4px)}
  .postcard:hover .postshot img{transform:scale(1.06)}
  .postcard:hover .go i{transform:translate(5px,-5px)}
}
@media(max-width:820px){.postcard{grid-template-columns:1fr}}

/* ---- the article ----
   .prose sets the type; this sets the rhythm of a long body, which the two
   pages that used .prose before never needed because neither runs past four
   paragraphs. */
.postbodycopy{max-width:68ch}
.postbodycopy h2{font-size:clamp(20px,2.2vw,28px);margin:38px 0 16px}
.postbodycopy h2:first-child{margin-top:0}
.postlist{list-style:none;margin:0 0 22px;padding:0;display:grid;gap:10px}
.postlist li{position:relative;padding-left:24px;color:var(--ink-2);
  font-size:clamp(14.5px,1.4vw,16.5px)}
.postlist li::before{content:"";position:absolute;left:0;top:.62em;width:11px;height:2px;
  background:var(--acc-dim)}
/* The one line the reader keeps if they keep nothing else. */
.posttakeaway{margin:34px 0 0;padding:22px 26px;background:var(--cream-2);
  border-left:3px solid var(--acc);border-radius:0 var(--r) var(--r) 0;
  font-size:clamp(15px,1.5vw,17.5px);font-weight:600;color:var(--ink);text-wrap:pretty}

/* ---- "from the build log", on the pages that link into the blog ----
   Not the index card: this row sits at the foot of a trade or a city page,
   under everything that page is actually about, so it has to read as a
   footnote rather than compete with it. Three lines, no photograph. */
.logrow{position:relative;z-index:3;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:clamp(12px,1.6vw,18px)}
.logrow a{display:block;background:#fff;border-radius:var(--r);padding:22px 24px;
  box-shadow:var(--sh);transition:transform .28s cubic-bezier(.2,.8,.2,1)}
.logmeta{display:block;font-size:10px;letter-spacing:.12em;text-transform:uppercase;
  font-weight:700;color:var(--acc-dim);margin-bottom:12px}
.logtitle{display:block;font-weight:800;font-size:clamp(15.5px,1.6vw,18px);
  line-height:1.25;margin-bottom:9px;text-wrap:balance}
.logdek{display:block;color:var(--ink-2);font-size:14px;line-height:1.55;text-wrap:pretty}
@media (hover:hover) and (pointer:fine){.logrow a:hover{transform:translateY(-3px)}}

/* The questions block at the foot of a post. Its heading is one of the article's
   own h2s, so it takes the article's rhythm rather than a section heading's. */
.postfaq-h{font-size:clamp(20px,2.2vw,28px);font-weight:900;margin:44px 0 18px}

/* Seven items and a phone number is more than the desktop bar was drawn for,
   so the gap tightens before the drawer takes over at 940px. */
@media(max-width:1180px){.nav nav{gap:clamp(11px,1.5vw,20px)}}

/* ---- scope cards + quality note (concrete only) ---- */
.scope{position:relative;z-index:3;display:grid;grid-template-columns:repeat(2,1fr);
  gap:clamp(14px,1.8vw,22px)}
.sc{background:#fff;border-radius:var(--r-lg);padding:var(--sp-1);box-shadow:var(--sh)}
.sc h3{font-size:clamp(18px,2vw,23px);font-weight:800;margin-bottom:11px}
.sc p{margin:0;font-size:14.5px;color:var(--ink-2)}
.quality{position:relative;z-index:3;margin-top:clamp(18px,2.4vw,28px);
  background:var(--acc);color:var(--on-acc);border-radius:var(--r-lg);padding:var(--sp-1)}
.quality h3{font-size:clamp(18px,2vw,23px);font-weight:800;margin-bottom:11px}
.quality p{margin:0;font-size:14.5px;color:rgba(20,19,16,.78);max-width:76ch}
@media(max-width:780px){.scope{grid-template-columns:1fr}}

/* ---- the city register ----
   Eleven rows, hairline ruled, numbered like a drawing index. Closed it is a
   list you can scan in one pass; open it is the paragraph and the four things
   that actually differ about building in that city.

   Native <details>: a button to a screen reader, announcing its own expanded
   state, keyboard-operable, no script. The marker is drawn rather than typed,
   and it changes shape as well as colour — the open state does not depend on
   anyone seeing the accent. */
.register{position:relative;z-index:3;border-top:1px solid rgba(21,20,15,.16)}
.reg{border-bottom:1px solid rgba(21,20,15,.16)}
.reg summary{list-style:none;cursor:pointer;display:grid;
  grid-template-columns:auto 1fr auto;align-items:center;
  gap:clamp(14px,2.4vw,34px);padding:clamp(15px,1.7vw,22px) 0;min-height:64px}
.reg summary::-webkit-details-marker{display:none}
.reg summary:focus-visible{outline:2px solid var(--acc);outline-offset:4px;border-radius:6px}
/* Tabular figures, or the column of numerals wanders as it counts past nine. */
.reg-n{color:var(--mute-lt);font-variant-numeric:tabular-nums;font-size:11px;
  transition:color .22s ease}
.reg-name{font-size:clamp(21px,2.9vw,40px);font-weight:900;letter-spacing:-.035em;
  line-height:1.05;transition:transform .28s cubic-bezier(.2,.8,.2,1)}
.reg[open] .reg-n{color:var(--acc-dim)}
@media (hover:hover) and (pointer:fine){
  .reg summary:hover .reg-n{color:var(--acc-dim)}
  .reg summary:hover .reg-name{transform:translateX(7px)}
}
/* the marker: a plus that loses its upright to become a minus */
.reg-mark{width:34px;height:34px;border-radius:50%;flex:0 0 auto;position:relative;
  border:1.5px solid rgba(21,20,15,.22);transition:border-color .22s ease,background .22s ease}
.reg-mark::before,.reg-mark::after{content:"";position:absolute;inset:50% 9px;
  height:1.5px;background:var(--ink);transform:translateY(-50%);
  transition:transform .28s cubic-bezier(.2,.8,.2,1)}
.reg-mark::after{transform:translateY(-50%) rotate(90deg)}
.reg[open] .reg-mark{background:var(--acc);border-color:var(--acc)}
.reg[open] .reg-mark::after{transform:translateY(-50%) rotate(90deg) scaleX(0)}
@media (hover:hover) and (pointer:fine){
  .reg summary:hover .reg-mark{border-color:var(--acc)}}

.reg-panel{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(0,.65fr);
  gap:clamp(20px,3vw,52px);padding:0 0 clamp(22px,2.6vw,34px);
  padding-left:calc(11px + clamp(14px,2.4vw,34px))}
.reg-panel p{margin:0;color:var(--ink-2);max-width:68ch;
  font-size:clamp(14.5px,1.4vw,16px)}
.reg-notes{list-style:none;margin:0 0 18px;padding:0;display:grid;gap:9px;
  color:var(--mute-lt);font-size:10.5px}
.reg-notes li{padding-left:16px;position:relative}
.reg-notes li::before{content:"";position:absolute;left:0;top:.5em;
  width:6px;height:6px;background:var(--acc)}
.reg .go{display:inline-flex;align-items:center;gap:9px;min-height:24px;
  font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;font-weight:700;color:var(--acc-dim)}
.reg .go i{font-style:normal;transition:transform .28s ease}
@media (hover:hover) and (pointer:fine){.reg .go:hover i{transform:translate(4px,-4px)}}
/* A panel that appears instantly reads as a page jump rather than as a drawer. */
@keyframes regopen{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
.reg[open] .reg-panel{animation:regopen .26s cubic-bezier(.2,.8,.2,1) both}
@media(prefers-reduced-motion:reduce){.reg[open] .reg-panel{animation:none}}
@media(max-width:820px){
  .reg-panel{grid-template-columns:1fr;padding-left:0}
  .reg-name{font-size:clamp(19px,5vw,26px)}
}

/* ---- FAQ accordion ----
   No max-width. It carried 940px with no auto margins, which is the one inner
   column in this stylesheet that neither filled its section nor centred in it:
   the wrap is 1280px at full width, so the questions stopped 340px short of
   the right-hand edge while the section heading above them ran the whole way.
   That reads as a block that failed to load rather than as a narrow measure.
   Everything else here — the cards, the steps, the register, the bands — fills
   the wrap, and the wrap is what centres the page. The rows are long and thin
   at 1280, which is what an accordion row is; the answer inside still sets to
   78ch, because a measure belongs on the text and not on the box around it. */
.faqlist{position:relative;z-index:3;display:grid;gap:12px}
/* The padding is on the summary, not on the details around it. A <summary> is
   the only part of a <details> that toggles, so with the padding on the parent
   the card drew a 64px-tall button of which the middle 24px responded to a tap
   and the 20px above and below did nothing. Closed, the box measures exactly
   what it did before; what changed is that all of it is now the control. */
.faqlist details{background:#fff;border-radius:var(--r);box-shadow:var(--sh)}
.faqlist summary{font-weight:800;cursor:pointer;list-style:none;font-size:clamp(15px,1.5vw,18px);
  padding:20px 24px;
  display:flex;justify-content:space-between;gap:18px;align-items:flex-start}
.faqlist summary::-webkit-details-marker{display:none}
.faqlist summary::after{content:"+";font-weight:500;color:var(--acc-dim);flex:0 0 auto;
  font-size:22px;line-height:1;transition:transform .25s ease}
.faqlist details[open] summary::after{transform:rotate(45deg)}
.faqlist details p{margin:0;padding:0 24px 20px;color:var(--ink-2);font-size:14.5px;
  max-width:78ch}

/* ---- service-area local notes ---- */
.localnotes{background:var(--dark);color:var(--on-dark);border-radius:var(--r-lg);
  padding:var(--sp-1);box-shadow:var(--sh-lg)}
.localnotes h3{font-size:clamp(17px,1.9vw,21px);font-weight:800;margin-bottom:18px}
.localnotes ul{list-style:none;display:grid;gap:11px;margin:0 0 26px;padding:0}
.localnotes li{position:relative;padding-left:22px;font-size:14.5px;color:var(--on-dark-2)}
.localnotes li::before{content:"";position:absolute;left:0;top:.5em;width:9px;height:9px;
  background:var(--acc)}
/* The address was in the schema and in the form's failure message and nowhere
   a visitor could read it. It is a published business email; it belongs on the
   page. Sized under the phone number, because the phone is still the faster
   route and the card should say so by its typography. */
.help-mail{display:inline-flex;align-items:center;min-height:24px;font-size:clamp(15px,1.6vw,18px);
  font-weight:700;color:var(--acc-on-dark);word-break:break-word;margin-bottom:20px}
.fmail{display:inline-flex;align-items:center;min-height:24px;margin-top:14px;
  font-size:14px;color:var(--on-dark-2);word-break:break-word}
@media (hover:hover) and (pointer:fine){.fmail:hover{color:var(--acc)}}
/* ---- the social link ----
   One profile, so one link. It lived at 10px in the legal bar, which is a
   footnote; it is a chip with the mark on it now, in the two places somebody
   looks for it. The glyph is filled and carries its own colour, so the trade
   icons' stroke rules cannot reach it. */
.fsocial,.help-social{display:inline-flex;align-items:center;gap:11px;min-height:44px;
  font-size:14px;font-weight:600;color:var(--on-dark-2);transition:color .2s}
.fsocial{margin-top:12px}
.help-social{color:var(--on-dark);margin-bottom:20px}
.soc-ic{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;
  border-radius:50%;background:rgba(243,239,230,.1);flex:0 0 auto;
  transition:background .22s ease,color .22s ease}
.soc-ic svg{width:17px;height:17px;display:block}
@media (hover:hover) and (pointer:fine){
  .fsocial:hover,.help-social:hover{color:var(--on-dark)}
  .fsocial:hover .soc-ic,.help-social:hover .soc-ic{background:var(--acc);color:var(--on-acc)}
}
.localcall{border-top:1px solid rgba(243,239,230,.14);padding-top:20px}
.localcall b{display:block;font-size:clamp(20px,2.3vw,27px);color:var(--acc)}
.localcall span{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--on-dark-2);display:block;margin-top:7px}

/* The short version of a trade, on a trade-by-city page. Not the whole service
   page repeated — a few lines and a link to it. */
.svcpts{position:relative;z-index:3;display:grid;gap:14px;max-width:74ch;margin-bottom:26px}
.svcpts p{margin:0;color:var(--on-dark-2);font-size:clamp(14.5px,1.4vw,16.5px);
  padding-left:20px;position:relative}
.svcpts p::before{content:"";position:absolute;left:0;top:.62em;width:9px;height:2px;
  background:var(--acc)}

/* ---- nearby-areas link cloud ---- */
.arealinks{position:relative;z-index:3;display:flex;flex-wrap:wrap;gap:10px}
.arealinks a{padding:12px 20px;border-radius:999px;background:#fff;box-shadow:var(--sh);
  font-weight:600;font-size:14.5px;transition:transform .25s ease,background .25s ease}
@media (hover:hover) and (pointer:fine){
  .arealinks a:hover{background:var(--acc);color:var(--on-acc);translate:0 -3px}
}

/* The four .sec.dark overrides that used to live here are gone with the band
   they served. The process moved to the cream section under the subhero, which
   is the ground .steps was drawn on — cream discs with an accent ring, a dark
   dashed connector — so the base rules are the whole treatment again. If it
   ever goes back on dark, the numerals and the connector need restating: on a
   dark section the disc took near-white text and the copy went dark-on-black. */

/* ---- footer call button ----
   `.fg a` is one class plus an element (0,1,1) and outranks `.btn` (0,1,0)
   whatever the source order, so the footer's phone button was being forced to
   display:block with the list padding and the pill fell apart. Two classes
   plus the element wins it back. */
.fg a.btn{display:inline-flex;align-items:center;padding:15px 26px;margin-top:22px;
  color:var(--on-acc)}
@media (hover:hover) and (pointer:fine){.fg a.btn:hover{color:var(--on-acc)}}

/* ---- gallery, projects, contact, sitemap ---- */
.sec.cream.alt{background:var(--cream-2)}
/* The gallery is Quest's own camera roll — two phone shoots, so most frames
   are portrait and some are landscape. A fixed 3:2 grid cropped the roof off
   the portrait ones and the sky off the rest, so the wall is columns instead:
   every photograph keeps its own shape and nothing is cut. */
/* ---- the gallery: justified rows ----
   Not a masonry. A masonry pours the frames down fixed columns and cannot end
   flat — whichever column takes the last tall portrait overruns the others,
   which on this library left four hundred pixels of empty cream under two of
   the three columns.

   Each row is a flex line filled to the full measure, and each frame carries
   its own aspect ratio twice: as --ar on flex-grow, so widths come out
   proportional to the ratios, and as aspect-ratio, so the heights that follow
   from those widths are identical across the row. The row is therefore flush
   on both edges and level along the bottom, at any width, with nothing
   cropped and no height written down anywhere. Which frames share a row is
   decided at build time — see justified() in the direction. */
/* ---- the gallery's chapters ----
   Seventy-five frames in one unbroken run read as a camera roll even though
   they were already ordered as a build sequence. The order did not change; the
   breaks in it are visible now, and the rail below names them so the run is
   navigable rather than just long. */
.galnav{position:relative;z-index:3;display:flex;flex-wrap:wrap;gap:8px;
  margin:0 0 clamp(30px,4vw,52px)}
.galnav a{display:inline-flex;align-items:center;gap:9px;min-height:44px;
  padding:0 17px;border-radius:999px;background:#fff;box-shadow:var(--sh);
  font-size:13.5px;font-weight:600;
  transition:background .22s ease,color .22s ease,transform .22s ease}
.galnav a span{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:700;
  color:var(--acc-dim);transition:color .22s ease}
@media (hover:hover) and (pointer:fine){
  .galnav a:hover{background:var(--acc);color:var(--on-acc);translate:0 -2px}
  .galnav a:hover span{color:var(--on-acc)}
}
.galchap{position:relative;z-index:3}
.galchap+.galchap{margin-top:clamp(46px,6vw,88px)}
/* The anchor has to clear the sticky header, the same as every other target. */
.galchap{scroll-margin-top:calc(var(--navh) + 22px)}
.galchap-h{display:grid;grid-template-columns:1fr auto;gap:6px clamp(20px,3vw,44px);
  align-items:baseline;margin:0 0 clamp(16px,2vw,26px)}
.galchap-h h3{font-size:clamp(20px,2.4vw,30px);font-weight:900;letter-spacing:-.02em;
  display:flex;align-items:baseline;gap:14px}
/* The chapter heading is the way into that trade's own page. It is a link and
   it should look like the heading it is until a pointer says otherwise. */
.galchap-h h3 a{color:inherit;text-decoration:none}
@media (hover:hover) and (pointer:fine){
  .galchap-h h3 a:hover{color:var(--acc-dim);text-decoration:underline;
    text-underline-offset:6px;text-decoration-thickness:2px}
}
.galchap-n{font-size:12px;font-weight:700;color:var(--acc-dim);letter-spacing:.1em}
.galchap-note{margin:0;color:var(--ink-2);font-size:14.5px;max-width:52ch;
  justify-self:end;text-align:right}
@media(max-width:820px){
  .galchap-h{grid-template-columns:1fr}
  .galchap-note{justify-self:start;text-align:left}
}

/* ---- the showcase ----
   One frame large with the rest on a rail beneath it. The viewer is a
   scroll-snap track holding every frame in the stage at full size rather than
   one <img> whose src gets swapped: the swap would take seventy-four
   photographs out of the document, and they are what a crawler indexes. It
   also means a swipe works on a touch screen without a line of script.
   object-fit:contain, not cover. Half this library is portrait phone frames
   and the point of the large view is to inspect one, which a crop defeats. The
   dark ground is what makes the letterboxing read as a mount. */
/* The mount and the rail are the same width — the whole wrap — because a
   filmstrip that runs wider than the frame it feeds reads as a mistake, and
   Quest asked for them flush. The 1000px cap that used to sit here was buying
   something real: half this library is portrait phone frames, and contain in a
   box the full 1280 floats a 450px photograph in 830px of black rather than
   280px of it. That is the trade, made deliberately, and the letterbox is the
   price of it. */
.showcase{position:relative;z-index:3}
.showcase-view{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;
  scroll-behavior:smooth;overscroll-behavior-x:contain;scrollbar-width:none;
  border-radius:var(--r-lg);background:var(--dark);box-shadow:var(--sh-lg)}
.showcase-view::-webkit-scrollbar{display:none}
@media(prefers-reduced-motion:reduce){.showcase-view{scroll-behavior:auto}}
/* Two panels, not a picture with a line of type under it. Most of this library
   is portrait phone frames, and a mount the full width of the wrap left a
   contained portrait sitting in 830px of black. The plate is what that black
   becomes.

   The picture column is sized by the picture: the height is fixed and the
   width is auto, so a portrait takes about 460px of the box and a landscape
   takes about 900px of the same box, and neither is letterboxed sideways.
   Whatever is left over is the plate. Its 280px minimum is what stops a
   panorama from squeezing the type out of existence — past that the picture
   column gives ground instead. */
.shot{flex:0 0 100%;scroll-snap-align:center;margin:0;min-width:0;position:relative;
  display:grid;grid-template-columns:minmax(0,auto) minmax(clamp(300px,26%,420px),1fr)}
/* The height answers to the width as well as to the viewport. A flat 62vh with
   a column that narrows is what put a landscape in 326px of vertical black at
   640: the picture column had 309px to give and the box still wanted 558. The
   vw term is what falls with it.

   58, not 46. The picture column is sized by the picture, so the height is
   what decides how big a frame gets; at 46vw a portrait came out 242px wide on
   a 700px screen with 364px of column going spare. 58 fills it. The cost lands
   on landscapes, which letterbox by up to ~130px between 600 and 1100 — and
   that is the right way round, because 53 of the 75 frames are portrait. */
.shot img{height:clamp(300px,min(74vh,58vw),780px);width:auto;max-width:100%;
  object-fit:contain;background:var(--dark)}
/* ---- the plate, which was the flat half ----
   --dark with three lines on it and nothing else: no light, no material, no
   depth. That is the same fault the voucher stub had, and these are the same
   three layers that fixed it, at the strengths measured there. All of them sit
   behind z-index 2, so nothing lands on the type. */
.shotcap{display:grid;grid-template-rows:auto 1fr auto;gap:16px;
  padding:clamp(22px,2.6vw,40px);margin:0;text-align:left;
  position:relative;overflow:hidden;container-type:inline-size;
  border-left:1px solid rgba(243,239,230,.1)}
/* 1. THE LIGHT, thrown from beyond the right edge and low, with the head of
      the plate pooled in shadow. Not from the picture's side, tempting as that
      is. The frame number is set in --acc-on-dark at the top left and measures
      5.9:1 on this ground against a 4.5:1 bar; lifting the field under it
      would spend that margin, and the shadow at the head buys margin instead.
      It also means the plate is lit from away from the photograph, so the two
      halves read as two surfaces rather than one flat one. */
.shotcap::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:
    radial-gradient(72% 108% at 116% 74%,
      color-mix(in srgb,var(--acc) 16%,transparent) 0%,
      color-mix(in srgb,var(--acc) 5%,transparent) 40%,transparent 70%),
    linear-gradient(180deg,rgba(0,0,0,.34) 0%,rgba(0,0,0,.08) 30%,
      transparent 50%,rgba(0,0,0,.16) 100%)}
/* 2. THE MATERIAL. The raking hatch the voucher stub and the CTA bar carry —
      enough that the surface has a weave under the light and not enough to
      read as a pattern. Masked away from the head for the reason the light is:
      the hatch is white at three percent, which is a lift, and the head is the
      one part of this plate that cannot afford one. */
.shotcap::after{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:repeating-linear-gradient(108deg,
    rgba(243,239,230,.03) 0 1px,transparent 1px 8px);
  -webkit-mask-image:radial-gradient(96% 108% at 104% 66%,#000 0 34%,transparent 82%);
          mask-image:radial-gradient(96% 108% at 104% 66%,#000 0 34%,transparent 82%)}
.shotcap>*{position:relative;z-index:2}
/* 3. THE NUMBER, PRINTED TWICE — once at reading size and once enormous behind
      it, bleeding off the right edge. The eye reads two copies of one number
      at two scales as near and far rather than as a repeat, which is depth for
      the price of one span. Faded out downward so the only thing it can sit
      behind is its own small copy, never the sentence. */
.shotghost{position:absolute;z-index:1;top:-.2em;right:-.08em;
  font-size:clamp(90px,38cqi,300px);font-weight:900;letter-spacing:-.06em;
  line-height:.78;white-space:nowrap;pointer-events:none;user-select:none;
  color:rgba(243,239,230,.045);
  -webkit-mask-image:linear-gradient(172deg,#000 0 36%,transparent 76%);
          mask-image:linear-gradient(172deg,#000 0 36%,transparent 76%)}
.shotn{font-size:13px;font-weight:700;letter-spacing:.16em;color:var(--acc-on-dark)}
.shotn i{font-style:normal;opacity:.4;padding:0 5px}
/* Set large, and sized in cqi — a percentage of the plate, not of the window.
   The plate is what varies: a landscape frame is 888px wide at 1280 and leaves
   the plate on its floor, and 34px type in a 224px column came out at seven
   characters a line. The viewport knew nothing about that; the container does.
   The measure is still 24ch, because the plate is allowed to be wide and the
   line is not.
   The floor is 21px and not the 17px the ratio alone would give: two frames in
   one stage can hold a portrait and a landscape, their plates are 678 and 306
   wide, and a caption that halved in size between one swipe and the next read
   as a fault. 21 to 34 is a range the eye takes as one setting. */
.shott{margin:0;align-self:center;max-width:24ch;color:var(--on-dark);
  font-size:clamp(21px,6.4cqi,34px);line-height:1.32;letter-spacing:-.01em;
  text-wrap:pretty}
.shotstage{font-size:12px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;
  color:var(--on-dark-2)}



/* ---- zoom ----
   The photograph is a link to its own file. With this stylesheet's dialog
   unavailable — script off, or a browser without <dialog> — clicking a frame
   opens the full-resolution image and that IS the zoom. Everything below is
   the better version of the same thing, not the only version of it. */
.shotzoom{display:block;position:relative;min-width:0;line-height:0;
  cursor:zoom-in;align-self:center}
.shotzi{position:absolute;right:14px;bottom:14px;width:42px;height:42px;
  display:grid;place-items:center;border-radius:50%;pointer-events:none;
  background:rgba(20,20,19,.6);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  opacity:0;transition:opacity .22s ease}
.shotzi svg{width:20px;height:20px;fill:none;stroke:var(--on-dark);stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
/* Revealed by the pointer that can use it. On a touch screen there is no hover
   to reveal it with, and a visitor would otherwise have no way of knowing the
   frame opens at all, so there it simply stands. */
@media (hover:hover) and (pointer:fine){
  .shotzoom:hover .shotzi,.shotzoom:focus-visible .shotzi{opacity:1}
}
@media (hover:none){.shotzi{opacity:1}}
@media(prefers-reduced-motion:reduce){.shotzi{transition:none}}

/* A modal dialog stops the page being clicked, not scrolled. */
html.has-zoom{overflow:hidden}
/* A <dialog> is display:none until it is open, and that comes from the UA
   stylesheet — so any author rule that sets display without keying it to
   [open] overrides it and leaves the dialog laid out on the page for good.
   This one did exactly that: a closed, full-viewport black block sat in the
   flow under the gallery, with every z-index:3 section painting up through it,
   and the page below the gallery looked destroyed. The display belongs to the
   open state and to nothing else.

   Sized by inset rather than by 100vw, too. 100vw counts the vertical
   scrollbar, so a full-width element on a scrolling page is a scrollbar wider
   than the viewport; inset:0 on a fixed box is exactly the viewport, which is
   what a modal dialog is positioned as anyway. */
.zoom{position:fixed;inset:0;width:auto;height:auto;max-width:none;max-height:none;
  margin:0;padding:0;border:0;overflow:hidden;
  background:var(--dark);color:var(--on-dark)}
.zoom[open]{display:grid;grid-template-rows:minmax(0,1fr) auto}
.zoom::backdrop{background:rgba(10,10,9,.94)}
/* Flex and not grid, and the reason is a measured one. place-items:center in
   an auto-sized grid leaves the row sized by its content, so max-height:100%
   on the picture resolves against its own height and constrains nothing: a
   1500x1125 frame came out 1384x1038 in an 819px stage and the bottom of it
   was simply cut off. A flex item's percentage max-height resolves against the
   container, which has a definite height from the dialog's own grid row. */
.zoom-stage{overflow:hidden;min-height:0;padding:clamp(10px,2vw,28px);
  display:flex;align-items:center;justify-content:center}
.zoom-stage img{display:block;max-width:100%;max-height:100%;width:auto;height:auto}
/* can-zoom is set by the script and only when there is more picture to show.
   Half this library is 768px wide, and on a large screen the fitted view is
   already every pixel of it; a magnifier that made the photograph smaller
   would be a lie, so it is not offered. */
.zoom-stage.can-zoom img{cursor:zoom-in}
.zoom-stage.is-zoomed{overflow:auto;padding:0;cursor:grab;
  align-items:flex-start;justify-content:flex-start;
  overscroll-behavior:contain;scrollbar-width:none}
.zoom-stage.is-zoomed:active{cursor:grabbing}
.zoom-stage.is-zoomed img{max-width:none;max-height:none;cursor:zoom-out}
.zoom-stage::-webkit-scrollbar{width:0;height:0}
.zoom-bar{display:flex;align-items:center;gap:clamp(14px,2vw,28px);
  padding:clamp(12px,1.4vw,18px) clamp(14px,2.4vw,34px);
  border-top:1px solid rgba(243,239,230,.12);background:var(--dark-2)}
.zoom-cap{margin:0;flex:1;min-width:0;color:var(--on-dark-2);
  font-size:14.5px;line-height:1.45;text-wrap:pretty}
.zoom-x{flex:0 0 auto;min-height:44px;padding:0 20px;cursor:pointer;
  border:1px solid rgba(243,239,230,.24);border-radius:999px;background:transparent;
  color:var(--on-dark);font-size:12px;font-weight:700;letter-spacing:.16em;
  text-transform:uppercase;font-family:'JetBrains Mono',ui-monospace,Consolas,monospace}
.zoom-x:hover{background:rgba(243,239,230,.1)}
@media(max-width:600px){
  .zoom-cap{font-size:13px}
  .zoom-bar{gap:12px;padding:11px 14px}
}
/* ---- the hover lens ----
   A square follows the pointer over the photograph and the plate beside it
   shows that square at native resolution. It is the thing catalogue sites do,
   and the plate is already the panel that pattern needs, so it is drawn there
   rather than floated somewhere new.

   Pointer only, and by omission rather than by a media query: nothing builds
   it unless the script finds a fine pointer that hovers. On a touch screen
   there is no hover to drive it with, and a tap opens the frame full size,
   which is the better answer there anyway. */
.lens{position:absolute;z-index:4;pointer-events:none;border-radius:3px;
  border:1.5px solid rgba(243,239,230,.9);background:rgba(243,239,230,.1);
  box-shadow:0 2px 16px rgba(10,10,9,.5),inset 0 0 0 1px rgba(10,10,9,.4)}
.lenspanel{position:absolute;z-index:5;pointer-events:none;
  border-radius:var(--r);background-repeat:no-repeat;background-color:var(--dark);
  box-shadow:var(--sh-lg),0 0 0 1px rgba(243,239,230,.18)}
/* The plate's own three lines stand down while the panel is over them. They
   are not hidden: at .16 the number and the stage still read as the furniture
   of the plate, so the panel looks like it is sitting on the plate rather than
   replacing it. */
.shotn,.shott,.shotstage,.shotzi{transition:opacity .18s ease}
.shot.lensing .shotn,.shot.lensing .shott,.shot.lensing .shotstage{opacity:.16}
.shot.lensing .shotzi{opacity:0}
@media(prefers-reduced-motion:reduce){
  .shotn,.shott,.shotstage,.shotzi{transition:none}
}


/* The rail. It runs, and it runs the way the marquee under the header runs:
   one set of frames repeated and translated by exactly one set, so the loop
   closes on itself with no seam. --shift and --dur are written by the builder,
   which knows how many copies it laid down and how many frames are in a set.
   The duration is proportional to the set, so a frame crosses the rail at the
   same speed in a five-frame stage and a twenty-five-frame one.

   The spacing is a margin on each frame and not a gap on the track. A gap sits
   between frames and not after the last one, so one set would be a gap short
   of a clean division of the track and the loop would jump by a few pixels
   every time it came round. A margin belongs to the frame and repeats with it.

   A moving picture cannot be clicked, so the whole track holds still under a
   pointer, under the keyboard, and under a finger — :active is in that list
   for touch, where there is no hover to pause with and a finger down says the
   same thing. This is the bargain the strip already makes. */
.showcase-rail{margin-top:clamp(11px,1.3vw,16px);padding:5px 0;overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 56px,
    #000 calc(100% - 56px),transparent);
          mask-image:linear-gradient(90deg,transparent,#000 56px,
    #000 calc(100% - 56px),transparent)}
.showcase-rail::-webkit-scrollbar{display:none}
.showcase-track{display:flex;width:max-content;
  animation:railrun var(--dur,40s) linear infinite}
@keyframes railrun{to{transform:translateX(var(--shift,-50%))}}
.showcase-rail:hover .showcase-track,
.showcase-rail:focus-within .showcase-track,
.showcase-rail:active .showcase-track{animation-play-state:paused}
/* With the animation off the track cannot move at all, and every frame past
   the first screenful would be unreachable. So the rail goes back to being
   what it was before it ran: a scroller, holding one set of frames. */
@media(prefers-reduced-motion:reduce){
  .showcase-rail{overflow-x:auto;scrollbar-width:none;scroll-snap-type:x proximity;
    -webkit-overflow-scrolling:touch}
  .showcase-track{animation:none}
  .showcase-track .thumb[aria-hidden="true"]{display:none}
}
.thumb{flex:0 0 auto;scroll-snap-align:start;width:clamp(118px,13.2vw,190px);aspect-ratio:1;
  margin-right:clamp(11px,1.2vw,16px);
  border-radius:15px;overflow:hidden;background:var(--dark-2);
  outline:2px solid transparent;outline-offset:2px;opacity:.82;
  transition:opacity .25s ease,outline-color .25s ease}
.thumb img{width:100%;height:100%;object-fit:cover}
/* The frame that is up. Not a colour change on the picture — an outline round
   it, because the pictures are already every colour. */
.thumb[aria-current="true"]{opacity:1;outline-color:var(--acc)}
@media (hover:hover) and (pointer:fine){.thumb:hover{opacity:1}}
.thumb:focus-visible{outline-color:var(--acc);opacity:1}
@media(prefers-reduced-motion:reduce){.thumb{transition:none}}

/* Two columns hold a long way down. Measured at 700 wide, stacking put a
   portrait in a 644px box with 320px of black beside it — the exact fault the
   plate exists to remove — while two columns at the same width give the
   picture 364px and the plate its 280px floor.

   600 is where it stops paying, and the number is derived rather than picked:
   the plate's floor is 280px, so the picture only out-measures the plate while
   the wrap is over 560, which is a viewport of about 605. Below that the text
   panel would be the larger half of a photograph's own showcase, and stacking
   is the better answer. */
@media(max-width:600px){
  /* Everything on the plate stays when it goes back under the picture — the
     number, the sentence, the stage. None of it was there to fill space. */
  .shot{grid-template-columns:minmax(0,1fr)}
  /* One box has to hold both orientations here and cannot suit both, so it is
     shaped for the one the library is mostly made of. At 4:5 a portrait loses
     20px of black and a landscape loses 158; square would split that evenly
     and make the common case worse. */
  .shot img{width:100%;height:auto;aspect-ratio:4/5}
  .shotcap{grid-template-rows:auto auto auto;gap:10px;
    border-left:0;border-top:1px solid rgba(243,239,230,.1)}
  /* The stacked plate is a 130px strip, and the ghost is taller than it is —
     measured, its painted band reached down into the sentence, which is the
     one thing the fade exists to stop. There is no room for depth theatre on
     a strip; the light and the hatch stay, the numeral goes. */
  .shotghost{display:none}
  .shott{max-width:none;align-self:start;font-size:clamp(16px,4.4cqi,21px)}
}

@media(max-width:760px){
  .galchap-h h3{gap:10px}
  .galnav{gap:7px}
  .galnav a{padding:0 13px;font-size:12.5px}
}

/* ---- the display band ----
   One word set as large as the line will carry, the photographs lifted over
   its foot so the type runs behind them, and the sentence justified edge to
   edge underneath as a rule rather than a paragraph.

   --len is the word's character count, printed on the section. 118vw over it
   is the size that fills the measure whatever the word turns out to be, which
   is what a fixed clamp cannot do: it either wraps "FRAMING TO FINISH" or
   wastes half the line on "DETAIL SHOTS". */
/* The word is sized to fill the band, so it has to be measured against the
   band and not against the window. It was `118vw / --len`, which tracks the
   viewport past the point where the wrap stops growing: above about 1800px the
   wrap is still 1280 and the word kept widening, so on a large monitor
   "DETAIL SHOTS" ran 1374px inside a 1280px box and overflow:hidden took a
   letter off each end. The 38px floor did the same thing at the other end —
   below about 420px it overrode the fit and "FRAMING TO FINISH" came out 379px
   wide in a 276px band.
   min(100vw,--maxw) less the two page margins IS the band's width, and 1.327
   is the old constant re-expressed against it: at 1400, where the two agree,
   the size is unchanged to within a tenth of a pixel, and everywhere else it
   is within 3.5% of what it was. The floor is the smallest this reads at as a
   ghost behind the tiles, and now it only engages under about 330px. */
.bigband{--big:clamp(24px,
  calc(1.327 * (min(100vw, var(--maxw)) - 2 * var(--m)) / var(--len,12)),190px);
  overflow:hidden}
.bigband .eyebrow{color:var(--acc-on-dark);margin:0 0 clamp(10px,1.4vw,18px);
  position:relative;z-index:3}
.bigband h2.bigword{position:relative;z-index:1;margin:0;text-align:center;white-space:nowrap;
  font-size:var(--big);font-weight:900;letter-spacing:-.055em;line-height:.82;
  color:color-mix(in srgb,var(--on-dark) 17%,transparent);
  pointer-events:none;user-select:none}
/* the overlap — a third of the word's height, so the tiles cut its baseline */
.bigband .shotband{position:relative;z-index:3;margin-top:calc(var(--big) * -.3)}
.justrow{margin:var(--sp-2) 0 0;text-align:justify;text-align-last:justify;
  font-size:10.5px;letter-spacing:.09em;color:var(--mute)}
@media(max-width:620px){
  /* Nothing to run behind at this width — the tiles are two-up and the word
     would be a stripe of grey behind nothing. It sits above them instead. */
  .bigband .shotband{margin-top:var(--sp-1)}
  .justrow{text-align:left;text-align-last:left}
}

/* The in-page photograph band. Square tiles: the library runs both
   orientations, and a square takes the same bite out of either. The column
   count is declared per band rather than auto-fitted, so a four-shot band
   fills the row instead of leaving a dead track on the right. */
.shotband{position:relative;z-index:3;display:grid;gap:clamp(12px,1.6vw,18px);
  grid-template-columns:repeat(3,1fr)}
.shotband.n4,.shotband.n8{grid-template-columns:repeat(4,1fr)}
/* Two tiles, for the pergola section. The library holds exactly two
   post-and-beam photographs, so a third cell would have to be filled with a
   picture of something else. They sit under the split rather than beside it,
   because a square tile in a half-width column is a thumbnail. */
.shotband.n2{grid-template-columns:repeat(2,1fr)}
.pergola-shots{margin-top:clamp(32px,4.4vw,56px)}
.shotband figure{margin:0;border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--sh);
  background:var(--dark-2)}
.shotband img{display:block;width:100%;height:100%;object-fit:cover;aspect-ratio:1;
  transition:transform .8s cubic-bezier(.2,.8,.2,1)}
@media (hover:hover) and (pointer:fine){.shotband figure:hover img{transform:scale(1.06)}}
@media(max-width:820px){
  .shotband.n4,.shotband.n6,.shotband.n8{grid-template-columns:repeat(2,1fr)}}
@media(max-width:520px){
  .shotband,.shotband.n3,.shotband.n4,.shotband.n6,.shotband.n8{
    grid-template-columns:1fr 1fr;gap:10px}}
.note{opacity:.5;margin-top:26px;font-size:10.5px;letter-spacing:.12em}

.pjs{position:relative;z-index:3;display:grid;gap:clamp(18px,2.4vw,30px)}
.pjcard{display:grid;grid-template-columns:1.1fr .9fr;background:#fff;
  border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--sh-lg)}
.pjcard:nth-child(even){grid-template-columns:.9fr 1.1fr}
.pjcard:nth-child(even) .pjshot{order:2}
.pjshot{overflow:hidden}
.pjshot img{width:100%;height:100%;object-fit:cover;aspect-ratio:4/3}
.pjbody{padding:var(--sp-2);display:flex;flex-direction:column;justify-content:center}
.pjbody .n{color:var(--acc-dim);display:block;margin-bottom:16px}
.pjbody h3{font-size:clamp(22px,2.8vw,36px);font-weight:900;margin-bottom:14px}
.pjbody p{margin:0;color:var(--ink-2);font-size:clamp(14.5px,1.4vw,16.5px)}
@media(max-width:820px){
  .pjcard,.pjcard:nth-child(even){grid-template-columns:1fr}
  .pjcard:nth-child(even) .pjshot{order:0}
}

/* ---------- the contact plates ----------
   Two columns that used to be align-items:start, which let the form run some
   two hundred pixels past the foot of the aside and left a hole in the cream
   beside it. They stretch now, and the photograph at the foot of the aside is
   the flexible part: whatever height the form comes out, the picture takes up
   the difference. Nothing is measured, and nothing has to be kept in step by
   hand when a field is added. */
.contact-grid{position:relative;z-index:3;display:grid;grid-template-columns:1.3fr .8fr;
  gap:clamp(20px,3vw,34px);align-items:stretch}
.contact-form{background:#fff;border-radius:var(--r-lg);padding:var(--sp-2);
  box-shadow:var(--sh-lg);display:grid;gap:clamp(20px,2.4vw,30px);align-content:start}
.cf-head h2{font-size:clamp(24px,3vw,36px);font-weight:900;margin:0 0 10px}
.cf-head p{margin:0;color:var(--ink-2);font-size:15px;max-width:46ch}
.cf-fields{display:grid;gap:18px}
/* Email and phone are one question asked twice. */
.cf-pair{display:grid;grid-template-columns:1fr 1fr;gap:18px}

/* ---- one field ----
   The label row is a name on the left and a Required/Optional word on the
   right, both in the drawing-label register the rest of the direction uses for
   annotation. A word rather than a red asterisk: an asterisk is a convention
   the visitor has to already know, and a colour carries nothing to a visitor
   who cannot see it. */
.fld{display:grid;gap:7px;min-width:0;align-content:start}
.fld label{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.fld-name{font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink-2)}
.fld-flag{font-size:10.5px;letter-spacing:.13em;color:var(--mute-lt);flex:0 0 auto}
/* The controls used to be 16px-radius cream slabs on a white plate, which is
   the shape and the fill of a button — the form read as a stack of five things
   to press. Squarer, inset by a hairline and a top shadow, they read as
   something to type into instead, and the one pill on the plate is the one
   thing that submits it.
   16px type, not 15px. Safari on iOS zooms the whole page in when a field
   under 16px takes focus, and it does not zoom back out — the visitor is left
   on a form that scrolls sideways, halfway through typing. */
.fld input,.fld textarea{font:inherit;font-size:16px;padding:13px 15px;width:100%;
  border:1px solid rgba(21,20,15,.18);border-radius:9px;background:var(--cream);
  color:var(--ink);min-height:48px;
  box-shadow:inset 0 1px 2px rgba(21,20,15,.05);
  transition:border-color .18s ease,background .18s ease,box-shadow .18s ease}
.fld textarea{resize:vertical;min-height:132px;line-height:1.55}
.fld input::placeholder,.fld textarea::placeholder{color:var(--mute-lt);opacity:1}
.fld input:hover,.fld textarea:hover{border-color:rgba(21,20,15,.32)}
.fld input:focus,.fld textarea:focus{outline:2px solid var(--acc);outline-offset:1px;
  border-color:transparent;background:#fff;box-shadow:none}
/* Standing help, not a placeholder: a placeholder is gone by the time the
   visitor is typing, which is exactly when the hint is wanted. */
.fld-help{margin:0;font-size:12.5px;line-height:1.5;color:var(--mute-lt)}
.fld-err{margin:0;color:var(--bad);font-size:12px;font-weight:500;letter-spacing:.01em;
  text-transform:none;line-height:1.55}
.fld-err:empty{display:none}
/* The failed state is a border weight, a wash, and a word — three signals, so
   it survives being unable to see the colour. */
.fld.bad input,.fld.bad textarea{border-color:var(--bad);border-width:1.5px;
  background:color-mix(in srgb,var(--bad) 5%,#fff)}
.fld.bad .fld-name{color:var(--bad)}
.fld.bad .fld-err::before{content:"! ";font-weight:700}

.cf-foot{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px}
.cf-foot .btn{flex:0 0 auto}
/* The form is one of two ways to reach a contractor and the slower one. The
   other sits right beside the button rather than only in the aside. */
.cf-alt{color:var(--mute-lt);text-transform:none;letter-spacing:.02em;font-size:12px}
.cf-alt a{color:var(--acc-dim);font-weight:700;text-decoration:underline;
  text-underline-offset:3px;display:inline-flex;align-items:center;min-height:24px}
/* The honeypot. Off the page rather than display:none — some bots skip the
   fields they can tell are hidden, and this one wants to be filled in. */
.cf-honey{position:absolute;left:-9999px;width:1px;height:1px;opacity:0;
  pointer-events:none}
/* While the post is in flight. The button stays put and stops taking clicks,
   so a second tap cannot send the same enquiry twice. */
.cf-foot .btn:disabled{opacity:.55;cursor:progress}
.form-note{margin:0;color:var(--acc-dim);letter-spacing:.02em;text-transform:none;
  font-size:12.5px;font-weight:500;line-height:1.6}
.form-note:empty{display:none}
.form-note.bad{color:var(--bad)}
.form-note a{color:inherit;font-weight:700;text-decoration:underline;
  text-underline-offset:3px}

/* ---- the aside ---- */
.help-card{background:var(--dark);color:var(--on-dark);border-radius:var(--r-lg);
  padding:var(--sp-2);box-shadow:var(--sh-lg);overflow:hidden;
  display:flex;flex-direction:column;gap:26px}
.help-card h2{font-size:clamp(21px,2.4vw,28px);font-weight:900;margin-bottom:18px}
.help-card .eyebrow{color:var(--acc-on-dark);margin-bottom:8px}
.help-card .phone{display:block;font-size:clamp(25px,3.1vw,36px);
  color:var(--acc);margin-bottom:18px}
.help-card p{color:var(--on-dark-2);font-size:14.5px;margin:0}
/* Three facts, all of them already stated elsewhere on the site. They are here
   so the card has something to say while a visitor decides whether to call. */
.help-facts{list-style:none;margin:20px 0 0;padding:0;display:grid;gap:10px;
  font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--on-dark-2)}
.help-facts li{padding-left:16px;position:relative;line-height:1.5}
.help-facts li::before{content:"";position:absolute;left:0;top:.45em;
  width:6px;height:6px;background:var(--acc)}
/* The flexible part. The form decides the height of the row; the photograph
   takes whatever is left, which is what keeps the two plates level. */
.help-shot{flex:1 1 auto;min-height:200px;border-radius:var(--r);overflow:hidden}
.help-shot img{width:100%;height:100%;object-fit:cover}

@media(max-width:880px){
  .contact-grid{grid-template-columns:1fr}
  /* Nothing left to match heights with, so the picture goes back to its own
     shape rather than stretching to fill a column that is no longer there. */
  .help-shot{min-height:0}
  .help-shot img{height:auto;aspect-ratio:16/10}
}
@media(max-width:520px){
  .cf-pair{grid-template-columns:1fr}
  .cf-foot .btn{width:100%;justify-content:center}
}

.smap{position:relative;z-index:3;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:clamp(26px,4vw,54px)}
.smap h2{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--mute-lt);margin-bottom:18px;font-weight:700}
.smap ul{list-style:none;padding:0;margin:0;display:grid;gap:2px}
.smap a{display:block;padding:10px 0;font-size:15px;font-weight:600;
  border-bottom:1px solid rgba(21,20,15,.08);transition:color .2s,padding .2s}
@media (hover:hover) and (pointer:fine){.smap a:hover{color:var(--acc-dim);padding-left:8px}}

/* link cloud on a dark section */
.arealinks.light a{background:var(--dark-2);color:var(--on-dark);
  border:1px solid rgba(243,239,230,.1);box-shadow:none}
@media (hover:hover) and (pointer:fine){
  .arealinks.light a:hover{background:var(--acc);color:var(--on-acc);border-color:transparent}
}

/* ================================================================
   ACCESSIBILITY AND INPUT (punch-list 02)
   Appended last so the focus rules win over the hover states above.
   Everything here came out of an audit of the built pages: the only
   focus treatment on the site was on the contact inputs, the sticky
   header hid whatever an anchor jumped to, and nothing told a touch
   device that these are taps rather than possible double-tap zooms.
   ================================================================ */

/* ---- one focus ring, three grounds ----
   :focus-visible only, so a mouse press never draws it. The colour
   changes with the ground because one value cannot clear 3:1 against
   cream, near-black AND the orange plane: acc-dim is 5.6 on cream,
   acc is 5.7 on the dark, on-acc is 5.7 on the accent. Ordered so the
   accent-ground rule wins inside .cta, which nests one in the other. */
:focus-visible{outline:3px solid var(--acc-dim);outline-offset:2px}
.sec.dark :focus-visible,footer :focus-visible,.svctabs :focus-visible,
.cta :focus-visible,.localnotes :focus-visible{outline-color:var(--acc)}
.hero :focus-visible,.subhero :focus-visible,.cta-in :focus-visible,
.figs :focus-visible,.svc--acc :focus-visible{outline-color:var(--on-acc)}

/* ---- an anchor jump has to clear the sticky header ----
   Off the same token the header is sized from, so the phone's shorter bar does
   not leave a 32px hole above whatever was jumped to. */
:target,#main,#services,#offers,#work{scroll-margin-top:calc(var(--navh) + 14px)}
/* The skip link's target has to be focusable for the skip to move focus
   rather than only the scroll position. */
#main:focus{outline:none}

/* ---- touch ---- */
body{-webkit-tap-highlight-color:color-mix(in srgb,var(--acc) 18%,transparent)}
a,button,summary,label,input,textarea,select{touch-action:manipulation}

/* ---- headline widows ---- */
h1,h2,h3{text-wrap:balance}
.lede,.prose p,.sc p,.svc p{text-wrap:pretty}

/* ---- long words at display sizes ----
   A trade was once named "Full Remodel (kitchen, bathroomcabinets, flooring,
   counter tops)", and `bathroomcabinets,` set at 30px is 299px of unbreakable
   word. In a grid item — which defaults to min-width:auto, so the column grows
   to its longest word — that pushed the whole closing CTA 33px past the screen
   and `overflow:hidden` cut the heading off mid-word. The name is clean now,
   but the guard stays: it costs nothing and the next long word will not
   announce itself. Both halves are needed — the break lets the word wrap, the
   min-width stops the track expanding. */
h1,h2,h3,h4{overflow-wrap:break-word}
.cta-in>*,.shead>*,.split>*,.prose-wrap>*,.contact-grid>*,.svc,.offer{min-width:0}

/* ---- a 17px-tall link is under the 24px target floor ---- */
.svc .go,.citycard .go{min-height:24px;align-items:center}
/* The breadcrumb trail is 10.5px mono, so its links measured 17px tall. They
   are padded to clear 24px without moving the type off its line. The footer's
   social link used to need the same treatment; it is a 44px chip now. */
.crumbs a,.crumbs b{display:inline-flex;align-items:center;min-height:24px}
.fbar a{display:inline-flex;align-items:center;min-height:24px;padding:4px 0}

/* The desktop nav links measured 23px tall — one pixel under the 24px target
   floor. Padding grows the hit area without moving the type. */
.nav nav a,.nav .drop>button{padding-block:6px}

/* ================================================================
   PHONE (punch-list 03)
   Everything here came out of rendering the built pages at a real
   390px viewport rather than a narrowed desktop window. Kept last
   and kept together so the phone layout is one thing to read.
   ================================================================ */
@media(max-width:620px){
  /* 9.5–10px mono is legible on a desktop at arm's length and not on a
     phone. Nothing here is display type; it is all labelling. */
  .mono,.pill,.figs .f span,.hero-trust div span,.badge span,.svc .go,
  .fg h2,.smap h2,.crumbs,.eyebrow,.localcall span,.citycard .go,
  /* The two grouped-menu headings were written after this list and never
     joined it, so the footer's valley and trade labels stayed at 10px on a
     phone — the exact size the note above says does not work there. */
  .fgrp-h,.dropgrp-h,
  .fbar,.note{font-size:11.5px}
  .strip span{font-size:12px}
  /* The band under the hero scrolls its own text past at 36s; on a narrow
     screen that is a lot of movement for something decorative. */
  .strip-in{animation-duration:52s}
  /* Fourteen chips wrapped to five rows and pushed the page's own heading
     below the fold. One row that scrolls, with the current trade first. */
  /* scroll-padding, or `scroll-snap-align:start` aligns each chip hard
     against the scrollport edge and undoes the gutter the rail was just
     scrolled to — including the one the active trade is placed in. */
  .svctabs .wrap{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;
    scroll-snap-type:x proximity;scroll-padding-inline:14px;
    -webkit-overflow-scrolling:touch}
  .svctabs .wrap::-webkit-scrollbar{display:none}
  .svctabs a{flex:0 0 auto;scroll-snap-align:start;min-height:38px;
    display:inline-flex;align-items:center}
  /* A rail with a hard edge reads as a clipped row. Fading the last few
     millimetres is what says the trades carry on past the screen. */
  .svctabs{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 34px),transparent);
           mask-image:linear-gradient(90deg,#000 calc(100% - 34px),transparent)}
  /* The step numerals are the only thing on their row; full size they
     cost 90px of height each, four times over. */
  .step .n{width:54px;height:54px;border-radius:16px;font-size:18px;margin-bottom:16px}
  /* An arrow chip inside a card caption, at 40px, took a third of the
     line away from the words next to it. */
  .pj .cap .go{width:34px;height:34px;font-size:13px}
}

/* ---------- 10 PAGE-TRANSITION SKELETON ----------
   Ten directions and a static site: every navigation is a full document load,
   and the browser leaves the old page on screen until the new one paints. On
   a fast connection that is invisible. On a slow one it is a click that
   appears to do nothing, and the second click is a second request.

   So: a 450ms delay, then the page below the header becomes its own outline.
   The delay is the whole design — a skeleton that appears and vanishes inside
   a fifth of a second reads as a flicker and costs confidence rather than
   buying it. The header is not covered, because the next page carries the
   same one and leaving it in place is what makes the load read as a
   navigation rather than as a crash. */
.skel{position:fixed;inset:var(--navh) 0 0;z-index:240;background:var(--cream);
  overflow:hidden;opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .18s ease,visibility 0s linear .18s}
.skel.on{opacity:1;visibility:visible;pointer-events:auto;transition-delay:0s}
/* Every inner page opens on a dark hero band over a cream body. The outline
   says that much and no more: guessing at the specific page's layout would be
   wrong more often than right, and a wrong skeleton is a worse promise than a
   vague one. */
.skel-hero{background:var(--dark);padding:clamp(30px,4.5vw,54px) 0 clamp(38px,5.5vw,66px)}
.skel-body{padding:clamp(34px,5vw,56px) 0}
.sk{display:block;border-radius:9px;background-color:rgba(21,20,15,.07);
  background-image:linear-gradient(90deg,transparent,rgba(21,20,15,.05),transparent);
  background-size:260px 100%;background-repeat:no-repeat;
  animation:skelpass 1.15s linear infinite}
.skel-hero .sk{background-color:rgba(243,239,230,.09);
  background-image:linear-gradient(90deg,transparent,rgba(243,239,230,.08),transparent)}
@keyframes skelpass{from{background-position:-260px 0}to{background-position:calc(100% + 260px) 0}}
.sk-crumb{width:180px;height:11px;margin-bottom:26px}
.sk-h1{width:min(560px,72%);height:clamp(30px,4.4vw,48px);margin-bottom:14px;border-radius:11px}
.sk-h1b{width:min(360px,50%)}
.sk-lede{width:min(440px,62%);height:14px;margin-top:24px}
.sk-btn{width:186px;height:52px;border-radius:26px;margin-top:30px}
.sk-eyebrow{width:118px;height:10px;margin-bottom:18px}
.sk-h2{width:min(400px,58%);height:clamp(22px,2.6vw,32px);margin-bottom:28px;border-radius:11px}
.skel-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(14px,1.8vw,24px)}
.sk-card{height:clamp(150px,17vw,205px);border-radius:var(--r)}
@media(max-width:760px){.skel-cards{grid-template-columns:1fr 1fr}}
@media(max-width:520px){.skel-cards{grid-template-columns:1fr}.sk-card:nth-child(3){display:none}}
/* The wait is announced in words rather than mimed in grey boxes. */
.skel-say{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}
/* A shimmer is a decorative animation, and the whole overlay is one long
   moving thing. Reduced motion gets the same outline, holding still. */
@media(prefers-reduced-motion:reduce){
  .sk{animation:none}
  .skel{transition:none}
}

/* ---------- city cards on /service-areas/ ---------- */
.scope--3{grid-template-columns:repeat(3,1fr)}
@media(max-width:1040px){.scope--3{grid-template-columns:repeat(2,1fr)}}
@media(max-width:700px){.scope--3{grid-template-columns:1fr}}
a.sc{display:flex;flex-direction:column;gap:12px;color:inherit;
  transition:translate .26s cubic-bezier(.2,.8,.2,1),box-shadow .26s ease}
a.sc h3{margin:0}
a.sc p{flex:1}
.citycard .go{display:inline-flex;align-items:center;gap:9px;
  font-family:'JetBrains Mono',ui-monospace,Consolas,monospace;font-size:10.5px;
  letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:var(--acc-dim)}
.citycard .go i{font-style:normal;transition:transform .28s ease}
@media (hover:hover) and (pointer:fine){
  a.sc:hover{translate:0 -4px;box-shadow:var(--sh-lg)}
  a.sc:hover .go i{transform:translate(5px,-5px)}
}

/* ---------- the 404 ---------- */
.gone{position:relative;padding:clamp(90px,14vw,190px) 0}
.gone .wrap{position:relative;z-index:3;max-width:760px;text-align:center}
.gone .code{font-family:'JetBrains Mono',ui-monospace,Consolas,monospace;font-size:11px;
  letter-spacing:.18em;text-transform:uppercase;color:var(--acc-dim);margin:0 0 18px;font-weight:700}
.gone h1{font-size:clamp(38px,6vw,78px);font-weight:900;margin-bottom:18px}
.gone p{font-size:clamp(16px,1.5vw,18px);color:var(--ink-2);margin:0 auto 30px;max-width:52ch}
.gone .hero-acts{justify-content:center}
/* 11px mono is a desktop size. The phone block in direction 01's stylesheet
   raises the labelling type, and this page's is the one it cannot see. */
@media(max-width:620px){.gone .code{font-size:11.5px}}
