/* ==========================================================================
   Components
   ========================================================================== */

/* --------------------------------------------------------------------------
   Wordmark — the waterline device in miniature
   -------------------------------------------------------------------------- */
.wordmark {
  display: inline-grid;
  gap: 0.28em;
  justify-items: start;
  color: currentColor;
  text-decoration: none;
  line-height: 1;
}

.wordmark__name {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-bold);
  letter-spacing: -0.02em;
}

.wordmark__line {
  width: 100%;
  height: 1px;
  background-color: var(--brand-ice);
  transform-origin: left;
  transition: transform var(--dur) var(--ease-out);
}

.wordmark__desc {
  font-family: var(--font-display);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: currentColor;
  opacity: 0.66;
  /* Wrapping the descriptor would push the fixed header taller and shift
     every page's top offset with it. */
  white-space: nowrap;
}

/* The tracking is what makes the descriptor too wide on a narrow phone;
   tightening it keeps the mark on two lines rather than three. */
@media (max-width: 26rem) {
  .wordmark__desc {
    letter-spacing: 0.1em;
  }
}

.wordmark:hover .wordmark__line {
  transform: scaleX(1.06);
}

/* The real brand mark.

   Sized by height so the header bar keeps a fixed rhythm; width follows from
   the viewBox, which also means the box is reserved before paint — no CLS.
   Colour comes from `currentColor`, so one file works on navy and on light. */
.wordmark__mark {
  display: block;
  width: auto;
  height: var(--mark-h, 2rem);
  transition: opacity var(--dur) var(--ease-out);
}

/* 4.71:1 — wide, so it is height-capped on narrow screens to leave room for
   the menu toggle beside it. */
.wordmark--horizontal {
  --mark-h: clamp(2.1rem, 5vw, 2.6rem);
}

/* 1.49:1 — the stacked lockup fills the footer's brand column. */
.wordmark--stacked {
  --mark-h: clamp(3.2rem, 8vw, 4.4rem);
}

.wordmark:hover .wordmark__mark {
  opacity: 0.78;
}

/* The mark keeps its colour on hover — recolouring a brand logo is not ours
   to do, and the opacity shift above is already the affordance. */
.site-header .wordmark:hover,
.site-footer .wordmark:hover {
  color: var(--brand-ghost);
}

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */
.site-header {
  /* base.css sets `a:hover { color: var(--link-hover) }`, and --link-hover is
     navy for light grounds. The header never overrode it, so every link in
     here — nav items and the wordmark — turned the exact navy of the header
     ground on hover: 1:1 contrast, invisible.

     Azure on navy is 5.53:1. The alternative of an azure header ground with
     navy rollovers was rejected: it would put Ghost White on raw Azure at
     3.25:1 in the RESTING state, which is what people look at all the time. */
  --link-hover: var(--brand-azure);
  /* The nav's own rollover colour. Azure on the solid navy header; the
     adaptive header below animates it. */
  --nav-hover: var(--brand-azure);
  --nav-line: var(--brand-ice);

  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--z-header);
  color: var(--brand-ghost);
  background-color: var(--brand-navy);
  border-block-end: 1px solid transparent;
}

/* Pages that open on a full-bleed hero start transparent; motion.css fades
   the ground in on scroll. Without scroll timelines it stays translucent,
   which is still legible over the hero scrim.

   The nav rollover follows the same fade. On the glass, Azure sits over
   bright footage and washes out, so hover stays white and the underline
   alone signals it — itself white, since a Neon Ice hairline all but
   vanished over the pale water. As the navy ground arrives,
   ep-header-ground carries --nav-hover across to Azure (5.53:1 on navy)
   and --nav-line to Neon Ice. Where the ground never
   fades in — reduced motion, no scroll timelines — the header stays glass
   and the white rollover stays correct. */
.site-header--adaptive {
  --nav-hover: var(--brand-ghost);
  --nav-line: var(--brand-ghost);

  background-color: color-mix(in srgb, var(--brand-navy) 30%, transparent);
  backdrop-filter: blur(10px);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  height: var(--header-h);
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.site-header__rule {
  height: 1px;
  background-color: var(--line-invert);
}

.site-nav {
  margin-inline-start: auto;
}

.site-nav__list {
  display: flex;
  gap: var(--space-4);
}

.site-nav__link {
  position: relative;
  display: inline-block;
  padding-block: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--brand-ghost);
  text-decoration: none;
}

.site-nav__link::after {
  content: "";
  position: absolute;
  inset-block-end: 0.3em;
  inset-inline: 0;
  height: 1px;
  background-color: var(--nav-line);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease-out);
}

.site-nav__link:hover {
  color: var(--nav-hover);
}

.site-nav__link:hover::after,
.site-nav__link:focus-visible::after,
.site-nav__link.is-current::after {
  transform: scaleX(1);
}

.site-header__toggle {
  display: none;
  margin-inline-start: auto;
  padding: var(--space-2);
  color: var(--brand-ghost);
}

.site-header__toggle-bars {
  display: grid;
  gap: 6px;
  width: 26px;
}

.site-header__toggle-bars > span {
  height: 1px;
  background-color: currentColor;
}

@media (max-width: 56rem) {
  .site-nav,
  /* Needs the extra class: .btn sets display:inline-flex and is declared
     further down this file, so at equal specificity it won and the Enquire
     pill stayed visible beside the hamburger. */
  .site-header .site-header__cta {
    display: none;
  }

  .site-header__toggle {
    display: block;
  }
}

/* --------------------------------------------------------------------------
   Drawer
   -------------------------------------------------------------------------- */
.drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  background-color: color-mix(in srgb, var(--brand-navy) 70%, transparent);
}

.drawer[hidden] {
  display: none;
}

.drawer__panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  width: min(24rem, 88vw);
  height: 100%;
  margin-inline-start: auto;
  padding: var(--space-4) var(--space-5) var(--space-6);
  background-color: var(--brand-navy);
  border-inline-start: 1px solid var(--brand-ice);
  color: var(--brand-ghost);
  overflow-y: auto;
}

.drawer__close {
  align-self: flex-end;
  padding: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--brand-ghost);
}

.drawer__link {
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-tight);
  color: var(--brand-ghost);
  text-decoration: none;
}

.drawer__link:hover {
  color: var(--brand-ice);
}

.drawer__foot {
  display: grid;
  gap: var(--space-3);
  margin-block-start: auto;
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--line-invert);
}

.drawer__phone {
  font-family: var(--font-display);
  font-size: var(--step-1);
  color: var(--brand-ice);
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   Buttons

   Filled buttons use Neon Ice with navy text — 12.45:1, and the one place
   the accent is allowed to occupy real area.
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 2.75rem; /* 55px — comfortably past the 44px target floor */
  padding: 0.7em 1.5em;
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-decoration: none;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  transition: background-color var(--dur) var(--ease-out),
    color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}

.btn--lg {
  font-size: var(--step-0);
  padding: 0.8em 2em;
}

.btn--fill {
  background-color: var(--brand-ice);
  color: var(--brand-navy);
}

.btn--fill:hover {
  background-color: var(--brand-ghost);
  color: var(--brand-navy);
}

/* .btn--fill's hover is Ghost White — correct for "Start your project" in
   the header, which sits on navy ground and reads as a light pill lifting
   off a dark bar. The enquiry submit button sits on the page's own Ghost
   White ground, so that same hover turns the button the exact colour of the
   page behind it: on hover it visually disappears, leaving only unboxed
   navy text. Inverted instead, matching .btn--outline:hover's treatment —
   navy on ghost is the same 17.98:1 pair already used site-wide. */
.enquiry__actions .btn--fill:hover {
  background-color: var(--brand-navy);
  color: var(--brand-ghost);
}

.btn--ghost {
  border-color: currentColor;
  color: var(--brand-ghost);
}

.btn--ghost:hover {
  background-color: var(--brand-ice);
  border-color: var(--brand-ice);
  color: var(--brand-navy);
}

.btn--outline {
  border-color: var(--line-strong);
  color: var(--text);
}

.btn--outline:hover {
  background-color: var(--text);
  border-color: var(--text);
  color: var(--ground);
}

/* --------------------------------------------------------------------------
   The pill

   The surface shared by the quiet invite and the CTA rail action, so the two
   stay identical by construction rather than by coincidence. Surface only —
   padding and type belong to whatever wears it.
   -------------------------------------------------------------------------- */
.pill {
  background-color: var(--brand-navy);
  border: 1px solid var(--brand-ice);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-float);
  color: var(--brand-ghost);
}

.pill__dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: var(--brand-ice);
}

/* Text link with a rule that draws on hover */
.link-arrow-inline {
  position: relative;
  display: inline-block;
  padding-block-end: 0.2em;
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--link);
}

.link-arrow-inline::after {
  content: "";
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  height: 1px;
  background-color: currentColor;
  transform: scaleX(0.28);
  transform-origin: left;
  transition: transform var(--dur) var(--ease-out);
}

.link-arrow-inline:hover::after {
  transform: scaleX(1);
}

/* --------------------------------------------------------------------------
   Hero

   NOTE: this section had accumulated THREE overlapping copies of .hero,
   .hero__content, .hero__title, .hero__waterline and .hero__sub, plus two of
   .hero__panel. Later copies silently won for whatever they declared and
   earlier ones won for whatever they did not, so the effective style was a
   merge of all three and no single block described what actually rendered.
   That is what made a width override inside a media query lose to a base
   declaration further down the file. Consolidated to one copy — keep it that
   way.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100svh;
  /* Keeps the panel clear of the fixed header when its content makes it
     taller than the space left (the award badge on a laptop or a phone).
     While it fits, the panel centres in what remains below the header. */
  padding-block: var(--header-h) var(--space-5);
  color: var(--brand-ghost);
  isolation: isolate;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
}

.hero > .scrim {
  z-index: -1;
}

/* --------------------------------------------------------------------------
   Scroll cue

   Shared by the home hero and every project hero, so the two cannot drift.
   -------------------------------------------------------------------------- */
.scroll-cue {
  position: absolute;
  inset-block-end: var(--space-4);
  inset-inline-end: var(--gutter);
  z-index: 2;
  display: grid;
  gap: var(--space-2);
  justify-items: center;
  font-family: var(--font-display);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--brand-ghost);
  text-decoration: none;
  opacity: 0.78;
  transition: opacity var(--dur) var(--ease-out);
}

.scroll-cue:hover {
  color: var(--brand-ghost);
  opacity: 1;
}

.scroll-cue__line {
  width: 1px;
  height: var(--space-5);
  background: linear-gradient(to bottom, var(--brand-ice), transparent);
}

@media (max-width: 40rem) {
  .scroll-cue {
    display: none;
  }
}


/* --------------------------------------------------------------------------
   The hero panel

   A large still held in the centre of the video with the headline over it.
   The ice hairline along the top edge is the waterline device again.
   -------------------------------------------------------------------------- */
.hero__panel {
  position: relative;
  z-index: 1;
  /* Phones keep 88vw — there is no video worth exposing at that size, and
     the type nearly fills the panel. */
  --panel-w: min(78rem, 88vw);
  /* The tallest the panel may be and still sit wholly below the header. */
  --panel-room: calc(100svh - var(--header-h) - var(--space-6));
  width: var(--panel-w);
  /* Every height here is a FLOOR, never a cap. Content taller than it grows
     the panel. A max-height used to keep it on screen, but with the award
     badge added that cap clipped the badge off the top of the panel. */
  min-height: min(76svh, 40rem, var(--panel-room));
  border-block-start: 2px solid var(--brand-ice);
  box-shadow: 0 4px 14px color-mix(in srgb, #000 28%, transparent),
    0 40px 90px -24px color-mix(in srgb, #000 66%, transparent);
  overflow: hidden;

  /* Flex so the content stretches to the panel's height.

     It used to rely on `height: 100%`, which only resolved because a stale
     `aspect-ratio: 4/5` made the height definite. With that residue gone the
     percentage fell back to auto, the text stopped filling the panel and sat
     at the TOP of the image on phones — dropping the eyebrow to 1.95:1.
     Stretching a flex item does not depend on the height being definite, and
     it keeps the content in flow so `min-height: fit-content` still works. */
  display: flex;
}

@media (min-width: 48rem) {
  .hero__panel {
    /* 88vw leaves 6vw of gap each side; 76vw leaves 12vw — so the space
       around the panel doubles. Measured at exactly 2.00x from this
       breakpoint to 1728px, easing past 1920px where the rem cap binds
       rather than the viewport. */
    /* 4:3 rather than 3:2 across the tablet band. The narrower panel made
       3:2 only 389px tall at 768px, so the text filled 88% of it and pushed
       the eyebrow to the very top of the gradient. */
    /* The ratio is a min-height worked out from the width, not
       aspect-ratio: an aspect-ratio box would not grow for content taller
       than the ratio, and the badge spilled off the top instead. */
    min-height: min(calc(var(--panel-w) * 3 / 4), var(--panel-room));
  }
}

/* 52rem = 832px. The width reduction starts here, not at 768px.

   Below this the narrower panel squeezed the sub-line onto a THIRD line,
   which together with the shorter panel put the text at 82% of the panel
   height and the eyebrow at 2.36:1. Tablet portrait has almost no video to
   expose either side anyway, so it keeps the full-width panel — the same
   reasoning that leaves phones alone. */
@media (min-width: 52rem) {
  .hero__panel {
    --panel-w: min(67rem, 76vw);
  }
}

@media (min-width: 64rem) {
  .hero__panel {
    min-height: min(calc(var(--panel-w) * 9 / 16), var(--panel-room));
  }
}

.hero__panel-media {
  /* Overfilled so the scroll drift can move without exposing an edge; the
     panel clips it. */
  position: absolute;
  inset: -4%;
  z-index: 0;
}

.hero__panel-media > picture,
.hero__panel-media > img {
  display: block;
  width: 100%;
  height: 100%;
}

.hero__panel-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero__panel-scrim {
  /* Anchored to the panel, not to .hero__panel-media — that layer is
     overfilled by 4% for the drift, which pushed the darkest end of this
     gradient outside the clip and weakened it exactly where the type is.

     The vertical gradient reaches high because the bottom-anchored text
     block occupies 53-78% of the panel height at these sizes. Its strength
     was swept against the real photograph: 74% of these values dropped the
     eyebrow to 3.95:1, 82% reached 4.81:1 with almost no margin, so this
     sits at 86% — the lightest setting that keeps real headroom.

     The mask then fades that darkening out to the RIGHT, where there is no
     type to protect. Measured ink extent: the headline and sub-line reach
     only 52-66% of the panel width above 64rem, so darkening the remaining
     third was costing image for nothing. Masking multiplies the two, which
     layering a second gradient would not — layered gradients composite over
     each other and would darken both bands, not just their overlap. */
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--brand-navy) 83%, transparent) 0%,
    color-mix(in srgb, var(--brand-navy) 83%, transparent) 48%,
    color-mix(in srgb, var(--brand-navy) 80%, transparent) 72%,
    color-mix(in srgb, var(--brand-navy) 67%, transparent) 88%,
    color-mix(in srgb, var(--brand-navy) 31%, transparent) 96%,
    transparent 100%
  );

}

/* Tablet and below the scrim runs to both edges of the panel — no mask.

   The type reaches 81-84% of the panel width at those sizes, so any fade to
   the right clips into the text block and reads as a soft, uneven edge
   against an otherwise hard-edged panel. There is nothing to reclaim there
   anyway: the ink already covers almost the full width. */

/* 80rem = 1280px. Media-query rem resolves against the initial 16px, NOT
   the 125% root size — so 64rem here would be 1024px and would have caught
   an iPad in landscape. */
@media (min-width: 80rem) {
  .hero__panel-scrim {
    /* Ink reaches only 66% at 1280px and less above it, so the darkening
       fades out to the right where no type needs protecting. A mask rather
       than a second gradient: layered gradients composite over each other
       and would darken both bands, where a mask multiplies them. */
    -webkit-mask-image: linear-gradient(to right, #000 0 76%, transparent 100%);
    mask-image: linear-gradient(to right, #000 0 76%, transparent 100%);
  }
}

/* The text block occupies proportionally more of a small panel, so the
   gradient has to reach further up. */
@media (max-width: 48rem) {
  .hero__panel-scrim {
    background: linear-gradient(
      to top,
      color-mix(in srgb, var(--brand-navy) 97%, transparent) 0%,
      color-mix(in srgb, var(--brand-navy) 94%, transparent) 55%,
      color-mix(in srgb, var(--brand-navy) 72%, transparent) 78%,
      color-mix(in srgb, var(--brand-navy) 30%, transparent) 92%,
      transparent 100%
    );
  }
}

.hero__content {
  position: relative;
  z-index: 1;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  width: 100%;
  padding: clamp(var(--space-4), 4vw, var(--space-6));
}

/* --------------------------------------------------------------------------
   Hero award badge

   The white artwork above the eyebrow, on the home panel and on an
   award-winning project's hero. Sized by width; the height follows.
   -------------------------------------------------------------------------- */
.hero-award {
  display: block;
  width: clamp(4.25rem, 8vw, 6.5rem);
  height: auto;
  margin-block-end: var(--space-4);
}

/* The home panel already fills most of a laptop screen, so the badge also
   scales with the viewport height: smaller on a short screen, which keeps
   the panel from running far past the fold at 1366×768 and 1280×720. */
.hero__award {
  width: clamp(3.5rem, min(8vw, 9svh), 6.5rem);
  margin-block-end: var(--space-3);
}

.hero__eyebrow {
  color: var(--brand-ice);
}

.hero__title {
  margin-block-start: var(--space-3);
  font-size: var(--step-6-fluid);
  font-weight: var(--weight-display);
  letter-spacing: -0.035em;
  line-height: 0.92;
  text-wrap: balance;
}

/* Not sticky. That was residue from the full-bleed hero this replaced —
   inside a panel with overflow: hidden it does nothing. */
.hero__waterline {
  margin-block: var(--space-3);
  max-width: 32rem;
}

.hero__sub {
  max-width: var(--measure-tight);
  font-size: var(--step-1);
  line-height: var(--leading-loose);
  color: var(--brand-ghost);
}


/* --------------------------------------------------------------------------
   Section headings
   -------------------------------------------------------------------------- */
.statement {
  font-size: var(--step-5-fluid);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-display);
  line-height: 0.98;
  max-width: 14ch;
}

.section-title {
  font-size: var(--step-3-fluid);
  letter-spacing: var(--tracking-tight);
  max-width: 16ch;
}

.section-head {
  padding-block-end: var(--space-5);
  border-block-end: 1px solid var(--line);
  margin-block-end: var(--space-6);
}

.page-head__title {
  font-size: var(--step-5-fluid);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-display);
  line-height: 0.98;
}

/* Page heads clear the fixed header. */
.page-head {
  padding-block-start: calc(var(--header-h) + var(--section-y));
}

/* --------------------------------------------------------------------------
   Feature rows (home)
   -------------------------------------------------------------------------- */
.feature-row__media {
  display: block;
}

@media (min-width: 48rem) {
  /* Pin both children to the same row.

     Without this the flipped rows break. The media is first in the DOM and
     takes columns 6-12, which advances grid's auto-placement cursor past
     column 12; the text then asks for columns 1-4, which is behind the
     cursor, and sparse packing never moves backwards — so the text drops to
     a second row beneath the image. The unflipped rows are unaffected only
     because their cursor happens to move forward.

     Explicit placement is deterministic; `grid-auto-flow: dense` would also
     fix it but leaves the outcome to the packing algorithm. */
  .feature-row > * {
    grid-row: 1;
  }
}

.feature-row__body {
  display: grid;
  gap: var(--space-2);
  align-content: start;
}

/* Grid items stretch to the column width by default — fine for the text,
   wrong for the button, which should size to its own content like every
   other .btn on the site. */
.feature-row__body .btn {
  justify-self: start;
}

@media (min-width: 48rem) {
  /* Drop the text column so it sits against the lower third of the image —
     an offset that reads as considered rather than aligned by default. */
  .feature-row__body {
    padding-block-start: var(--space-6);
  }
}

.feature-row__title {
  font-size: var(--step-3-fluid);
  letter-spacing: var(--tracking-display);
}

.feature-row__link {
  color: var(--text);
  text-decoration: none;
}

.feature-row__link:hover {
  color: var(--link);
}

.feature-row__summary {
  color: var(--text-muted);
  max-width: var(--measure-tight);
}

.feature-rows__more {
  margin-block-start: var(--space-7);
}

/* --------------------------------------------------------------------------
   Services
   -------------------------------------------------------------------------- */
.services__list {
  display: grid;
  gap: var(--space-5);
  margin-block-start: var(--space-6);
}

@media (min-width: 48rem) {
  .services__list {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-6);
  }
}

.services__item {
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--line-accent);
}

.services__name {
  font-size: var(--step-2);
  margin-block-end: var(--space-2);
}

.services__body {
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   The rail — the primary CTA rhythm
   -------------------------------------------------------------------------- */
.rail {
  position: relative;
  display: block;
  padding-block: clamp(var(--space-6), 9vw, var(--space-7));
}

.rail__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-5);
}

/* Newsreader italic. The one warm, spoken voice on a page otherwise set in
   Syne's geometry — this is what stops the rail reading as a banner. */
.rail__line {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--step-2);
  font-weight: var(--weight-body);
  line-height: 1.3;
  letter-spacing: -0.01em;
  /* Wider than the old side-by-side cap, but still short enough that a
     centred line never runs long enough to be hard to track back. */
  max-width: 28ch;
  text-wrap: balance;
  color: var(--text);
}

.rail__action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.75rem;
  padding: 0.75em 1.6em;
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur) var(--ease-out),
    background-color var(--dur) var(--ease-out);
}

/* .on-cta sets --link to ghost white; the pill supplies its own navy
   ground, so the action keeps the invite's exact colouring on any rail. */
.rail .rail__action {
  color: var(--brand-ghost);
}

.rail .rail__action:hover {
  color: var(--brand-ice);
}

/* --------------------------------------------------------------------------
   Project grid and cards
   -------------------------------------------------------------------------- */
/* One card per row, every card the same size. */
.project-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-7);
  margin-block-start: var(--space-6);
}

.project-card {
  position: relative;
  display: grid;
  gap: var(--space-3);
  align-content: start;
}

.project-card[hidden] {
  display: none;
}

.project-card__frame {
  overflow: hidden;
}

.project-card__title {
  font-size: var(--step-2);
  letter-spacing: var(--tracking-tight);
}

/* Now that a card runs the full width, the body reads better as a row —
   identity on the left, description on the right — than as a narrow stack
   stranded under a 1296px image. */
@media (min-width: 48rem) {
  .project-card__body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: baseline;
    column-gap: var(--gutter);
  }

  .project-card__title {
    grid-column: 1;
  }

  .project-card__meta {
    grid-column: 1;
  }

  .project-card__summary {
    grid-column: 2;
    grid-row: 1 / span 2;
  }
}

/* One tab stop per card. The stretched link makes the whole card clickable
   while keeping the accessible name as the project title. */
.project-card__link {
  color: var(--text);
  text-decoration: none;
}

.project-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.project-card__link:hover {
  color: var(--link);
}

.project-card__meta {
  color: var(--text-quiet);
}

.project-card__summary {
  color: var(--text-muted);
  font-size: var(--step-0);
  line-height: var(--leading-loose);
  max-width: 52ch;
}

.project-grid__empty {
  margin-block-start: var(--space-6);
  color: var(--text-muted);
  font-style: italic;
}

/* --------------------------------------------------------------------------
   Project detail
   -------------------------------------------------------------------------- */
.project-hero {
  /* Full viewport height so the scrim meets the fold. At 88svh the gradient
     stopped 12% short and left a band of the next section showing under it. */
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: 100svh;
  /* The top padding only matters when the content outgrows the screen, as
     the award badge does on a landscape phone: it keeps the content below
     the header. */
  padding-block: calc(var(--header-h) + var(--space-3)) var(--space-7);
  color: var(--brand-ghost);
  isolation: isolate;
}

.project-hero > .frame {
  position: absolute;
  inset: 0;
  z-index: -2;
}

.project-hero > .scrim {
  z-index: -1;
  /* The shared .scrim is tuned for the section it was written for and is too
     light here: measured against the real photography it left the Neon Ice
     meta line at 2.30:1 and the title at 2.52:1 on Neridah's Way. Bottom
     weighted and stronger, so the upper two thirds of the photograph keep
     full brightness and legibility is paid for only where the type sits. */
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--brand-navy) 94%, transparent) 0%,
    color-mix(in srgb, var(--brand-navy) 88%, transparent) 26%,
    color-mix(in srgb, var(--brand-navy) 62%, transparent) 46%,
    color-mix(in srgb, var(--brand-navy) 22%, transparent) 68%,
    transparent 88%
  );
}

/* On a phone the hero text block occupies far more of the frame, so the meta
   line — Neon Ice, and the tightest element every time — ends up where the
   desktop gradient has already fallen away. Measured against the new client
   photography it sat at 3.63:1 on Horizon, 3.98 on Connection and 4.10 on
   Repose, all short of the 4.5 small text needs. */
@media (max-width: 48rem) {
  .project-hero > .scrim {
    background: linear-gradient(
      to top,
      color-mix(in srgb, var(--brand-navy) 96%, transparent) 0%,
      color-mix(in srgb, var(--brand-navy) 93%, transparent) 40%,
      color-mix(in srgb, var(--brand-navy) 84%, transparent) 62%,
      color-mix(in srgb, var(--brand-navy) 48%, transparent) 80%,
      color-mix(in srgb, var(--brand-navy) 14%, transparent) 94%,
      transparent 100%
    );
  }
}

.project-hero__content {
  position: relative;
  width: 100%;
}

.project-hero__meta {
  color: var(--brand-ice);
}

.project-hero__title {
  margin-block-start: var(--space-2);
  font-size: var(--step-5-fluid);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-display);
  line-height: 0.96;
}

.project-hero__waterline {
  margin-block: var(--space-3);
  max-width: 28rem;
}

.project-hero__summary {
  max-width: var(--measure-tight);
  font-size: var(--step-1);
  line-height: var(--leading-loose);
}

.project-intro {
  font-size: var(--step-2);
  font-weight: var(--weight-body);
  line-height: 1.42;
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

/* The grid sets align-items: start, which would size this cell to its own
   content and leave sticky with nowhere to travel. Stretching it to the row
   height is what gives the spec list room to hold position. */
.project-aside {
  align-self: stretch;
}

/* --------------------------------------------------------------------------
   Award badge

   Sits above the Detail list. The artwork is black and cyan on transparency,
   so it needs the light sidebar ground — never place it on navy.
   -------------------------------------------------------------------------- */
.project-award {
  margin: 0 0 var(--space-5);
  padding-block-end: var(--space-4);
  border-block-end: 1px solid var(--line);
}

.project-award__mark {
  width: clamp(7rem, 60%, 10rem);
  height: auto;
}

.project-award__label {
  margin-block-start: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-quiet);
}

@media (min-width: 48rem) {
  .project-aside__inner {
    position: sticky;
    inset-block-start: calc(var(--header-h) + var(--space-4));
  }
}

.spec-list {
  margin-block-start: var(--space-3);
  border-block-start: 1px solid var(--line);
}

.spec-list__row {
  display: grid;
  gap: var(--space-1);
  padding-block: var(--space-3);
  border-block-end: 1px solid var(--line);
}

.spec-list__label {
  font-family: var(--font-display);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-quiet);
}

.spec-list__value {
  margin: 0;
  font-size: var(--step-0);
  color: var(--text);
}

/* --------------------------------------------------------------------------
   Gallery
   -------------------------------------------------------------------------- */
.gallery {
  display: grid;
  gap: var(--space-5);
}

@media (min-width: 60rem) {
  .gallery {
    grid-template-columns: repeat(2, 1fr);
  }

  .gallery__item--wide {
    grid-column: 1 / -1;
  }
}

.gallery__trigger {
  display: block;
  width: 100%;
  padding: 0;
  cursor: zoom-in;
}

.gallery__caption {
  margin-block-start: var(--space-2);
  font-size: var(--step--1);
  line-height: var(--leading-loose);
  color: var(--text-quiet);
  max-width: 44ch;
}

/* --------------------------------------------------------------------------
   Project nav
   -------------------------------------------------------------------------- */
.project-nav__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block-start: var(--space-5);
  border-block-start: 1px solid var(--line);
}

.project-nav__item {
  display: grid;
  gap: var(--space-1);
  color: var(--text);
  text-decoration: none;
}

.project-nav__item--next {
  text-align: right;
}

.project-nav__title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-tight);
}

.project-nav__item:hover .project-nav__title {
  color: var(--link);
}

.project-nav__all {
  font-family: var(--font-display);
  font-size: var(--step--1);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-quiet);
}

@media (max-width: 40rem) {
  .project-nav__all {
    order: 3;
    width: 100%;
    text-align: center;
  }
}

/* --------------------------------------------------------------------------
   Lightbox
   -------------------------------------------------------------------------- */
.lightbox {
  width: 100vw;
  max-width: 100vw;
  height: 100svh;
  max-height: 100svh;
  padding: 0;
  border: none;
  background-color: color-mix(in srgb, var(--brand-navy) 96%, black);
  color: var(--brand-ghost);
}

.lightbox::backdrop {
  background-color: color-mix(in srgb, var(--brand-navy) 88%, black);
}

.lightbox__stage {
  display: grid;
  align-content: center;
  gap: var(--space-3);
  height: 100%;
  padding: var(--space-6) var(--space-7);
}

.lightbox__media img {
  max-height: 76svh;
  width: auto;
  margin-inline: auto;
  object-fit: contain;
}

.lightbox__caption {
  text-align: center;
  font-size: var(--step--1);
  color: var(--text-invert-quiet);
}

.lightbox__caption:empty {
  display: none;
}

.lightbox__close,
.lightbox__nav {
  position: absolute;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  font-size: var(--step-1);
  color: var(--brand-ghost);
  border: 1px solid var(--line-invert);
  border-radius: var(--radius-pill);
  background-color: color-mix(in srgb, var(--brand-navy) 60%, transparent);
  transition: border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.lightbox__close:hover,
.lightbox__nav:hover {
  border-color: var(--brand-ice);
  color: var(--brand-ice);
}

.lightbox__close {
  inset-block-start: var(--space-3);
  inset-inline-end: var(--space-3);
}

.lightbox__nav--prev {
  inset-block-start: 50%;
  inset-inline-start: var(--space-2);
  transform: translateY(-50%);
}

.lightbox__nav--next {
  inset-block-start: 50%;
  inset-inline-end: var(--space-2);
  transform: translateY(-50%);
}

@media (max-width: 40rem) {
  .lightbox__stage {
    padding: var(--space-6) var(--space-3);
  }

  .lightbox__nav {
    inset-block-start: auto;
    inset-block-end: var(--space-3);
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   Process (about)
   -------------------------------------------------------------------------- */
.process__list {
  display: grid;
  gap: var(--space-6);
  margin-block-start: var(--space-6);
  counter-reset: step;
}

@media (min-width: 52rem) {
  .process__list {
    grid-template-columns: repeat(3, 1fr);
  }
}

.process__step {
  display: grid;
  gap: var(--space-2);
  align-content: start;
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--line-accent);
}

.process__number {
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-eyebrow);
  color: var(--brand-ice);
}

.process__name {
  font-size: var(--step-2);
}

/* The step's one-line promise: full-strength and a size up, so it reads
   first; the muted body below explains it. */
.process__lede {
  font-size: var(--step-1);
  line-height: 1.35;
}

.process__body {
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Credential list
   -------------------------------------------------------------------------- */
.cred-list {
  display: grid;
  gap: var(--space-3);
  margin-block-start: var(--space-4);
  border-block-start: 1px solid var(--line);
  padding-block-start: var(--space-4);
}

.cred-list__item {
  display: grid;
  gap: var(--space-1);
}

.cred-list__name {
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.cred-list__note {
  font-size: var(--step--1);
  color: var(--text-quiet);
}

/* --------------------------------------------------------------------------
   Maintenance page
   -------------------------------------------------------------------------- */

/* Credentials as one quiet line, middot-separated like the home hero's
   "Concrete pools · Spas · Landscapes". The dots are generated, so screen
   readers hear a plain list of five. */
.trust-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) 0;
  font-family: var(--font-display);
  font-size: var(--step--2);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-quiet);
}

.trust-list li:not(:last-child)::after {
  content: "·";
  margin-inline: 0.7em;
  color: var(--brand-ice);
}

/* A real table: screen readers can announce "Weekly Service, $80 per visit"
   as a row. Hairlines borrow .cred-list's rule weight. */
.price-list {
  width: 100%;
  border-collapse: collapse;
}

.price-list tr {
  border-block-end: 1px solid var(--line);
}

.price-list tbody tr:first-child {
  border-block-start: 1px solid var(--line);
}

.price-list__service,
.price-list__price {
  padding-block: var(--space-3);
  vertical-align: baseline;
}

/* The service names are short and should hold one line; the long one-off
   price is the one that wraps. */
.price-list__service {
  white-space: nowrap;
  padding-inline-end: var(--space-4);
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-align: start;
  text-transform: uppercase;
}

.price-list__price {
  font-size: var(--step-1);
  text-align: end;
}

/* Phones: label over price, so "$80 call out fee + $80/hour" never has to
   squeeze into half a column. */
@media (max-width: 30rem) {
  .price-list tr {
    display: grid;
    gap: var(--space-1);
    padding-block: var(--space-3);
  }

  .price-list__service,
  .price-list__price {
    padding-block: 0;
    text-align: start;
    white-space: normal;
  }
}

.price-list__note {
  font-size: var(--step--1);
  color: var(--text-quiet);
}

/* Twelve short items, so columns rather than one long run. Each carries a
   Neon Ice tick drawn in CSS — decorative, the list semantics carry the
   meaning. */
/* No top margin: the checklist sits in the section's .grid, whose row gap
   already separates it from the heading above. */
.checklist {
  display: grid;
  gap: var(--space-3) var(--space-5);
}

@media (min-width: 40rem) {
  .checklist {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 64rem) {
  .checklist--wide {
    grid-template-columns: repeat(3, 1fr);
  }
}

.checklist__item {
  position: relative;
  padding-inline-start: 1.6em;
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--line);
  color: var(--text-muted);
}

.checklist__item::before {
  content: "";
  position: absolute;
  inset-inline-start: 0.1em;
  inset-block-start: calc(var(--space-3) + 0.42em);
  width: 0.75em;
  height: 0.4em;
  border-inline-start: 2px solid var(--brand-ice);
  border-block-end: 2px solid var(--brand-ice);
  transform: rotate(-45deg);
}

/* Three square photo details in a row; stacked on phones. */
.detail-strip {
  display: grid;
  gap: var(--gutter);
}

@media (min-width: 40rem) {
  .detail-strip {
    grid-template-columns: repeat(3, 1fr);
  }
}

.terms-list {
  display: grid;
  gap: var(--space-2);
  padding-inline-start: 1.1em;
  list-style: disc;
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--text-muted);
}

.terms-list li::marker {
  color: var(--text-quiet);
}

/* --------------------------------------------------------------------------
   Enquiry form
   -------------------------------------------------------------------------- */
.enquiry {
  display: grid;
  gap: var(--space-6);
}

.enquiry__honey {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

.enquiry__group {
  border: none;
  padding: 0;
  border-block-start: 1px solid var(--line);
  padding-block-start: var(--space-4);
}

.enquiry__legend {
  padding-inline-end: var(--space-3);
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-quiet);
}

.field-row {
  display: grid;
  gap: var(--space-4);
  margin-block-start: var(--space-4);
}

@media (min-width: 40rem) {
  .field-row {
    grid-template-columns: repeat(2, 1fr);
  }

  .field--full {
    grid-column: 1 / -1;
  }

  /* Subgrid so label, hint, control and error line up across each pair of
     fields regardless of whether one of them carries a hint. */
  .field-row > .field {
    grid-row: span 4;
    grid-template-rows: subgrid;
  }
}

.field {
  display: grid;
  gap: var(--space-1);
  align-content: start;
}

/* An empty hint still holds its row open, keeping the pair aligned. */
.field__hint:empty {
  min-height: 0;
}

.field--full {
  margin-block-start: var(--space-4);
}

.field__label {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.field__req {
  color: var(--text-error);
}

.field__optional {
  font-size: var(--step--2);
  font-weight: var(--weight-body);
  letter-spacing: var(--tracking-normal);
  text-transform: none;
  color: var(--text-quiet);
}

.field__hint {
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--text-quiet);
}

.field__control {
  width: 100%;
  min-height: 2.75rem;
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-body);
  font-size: var(--step-0);
  color: var(--text);
  background-color: var(--ground-sink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out);
}

.field__control:hover {
  border-color: var(--line-strong);
}

.field__control:focus {
  border-color: var(--focus);
  background-color: var(--brand-ghost);
}

.field__control--textarea {
  min-height: 8rem;
  line-height: var(--leading-body);
  resize: vertical;
}

.field__select-wrap {
  position: relative;
  display: grid;
}

.field__control--select {
  appearance: none;
  padding-inline-end: var(--space-5);
  cursor: pointer;
}

.field__chevron {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: var(--space-3);
  transform: translateY(-50%);
  pointer-events: none;
  color: var(--text-quiet);
}

/* Errors are never signalled by colour alone — each carries a glyph and a
   sentence saying how to fix it. */
.field__error {
  display: none;
  gap: var(--space-1);
  font-size: var(--step--1);
  line-height: 1.4;
  color: var(--text-error);
}

.field__error::before {
  content: "▲";
  font-size: 0.7em;
  line-height: 1.9;
}

.field__error.is-shown {
  display: flex;
}

.field__control[aria-invalid="true"] {
  border-color: var(--text-error);
  border-width: 2px;
}

.checkbox {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3);
  align-items: start;
  margin-block-start: var(--space-4);
  padding: var(--space-3);
  background-color: var(--ground-sink);
  border-radius: var(--radius);
}

.checkbox__control {
  width: 1.4rem;
  height: 1.4rem;
  margin-block-start: 0.15rem;
  accent-color: var(--brand-navy);
  cursor: pointer;
}

.checkbox__label {
  display: grid;
  gap: var(--space-1);
  font-size: var(--step-0);
  cursor: pointer;
}

.checkbox__note {
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--text-quiet);
}

/* A required radio group dressed in the .checkbox card. The legend borrows
   .field__label so the question reads like every other field's label. */
.choice-group {
  margin-block-start: var(--space-4);
  padding: 0;
  border: 0;
  min-inline-size: 0;
}

.choice-group__legend {
  padding: 0;
  margin-block-end: var(--space-2);
}

.choice-group__options {
  display: grid;
  gap: var(--space-2);
}

.choice-group__options .checkbox {
  margin-block-start: 0;
}

.choice-group .field__error {
  margin-block-start: var(--space-2);
}

/* --------------------------------------------------------------------------
   Collapsible section (the enquiry form's "About the project")

   Opens off any radio marked data-opens-project (build, renovate) with
   :has(), so the checked radio is the only
   state — nothing to fall out of sync when the browser restores a form on
   back or reload. js/form.js separately disables the fieldset while shut,
   which is what keeps its required fields out of validation and submission.

   Scoped to .js (set in <head> before paint): without JavaScript nothing
   would re-enable the fields, so the section simply stays open.

   0fr -> 1fr animates to the content's real height with no measuring. Two
   details make it clean:
   · The section shares one .enquiry grid item with The call (see
     contact.mjs), so the form's gap sits above the pair once. The space
     between the section and The call is the group's own bottom margin,
     inside the clip, so it shuts along with the section.
   · overflow: hidden would clip the focus rings of the selects at the
     column edges, so the inner box is widened by the ring's reach and pulled
     back by the same amount.
   -------------------------------------------------------------------------- */
.collapsible__group {
  margin-block-end: var(--space-6);
}

.js .collapsible {
  --ring-reach: calc(var(--focus-width) + var(--focus-offset) + 2px);

  display: grid;
  grid-template-rows: 0fr;
  visibility: hidden;
}

.js .collapsible__inner {
  min-height: 0;
  overflow: hidden;
  padding-inline: var(--ring-reach);
  margin-inline: calc(var(--ring-reach) * -1);
}

.js .enquiry:has([data-opens-project]:checked) .collapsible {
  grid-template-rows: 1fr;
  visibility: visible;
}

@media (prefers-reduced-motion: no-preference) {
  /* Shutting: hold visibility until the slide finishes, then hide. Opening:
     show at once. Hidden content is out of the tab order and the
     accessibility tree, which is the point of toggling it at all. */
  .js .collapsible {
    transition: grid-template-rows var(--dur-slow) var(--ease-out),
      visibility 0s linear var(--dur-slow);
  }

  .js .enquiry:has([data-opens-project]:checked) .collapsible {
    transition: grid-template-rows var(--dur-slow) var(--ease-out),
      visibility 0s linear 0s;
  }
}

.enquiry__summary {
  padding: var(--space-3) var(--space-4);
  border-inline-start: 3px solid var(--fill-error);
  background-color: color-mix(in srgb, var(--fill-error) 8%, var(--ground));
  color: var(--text);
}

.enquiry__summary[hidden] {
  display: none;
}

.enquiry__summary-title {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  margin-block-end: var(--space-2);
}

.enquiry__summary-list {
  display: grid;
  gap: var(--space-1);
  list-style: disc;
  padding-inline-start: var(--space-4);
}

.enquiry__summary-list a {
  color: var(--text-error);
}

.enquiry__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

.enquiry__note {
  font-size: var(--step--1);
  color: var(--text-quiet);
}

.contact-aside {
  align-self: stretch;
}

.contact-aside__inner {
  padding: var(--space-5);
  background-color: var(--ground-sink);
  border-block-start: 1px solid var(--line-accent);
}

@media (min-width: 48rem) {
  .contact-aside__inner {
    position: sticky;
    inset-block-start: calc(var(--header-h) + var(--space-4));
  }
}

.contact-aside__phone {
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-tight);
  text-decoration: none;
}

.thanks {
  padding-block-start: calc(var(--header-h) + var(--section-y));
}

.thanks__waterline {
  margin-block: var(--space-4);
  max-width: 22rem;
}

/* --------------------------------------------------------------------------
   The quiet invite
   -------------------------------------------------------------------------- */
.invite {
  /* Surface (ground, border, radius, shadow) comes from .pill, so this and
     the CTA rail action cannot drift apart. */
  position: fixed;
  /* --invite-dock is set by js/cta.js as the footer arrives: it lifts the
     pill to ride up with the accreditation logos and settle beside them.
     It tracks the scroll position exactly, so it must never be
     transitioned. */
  inset-block-end: max(var(--space-3), var(--invite-dock, 0px));
  inset-inline-end: var(--space-3);
  z-index: var(--z-invite);
  display: flex;
  align-items: stretch;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
}

.invite[hidden] {
  display: none;
}

.invite.is-shown {
  opacity: 1;
  transform: none;
}

.invite__link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.7em 0.6em 0.7em 1.2em;
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--brand-ghost);
  text-decoration: none;
}

.invite__link:hover {
  color: var(--brand-ice);
}

.invite__dismiss {
  display: grid;
  place-items: center;
  width: 2.75rem;
  padding-inline-end: 0.4em;
  font-size: var(--step-1);
  line-height: 1;
  color: var(--text-invert-quiet);
  border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
}

.invite__dismiss:hover {
  color: var(--brand-ghost);
}

@media (max-width: 40rem) {
  .invite {
    inset-inline: var(--space-3);
    justify-content: space-between;
  }
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */
.site-footer {
  position: relative;
  padding-block: var(--space-7) var(--space-5);
}

/* Set by js/cta.js only while the "Start your project" pill is showing AND
   there is no room for it beside the accreditation logos (phones and narrow
   tablets). The pill then stays at the bottom of the window, so the footer
   grows by exactly the pill's height and offset to keep the copyright line
   clear of it. Everywhere else the pill docks beside the logos instead. */
.site-footer--clear-invite {
  padding-block-end: calc(var(--space-5) + var(--invite-clearance, 0px));
}

.site-footer__inner {
  display: grid;
  gap: var(--space-5);
}

@media (min-width: 48rem) {
  .site-footer__inner {
    grid-template-columns: 1.6fr repeat(3, 1fr);
    gap: var(--space-6) var(--gutter);
  }

  .site-footer__col--wide {
    grid-column: 2 / -1;
  }
}

.site-footer__brand {
  display: grid;
  gap: var(--space-3);
  align-content: start;
}

.site-footer__tagline {
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--step-1);
  color: var(--brand-ice);
}

.site-footer a {
  color: var(--text-invert-muted);
  text-decoration: none;
}

.site-footer a:hover {
  color: var(--brand-ice);
}

/* Follow links: brand mark beside the name. The mark is currentColor, so it
   shares the link's colour and hover. */
.site-footer__social {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.site-footer__icon {
  flex: none;
  width: 1.1em;
  height: 1.1em;
}

/* .site-footer a paints links a muted blue-grey. The wordmark is an anchor
   too, but it is the brand — it must stay full strength. */
.site-footer .wordmark {
  color: var(--brand-ghost);
}

.site-footer__quiet {
  font-size: var(--step--1);
  color: var(--text-invert-quiet);
}

/* Accreditation logos above the copyright, both left-aligned to the shell's
   edge. A column rather than the old space-between row, which pushed the
   credentials under the fixed pill at the right. */
.site-footer__base {
  display: grid;
  justify-items: start;
  gap: var(--space-4);
  margin-block-start: var(--space-6);
  padding-block-start: var(--space-5);
  border-block-start: 1px solid var(--line-invert);
}

/* All stacked or all in one row — never a partial wrap (two side by side,
   one below). The row needs ~761px of content width (three logos plus two
   gaps); with the shell's 5vw gutters that arrives at an 864px viewport.
   54rem = 864px: media-query rem resolves against the initial 16px, not the
   125% root. Re-measure if a logo is added or resized. */
.site-footer__creds {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
}

@media (min-width: 54rem) {
  .site-footer__creds {
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--space-6);
  }
}

/* Sized by height for similar visual weight rather than equal height: the
   Master Pool Builders mark is a thin 8:1 strip, SPASA a deeper ~4:1 block.
   Each logo's height lives with its data (logo.displayHeight in
   content/site.mjs) so reordering the list cannot swap the sizes. Both files
   are tightly cropped, so each mark's left edge is its ink. */
.site-footer__cred {
  display: block;
  width: auto;
  height: var(--cred-height, 1.6rem);
}

/* --------------------------------------------------------------------------
   Bleed frame — full-width media inside a shell
   -------------------------------------------------------------------------- */
.bleed-frame {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
}
