/* ==========================================================================
   Suttons Business Park — Components
   --------------------------------------------------------------------------
   Every UI element identified across the five page comps, tokenised and
   ready for real content. Ordered roughly by where it appears in the build.
   Depends on: tokens.css, base.css
   --------------------------------------------------------------------------
   CONTENTS
     01  Buttons & pills
     02  Site header / primary nav
     03  Mobile nav drawer
     04  Hero
     05  "Inspired" drum  (iOS-picker wheel + click sound)
     06  Aerial band
     07  Occupier ribbon  (right-to-left marquee)
     08  Specification rows / accordion
     09  Feature icon row
     10  Unit cards & availability carousel
     11  Availability list (light theme)
     12  Site plan  (toggle · zoom · hotspots · callouts)
     13  Toggle switch
     14  Live map & info panel  (tabs + data table)
     15  Watermark band
     16  Contact & agent card
     17  Contact panel  (the Contact page)
     18  Site footer
     19  Cookie consent bar
     20  Brochure banner
   ========================================================================== */


/* ==========================================================================
   01  BUTTONS & PILLS
   --------------------------------------------------------------------------
   One shape across the whole site: a 40px-radius pill with a hairline
   border and light-weight display type. Variants change the border and
   fill only.

     <a class="btn">Explore Downloads</a>
     <a class="btn btn--accent">Download Brochure</a>
     <a class="btn btn--solid">Enquire</a>
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);

  /* 26px side padding on a 38px pill at 1440 — measured off the comp */
  padding: var(--space-3xs) var(--btn-padding-x);
  min-height: 38px;

  font-family: var(--font-display);
  font-size: var(--fs-button);
  line-height: var(--lh-button);
  font-weight: var(--fw-light);
  white-space: nowrap;

  color: var(--color-text);
  background-color: var(--color-btn-surface);
  border: var(--border-thin) solid var(--color-border);
  border-radius: var(--radius-pill);

  cursor: pointer;
  transition: color        var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              transform    var(--dur-fast) var(--ease-out);
}

.btn:hover {
  background-color: var(--color-btn-surface-hover);
  border-color: var(--color-text);
  transform: translateY(-1px);
}

.btn:active { transform: translateY(0); }

.btn[aria-disabled='true'],
.btn:disabled {
  opacity: 0.45;
  pointer-events: none;
}

/* Variants */

/* Emphasised pair-mate — solid blue with a subtle border.
   In the hero this is "Google Maps" next to the default "Explore Downloads". */
.btn--primary {
  background-color: var(--color-btn-primary-bg);
  border-color: var(--color-btn-primary-border);
  color: var(--sbp-white);
}
.btn--primary:hover {
  background-color: var(--color-btn-primary-bg-hover);
  border-color: var(--color-btn-primary-border);
  color: var(--sbp-white);
}

/* Gold hairline over a light glass fill — "Explore All Availability" and
   the footer's "Google Maps". Fills gold on hover, label flips to ink. */
.btn--accent {
  background-color: var(--color-btn-surface-light);
  border-color: var(--color-border-accent);
  color: var(--color-text);
}
.btn--accent:hover {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-on-accent);
}

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

.btn--solid {
  background-color: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-on-accent);
}
.btn--solid:hover {
  background-color: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
  color: var(--color-on-accent);
}

.btn--ghost       { border-color: transparent; }
.btn--ghost:hover { border-color: var(--color-border); }

/* Soft filled slate — "Download Brochure" on the light Availability page,
   where an outline pill would disappear against the mist background. */
.btn--muted {
  background-color: rgba(85, 100, 125, 0.72);
  border-color: transparent;
  color: var(--sbp-white);
}
.btn--muted:hover {
  background-color: var(--sbp-slate-500);
  border-color: transparent;
  color: var(--sbp-white);
}

/* Sizes */
.btn--sm { padding: var(--space-3xs) var(--space-sm); min-height: 32px; font-size: var(--fs-body-s); }
.btn--lg { padding: var(--space-xs) var(--space-lg);  min-height: 48px; }
.btn--block { display: flex; width: 100%; }

/* Circular icon button — carousel arrows, the site-plan zoom "+" */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--btn-icon-size, 40px);
  height: var(--btn-icon-size, 40px);
  color: var(--color-text);
  border: var(--border-thin) solid var(--color-border);
  border-radius: var(--radius-full);
  background-color: transparent;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.btn-icon:hover { color: var(--color-accent); border-color: var(--color-accent); }
.btn-icon--lg   { --btn-icon-size: 48px; }
.btn-icon svg   { width: 40%; height: 40%; }

/* Filled, borderless — the carousel arrows */
.btn-icon--solid {
  background-color: var(--sbp-slate-500);
  border-color: transparent;
  color: var(--sbp-white);
}
.btn-icon--solid:hover {
  background-color: var(--sbp-blue-300);
  border-color: transparent;
  color: var(--sbp-white);
}

.btn-icon:disabled {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}

/* Text link with animated underline */
.link-underline {
  position: relative;
  padding-bottom: 2px;
}
.link-underline::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-base) var(--ease-out);
}
.link-underline:hover::after { transform: scaleX(1); }


/* ==========================================================================
   02  SITE HEADER / PRIMARY NAV
   --------------------------------------------------------------------------
   Transparent over the hero, gains a scrim once scrolled. JS adds
   .is-scrolled — see app.js.
   ========================================================================== */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: var(--z-header);
  height: var(--header-height);
  display: flex;
  align-items: center;
}

/* Over the hero the page's own scrim does the work, so the header carries
   no chrome of its own until it leaves the hero.
   ---------------------------------------------------------------------
   The scrim sits on a pseudo-element and NOT on .site-header, which is the
   whole point of this rule. backdrop-filter makes its own element a
   containing block for every fixed-position descendant — and the mobile
   drawer, .nav-primary, is one of those. With the filter on the header,
   `inset: 0` on an open drawer resolved against a 60px-tall header rather
   than the viewport: below the fold the drawer's background stopped at the
   header and the menu items spilled onto the page with nothing behind them.

   A pseudo-element has no descendants, so it can carry the filter safely.
   Absolutely positioned, so it is out of flow and never becomes a flex item
   of the header. */
.site-header::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;

  background-color: rgba(9, 18, 24, 0.85);
  backdrop-filter: var(--blur-panel);

  /* Fading opacity rather than animating backdrop-filter from none — the
     filter cannot interpolate from a keyword, and re-rasterising a blur on
     every frame is the expensive way to do this. */
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
}
.site-header.is-scrolled::before { opacity: 1; }

/* Light pages (Availability) have no scrim to sit on, so the white lockup
   and menu would vanish against the mist. Invert them until the header
   picks up its dark backdrop on scroll.
   brightness(0) rather than invert(1): the logo is white-on-transparent, so
   this drives it to solid ink without touching the alpha.

   :not(.nav-is-open) because the mobile drawer brings its own dark
   background and covers the page entirely. Inverted against that, the menu
   was near-black on near-black and the lockup disappeared outright — the
   inversion answers the white page, and once the drawer is over the white
   page there is no white page to answer. */
.sbp-light:not(.nav-is-open) .site-header:not(.is-scrolled) .site-brand__logo { filter: brightness(0); }
.sbp-light:not(.nav-is-open) .site-header:not(.is-scrolled) .nav-primary .nav-menu > li > a { color: var(--sbp-ink); }
.sbp-light:not(.nav-is-open) .site-header:not(.is-scrolled) .nav-toggle__bar { background: var(--sbp-ink); }

.site-header__inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  width: 100%;
}

/* Logo lockup — sygnet + wordmark, supplied as one SVG.
   148 x 29.6 at 1440, sitting flush to the 45px gutter. */
.site-brand { display: inline-flex; align-items: center; flex-shrink: 0; }
.site-brand__logo {
  width: auto;
  height: var(--logo-height);
  transition: opacity var(--dur-fast) var(--ease-out);
}
.site-brand:hover .site-brand__logo { opacity: 0.75; }

/* Sits inline with the menu, just past the last item */
.nav-primary {
  display: flex;
  align-items: center;
  gap: var(--nav-gap);
}

/* --- sound switch -------------------------------------------------------
   Muted state reads two ways at once: the diagonal appears AND the whole
   control dims, so it is legible without relying on the small stroke. */
.sound-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
  color: var(--color-text);
  transition: color var(--dur-fast) var(--ease-out);
}
.sound-toggle[hidden] { display: none; }

/* The fade sits on the ICON, not the button — putting it on the button
   would drag the tooltip down with it. */
.sound-toggle__icon {
  width: 18px;
  height: 18px;
  opacity: 0.85;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.sound-toggle:hover .sound-toggle__icon { opacity: 1; }

.sound-toggle__slash {
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}

.sound-toggle[aria-pressed='true'] .sound-toggle__icon       { opacity: 0.45; }
.sound-toggle[aria-pressed='true']:hover .sound-toggle__icon { opacity: 0.7; }
.sound-toggle[aria-pressed='true'] .sound-toggle__slash { opacity: 1; }
.sound-toggle[aria-pressed='true'] .sound-toggle__wave  { opacity: 0.35; }

/* dark control on the light pages, matching the menu */
/* Inside .nav-primary, so on mobile it rides inside the drawer — same
   :not(.nav-is-open) guard as the lockup and the menu above. */
.sbp-light:not(.nav-is-open) .site-header:not(.is-scrolled) .sound-toggle { color: var(--sbp-ink); }

/* --- tooltip ------------------------------------------------------------
   Pure CSS off a data attribute, so there is no JS to keep in sync and the
   browser's own title tooltip never double-fires. */
.has-tip { position: relative; }

.has-tip::after {
  content: attr(data-tip);
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: var(--z-overlay);

  padding: 5px 9px;
  border-radius: 4px;
  background-color: rgba(9, 18, 24, 0.94);
  color: var(--sbp-white);
  font-size: var(--fs-legal);
  line-height: 1.4;
  white-space: nowrap;

  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity    var(--dur-fast) var(--ease-out),
              transform  var(--dur-fast) var(--ease-out),
              visibility var(--dur-fast) var(--ease-out);
}

.has-tip:hover::after,
.has-tip:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transform: none;
}

@media (max-width: 900px) {
  /* no hover to speak of, and the drawer has no room for it */
  .has-tip::after { display: none; }
}

/* Primary menu */
.nav-primary .nav-menu {
  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav-primary .nav-menu > li > a {
  display: block;
  position: relative;
  padding-block: var(--space-2xs);
  font-size: var(--fs-nav);
  line-height: var(--lh-nav);
  font-weight: var(--fw-regular);
  color: var(--color-text);
}

/* No underline and no current-page marker — the comp shows five plain white
   items. Hover feedback is a slight dim only, so nothing is drawn that the
   design does not have. (base.css turns links gold on hover; overridden.) */
.nav-primary .nav-menu > li > a:hover,
.nav-primary .nav-menu > li > a:focus-visible {
  color: var(--color-text);
  opacity: 0.65;
}


/* ==========================================================================
   03  MOBILE NAV DRAWER
   ========================================================================== */

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 0 8px;
}
.nav-toggle__bar {
  display: block;
  height: 1.5px;
  width: 100%;
  background: var(--color-text);
  transition: transform var(--dur-base) var(--ease-out),
              opacity   var(--dur-fast) var(--ease-out);
}
.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

@media (max-width: 900px) {
  /* z-index because the drawer is a later sibling covering the whole
     viewport: without it the drawer paints over the button that opened it
     and the X cannot be tapped, leaving Escape — which a phone does not
     have — and tapping a menu item as the only ways out. */
  .nav-toggle {
    display: flex;
    position: relative;
    z-index: 2;
  }

  .nav-primary {
    position: fixed;
    inset: 0;
    display: grid;
    place-items: center;
    background: var(--grad-section);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--dur-base) var(--ease-out),
                visibility var(--dur-base) var(--ease-out);
  }
  .nav-primary.is-open { opacity: 1; visibility: visible; }

  .nav-primary .nav-menu {
    flex-direction: column;
    gap: var(--space-md);
    text-align: center;
  }
  .nav-primary .nav-menu > li > a {
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
  }
}


/* ==========================================================================
   04  HERO
   --------------------------------------------------------------------------
   Full-bleed Ken Burns image, top scrim, headline bottom-left, button
   cluster beneath. Headline is two-tone: light first line, bold second.
   ========================================================================== */

.hero {
  position: relative;
  display: flex;
  align-items: flex-start;          /* content is TOP-anchored in the comp */
  overflow: hidden;
  background-color: var(--sbp-ink);
  color: var(--sbp-white);

  /* The comp band is 1440 x 1005. Track that ratio, but never let the hero
     grow past the viewport on tall or narrow screens. */
  height: min(var(--hero-ratio), 100svh);
  min-height: 560px;

  padding-top: var(--hero-title-top);
  padding-bottom: var(--space-2xl);
}

/* Inner pages sit shorter */
.hero--short {
  height: min(48vw, 70svh);
  min-height: 420px;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.hero__media img,
.hero__media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-focus, center);
}

/* Darkens the top third only — the nav and headline stay legible while the
   aerial photography below them keeps its contrast. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--grad-scrim-hero);
  pointer-events: none;
}

.hero__inner {
  position: relative;
  z-index: 2;
  width: 100%;
}

/* 50/60, weight 300, +0.05em, uppercase — with the emphatic half at 700.
   max-width matches the comp's 1146px measure, which is what forces the
   break after "INDUSTRIAL". */
.hero__title {
  font-size: var(--fs-display-s);
  line-height: var(--lh-display-s);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  text-wrap: balance;
  max-width: 1146px;
}
.hero__title strong,
.hero__title .is-bold { font-weight: var(--fw-bold); }

/* Reserved for a larger treatment if a page needs it */
.hero__title--xl {
  font-size: var(--fs-display-m);
  line-height: var(--lh-display-m);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9375rem;                    /* 15px between the two pills */
  margin-top: var(--hero-actions-gap);
}

/* Scroll cue */
.hero__cue {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: var(--space-md);
  transform: translateX(-50%);
  color: var(--color-text-muted);
}

@media (max-width: 900px) {
  .hero {
    height: auto;
    min-height: 88svh;
  }
  .hero--short { height: auto; min-height: 60svh; }
  .hero__title { text-wrap: pretty; }
}


/* ==========================================================================
   05  "INSPIRED" DRUM
   --------------------------------------------------------------------------
   "INSPIRED" sits fixed in gold; the word column beside it turns like an
   iOS picker wheel — BUSINESS / COMMUNITY / LOCATION / SERVICES / LOGISTICS
   rotating through a five-slot window, the centre word at full white and
   its neighbours falling away. The copy on the right swaps with it.

   Geometry is comp-exact at 1440: lead at x=116, words at x=335, 60px
   pitch, 40/60 bold, panel 501px wide ending at x=1243.

   app.js drives it on rAF from a single continuous `index` float, so drag,
   auto-advance and click all share one motion model. Positions wrap, so the
   wheel spins forever in either direction.

     <div class="inspired__inner">
       <p class="inspired__lead">Inspired</p>
       <div class="drum" role="listbox" tabindex="0">
         <ul class="drum__list">
           <li class="drum__item" role="option" id="w-business">Business</li> …
         </ul>
       </div>
       <div class="inspired__panels">
         <div class="inspired__panel" id="p-business">…</div> …
       </div>
     </div>
   ========================================================================== */

/* Carries the wash only when it stands alone. Inside a .band the wrapper
   owns the gradient so it runs unbroken across every section in the group. */
.inspired { background: var(--grad-band); }
.band > .inspired { background: transparent; }

/* The container's 10% inset sets the measure — the lead sits hard left, the
   copy panel hard right, within it. */
.inspired__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
}

.inspired__head {
  display: flex;
  align-items: center;
  gap: var(--inspired-gap);
  flex: 0 0 auto;
}

.inspired__lead {
  font-size: var(--fs-h1);
  line-height: var(--drum-item-h);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--color-accent);
  white-space: nowrap;
}

/* --- the wheel ---------------------------------------------------------- */

.drum {
  position: relative;
  flex: 0 0 auto;
  height: calc(var(--drum-item-h) * var(--drum-visible));
  min-width: 1px;
  overflow: hidden;
  cursor: grab;

  /* Vertical gestures belong to the wheel, so they are claimed here: with
     the pan-y this used to carry, the browser took every upward or downward
     swipe as a page scroll, the drum never saw the drag, and on a phone the
     whole site slid away under the finger.

     pan-x and pinch-zoom are left native deliberately — nothing here reads a
     horizontal swipe, and taking pinch-zoom away from a page of small type
     is not ours to do. */
  touch-action: pan-x pinch-zoom;
  -webkit-user-select: none;
  user-select: none;

  /* fades the wrap-around at both ends */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 24%, #000 76%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 24%, #000 76%, transparent 100%);
}
.drum.is-dragging { cursor: grabbing; }
.drum:focus-visible { outline: var(--border-thick) solid var(--color-focus); outline-offset: 6px; }

/* All items share one grid cell so they stack — and unlike absolute
   positioning, the cell still gives .drum its intrinsic width (the widest
   word), which overflow:hidden then has something to clip. */
.drum__list {
  display: grid;
  grid-template-columns: max-content;
  grid-template-rows: var(--drum-item-h);
  align-content: center;
  justify-items: start;
  height: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.drum__item {
  grid-area: 1 / 1;
  height: var(--drum-item-h);

  display: flex;
  align-items: center;
  white-space: nowrap;
  cursor: pointer;

  font-size: var(--fs-h1);
  line-height: 1;
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--color-text);

  /* app.js writes --y / --o / --s each frame */
  transform: translate3d(0, var(--y, 0px), 0) scale(var(--s, 1));
  transform-origin: left center;
  opacity: var(--o, 1);
  will-change: transform, opacity;
}

/* Without JS the wheel unstacks into a plain readable list */
.no-js .drum { height: auto; -webkit-mask-image: none; mask-image: none; }
.no-js .drum__list { grid-template-rows: none; align-content: start; }
.no-js .drum__item { grid-area: auto; opacity: 1; transform: none; }

/* --- copy panels -------------------------------------------------------- */

.inspired__panels {
  position: relative;
  flex: 0 1 auto;
  width: 100%;
  max-width: 550px;
  min-height: calc(var(--lh-panel, 2rem) * 5);
}

.inspired__panel {
  position: absolute;
  inset: 0;
  margin: auto 0;
  height: max-content;

  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  font-weight: var(--fw-regular);
  text-align: center;
  color: var(--color-text);

  /* The swap is a cut, and the softening is movement rather than opacity.
     Both of the obvious alternatives fail, in opposite directions: fading
     the two at once puts two paragraphs at half opacity on top of each
     other and reads as garbled, while fading one out before the other in
     leaves a moment with nothing on screen at all — and on a fast spin,
     that moment is the whole spin.

     So the outgoing panel is gone on the same frame the incoming arrives at
     full opacity. There is always exactly one, always readable. What eases
     is the new one's rise into place. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(0.4rem);
  transition: none;
}
.inspired__panel strong { font-weight: var(--fw-bold); }

.inspired__panel.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  /* transform only — opacity and visibility land instantly, which is what
     keeps a panel on screen at every moment of a swap.

     Short, because the copy now changes on every word the wheel passes. A
     320ms settle never finished between two words of a fast spin, so the
     text sat permanently mid-rise and jittered; at 140ms it lands between
     them and reads as each line arriving rather than wobbling. */
  transition: transform 140ms var(--ease-out);
}

.no-js .inspired__panel {
  position: static;
  opacity: 1;
  visibility: visible;
  transform: none;
  margin-bottom: var(--space-md);
}

@media (max-width: 900px) {
  .inspired__inner {
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding-inline: 0;
    gap: var(--space-xl);
  }
  .inspired__head { flex-direction: column; gap: 0; }
  .inspired__panels { max-width: 40ch; }
  .drum__list { justify-items: center; }
  .drum__item { transform-origin: center; }
}

/* Beside the wheel rather than under it, the copy panel used to be pushed
   hard against the right edge by space-between: as the window widened its
   centre drifted further and further from the middle of the gap — 158px off
   by 1700px, and still going. Letting the box take the leftover room and
   centring the measure inside it keeps the copy in the middle of that space
   at any width.

   Row layout only. In the column layout above, `flex: 1 1 auto` grows the
   HEIGHT and the width collapses to zero, which is exactly what it did. */
@media (min-width: 901px) {
  .inspired__panels {
    flex: 1 1 auto;
    max-width: none;
    min-width: 0;
  }

  .inspired__panel {
    /* auto on all four sides, not `auto 0`: centres the measure across the
       space as well as down it. */
    margin: auto;
    width: max-content;
    /* 550px is the measure; 100% is the room actually left beside the wheel.
       Whichever is smaller — at the narrow end of the row layout the space
       is under 550 and an uncapped measure spills out of both sides of it. */
    max-width: min(550px, 100%);
  }
}


/* ==========================================================================
   06  AERIAL BAND
   --------------------------------------------------------------------------
   The annotated aerial. Full width of the page gutter (80px a side), not
   capped — it is a map, so it wants the room.

   The source art is 1.351:1 but the comp shows the band at 1440x853
   (1.687:1), so the frame sets that ratio and the image covers it. The
   object-position bias trims sky off the top rather than cutting the
   labels or the park off the bottom.
   ========================================================================== */

.aerial__frame {
  position: relative;
  margin: 0;
  overflow: hidden;
  aspect-ratio: 1440 / 853;
  background-color: var(--sbp-ink);
}

/* The frame's shape as a plain number, for the scroll-expand height in
   animations.css — calc() cannot divide by "1440 / 853" written as a value,
   so the two are kept in step by hand. On the section, not the frame,
   because .expand-track derives its own height from it too. */
.aerial { --frame-ratio: 1.688; }

.aerial__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 72%;
}

/* Narrow screens crop far less, so the annotations stay readable */
@media (max-width: 900px) {
  .aerial { --frame-ratio: 1.3333; }
  .aerial__frame { aspect-ratio: 4 / 3; }
  .aerial__frame img { object-position: center 60%; }
}
@media (max-width: 600px) {
  .aerial { --frame-ratio: 1; }
  .aerial__frame { aspect-ratio: 1 / 1; }
  .aerial__frame img { object-position: center 55%; }
}


/* ==========================================================================
   07  OCCUPIER RIBBON
   --------------------------------------------------------------------------
   Tenant marks drifting right-to-left, edge to edge, fading out at both
   sides. The track must be duplicated in the markup — the animation travels
   exactly -50%, so copy two lands where copy one began and the loop is
   seamless with no jump.

     <div class="logo-ribbon">
       <div class="logo-ribbon__track">
         …items…            <!-- copy 1 -->
         …items…            <!-- copy 2, aria-hidden -->
       </div>
     </div>
   ========================================================================== */

.logo-ribbon {
  position: relative;
  margin-top: var(--space-lg);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 7%, #000 93%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0%, #000 7%, #000 93%, transparent 100%);
}

/* Spacing lives on the ITEMS, not as a track `gap`, and that matters.
   With `gap`, N items have only N−1 gaps, so half the track width is not
   one whole copy — the -50% loop lands half a gap out and visibly jumps
   every pass. Giving each item its own trailing margin makes every item
   (slot + gap) wide, so half the track is precisely one copy. */
.logo-ribbon__track {
  display: flex;
  align-items: center;
  width: max-content;
  animation: sbp-marquee var(--ribbon-duration) linear infinite;
  will-change: transform;
}

/* Let people stop it to read a mark */
.logo-ribbon:hover .logo-ribbon__track,
.logo-ribbon:focus-within .logo-ribbon__track { animation-play-state: paused; }

/* Each mark sits in a fixed slot rather than being sized by height alone.
   The artwork is alpha-trimmed and wildly inconsistent in aspect (Moog is
   400x54, Costa is 400x400) — a shared box with object-fit is the only
   thing that makes the row read as optically even. */
.logo-ribbon__item {
  flex: 0 0 auto;
  width:  var(--logo-slot-w);
  height: var(--logo-slot-h);
  margin-right: var(--ribbon-gap);
}

/* The image fills the slot and object-fit does the scaling. Sizing it with
   max-width/max-height instead would silently fail — those percentages
   resolve against an auto-sized track, so nothing constrains them and the
   marks overflow. */
.logo-ribbon__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  opacity: 0.85;
  transition: opacity var(--dur-base) var(--ease-out);
}
.logo-ribbon__item:hover .logo-ribbon__img { opacity: 1; }

/* Optical sizing.
   The slot is the same for every mark, but the artwork inside each one is
   not: some are drawn with margin around them, some run to the very edge of
   their canvas. contain fits the canvas, not the mark, so the tight ones sit
   noticeably larger than the rest of the strip.

   scale() rather than a smaller width/height, so the correction never
   touches the slot and the marks stay evenly spaced. Add a line here per
   mark that needs it; the class comes from the slug in occupiers.php. */
.logo-ribbon__img--sig { transform: scale(0.85); }

/* No motion → no marquee, so make it scrollable instead of hiding marks */
@media (prefers-reduced-motion: reduce) {
  .logo-ribbon {
    overflow-x: auto;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .logo-ribbon__track { animation: none; }
}


/* ==========================================================================
   07  SPECIFICATION ROWS / ACCORDION
   --------------------------------------------------------------------------
   Numbered rows (01 / 02 / 03) with a hairline rule, an icon, a two-line
   light heading and a bulleted list. On mobile these collapse into a real
   accordion — same markup, <details>-driven.

     <div class="spec-row">
       <span class="spec-row__num">01</span>
       <img class="spec-row__icon" …>
       <h3 class="spec-row__title">High-<br>Specification</h3>
       <ul class="spec-list">…</ul>
     </div>
   ========================================================================== */

.spec-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));   /* even thirds */
  align-items: start;
  gap: var(--space-lg) var(--space-xl);
  padding-block: var(--space-xl);
  border-top: var(--border-thin) solid var(--color-border-subtle);

  transition: border-color var(--dur-base) var(--ease-out);
}
/* No closing rule — the last row runs out into the band */

/* The gold rule that separates rows 1↔2 in the comp */
.spec-row--accent { border-top-color: var(--color-accent); }

/* The number and the icon share one grid cell so they cross-fade in place
   on hover — no reflow, nothing shifts. */
.spec-row__marker {
  display: grid;
  place-items: center start;
  width: 48px;
  height: 48px;
  color: var(--color-text-muted);
  transition: color var(--dur-base) var(--ease-out);
}

.spec-row__num,
.spec-row__icon-wrap {
  grid-area: 1 / 1;
  transition: opacity   var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}

.spec-row__num {
  font-size: var(--fs-h2);
  line-height: 1;
  font-weight: var(--fw-light);
  font-variant-numeric: tabular-nums;
}

/* Inlined so `color` reaches the strokes — the SVGs use currentColor */
.spec-row__icon {
  display: block;
  width: 40px;
  height: 40px;
}

/* Rest: number showing, icon waiting just behind it */
.spec-row__num       { opacity: 1; transform: none; }
.spec-row__icon-wrap { opacity: 0; transform: scale(0.8); }

/* Icon-only variant — nothing to swap. A modifier class rather than
   :not(:has(…)) so it does not depend on :has() support. */
.spec-row__marker--static .spec-row__icon-wrap {
  opacity: 1;
  transform: none;
}

.spec-row__title {
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  font-weight: var(--fw-light);
  /* Muted at rest, like the list beneath it. The title used to sit at full
     white while everything around it was blue, so it had nowhere to go on
     hover — the row lit up and its heading stayed exactly as it was. */
  color: var(--color-text-muted);
  transition: color var(--dur-base) var(--ease-out);
}

.spec-list { margin: 0; padding: 0; list-style: none; }

.spec-list li {
  position: relative;
  padding-left: var(--space-sm);
  font-size: var(--fs-body-s);
  line-height: 2;
  color: var(--color-text-muted);
  transition: color var(--dur-base) var(--ease-out);
}
.spec-list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.85em;
  width: 4px;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--color-accent);
  transition: background-color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}

/* --- hover ---------------------------------------------------------------
   The whole row lights up together: both its rules and its marker take the
   accent, and the list copy comes up to full white.

   Hover only, deliberately. Nothing here is hidden until you interact, so
   the effect is embellishment — putting the rows in the tab order would add
   stops a keyboard user cannot act on. :focus-within is kept in case a link
   is ever added inside a row. */

/* Only the row's OWN top rule takes the accent — the one below it stays put */
.spec-row:hover,
.spec-row:focus-within {
  border-top-color: var(--color-accent);
}

.spec-row:hover .spec-row__marker,
.spec-row:focus-within .spec-row__marker { color: var(--color-accent); }

/* the number gives way to the icon */
.spec-row:hover .spec-row__num,
.spec-row:focus-within .spec-row__num {
  opacity: 0;
  transform: scale(0.8);
}
.spec-row:hover .spec-row__icon-wrap,
.spec-row:focus-within .spec-row__icon-wrap {
  opacity: 1;
  transform: none;
}

/* Title and list come up to white together, so the whole row lifts as one */
.spec-row:hover .spec-row__title,
.spec-row:focus-within .spec-row__title,
.spec-row:hover .spec-list li,
.spec-row:focus-within .spec-list li { color: var(--color-text); }

.spec-row:hover .spec-list li::before,
.spec-row:focus-within .spec-list li::before {
  background-color: var(--color-text);
  transform: scale(1.4);
}

@media (max-width: 900px) {
  .spec-row { grid-template-columns: auto minmax(0, 1fr); }
  .spec-row .spec-list { grid-column: 1 / -1; }
}

/* --- Generic accordion ---------------------------------------------------
   Used for "View Specification +" on the Availability page and anywhere
   else. Built on <details>/<summary> so it works without JS; JS only adds
   the height transition.
   ------------------------------------------------------------------------ */

.accordion__item { border-bottom: var(--border-thin) solid var(--color-border-subtle); }

.accordion__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  width: 100%;
  padding-block: var(--space-xs);
  font-size: var(--fs-body-s);
  color: var(--color-text);
  cursor: pointer;
  list-style: none;
  text-align: left;
}
.accordion__trigger::-webkit-details-marker { display: none; }

/* The "+" that rotates to an "×" */
.accordion__icon {
  position: relative;
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  transition: transform var(--dur-base) var(--ease-out);
}
.accordion__icon::before,
.accordion__icon::after {
  content: '';
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1px;
  background: currentColor;
}
.accordion__icon::after { transform: rotate(90deg); }

.accordion__item[open] > .accordion__trigger .accordion__icon,
.accordion__trigger[aria-expanded='true'] .accordion__icon { transform: rotate(135deg); }

.accordion__panel {
  overflow: hidden;
  padding-bottom: var(--space-sm);
  color: var(--color-text-muted);
  font-size: var(--fs-body-s);
}


/* ==========================================================================
   08  FEATURE ICON ROW
   --------------------------------------------------------------------------
   Five gold line-icons with a bold label and two lines of copy —
   Motorway Access / Talent Pool / Airport Connectivity / Secure Yards /
   Rapid Distribution.
   ========================================================================== */

.feature-row {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--space-lg);
  text-align: center;
}

.feature {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
}

.feature__icon {
  width: 40px;
  height: 40px;
  color: var(--color-accent);
}

.feature__title {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  font-weight: var(--fw-bold);
  color: var(--color-text);
}

.feature__text {
  font-size: var(--fs-caption);
  line-height: 1.6;
  color: var(--color-text-muted);
  max-width: 18ch;
}

/* Two across from 900px all the way down. A single column made each of the
   five features a full screen apart on a phone, so the row read as five
   unrelated statements rather than one set — and the icons, which are the
   quickest thing to scan, were the first casualty. Five in two columns
   leaves the last one on its own; that is the lesser cost. */
@media (max-width: 900px) { .feature-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }


/* ==========================================================================
   09  UNIT CARDS & AVAILABILITY CAROUSEL
   --------------------------------------------------------------------------
   Edge-to-edge, four-up, horizontally scrollable with round prev/next
   controls. Scroll-snap does the work; JS only wires the arrows.
   ========================================================================== */

.carousel { position: relative; }

.carousel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}

.carousel__label {
  font-size: var(--fs-body);
  line-height: 1.4;
  font-weight: var(--fw-regular);
  color: var(--color-text);
}

.carousel__controls { display: flex; gap: var(--space-xs); }

/* Cards are sized from how many should be visible, so a whole number fills
   the row exactly with no orphan peeking at the edge. Change the count per
   breakpoint and the maths follows. */
.carousel__track {
  --carousel-visible: 4;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc(
    (100% - (var(--carousel-visible) - 1) * var(--carousel-gap, var(--space-2xs)))
    / var(--carousel-visible)
  );
  gap: var(--carousel-gap, var(--space-2xs));

  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}
.carousel__track::-webkit-scrollbar { display: none; }
.carousel__track > * { scroll-snap-align: start; }

@media (max-width: 1200px) { .carousel__track { --carousel-visible: 3; } }
@media (max-width: 900px)  { .carousel__track { --carousel-visible: 2; } }
/* a fractional count leaves the next card peeking, which is the cue that
   there is more to swipe */
@media (max-width: 600px)  { .carousel__track { --carousel-visible: 1.15; } }

/* Centred call to action beneath the track */
.carousel__foot {
  display: flex;
  justify-content: center;
  margin-top: var(--space-lg);
}

/* The explore button already carries its own breathing room, so the
   section does not need a full block of padding under it as well. */
.availability { padding-bottom: var(--space-lg); }

/* --- Unit card ---------------------------------------------------------- */

.unit-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  color: var(--color-text);
}
/* the column gap does the spacing here — base.css's `p + p` would add to it */
.unit-card p + p { margin-top: 0; }
.unit-card__media { margin-bottom: var(--space-2xs); }

.unit-card__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 1 / 1;
  background-color: var(--sbp-ink);
}
.unit-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}
.unit-card:hover .unit-card__media img { transform: scale(1.04); }

/* Reference has the unit reference quiet and the availability bright —
   the status is the thing worth reading at a glance. */
.unit-card__name {
  font-size: var(--fs-body-s);
  line-height: 1.4;
  font-weight: var(--fw-regular);
  text-transform: uppercase;
  color: var(--color-text-muted);
  transition: color var(--dur-base) var(--ease-out);
}

.unit-card__status {
  font-size: var(--fs-body-s);
  line-height: 1.4;
  color: var(--color-text);
}
.unit-card__status--let { color: var(--color-text-subtle); }

.unit-card:hover .unit-card__name { color: var(--color-text); }


/* ==========================================================================
   10  AVAILABILITY LIST  (light theme page)
   --------------------------------------------------------------------------
   Alternating text / image rows: eyebrow, status, size headline, a
   "View Specification +" accordion and a Download Brochure pill.
   ========================================================================== */

.unit-row {
  display: grid;
  grid-template-columns: minmax(0, 0.32fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: stretch;
  padding-block: clamp(1.5rem, 3vw, 2.75rem);
}

/* height:0 + min-height:100% is the point of this rule.
   An explicit height keeps the text column OUT of the row's intrinsic
   sizing, so the row is only ever as tall as the photograph; min-height
   then stretches the column back to that resolved height. Without it, a
   long specification list would push the whole row taller than its image. */
.unit-row__body {
  display: flex;
  flex-direction: column;
  gap: 0;
  height: 0;
  min-height: 100%;
}

/* A row arrived at from a carousel card carries .is-targeted, and it is
   deliberately not drawn: no tint, no rule, no border. Scrolling to the row
   and opening its specification is the whole answer — the visitor asked to
   see this unit and is already looking at it.

   The row takes focus on arrival (tabindex="-1"), so the browser's default
   ring is suppressed: a focus ring around an entire row is a border by
   another name. :focus-visible is left alone, because it only fires for
   someone actually tabbing through, never on the programmatic focus that
   unitDeepLink() sets. */
.unit-row[tabindex]:focus { outline: none; }

/* base.css spaces consecutive paragraphs; here the margins below do it */
.unit-row__body p + p { margin-top: 0; }

.unit-row__eyebrow {
  font-size: var(--fs-body-s);
  line-height: 1.4;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  color: var(--color-text);
}

.unit-row__status {
  font-size: var(--fs-body-s);
  line-height: 1.4;
  color: var(--color-text);
}

/* The headline sits away from the label pair */
.unit-row__title {
  margin-top: var(--space-md);
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  font-weight: var(--fw-light);
  color: var(--color-text-muted);
}
.unit-row__size {
  font-weight: var(--fw-bold);
  color: var(--color-text);
}

/* Standing in for a floor area that has not been confirmed. It holds the
   line so the card keeps its shape, but it is not a figure — so it does not
   get the weight and colour a figure earns. */
.unit-row__size--pending {
  font-weight: var(--fw-regular);
  color: var(--color-text-muted);
}

/* Page heading with the rule beneath it */
.availability-head {
  padding-bottom: var(--space-md);
  border-bottom: var(--border-thin) solid var(--color-border-subtle);
}
.availability-head__title {
  font-size: var(--fs-display-s);
  line-height: var(--lh-display-s);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
}
.availability-head__title strong { font-weight: var(--fw-bold); }

/* Specification disclosure --------------------------------------------
   A rule sits above the trigger. Opening it hides the label and leaves
   only the close mark, with a second rule under the list — which is what
   the comp shows in its expanded state. */
.unit-row__spec {
  margin-top: var(--space-md);
  border-top: var(--border-thin) solid var(--color-border-subtle);

  /* the disclosure takes whatever height is left over and no more */
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.unit-row__spec .accordion__trigger { flex: 0 0 auto; }

.unit-row__spec .accordion__trigger {
  justify-content: space-between;
  font-size: var(--fs-body-s);
  color: var(--color-text);
}

/* visibility, not display: the label keeps its box so `space-between` still
   has something to push against and the mark stays exactly where it was —
   it just spins from + to ×, rather than jumping to the left. */
.unit-row__spec .accordion__trigger[aria-expanded='true'] .accordion__label {
  visibility: hidden;
}

/* Scrolls within the space available rather than growing the row */
.unit-row__spec .accordion__panel {
  border-bottom: var(--border-thin) solid var(--color-border-subtle);
  padding-bottom: var(--space-sm);
  color: var(--color-text);

  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.unit-row__spec .spec-list li {
  color: var(--color-text);
  line-height: 1.9;
  padding-left: 0;
}
.unit-row__spec .spec-list li::before { display: none; }

/* pinned to the foot of the column, level with the photograph */
.unit-row__cta { margin-top: auto; padding-top: var(--space-lg); flex: 0 0 auto; }

.unit-row__media {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background-color: var(--sbp-ink);
}
/* Biased low on purpose: several of the unit photographs are portrait with
   a large expanse of sky, so a centred crop lands in the clouds rather than
   on the building. */
.unit-row__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Override per row with style="--unit-focus: center 40%" if a particular
     photograph needs different framing. */
  object-position: var(--unit-focus, center 85%);
}

@media (max-width: 900px) {
  .unit-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }
  .unit-row__media { order: -1; }

  /* Stacked, there is no photograph to borrow a height from — let the
     column size to its content and the list run in full. */
  .unit-row__body { height: auto; min-height: 0; }
  .unit-row__spec .accordion__panel { overflow-y: visible; }
  .unit-row__cta { margin-top: var(--space-md); }
}


/* ==========================================================================
   11  SITE PLAN
   --------------------------------------------------------------------------
   A layered illustration: base plan image, an "available" overlay that
   cross-fades in when the toggle is on, absolutely-positioned unit
   hotspots, callout chips and a zoom control.

     <figure class="siteplan" data-mode="general">
       <img class="siteplan__layer siteplan__layer--base"      …>
       <img class="siteplan__layer siteplan__layer--available" …>
       <button class="siteplan__hotspot" style="--x:38%;--y:52%">57-58</button>
       <div class="siteplan__callout" style="--x:60%;--y:24%">…</div>
       <button class="siteplan__zoom btn-icon">+</button>
     </figure>
   ========================================================================== */

.siteplan {
  position: relative;
  margin: 0;
  overflow: hidden;
}

.siteplan__layer {
  width: 100%;
  height: auto;
  display: block;
}
.siteplan__layer--available {
  position: absolute;
  inset: 0;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-in-out);
}
/* Unscoped so data-mode can sit on the figure OR on any ancestor — The Site
   puts it on the whole section so the wash and icons switch with the plan. */
[data-mode='available'] .siteplan__layer--available { opacity: 1; }
[data-mode='available'] .siteplan__layer--base      { opacity: 0; }

.siteplan__layer--base { transition: opacity var(--dur-slow) var(--ease-in-out); }

/* Unit hotspot */
.siteplan__hotspot {
  position: absolute;
  left: var(--x, 50%);
  top: var(--y, 50%);
  transform: translate(-50%, -50%);

  padding: 2px var(--space-2xs);
  font-size: var(--fs-legal);
  line-height: var(--lh-legal);
  color: var(--color-text);
  background-color: rgba(9, 18, 24, 0.6);
  border: var(--border-hair) solid var(--color-border-subtle);
  border-radius: 3px;

  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.siteplan__hotspot:hover,
.siteplan__hotspot.is-active {
  border-color: var(--color-accent);
  background-color: rgba(224, 175, 90, 0.18);
  transform: translate(-50%, -50%) scale(1.06);
}
.siteplan__hotspot--available { border-color: var(--color-accent); }

/* Callout chip — the Costa/Aldi and Park Management boxes */
.siteplan__callout {
  position: absolute;
  left: var(--x, 50%);
  top: var(--y, 50%);
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  max-width: 240px;
  padding: var(--space-2xs) var(--space-xs);

  font-size: var(--fs-legal);
  line-height: 1.45;
  color: var(--color-text);

  background: var(--grad-panel);
  border: var(--border-hair) solid var(--color-border-subtle);
  border-radius: 4px;
  backdrop-filter: var(--blur-panel);
}

.siteplan__zoom {
  position: absolute;
  left: var(--space-md);
  bottom: var(--siteplan-zoom-bottom);
  z-index: var(--z-raised);
}

/* --- the touch alternative to the magnifier ------------------------------
   The lens is painted wherever the pointer is, which a touchscreen cannot
   supply: there is no hover, so on a phone the button opened a glass that
   sat still and covered the plan. The gesture the platform already provides
   does the job better, so on touch the control is swapped for the sentence
   that tells you about it. */
.siteplan__hint {
  display: none;                       /* fine pointers get the lens instead */
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  margin-bottom: var(--space-sm);
  font-size: var(--fs-body-s);
  line-height: var(--lh-body-s);
  color: var(--color-text-muted);
}

/* The mark is 70 x 82, not square — height is set and the width left to
   follow, or the hand comes out squashed. */
.siteplan__hint-icon {
  flex: 0 0 auto;
  width: auto;
  height: 26px;
  color: var(--color-accent);
}

@media (pointer: coarse) {
  .siteplan__zoom { display: none; }
  .siteplan__hint { display: flex; }
}

/* --- magnifier -----------------------------------------------------------
   The zoom control IS the lens. Clicking it grows the circle to three times
   its size, swaps the hairline for a 3px gold ring and hands it to the
   cursor: the plan is painted inside at 2x, offset so whatever sits under
   the pointer lands in the middle of the glass.

   The plan artwork is transparent, so the lens carries its own backdrop —
   --lens-bg follows the toggle, or the magnified plan would read against
   whatever happens to be behind it. */

/* The figure clips its layers, which would slice the glass in half the
   moment it neared an edge — that hard vertical line. While magnifying,
   let it spill so the lens can roam the whole band uncut. */
.siteplan.is-lensing {
  cursor: none;
  overflow: visible;
}

.siteplan__zoom {
  transition: width var(--dur-base) var(--ease-out),
              height var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              border-width var(--dur-base) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}

.siteplan__zoom.is-lens {
  width: var(--lens-size);
  height: var(--lens-size);

  border: 3px solid var(--color-accent);
  background-color: var(--lens-bg, var(--sbp-charcoal));
  background-repeat: no-repeat;
  box-shadow: var(--shadow-lg);

  /* JS writes left/top on every pointer move */
  left: 0;
  top: 0;
  bottom: auto;
  transform: translate(-50%, -50%);
  pointer-events: none;      /* so the click that closes it reaches the page */
  z-index: var(--z-overlay);
}

.siteplan__zoom.is-lens svg { display: none; }

/* "Click anywhere to close", riding under the glass */
.siteplan__zoom.is-lens::after {
  content: attr(data-lens-hint);
  position: absolute;
  top: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: var(--fs-legal);
  line-height: 1.4;
  color: var(--color-text);
  text-shadow: 0 1px 3px rgba(9, 18, 24, 0.7);
}

/* Hide the fiddly overlay bits on small screens — the plan gets its own
   pinch-zoom treatment there. */
@media (max-width: 720px) {
  .siteplan__hotspot,
  .siteplan__callout { display: none; }
}


/* --- The Site: the whole band answers the toggle -------------------------
   One data-mode on the section drives the wash, the plan and the icon
   colour together, so the flip reads as a single change of state rather
   than three things animating independently. */

.site-band {
  background: var(--grad-site-general);
  transition: background var(--dur-slow) var(--ease-in-out);
}
.site-band[data-mode='available'] { background: var(--grad-site-available); }

/* Backdrop painted inside the magnifier — the plan artwork is transparent,
   so it needs the surface it was drawn for. Roughly the band's value at the
   plan's own height, in each state. */
.site-band                        { --lens-bg: #4E5561; }
.site-band[data-mode='available'] { --lens-bg: #1E242E; }

/* Icons are white while the plan is general, gold once it shows what is
   actually free — the same signal the highlighted units use. */
.site-band .feature__icon {
  color: var(--color-text);
  transition: color var(--dur-slow) var(--ease-in-out);
}
.site-band[data-mode='available'] .feature__icon { color: var(--color-accent); }

/* The general wash is light enough that muted body copy loses contrast —
   the feature text stays white on this page in both states. */
.site-band .feature__text { color: var(--color-text); }

.site-band__title {
  text-align: center;
  font-size: var(--fs-display-s);
  line-height: var(--lh-display-s);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
}
.site-band__title strong { font-weight: var(--fw-bold); }

.site-band__switch {
  display: grid;
  justify-items: center;
  margin-block: var(--space-xl);
}


/* ==========================================================================
   12  TOGGLE SWITCH
   --------------------------------------------------------------------------
   Pill track, circular knob, gold when on. Drives the site-plan layers.

     <button class="toggle" role="switch" aria-checked="false">
       <span class="toggle__track"><span class="toggle__knob"></span></span>
       <span class="toggle__label">Find Out What's Available</span>
     </button>
   ========================================================================== */

.toggle {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2xs);
}

.toggle__track {
  position: relative;
  display: block;
  width: 44px;
  height: 20px;
  border-radius: var(--radius-full);
  background-color: var(--sbp-metal-500);
  transition: background-color var(--dur-base) var(--ease-out);
}

.toggle__knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-full);
  background-color: var(--sbp-white);
  transition: transform var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}

.toggle[aria-checked='true'] .toggle__track { background-color: var(--color-accent); }
.toggle[aria-checked='true'] .toggle__knob  { transform: translateX(24px); background-color: var(--sbp-white); }

.toggle__label {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--color-text-muted);
  transition: color var(--dur-base) var(--ease-out);
}
.toggle[aria-checked='true'] .toggle__label { color: var(--color-text); }


/* ==========================================================================
   13  LIVE MAP & INFO PANEL
   --------------------------------------------------------------------------
   Full-bleed Google map (styled from assets/map/map_style.json) with a
   floating translucent card: intro copy, a row of round transport tabs
   (walk / bus / rail / road) and a swappable data table.
   ========================================================================== */

.map { position: relative; }

/* Keeps the nav legible over whatever the map happens to be showing.
   pointer-events:none so it never intercepts a drag or a scroll-zoom. */
.map::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--map-scrim-height);
  z-index: 1;
  background: var(--grad-scrim-top);
  pointer-events: none;
}

.map__canvas {
  width: 100%;
  /* svh rather than vh: on mobile it measures against the small viewport,
     so the section does not jump as the browser chrome hides and shows */
  height: 85svh;
  min-height: 460px;
  background-color: var(--map-bg);
}

/* The pin is drawn by the Maps API from map/icons_map-marker.svg, sized and
   anchored in custom.js — there is no element here to style. An earlier
   HTML-overlay pin lived at .map__pin; it is gone, and so is its CSS. */

.map__panel {
  position: absolute;
  /* Centred on the map's vertical midpoint.
     Done with inset + auto margins, NOT translateY(-50%): the panel carries
     data-reveal, whose animation owns `transform`, and the two would fight. */
  top: 0;
  bottom: 0;
  margin-block: auto;
  height: max-content;
  max-height: calc(100% - var(--header-height) - var(--space-xl));
  overflow-y: auto;
  right: var(--container-inset);
  z-index: var(--z-raised);
  width: min(440px, calc(100% - var(--container-inset) * 2));
  padding: var(--space-md);

  /* Gradient hairline, gold into blue.
     Done with two background layers rather than border-image: border-image
     ignores border-radius and would square the panel's corners off. The
     padding-box layer is the panel fill, the border-box layer shows only
     where the transparent 1px border sits. */
  border: var(--border-thin) solid transparent;
  border-radius: var(--radius-card);
  background:
    var(--grad-panel) padding-box,
    linear-gradient(329.8deg,
      rgba(224, 175, 90, 0.6) 0.75%,
      rgba(117, 145, 197, 0.6) 99.25%) border-box;

  backdrop-filter: var(--blur-panel);
  box-shadow: var(--shadow-lg);
  color: var(--sbp-white);
}

.map__intro {
  font-size: var(--fs-body-s);
  line-height: var(--lh-body-s);
}

/* Transport tabs */
.map__tabs {
  display: flex;
  gap: var(--space-2xs);
  margin-block: var(--space-md);
}

.map__tab {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-full);
  border: var(--border-thin) solid var(--color-border-subtle);
  color: var(--sbp-white);
  transition: border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.map__tab:hover { border-color: var(--color-border); }
.map__tab[aria-selected='true'] {
  border-color: var(--color-accent);
  color: var(--color-accent);
}
/* Both dimensions set, so the differing viewBoxes letterbox to a common
   optical size instead of stretching. */
.map__tab-icon,
.map__tab svg { width: 18px; height: 18px; }

/* Data table — Road/Rail/Air times & distances */
.map__table { font-size: var(--fs-legal); line-height: 2.1; }

.map__table th {
  text-align: left;
  font-weight: var(--fw-bold);
  color: var(--sbp-white);
  border-bottom: var(--border-hair) solid var(--color-border-subtle);
  padding-bottom: var(--space-3xs);
}
.map__table td {
  color: var(--color-text-muted);
  padding-block: 1px;
}
.map__table td + td,
.map__table th + th { text-align: right; }

.map__table-note {
  font-weight: var(--fw-regular);
  color: var(--color-text-muted);
}

/* Header underline is gold in the comp, and the rows keep a hairline */
.map__table th { border-bottom-color: var(--color-accent); }
.map__table tbody tr + tr td { border-top: var(--border-hair) solid var(--color-border-subtle); }
.map__table td { padding-block: 3px; }

/* Tab panels */
.map__panel-pane { display: none; }
.map__panel-pane.is-active { display: block; }

/* A pane that is plain prose rather than a table (the bus copy) sits above
   a gold rule. */
.map__note {
  padding-top: var(--space-sm);
  border-top: var(--border-thin) solid var(--color-accent);
  font-size: var(--fs-body-s);
  line-height: var(--lh-body-s);
}

@media (max-width: 720px) {
  .map__panel {
    position: static;
    width: 100%;
    border-radius: 0;
    backdrop-filter: none;
  }
}


/* ==========================================================================
   14  WATERMARK BAND
   --------------------------------------------------------------------------
   The oversized brushed-metal word that runs off both edges beneath the
   map ("…LED CONNE…"). Purely decorative — keep it out of the a11y tree.
   ========================================================================== */

.watermark {
  overflow: hidden;
  user-select: none;
  pointer-events: none;
  padding-block: var(--space-lg);
}

/* Travelling leftwards, so each word arrives from the right and leaves at
   the left — the direction the eye already moves through a line of English.
   Drifting the other way meant reading every word backwards out of the edge
   it was heading into.

   sbp-marquee is the occupier ribbon's keyframe, reused: both are the same
   problem, a track of two identical copies shifted by exactly half its own
   width. Each copy carries its own trailing space so half the track is one
   whole copy and the loop never jumps. */
.watermark--scroll .watermark__track {
  display: flex;
  width: max-content;
  animation: sbp-marquee var(--watermark-duration) linear infinite;
  will-change: transform;
}
.watermark--scroll .watermark__text { padding-right: 0.35em; }

@media (prefers-reduced-motion: reduce) {
  .watermark--scroll .watermark__track { animation: none; transform: none; }
}

.watermark__text {
  display: block;
  white-space: nowrap;
  font-size: var(--fs-watermark);
  line-height: var(--lh-watermark);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  background: var(--grad-metal);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}


/* ==========================================================================
   15  CONTACT & AGENT CARD
   ========================================================================== */

.contact-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  flex-wrap: wrap;
}

.contact-cta__text {
  font-size: var(--fs-body-l);
  line-height: var(--lh-body-l);
  color: var(--color-text);
}

/* The agent lockup matches the full height of the contact list beside it.
   The mark needs a wrapper: `align-self: stretch` on an <img> stretches the
   BOX but leaves the artwork at its intrinsic size, so the image is given a
   stretched parent to measure `height: 100%` against instead. */
.agent-block {
  display: flex;
  align-items: stretch;
  gap: var(--space-md);
  flex-wrap: wrap;
}

.agent-block__mark {
  display: flex;
  align-items: stretch;
  flex: 0 0 auto;
}

.agent-block__logo {
  height: 100%;
  width: auto;
  min-height: 100px;         /* floor for when the row wraps on mobile */
  max-height: 130px;
  object-fit: contain;
  object-position: left center;
}

.agent-list { display: grid; gap: var(--space-xs); }

.agent__name {
  font-size: var(--fs-body-s);
  line-height: 1.4;
  font-weight: var(--fw-bold);
  color: var(--color-text);
}
.agent__email {
  font-size: var(--fs-body-s);
  line-height: 1.4;
  color: var(--color-text-muted);
}
.agent__email:hover { color: var(--color-accent); }


/* ==========================================================================
   17  CONTACT PANEL
   --------------------------------------------------------------------------
   One full-bleed photograph with the title, a rule and the agent details
   laid over it. The source frame is portrait with sky above the building —
   which is exactly how the comp uses it, so a plain centred cover crop puts
   sky behind the title and the offices below with no fiddling.
   ========================================================================== */

.contact-panel {
  position: relative;
  display: block;          /* NOT flex: as a flex item the inner container
                              could not shrink below its min-content width
                              and pushed past the viewport */
  overflow: hidden;
  color: var(--sbp-white);

  /* As tall as the photograph, so nothing is cropped at the design width.
     Deliberately NOT aspect-ratio: on a block-level section the ratio
     resolves the WIDTH from the height and the box grew past the viewport
     (1419 x 1.1222 = 1593). Deriving the height from the viewport width
     cannot feed back. --contact-ratio-h is the frame's height / width. */
  min-height: max(calc(100vw * var(--contact-ratio-h)), 520px);

  padding-top: calc(var(--header-height) + var(--space-3xl));
  padding-bottom: var(--space-3xl);

  /* Anchored to the foot so the offices stay in frame whatever the crop */
  background-color: var(--sbp-ink);
  background-image: url('../img/photos/pages/contact-bg.webp');
  background-size: cover;
  background-position: center bottom;
  background-repeat: no-repeat;
}

/* A background cannot carry a srcset, so the picking is done by hand: the
   media query reads the CSS width, image-set() reads the pixel density, and
   between them a phone gets 52KB where it used to get 1.3MB. The plain
   url() above stays as the fallback — anything that cannot parse image-set()
   ignores these rules and keeps the full-size file.
   Each pair is sized so the 2x candidate covers twice its breakpoint. */
@media (max-width: 900px) {
  .contact-panel {
    background-image: image-set(
      url('../img/photos/pages/contact-bg-1024.webp') 1x,
      url('../img/photos/pages/contact-bg.webp') 2x);
  }
}
@media (max-width: 600px) {
  .contact-panel {
    background-image: image-set(
      url('../img/photos/pages/contact-bg-640.webp') 1x,
      url('../img/photos/pages/contact-bg-1024.webp') 2x);
  }
}

/* Supplied as a fully opaque colour wash, so the transparency is applied
   here — at 100% it would hide the photograph entirely. It covers the top
   band only, and is masked out at its foot so it never ends on a hard
   horizontal line across the picture. */
.contact-panel__scrim {
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--contact-overlay-height);
  z-index: 1;
  pointer-events: none;

  background-image: url('../img/photos/pages/contact-overlay.webp');
  background-size: cover;
  background-position: center top;
  background-repeat: no-repeat;
  opacity: var(--contact-overlay-opacity);

  -webkit-mask-image: linear-gradient(180deg, #000 62%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 62%, transparent 100%);
}

/* Weight across the whole content band. This frame is bright open sky
   exactly where the title, sygnet and agent details sit — without this the
   white type has nothing to hold against. */
.contact-panel::after {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--contact-overlay-height);
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(180deg,
                rgba(9, 18, 24, 0.55) 0%,
                rgba(9, 18, 24, 0.38) 45%,
                rgba(9, 18, 24, 0)   100%);
}

.contact-panel__inner { position: relative; z-index: 2; width: 100%; }

/* Title left, sygnet right, sharing a centre line */
.contact-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  padding-bottom: var(--space-xl);
  border-bottom: var(--border-thick) solid var(--sbp-blue-300);
}

.contact-panel__title {
  font-size: var(--fs-display-l);      /* 100px at 1440 */
  line-height: 1;
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
}

.contact-panel__mark {
  height: clamp(40px, 4.7vw, 68px);
  width: auto;
  flex: 0 0 auto;
}

.contact-panel__body {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-xl);
  flex-wrap: wrap;
  padding-top: var(--space-lg);
}

.contact-panel__copy {
  font-size: var(--fs-h3);             /* 22 / 32 */
  line-height: var(--lh-h3);
  font-weight: var(--fw-light);
  max-width: 46ch;
}

/* Everything here sits on photography, not a flat surface. The muted blue
   the footer uses for emails all but disappears against bright sky, so on
   this panel the type stays white and carries a shadow to hold an edge
   wherever the picture happens to be light. */
.contact-panel .agent__email {
  color: var(--sbp-white);
  opacity: 0.92;
}
.contact-panel .agent__email:hover { color: var(--color-accent); opacity: 1; }

.contact-panel__copy,
.contact-panel .agent__name,
.contact-panel .agent__email { text-shadow: 0 1px 6px rgba(9, 18, 24, 0.55); }

@media (max-width: 900px) {
  .contact-panel {
    aspect-ratio: auto;
    min-height: 88svh;
    padding-block: calc(var(--header-height) + var(--space-2xl)) var(--space-2xl);
  }
  .contact-panel__mark { height: 40px; }
}


/* ==========================================================================
   18  SITE FOOTER
   ========================================================================== */

.site-footer {
  background: var(--grad-footer);
  color: var(--sbp-white);
  padding-block: var(--space-xl);
}

/* Footer buttons sit on a flat dark surface rather than photography, so
   they take the light glass fill. The hero pair keeps the dark one, which
   is what holds up over a bright sky. */
.site-footer .btn { background-color: var(--color-btn-surface-light); }
.site-footer .btn:hover { background-color: var(--color-btn-surface-hover); }
.site-footer .btn--accent:hover { background-color: var(--color-accent); }

.site-footer__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  flex-wrap: wrap;
  padding-bottom: var(--space-xl);
}

.site-footer__main {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-lg);
  flex-wrap: wrap;
  padding-bottom: var(--space-lg);
}

.site-footer__logo { height: 46px; width: auto; }

/* Runs the full width of the container — no measure cap on the small print */
.site-footer__legal {
  font-size: var(--fs-legal);
  line-height: var(--lh-legal);
  color: var(--color-text-subtle);
  text-align: justify;
}


/* ==========================================================================
   19  COOKIE CONSENT BAR
   --------------------------------------------------------------------------
   Fixed to the bottom edge, full width, dark and blurred so it reads over
   any section. The buttons are the hero pair: Reject is the default
   dark-glass pill, Accept the brighter blue one.

   Rendered by theme-partials/cookie-bar.php only when no choice is stored;
   app.js slides it in, writes the cookie and removes it on either button.
   ========================================================================== */

.cookie-bar {
  position: fixed;
  inset: auto 0 0 0;
  z-index: var(--z-modal);

  padding-block: calc(var(--space-2xs) + 5px);   /* 13px */
  background-color: rgba(9, 18, 24, 0.72);
  -webkit-backdrop-filter: var(--blur-panel);
          backdrop-filter: var(--blur-panel);
  border-top: var(--border-thin) solid var(--color-border-subtle);
  color: var(--sbp-white);

  /* Revealed by CSS, not JS — a consent bar must never be able to hide
     itself because a script failed. JS only takes it away again. */
  animation: sbp-cookie-in var(--dur-slow) var(--ease-out) 400ms both;
  transition: transform var(--dur-slow) var(--ease-out);
}

.cookie-bar.is-dismissed {
  animation: none;
  transform: translateY(110%);
}

@keyframes sbp-cookie-in {
  from { transform: translateY(110%); }
  to   { transform: none; }
}

.cookie-bar__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2xs) var(--space-md);
  flex-wrap: wrap;
}

/* Held to roughly half the copy's natural length so it sets as two lines,
   with text-wrap:balance evening them up rather than leaving a short widow. */
.cookie-bar__text {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 66ch;
  text-wrap: balance;
  font-size: var(--fs-caption);
  line-height: 1.45;
  color: var(--color-text-muted);
}
.cookie-bar__text a { color: var(--sbp-white); }

.cookie-bar__actions {
  display: flex;
  gap: var(--space-2xs);
  flex: 0 0 auto;
}

@media (max-width: 600px) {
  .cookie-bar__actions { width: 100%; }
  .cookie-bar__actions .btn { flex: 1 1 0; }
}


/* ==========================================================================
   20  BROCHURE BANNER
   --------------------------------------------------------------------------
   Framework component (see docs/BROCHURE-SYSTEM.md), re-skinned onto the
   Suttons palette.
   ========================================================================== */

.brochure-banner {
  position: relative;
  z-index: var(--z-sticky);
  padding-block: var(--space-xs);
  background: var(--grad-section-alt);
  color: var(--sbp-white);
  box-shadow: var(--shadow-sm);
}

.brochure-banner-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.brochure-banner-label {
  font-size: var(--fs-caption);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
}

.brochure-links {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}

.brochure-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  padding: var(--space-3xs) var(--space-sm);
  font-size: var(--fs-body-s);
  color: var(--sbp-white);
  border: var(--border-thin) solid var(--color-border-subtle);
  border-radius: var(--radius-pill);
  transition: border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.brochure-link:hover { color: var(--color-accent); border-color: var(--color-accent); }
.brochure-link svg { flex-shrink: 0; }

@media (max-width: 768px) {
  .brochure-banner-inner { flex-direction: column; text-align: center; }
}


/* ==========================================================================
   21  TOUCH TARGETS
   --------------------------------------------------------------------------
   The pills, tabs and email links are drawn at the sizes the comp specifies
   — a 38px pill, a 34px tab, an 19px line of text. Those are comfortable
   with a mouse and small under a thumb.

   So the artwork is left alone and the *hit area* is grown instead: a
   transparent ::after inside each control, stretched to 44px, the figure
   Apple's HIG and WCAG 2.5.5 both settle on. Nothing moves, nothing
   reflows, and none of it applies on a mouse (`pointer: coarse` only).

   Vertical growth only, except where a horizontal figure can be derived
   from the gap — two overlays must never overlap, or the upper one starts
   swallowing taps meant for its neighbour.
   ========================================================================== */

@media (pointer: coarse) {

  .btn,
  .brochure-link,
  .map__tab,
  .site-brand,
  .agent__email,
  .nav-primary .nav-menu > li > a { position: relative; }

  .btn::after,
  .brochure-link::after,
  .map__tab::after,
  .site-brand::after,
  .agent__email::after,
  .nav-primary .nav-menu > li > a::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    /* the pills already animate `transform` on :hover/:active, but that is
       the button's own transform — this one belongs to the pseudo-element
       and the two never meet */
    transform: translateY(-50%);
    height: 100%;
    min-height: 44px;
  }

  /* The tabs sit in a flex row with --space-2xs between them. Reaching half
     that distance each side widens 34px to 42px and leaves the two overlays
     meeting exactly, never overlapping. */
  .map__tab::after {
    left: calc(var(--space-2xs) / -2);
    right: calc(var(--space-2xs) / -2);
  }
}

/* .sound-toggle is deliberately absent: it is 44px already, and its ::after
   is the tooltip bubble (.has-tip). A second one here would replace it. */
