/* ============================================================================
   SEVERN SKYLIGHTS GLOUCESTER — hero preview
   DNA from the operator-supplied reference: warm light grounds, photography-led,
   two-tone serif headlines, enquiry form inside the hero.

   No navy anywhere. A company that sells daylight should not present itself on
   a dark ground, which is the one thing all five sister sites do.
   ========================================================================= */

:root {
  --bone:       #F8F5EF;
  --sand:       #EBE4D6;
  --stone:      #DCD3C2;
  --oxide:      #B5533A;
  --oxide-deep: #8E3E2B;
  --ink:        #2A2B26;
  --ink-2:      #5F6158;
  --slate:      #33413E;
  --white:      #FFFFFF;

  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px; --s8:64px; --s9:96px; --s10:128px;

  --display: "Gambetta", "Iowan Old Style", Palatino, Georgia, serif;
  --body:    "Satoshi", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono:    ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;

  --radius: 4px;
  --shadow-card: 0 1px 2px rgba(42,43,38,.04), 0 18px 44px rgba(42,43,38,.10);
}

*,*::before,*::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bone);
  color: var(--ink);
  font-family: var(--body);
  font-size: 1.0625rem;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
}
img, svg { display: block; max-width: 100%; height: auto; }

/* LONG UNBREAKABLE STRINGS ARE A WCAG 1.4.4 FAILURE, NOT A COSMETIC ONE.
 *
 * "info@severnskylightsgloucester.co.uk" is 37 characters with no break
 * opportunity, so at 200% zoom it forced 1513px of layout into a 1366px viewport
 * on every page of the site through the footer alone. Reflow at 200% is a
 * conformance requirement, so this is a failure rather than an untidy line.
 *
 * `anywhere` rather than `break-word` because only `anywhere` lets the string
 * contribute a smaller min-content width to its grid or flex track, which is what
 * actually stops the track refusing to shrink. Scoped to the elements that carry
 * addresses and URLs: applying it to all prose would let ordinary words break
 * mid-syllable. */
a[href^="mailto:"],
a[href^="tel:"],
.site-footer a,
.contact-direct a,
code, kbd, samp {
  overflow-wrap: anywhere;
}

/* An inline SVG diagram with its own viewBox still needs the box capped, or it
   reports its intrinsic width at 200% zoom and overflows the same way. */
svg:not([width]) { width: 100%; }

h1,h2,h3 {
  font-family: var(--display);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -.015em;
  color: var(--ink);
  margin: 0 0 var(--s4);
  text-wrap: balance;
}

/* THE SIGNATURE. Roman for the fact, oxide italic for the judgement.
   Once per heading, never on a measurement or a place name. */
.tt { font-style: italic; color: var(--oxide); }

:focus-visible { outline: 2px solid var(--oxide-deep); outline-offset: 3px; border-radius: 2px; }

.container { width: min(100% - var(--s7), 1220px); margin-inline: auto; }
@media (max-width: 640px) { .container { width: calc(100% - var(--s5)); } }

.eyebrow {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin: 0 0 var(--s3);
  display: block;
}
/* Eyebrow, then a 44px oxide rule, then the heading. The recurring opener. */
.rule { display: block; width: 44px; height: 2px; background: var(--oxide); margin: 0 0 var(--s5); }

/* ------------------------------------------------------------------ HEADER
   Light header with a serif wordmark and a filled phone pill, as the reference. */
.site-header {
  background: var(--bone);
  border-bottom: 1px solid var(--stone);
  position: sticky; top: 0; z-index: 100;
}
body.admin-bar .site-header { top: 32px; }
.site-header .container {
  display: flex; align-items: center; gap: var(--s5); flex-wrap: wrap;
  padding-block: var(--s3);
}
.brand { display: flex; align-items: center; gap: var(--s3); text-decoration: none; color: var(--ink); }
.brand__mark { width: 26px; height: 26px; flex: 0 0 auto; }
.brand strong {
  font-family: var(--display); font-weight: 500; font-size: 1.24rem;
  letter-spacing: -.01em; line-height: 1;
}
/* The full stop after the wordmark, in the accent. Straight from the reference. */
.brand strong::after { content: "."; color: var(--oxide); }

.nav { margin-left: auto; }
.menu { display: flex; align-items: center; gap: var(--s5); list-style: none; margin: 0; padding: 0; }
.menu a { color: var(--ink); text-decoration: none; font-size: .95rem; }
.menu a:hover { color: var(--oxide-deep); }
.tel-pill {
  display: inline-flex; align-items: center; gap: var(--s2);
  background: var(--oxide); color: var(--bone);
  padding: var(--s3) var(--s5); border-radius: var(--radius);
  font-weight: 700; font-size: .95rem; text-decoration: none;
}
.tel-pill:hover { background: var(--oxide-deep); }
.menu-toggle { display: none; background: none; border: 1px solid var(--stone); border-radius: var(--radius); padding: var(--s2) var(--s3); }

@media (max-width: 980px) {
  .menu-toggle { display: inline-flex; order: 2; margin-left: auto; }
  .nav { order: 3; width: 100%; display: none; }
  .brand { order: 1; }
  .menu { flex-direction: column; align-items: stretch; gap: 0; }
  .menu > li { border-top: 1px solid var(--stone); }
  .menu > li > a { display: block; padding: var(--s3) 0; }
}

/* -------------------------------------------------------------------- HERO
   Full-bleed warm photograph, washed back with a bone veil so the type holds,
   with the enquiry form INSIDE the hero rather than a button pointing at one. */
.hero { position: relative; overflow: hidden; background: var(--sand); }
.hero__bg { position: absolute; inset: 0; z-index: 0; }
.hero__bg img { width: 100%; height: 100%; object-fit: cover; }
/* The veil. Strong at the left where the words are, clearing to the right where
   the photograph should still read as a photograph. */
.hero__bg::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(100deg, rgba(248,245,239,.97) 0%, rgba(248,245,239,.93) 34%, rgba(248,245,239,.62) 58%, rgba(248,245,239,.42) 100%);
}
.hero__grid {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: 1fr minmax(0, 420px);
  gap: clamp(var(--s6), 5vw, var(--s9));
  align-items: center;
  padding-block: clamp(var(--s8), 7vw, 108px);
}
@media (max-width: 1020px) {
  .hero__grid { grid-template-columns: 1fr; }
  .hero__bg::after { background: linear-gradient(180deg, rgba(248,245,239,.93) 0%, rgba(248,245,239,.80) 46%, rgba(248,245,239,.55) 100%); }
}

.hero h1 { font-size: clamp(2.5rem, 5.2vw, 3.9rem); margin-bottom: var(--s5); }
.hero__lead {
  font-size: clamp(1.05rem, 1.5vw, 1.18rem);
  line-height: 1.65; color: var(--ink-2);
  max-width: 46ch; margin: 0 0 var(--s6);
}
.hero__cta { display: flex; align-items: center; gap: var(--s5); flex-wrap: wrap; }
.btn {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-family: var(--body); font-weight: 700; font-size: 1rem;
  padding: var(--s4) var(--s6); border-radius: var(--radius);
  border: 0; text-decoration: none; cursor: pointer;
  transition: background-color .15s, transform .15s;
}
.btn--primary { background: var(--oxide); color: var(--bone); }
.btn--primary:hover { background: var(--oxide-deep); transform: translateY(-1px); }
.btn--text {
  color: var(--ink); font-weight: 500; text-decoration: none;
  border-bottom: 1px solid var(--oxide); padding-bottom: 2px;
}
.btn--text:hover { color: var(--oxide-deep); }

/* Dash-separated trust line in mono small caps, under the CTA. */
.trust {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2) var(--s4);
  margin: var(--s7) 0 0; padding-top: var(--s5);
  border-top: 1px solid var(--stone);
  font-family: var(--mono); font-size: .68rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2);
  list-style: none;
}
.trust li { display: flex; align-items: center; gap: var(--s4); }
.trust li + li::before { content: ""; width: 12px; height: 1px; background: var(--oxide); flex: 0 0 auto; }
@media (max-width: 620px) {
  .trust { display: grid; gap: var(--s2); }
  .trust li + li::before { display: none; }
}

/* ------------------------------------------------------ the hero form card */
.enq {
  background: var(--bone);
  border: 1px solid var(--stone);
  border-radius: var(--radius);
  padding: clamp(var(--s5), 2.4vw, var(--s7));
  box-shadow: var(--shadow-card);
}
.enq h2 { font-size: 1.55rem; margin-bottom: var(--s2); }
.enq__sub { font-size: .92rem; color: var(--ink-2); margin: 0 0 var(--s5); }
.enq label {
  display: block;
  font-family: var(--mono); font-size: .62rem; letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-2); margin-bottom: var(--s1);
}
.enq input, .enq textarea {
  width: 100%; font: inherit; font-size: .95rem;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--stone); border-radius: 3px;
  background: var(--white); color: var(--ink);
  margin-bottom: var(--s4);
}
.enq textarea { resize: vertical; min-height: 82px; }
.enq input:focus-visible, .enq textarea:focus-visible { outline: 2px solid var(--oxide-deep); outline-offset: 1px; }
.enq .btn { width: 100%; justify-content: center; }
.enq__note { font-size: .78rem; color: var(--ink-2); margin: var(--s3) 0 0; text-align: center; }

/* ------------------------------------------------------- the section below
   Included so the hero can be judged against what follows it, which is how it
   will actually be seen. */
.section { padding-block: clamp(var(--s8), 7vw, var(--s9)); }
.section--sand { background: var(--sand); }
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: var(--s7); }
/* Cards with no border and no box: image, serif heading, copy. Space separates. */
.tile img { border-radius: var(--radius); aspect-ratio: 4/3; object-fit: cover; width: 100%; }
.tile h3 { font-size: 1.45rem; margin: var(--s5) 0 var(--s3); }
.tile p { color: var(--ink-2); margin: 0; font-size: .98rem; }
.section-head { max-width: 760px; margin-bottom: var(--s8); }
.section-head h2 { font-size: clamp(1.9rem, 3.2vw, 2.6rem); }

/* ------------------------------------------------------------------ FOOTER
   NO vertical margin on a full-bleed band. On the last build a margin-top here
   showed the body ground through as a stripe between two coloured bands, on
   every page, for the whole build. Padding only. */
.site-footer {
  background: var(--slate);
  color: var(--bone);
  padding-block: var(--s8);
  margin: 0;
}
.site-footer__mark { font-family: var(--display); font-size: 1.3rem; margin: 0 0 var(--s2); }
.site-footer__note { margin: 0; color: rgba(248,245,239,.72); font-size: .94rem; max-width: 60ch; }

/* ------------------------------------------------------------------- SKIP */
.skip {
  position: absolute; left: var(--s4); top: -60px; z-index: 200;
  background: var(--oxide); color: var(--bone);
  padding: var(--s3) var(--s5); border-radius: var(--radius);
  text-decoration: none; font-weight: 700;
  transition: top .15s;
}
.skip:focus { top: var(--s4); }

/* ------------------------------------------------- mobile menu mechanics */
.menu-toggle { align-items: center; gap: var(--s2); font: inherit; font-size: .9rem; color: var(--ink); cursor: pointer; }
.menu-toggle__bars { display: grid; gap: 3px; width: 16px; }
.menu-toggle__bars i { display: block; height: 2px; background: var(--ink); }
@media (max-width: 980px) {
  /* Opening the nav must not split the header row three ways: the nav sits
     between the brand and the actions in the DOM, so it takes the full width
     on its own line via `order` above and this. */
  .nav.is-open { display: block; padding-bottom: var(--s4); }
  html.nav-locked, html.nav-locked body { overflow: hidden; }
}


/* ===== parts/20-home.css ================================================= */
/* ============================================================================
   parts/20-home.css — the homepage conversion bands.

   Five bands, five different layout skeletons, because §2 forbids two adjacent
   sections sharing one. Every grid here names its own minimum: `1fr` is
   `minmax(auto, 1fr)` whose auto minimum is min-content, so an unnamed track
   refuses to shrink and overflows at 390px and at 200% zoom.
   ========================================================================= */

/* Cards are borderless in this language: image, serif heading, copy, and space
   doing the separating. The link wraps image and heading so the whole visual
   block is one target, while the body copy stays outside it and selectable. */
.tile__link { display: block; text-decoration: none; color: inherit; }
.tile__link:hover h3 { color: var(--oxide-deep); }
.tile__link img { transition: transform .15s ease; }
@media (hover: hover) {
  .tile__link:hover img { transform: translateY(-2px); }
}

/* ------------------------------------------------------------- 2. the split */
.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(400px, 100%), 1fr));
  gap: clamp(var(--s6), 5vw, var(--s9));
  align-items: center;
}
.split__words { min-width: 0; }
.split__plate { margin: 0; min-width: 0; }
.split__plate img {
  border-radius: var(--radius);
  aspect-ratio: 4 / 5;
  object-fit: cover;
  width: 100%;
}
@media (max-width: 860px) {
  /* Portrait crop is wrong once the plate is full width. */
  .split__plate img { aspect-ratio: 16 / 10; }
}

.worries { margin: var(--s7) 0 0; display: grid; gap: var(--s5); }
.worry { padding-left: var(--s5); border-left: 2px solid var(--stone); }
.worry dt {
  font-family: var(--display);
  font-size: 1.22rem;
  color: var(--ink);
  margin-bottom: var(--s2);
}
.worry dd { margin: 0; color: var(--ink-2); }
/* The accent marks the question, and it is a 2px bar rather than text, so the
   4.6:1 ratio of oxide on bone is irrelevant here. Text under 24px never gets
   plain oxide: it gets --oxide-deep at 7.2:1. */
.worry:hover { border-left-color: var(--oxide); }

/* ----------------------------------------------------------- 3. the process */
.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: var(--s7);
  counter-reset: none;
}
.step { min-width: 0; padding-top: var(--s5); border-top: 2px solid var(--stone); }
.step__n {
  display: block;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .14em;
  /* Deepened, because this is well under 24px. */
  color: var(--oxide-deep);
  margin-bottom: var(--s3);
}
.step h3 { font-size: 1.3rem; margin: 0 0 var(--s3); }
.step p { margin: 0; color: var(--ink-2); font-size: .98rem; }

/* ------------------------------------------------------------- 4. the areas */
.regions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: var(--s6) var(--s7);
}
.region { min-width: 0; }
.region h3 {
  font-size: 1.24rem;
  margin: 0 0 var(--s2);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--stone);
}
.region p { margin: 0; color: var(--ink-2); font-size: .95rem; }
.regions__more { margin: var(--s8) 0 0; }

/* --------------------------------------------------------- 5. the guarantee */
.guarantee {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(380px, 100%), 1fr));
  gap: clamp(var(--s6), 5vw, var(--s9));
  align-items: start;
}
.guarantee__words { min-width: 0; }
.guarantee__words p { color: var(--ink-2); max-width: 54ch; }
/* The card sits on --sand here rather than the hero photograph, so it needs a
   visible edge of its own; the shadow alone disappears against a mid tone. */
.guarantee .enq { border-color: var(--stone); background: var(--bone); }

/* ---------------------------------------------------------------- the prose
   Fallback measure for the long-read when the router has not loaded. Never a
   bare column: it still gets a real measure and the site's type. */
.prose { max-width: 68ch; }
.prose h2 { font-size: clamp(1.6rem, 2.6vw, 2.1rem); margin-top: var(--s8); }
.prose h3 { font-size: 1.3rem; margin-top: var(--s6); }
.prose p, .prose li { color: var(--ink-2); }
.prose table { width: 100%; border-collapse: collapse; margin: var(--s6) 0; }
.prose th, .prose td { text-align: left; padding: var(--s3) var(--s4); border-bottom: 1px solid var(--stone); }
.prose th { font-family: var(--mono); font-size: .68rem; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-2); }


/* ===== parts/30-chrome.css =============================================== */
/* ============================================================================
   CHROME — header, dropdowns, mobile menu, footer, hub pages, motion.

   Extends base.css, never replaces it. Everything here uses the tokens and the
   class names already signed off there.
   ========================================================================= */

/* ==========================================================================
   1. HEADER
   ====================================================================== */

.site-header .container { row-gap: 0; }

.brand strong { min-width: 0; }

.header-actions {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex: 0 0 auto;
}
.header-tel {
  font-family: var(--mono);
  font-size: .9rem;
  color: var(--oxide-deep);
  text-decoration: none;
}

.menu-item { min-width: 0; }

/* Shared list resets, so the panel is a list at every width and the skin below
   is the only thing that changes. */
.submenu, .submenu ul { list-style: none; margin: 0; padding: 0; }
.submenu a { text-decoration: none; color: var(--ink); }

.sub-toggle {
  background: none;
  border: 0;
  color: var(--ink-2);
  font: inherit;
  padding: 5px;
  border-radius: 3px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}
.sub-toggle:hover { color: var(--oxide-deep); }
.sub-toggle svg { transition: transform .18s ease; }
.menu-item.is-open > .sub-toggle svg { transform: rotate(180deg); }

/* --------------------------------------------------------------------------
   THE DROPDOWN, AND WHY THE LAST ONE WAS UNUSABLE.

   The operator's report on the sister build: "the header menu service drop down
   is hyper sensitive so cant actually reveal the drop down easily." It opened on
   hover and closed the instant hover was lost, across a visible gap between the
   trigger and the panel. Moving the pointer down from the trigger crossed that
   gap and the menu vanished. It was not sensitivity, it was a dead zone plus a
   zero-tolerance close.

   Three things here, and a fourth in nav.js:
     1. NO DEAD GAP. The menu item stretches the full height of the header bar
        and the panel hangs off the bar's bottom edge, so the pointer travels
        from one hoverable box straight into the next.
     2. A BRIDGE across the 12px of header padding beneath the item, invisible,
        which keeps the pointer inside the parent even on a diagonal path.
     3. Open state is a class, not :hover, so nav.js can hold it open for 240ms
        after the pointer leaves and can keep aria-expanded honest.
   The :focus-within rule is the no-JavaScript fallback and stands down the
   moment nav.js adds `js-nav`: leaving both live means Escape cannot close a
   panel, because focus returns to the trigger inside the parent.
   ---------------------------------------------------------------------- */

@media (min-width: 981px) {
  /* Positioned so the wide panels can anchor to the bar rather than to the item
     they hang from, which is what stops a 720px panel running off the right of
     the viewport at any width. */
  .site-header .container { position: relative; }

  .nav { align-self: stretch; display: flex; align-items: stretch; min-width: 0; }
  .menu { align-items: stretch; }
  .menu-item { position: relative; display: flex; align-items: center; }
  .menu-item.has-sub { gap: 2px; }
  /* Wide panels take the bar as their containing block, not the item. */
  .menu-item.has-sub--wide { position: static; }

  .submenu {
    position: absolute;
    top: calc(100% + var(--s3));
    left: 0;
    z-index: 120;

    /* THE PANEL WIDTH BUG.
     *
     * This was `min-width: min(420px, 100%)`. The percentage resolves against
     * the containing block, which is the menu ITEM at about 80px, so min()
     * always picked the item width and the panel came out roughly 157px. Ten
     * services then stacked into one narrow column tall enough to run off the
     * bottom of the screen, so the last few were unreachable. The percentage
     * has to be viewport relative, because the viewport is the thing the panel
     * must actually fit inside. */
    min-width: min(560px, calc(100vw - var(--s7)));
    max-width: calc(100vw - var(--s7));

    /* NEVER TALLER THAN THE SCREEN. A dropdown that overflows the viewport has
     * no scroll of its own and the page behind it will not scroll while the
     * pointer is inside, so whatever falls past the fold is simply lost. */
    max-height: calc(100vh - 100% - var(--s6));
    overflow-y: auto;
    overscroll-behavior: contain;

    padding: var(--s5);
    background: var(--bone);
    border: 1px solid var(--stone);
    border-top: 0;
    border-radius: 0 0 var(--radius) var(--radius);
    box-shadow: var(--shadow-card);

    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity .14s ease, transform .14s ease, visibility 0s linear .14s;
  }

  /* The bridge. Sits in the header's bottom padding, invisible, and keeps the
     pointer inside the parent on the way down. */
  .submenu::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(var(--s3) * -1 - 1px);
    height: calc(var(--s3) + 2px);
  }

  .submenu--wide {
    left: auto;
    right: 0;
    top: 100%;
    width: min(720px, 100%);
    min-width: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
    gap: var(--s5) var(--s6);
  }

  .menu-item.is-open > .submenu,
  html:not(.js-nav) .menu-item.has-sub:focus-within > .submenu {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity .14s ease, transform .14s ease, visibility 0s;
  }

  /* Services has no group labels, so its one list becomes the columns instead. */
  .submenu:not(.submenu--wide) .submenu__group > ul {
    display: grid;
    /* Two real columns. auto-fit was already here but could never produce more
       than one while the panel itself was stuck at the width of its menu item,
       which is what made the services list a single tall column. */
    grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
    gap: 0 var(--s5);
  }

  /* A panel hanging off an item near the right edge would run past the viewport
     on a narrower desktop. Anchoring the last dropdown to the right of its item
     keeps it on screen without moving anything at 1440. */
  .menu > .menu-item.has-sub:nth-last-of-type(-n+2) > .submenu:not(.submenu--wide) {
    left: auto;
    right: 0;
  }

  .submenu__group { min-width: 0; }
  .submenu a { display: block; padding: var(--s2) var(--s3); border-radius: 3px; font-size: .9rem; }
  .submenu a:hover { background: var(--sand); color: var(--oxide-deep); }

  .submenu__label {
    font-family: var(--mono);
    font-size: .66rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-2);
    padding: var(--s1) var(--s3) var(--s2);
  }
  .submenu__label:hover { background: none; color: var(--oxide-deep); }

  .submenu__all {
    grid-column: 1 / -1;
    border-top: 1px solid var(--stone);
    margin-top: var(--s3);
    padding-top: var(--s3);
  }
  .submenu__all a { color: var(--oxide-deep); font-weight: 700; }
}

/* --------------------------------------------------------------------------
   THE MOBILE MENU, DONE PROPERLY FIRST TIME.

   Three real defects on the sister build, every one of them a desktop rule
   leaking down:

     1. The panel rendered 950px wide inside an 844px viewport with overflow
        visible, so it simply ran off the screen with nothing to scroll.
     2. There was no body scroll lock, so scrolling the menu scrolled the page
        behind it. The operator's word for that was "very lazily done".
     3. The submenu kept the desktop panel skin: a pale card with a 300px
        min-width, a border and a shadow, dropped inside the nav. It looked
        worst on exactly the two items that have a submenu.

   Fault 3 is fixed structurally rather than by overriding: every panel rule
   above lives inside the desktop media query, so at this width there is no skin
   to undo. A submenu here is a plain indented list on the same ground as the
   nav, with one stone hairline to carry the indent.
   ---------------------------------------------------------------------- */

@media (max-width: 980px) {
  .site-header .container { column-gap: var(--s3); }
  .brand { order: 1; min-width: 0; }
  .header-actions { order: 2; margin-left: auto; gap: var(--s3); }
  /* base.css pushes the toggle right on its own; inside the actions group that
     would only add a second gap before it. */
  .header-actions .menu-toggle { order: 0; margin-left: 0; }
  .header-tel { display: none; }
  .nav { order: 3; width: 100%; min-width: 0; margin-left: 0; }
  .menu, .menu > li { min-width: 0; }

  /* Bounded, scrollable, and the scroll stops here instead of moving the page
     underneath. nav.js locks the document as well. */
  .nav.is-open {
    max-height: calc(100vh - 132px);
    max-height: calc(100dvh - 132px);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    border-top: 1px solid var(--stone);
    margin-top: var(--s3);
  }

  .menu-item.has-sub {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
  }
  /* Placed explicitly. Grid columns follow DOM order only until something is
     reordered, and a caret that lands in column one pushes the link out of the
     row entirely. */
  .menu-item.has-sub > a { grid-column: 1; }
  .menu-item.has-sub > .sub-toggle {
    grid-column: 2;
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    color: var(--ink);
  }
  .menu-item.has-sub > .submenu { grid-column: 1 / -1; }

  .submenu { display: none; padding: 0 0 var(--s4) var(--s4); border-left: 2px solid var(--stone); }
  .menu-item.is-open > .submenu,
  html:not(.js-nav) .menu-item.has-sub:focus-within > .submenu { display: block; }

  .submenu__group + .submenu__group { margin-top: var(--s4); }
  .submenu__label {
    display: block;
    font-family: var(--mono);
    font-size: .64rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-2);
    padding: var(--s2) 0;
  }
  .submenu a { display: block; padding: var(--s3) 0; font-size: .92rem; }
  .submenu__all { margin-top: var(--s2); padding-top: var(--s2); border-top: 1px solid var(--stone); }
  .submenu__all a { color: var(--oxide-deep); font-weight: 700; }
}

@media (max-width: 560px) {
  /* Brand, quote and burger have to share 336px on a 360px phone. Shrinking all
     three is what keeps the header one row rather than two. */
  .brand strong { font-size: .98rem; }
  .brand__mark { width: 22px; height: 22px; }
  .tel-pill { padding: var(--s2) var(--s3); font-size: .82rem; }
  .menu-toggle__label { display: none; }
}

/* Belt and braces on the lock. base.css already hides overflow; this stops iOS
   rubber-banding the page behind the panel. */
html.nav-locked { overscroll-behavior: none; }

/* ==========================================================================
   2. FOOTER — the page's one dark band
   ====================================================================== */

/* NO VERTICAL MARGIN. A margin-top here showed the body ground through as a
   cream stripe between two coloured bands on all 343 pages of the sister build,
   and it survived being called fixed once. Padding only, on the band itself. */
.site-footer {
  margin: 0;
  padding-block: clamp(var(--s8), 6vw, var(--s9)) var(--s6);
}

.footer-top { max-width: 64ch; padding-bottom: clamp(var(--s7), 5vw, var(--s8)); }
.site-footer__mark { font-size: 1.5rem; }
/* The wordmark's full stop, without the accent. Oxide on Deep Slate is 1.8:1:
   the accent is a light-ground device and putting it here would be unreadable
   punctuation rather than a signature. */
.site-footer__mark::after { content: "."; }

.footer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: var(--s7) var(--s6);
}
.footer-col { min-width: 0; }

/* base.css colours every h1/h2/h3 with --ink, which on this ground would be a
   heading you cannot read. Overridden here rather than in a per-band block. */
.footer-col__h {
  font-family: var(--mono);
  font-weight: 400;
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--stone);
  margin: 0 0 var(--s4);
  line-height: 1.4;
}

.footer-col ul { list-style: none; margin: 0; padding: 0; }
.footer-col li + li { margin-top: var(--s3); }
.footer-col a { text-decoration: none; font-size: .95rem; }
.footer-col a:hover { text-decoration: underline; }
.footer-col__note { font-size: .88rem; margin: var(--s4) 0 0; color: var(--stone); }

/* FALLBACK ONLY. Link colour on this band belongs to the single .on-dark
   utility in parts/40-core.css, whose `.on-dark a:not(.btn)` outranks this on
   specificity. This is here so the footer is never default-blue on slate. */
.site-footer a { color: var(--stone); }

.footer-bottom {
  margin-top: clamp(var(--s7), 5vw, var(--s8));
  padding-top: var(--s5);
  border-top: 1px solid var(--ink-2);
}
.footer-bottom__line { margin: 0; font-size: .88rem; }
.footer-legal ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s5);
  list-style: none;
  margin: var(--s4) 0 0;
  padding: 0;
}
.footer-legal a { font-size: .85rem; text-decoration: none; }
.footer-legal a:hover { text-decoration: underline; }
.footer-sitemap { margin: var(--s5) 0 0; font-size: .85rem; }
.footer-sitemap a { text-decoration: none; }
.footer-sitemap a:hover { text-decoration: underline; }

/* Build credit. Quieter than the sitemap line above it: it is an attribution,
   not wayfinding, so it sits below everything and does not compete. The link
   still carries an underline on hover because a link that never looks like one
   is a link people do not know they can use. */
.footer-builtby { margin: var(--s3) 0 0; font-size: .8rem; color: var(--stone); }
.footer-builtby a { text-decoration: none; }
.footer-builtby a:hover { text-decoration: underline; }

/* Clearance for the mobile sticky action bar, INSIDE the band. On the body it
   would show the body ground through as a stripe, which is the whole reason this
   file has a rule about margins at all. Same breakpoint and same token as the
   bar itself, which parts/40-core.css owns; the fallback value is there so this
   still clears something if the token ever moves. */
@media (max-width: 760px) {
  .site-footer { padding-bottom: calc(var(--s6) + var(--mcta-h, 64px)); }
}

/* ==========================================================================
   3. HUB PAGES — services, areas, guides, answers

   Wayfinding, not selling. The cards lead DIRECTLY below the hero and the
   editorial copy sits under them. Burying a hub's navigation beneath a
   paragraph of preamble was a real defect on a sister build.
   ====================================================================== */

/* Fallback page head only: a hub normally uses the house .pagehead through
   sev_read_pagehead(). Same ground, same opener, so the two paths cannot look
   like different sites. Padding only, never a vertical margin. */
.hub-hero {
  background: var(--sand);
  padding-block: clamp(var(--s7), 5vw, var(--s8));
  margin: 0;
}
.hub-hero h1 { font-size: clamp(2.1rem, 4vw, 3rem); max-width: 26ch; margin: 0; }
.hub-hero__lead {
  font-size: clamp(1.05rem, 1.5vw, 1.2rem);
  line-height: 1.65;
  color: var(--ink-2);
  max-width: 58ch;
  margin: var(--s5) 0 0;
}

/* Sits between the head and the cards. It is wayfinding itself, so the cards
   still lead: what must never go here is a paragraph of preamble. */
.hub-jump { margin-bottom: clamp(var(--s7), 5vw, var(--s8)); }
.hub-jump ul { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); list-style: none; margin: 0; padding: 0; }
/* Oxide Deep, not Oxide: this is 11px text, and the accent at 4.6:1 is a
   heading colour and a button ground only. */
.hub-jump a {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--oxide-deep);
  text-decoration: none;
  border-bottom: 1px solid var(--stone);
  padding-bottom: 3px;
}
.hub-jump a:hover { border-bottom-color: var(--oxide); }

.hub-group { min-width: 0; scroll-margin-top: 96px; }
.hub-group + .hub-group { margin-top: clamp(var(--s8), 6vw, var(--s9)); }
.hub-group__head { margin-bottom: var(--s7); }
.hub-group__head h2 { font-size: clamp(1.6rem, 2.6vw, 2.1rem); margin: 0; }

/* Borderless cards: photograph, serif heading, a line of copy, and space doing
   the separating. No box, no outline. */
.tile { min-width: 0; }
.tile__link { display: block; text-decoration: none; color: inherit; }
.tile h2 { font-size: 1.45rem; margin: var(--s5) 0 var(--s3); }
.tile__link:hover h2, .tile__link:hover h3 { color: var(--oxide-deep); }
/* Cards past the photograph budget. A single hairline gives them a top edge to
   sit on without turning them into boxes. */
.tile--plain { border-top: 1px solid var(--stone); padding-top: var(--s4); }
.tile--plain h2, .tile--plain h3 { margin-top: 0; }

.hub-note { max-width: 68ch; }

/* ==========================================================================
   4. MOTION — the four movements in MOTION-PROFILE.md, and nothing else

   Every hidden starting state is under html.js-motion, which an inline head
   script sets only after confirming there is an IntersectionObserver and that
   reduced motion is off. Without JavaScript the page renders fully visible: the
   difference between a JS failure being invisible and a JS failure being blank.
   ====================================================================== */

/* 1. Reveal on scroll: 10px rise plus fade, 420ms. The 60ms stagger inside a
   batch is timed in sev-motion.js, because CSS cannot know what arrived
   together. */
[data-reveal] {
  transition:
    opacity .42s cubic-bezier(.22, .61, .36, 1),
    transform .42s cubic-bezier(.22, .61, .36, 1);
}
html.js-motion [data-reveal] { opacity: 0; transform: translateY(10px); }
html.js-motion [data-reveal].is-in { opacity: 1; transform: none; }

/* 2. The oxide section rule draws from its left edge. scaleX, never a width
   transition, so it runs on the compositor. The observer watches the rule's
   PARENT: a rule scaled to zero has zero area, and a zero-area element has zero
   intersection, which is a deadlock it can never escape. */
.rule { transition: transform .5s cubic-bezier(.22, .61, .36, 1); }
html.js-motion .rule { transform: scaleX(0); transform-origin: left center; }
html.js-motion .rule.is-drawn { transform: none; }

/* 3. Image card lift, 2px, pointer devices only. */
@media (hover: hover) {
  .tile img { transition: transform .15s ease; }
  .tile__link:hover img { transform: translateY(-2px); }
}

/* 4. Button ground shift is already in base.css: oxide to oxide-deep plus a 1px
   lift, 150ms. Nothing to add, and nothing to duplicate. */

/* Reduced motion: removed, not shortened. */
@media (prefers-reduced-motion: reduce) {
  [data-reveal], .rule, .tile img, .sub-toggle svg, .submenu { transition: none; }
  html.js-motion [data-reveal] { opacity: 1; transform: none; }
  html.js-motion .rule { transform: none; }
  .tile__link:hover img { transform: none; }
}


/* ===== parts/40-core.css ================================================= */
/* ============================================================================
   40-core.css — the shared furniture: the .on-dark utility, breadcrumbs, the
   WhatsApp widget and the mobile sticky CTA bar.

   Tokens only. Nothing here redefines a token from base.css, and nothing here
   carries a raw hex: the two colour values that look like exceptions are the
   translucent bone veils, which are bone at a stated opacity because there is no
   token for a tint and inventing one would be a second source of truth.
   ========================================================================= */

:root {
  /* Height of the sticky mobile bar. It is needed in three places: the bar, the
     clearance under the footer, and the offset that lifts the WhatsApp pill
     clear of it. Three hand-typed 64s is how one of them ends up as 68 and the
     pill sits on top of the bar on one breakpoint only. */
  --mcta-h: 64px;
}

/* ============================================================ THE DARK GROUND
   ONE opt-in class, never a list of dark selectors.

   The sister build enumerated every dark surface by name, and each new dark
   panel silently failed until somebody remembered to add it to the list: a
   ghost button shipped at 1.43:1 on its own ground that way. A class means a
   new dark surface inherits the corrections instead of needing to be recalled.

   Contrast, measured on --slate #33413E:
     bone   11.8:1   headings and links
     stone   7.2:1   body copy, and the accent's stand-in
     oxide   2.2:1   FAILS as text, so the accent inverts to light in here
   That last line is why .tt and .rule are overridden below rather than left to
   inherit the oxide they carry everywhere else. The signature survives as a
   change of weight and slant; it would not survive as unreadable text. */
.on-dark { color: var(--bone); }
.on-dark h1,
.on-dark h2,
.on-dark h3,
.on-dark strong { color: var(--bone); }
.on-dark p,
.on-dark li,
.on-dark dd,
.on-dark figcaption,
.on-dark .eyebrow { color: var(--stone); }

/* The two-tone heading, inverted. Still italic, still the second half only. */
.on-dark .tt { color: var(--stone); }
/* The section rule, which is decorative and so has no contrast minimum, but at
   2.2:1 it disappears rather than reads as a device. */
.on-dark .rule { background: var(--sand); }

/* a:not(.btn), because a button brings its own ground with it and must not be
   repainted by the band it happens to be sitting on. */
.on-dark a:not(.btn) {
  color: var(--bone);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.on-dark a:not(.btn):hover { color: var(--sand); }
.on-dark :focus-visible { outline-color: var(--white); }
.on-dark .btn--text { color: var(--bone); border-bottom-color: var(--sand); }
.on-dark .trust { border-top-color: rgba(248, 245, 239, .28); color: var(--stone); }
.on-dark .trust li + li::before { background: var(--sand); }

/* ================================================================ BREADCRUMBS
   Driven by the _sev_parent chain in PHP, since every page sits at the top level
   and WordPress knows nothing about the hierarchy.

   Set in mono, like the eyebrows and the trust line: this is navigational data
   about where you are, not prose. Ink Soft at 6.4:1, never oxide at 4.6:1,
   because none of it is anywhere near 24px.

   TWO SHAPES, ON PURPOSE. Bare `.crumbs` is the typographic base and carries no
   ground, because the long-read templates render their crumbs INSIDE a page head
   and a bone bar across a photographic hero is a stripe through the picture.
   `.crumbs--band` is the standalone band that sev_breadcrumbs() emits on a page
   with no hero of its own. The band properties are written at `.crumbs.crumbs--band`
   so they hold whatever order the partials happen to concatenate in: a sibling
   partial also styles `.crumbs`, and a one-class rule loaded later would
   otherwise silently take the margin back. */
.crumbs {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .08em;
  line-height: 1.5;
  color: var(--ink-2);
  min-width: 0;
}
.crumbs.crumbs--band {
  background: var(--bone);
  border-bottom: 1px solid var(--stone);
  padding-block: var(--s3);
  margin: 0;
}
.crumbs__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}
.crumbs__item {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-width: 0;
}
.crumbs__item + .crumbs__item::before {
  content: "";
  width: 10px;
  height: 1px;
  background: var(--stone);
  flex: 0 0 auto;
}
.crumbs__item a {
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 1px solid var(--stone);
}
.crumbs__item a:hover { color: var(--oxide-deep); border-bottom-color: var(--oxide-deep); }
.crumbs__item [aria-current="page"] { color: var(--ink); }

/* ============================================================ WHATSAPP WIDGET
   The house pattern: a LABELLED PILL that opens a short form, which composes a
   pre-filled message from whatever the visitor actually filled in.

   Only the colours are Severn's. A previous version of this component was
   invented from nothing as a plain green icon, and the operator's reply was
   that it did not match the other sites. The behaviour, the field set and the
   ARIA are the sister build's; the palette is the one thing this build is for.

   The green is gone deliberately. On a warm light ground built out of one accent
   it read as a third-party badge stuck to the corner of the page, and the label
   is what makes the feature legible anyway. */
.wa {
  position: fixed;
  right: var(--s5);
  bottom: var(--s5);
  z-index: 120;
}
.wa__fab {
  display: flex;
  align-items: center;
  gap: var(--s2);
  background: var(--oxide);
  color: var(--bone);
  border: 0;
  border-radius: 999px;
  padding: var(--s3) var(--s5);
  font-family: var(--body);
  font-weight: 700;
  font-size: .875rem;
  cursor: pointer;
  box-shadow: 0 10px 28px rgba(42, 43, 38, .22);
  transition: background-color .15s, transform .15s;
}
.wa__fab:hover {
  background: var(--oxide-deep);
  transform: translateY(-1px);
}
/* Ink, not oxide-deep: an oxide-deep ring on an oxide ground is not a ring. */
.wa__fab:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }

.wa__panel {
  position: absolute;
  right: 0;
  bottom: calc(100% + var(--s3));
  width: min(320px, calc(100vw - var(--s6)));
  max-height: min(70vh, 560px);
  overflow-y: auto;
  background: var(--bone);
  border: 1px solid var(--stone);
  border-top: 3px solid var(--oxide);
  border-radius: var(--radius);
  padding: var(--s5);
  box-shadow: var(--shadow-card);
}
.wa__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s3);
}
.wa__head strong {
  font-family: var(--display);
  font-weight: 500;
  font-size: 1.15rem;
  line-height: 1.2;
  color: var(--ink);
}
.wa__close {
  background: transparent;
  border: 0;
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-2);
  padding: 0 var(--s1);
  flex: 0 0 auto;
}
.wa__close:hover { color: var(--ink); }
.wa__close:focus-visible { outline: 2px solid var(--oxide-deep); outline-offset: 2px; }
.wa__intro {
  font-size: .82rem;
  line-height: 1.55;
  color: var(--ink-2);
  margin: 0 0 var(--s4);
}
/* Every control has an explicit for/id pair in the markup as well as sitting
   inside its label. The implicit association alone is valid and invisible to a
   static parser, which is how six fields on 343 pages became 2,058 findings. */
.wa label {
  display: block;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: var(--s3);
}
.wa input,
.wa textarea,
.wa select {
  display: block;
  width: 100%;
  margin-top: var(--s1);
  padding: var(--s2) var(--s3);
  border: 1px solid var(--stone);
  border-radius: 3px;
  background: var(--white);
  color: var(--ink);
  /* The label above is uppercase and tracked out. Without these three lines the
     value the visitor types inherits all of it. */
  font-family: var(--body);
  font-size: .9rem;
  letter-spacing: normal;
  text-transform: none;
}
.wa textarea { resize: vertical; min-height: 62px; }
.wa input:focus-visible,
.wa textarea:focus-visible,
.wa select:focus-visible { outline: 2px solid var(--oxide-deep); outline-offset: 1px; }
/* auto-fit with a floor, never repeat(2, 1fr): a 1fr track's auto minimum is
   min-content, and a date input's min-content is wide, so the fixed two-column
   form overflows the 320px panel. This gives two columns where they fit and one
   where they do not. */
.wa__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(120px, 100%), 1fr));
  gap: var(--s3);
}
.wa__row > label { min-width: 0; }
.wa__send { width: 100%; justify-content: center; margin-top: var(--s2); }

/* ========================================================= MOBILE STICKY CTA
   PRESENT FROM THE FIRST PIXEL. No scroll threshold, no entrance animation, no
   transform and no JavaScript.

   The last build hid this behind a scroll threshold, so on a phone the two
   things a visitor might do were invisible for the first half of every page.
   There is nothing to reveal here: the bar is simply there. */
.mcta { display: none; }

@media (max-width: 760px) {
  .mcta {
    display: grid;
    grid-auto-flow: column;
    /* Two buttons today, three the moment a phone number is configured. Auto
       columns take both cases without a second rule. minmax(0,...) for the same
       reason as above. */
    grid-auto-columns: minmax(0, 1fr);
    gap: var(--s2);
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 200;
    min-height: var(--mcta-h);
    align-items: stretch;
    padding: var(--s2);
    background: var(--bone);
    border-top: 1px solid var(--stone);
    box-shadow: 0 -6px 22px rgba(42, 43, 38, .10);
  }
  .mcta__btn {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    padding: var(--s3) var(--s2);
    border-radius: var(--radius);
    font-family: var(--body);
    font-weight: 700;
    font-size: .92rem;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
  }
  /* Bone on oxide is 4.6:1, which the style guide allows for a button label and
     nowhere else. */
  .mcta__btn--quote { background: var(--oxide); color: var(--bone); }
  .mcta__btn--quote:active { background: var(--oxide-deep); }
  /* Ink on bone is 13.9:1. The outlined pair read as secondary without either
     of them dropping below AA. */
  .mcta__btn--wa,
  .mcta__btn--call {
    color: var(--ink);
    border: 1px solid var(--stone);
    background: var(--white);
  }
  .mcta__btn:focus-visible { outline: 2px solid var(--oxide-deep); outline-offset: 2px; }

  /* Lift the WhatsApp pill clear of the bar rather than letting them stack. */
  .wa {
    right: var(--s3);
    bottom: calc(var(--mcta-h) + var(--s3));
  }

  /* THE CLEARANCE GOES ON THE FOOTER, NEVER ON THE BODY.
     Clearing the bar with body padding works mechanically and looks wrong: the
     body ground is bone, so those pixels rendered as a cream strip underneath
     the slate footer, on every page, for the whole of the last build. Inside the
     footer, the footer's own ground runs on behind the bar and the seam has
     nowhere to appear.

     PADDING, not margin. A vertical margin on a full-bleed band shows the body
     through the gap, which is the same defect wearing a different hat. */
  .site-footer { padding-bottom: calc(var(--s8) + var(--mcta-h)); }
}

/* ==================================================================== TABLES
   A wide table scrolls inside its own box. The body never scrolls sideways:
   two-column tables pushed the sister build's document to 417px at a 390px
   viewport. sev_wrap_tables() in functions.php emits this wrapper. */
.tablewrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
  min-width: 0;
}

/* ===================================================================== STUB
   The paragraph the seeder writes when a page has no content file yet. Visible
   on purpose: a silent empty page looks finished, and an obvious one gets
   written. */
.sev-stub {
  font-family: var(--mono);
  font-size: .8rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-2);
  border-left: 2px solid var(--oxide);
  padding-left: var(--s4);
}

/* Motion is additive here and there is little of it, so removing it is enough. */
@media (prefers-reduced-motion: reduce) {
  .wa__fab { transition: none; }
  .wa__fab:hover { transform: none; }
}


/* ===== parts/50-read.css ================================================= */
/* ============================================================================
   50-read.css: the page head, the shared long-form language, and four of the
   five reading treatments from STYLE-GUIDE section 8.

   Tokens and classes come from base.css. Nothing here redefines a token and
   nothing here is a hex literal. There is no dark band in this file: the footer
   already spends the one Deep Slate band a page is allowed.
   ========================================================================= */

/* -------------------------------------------------------------- THE PAGE HEAD
   Sand, so it reads as a step deeper than the bone body below it. Padding only,
   never a vertical margin: a margin on a full bleed band shows the body ground
   through as a stripe, which is what happened on all 343 pages of the last
   build. */
.pagehead {
  background: var(--sand);
  padding-block: clamp(var(--s7), 5vw, var(--s8));
  margin: 0;
}
.pagehead__inner { max-width: 820px; }
.pagehead h1,
.pagehead h2 {
  font-size: clamp(2.1rem, 4vw, 3rem);
  line-height: 1.1;
  margin: 0;
}
.pagehead__lead {
  font-size: clamp(1.05rem, 1.5vw, 1.2rem);
  line-height: 1.65;
  color: var(--ink-2);
  max-width: 58ch;
  margin: var(--s5) 0 0;
}

.crumbs {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin: 0 0 var(--s5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
}
/* Ink Soft at 6.4:1, never oxide: this is well under 24px. */
.crumbs a { color: var(--ink-2); text-decoration: none; border-bottom: 1px solid var(--stone); }
.crumbs a:hover { color: var(--oxide-deep); border-bottom-color: var(--oxide-deep); }

/* ================================================== THE SHARED PROSE LANGUAGE
   Every treatment below wears this, so long form is never an unstyled column:
   the intro band, the standfirst, styled list markers, styled tables, panelled
   callouts and accent underlined links. */
.prose { color: var(--ink); font-size: 1.0625rem; line-height: 1.75; }
.prose p { margin: 0 0 var(--s5); }
.prose > :last-child { margin-bottom: 0; }

.prose h2 {
  font-size: clamp(1.7rem, 2.8vw, 2.3rem);
  line-height: 1.15;
  margin: var(--s8) 0 var(--s4);
  /* Clear the sticky header when an in page anchor lands on a heading. */
  scroll-margin-top: var(--s8);
}
.prose h3 { font-size: 1.35rem; line-height: 1.3; margin: var(--s7) 0 var(--s3); }
.prose h4 {
  font-family: var(--body);
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: .01em;
  margin: var(--s6) 0 var(--s2);
}
.prose > h2:first-child, .prose > h3:first-child { margin-top: 0; }

/* Links carry an accent underline rather than an accent colour. Oxide is 4.6:1
   and body copy is 17px, so the colour stays Ink and the oxide does the
   signalling as a rule, which is a fill and not a glyph. */
.prose a:not(.btn) {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--oxide);
  padding-bottom: 1px;
}
.prose a:not(.btn):hover { color: var(--oxide-deep); border-bottom-color: var(--oxide-deep); }

/* The intro band. A lead paragraph set larger, closed by a hairline, so a long
   read opens rather than starts. */
.read-intro {
  font-size: clamp(1.08rem, 1.4vw, 1.22rem);
  line-height: 1.7;
  color: var(--ink-2);
  padding-bottom: var(--s6);
  margin-bottom: var(--s7);
  border-bottom: 1px solid var(--stone);
}
.read-intro p:last-child { margin-bottom: 0; }

/* The first paragraph after a heading, promoted. */
.prose .standfirst { font-size: 1.12rem; line-height: 1.7; color: var(--ink); }

/* parts/20-home.css sets .prose p and .prose li to Ink Soft, which is right for
   the homepage's short sections and wrong for a 4,000 word read: STYLE-GUIDE
   section 3 makes Ink the body colour and Ink Soft the secondary one. Scoped to
   these four treatments so the homepage keeps its own decision. */
.dossier__body p, .dossier__body li,
.flow__inner p, .flow__inner li,
.answer__inner p, .answer__inner li,
.article__inner p, .article__inner li { color: var(--ink); }

/* The intro band stays Ink Soft, because a lead is exactly what Ink Soft is for.
   This has to sit AFTER the rule above: same specificity, later wins. */
.read-intro p, .read-intro li { color: var(--ink-2); }

/* Styled list markers. An oxide dash for unordered, a mono numeral in Oxide Deep
   for ordered, because a numeral is small text and 4.6:1 is not enough for it. */
.prose ul, .prose ol { margin: 0 0 var(--s5); padding: 0; list-style: none; }
.prose li { position: relative; padding-left: var(--s6); margin-bottom: var(--s3); }
.prose li > ul, .prose li > ol { margin-top: var(--s3); margin-bottom: 0; }
.prose ul > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .82em;
  width: 10px;
  height: 2px;
  background: var(--oxide);
}
.prose ol { counter-reset: proseol; }
.prose ol > li { counter-increment: proseol; }
.prose ol > li::before {
  content: counter(proseol, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: .1em;
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .06em;
  color: var(--oxide-deep);
}

/* Tables. Hairlines and mono headers, no boxes, and the wrapper scrolls itself
   so the document never scrolls sideways at 390px. */
.tablewrap { overflow-x: auto; margin: 0 0 var(--s6); -webkit-overflow-scrolling: touch; }
.tablewrap:focus-visible { outline: 2px solid var(--oxide-deep); outline-offset: 3px; }
.prose table { width: 100%; min-width: 34ch; border-collapse: collapse; font-size: .97rem; }
.prose caption {
  text-align: left;
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-2);
  padding-bottom: var(--s3);
}
.prose th, .prose td { text-align: left; padding: var(--s3) var(--s4); border-bottom: 1px solid var(--stone); vertical-align: top; }
.prose th {
  font-family: var(--mono);
  font-weight: 400;
  font-size: .66rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-2);
  border-bottom-width: 2px;
}
.prose tbody tr:last-child td { border-bottom: 0; }

/* Callouts and pull panels. Sand ground, 3px oxide rule on the left edge. */
.prose blockquote, .prose .callout {
  background: var(--sand);
  border-left: 3px solid var(--oxide);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: clamp(var(--s5), 2.4vw, var(--s6));
  margin: 0 0 var(--s6);
}
.prose blockquote p, .prose .callout p:last-child { margin-bottom: 0; }
.prose blockquote p { font-family: var(--display); font-size: 1.2rem; line-height: 1.5; font-style: italic; }
.prose .callout > :last-child { margin-bottom: 0; }

.prose figure { margin: 0 0 var(--s6); }
.prose figure img { border-radius: var(--radius); width: 100%; }
.prose figcaption { font-size: .86rem; color: var(--ink-2); margin-top: var(--s3); }
.prose hr { border: 0; border-top: 1px solid var(--stone); margin: var(--s7) 0; }
.prose strong { font-weight: 700; }
.prose code { font-family: var(--mono); font-size: .9em; background: var(--sand); padding: 0 var(--s1); border-radius: 2px; }

/* ============================================================== 1. THE DOSSIER
   Home and services. A sticky contents rail with oxide numerals.

   The columns are declared body first and the body is PLACED in the first track
   explicitly. On the sister build the tracks read `250px 1fr` while the article
   came first in the DOM, so the article rendered inside the 250px track and the
   page came out 39,480px tall while passing every HTTP check. Row and column are
   both explicit here, because the rail comes first in the DOM and sparse auto
   placement would otherwise push the body onto a second row underneath it. */
.dossier__grid { display: block; }
.dossier__grid--rail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: clamp(var(--s6), 4vw, var(--s8));
  align-items: start;
}
.dossier__grid--rail .dossier__body { grid-column: 1; grid-row: 1; min-width: 0; }
.dossier__grid--rail .dossier__rail { grid-column: 2; grid-row: 1; min-width: 0; }
.dossier__body { max-width: 72ch; min-width: 0; }

.dossier__rail {
  position: sticky;
  top: var(--s8);
  /* Its OWN ground. A sticky element sitting over a scrolling page has nothing
     reliable behind it, and a contrast checker reads the element it is given,
     not what happens to be underneath at that scroll position. */
  background: var(--sand);
  border: 1px solid var(--stone);
  border-radius: var(--radius);
  padding: var(--s5);
  max-height: calc(100vh - var(--s9));
  overflow-y: auto;
}
.dossier__rail h2 {
  font-family: var(--display);
  font-size: 1.1rem;
  line-height: 1.2;
  margin: 0 0 var(--s4);
  scroll-margin-top: 0;
}

.rail-list { counter-reset: rail; margin: 0; padding: 0; list-style: none; }
.rail-list li { counter-increment: rail; position: static; padding: 0; margin: 0; }
.rail-list li::before { content: none; }
.rail-list a {
  display: grid;
  grid-template-columns: 2.2em minmax(0, 1fr);
  gap: var(--s2);
  padding: var(--s3) var(--s3) var(--s3) var(--s3);
  color: var(--ink);
  text-decoration: none;
  font-size: .9rem;
  line-height: 1.4;
  border-top: 1px solid var(--stone);
  /* Transparent until the spy marks it, so nothing reflows when it does. */
  border-left: 2px solid transparent;
  transition: color .15s, border-color .15s;
}
.rail-list li:first-child a { border-top: 0; }
.rail-list a::before {
  content: counter(rail, decimal-leading-zero);
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .06em;
  color: var(--oxide-deep);
  align-self: start;
  padding-top: .25em;
}
.rail-list a:hover { color: var(--oxide-deep); }
.rail-list a.is-here { color: var(--oxide-deep); border-left-color: var(--oxide); }

@media (max-width: 1020px) {
  /* Single column, and both children placed explicitly again so the contents
     list stays above the reading rather than landing beside it. */
  .dossier__grid--rail { grid-template-columns: minmax(0, 1fr); gap: var(--s7); }
  .dossier__grid--rail .dossier__rail { grid-column: 1; grid-row: 1; position: static; max-height: none; }
  .dossier__grid--rail .dossier__body { grid-column: 1; grid-row: 2; }
}
@media (prefers-reduced-motion: reduce) {
  .rail-list a { transition: none; }
}

/* ========================================================= 2. FEATURE FLOW
   Guides. One elegant column, a drop cap, and an oxide numeral centred on a
   hairline rule announcing each chapter. No rail: a guide is read straight
   through, and the dossier's device belongs to the dossier. */
.flow { padding-block: clamp(var(--s8), 7vw, var(--s9)); }
.flow__inner { width: min(100%, 68ch); margin-inline: auto; }

/* flow-root contains the floated drop cap, so the hairline under the intro band
   sits below the whole cap rather than through it. */
.flow__lede > p:first-of-type { display: flow-root; }
.flow__lede > p:first-of-type::first-letter {
  float: left;
  font-family: var(--display);
  font-size: 4.1em;
  line-height: .76;
  color: var(--oxide);
  padding: .06em var(--s3) 0 0;
}

.flow__chapter { margin-top: clamp(var(--s7), 5vw, var(--s9)); }
.flow__chapter:first-child { margin-top: 0; }

.chapnum {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s5);
  margin: 0 0 var(--s6);
}
.chapnum::before, .chapnum::after { content: ""; height: 1px; background: var(--stone); min-width: 0; }
/* 1.9rem is 30px, comfortably in the large text band where oxide is legitimate. */
.chapnum span {
  font-family: var(--display);
  font-style: italic;
  font-size: 1.9rem;
  line-height: 1;
  color: var(--oxide);
}
.flow__chapter h2 { margin-top: 0; text-align: center; }
.flow__chapter--prose h2 { text-align: center; }

/* The pull panel. A section carrying a table is reference material, so it gets
   panelled off the reading rather than dropped into the middle of it. */
.flow__chapter--panel .flow__body {
  background: var(--sand);
  border-radius: var(--radius);
  padding: clamp(var(--s5), 3vw, var(--s7));
}
.flow__chapter--panel .prose table, .flow__chapter--panel table { background: transparent; }
.flow__chapter--panel th, .flow__chapter--panel td { border-bottom-color: var(--stone); }

/* A section carrying a substantive list gets hairline separated items, which
   reads as a comparison instead of a bullet dump. */
.flow__chapter--cards > .flow__body > ul > li {
  padding: var(--s4) 0 var(--s4) var(--s6);
  margin: 0;
  border-top: 1px solid var(--stone);
}
.flow__chapter--cards > .flow__body > ul > li:first-child { border-top: 0; }
.flow__chapter--cards > .flow__body > ul > li::before { top: 1.5em; }

/* ========================================================= 3. DIRECT ANSWER
   Answer pages. The answer first, in a sand callout with a 3px oxide left rule,
   then short clean sections beneath it. No numerals, no rail. */
.answer__inner { width: min(100%, 68ch); margin-inline: auto; }
.answer__callout {
  background: var(--sand);
  border-left: 3px solid var(--oxide);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: clamp(var(--s5), 3vw, var(--s7));
  margin: 0 0 var(--s8);
}
.answer__tag {
  display: block;
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: var(--s3);
}
.answer__callout p {
  font-family: var(--display);
  font-size: clamp(1.22rem, 2vw, 1.55rem);
  line-height: 1.45;
  color: var(--ink);
  margin: 0;
}
.answer__callout a:not(.btn) { border-bottom-color: var(--oxide-deep); }

.asec + .asec { border-top: 1px solid var(--stone); margin-top: var(--s7); padding-top: var(--s7); }
.asec h2 { margin-top: 0; font-size: clamp(1.45rem, 2.2vw, 1.8rem); }

/* ======================================================= 4. DESIGNED ARTICLE
   Costs, about, contact, quote and every legal page, and the router's fallback.
   Applied regardless of length: a 200 word legal page gets the kicker rules
   too, because legal copy set as an unstyled column reads as an oversight. */
.article__inner { width: min(100%, 68ch); margin-inline: auto; }
.article__inner h2::before {
  content: "";
  display: block;
  width: 44px;
  height: 2px;
  background: var(--oxide);
  margin-bottom: var(--s4);
}
.article__inner h3 { color: var(--ink); }
.article__inner h3::before {
  content: "";
  display: block;
  width: 22px;
  height: 1px;
  background: var(--stone);
  margin-bottom: var(--s3);
}


/* ===== parts/60-area.css ================================================= */
/* ============================================================================
   AREA (TOWN) PAGES — the editorial-chapters treatment
   Owns: inc/area-template.php only. 70 pages, about 2,000 words each.

   Two rules govern this whole file, both bought with defects on the sister
   build:

   1. EVERY SELECTOR IS SCOPED to .ar / .ar-*. Nothing here can reach another
      template. The sister build had a bare `.chapter:nth-child(even)` rule at
      specificity (0,3,0) that outranked every later correction and shipped 17
      contrast failures.
   2. NO :nth-child ANYWHERE. Which band is bone and which is sand is decided in
      PHP from the page slug and emitted as an explicit .ar-band--* class. A
      ground the template chose cannot be argued with by a stylesheet, and it is
      the only way the rhythm can be seeded per town.

   Grounds: bone and sand alternating, plus exactly ONE slate band, which is the
   seeded interlude. That band states its own background AND its own text
   colours here, because the sister build let a generic `.tr-chapters .chapter`
   rule at (0,2,0) beat `.section--slate` at (0,1,0) and rendered cream text on
   a cream ground.
   ========================================================================= */

/* The two grounds. One class each, (0,1,0), written once. */
.ar-band--bone { background: var(--bone); }
.ar-band--sand { background: var(--sand); }

/* -------------------------------------------------------------------- HERO
   Reuses .hero and .hero__bg from base for the photograph and the bone veil.
   No form card here: the words get the whole veiled side and the photograph
   still reads as a photograph on the other. */
.ar-hero__inner {
  position: relative;             /* .hero__bg is absolute at z-index 0 */
  z-index: 1;
  max-width: 660px;
  padding-block: clamp(var(--s8), 6.5vw, 104px);
}
/* Same specificity as base's `.hero h1` (0,1,1); this partial is concatenated
   after base.css, so it wins on order. Counted, not guessed. */
.hero--area h1 { font-size: clamp(2.1rem, 4vw, 3rem); margin-bottom: var(--s5); }
.hero--area .hero__lead { margin-bottom: var(--s6); }

/* The breadcrumb is scoped, not the shared .crumbs: parts/40-core.css makes that
   one a full width bone band with a bottom border, which across a photographic
   hero would read as a stripe through the picture. Same mono language, no ground.
   Ink Soft at 6.4:1, never oxide at 4.6:1, because this is well under 24px. */
.ar-crumbs {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3);
  font-family: var(--mono); font-size: .7rem;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-2); margin-bottom: var(--s5);
}
.ar-crumbs a { color: var(--oxide-deep); text-decoration: none; border-bottom: 1px solid transparent; }
.ar-crumbs a:hover { border-bottom-color: var(--oxide-deep); }

/* ------------------------------------------------------------------- FACTS
   Designed data, not a sentence. A definition list, so the label really is the
   label of the value. Each cell carries a 44px oxide bar over a stone hairline:
   the section-opener rule, repeated as a data device. */
.ar-facts__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
  gap: var(--s7) var(--s7);
  margin: 0;
}
.ar-fact {
  position: relative; min-width: 0;
  border-top: 1px solid var(--stone);
  padding-top: var(--s5);
}
.ar-fact::before {
  content: ""; position: absolute; top: -2px; left: 0;
  width: 44px; height: 2px; background: var(--oxide);
}
.ar-fact dt {
  font-family: var(--mono); font-size: .66rem;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2); margin-bottom: var(--s3);
}
.ar-fact dd { margin: 0; }
.ar-fact__v {
  display: block; font-family: var(--display);
  font-size: clamp(1.2rem, 1.8vw, 1.55rem); line-height: 1.22;
  letter-spacing: -.01em; color: var(--ink);
}
.ar-fact__foot {
  display: block; margin-top: var(--s3);
  font-size: .82rem; line-height: 1.5; color: var(--ink-2);
}

/* ---------------------------------------------------------------- SERVICES
   Wayfinding into the service hubs. Six, because ten is a list rather than a
   choice. Typographic while the image pool is small: the same photograph six
   times is worse than none, which is the note the operator made on the sister
   build's index cards. */
.ar-svc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr));
  gap: var(--s7);
}
.ar-svc {
  min-width: 0;
  border-top: 1px solid var(--stone);
  padding-top: var(--s5);
  transition: transform .15s;
}
.ar-svc--pic img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--radius); margin-bottom: var(--s5);
}
.ar-svc__n {
  display: block; font-family: var(--mono); font-size: .68rem;
  letter-spacing: .14em; color: var(--oxide-deep);   /* 7.2:1, under 24px */
  margin-bottom: var(--s3);
}
.ar-svc h3 { font-size: 1.3rem; margin: 0 0 var(--s3); }
.ar-svc h3 a { color: var(--ink); text-decoration: none; }
.ar-svc h3 a:hover { color: var(--oxide-deep); text-decoration: underline; text-underline-offset: 3px; }
.ar-svc p { margin: 0; color: var(--ink-2); font-size: .98rem; }
@media (hover: hover) {
  .ar-svc:hover { transform: translateY(-2px); }
}

/* ---------------------------------------------------------- FEATURE OPENER
   The way in. Fifteen chapters with no map is pagination, not a page: the
   reader gets no sense of length and no entry other than the top. */
.ar-opener__grid {
  display: grid;
  grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
  gap: clamp(var(--s7), 5vw, var(--s9));
  align-items: start;
}
/* Children placed explicitly. Grid columns follow DOM order and reversing that
   once produced a 39,480px page that passed every HTTP check. */
.ar-opener__intro { grid-column: 1; min-width: 0; }
.ar-index { grid-column: 2; min-width: 0; }

.ar-index ol {
  list-style: none; margin: 0; padding: 0;
  columns: 2; column-gap: var(--s7);
}
.ar-index li { break-inside: avoid; }
.ar-index a {
  display: flex; gap: var(--s4); align-items: baseline;
  padding: var(--s3) 0; border-bottom: 1px solid var(--stone);
  text-decoration: none; color: var(--ink);
}
.ar-index__n {
  flex: 0 0 auto; font-family: var(--mono); font-size: .68rem;
  letter-spacing: .1em; color: var(--oxide-deep);
}
.ar-index__t { font-family: var(--display); font-size: 1.04rem; line-height: 1.35; min-width: 0; }
.ar-index a:hover .ar-index__t { color: var(--oxide-deep); }

@media (max-width: 1040px) {
  .ar-index ol { columns: 1; }
}
@media (max-width: 900px) {
  .ar-opener__grid { grid-template-columns: minmax(0, 1fr); }
  .ar-opener__intro, .ar-index { grid-column: 1; }
}

/* ------------------------------------------------------------- THE CHAPTERS
   A numeral rail that sticks beside the chapter it belongs to, so a reader
   fifteen chapters deep always knows which one they are in.

   The sister build solved that with a JavaScript progress rail with a scroll
   spy. This template owns no JavaScript, and it does not need any: a sticky
   numeral plus the contents list at the opener does the same work with nothing
   to fail. */
.ar-chapter { padding-block: clamp(var(--s7), 5.5vw, var(--s9)); }
.ar-chapter__grid {
  display: grid;
  grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
  gap: clamp(var(--s5), 4vw, var(--s8));
  align-items: start;
}
.ar-chapter__rail { grid-column: 1; min-width: 0; position: sticky; top: 92px; }
.ar-chapter__body { grid-column: 2; min-width: 0; max-width: 68ch; }

.ar-chapter__n {
  display: block; font-family: var(--display);
  font-size: clamp(2.2rem, 3.4vw, 3.1rem); line-height: 1;
  color: var(--oxide);            /* large text only, which this is */
}
.ar-chapter__tick { display: block; width: 2px; height: 32px; background: var(--stone); margin: var(--s3) 0; }
.ar-chapter__town {
  display: block; font-family: var(--mono); font-size: .62rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-2);
}
/* The pull quote is a sentence from the chapter beside it, set large, and
   aria-hidden in the markup because it is a repeat rather than new content. */
.ar-pull {
  font-family: var(--display); font-style: italic;
  font-size: clamp(1.1rem, 1.5vw, 1.35rem); line-height: 1.4;
  color: var(--ink); border-left: 2px solid var(--oxide);
  padding-left: var(--s4); margin: var(--s6) 0 0;
}
.ar-railnote {
  margin: var(--s6) 0 0; padding-top: var(--s4);
  border-top: 1px solid var(--stone);
  font-family: var(--mono); font-size: .72rem; line-height: 1.6; color: var(--ink-2);
}
.ar-railnote span {
  display: block; margin-bottom: var(--s2);
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--oxide-deep);
}

/* A chapter carrying a table is a panel and gets the full measure; the rail
   turns into a horizontal kicker. (0,2,0), which beats the (0,1,0) rules above
   on purpose. */
.ar-chapter--panel .ar-chapter__grid { grid-template-columns: minmax(0, 1fr); }
.ar-chapter--panel .ar-chapter__rail {
  grid-column: 1; position: static;
  display: flex; align-items: center; gap: var(--s4);
  margin-bottom: var(--s5);
}
.ar-chapter--panel .ar-chapter__n { font-size: 1.7rem; }
.ar-chapter--panel .ar-chapter__tick { width: 28px; height: 2px; margin: 0; }
.ar-chapter--panel .ar-chapter__body { grid-column: 1; max-width: 94ch; }
/* A chapter carrying a list needs room for two columns of points. */
.ar-chapter--points .ar-chapter__body { max-width: 78ch; }

@media (max-width: 900px) {
  .ar-chapter__grid { grid-template-columns: minmax(0, 1fr); }
  .ar-chapter__rail, .ar-chapter__body { grid-column: 1; }
  .ar-chapter__rail {
    position: static;
    display: grid; grid-template-columns: auto auto minmax(0, 1fr);
    align-items: center; gap: var(--s4);
    margin-bottom: var(--s5);
  }
  .ar-chapter__n { font-size: 1.7rem; }
  .ar-chapter__tick { width: 28px; height: 2px; margin: 0; }
  .ar-pull, .ar-railnote { grid-column: 1 / -1; margin-top: var(--s4); }
}

/* --------------------------------------------------------------- THE PROSE
   The chapters wear .prose from parts/50-read.css: the shared long-form
   language, with its own standfirst, list markers, tables and callouts. Nothing
   is restated here. Only three things are area specific.

   1. parts/20-home.css sets `.prose p, .prose li` to Ink Soft at (0,1,1), which
      is right for the homepage's short sections and wrong for a 2,000 word read.
      50-read.css corrects that for its four treatments by name and this template
      is not one of them, so the correction is repeated here at the same
      specificity. This partial concatenates last, so it wins on order. */
.ar-chapter__body p, .ar-chapter__body li { color: var(--ink); }

/* 2. An in page anchor lands on the chapter SECTION, not on its heading, so the
      section is what needs to clear the sticky header. */
.ar-chapter { scroll-margin-top: var(--s8); }

/* 3. A list of four or more points becomes a set, not a tall column. (0,2,0),
      which beats 50-read's `.prose ul` at (0,1,1) whatever the source order.
      The oxide dash markers are inherited rather than redrawn. */
.prose .ar-points {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: var(--s4) var(--s6);
}
.prose .ar-points > li { min-width: 0; margin-bottom: 0; }

.ar-opener__lede { max-width: 56ch; margin-top: var(--s5); }

/* -------------------------------------------------------------- THE PLATES
   No vertical margin on any band here, including the figures: a figure's UA
   margin would show the body ground through as a stripe between two coloured
   bands, which is exactly what the footer did on the last build. */
.ar-plate { margin: 0; }
.ar-plate--bleed img {
  width: 100%; aspect-ratio: 16 / 9; max-height: 560px;
  object-fit: cover; object-position: center 42%;  /* favours the roof, not the lawn */
}
.ar-plate--bleed figcaption { background: var(--sand); padding-block: var(--s4); }
.ar-plate--bleed figcaption .container {
  display: block; font-size: .88rem; color: var(--ink-2);
}
/* The split plate is the primary form: the source photographs are square, and
   at half width they are shown at their native aspect and stay crisp. */
.ar-plate--split, .ar-plate--note { padding-block: clamp(var(--s7), 5vw, var(--s9)); }
.ar-plate__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(var(--s6), 4vw, var(--s8));
  align-items: center;
}
.ar-plate__fig { grid-column: 1; min-width: 0; margin: 0; }
.ar-plate__note { grid-column: 2; min-width: 0; }
.ar-plate__fig img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--radius); }
.ar-plate__line {
  font-family: var(--display); font-size: clamp(1.3rem, 2.2vw, 1.85rem);
  line-height: 1.24; letter-spacing: -.015em; color: var(--ink);
  margin: 0 0 var(--s4); max-width: 34ch;
}
.ar-plate__sub { margin: 0; color: var(--ink-2); max-width: 46ch; }
@media (max-width: 860px) {
  .ar-plate__grid { grid-template-columns: minmax(0, 1fr); }
  .ar-plate__fig, .ar-plate__note { grid-column: 1; }
}

/* ----------------------------------------------------------- THE INTERLUDES
   Padding only, no margin, and each variant states its own background. */
.ar-interlude { margin: 0; padding-block: clamp(var(--s7), 5vw, var(--s8)); }
.ar-interlude__line {
  margin: 0; font-family: var(--display); font-style: italic;
  font-size: clamp(1.25rem, 2.3vw, 1.85rem); line-height: 1.32;
  letter-spacing: -.01em; max-width: 40ch;
}
.ar-interlude--quiet { border-block: 1px solid var(--stone); }
.ar-interlude__grid {
  display: grid;
  grid-template-columns: minmax(0, 96px) minmax(0, 1fr);
  gap: var(--s5);
  align-items: baseline;
}
.ar-interlude__n {
  grid-column: 1; font-family: var(--mono); font-size: .7rem;
  letter-spacing: .1em; color: var(--oxide-deep);
}
.ar-interlude--quiet .ar-interlude__line { grid-column: 2; color: var(--ink); }
@media (max-width: 640px) {
  .ar-interlude__grid { grid-template-columns: minmax(0, 1fr); }
  .ar-interlude__n, .ar-interlude--quiet .ar-interlude__line { grid-column: 1; }
  .ar-interlude__n { margin-bottom: var(--s3); }
}

/* THE ONE SLATE BAND ON THE PAGE.
   Two things make it safe, and both were defects on the sister build.

   1. IT STATES ITS OWN GROUND. There, a generic `.tr-chapters .chapter` rule at
      (0,2,0) beat `.section--slate` at (0,1,0) and the interlude rendered cream
      text on a cream ground. Here the interlude is an <aside> and not a
      .ar-chapter, so no rule above can reach it: nothing in this file selects it
      except this block.
   2. ITS TEXT COMES FROM .on-dark IN THE MARKUP, the single opt-in class in
      parts/40-core.css, which already inverts headings, body, eyebrows, the .tt
      italic, the .rule and a:not(.btn). Enumerating those again per band is the
      habit that shipped a 1.43:1 ghost button, so the only declaration here is
      the ground the class is opting into.
   Bone on slate is 11.8:1 and stone on slate is 7.2:1. */
.ar-interlude--slate { background: var(--slate); }
.ar-interlude--slate .ar-interlude__line { max-width: 44ch; }
.ar-interlude__more { margin: var(--s5) 0 0; font-size: .95rem; }

/* ------------------------------------------------------------------ NEARBY */
.ar-chips {
  list-style: none; display: flex; flex-wrap: wrap;
  gap: var(--s3); margin: 0 0 var(--s7); padding: 0;
}
.ar-chip {
  display: inline-flex; align-items: center;
  padding: var(--s3) var(--s5);
  border: 1px solid var(--stone); border-radius: 100px;
  background: transparent; color: var(--ink);
  text-decoration: none; font-size: .95rem;
  transition: background-color .15s, border-color .15s, color .15s;
}
.ar-chip:hover { background: var(--oxide); border-color: var(--oxide); color: var(--bone); }
.ar-nearby__cta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s5); }

/* ------------------------------------------------------------------ MOTION
   Movements 1 and 2 are NOT redefined here. parts/30-chrome.css owns them for
   the whole site: [data-reveal] gains .is-in and a .rule gains .is-drawn from
   assets/js/sev-motion.js, with every hidden starting state under
   html.js-motion, so a JS failure renders a fully visible page rather than a
   blank 2,000 word read. This template only has to USE those hooks, which it
   does on the facts, the service cards and the plates, and it deliberately
   leaves the chapter prose out of them: a reveal is additive to a page that is
   already readable.

   The only thing left to state is the hover lift on the service cards, which is
   this file's own, and its removal under reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .ar-svc { transition: none; }
  .ar-svc:hover { transform: none; }
  .ar-chip { transition: none; }
}

/* .ar-crumbs was written as a flex row over bare anchors. The trail now renders
   the canonical .crumbs__list markup from one shared renderer, so the flex has to
   move onto the list itself or the row collapses into a single stacked child. */
.ar-crumbs { display: block; }
.ar-crumbs .crumbs__list {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s2) var(--s3); margin: 0; padding: 0; list-style: none;
}
.ar-crumbs .crumbs__item { display: flex; align-items: center; gap: var(--s3); }
.ar-crumbs .crumbs__item + .crumbs__item::before {
  content: ""; width: 10px; height: 1px; background: var(--stone); flex: 0 0 auto;
}


/* ===== parts/70-features.css ============================================= */
/* ============================================================================
   70-features.css
   Engagement layer: technical diagrams, the three tools, the costs page and the
   contact page.

   Owned by the features group. Concatenated into main.css by
   build/css-bundle.mjs. Every colour is a token from base.css; there is not a
   single hex in this file, which is the only way six agents writing one
   stylesheet can stay on one palette.

   Component names are deliberately distinctive (.diagram .toolband .orient
   .checker .matrix .cpage .coverage .osm .twofig .nextup .cchap) so
   they cannot collide with another agent's partial. Where a base.css class does
   the job it is reused rather than reinvented, and the only base classes touched
   here are scoped overrides, never redefinitions.
   ========================================================================= */

/* -------------------------------------------------------------- page head
   Costs and contact use the house .pagehead from 50-read.css rather than a
   second page-head component: the two would drift apart within a week, and a
   parallel invention is the thing the brief forbids most plainly. Only the two
   things it does not already cover are added, both scoped. */
.pagehead .crumbs {
  display: flex; flex-wrap: wrap; gap: var(--s2);
  margin-bottom: var(--s5);
}
.pagehead .crumbs a { color: var(--ink-2); }
.pagehead .crumbs a:hover { color: var(--oxide-deep); }
/* The trust row sits on sand here, so its hairline needs the deeper token. */
.pagehead .trust { border-top-color: var(--stone); margin-top: var(--s6); }

/* A shorter band, for the form section that has to sit right under the hero. */
.section--tight { padding-block: clamp(var(--s7), 5vw, var(--s8)); }

/* Section-head leads: base.css sets .hero__lead to 46ch for the hero column,
   which is too narrow for a section opener. Scoped, not redefined. */
.section-head .hero__lead { max-width: 64ch; margin-bottom: 0; }
.section-head--centre { margin-inline: auto; text-align: center; }
.section-head--centre .rule,
.section-head--centre .hero__lead { margin-inline: auto; }

/* ------------------------------------------------------------------ callout */
.callout {
  margin: var(--s8) 0 0; max-width: 78ch;
  background: var(--sand);
  border-left: 3px solid var(--oxide);
  border-radius: var(--radius);
  padding: clamp(var(--s5), 2vw, var(--s6));
  color: var(--ink);
}
.section--sand .callout { background: var(--bone); }
.callout strong { font-family: var(--display); font-weight: 500; font-size: 1.1em; }

/* --------------------------------------------------- the two figures, costs
   No boxes and no borders: space separates them, per STYLE-GUIDE 6. */
.twofig {
  display: grid; gap: clamp(var(--s6), 4vw, var(--s8));
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
}
.twofig__card { min-width: 0; }
.twofig__n {
  display: block; font-family: var(--display);
  font-size: 2.5rem; line-height: 1; color: var(--oxide);
  margin-bottom: var(--s4);
}
.twofig__card h3 { font-size: 1.5rem; margin-bottom: var(--s4); }
.twofig__card p { margin: 0; color: var(--ink-2); }

/* ------------------------------------------------------------ tables (matrix)
   Never repeat(N, 1fr) anywhere in this file, and never a table that pushes the
   page sideways: the wrapper scrolls, the body does not. tabindex="0" in the
   markup makes that scroll reachable from the keyboard. */
.matrix {
  margin: var(--s7) 0 0;
  overflow-x: auto;
  background: var(--white);
  border: 1px solid var(--stone);
  border-radius: var(--radius);
  -webkit-overflow-scrolling: touch;
}
.matrix table { width: 100%; min-width: 660px; border-collapse: collapse; }
.matrix--tight table { min-width: 440px; }
.matrix caption {
  text-align: left; padding: var(--s4) var(--s5);
  font-family: var(--mono); font-size: .66rem;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2); border-bottom: 1px solid var(--stone);
}
.matrix th, .matrix td {
  text-align: left; vertical-align: top;
  padding: var(--s4) var(--s5);
  border-top: 1px solid var(--stone);
  font-size: .97rem;
}
.matrix thead th {
  font-family: var(--mono); font-weight: 400;
  font-size: .64rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2); background: var(--sand); border-top: 0;
}
.matrix tbody th {
  font-family: var(--display); font-weight: 400;
  font-size: 1.08rem; color: var(--ink);
  width: 22%; min-width: 150px;
}
.matrix--drivers tbody td:last-child { color: var(--ink-2); max-width: 60ch; }

/* Effect and gain pills. Small text, so oxide-deep at 7.2:1, never oxide. */
.pill {
  display: inline-block; white-space: nowrap;
  font-family: var(--mono); font-size: .62rem;
  letter-spacing: .1em; text-transform: uppercase;
  padding: 5px 10px; border-radius: 999px;
  border: 1px solid var(--stone); background: var(--bone); color: var(--ink-2);
}
.pill--high { color: var(--oxide-deep); border-color: var(--oxide); }
.pill--mid { color: var(--ink); }

/* ===========================================================================
   DIAGRAMS
   Prose lives in the figcaption and the key, which are real HTML at real sizes.
   The SVG carries short labels and single digits only, because SVG text scales
   with the viewBox: on the sister build a 15 unit note in a 520 unit drawing
   rendered at about 10px on a phone and was simply unreadable.
   ======================================================================== */
.diagram { margin: var(--s8) 0 0; }
.diagram__frame {
  background: var(--white);
  border: 1px solid var(--stone);
  border-radius: var(--radius);
  padding: clamp(var(--s3), 2vw, var(--s6));
}
.dg { display: block; width: 100%; height: auto; }

.diagram__key {
  list-style: none; margin: var(--s5) 0 0; padding: 0;
  display: grid; gap: var(--s3) var(--s7);
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
}
.diagram__key li { display: flex; gap: var(--s3); min-width: 0; }
.diagram__keyn {
  flex: 0 0 auto;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--oxide-deep); color: var(--bone);
  font-family: var(--mono); font-size: .68rem;
  display: inline-flex; align-items: center; justify-content: center;
}
.diagram__keyt { min-width: 0; font-size: .93rem; line-height: 1.6; color: var(--ink-2); }
.diagram__cap {
  margin: var(--s5) 0 0; max-width: 76ch;
  padding-left: var(--s5); border-left: 2px solid var(--oxide);
  font-size: .94rem; line-height: 1.7; color: var(--ink-2);
}

/* ------------------------------------------------------- SVG drawing palette
   Oxide strokes, ink labels, sand and stone fills. Transparency by fill-opacity
   rather than an rgba literal, so the token stays the single source of colour. */
.dg-struct { fill: var(--sand); stroke: var(--stone); stroke-width: 1.2; }
.dg-deck { fill: var(--stone); }
.dg-kerb { fill: var(--stone); stroke: var(--oxide-deep); stroke-width: 1.2; }
.dg-mem { fill: none; stroke: var(--oxide); stroke-width: 3; stroke-linejoin: round; stroke-linecap: round; }
.dg-frame { fill: var(--stone); stroke: var(--ink); stroke-width: 1.4; }
.dg-frame-line { fill: none; stroke: var(--oxide); stroke-width: 2.6; stroke-linejoin: round; }
.dg-glass { fill: var(--oxide); fill-opacity: .16; stroke: var(--oxide); stroke-width: 1.6; }
.dg-bar { stroke: var(--oxide); stroke-width: 1.6; opacity: .75; }
.dg-slope { fill: none; stroke: var(--ink); stroke-width: 3; stroke-linecap: round; }
.dg-slope-alt { fill: none; stroke: var(--ink-2); stroke-width: 1.6; stroke-dasharray: 7 6; }
.dg-ground { stroke: var(--ink-2); stroke-width: 1.2; stroke-dasharray: 5 5; }
.dg-arc { fill: var(--oxide); fill-opacity: .2; }
.dg-arc-line { fill: none; stroke: var(--stone); stroke-width: 2; stroke-dasharray: 6 7; }
.dg-flow { fill: none; stroke: var(--oxide); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.dg-beam-hard { fill: var(--oxide); fill-opacity: .24; }
.dg-beam-soft { fill: var(--oxide); fill-opacity: .12; }
.dg-dim { stroke: var(--ink-2); stroke-width: 1; }
.dg-tick { fill: none; stroke: var(--ink-2); stroke-width: 1.4; stroke-linecap: round; }
.dg-lead { fill: none; stroke: var(--ink-2); stroke-width: 1; }
/* stone slate only */
.dg-rafter { fill: none; stroke: var(--ink-2); stroke-width: 2.4; }
.dg-plane { fill: none; stroke: var(--ink); stroke-width: 1.4; }
.dg-slates { fill: var(--stone); stroke: var(--ink); stroke-width: 1.4; }
.dg-jt { stroke: var(--oxide-deep); stroke-width: 1.2; }
.dg-batten { fill: var(--sand); stroke: var(--ink-2); stroke-width: 1; }
.dg-hatch { stroke: var(--ink-2); stroke-width: 1; opacity: .5; }
.dg-zone { fill: var(--oxide); fill-opacity: .3; stroke: var(--oxide-deep); stroke-width: 1.8; stroke-dasharray: 6 5; }
.dg-bracket { fill: none; stroke: var(--oxide-deep); stroke-width: 1.6; }
.dg-num circle { fill: var(--oxide-deep); }
.dg-numt { fill: var(--bone); font-family: var(--mono); font-size: 17px; font-weight: 700; }
.dg-label { fill: var(--ink); font-family: var(--body); font-size: 18px; }
.dg-dimt { fill: var(--ink); font-family: var(--mono); font-size: 16px; }
.dg-note { fill: var(--ink-2); font-family: var(--body); font-size: 15px; }

/* The drawing shrinks with the container, so the type has to grow relative to
   the drawing or it becomes illegible on a phone. Checked at 390px. */
@media (max-width: 760px) {
  .dg-label { font-size: 24px; }
  .dg-dimt { font-size: 22px; }
  .dg-note { font-size: 21px; }
  .dg-numt { font-size: 21px; }
}

@media (hover: hover) {
  .diagram__frame { transition: transform .15s; }
  .diagram:hover .diagram__frame { transform: translateY(-2px); }
}

/* ===========================================================================
   TOOLS
   ======================================================================== */
.tool {
  display: grid; align-items: start;
  gap: clamp(var(--s6), 4vw, var(--s8));
  grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
}
@media (max-width: 900px) { .tool { grid-template-columns: minmax(0, 1fr); } }

.tool__controls {
  min-width: 0;
  display: grid; gap: var(--s4) var(--s5);
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  background: var(--white);
  border: 1px solid var(--stone);
  border-radius: var(--radius);
  padding: clamp(var(--s5), 2vw, var(--s6));
}
.tool__field { min-width: 0; }
.tool__field--wide, .tool__field--go { grid-column: 1 / -1; }
.tool__field label {
  display: block;
  font-family: var(--mono); font-size: .62rem;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-2); margin-bottom: var(--s1);
}
.tool__field input, .tool__field select {
  width: 100%; min-width: 0; font: inherit; font-size: .95rem;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--stone); border-radius: 3px;
  background: var(--white); color: var(--ink);
}
.tool__input { position: relative; display: flex; align-items: center; }
.tool__input input { padding-right: var(--s7); }
.tool__unit {
  position: absolute; right: var(--s4);
  font-family: var(--mono); font-size: .74rem; color: var(--ink-2);
  pointer-events: none;
}
.tool__field--go .btn { width: 100%; justify-content: center; }

.tool__out {
  display: block; min-width: 0;
  background: var(--white);
  border: 1px solid var(--stone);
  border-top: 3px solid var(--oxide);
  border-radius: var(--radius);
  padding: clamp(var(--s5), 2.4vw, var(--s7));
  box-shadow: var(--shadow-card);
}
.tool__label {
  display: block;
  font-family: var(--mono); font-size: .64rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
}
.tool__figure {
  display: block; font-family: var(--display); font-weight: 400;
  font-size: clamp(1.9rem, 3.4vw, 2.5rem); line-height: 1.1;
  color: var(--oxide); margin: var(--s2) 0 var(--s1);
}
.tool__sub { display: block; font-size: .9rem; color: var(--ink-2); }
.tool__rows, .orient__rows { margin: var(--s6) 0 0; display: grid; gap: var(--s4); }
.tool__rows > div, .orient__rows > div {
  display: grid; gap: var(--s1);
  grid-template-columns: minmax(0, 140px) minmax(0, 1fr);
}
.tool__rows dt, .orient__rows dt {
  margin: 0; padding-top: 3px;
  font-family: var(--mono); font-size: .62rem;
  letter-spacing: .13em; text-transform: uppercase; color: var(--ink-2);
}
.tool__rows dd, .orient__rows dd { margin: 0; min-width: 0; font-size: .95rem; color: var(--ink); }
@media (max-width: 620px) {
  .tool__rows > div, .orient__rows > div { grid-template-columns: minmax(0, 1fr); }
}
.tool__cta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s4) var(--s5); margin: var(--s6) 0 0;
}
.tool__note { font-size: .8rem; color: var(--ink-2); margin: var(--s4) 0 0; }
.toolband__foot { max-width: 76ch; font-size: .96rem; color: var(--ink-2); margin: var(--s5) 0 0; }

/* ------------------------------------------------------- orientation picker
   Radio inputs plus :checked. No script involved at any point, which is why
   there is no .js-only state here to fail into. */
.orient { position: relative; margin-bottom: var(--s7); }
.orient__pick { border: 0; margin: 0; padding: 0; min-width: 0; }
.orient__pick > legend { padding: 0; margin-bottom: var(--s3); }
.orient__radio {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.orient__tab {
  display: inline-flex; align-items: center; cursor: pointer;
  margin: 0 var(--s2) var(--s2) 0;
  padding: var(--s3) var(--s5);
  border: 1px solid var(--stone); border-radius: 999px;
  background: var(--bone); color: var(--ink); font-size: .95rem;
  transition: background-color .15s, color .15s, border-color .15s;
}
.orient__tab:hover { border-color: var(--oxide); color: var(--oxide-deep); }
.orient__radio:checked + .orient__tab {
  background: var(--oxide-deep); border-color: var(--oxide-deep); color: var(--bone);
}
.orient__radio:focus-visible + .orient__tab { outline: 2px solid var(--oxide-deep); outline-offset: 3px; }

.orient__panel {
  display: none; margin-top: var(--s5);
  background: var(--white);
  border: 1px solid var(--stone); border-left: 3px solid var(--oxide);
  border-radius: var(--radius);
  padding: clamp(var(--s5), 2.4vw, var(--s7));
}
.orient__radio[value="north"]:checked ~ .orient__panel--north,
.orient__radio[value="east"]:checked ~ .orient__panel--east,
.orient__radio[value="south"]:checked ~ .orient__panel--south,
.orient__radio[value="west"]:checked ~ .orient__panel--west,
.orient__radio[value="flat"]:checked ~ .orient__panel--flat { display: block; }
.orient__panel h3 { font-size: 1.5rem; margin-bottom: var(--s4); }
.orient__sub {
  display: block; margin-top: var(--s2);
  font-family: var(--body); font-size: .9rem;
  letter-spacing: 0; color: var(--ink-2);
}
.orient__panel > p { margin: 0; max-width: 72ch; color: var(--ink); }

/* --------------------------------------------------------------- checklist */
.checker {
  display: grid; align-items: start;
  gap: clamp(var(--s6), 4vw, var(--s8));
  grid-template-columns: minmax(0, 1fr) minmax(0, 360px);
}
@media (max-width: 920px) { .checker { grid-template-columns: minmax(0, 1fr); } }
.checker__list { list-style: none; margin: 0; padding: 0; min-width: 0; }
.checker__item {
  display: grid; gap: var(--s4);
  grid-template-columns: auto minmax(0, 1fr);
  padding: var(--s5) 0;
  border-bottom: 1px solid var(--stone);
}
.checker__item:first-child { padding-top: 0; }
.checker__box {
  width: 22px; height: 22px; margin: 2px 0 0;
  accent-color: var(--oxide-deep); flex: 0 0 auto;
}
.checker__item label { display: block; min-width: 0; cursor: pointer; }
.checker__t { display: block; font-family: var(--display); font-size: 1.14rem; color: var(--ink); }
.checker__d { display: block; margin-top: var(--s1); font-size: .92rem; line-height: 1.6; color: var(--ink-2); }
.checker__box:checked ~ label .checker__t { color: var(--oxide-deep); }

.checker__out {
  display: block; min-width: 0;
  position: sticky; top: var(--s9);
  background: var(--white);
  border: 1px solid var(--stone); border-top: 3px solid var(--oxide);
  border-radius: var(--radius);
  padding: clamp(var(--s5), 2.4vw, var(--s6));
  box-shadow: var(--shadow-card);
}
@media (max-width: 920px) { .checker__out { position: static; } }
.checker__meter {
  display: block; height: 4px; margin: var(--s4) 0 var(--s5);
  background: var(--stone); border-radius: 999px; overflow: hidden;
}
.checker__meter i {
  display: block; height: 100%; background: var(--oxide);
  transform-origin: left;
  transition: transform .3s cubic-bezier(.22, .61, .36, 1);
}
.checker__verdict { margin: 0 0 var(--s5); font-size: .94rem; line-height: 1.65; color: var(--ink-2); }

/* ===========================================================================
   CONTACT PAGE
   ======================================================================== */
.cpage {
  display: grid; align-items: start;
  gap: clamp(var(--s6), 4vw, var(--s8));
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 380px);
}
@media (max-width: 980px) { .cpage { grid-template-columns: minmax(0, 1fr); } }
.cpage__form, .cpage__aside { min-width: 0; }
.cpage__hint { margin: var(--s5) 0 0; max-width: 58ch; font-size: .92rem; color: var(--ink-2); }

.cpage__aside {
  background: var(--sand);
  border-radius: var(--radius);
  padding: clamp(var(--s5), 2.4vw, var(--s7));
}
.section--sand .cpage__aside { background: var(--bone); }
.cpage__aside h2 { font-size: 1.5rem; margin-bottom: var(--s5); }
.cpage__rows { margin: 0; display: grid; gap: var(--s5); }
.cpage__row { display: grid; gap: var(--s1); min-width: 0; }
.cpage__row dt, .cpage__row dd { margin: 0; }
.cpage__val { font-size: 1rem; line-height: 1.6; color: var(--ink); overflow-wrap: anywhere; }
a.cpage__val {
  color: var(--oxide-deep); text-decoration: none;
  border-bottom: 1px solid var(--stone);
}
a.cpage__val:hover { border-bottom-color: var(--oxide); }
.cpage__note {
  margin: var(--s6) 0 0; padding-top: var(--s5);
  border-top: 1px solid var(--stone);
  font-size: .88rem; color: var(--ink-2);
}

/* -------------------------------------------------------- what happens next */
.nextup {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: clamp(var(--s6), 4vw, var(--s7));
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}
.nextup__step { min-width: 0; }
.nextup__n {
  display: flex; align-items: center; gap: var(--s3);
  margin-bottom: var(--s4);
  font-family: var(--mono); font-size: .78rem;
  letter-spacing: .14em; color: var(--oxide-deep);
}
.nextup__n::after { content: ""; flex: 1; height: 2px; background: var(--oxide); opacity: .35; }
.nextup__step h3 { font-size: 1.35rem; margin-bottom: var(--s3); }
.nextup__step p { margin: 0; font-size: .98rem; color: var(--ink-2); }
.nextup__step a { color: var(--oxide-deep); }

/* ------------------------------------------------------------ service radius
   A real Leaflet L.circle on OpenStreetMap tiles, from a copy of Leaflet
   bundled in the theme. Never a CSS ring over a map iframe: that is correct
   only at the zoom it was drawn at and slides off the moment anybody pans. */
.osm { margin: 0 0 var(--s8); }
.osm__frame {
  position: relative;
  height: clamp(320px, 46vw, 520px);
  background: var(--sand);
  border: 1px solid var(--stone);
  border-radius: var(--radius);
  overflow: hidden;
  z-index: 0; /* keeps Leaflet's panes under the sticky site header */
}
.osm__fallback {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: var(--s5); text-align: center;
  color: var(--oxide-deep); text-decoration: none;
  font-size: 1.05rem;
}
.osm__fallback:hover { text-decoration: underline; }
.osm__note { margin: var(--s4) 0 0; font-size: .88rem; color: var(--ink-2); }
.osm__note a { color: var(--oxide-deep); overflow-wrap: anywhere; }

/* ------------------------------------------------------------- coverage list
   The map is the picture; this is the answer. It is always present, so a
   reader with no script, or on a metered connection, loses nothing. */
.coverage {
  display: grid; gap: var(--s7) var(--s8);
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
}
.coverage__group { min-width: 0; }
.coverage__group h3 {
  display: flex; align-items: baseline; gap: var(--s3);
  font-size: 1.26rem; margin-bottom: var(--s3);
}
.coverage__n {
  flex: 0 0 auto;
  font-family: var(--mono); font-size: .7rem;
  letter-spacing: .1em; color: var(--oxide-deep);
}
.coverage__group p { margin: 0; font-size: .96rem; line-height: 1.7; color: var(--ink-2); }
.coverage__more { margin: var(--s7) 0 0; }

/* ===========================================================================
   COSTS ARTICLE CHAPTERS  (.cchap*, kept clear of 50-read.css .article__inner)
   ======================================================================== */
.cchap + .cchap { margin-top: var(--s9); }
.cchap__mark { display: flex; align-items: center; gap: var(--s4); margin-bottom: var(--s5); }
.cchap__ray { flex: 1; height: 1px; background: var(--stone); }
.cchap__n { font-family: var(--mono); font-size: .76rem; letter-spacing: .16em; color: var(--oxide-deep); }
.cchaps .prose, .prose--centre { max-width: 68ch; margin-inline: auto; }
.cchaps .prose h2 { font-size: clamp(1.7rem, 2.8vw, 2.3rem); }

/* ===========================================================================
   Reduced motion: removed, not shortened, per MOTION-PROFILE.
   ======================================================================== */
@media (prefers-reduced-motion: reduce) {
  .checker__meter i,
  .diagram__frame,
  .orient__tab { transition: none; }
  .diagram:hover .diagram__frame { transform: none; }
}


/* ===== parts/80-dossier.css ============================================== */
/* ===========================================================================
 * THE TECHNICAL DOSSIER
 *
 * Long-form service pages and the homepage. The brief was a specification
 * document from a practice that knows what it is doing: numbered sections,
 * measured drawings with keys, hard figures pulled out of the prose, and one
 * dark band where a 4,000 word read needs a breath.
 *
 * Everything here is built from the tokens already in base.css. No new colour
 * is introduced, because the palette was contrast-checked and the Slate band is
 * the one dark pairing that passed.
 * ======================================================================== */

/* ------------------------------------------------------------------ SECTION
 * The numeral is the spine of the whole layout. It sits in the margin on wide
 * screens and above the heading on narrow ones, and it is decorative: the
 * heading text alone still reads correctly to a screen reader.
 */

/* MEASURE, AND WHAT IS ALLOWED TO BREAK OUT OF IT.
 *
 * parts/50-read.css caps .dossier__body at 72ch, which is right for prose and
 * wrong for everything else: it left the drawings and photographs inside a
 * narrow column with roughly 300px of empty ground between them and the rail,
 * which is the single most obviously undesigned thing on the page.
 *
 * The cap moves off the container and onto the prose itself. Text keeps a
 * readable measure. Drawings, photographs, panels and bands take the full
 * column. That contrast between a narrow text block and a wide figure is the
 * rhythm a long read is missing when it reads as a wall. */
.dossier__grid .dossier__body { max-width: none; }

.dsec > .prose,
.dsec__figbody,
.dsec__splitbody,
.dsec__steps,
.dsec__check,
.dossier__body > .read-intro { max-width: 68ch; }

/* The panel is a table, and a table wants the room. */
.dsec__panel { max-width: none; }

.dsec {
  position: relative;
  margin: 0 0 var(--s8);
  scroll-margin-top: 6rem;
}

.dsec__head {
  display: grid;
  grid-template-columns: 3.25rem 1fr;
  align-items: baseline;
  column-gap: 1rem;
  margin: 0 0 var(--s3);
}

.dsec__num {
  font-family: var(--mono, ui-monospace, "SFMono-Regular", Menlo, monospace);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  color: var(--oxide);
  padding-top: 0.55rem;
  border-top: 2px solid var(--oxide);
  display: block;
}

.dsec__title {
  font-family: var(--display);
  font-size: clamp(1.45rem, 2.3vw, 1.95rem);
  line-height: 1.18;
  margin: 0;
  color: var(--ink);
}

/* The rule that closes a section. Hairline, stone, and absent on the last one
 * so the page does not end on a divider. */
.dsec + .dsec { border-top: 1px solid var(--stone); padding-top: var(--s7); }

@media (max-width: 700px) {
  .dsec__head { grid-template-columns: 1fr; row-gap: 0.4rem; }
  .dsec__num { border-top: none; padding-top: 0; }
}

/* ------------------------------------------------------------------- FIGURE
 * The differentiator. A drawing beside its prose on wide screens, drawing
 * first on narrow ones, because the drawing is the point.
 */

.dsec__figwrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s5);
}

@media (min-width: 900px) {
  .dsec__figwrap { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); align-items: start; }
}

.dsec__fig {
  position: relative;
  background: var(--sand);
  border: 1px solid var(--stone);
  border-radius: var(--radius, 2px);
  padding: var(--s4);
}

/* The figure number, set as a drawing sheet would set it. */
.dsec__fig::before {
  content: "Fig. " attr(data-fig);
  position: absolute;
  top: 0.6rem;
  right: 0.8rem;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.dsec__fig svg { display: block; width: 100%; height: auto; }

/* -------------------------------------------------------------------- SPLIT
 * Photograph beside prose. The side alternates down the page, set per section
 * in PHP, so the eye is not led down one edge.
 */

.dsec__split { display: grid; gap: var(--s5); grid-template-columns: minmax(0, 1fr); }

@media (min-width: 900px) {
  .dsec__split { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); align-items: start; }
  .dsec--left  .dsec__media { order: 1; }
  .dsec--left  .dsec__splitbody { order: 2; }
  .dsec--right .dsec__media { order: 2; }
  .dsec--right .dsec__splitbody { order: 1; }
}

.dsec__media { margin: 0; }

.dsec__media img,
.dsec__plate img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--stone);
  border-radius: var(--radius, 2px);
}

/* -------------------------------------------------------------------- PLATE
 * Full width photograph under its prose, with a technical caption.
 */

.dsec__plate { margin: var(--s5) 0 0; }

.dsec__plate figcaption,
.dsec__media figcaption {
  margin-top: 0.6rem;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--ink-2);
  border-left: 2px solid var(--oxide);
  padding-left: 0.7rem;
}

/* -------------------------------------------------------------- SPEC STRIP
 * Hard numbers lifted out of the paragraph above. Never authored separately,
 * so it cannot drift from the prose or invent a figure.
 */

.specstrip {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--s5);
  list-style: none;
  margin: var(--s4) 0 0;
  padding: 0;
  border-top: 1px solid var(--stone);
  padding-top: var(--s3);
}

.specstrip li {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.86rem;
  letter-spacing: 0.02em;
  color: var(--ink);
  padding-left: 0.7rem;
  border-left: 2px solid var(--oxide);
  margin: 0.35rem 0;
}

/* -------------------------------------------------------------------- PANEL
 * A table, set as a data panel. The wrapper from sev_wrap_tables() still does
 * the horizontal scrolling; this is the frame around it.
 */

.dsec__panel .matrix,
.dsec__panel table { margin-top: var(--s3); }

.dsec__panel table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.92rem;
}

.dsec__panel th {
  text-align: left;
  font-family: var(--body);
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
  border-bottom: 2px solid var(--oxide);
  padding: 0.55rem 0.9rem 0.55rem 0;
  vertical-align: bottom;
}

.dsec__panel td {
  padding: 0.7rem 0.9rem 0.7rem 0;
  border-bottom: 1px solid var(--stone);
  vertical-align: top;
}

.dsec__panel tr:last-child td { border-bottom: none; }

/* -------------------------------------------------------------------- STEPS
 * A list that describes a sequence. Numbered, with a connective rule, because
 * an installation runs in an order and the order is the information.
 */

.dsec__steps ul,
.dsec__steps ol {
  list-style: none;
  margin: var(--s3) 0 0;
  padding: 0;
  counter-reset: sevstep;
}

.dsec__steps li {
  counter-increment: sevstep;
  position: relative;
  padding: 0 0 var(--s4) var(--s6);
  margin: 0;
}

.dsec__steps li::before {
  content: counter(sevstep, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0.05rem;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.76rem;
  letter-spacing: 0.1em;
  color: var(--oxide);
}

/* The connective line, stopping short of the last item. */
.dsec__steps li::after {
  content: "";
  position: absolute;
  left: 0.65rem;
  top: 1.5rem;
  bottom: 0.4rem;
  width: 1px;
  background: var(--stone);
}
.dsec__steps li:last-child { padding-bottom: 0; }
.dsec__steps li:last-child::after { display: none; }

/* ---------------------------------------------------------------- CHECKLIST
 * A set of parallel facts, set as the survey checklist it usually is.
 */

.dsec__check ul {
  list-style: none;
  margin: var(--s3) 0 0;
  padding: var(--s4);
  background: var(--sand);
  border: 1px solid var(--stone);
  border-left: 3px solid var(--oxide);
  border-radius: var(--radius, 2px);
}

.dsec__check li {
  position: relative;
  padding-left: 1.5rem;
  margin: 0 0 0.7rem;
}

.dsec__check li:last-child { margin-bottom: 0; }

.dsec__check li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.6rem;
  height: 1px;
  background: var(--oxide);
}

/* ---------------------------------------------------------------- INTERLUDE
 * The one dark band. Bone on Slate, which is the pairing the style guide
 * already contrast-checked. Full bleed, and with no vertical margin, which is
 * the locked rule from §6.
 */

.interlude {
  background: var(--slate);
  color: var(--bone);
  margin: var(--s8) 0;
  padding: clamp(2.4rem, 6vw, 4rem) var(--s4);
}

.interlude__inner { max-width: 46rem; margin: 0 auto; }

/* SPECIFICITY, DELIBERATELY.
 *
 * parts/50-read.css carries `.dossier__body p { color: var(--ink) }`, correctly,
 * because Ink is the body colour for a long read. The interlude lives inside
 * .dossier__body, so a single-class rule here loses to it and the band rendered
 * warm near-black on Deep Slate: 1.34:1, which the computed contrast gate caught
 * on every service page. These selectors clear (0,2,0) so the dark band keeps
 * the Bone-on-Slate pairing the style guide contrast-checked. */
.interlude .interlude__line,
.dossier__body .interlude p.interlude__line {
  font-family: var(--display);
  font-size: clamp(1.4rem, 3vw, 2.1rem);
  line-height: 1.28;
  margin: 0;
  color: var(--bone);
}

.interlude .interlude__line em,
.dossier__body .interlude p.interlude__line em {
  color: var(--sand);
  font-style: italic;
}

.interlude .interlude__sub,
.dossier__body .interlude p.interlude__sub {
  margin: var(--s3) 0 0;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--stone);
}

/* -------------------------------------------------------------------- MIDCTA
 * One conversion band, after the page has framed a problem. Sand rather than
 * Slate, so it reads as an offer rather than as a second interlude.
 */

.midcta {
  background: var(--sand);
  border-top: 2px solid var(--oxide);
  border-bottom: 1px solid var(--stone);
  margin: var(--s8) 0;
  padding: var(--s6) var(--s4);
}

.midcta__inner {
  max-width: 46rem;
  margin: 0 auto;
  display: grid;
  gap: var(--s4);
  align-items: center;
}

@media (min-width: 780px) {
  .midcta__inner { grid-template-columns: minmax(0, 1fr) auto; gap: var(--s6); }
}

.midcta__lead {
  margin: 0;
  font-size: 1.02rem;
  line-height: 1.6;
  color: var(--ink);
}

.midcta .btn { white-space: nowrap; }

/* --------------------------------------------------------------- READ INTRO
 * The standfirst above section 01. Larger, and set in the display face, so the
 * page opens on a voice rather than on body copy.
 */

.dossier__body > .read-intro p:first-child {
  font-family: var(--display);
  font-size: clamp(1.12rem, 1.8vw, 1.32rem);
  line-height: 1.5;
  color: var(--ink);
}

.dossier__body > .read-intro { margin-bottom: var(--s7); }

/* ------------------------------------------------------- REDUCED MOTION etc.
 * Nothing here animates, so there is nothing to suppress. Recorded so the next
 * person does not go looking for a motion rule that was never needed.
 */

/* ------------------------------------------------------------- GUIDE BREAKS
 * Guides keep the flow treatment. These are the three pieces of furniture
 * dropped between chapters: a drawing, a photograph and the enquiry band.
 * Deliberately quieter than the dossier equivalents so the two tiers still
 * read as different page types.
 */

.flow__figure { margin: var(--s7) 0; }

.flow__figure .diagram {
  background: var(--sand);
  border: 1px solid var(--stone);
  border-radius: var(--radius, 2px);
  padding: var(--s4);
  margin: 0;
}

.flow__plate { margin: var(--s7) 0; }

.flow__plate img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--stone);
  border-radius: var(--radius, 2px);
}

.flow__plate figcaption {
  margin-top: 0.6rem;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--ink-2);
  border-left: 2px solid var(--oxide);
  padding-left: 0.7rem;
}

/* ---------------------------------------------------------------- WORDMARK
 * The mark is 26px and the words sit next to it, on one line. Both were broken
 * by `svg:not([width])` in base.css sizing the mark to the full brand box; the
 * width attribute in header.php is the real fix and these are the belt and
 * braces, including the nowrap that stops "Severn Skylights." breaking over two
 * lines in a header that has the room for one.
 */
.brand { gap: 0.55rem; }
.brand__mark { width: 26px; height: 26px; flex: 0 0 auto; }
.brand strong { white-space: nowrap; }

@media (max-width: 420px) {
  /* Only here is there genuinely not enough room for one line. */
  .brand strong { white-space: normal; }
}


/* ===== parts/85-homeread.css ============================================= */
/* ===========================================================================
 * THE HOMEPAGE LONG-READ
 *
 * Deliberately NOT the service-page dossier. The dossier is a narrow measure in
 * a grid with a sticky contents card, which is right for a reference document
 * somebody arrives at wanting one clause of. Two sister builds give their
 * homepage that same treatment, which is why three brands open on the same
 * skeleton.
 *
 * This is built for scrolling instead of navigating:
 *   - full-bleed zones that alternate Bone and Sand every two chapters
 *   - a large oxide display numeral in the margin, against the dossier's small
 *     mono one
 *   - a thin progress line rather than a rail
 *   - a dark threshold band so the read announces itself
 *
 * §6 is respected throughout: no vertical margin on any full-bleed band, only
 * padding, so no body ground shows through as a stripe.
 * ======================================================================== */

.homeread { position: relative; }

/* ---------------------------------------------------------------- PROGRESS */

.homeread__progress {
  position: sticky;
  top: 0;
  z-index: 90;
  height: 2px;
  background: transparent;
  /* Sticky inside a block that starts below the header, so it rides the read
     rather than the whole document. */
}

.homeread__progress span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--oxide);
  transition: width .12s linear;
}

@media (prefers-reduced-motion: reduce) {
  .homeread__progress { display: none; }
}

/* --------------------------------------------------------------- THRESHOLD
 * The one dark band that opens the read. Bone on Slate, the pairing already
 * contrast-checked in the style guide.
 */

.homeread__threshold {
  background: var(--slate);
  color: var(--bone);
  padding: clamp(3rem, 7vw, 5.5rem) 0;
}

.homeread__threshold-in { max-width: 62rem; }

.homeread__threshold .eyebrow { color: var(--stone); }

.homeread__thresh-line {
  font-family: var(--display);
  font-size: clamp(1.7rem, 4vw, 2.9rem);
  line-height: 1.16;
  margin: var(--s3) 0 0;
  color: var(--bone);
  max-width: 20ch;
}

.homeread__thresh-line em { color: var(--sand); font-style: italic; }

/* The standfirst, set once, wide. */
.homeread__lede { margin: var(--s5) 0 0; max-width: 56ch; }

.homeread__threshold .homeread__lede p {
  color: var(--stone);
  font-size: 1.03rem;
  line-height: 1.65;
  margin: 0 0 var(--s3);
}

.homeread__threshold .homeread__lede p:last-child { margin-bottom: 0; }

.homeread__meta {
  display: flex;
  gap: var(--s5);
  margin: var(--s6) 0 0;
  padding-top: var(--s4);
  border-top: 1px solid rgba(248, 245, 239, .22);
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .74rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--stone);
}

/* -------------------------------------------------------------------- ZONES
 * Alternating ground, edge to edge, padding only. Changing every second
 * chapter rather than every chapter keeps it to a handful of bands.
 */

.homeread__zone { background: var(--bone); padding: clamp(2.6rem, 5vw, 4.2rem) 0; }
.homeread__zone--sand { background: var(--sand); }

/* Consecutive zones on the same ground would otherwise show a double pad where
   they meet, which reads as a gap rather than as one band. */
.homeread__zone + .homeread__zone { padding-top: 0; }
.homeread__zone--sand + .homeread__zone:not(.homeread__zone--sand),
.homeread__zone:not(.homeread__zone--sand) + .homeread__zone--sand {
  padding-top: clamp(2.6rem, 5vw, 4.2rem);
}

/* ----------------------------------------------------------------- CHAPTER */

.hchap {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s2);
  max-width: 74rem;
}

@media (min-width: 900px) {
  .hchap {
    grid-template-columns: 6.5rem minmax(0, 1fr);
    gap: 0 var(--s6);
    align-items: start;
  }
}

/* The numeral. Large, oxide, display face. This is the single clearest
   difference from the dossier, where the same information is a 0.78rem mono
   label. */
.hchap__num {
  font-family: var(--display);
  font-size: clamp(2.4rem, 5vw, 4rem);
  line-height: .85;
  color: var(--oxide);
  opacity: .34;
  margin: 0;
  letter-spacing: -.02em;
}

@media (min-width: 900px) {
  .hchap__num { text-align: right; position: sticky; top: 5.5rem; }
}

.hchap__title {
  font-family: var(--display);
  font-size: clamp(1.55rem, 2.6vw, 2.15rem);
  line-height: 1.15;
  margin: 0 0 var(--s4);
  color: var(--ink);
  max-width: 24ch;
}

.hchap__body { min-width: 0; }

/* Inside a chapter the patterns keep their dossier styling but lose the
   numbered head, which the chapter supplies instead. */
.hchap .dsec { margin: 0; }
.hchap .dsec + .dsec { border-top: 0; padding-top: 0; }
.hchap .dsec > .prose,
.hchap .dsec__figbody,
.hchap .dsec__splitbody,
.hchap .dsec__steps,
.hchap .dsec__check { max-width: 66ch; }

/* On Sand, a figure frame that is also Sand disappears. Flip it to Bone so the
   drawing still reads as a plate on the page. */
.homeread__zone--sand .dsec__fig { background: var(--bone); }
.homeread__zone--sand .dsec__check ul { background: var(--bone); }

/* The bands between chapters sit inside the zone flow, so they take padding
   rather than margin, per §6. */
.homeread .midcta,
.homeread .interlude { margin: 0; }

/* ------------------------------------------------------- THRESHOLD CONTENTS
 * The index that replaces the sticky rail on a service page. It sits in the
 * dark threshold band at the head of the read, so navigation survives without
 * the narrow column and the dead ground the rail forced beside it.
 */

.threshindex {
  margin: var(--s6) 0 0;
  padding-top: var(--s5);
  border-top: 1px solid rgba(248, 245, 239, .22);
}

.threshindex ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  gap: 0 var(--s6);
}

.threshindex li { border-bottom: 1px solid rgba(248, 245, 239, .12); }

.threshindex a {
  display: grid;
  grid-template-columns: 2.4rem 1fr;
  gap: var(--s2);
  align-items: baseline;
  padding: var(--s3) 0;
  text-decoration: none;
  color: var(--bone);
}

.threshindex a:hover .threshindex__t,
.threshindex a:focus-visible .threshindex__t {
  color: var(--sand);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.threshindex a:focus-visible {
  outline: 2px solid var(--oxide);
  outline-offset: 3px;
}

.threshindex__n {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .68rem;
  letter-spacing: .12em;
  /* Stone on Slate is the checked pairing; the numerals are secondary here so
     they take the softer of the two rather than full Bone. */
  color: var(--stone);
}

.threshindex__t {
  font-size: .93rem;
  line-height: 1.4;
  color: var(--bone);
}

/* The threshold holds more on a service page than on the homepage, so it needs
   the room. */
.homeread__threshold-in { max-width: 72rem; }

/* The page title, when the threshold carries the h1 rather than a hero. */
.homeread__h1 {
  font-family: var(--display);
  font-size: clamp(2.1rem, 4.6vw, 3.4rem);
  line-height: 1.08;
  margin: var(--s3) 0 0;
  color: var(--bone);
  max-width: 18ch;
  letter-spacing: -.01em;
}

/* With a title above it the standfirst line steps down so the two do not
   compete for the same weight. */
.homeread__h1 + .homeread__thresh-line {
  font-size: clamp(1.15rem, 2vw, 1.5rem);
  color: var(--sand);
  max-width: 34ch;
  margin-top: var(--s4);
}

/* Links inside the dark threshold.
 *
 * The lede in this band is real page content and several service pages open
 * with a cross-link in it. Nothing styled an anchor here, so it rendered in the
 * browser default blue on Deep Slate: 1.14:1, the worst contrast on the site,
 * and invisible in practice. Sand on Slate is the pairing already checked for
 * the italic half of the threshold line. */
.homeread__threshold a {
  color: var(--sand);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

.homeread__threshold a:hover,
.homeread__threshold a:focus-visible { color: var(--bone); }

.homeread__threshold a:focus-visible {
  outline: 2px solid var(--oxide);
  outline-offset: 2px;
}


/* ===== parts/86-longform-craft.css ======================================= */
/* ===========================================================================
 * LONG-FORM CRAFT — art direction and data panels
 *
 * Two things were wrong and both were mine.
 *
 * IMAGES. Every source photograph is 1024x1024. They were being placed at
 * whatever width the slot happened to be, so the same square rendered at 373px
 * beside a text column and at 902px as a full width plate. A 902px square
 * dropped into the middle of a read is not a photograph in a layout, it is a
 * hole in the page, and the two sizes together are the size variance the
 * operator could see. Photographs are now ART DIRECTED: fixed aspect ratios,
 * cropped with object-fit, identical every time a given pattern is used.
 *
 * TABLES. 272 of them, mostly three and four columns, wrapped in a bare
 * focusable scroller with no frame, no label and no column discipline, sitting
 * loose in the prose. They are the densest technical content on the site and
 * they were the least designed thing on it. They are now plates in their own
 * right, numbered in the same language as the drawings.
 * ======================================================================== */

/* ==================================================================== IMAGES
 * One ratio per pattern, applied everywhere that pattern appears, so the eye
 * learns the rhythm instead of meeting a new size each time.
 *
 *   plate  2.2 : 1   a wide band across the measure
 *   split  4 : 5     a portrait sitting beside its text
 *   guide  16 : 9    the single photograph in a guide
 *
 * object-position favours the upper middle because these are roof photographs:
 * the subject is above the horizon and a centre crop cuts the roof off.
 */

.dsec__plate img,
.dsec__media img,
.flow__plate img {
  object-fit: cover;
  object-position: 50% 38%;
  width: 100%;
  height: auto;
}

.dsec__plate img { aspect-ratio: 2.2 / 1; }
.dsec__media img { aspect-ratio: 4 / 5; }
.flow__plate img { aspect-ratio: 16 / 9; }

@media (max-width: 899px) {
  /* Stacked, a portrait crop is too tall for a phone and a 2.2:1 band is too
     thin. Both settle on the same 3:2, which is the only place the two
     treatments are allowed to converge. */
  .dsec__plate img,
  .dsec__media img { aspect-ratio: 3 / 2; }
}

/* The frame. A hairline and a very slight inset shadow, so a pale roof against
   a pale sky still reads as a photograph and not as a gap in the page. */
.dsec__plate img,
.dsec__media img,
.flow__plate img {
  border: 1px solid var(--stone);
  border-radius: 0;
  box-shadow: 0 1px 0 rgba(42, 43, 38, .04);
}

/* Caption, set as a technical note rather than as a sentence of body copy. */
.dsec__plate figcaption,
.dsec__media figcaption,
.flow__plate figcaption {
  margin-top: 0.7rem;
  padding-left: 0.75rem;
  border-left: 2px solid var(--oxide);
  font-family: var(--body);
  font-size: 0.8rem;
  line-height: 1.5;
  letter-spacing: 0.01em;
  color: var(--ink-2);
  max-width: 52ch;
}

/* In a split the picture and the prose should start on the same line. */
@media (min-width: 900px) {
  .dsec__split { align-items: start; }
  .dsec__media { position: sticky; top: 5.5rem; }
}

/* ============================================================== DATA PANELS
 * A table is a drawing made of words. It gets the same treatment: a frame, a
 * ground that separates it from the prose, a number, and a rule under the
 * header row in the accent.
 */

.dossier__body,
.hchap__body,
.flow__inner,
.article__inner,
.answer__inner { counter-reset: sevtable; }

.tablewrap {
  counter-increment: sevtable;
  position: relative;
  margin: var(--s6) 0;
  padding: var(--s5) var(--s5) var(--s4);
  background: var(--bone);
  border: 1px solid var(--stone);
  border-top: 2px solid var(--oxide);
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* On a sand ground the panel flips to bone, and vice versa, so it always
   separates from what it is sitting on. */
.section--sand .tablewrap,
.homeread__zone--sand .tablewrap,
.dsec__check .tablewrap { background: var(--bone); }

.tablewrap::before {
  content: "Table " counter(sevtable, decimal-leading-zero);
  display: block;
  margin-bottom: var(--s4);
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.tablewrap:focus-visible {
  outline: 2px solid var(--oxide);
  outline-offset: 2px;
}

.tablewrap table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
  line-height: 1.5;
  /* Numerals line up in columns, which is most of what makes a specification
     table look measured rather than typed. */
  font-variant-numeric: tabular-nums;
}

.tablewrap th {
  text-align: left;
  vertical-align: bottom;
  font-family: var(--mono, ui-monospace, monospace);
  font-weight: 400;
  font-size: 0.66rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-2);
  padding: 0 var(--s4) var(--s3) 0;
  border-bottom: 1px solid var(--oxide);
  white-space: nowrap;
}

.tablewrap td {
  vertical-align: top;
  padding: var(--s4) var(--s4) var(--s4) 0;
  border-bottom: 1px solid var(--stone);
  color: var(--ink);
}

.tablewrap th:last-child,
.tablewrap td:last-child { padding-right: 0; }

.tablewrap tr:last-child td { border-bottom: 0; padding-bottom: 0; }

/* The first column is the subject of every row on this site: the build-up, the
   covering, the situation. It carries the weight. */
.tablewrap td:first-child {
  color: var(--ink);
  font-weight: 500;
  min-width: 11ch;
}

/* Three and four column tables are 232 of the 272. Giving the first column
   room and letting the rest share what is left stops the ragged wrapping that
   made these look thrown together. */
.tablewrap table { table-layout: auto; }
.tablewrap td, .tablewrap th { min-width: 9ch; }

/* The scroll affordance. Without it a table wider than its panel just looks
   cut off. */
.tablewrap::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 2.2rem;
  pointer-events: none;
  background: linear-gradient(to right, rgba(248, 245, 239, 0), var(--bone));
  opacity: 0;
  transition: opacity .15s ease;
}

@media (max-width: 700px) {
  /* Below this a three column table always scrolls, so the affordance is
     always right. */
  .tablewrap::after { opacity: 1; }
  .tablewrap { padding-left: var(--s4); padding-right: var(--s4); }
  .tablewrap table { font-size: 0.84rem; }
  .tablewrap td { padding: var(--s3) var(--s3) var(--s3) 0; }
}

@media (prefers-reduced-motion: reduce) {
  .tablewrap::after { transition: none; }
}

/* ============================================================ OPENING VOICE
 * A drop cap on the first paragraph of a long read. One per page, on the
 * homepage read and on a service dossier, never on a short answer where it
 * would be louder than the answer.
 */

.homeread__zone:first-of-type .hchap:first-of-type .dsec > .prose > p:first-of-type::first-letter,
.dossier__body .dsec:first-of-type .prose > p:first-of-type::first-letter {
  float: left;
  font-family: var(--display);
  font-size: 3.6em;
  line-height: 0.82;
  padding: 0.06em 0.14em 0 0;
  color: var(--oxide);
}

@media (max-width: 600px) {
  .homeread__zone:first-of-type .hchap:first-of-type .dsec > .prose > p:first-of-type::first-letter,
  .dossier__body .dsec:first-of-type .prose > p:first-of-type::first-letter {
    font-size: 2.8em;
  }
}

/* ------------------------------------------------------- PANEL BREAKS OUT
 * `.dsec > .prose { max-width: 68ch }` is (0,2,0) and beat the earlier
 * `.dsec__panel { max-width: none }` at (0,1,0), so data panels were being
 * squeezed into the prose measure at about 620px and every three column table
 * scrolled when it did not need to. A table is not prose and does not take the
 * prose measure.
 */
.dsec > .dsec__panel,
.hchap .dsec > .dsec__panel { max-width: none; }

/* Same for the guide and article treatments, where the panel sits directly in
   the reading column. */
.flow__body .tablewrap,
.article__inner .tablewrap,
.answer__inner .tablewrap { max-width: none; }

/* ============================================================ THE TEXT SPINE
 * Prose was capped at a readable measure and then left flush against the left
 * edge of a much wider column, so any section without a photograph ran 623px of
 * text inside 902px of column and left 279px of empty ground down the right.
 * The section heading meanwhile ran the full 902px, so the heading and its own
 * paragraphs did not even share an edge.
 *
 * The fix is the standard one for a long read and it should have been here from
 * the start: a CENTRED TEXT SPINE at a fixed measure, with the things that are
 * not text allowed to break out of it. Prose, headings, steps, checklists and
 * the figures strip share one centred column. Drawings, photographs and data
 * panels take the full width. The contrast between the two is what gives a long
 * read its rhythm, and it removes the ragged right entirely.
 */

/* THE SPINE IS AN ABSOLUTE LENGTH, NOT ch.
 *
 * It was 68ch, and ch is relative to the font size of the element it is applied
 * to. On body copy that is about 623px, which is right. On a chapter title set
 * at 34px the same 66ch computes to roughly 1120px, so the title was never
 * constrained at all and sat flush left at full width while its own paragraphs
 * sat centred beneath it. A spine that has to align elements set at different
 * sizes cannot be measured in a unit that changes with size.
 *
 * 39rem is the 68ch body measure resolved once, at the body size. */
.dsec { --spine: 39rem; }

.dsec__head,
.dsec > .prose,
.dsec > .dsec__steps,
.dsec > .dsec__check,
.dsec > .specstrip {
  max-width: var(--spine);
  margin-left: auto;
  margin-right: auto;
}

/* Inside a plate the prose sits above the photograph, so it takes the spine
   while the photograph itself does not. */
.dsec--plate > .prose { max-width: var(--spine); margin-inline: auto; }

/* Break-out elements. Stated explicitly rather than left to inherit, so adding
   a pattern later means deciding which of the two it is. */
.dsec__figwrap,
.dsec__split,
.dsec__plate,
.dsec > .dsec__panel { max-width: none; margin-inline: 0; }

/* The figure and split carry their own prose INSIDE a grid column, so they are
   already measured by the column and must not be centred again. */
.dsec__figbody,
.dsec__splitbody { max-width: none; margin-inline: 0; }

/* ---- the homepage long-read uses the same spine -------------------------
 * .hchap already offsets the body past the display numeral, so the spine is
 * centred inside that body rather than inside the whole zone.
 */

.hchap { --spine: 39rem; }

.hchap__title,
.hchap .dsec > .prose,
.hchap .dsec > .dsec__steps,
.hchap .dsec > .dsec__check,
.hchap .dsec > .specstrip {
  max-width: var(--spine);
  margin-left: auto;
  margin-right: auto;
}

.hchap .dsec__figwrap,
.hchap .dsec__split,
.hchap .dsec__plate,
.hchap .dsec > .dsec__panel { max-width: none; margin-inline: 0; }

/* A centred spine under a numeral that is hard against the left of its own
   column reads as a mistake. Give the numeral the same treatment so the two
   line up as one composition. */
@media (min-width: 900px) {
  .hchap__num { padding-top: 0.1rem; }
}

/* ---- guides and articles ------------------------------------------------
 * Same problem, same fix, in the two treatments that also run a measure inside
 * a wider column.
 */

.flow__body > *,
.article__inner > * {
  margin-left: auto;
  margin-right: auto;
}

.flow__body .tablewrap,
.article__inner .tablewrap,
.flow__figure,
.flow__plate { max-width: none; }

/* ---- prose inside a break-out container ---------------------------------
 * A panel section holds its paragraphs AND its table in one container, and
 * that container is full width because the table needs it. The paragraphs were
 * inheriting that width and running at about 110 characters, which is far past
 * a readable measure and looked flush left against the centred spine of every
 * neighbouring section. The container stays wide; only the table is allowed to
 * use it.
 */
.dsec__panel > p,
.dsec__panel > ul,
.dsec__panel > ol,
.dsec__panel > h3,
.dsec__panel > .callout,
.hchap .dsec__panel > p,
.hchap .dsec__panel > ul,
.hchap .dsec__panel > ol,
.hchap .dsec__panel > h3 {
  max-width: var(--spine);
  margin-left: auto;
  margin-right: auto;
}

.dsec__panel > .tablewrap { max-width: none; margin-inline: 0; }

/* Steps and checklists are lists of text and belong on the spine, but their
   card backgrounds should not be narrower than the text they hold. */
.dsec__check ul { max-width: none; }


/* ===== parts/87-service.css ============================================== */
/* ===========================================================================
 * THE SERVICE PAGE
 *
 * A conversion page with the technical read underneath it, rather than a
 * technical read on its own. Everything here exists to get an enquiry from a
 * homeowner who has decided they probably want a rooflight and is now deciding
 * who fits it.
 * ======================================================================== */

/* ===================================================================== HERO
 * Proposition, trust, and the form, all above the fold. The photograph is a
 * washed back ground rather than a subject, so the type holds against it at
 * every width without a second treatment.
 */

.svchero {
  position: relative;
  isolation: isolate;
  background: var(--bone);
  padding: clamp(2.4rem, 5vw, 4rem) 0 clamp(2.8rem, 6vw, 4.5rem);
  overflow: hidden;
}

.svchero__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
}

.svchero__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
}

/* The veil. Bone at high opacity on the left where the words are, thinning to
   the right so the photograph is still visible behind the form card. */
.svchero__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    rgba(248, 245, 239, .97) 0%,
    rgba(248, 245, 239, .93) 42%,
    rgba(248, 245, 239, .82) 72%,
    rgba(248, 245, 239, .74) 100%
  );
}

.svchero__grid {
  display: grid;
  gap: var(--s6);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

@media (min-width: 981px) {
  .svchero__grid { grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); gap: var(--s8); }
}

.svchero h1 {
  font-family: var(--display);
  font-size: clamp(2.1rem, 4.4vw, 3.3rem);
  line-height: 1.06;
  letter-spacing: -.015em;
  margin: var(--s3) 0 0;
  color: var(--ink);
  max-width: 16ch;
}

.svchero__lede { margin: var(--s4) 0 0; max-width: 46ch; }

.svchero__lede p {
  margin: 0 0 var(--s3);
  font-size: 1.04rem;
  line-height: 1.62;
  color: var(--ink);
}

/* The content lede can run to several paragraphs. Above the fold only the
   first two earn their place; the rest is in the read below. */
.svchero__lede p:nth-of-type(n+3) { display: none; }

.svchero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s4);
  margin: var(--s5) 0 0;
}

/* ---- trust strip --------------------------------------------------------- */

.svctrust {
  list-style: none;
  margin: var(--s6) 0 0;
  padding: var(--s4) 0 0;
  border-top: 1px solid var(--stone);
  display: grid;
  gap: var(--s2) var(--s5);
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
}

.svctrust li {
  position: relative;
  padding-left: 1.1rem;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .72rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-2);
  line-height: 1.5;
}

.svctrust li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .58em;
  width: .5rem;
  height: 1px;
  background: var(--oxide);
}

/* ---- the form card ------------------------------------------------------- */

.svchero__form .enq {
  background: var(--bone);
  border: 1px solid var(--stone);
  border-top: 3px solid var(--oxide);
  padding: var(--s5);
  box-shadow: var(--shadow-card);
}

.svchero__form .enq h2 {
  font-family: var(--display);
  font-size: 1.32rem;
  line-height: 1.2;
  margin: 0;
  color: var(--ink);
}

.svchero__form .enq__sub {
  margin: var(--s2) 0 var(--s4);
  font-size: .92rem;
  line-height: 1.55;
  color: var(--ink-2);
}

/* ================================================================= VARIANTS */

.svcgrid {
  list-style: none;
  margin: var(--s6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
}

.svcgrid a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s4);
  background: var(--bone);
  border: 1px solid var(--stone);
  text-decoration: none;
  color: var(--ink);
  transition: border-color .15s ease, transform .15s ease;
  height: 100%;
}

.svcgrid a:hover,
.svcgrid a:focus-visible { border-color: var(--oxide); }

.svcgrid__t { font-size: .98rem; line-height: 1.4; }
.svcgrid__go { color: var(--oxide); flex: 0 0 auto; }

@media (prefers-reduced-motion: reduce) {
  .svcgrid a { transition: none; }
}

/* ============================================================== THE FOUR */

.svcfour { background: var(--bone); }

.svcfour__grid {
  display: grid;
  gap: var(--s6);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

@media (min-width: 900px) {
  .svcfour__grid { grid-template-columns: minmax(0, 1fr) minmax(0, .9fr); gap: var(--s8); }
}

.svcfour h2 {
  font-family: var(--display);
  font-size: clamp(1.6rem, 2.8vw, 2.25rem);
  line-height: 1.14;
  margin: 0;
  color: var(--ink);
  max-width: 18ch;
}

.svcfour__lede {
  margin: var(--s4) 0 0;
  max-width: 48ch;
  font-size: 1.01rem;
  line-height: 1.62;
  color: var(--ink);
}

.svcfour__list {
  list-style: none;
  margin: var(--s5) 0 0;
  padding: 0;
  counter-reset: four;
  max-width: 48ch;
}

.svcfour__list li {
  counter-increment: four;
  position: relative;
  padding: 0 0 var(--s4) var(--s6);
  line-height: 1.6;
}

.svcfour__list li::before {
  content: counter(four, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: .15rem;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .72rem;
  letter-spacing: .1em;
  color: var(--oxide);
}

.svcfour__list li:last-child { padding-bottom: 0; }

.svcfour__note {
  margin: var(--s5) 0 0;
  padding-left: .75rem;
  border-left: 2px solid var(--oxide);
  font-size: .95rem;
  color: var(--ink-2);
  max-width: 42ch;
}

.svcfour__fig .diagram {
  background: var(--sand);
  border: 1px solid var(--stone);
  padding: var(--s4);
  margin: 0;
}

/* ================================================================== PROCESS */

.svcsteps {
  list-style: none;
  margin: var(--s6) 0 0;
  padding: 0;
  counter-reset: none;
  display: grid;
  gap: 0;
  max-width: 58rem;
}

.svcsteps li {
  display: grid;
  grid-template-columns: 3.2rem minmax(0, 1fr);
  gap: var(--s4);
  align-items: start;
  padding: var(--s4) 0;
  border-bottom: 1px solid var(--stone);
}

.svcsteps li:last-child { border-bottom: 0; }

.svcsteps__n {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .74rem;
  letter-spacing: .12em;
  /* Oxide Deep, not Oxide. This section sits on Sand, and #B5533A on #EBE4D6 is
     3.89:1, under the 4.5 that 12px text needs. STYLE-GUIDE section 3 keeps
     Oxide Deep for exactly this: text on a light ground where the accent is too
     pale to carry it. On Bone the plain accent still clears the bar, which is
     why the same numeral passes elsewhere on the page. */
  color: var(--oxide-deep);
  padding-top: .28rem;
}

.svcsteps__b strong {
  display: block;
  font-family: var(--display);
  font-weight: 500;
  font-size: 1.12rem;
  line-height: 1.3;
  color: var(--ink);
  margin-bottom: .25rem;
}

.svcsteps__b span {
  display: block;
  font-size: .96rem;
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 52ch;
}

/* ====================================================================== FAQ
 * <details> so it is keyboard operable and works with no JavaScript.
 */

.svcfaq { margin: var(--s6) 0 0; max-width: 58rem; border-top: 1px solid var(--stone); }

.svcfaq__item { border-bottom: 1px solid var(--stone); }

.svcfaq__item summary {
  list-style: none;
  cursor: pointer;
  padding: var(--s4) var(--s6) var(--s4) 0;
  position: relative;
  font-family: var(--display);
  font-size: 1.08rem;
  line-height: 1.35;
  color: var(--ink);
}

.svcfaq__item summary::-webkit-details-marker { display: none; }

.svcfaq__item summary::after {
  content: "";
  position: absolute;
  right: .35rem;
  top: 50%;
  width: .62rem;
  height: .62rem;
  margin-top: -.42rem;
  border-right: 2px solid var(--oxide);
  border-bottom: 2px solid var(--oxide);
  transform: rotate(45deg);
  transition: transform .16s ease;
}

.svcfaq__item[open] summary::after { transform: rotate(-135deg); margin-top: -.16rem; }

.svcfaq__item summary:focus-visible {
  outline: 2px solid var(--oxide);
  outline-offset: -2px;
}

.svcfaq__a { padding: 0 var(--s6) var(--s5) 0; max-width: 54ch; }

.svcfaq__a p { margin: 0 0 var(--s3); line-height: 1.65; color: var(--ink); }

.svcfaq__more a {
  font-size: .9rem;
  color: var(--oxide-deep);
  text-underline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .svcfaq__item summary::after { transition: none; }
}

/* ================================================================ READ NEXT
 * Six cards, then one route to the rest. This replaced two stacked lists of all
 * 32 child pages, which read as a link dump and gave a reader no reason to pick
 * one over another.
 */

.readnext {
  list-style: none;
  margin: var(--s6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s4);
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
}

.readnext__card { display: flex; }

.readnext__card a {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  width: 100%;
  padding: var(--s5);
  background: var(--bone);
  border: 1px solid var(--stone);
  border-top: 2px solid var(--stone);
  text-decoration: none;
  color: var(--ink);
  transition: border-top-color .15s ease;
}

.readnext__card a:hover,
.readnext__card a:focus-visible { border-top-color: var(--oxide); }

.readnext__card a:focus-visible { outline: 2px solid var(--oxide); outline-offset: 2px; }

.readnext__kind {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.readnext__t {
  font-family: var(--display);
  font-size: 1.12rem;
  line-height: 1.28;
  color: var(--ink);
}

.readnext__d {
  font-size: .9rem;
  line-height: 1.55;
  color: var(--ink-2);
}

.readnext__all { margin: var(--s6) 0 0; }

@media (prefers-reduced-motion: reduce) {
  .readnext__card a { transition: none; }
}

/* ------------------------------------------------- the read, below the page
 * The technical read keeps its own treatment but no longer opens the page, so
 * its threshold does not need to shout as loudly as it did when it was the
 * first thing anybody saw.
 */

#the-detail .homeread__threshold { padding: clamp(2.4rem, 5vw, 3.6rem) 0; }
#the-detail .homeread__thresh-line { font-size: clamp(1.3rem, 2.6vw, 1.9rem); }

/* ======================================================== GUIDE / ANSWER CTA
 * The closing band on a guide or an answer. Deliberately quieter than the
 * service hero: these readers came for information, and the ask is made after
 * the page has been useful rather than before.
 */

.readcta {
  background: var(--slate);
  color: var(--bone);
  padding: clamp(2rem, 4.5vw, 3.2rem) 0;
  margin: var(--s8) 0 0;
}

.readcta__in {
  display: grid;
  gap: var(--s5);
  align-items: center;
}

@media (min-width: 860px) {
  .readcta__in { grid-template-columns: minmax(0, 1fr) auto; gap: var(--s8); }
}

/* Specificity, for the same reason as the interlude: .dossier__body p and its
   siblings set Ink on a long read, and this band is Bone on Slate. */
.readcta .readcta__lead {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(1.12rem, 2vw, 1.42rem);
  line-height: 1.4;
  color: var(--bone);
  max-width: 44ch;
}

.readcta .readcta__trust {
  margin: var(--s4) 0 0;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--stone);
  line-height: 1.6;
}

.readcta__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s4);
  margin: 0;
}

/* The text button sits on Slate here, so it cannot keep the ink colour it uses
   on a light ground. */
.readcta .btn--text {
  color: var(--sand);
  border-bottom-color: var(--sand);
}

.readcta .btn--text:hover,
.readcta .btn--text:focus-visible { color: var(--bone); border-bottom-color: var(--bone); }

/* The cards band, reused from the service page so the two tiers share one
   component rather than growing a second one that drifts. */
.readnextband { margin-top: 0; }

/* ============================================================== QUOTE BAND
 * §4e's site-wide form section. Dark ground so it separates from whatever
 * treatment the page above it used, and so it reads as the end of the page
 * rather than as another content section.
 */

.quoteband {
  background: var(--slate);
  color: var(--bone);
  padding: clamp(2.6rem, 5.5vw, 4.2rem) 0;
  /* §7c.4: padding, never margin, on a full-bleed band. */
  margin: 0;
  scroll-margin-top: 5.5rem;
}

.quoteband__grid {
  display: grid;
  gap: var(--s6);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

@media (min-width: 981px) {
  .quoteband__grid { grid-template-columns: minmax(0, 1fr) minmax(0, .95fr); gap: var(--s8); }
}

.quoteband .eyebrow { color: var(--stone); }

.quoteband .quoteband__h {
  font-family: var(--display);
  font-size: clamp(1.5rem, 3vw, 2.3rem);
  line-height: 1.16;
  margin: var(--s3) 0 0;
  color: var(--bone);
  max-width: 18ch;
}

.quoteband .quoteband__h em { color: var(--sand); font-style: italic; }

.quoteband__points {
  list-style: none;
  margin: var(--s5) 0 0;
  padding: 0;
  max-width: 44ch;
}

.quoteband .quoteband__points li {
  position: relative;
  padding: 0 0 var(--s3) 1.3rem;
  color: var(--stone);
  font-size: .97rem;
  line-height: 1.6;
}

.quoteband .quoteband__points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .62em;
  width: .62rem;
  height: 1px;
  background: var(--oxide);
}

/* The card sits on Bone inside the dark band so the vendor form, which brings
   its own light styling, is not stranded on a dark ground. */
.quoteband__form .enq {
  background: var(--bone);
  border: 1px solid var(--stone);
  border-top: 3px solid var(--oxide);
  padding: var(--s5);
}

.quoteband__form .enq h2 {
  font-family: var(--display);
  font-size: 1.3rem;
  line-height: 1.2;
  margin: 0;
  color: var(--ink);
}

.quoteband__form .enq__sub {
  margin: var(--s2) 0 var(--s4);
  font-size: .92rem;
  line-height: 1.55;
  color: var(--ink-2);
}

/* The pointer variant, on pages whose form is already elsewhere. */
.quoteband__form .enq--ref { background: transparent; border: 0; padding: 0; }
.quoteband__form .enq--ref h2 { color: var(--bone); }
.quoteband__form .enq--ref .enq__sub { color: var(--stone); }

/* The bare #quote anchor used on pages whose form sits in its own position.
   Zero-size, but with the sticky-header offset so the scroll lands correctly. */
.anchor-target {
  display: block;
  height: 0;
  scroll-margin-top: 5.5rem;
}

/* Breadcrumb in the service hero. The hero sits on --bone, the same ground the
   pagehead uses, so the base .crumbs colours already have their contrast; this
   only buys it room above the eyebrow. */
.svchero__words .crumbs { margin-bottom: var(--s4); }
