/* ==========================================================================
   Smithy's Welding Works, v2

   The lane: the steel that holds the building up, and the steel you see every
   day. The page performs that move once, and in v2 it performs it over a
   scroll distance rather than at a boundary.

   THE MOTION SYSTEM IS THE WELD LINE. Every divider and every underline on
   this site is a weld: a bead that draws left to right with a hot amber head
   at the point of travel, cooling to red behind it, settling to a hairline.
   Section dividers, the h1 underline, and the primary button hover. Nowhere
   else, or it stops being his and becomes a gimmick.

   THE ANGLE IS 45 DEGREES, because his mark is a diamond: two brackets
   meeting at 45. Hero seam, comparison handle, card hover wedge, corner cuts.

   COLOUR IS SAMPLED FROM THE REAL LOGO FILE (assets/brand/logo-source.jpeg),
   not estimated. Every text and background pair is computed in
   _tools/contrast.py; all 36 gated pairs pass. Run it before changing a value.

   Radius is 0 everywhere. Everything he makes is cut square and welded.
   ========================================================================== */

/* --------------------------------------------------------------- fonts */
@font-face {
  font-family: 'Chivo';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/chivo/v21/va9I4kzIxd1KFrBoQeNVkqDO.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: 'Chivo Mono';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/chivomono/v11/mFT0WbgRxKvF_Z5eQMO9gx8D1WB4m9w.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;
}
/* Size-adjusted fallbacks so the swap does not shift layout. */
@font-face {
  font-family: 'Chivo Fallback';
  src: local('Helvetica Neue'), local('Arial');
  size-adjust: 101%;
  ascent-override: 92%;
  descent-override: 24%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'Chivo Mono Fallback';
  src: local('Menlo'), local('Courier New');
  size-adjust: 92%;
  ascent-override: 100%;
  descent-override: 26%;
  line-gap-override: 0%;
}

/* --------------------------------------------------------------- tokens */
:root {
  /* ground: the structural half */
  --ground: #2E2F34;
  --ground-deep: #232429;
  --ground-raise: #3D3E43;      /* this IS the logo graphite */
  /* bone: the finished half. Tinted to the brand's own cool hue on purpose.
     A warm cream here is the saturated default and it would have made the
     oxide red look like an accident rather than the primer on his steel. */
  --bone: #EDEEF1;
  --bone-sunk: #E1E3E7;

  --red: #D11C23;               /* the SMITHY'S wordmark */
  --red-deep: #AE1216;          /* the upper bracket of the mark */
  --flame: #F5C32C;             /* the torch flame */

  --on-ground: #F1F2F4;
  --on-ground-soft: #A9AEB5;
  --on-bone: #212328;
  --on-bone-soft: #545A62;

  --edge-dark: rgba(241, 242, 244, .44);
  --edge-light: rgba(33, 35, 40, .46);
  --line-dark: rgba(241, 242, 244, .17);
  --line-dark-soft: rgba(241, 242, 244, .09);
  --line-light: rgba(33, 35, 40, .16);
  --line-light-soft: rgba(33, 35, 40, .08);

  --sans: 'Chivo', 'Chivo Fallback', system-ui, sans-serif;
  --mono: 'Chivo Mono', 'Chivo Mono Fallback', ui-monospace, monospace;

  /* modular scale, ratio 1.25 */
  --t--1: .8125rem;
  --t-0: 1.0625rem;
  --t-1: 1.25rem;
  --t-2: 1.5rem;
  --t-3: clamp(1.55rem, 1.2rem + 1.5vw, 1.95rem);
  --t-4: clamp(1.9rem, 1.4rem + 2.2vw, 2.45rem);
  --t-5: clamp(2.2rem, 1.45rem + 3.3vw, 3rem);
  --t-6: clamp(2.35rem, 1.35rem + 4.1vw, 3.75rem);

  --gut: clamp(1.25rem, 4vw, 2.75rem);
  --bay: clamp(3.75rem, 8vw, 6.75rem);
  --bay-tight: clamp(2.5rem, 5vw, 4rem);
  --wrap: 78rem;
  --head-h: 88px;

  /* the one angle */
  --ang: 45deg;
  --shear: 1.35rem;

  --ease: cubic-bezier(.22, 1, .36, 1);      /* ease-out-quint, no bounce */
  --ease-soft: cubic-bezier(.33, 1, .68, 1);

  --z-sticky: 100;
  --z-header: 200;
  --z-menu: 300;
  --z-lightbox: 400;
}

/* --------------------------------------------------------------- reset */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--head-h) + 16px);
  scroll-padding-bottom: 76px;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ground);
  color: var(--on-ground);
  font-family: var(--sans);
  font-size: var(--t-0);
  font-weight: 400;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }
h1, h2, h3, h4 { margin: 0; font-weight: 900; line-height: 1.05; letter-spacing: -.03em; text-wrap: balance; }
h1 { font-size: var(--t-6); }
h2 { font-size: var(--t-5); }
h3 { font-size: var(--t-3); letter-spacing: -.02em; }
h4 { font-size: var(--t-1); font-weight: 700; letter-spacing: -.01em; }
p { margin: 0 0 1.05em; max-width: 66ch; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }
ul, ol { margin: 0; padding: 0; }
a { color: inherit; }
/* Light type on dark reads lighter, so it gets more room. */
.dark, .dark-deep { line-height: 1.68; }

/* --------------------------------------------------------------- a11y */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--flame); color: var(--on-bone);
  padding: .9rem 1.4rem; font-weight: 700; text-decoration: none;
}
.skip:focus { left: 0; }

:focus-visible { outline: 3px solid var(--flame); outline-offset: 3px; }
.light :focus-visible, .light-sunk :focus-visible, .turn :focus-visible {
  outline-color: var(--red-deep);
}

.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* --------------------------------------------------------------- layout */
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--gut); }
.wrap-wide { width: 100%; max-width: 96rem; margin-inline: auto; padding-inline: var(--gut); }

.band { padding-block: var(--bay); }
.band-tight { padding-block: var(--bay-tight); }

.dark { background: var(--ground); color: var(--on-ground); }
.dark-deep { background: var(--ground-deep); color: var(--on-ground); }
.light { background: var(--bone); color: var(--on-bone); }
.light-sunk { background: var(--bone-sunk); color: var(--on-bone); }

.lede { font-size: var(--t-1); line-height: 1.55; max-width: 48ch; }
.dark .muted, .dark-deep .muted { color: var(--on-ground-soft); }
.light .muted, .light-sunk .muted { color: var(--on-bone-soft); }

/* mono is rationed hard: the phone number and the step numerals, nothing else */
.spec {
  font-family: var(--mono);
  font-size: var(--t--1);
  font-weight: 500;
  letter-spacing: .01em;
  word-spacing: -.34em;
}
.foot .spec { font-size: .9375rem; font-weight: 700; color: var(--on-bone); }

/* ============================================================= THE WELD
   A bead that draws left to right, hot amber at the point of travel, cooling
   to red behind it, settling to a 3px hairline. 620ms, ease-out, once.

   It ENHANCES an already-drawn default: with no JS, with reduced motion, or
   if the observer never fires, the bead is simply there. Nothing on this site
   is revealed by motion, only enhanced by it.
   ========================================================================== */
.weld {
  position: relative;
  height: 3px;
  margin: 0;
  overflow: visible;
  pointer-events: none;
}
.weld::before {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(180deg, var(--flame) 0 1px, var(--red) 1px);
  clip-path: inset(0 0 0 0);
}
.light .weld::before, .light-sunk .weld::before, .turn .weld::before {
  background: linear-gradient(180deg, var(--red) 0 1px, var(--red-deep) 1px);
}
.weld::after {
  content: '';
  position: absolute; top: -5px; bottom: -5px; left: 0; width: 84px;
  transform: translateX(-100%);
  background: radial-gradient(ellipse at 100% 50%,
              var(--flame) 0%, rgba(245, 195, 44, .5) 38%, rgba(245, 195, 44, 0) 72%);
  opacity: 0;
}
html.js .weld::before { clip-path: inset(0 100% 0 0); }
html.js .weld.in::before { animation: weld-draw .62s var(--ease) forwards; }
html.js .weld.in::after { animation: weld-head .62s var(--ease) forwards; }
@keyframes weld-draw { to { clip-path: inset(0 0 0 0); } }
@keyframes weld-head {
  0% { left: 0; opacity: 1; }
  74% { opacity: 1; }
  100% { left: 100%; opacity: 0; }
}
/* a heavier bead where a section edge wants mass rather than a hairline */
.weld-heavy { height: 6px; }
.weld-heavy::before { background: linear-gradient(180deg, var(--flame) 0 1px, var(--red-deep) 1px); }

/* The underline under a page's h1. ONE width, ONE per page. The first pass
   put this under h2s as well and inside two panels, at three different widths,
   which spent the only genuinely non-generic idea in the build until it read
   as decoration rather than as his. */
.h-weld { margin-top: 1.15rem; width: min(100%, 15rem); }

/* --------------------------------------------------------------- header
   ⚠ v3: the chrome is BONE, not graphite.

   Lewis asked for his logo to match his original file. His mark carries
   graphite lettering and a graphite lower diamond, which are near-invisible on
   a graphite header, so v2 shipped a lightened variant of his artwork. He was
   right to push back: the answer is to change the surface, not his mark.

   So the header and the footer sit on bone and carry his artwork in its
   original colours, exactly as his file is meant to be seen. Everything on
   this surface is re-derived for it: nav ink, the current-page cue, the
   burger, the focus ring, the weld bead and the mobile menu. Flame is 1.42:1
   on bone and is therefore banned up here; the interactive accent on this
   surface is the deep red at 6.25:1. */
.head {
  position: sticky; top: 0; z-index: var(--z-header);
  background: var(--bone);
  color: var(--on-bone);
}
/* the bead under the header is on a light surface, so it takes the light-half
   build: bright red over deep red, not flame over red */
.head .weld::before, .foot > .weld::before {
  background: linear-gradient(180deg, var(--red) 0 1px, var(--red-deep) 1px);
}
.head :focus-visible, .foot :focus-visible { outline-color: var(--red-deep); }
.head-in {
  display: flex; align-items: center; gap: 1.25rem;
  height: var(--head-h);
  max-width: 96rem; margin-inline: auto; padding-inline: var(--gut);
}
/* THE LOGO IS THE HEADER. There is no separate text lockup: the artwork
   already contains the words, and v1 said the name twice. */
.brand {
  display: flex; align-items: center; min-height: 44px;
  margin-right: auto; text-decoration: none; line-height: 0;
}
.brand img { width: auto; height: 56px; }
.head .weld { position: absolute; left: 0; right: 0; bottom: 0; }

.nav { display: flex; align-items: center; gap: clamp(.5rem, 1.3vw, 1.3rem); list-style: none; }
.nav a {
  display: block; padding: .55rem .1rem;
  font-size: .9375rem; font-weight: 500; text-decoration: none;
  color: var(--on-bone);
  border-bottom: 2px solid transparent;
  transition: color .2s var(--ease), border-color .2s var(--ease);
}
.nav a:hover { color: var(--red-deep); }
.nav a[aria-current="page"] { border-bottom-color: var(--red-deep); color: var(--red-deep); }

.head-call {
  display: inline-flex; align-items: center; gap: .5rem;
  min-height: 46px; padding: 0 1.05rem;
  background: var(--red); color: #fff;
  font-weight: 700; font-size: .9375rem; text-decoration: none;
  white-space: nowrap;
  transition: background .2s var(--ease), transform .12s var(--ease);
}
.head-call:hover { background: var(--red-deep); }
.head-call:active { transform: scale(.985); }

.burger {
  display: none; align-items: center; justify-content: center;
  width: 46px; height: 46px; padding: 0;
  background: none; border: 1px solid var(--edge-light); color: var(--on-bone);
  cursor: pointer;
}
.burger span { display: block; width: 20px; height: 2px; background: currentColor; position: relative; }
.burger span::before, .burger span::after {
  content: ''; position: absolute; left: 0; width: 20px; height: 2px; background: currentColor;
  transition: transform .28s var(--ease), top .28s var(--ease);
}
.burger span::before { top: -6px; }
.burger span::after { top: 6px; }
.burger[aria-expanded="true"] span { background: transparent; }
.burger[aria-expanded="true"] span::before { top: 0; transform: rotate(45deg); }
.burger[aria-expanded="true"] span::after { top: 0; transform: rotate(-45deg); }

@media (max-width: 1120px) {
  .burger { display: inline-flex; }
  .nav {
    position: fixed; inset: var(--head-h) 0 auto 0; z-index: var(--z-menu);
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--bone);
    border-bottom: 3px solid var(--red);
    padding: .5rem var(--gut) 1.5rem;
    display: none;
    max-height: calc(100dvh - var(--head-h));
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .nav.open { display: flex; }
  .nav a {
    padding: .95rem 0; font-size: 1.0625rem; font-weight: 700;
    border-bottom: 1px solid var(--line-light-soft);
  }
  .nav a[aria-current="page"] {
    border-bottom-color: var(--red-deep);
    /* a shape, not just a hue */
    border-left: 3px solid var(--red-deep);
    padding-left: .65rem;
    text-decoration: underline;
    text-underline-offset: .25em;
  }
}
@media (max-width: 780px) {
  :root { --head-h: 74px; }
  .brand img { height: 44px; }
}
.head-call .label-short { display: none; }
@media (max-width: 460px) {
  .head-call .label { display: none; }
  .head-call .label-short { display: inline; }
  .head-call { padding: 0 .9rem; }
  .brand img { height: 40px; }
}

/* --------------------------------------------------------------- buttons */
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  min-height: 52px; padding: 0 1.5rem;
  font-family: var(--sans); font-size: 1.0625rem; font-weight: 700;
  text-decoration: none; white-space: nowrap; cursor: pointer;
  border: 2px solid transparent;
  transition: background .2s var(--ease), color .2s var(--ease),
              border-color .2s var(--ease), transform .12s var(--ease);
}
.btn:active { transform: scale(.985); }
.btn-primary { background: var(--red); color: #fff; border-color: var(--red); }
.btn-primary:hover { background: var(--red-deep); border-color: var(--red-deep); }
/* the weld runs along the button edge on hover: the third and last place it
   is allowed to appear */
.btn-primary::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: linear-gradient(90deg, var(--flame) 0 100%);
  clip-path: inset(0 100% 0 0);
  transition: clip-path .32s var(--ease);
}
.btn-primary:hover::after, .btn-primary:focus-visible::after { clip-path: inset(0 0 0 0); }

.btn-ghost { color: var(--on-ground); border-color: var(--edge-dark); }
.btn-ghost:hover { border-color: var(--flame); color: var(--flame); }
.light .btn-ghost, .light-sunk .btn-ghost, .turn .btn-ghost {
  color: var(--on-bone); border-color: var(--edge-light);
}
.light .btn-ghost:hover, .light-sunk .btn-ghost:hover, .turn .btn-ghost:hover {
  border-color: var(--red-deep); color: var(--red-deep);
}
.btns { display: flex; flex-wrap: wrap; gap: .75rem; }
/* On a phone a 236px button in a 358px column reads as optional. */
@media (max-width: 480px) { .btns > .btn { flex: 1 1 100%; } }

a.tl {
  font-weight: 700; text-decoration: underline;
  text-underline-offset: .22em; text-decoration-thickness: 1px;
  transition: text-decoration-thickness .18s var(--ease);
}
.dark a.tl, .dark-deep a.tl { color: var(--flame); }
.light a.tl, .light-sunk a.tl, .turn a.tl { color: var(--red-deep); }
a.tl:hover { text-decoration-thickness: 3px; }
p > a.tl:only-child { display: inline-block; padding-block: .7rem; }

/* ==================================================== HERO: THE SWIPE-THROUGH
   ⚠ v3, 8 August. Lewis: "I'm not completely sold on the split photo on the
   main page, would it be possible to have a swipe through of maybe 3-5 photos
   instead?"

   So the 45 degree seam retires and four photographs take the slot. They
   alternate finish, raw, finish, raw, which is the same two-halves argument
   the seam was making, told by sequence instead of by a cut.

   Built on native scroll-snap, so touch swipe and momentum are the browser's
   own and cost nothing. Arrows and dots are inserted by site.js, never emitted
   in the HTML, so they cannot appear as controls that do nothing. With JS off
   the track is still a focusable scroll region: slide one fills the slot and a
   keyboard user can move through the rest.

   No autoplay. He asked for a swipe-through: it moves when a person moves it,
   which is also why there is no reduced-motion carousel problem to solve.
   ========================================================================== */
.hero { position: relative; background: var(--ground-deep); }
.hero-gallery { position: relative; }
.hero-track {
  --hero-h: clamp(230px, 34vw, 440px);
  display: flex;
  height: var(--hero-h);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.hero-track::-webkit-scrollbar { display: none; }
/* The track's focus ring lands on a photograph, where flame alone can fall
   under 3:1 against a bright frame. A dark inset ring backs it, so one of the
   two always reads whatever is behind it. The arrows and dots do not need this
   because they already sit on their own dark chips. */
.hero-track:focus-visible {
  outline: 3px solid var(--flame);
  outline-offset: -6px;
  box-shadow: inset 0 0 0 3px rgba(23, 24, 27, .85);
}
/* While the pointer is down the snap is off so the photographs follow the
   hand; it comes back on release and settles to the nearest slide. */
.hero-track.dragging { scroll-snap-type: none; scroll-behavior: auto; cursor: grabbing; }
.hero-slide {
  flex: 0 0 100%;
  height: 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  position: relative;
}
.hero-slide img {
  width: 100%; height: 100%; object-fit: cover; object-position: center 50%;
  /* the pointer has to land on the slide, not on a native image drag */
  user-select: none; -webkit-user-drag: none; pointer-events: none;
}

/* ---- controls, all inserted by site.js ---- */
.hero-arrow {
  position: absolute; top: 50%; margin-top: -24px;
  width: 48px; height: 48px; padding: 0;
  display: grid; place-items: center;
  background: rgba(23, 24, 27, .82); color: #fff;
  border: 0; cursor: pointer;
  transition: background .2s var(--ease);
}
.hero-arrow:hover { background: var(--red); }
.hero-arrow[disabled] { opacity: .45; cursor: default; }
.hero-arrow[disabled]:hover { background: rgba(23, 24, 27, .82); }
.hero-prev { left: clamp(.5rem, 1.5vw, 1rem); }
.hero-next { right: clamp(.5rem, 1.5vw, 1rem); }
.hero-dots {
  position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  display: flex; align-items: center;
  background: rgba(23, 24, 27, .62);
}
.hero-dots button {
  width: 44px; height: 44px; padding: 0;
  background: none; border: 0; cursor: pointer;
  display: grid; place-items: center;
}
.hero-dots button::before {
  content: ''; display: block;
  width: 9px; height: 9px;
  background: rgba(255, 255, 255, .55);
  transition: width .25s var(--ease), background .25s var(--ease);
}
.hero-dots button:hover::before { background: #fff; }
/* the current slide is marked by SHAPE as well as colour: a bar, not a dot */
.hero-dots button[aria-current="true"]::before { width: 24px; background: var(--red); }
.hero-arrow:focus-visible, .hero-dots button:focus-visible {
  outline: 3px solid var(--flame); outline-offset: -3px;
}
html:not(.is-swipe) .hero-arrow, html:not(.is-swipe) .hero-dots { display: none; }

/* the beam that carries the words. Solid, so the type never sits on a photo. */
.hero-beam { position: relative; background: var(--ground); }
.hero-beam > .weld-heavy { position: absolute; top: 0; left: 0; right: 0; }
.hero-in { padding-block: clamp(1.6rem, 3.4vw, 2.9rem); }
.hero-beam h1 { max-width: 17ch; }
.hero-foot { margin-top: clamp(1.2rem, 2.4vw, 1.7rem); }
.hero-beam .lede { margin: 0 0 1.4rem; }
@media (min-width: 900px) {
  .hero-foot {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    align-items: end; gap: clamp(1.5rem, 4vw, 3.5rem);
  }
  .hero-beam .lede { margin: 0; }
  .hero-beam h1 { max-width: 20ch; }
}
@media (max-width: 780px) {
  .hero-beam h1 { font-size: clamp(1.8rem, 7.2vw, 2.45rem); }
  .hero-beam .lede { font-size: 1.0625rem; }
  .hero-track { --hero-h: clamp(215px, 56vw, 290px); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-track { scroll-behavior: auto; }
  .hero-dots button::before { transition: none; }
}
/* the whole hero is above the fold, so nothing in it is hidden and then
   revealed: it renders, then the weld draws once the page has loaded */

/* ---------------------------------------------------- the two halves panel */
.services { display: grid; gap: 3px; background: var(--line-dark-soft); }
@media (min-width: 940px) {
  .services { grid-template-columns: repeat(3, 1fr); }
  .services > .half:first-child {
    grid-column: 1 / -1;
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  /* Bound this photograph. height:100% on an auto-height parent resolves to
     auto, so it rendered at its natural 900px and left ~600px of empty panel
     beside it: the same dead-space defect Cameron flagged in v1's hero. */
  .services > .half:first-child .half-img img { height: clamp(320px, 31vw, 460px); }
  .services > .half:first-child .half-body { justify-content: center; }
  /* margin-top:auto would still shove this to the bottom of a 460px panel */
  .services > .half:first-child .half-more { margin-top: 1.75rem; padding-top: 0; }
  /* the wedge is a proportion of the panel, not a fixed 56px on a 460px one */
  .services > .half:first-child:hover .half-img::after,
  .services > .half:first-child:focus-within .half-img::after {
    border-left-width: 88px; border-top-width: 88px;
  }
}
@media (min-width: 620px) and (max-width: 939px) {
  .services { grid-template-columns: 1fr 1fr; }
  .services > .half:first-child { grid-column: 1 / -1; }
}
.half {
  position: relative; display: flex; flex-direction: column;
  background: var(--ground-deep); color: inherit;
  overflow: hidden;
}
.half h3 a {
  display: block; text-decoration: none; color: inherit;
  /* the whole card is the hit area, but the anchor itself has to clear
     44px too or a screen-reader user tabbing to it gets a 26px target */
  padding-block: .62rem; margin-block: -.62rem;
}
.half h3 a::after { content: ""; position: absolute; inset: 0; }
.half:has(h3 a:focus-visible) { outline: 3px solid var(--flame); outline-offset: -3px; }
.half-img { position: relative; overflow: hidden; }
.half-img img {
  width: 100%; height: clamp(210px, 30vw, 330px); object-fit: cover;
  transition: transform .7s var(--ease);
}
/* the 45 degree wedge: the one hover move, taken from the diamond */
.half-img::after {
  content: ''; position: absolute; left: 0; bottom: 0;
  width: 0; height: 0;
  border-left: 0 solid var(--red);
  border-top: 0 solid transparent;
  transition: border-width .34s var(--ease);
}
.half:hover .half-img img, .half:focus-within .half-img img { transform: scale(1.03); }
.half:hover .half-img::after, .half:focus-within .half-img::after {
  border-left-width: 56px; border-top-width: 56px;
}
.half-body { padding: clamp(1.5rem, 3vw, 2.4rem); flex: 1; display: flex; flex-direction: column; }
.half-body h3 { margin-bottom: .75rem; }
.half-more {
  margin-top: auto; padding-top: 1.5rem;
  font-weight: 700; color: var(--flame);
  display: inline-flex; align-items: center; gap: .5rem;
}
.half-more svg { transition: transform .3s var(--ease); }
.half:hover .half-more svg, .half:focus-within .half-more svg { transform: translateX(5px); }
.plain { font-size: 1.0625rem; }
.half .plain { margin-bottom: .8rem; }
.half .named { color: var(--on-ground-soft); font-size: .9375rem; }

/* A wide band's heading gets its action on the same row. Without it the intro
   sits in the left 40 percent and the rest of the row is empty graphite, which
   is the dead-space defect from v1's hero showing up again one section down. */
.bay-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: .5rem 2rem;
}
.bay-head > a { white-space: nowrap; display: inline-block; padding-block: .7rem; }
/* must be allowed to shrink and wrap internally, or two buttons force a
   470px row and the page overflows at 390 */
.bay-head .btns { min-width: 0; }
.bay-head > div:first-child { min-width: 0; }
.lede-wide { max-width: 62ch; }

/* --------------------------------------------------------------- job grid */
.jobs { display: grid; gap: clamp(1rem, 2vw, 1.6rem); grid-template-columns: 1fr; }
@media (min-width: 480px) { .jobs { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 760px) { .jobs { grid-template-columns: repeat(3, 1fr); } }
.job { position: relative; margin: 0; align-self: start; }
.job-frame { position: relative; overflow: hidden; background: var(--ground-deep); }
.job-frame img {
  width: 100%; height: 100%; object-fit: cover;
  aspect-ratio: var(--ar, 4 / 5);
  transition: transform .7s var(--ease);
}
.job-frame::after {
  content: ''; position: absolute; left: 0; bottom: 0; width: 0; height: 0;
  border-left: 0 solid var(--red); border-top: 0 solid transparent;
  transition: border-width .34s var(--ease);
}
.job:hover .job-frame img, .job:focus-within .job-frame img { transform: scale(1.03); }
.job:hover .job-frame::after, .job:focus-within .job-frame::after {
  border-left-width: 44px; border-top-width: 44px;
}
.job figcaption { padding-top: .8rem; font-size: .9375rem; line-height: 1.45; }
.dark .job figcaption, .dark-deep .job figcaption { color: var(--on-ground-soft); }
.light .job figcaption, .light-sunk .job figcaption { color: var(--on-bone-soft); }

@media (min-width: 780px) {
  .jobs-featured { grid-template-columns: repeat(3, 1fr); }
  .jobs-featured .job:first-child { grid-column: span 2; grid-row: span 2; align-self: stretch; }
  .jobs-featured .job:first-child .job-frame { height: 100%; }
  .jobs-featured .job:first-child .job-frame img { aspect-ratio: auto; height: 100%; }
}

/* ============================================ THE TURN: raw becomes finished
   The one place the page changes ground, and it changes it over a scroll
   distance rather than at a boundary. --t is driven by how far through this
   section you have scrolled. The copy sits on a graphite plate whose colour
   never moves, so no text ever crosses a mid-crossfade background.
   Without JS --t stays at 1 and the section is simply bone.
   ========================================================================== */
.turn {
  --t: 1;
  background: color-mix(in oklab, var(--bone) calc(var(--t) * 100%), var(--ground-deep));
  padding-block: var(--bay);
}
@supports not (background: color-mix(in oklab, #fff 50%, #333)) {
  .turn { background: var(--bone); }
}
.turn-in { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); align-items: center; }
@media (min-width: 960px) {
  .turn-in { grid-template-columns: minmax(0, 21rem) minmax(0, 1fr); gap: clamp(2rem, 4vw, 3.5rem); }
}
.plate {
  background: var(--ground-deep); color: var(--on-ground);
  padding: clamp(1.5rem, 3vw, 2.1rem);
  /* the 45 degree corner, cut once */
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 34px), calc(100% - 34px) 100%, 0 100%);
}
.plate h2 { font-size: var(--t-4); }
.plate p { color: var(--on-ground-soft); margin-top: 1rem; font-size: .9375rem; }
.plate .weld { margin: 1.1rem 0; width: min(100%, 15rem); }

/* ------------------------------------------------------- the comparison */
.compare {
  position: relative;
  --x: 50;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--ground-deep);
  touch-action: pan-y;
  cursor: ew-resize;
}
.compare img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}
/* the BEFORE sits on top and is clipped back on a 45 degree seam */
/* aspect-ratio is 3:2, so a 45 degree cut runs two thirds of the width top to
   bottom: a third of the width each side of the handle. */
.compare .c-before {
  clip-path: polygon(0 0, calc(var(--x) * 1% + 33.333%) 0,
                     calc(var(--x) * 1% - 33.333%) 100%, 0 100%);
}
.c-seam {
  position: absolute; top: -20.71%; bottom: -20.71%;
  left: calc(var(--x) * 1%);
  width: 5px; margin-left: -2.5px;
  transform: rotate(var(--ang));
  background: linear-gradient(90deg, var(--flame) 0 1px, var(--red) 1px);
  box-shadow: 0 0 18px rgba(245, 195, 44, .4);
  pointer-events: none;
}
.c-grip {
  position: absolute; top: 50%; left: calc(var(--x) * 1%);
  width: 54px; height: 54px; margin: -27px 0 0 -27px;
  display: grid; place-items: center;
  background: var(--red); color: #fff;
  transform: rotate(45deg);
  box-shadow: 0 6px 24px rgba(20, 20, 22, .45);
  transition: transform .2s var(--ease), background .2s var(--ease);
}
.c-grip svg { transform: rotate(-45deg); }
.compare:hover .c-grip { transform: rotate(45deg) scale(1.06); }
/* The real control. Invisible, covers the pair, drives --x. Native range =
   keyboard, touch, pointer and click-to-position for free, and its own focus
   ring traces the container because it is inset:0.
   ⚠ The colour is red-deep, NOT flame: this box sits on the travelling ground,
   which finishes at bone, and flame on bone is 1.42:1 against a 3:1 floor.
   The shorthand here used to reset the colour that `.turn :focus-visible` had
   deliberately set. */
.c-range {
  position: absolute; inset: 0; z-index: 3;
  width: 100%; height: 100%; margin: 0; padding: 0;
  background: transparent; border: 0;
  appearance: none; -webkit-appearance: none;
  cursor: ew-resize; touch-action: pan-y;
}
.c-range::-webkit-slider-thumb {
  -webkit-appearance: none; width: 56px; height: 100%; background: transparent; cursor: ew-resize;
}
.c-range::-moz-range-thumb { width: 56px; height: 100%; background: transparent; border: 0; cursor: ew-resize; }
.c-range::-moz-range-track { background: transparent; border: 0; }
.c-range:focus-visible { outline: 3px solid var(--red-deep); outline-offset: 3px; }
.c-tag {
  position: absolute; bottom: 0; padding: .5rem .85rem;
  font-size: .8125rem; font-weight: 700; letter-spacing: .04em;
  background: rgba(23, 24, 27, .82); color: #fff;
  pointer-events: none;
}
.c-tag-a { left: 0; }
.c-tag-b { right: 0; }
/* Without JS the handle is inert, so it must not look draggable. Gated on the
   slider actually being wired, NOT on the motion preference: html.js is only
   set when reduced motion is off, so this used to hide the handle from
   reduced-motion users while leaving the slider working underneath. */
.compare:not(.is-wired) { cursor: default; }
.compare:not(.is-wired) .c-grip { display: none; }

/* --------------------------------------------------------- the plain route
   A large share of his enquiries will be people who cannot name the thing
   they want. This is the route for them, and it is a conversion feature. */
.route {
  display: grid; gap: clamp(1.25rem, 3vw, 2.25rem); align-items: center;
  border: 1px solid var(--line-light);
  padding: clamp(1.5rem, 3.5vw, 2.75rem);
  background: var(--bone-sunk);
}
@media (min-width: 860px) { .route { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); } }
.route h2 { font-size: var(--t-4); }
/* the same box on the dark half, for the builder block on the steelwork page */
.route-dark { background: var(--ground-raise); border-color: var(--line-dark); }
.route-dark p { color: var(--on-ground); }
.route .weld { width: min(100%, 15rem); margin: 1rem 0 1.1rem; }

/* ------------------------------------------------------------- prose bays */
.two-col { display: grid; gap: clamp(1.6rem, 4vw, 3.5rem); }
@media (min-width: 860px) { .two-col { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; } }

.split-hero { display: grid; gap: clamp(1.75rem, 4vw, 3.5rem); align-items: center; }
@media (min-width: 900px) { .split-hero { grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); } }
.split-hero figure { margin: 0; }
.split-hero img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }

/* full-bleed plate hero: one photograph, one plinth */
.plate-hero { position: relative; background: var(--ground-deep); overflow: hidden; }
.plate-hero > .par { height: clamp(280px, 42vh, 460px); overflow: hidden; }
/* On a phone this photograph pushed the call button 3px below the fold, which
   is the whole enquiry path, so it gets less of the screen there. */
@media (max-width: 780px) { .plate-hero > .par { height: clamp(190px, 30vh, 300px); } }
.plate-hero img { width: 100%; height: 100%; object-fit: cover; object-position: center 45%; }
.plinth { position: relative; background: var(--ground); padding-block: clamp(1.9rem, 4vw, 3rem); }
.plinth > .weld-heavy { position: absolute; top: 0; left: 0; right: 0; }
.plinth h1 { max-width: 17ch; }
@media (min-width: 1180px) {
  .plinth h1 { max-width: 24ch; }
  .stated h1 { max-width: 21ch; }
  .hero-beam h1 { max-width: 26ch; }
}

/* three-photo band */
.triptych { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; background: var(--line-light); }
.triptych img { width: 100%; height: clamp(230px, 38vh, 420px); object-fit: cover; }
.triptych figure { margin: 0; overflow: hidden; }
@media (max-width: 620px) {
  .triptych { grid-template-columns: 1fr 1fr; }
  .triptych figure:last-child { grid-column: 1 / -1; }
  .triptych img { height: clamp(180px, 26vh, 240px); }
}

/* type-first hero */
.stated h1 { max-width: 15ch; }
.stated-run {
  margin-top: clamp(1.8rem, 4vw, 2.8rem);
  display: grid; gap: 0;
  border-top: 1px solid var(--line-dark);
}
.light .stated-run, .light-sunk .stated-run { border-top-color: var(--line-light); }
.stated-run div {
  padding: 1.05rem 0;
  border-bottom: 1px solid var(--line-dark-soft);
  display: grid; gap: .15rem .5rem;
}
.light .stated-run div, .light-sunk .stated-run div { border-bottom-color: var(--line-light-soft); }
@media (min-width: 720px) {
  .stated-run div { grid-template-columns: 16rem minmax(0, 1fr); align-items: baseline; }
}
.stated-run dt { font-weight: 700; }
.stated-run dd { margin: 0; color: var(--on-ground-soft); font-size: .9375rem; }
.light .stated-run dd, .light-sunk .stated-run dd { color: var(--on-bone-soft); }

/* ---------------------------------------------------------------- steps */
.steps { display: grid; gap: 0; margin-top: clamp(1.75rem, 3.5vw, 2.75rem); }
.steps li { list-style: none; display: grid; gap: .35rem; padding: 1.35rem 0; border-top: 1px solid var(--line-light); }
.steps li:last-child { border-bottom: 1px solid var(--line-light); }
@media (min-width: 720px) and (max-width: 899px) {
  .steps li { grid-template-columns: 3.5rem minmax(0, 1fr); gap: 0 1.5rem; align-items: start; }
}
/* Three across, full width. Stacked in a two-column bay it was the third
   consecutive "prose left, thing right" section before the footer. */
@media (min-width: 900px) {
  .steps { grid-template-columns: repeat(3, 1fr); gap: 0 clamp(1.5rem, 3vw, 2.75rem); }
  .steps li { border-top: 3px solid var(--red-deep); padding-top: 1.1rem; }
  .steps li:last-child { border-bottom: 0; }
  .steps .n { margin-bottom: .35rem; }
}
.steps .n { font-family: var(--mono); font-size: var(--t-2); font-weight: 500; color: var(--red-deep); line-height: 1.1; }
.steps h3 { font-size: var(--t-1); font-weight: 700; letter-spacing: -.01em; }
.steps p { margin: .35rem 0 0; color: var(--on-bone-soft); }

/* ---------------------------------------------------------- work gallery */
.filters { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.75rem; }
.filters button {
  min-height: 44px; padding: 0 1.05rem;
  background: transparent; color: var(--on-ground);
  border: 1px solid var(--edge-dark);
  font-family: var(--sans); font-size: .9375rem; font-weight: 500; cursor: pointer;
  transition: background .2s var(--ease), border-color .2s var(--ease), color .2s var(--ease);
}
.filters button:hover { border-color: var(--flame); color: var(--flame); }
.filters button[aria-pressed="true"] {
  background: var(--red); color: #fff; font-weight: 700;
  border-color: var(--flame);
  box-shadow: inset 0 -3px 0 var(--flame);
}
.gallery {
  display: grid; gap: clamp(.75rem, 1.6vw, 1.25rem);
  grid-template-columns: repeat(auto-fill, minmax(min(48%, 240px), 1fr));
}
.gallery button {
  padding: 0; border: 0; background: var(--ground-deep); cursor: zoom-in;
  display: block; overflow: hidden; width: 100%;
}
.gallery img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  transition: transform .7s var(--ease);
}
.gallery button:hover img { transform: scale(1.04); }
.gallery [hidden] { display: none; }
/* FLIP: tiles travel to their new positions instead of snapping. The class is
   added for exactly one frame by site.js and removed on transitionend. */
/* promoted only while the FLIP is actually running: 52 permanent layers
   for a 460ms effect is not a trade worth making */
.gallery.flip button { transition: transform .46s var(--ease); will-change: transform; }
.gallery.flip button.fade-in { animation: tile-in .38s var(--ease) both; }
@keyframes tile-in { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: none; } }
.gallery-empty { color: var(--on-ground-soft); padding: 2rem 0; }

.lightbox {
  position: fixed; inset: 0; z-index: var(--z-lightbox);
  background: rgba(19, 20, 22, .95);
  border: 0; padding: 0; margin: 0; width: 100%; height: 100%; max-width: none; max-height: none;
  display: none; align-items: center; justify-content: center;
}
.lightbox[open] { display: flex; }
body:has(.lightbox[open]) { overflow: hidden; }
.lightbox::backdrop { background: rgba(19, 20, 22, .95); }
.lightbox-in { padding: clamp(1rem, 4vw, 3rem); text-align: center; max-width: 100%; }
.lightbox img { max-width: min(100%, 1100px); max-height: 76vh; width: auto; margin-inline: auto; }
.lightbox figcaption { color: var(--on-ground-soft); font-size: .9375rem; margin-top: 1rem; max-width: 60ch; margin-inline: auto; }
.lightbox-cta { justify-content: center; margin-top: 1.35rem; }
@media (max-height: 620px) { .lightbox img { max-height: 56vh; } }
@media (max-width: 780px) {
  .lightbox { align-items: flex-start; overflow-y: auto; overscroll-behavior: contain; }
  .lightbox img { max-height: 50vh; }
  .lightbox-in { padding-top: 4.5rem; padding-bottom: 3rem; }
}
.lightbox-close {
  position: absolute; top: 1rem; right: 1rem;
  width: 48px; height: 48px; cursor: pointer;
  background: var(--ground-deep); color: var(--on-ground);
  border: 1px solid var(--edge-dark); font-size: 1.4rem; line-height: 1;
}
.lightbox-close:hover { border-color: var(--flame); color: var(--flame); }

/* -------------------------------------------------------- contact hero */
.bignum {
  display: inline-block;
  margin-top: clamp(1.1rem, 2.5vw, 1.6rem);
  padding-block: .35rem;
  font-family: var(--mono);
  font-size: clamp(1.9rem, 1rem + 4.2vw, 3.4rem);
  font-weight: 500; line-height: 1.05; letter-spacing: -.02em; word-spacing: -.42em;
  color: var(--flame); text-decoration: none;
  border-bottom: 3px solid transparent;
  transition: border-color .2s var(--ease);
}
.bignum:hover { border-bottom-color: var(--flame); }

/* ------------------------------------------------------ contact page
   The form lives IN the hero. On the one page whose whole job is the form it
   used to sit in a section below the fold with the right half of the screen
   empty beside it. */
.contact-top { display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: start; }
@media (min-width: 940px) {
  .contact-top { grid-template-columns: minmax(0, 1fr) minmax(0, 27rem); }
}
.form-card {
  background: var(--bone); color: var(--on-bone);
  padding: clamp(1.4rem, 3vw, 2rem);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 30px), calc(100% - 30px) 100%, 0 100%);
}
.form-card h2 { font-size: var(--t-3); }
.form-card .form { margin-top: 1.4rem; max-width: none; }
.form-card .muted { color: var(--on-bone-soft); }
.form-card .form-note { margin-top: 1.1rem; }
/* This card is light, but it lives inside a .dark section, so the dark-half
   link and focus colours would otherwise apply to it. */
.form-card a.tl, .dark .form-card a.tl { color: var(--red-deep); }
.form-card :focus-visible { outline-color: var(--red-deep); }
.form-card .btn { width: 100%; }

/* ---------------------------------------------------------------- form */
.form { display: grid; gap: 1.1rem; max-width: 34rem; }
.field { display: grid; gap: .4rem; }
.field label { font-weight: 700; font-size: .9375rem; }
.field .req { font-weight: 400; color: var(--on-bone-soft); }
main:focus { outline: none; }
.field input, .field textarea, .field select {
  font-family: var(--sans); font-size: 1.0625rem; color: var(--on-bone);
  background: #fff; border: 1px solid var(--on-bone-soft);
  padding: .8rem .9rem; min-height: 52px; width: 100%; border-radius: 0;
}
.field textarea { min-height: 140px; resize: vertical; }
.field input::placeholder, .field textarea::placeholder { color: var(--on-bone-soft); opacity: 1; }
.field input:focus-visible, .field textarea:focus-visible {
  outline: 3px solid var(--red-deep); outline-offset: 1px; border-color: var(--red-deep);
}
.field .hint { font-size: .875rem; color: var(--on-bone-soft); }
/* The browser's own error bubble disappears after a few seconds and leaves the
   field looking exactly like a valid one. This keeps the error visible. */
.field input:user-invalid, .field textarea:user-invalid {
  border-color: var(--red-deep); border-width: 2px;
}
.field input:user-invalid + .hint, .field textarea:user-invalid + .hint {
  color: var(--red-deep); font-weight: 700;
}
.form-note { font-size: .9375rem; color: var(--on-bone-soft); max-width: 34rem; }

/* ------------------------------------------------------------ contact bay */
.contact-actions { display: grid; gap: .75rem; }
.action {
  display: flex; align-items: center; gap: 1rem;
  padding: 1.15rem 1.25rem; text-decoration: none;
  border: 1px solid var(--edge-dark); color: var(--on-ground);
  min-height: 76px;
  transition: border-color .2s var(--ease), background .2s var(--ease);
}
.action:hover { border-color: var(--flame); background: var(--ground-raise); }
.action b { display: block; font-size: var(--t-1); font-weight: 900; letter-spacing: -.01em; color: var(--on-ground); }
.action span { display: block; font-size: .9375rem; color: var(--on-ground-soft); }
.action svg { flex: none; }
.light .action, .light-sunk .action { border-color: var(--edge-light); color: var(--on-bone); }
.light .action b, .light-sunk .action b { color: var(--on-bone); }
.light .action:hover, .light-sunk .action:hover { border-color: var(--red-deep); background: var(--bone-sunk); }
.light-sunk .action:hover { background: var(--bone); }
.light .action span, .light-sunk .action span { color: var(--on-bone-soft); }

/* The lead action, so the contact bay has ONE primary rather than three equal
   boxes. It has to come AFTER the .light/.light-sunk rules above: .action-lead
   is a single class, so `.light-sunk .action-lead b` and `.light-sunk .action b`
   are the same specificity and the later one wins. Ordered the other way round
   this shipped #212328 text on a red fill at 1.9:1. */
.action-lead { background: var(--red); border-color: var(--red); }
.action-lead:hover { background: var(--red-deep); border-color: var(--flame); }
.light .action-lead, .light-sunk .action-lead { border-color: var(--red); }
.light .action-lead:hover, .light-sunk .action-lead:hover {
  background: var(--red-deep); border-color: var(--flame);
}
.action-lead, .action-lead b, .action-lead span,
.light .action-lead, .light .action-lead b, .light .action-lead span,
.light-sunk .action-lead, .light-sunk .action-lead b,
.light-sunk .action-lead span { color: #fff; }

/* ========================================================= GOOGLE REVIEWS
   The section is absent from the document until the API returns at least one
   real review. There is no empty shell, no skeleton and no "no reviews yet".
   The page is laid out so that nothing is left with a hole when it is absent.
   ========================================================================== */
.reviews[hidden] { display: none; }
.rev-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .6rem 1.25rem; }
.rev-score {
  font-family: var(--mono); font-size: var(--t-4); font-weight: 500;
  color: var(--on-bone); line-height: 1;
  /* a mono full stop takes a whole advance, so 4.9 read as "4 . 9" */
  letter-spacing: -.14em;
}
.rev-src { display: inline-flex; align-items: center; gap: .5rem; font-size: .9375rem; color: var(--on-bone-soft); }
.rev-list {
  list-style: none;
  display: grid; grid-auto-flow: column;
  grid-auto-columns: min(86%, 400px);
  gap: clamp(1rem, 2vw, 1.4rem);
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 2px;
  padding: 2px 2px 6px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.rev-list::-webkit-scrollbar { display: none; }
.rev-list:focus-visible { outline: 3px solid var(--flame); outline-offset: 3px; }
.rev-list.dragging { scroll-snap-type: none; scroll-behavior: auto; cursor: grabbing; user-select: none; }
.rev {
  position: relative;
  scroll-snap-align: start;
  background: var(--ground-raise); color: var(--on-ground);
  padding: clamp(1.4rem, 2.6vw, 1.85rem);
  display: flex; flex-direction: column; gap: .85rem;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 22px), calc(100% - 22px) 100%, 0 100%);
}
/* the forge quote mark: decorative, oversized, sits behind the card top */
.rev::before {
  content: '\201C';
  position: absolute; top: .15rem; right: 1rem;
  font-size: 4.75rem; line-height: 1; font-weight: 800;
  color: var(--flame); opacity: .16; pointer-events: none;
}
.rev-stars { color: var(--flame); letter-spacing: .12em; font-size: 1.125rem; line-height: 1; }
.rev p {
  font-size: 1rem; line-height: 1.65; margin: 0;
  display: -webkit-box; -webkit-line-clamp: 8; -webkit-box-orient: vertical;
  overflow: hidden;
}
.rev-by { margin-top: auto; padding-top: .35rem; font-size: .875rem; color: var(--on-ground-soft); }
.rev-by b { color: var(--on-ground); font-weight: 700; }

/* ---- slider controls, all inserted by site.js so they never render dead ---- */
.rev-nav { display: none; }
.reviews.is-slider .rev-nav {
  display: inline-flex; align-items: center; gap: .5rem;
  margin-left: auto;
}
.rev-arrow {
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  background: var(--ground); color: #fff;
  border: 0; cursor: pointer;
  transition: background .2s var(--ease);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%);
}
.rev-arrow:hover { background: var(--red); }
.rev-arrow[disabled] { opacity: .4; cursor: default; }
.rev-arrow[disabled]:hover { background: var(--ground); }
.rev-pos {
  font-family: var(--mono); font-size: .875rem; color: var(--on-bone-soft);
  min-width: 3.4em; text-align: center; font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
  .rev-list { scroll-behavior: auto; }
}

/* --------------------------------------------------------------- footer
   Bone, for the same reason as the header: the footer carries the full stacked
   lockup, so it is one of the two places Lewis's mark appears, and his mark now
   ships in its original colours. Sunk one step so it still reads as a base
   under whichever bay precedes it. */
.foot { position: relative; background: var(--bone-sunk); color: var(--on-bone); }
.foot > .weld { position: absolute; top: 0; left: 0; right: 0; }
.foot-in { display: grid; gap: 2.5rem; padding-block: clamp(2.75rem, 5vw, 4rem); }
@media (min-width: 820px) { .foot-in { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) minmax(0, 1fr); } }
.foot .brand img { height: auto; width: 168px; }
.foot-h { margin-bottom: .9rem; font-size: .9375rem; font-weight: 700; letter-spacing: .02em; }
.foot ul { list-style: none; display: grid; gap: .1rem; }
.foot a {
  color: var(--on-bone-soft); text-decoration: none;
  display: inline-block; padding-block: .72rem; min-height: 44px;
}
.foot a:hover { color: var(--red-deep); text-decoration: underline; text-underline-offset: .2em; }
.foot-base {
  border-top: 1px solid var(--line-light-soft);
  padding-block: 1.35rem;
  display: flex; flex-wrap: wrap; gap: .6rem 1.5rem; justify-content: space-between;
  font-size: .875rem; color: var(--on-bone-soft);
}
/* social: the real brand marks in their real colours, inline, no CDN */
.social { display: flex; gap: .5rem; margin-top: 1.1rem; }
.social a {
  width: 46px; height: 46px; min-height: 46px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--line-light);
  transition: border-color .2s var(--ease), background .2s var(--ease);
}
.social a:hover { border-color: var(--edge-light); background: var(--bone); }
.social svg { width: 24px; height: 24px; display: block; }

/* --------------------------------------------------- sticky mobile call bar */
.callbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-sticky);
  display: none; gap: 1px; background: var(--line-dark);
  border-top: 1px solid var(--line-dark);
}
.callbar a {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: .5rem;
  min-height: 58px; text-decoration: none; font-weight: 700; font-size: 1rem;
}
.callbar .c-call { background: var(--red); color: #fff; }
.callbar .c-wa { background: var(--ground-deep); color: var(--on-ground); }
@media (max-width: 780px) {
  .callbar { display: flex; }
  body { padding-bottom: 59px; }
}

/* ============================================================== PARALLAX
   Quiet. If it is noticeable as an effect it is too much, so it is 3% of the
   element height and it only runs while the element is on screen. */
.par { overflow: hidden; }
.par > img { will-change: transform; }
html.js .par > img { transform: translate3d(0, var(--py, 0px), 0) scale(1.07); }

/* ================================================================ MOTION
   Reveals ENHANCE an already visible default. Without JS, with reduced
   motion, or if the observer never fires, everything is visible. A previous
   build shipped 1,700px of invisible content because an observer never fired;
   site.js arms a forced reveal before anything else can throw. */
.reveal { opacity: 1; transform: none; }
html.js .reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .66s var(--ease), transform .66s var(--ease);
}
html.js .reveal.in, html.js .reveal.forced { opacity: 1; transform: none; }
html.js .reveal.d1 { transition-delay: .08s; }
html.js .reveal.d2 { transition-delay: .16s; }
html.js .reveal.d3 { transition-delay: .24s; }

@media (prefers-reduced-motion: reduce) {
  /* A complete experience, not a degraded one: every bead is drawn, every
     seam sits at its resting position, nothing moves. */
  html.js .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
  .weld::before { clip-path: inset(0 0 0 0) !important; animation: none !important; }
  .weld::after { display: none !important; }
  html.js .par > img { transform: none !important; }
  .btn-primary::after { transition: none; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
}

/* --------------------------------------------------------------- 404 */
.oops { min-height: 56vh; display: flex; align-items: center; }

/* --------------------------------------------------------- print safety */
@media print {
  .head, .callbar, .filters, .c-grip, .social { display: none; }
  html.js .reveal { opacity: 1 !important; transform: none !important; }
  html.js .par > img { transform: none !important; }
  body { background: #fff; color: #111; }
}

/* ======================================================== THE AREA ENGINE
   v5. 28 area pages: a real photograph of the town in the header, the town's
   own boundary highlighted on a live map, and bespoke copy between them.
   The map is Leaflet + OSM, vendored and lazy-injected; the boundary was
   baked at build time into /geo/<slug>.json, so the page makes no geocoding
   API call ever. The placeholder panel below IS the no-JS experience and
   holds the exact aspect ratio of the map, so tiles arriving shift nothing.
   ========================================================================== */
.area-hero { position: relative; background: var(--ground-deep); overflow: hidden; }
.area-photo { position: relative; height: clamp(300px, 44vh, 480px); overflow: hidden; }
@media (max-width: 780px) { .area-photo { height: clamp(200px, 30vh, 300px); } }
.area-photo img { width: 100%; height: 100%; object-fit: cover; object-position: center 42%; }
/* the town photo reads as scene, not as content: a quiet graphite grade at
   the base so the plinth beneath feels attached, never a heavy scrim */
.area-photo::after {
  content: ''; position: absolute; inset: auto 0 0 0; height: 34%;
  background: linear-gradient(180deg, rgba(35, 36, 41, 0) 0%, rgba(35, 36, 41, .38) 100%);
  pointer-events: none;
}
/* the licence credit. CC BY and CC BY-SA require attribution and it renders
   here, readable but quiet, on every area header that is not Lewis's own. */
.credit {
  position: absolute; right: 0; bottom: 0; z-index: 2;
  max-width: 92%;
  padding: .3rem .6rem;
  background: rgba(23, 24, 27, .78); color: #E5E7EA;
  font-size: .6875rem; line-height: 1.35; letter-spacing: .01em;
}

.area-prose h2 { font-size: var(--t-4); }
.area-prose p { margin-top: 1.05rem; }
.area-prose p:first-of-type { margin-top: 1.35rem; }
.dark .area-prose p { color: var(--on-ground-soft); }
.dark .area-prose p b { color: var(--on-ground); }

.area-links { list-style: none; display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }
.area-links a {
  display: inline-block; padding: .55rem .9rem;
  border: 1px solid var(--line-light); text-decoration: none; font-weight: 700;
  color: var(--on-bone);
  transition: border-color .2s var(--ease), color .2s var(--ease), background .2s var(--ease);
}
.area-links a:hover { border-color: var(--red-deep); color: var(--red-deep); background: #fff; }

/* ---- the map ---- */
.amap-box {
  position: relative; aspect-ratio: 4 / 3;
  background: var(--ground-raise);
  overflow: hidden;
}
.amap figcaption { margin-top: .8rem; font-size: .9375rem; }
.amap-still {
  position: absolute; inset: 0; z-index: 1;
  display: grid; place-content: center; justify-items: center; gap: .3rem;
  text-align: center; color: var(--on-ground);
  background:
    radial-gradient(ellipse 68% 52% at 50% 44%, rgba(245, 195, 44, .07), rgba(245, 195, 44, 0) 70%),
    var(--ground-raise);
}
.amap-still b { font-size: var(--t-2); letter-spacing: -.02em; }
.amap-still span:last-child { color: var(--on-ground-soft); font-size: .9375rem; }
.amap-pin {
  display: block; width: 14px; height: 14px; margin-bottom: .5rem;
  background: var(--red); transform: rotate(45deg);
  box-shadow: 0 0 0 2px rgba(241, 242, 244, .85), 0 0 22px rgba(209, 28, 35, .45);
}
.amap-box.is-ready .amap-still { display: none; }
.amap-box .leaflet-container {
  position: absolute; inset: 0; width: 100%; height: 100%;
  background: #DDD9D2; font-family: var(--sans);
  opacity: 0; transition: opacity .6s var(--ease);
}
.amap-box.is-ready .leaflet-container { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .amap-box .leaflet-container { transition: none; opacity: 1; }
}
/* ⚠ scope guard, the 3 Aug lesson: nothing of ours may style a tile. The
   ONLY img rules that touch the map are Leaflet's own. Ours are scoped to
   the still panel, which contains no img at all. */
.amap-marker i {
  display: block; width: 12px; height: 12px;
  background: var(--red); transform: rotate(45deg);
  box-shadow: 0 0 0 2px #fff, 0 2px 10px rgba(20, 20, 22, .4);
}
.leaflet-tooltip.amap-tip {
  background: var(--ground-deep); color: var(--on-ground);
  border: 1px solid var(--line-dark); border-radius: 0;
  font-family: var(--sans); font-weight: 700; font-size: .8125rem;
  padding: .25rem .6rem;
}
.leaflet-tooltip.amap-tip::before { display: none; }
/* zoom controls at the 44px touch floor, square like everything else here */
.amap .leaflet-bar a {
  border-radius: 0;
  width: 44px; height: 44px; line-height: 44px; font-size: 1.15rem;
}
.amap .leaflet-touch .leaflet-bar a { width: 44px; height: 44px; line-height: 44px; }
.amap .leaflet-container :focus-visible { outline: 3px solid var(--red-deep); outline-offset: -3px; }

/* ---- the homepage hub ---- */
.town-list {
  list-style: none; margin-top: clamp(1.5rem, 3vw, 2.25rem);
  display: grid; gap: 0; grid-template-columns: 1fr;
  border-top: 1px solid var(--line-dark);
}
@media (min-width: 560px) { .town-list { grid-template-columns: 1fr 1fr; column-gap: 2rem; } }
.town-list li {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .35rem 0; border-bottom: 1px solid var(--line-dark-soft);
}
.town-list b { font-size: 1.0625rem; letter-spacing: -.01em; }
.town-links { display: inline-flex; gap: .35rem; }
.town-links a {
  display: inline-block; padding: .55rem .7rem; min-height: 44px; line-height: 1.9;
  font-size: .875rem; font-weight: 700; text-decoration: none;
  color: var(--flame);
  transition: color .2s var(--ease), text-decoration-color .2s var(--ease);
}
.town-links a:hover { text-decoration: underline; text-underline-offset: .22em; }

/* ---- customer stories (fillable slots; render only when real) ---- */
.story { margin-top: clamp(2rem, 4vw, 3rem); }
.story blockquote {
  margin: 1.5rem 0 0; padding: 1.25rem 1.5rem;
  border-left: 3px solid var(--red-deep);
  background: var(--bone-sunk);
}
.story blockquote p { font-size: var(--t-1); line-height: 1.5; }
.story blockquote footer { margin-top: .8rem; font-weight: 700; }

.rev-more p { font-size: 1.0625rem; }

/* ================================================= FORGE ATMOSPHERE, v5
   Steel grain on the dark ground: a static noise texture, one data URI, at
   an opacity you feel rather than see. And an ember warmth where the welds
   live: the radial glow on the map placeholder above, and the glint the
   weld head already carries. Nothing loops, nothing moves on its own.
   ========================================================================== */
.dark, .dark-deep {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .035 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media print { .dark, .dark-deep { background-image: none; } }

/* Per-photo framing where the default centre crop cuts the landmark. The
   Bearsted header is a portrait: nudge the band up so the twin battlemented
   towers, the thing a local recognises, stay in frame. */
.area-photo img[src*="/bearsted-"] { object-position: center 32%; }

/* the service-page chip links on the DARK prose half of area pages: the
   light-half chip colours would vanish here, so they take the dark grammar */
.dark .area-links a, .dark-deep .area-links a {
  border-color: var(--edge-dark); color: var(--on-ground); background: none;
}
.dark .area-links a:hover, .dark-deep .area-links a:hover {
  border-color: var(--flame); color: var(--flame); background: var(--ground-raise);
}
