/* ============================================================================
   RIDDLE & COGHILL — PAGE-SPECIFIC LAYOUTS
   ========================================================================== */

/* ---------------------------------------------------------- HOME HERO */
.hero {
  position: relative;
  min-height: min(92vh, 880px);
  display: flex;
  /* Top-anchored: the headline and buttons sit as a block near the top of
     the frame, clear of the kitchen island/pendant that occupies the middle
     and lower thirds of every hero crop. */
  align-items: flex-start;
  overflow: hidden;
  /* The palette's DARK base, not --c-inverse-bg. "Inverse" flips meaning
     between modes, so in dark mode that resolved to the light paper and the
     hero flashed white before the photograph painted. Same trap as the scrim
     tint above — this is what sits behind a full-bleed image. */
  background: var(--p-d-bg, var(--c-inverse-bg));
}
.hero__media { position: absolute; inset: 0; }
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  z-index: 0;
  /* The film is 16:9 but the hero is not, so `cover` always crops one axis.
     The colour underneath matches the film's darkest frame, so the moment
     before the first frame paints is a deepening rather than a white flash. */
  background: #10192c;
}
/* Fallback content for a browser with no <video>: the same still, laid out the
   same way. It is a child of the video element, so it needs its own cover. */
.hero__video-fallback {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Two separate jobs, deliberately split so neither is doing the other's work:

   1. SHADE — neutral black, holds the white headline at readable contrast.
      Neutral because black reads as shadow; a coloured darkener reads as a
      film laid over the photograph and dulls the materials underneath.
   2. TINT — a whisper of the palette's dark colour (navy here, black in
      gold-black) so the fold still belongs to the scheme.

   Dial either with the two variables below. `--hero-tint: 0` removes the
   coloured film completely and leaves only the neutral shade. */
.hero {
  --hero-shade: 0.30;
  --hero-tint: 0.10;
  /* The coloured film over the photograph is the palette's DARK base, not
     --c-inverse-bg. "Inverse" flips meaning between modes: in dark mode it
     resolves to the light paper, which washed the hero white instead of navy.
     --p-d-bg is navy in navy-gold, black in gold-black, charcoal in
     charcoal-gold, in both light and dark mode. */
  --hero-tint-color: var(--p-d-bg, var(--c-inverse-bg));
}
/* Weighted to the TOP, falling away before the middle of the frame so the
   island and pendant in every crop stay clean — matches where the headline
   now actually sits. (Previously two competing scrims existed here: a
   centre-weighted radial version for a centred headline, silently overridden
   by a top-weighted one added later for a top-centred redesign. Consolidated
   to the single version that matches the current top-left layout.) */
.hero__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom,
      rgba(0, 0, 0, calc(var(--hero-shade) * 1.85)) 0%,
      rgba(0, 0, 0, calc(var(--hero-shade) * 1.30)) 30%,
      rgba(0, 0, 0, calc(var(--hero-shade) * 1.00)) 58%,
      rgba(0, 0, 0, calc(var(--hero-shade) * 0.35)) 78%,
      rgba(0, 0, 0, 0) 100%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--hero-tint-color) calc(var(--hero-tint) * 120%), transparent) 0%,
      color-mix(in srgb, var(--hero-tint-color) calc(var(--hero-tint) * 45%), transparent) 100%);
}
/* On a phone the headline runs closer to the middle of the frame, so the
   gradient has less room to fall away before it reaches the type. Lift the
   overall shade rather than adding another layer. */
@media (max-width: 700px) {
  .hero { --hero-shade: 0.46; }
}
/* The top-left block: headline then buttons, stacked in normal flow and
   left-aligned to each other and to the logo above. Both children are real
   content now (not absolutely positioned), so chrome.js's entrance stagger
   (which walks .hero__inner's direct children) picks up each one on its own
   without any extra selector work. */
.hero__inner {
  position: relative; z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  gap: clamp(1.5rem, 3.5vw, 2.25rem);
  padding-block: clamp(6rem, 13vw, 9.5rem) clamp(3rem, 7vw, 5rem);
}
.hero h1 {
  color: #fff;
  font-size: clamp(1.4rem, 2.4vw, 2rem);
  max-width: 22ch;
  margin: 0;
  text-shadow: 0 1px 30px rgba(10, 15, 30, 0.28);
}
/* Same row/gap treatment as before, just no longer needing its own
   positioning or centring math — it is simply the next item in
   .hero__inner's flex column, indented to nothing so its left edge lines up
   with the headline's. */
.hero__actions {
  display: flex; gap: clamp(1.5rem, 4vw, 3rem);
  flex-wrap: wrap; justify-content: flex-start;
}
.hero__scroll {
  position: absolute; right: var(--gutter); bottom: clamp(3rem, 8vw, 7rem); z-index: 2;
  writing-mode: vertical-rl; font-size: 0.62rem; letter-spacing: 0.24em; text-transform: uppercase;
  color: rgba(255,255,255,0.8);
}
@media (max-width: 700px) { .hero__scroll { display: none; } }

/* ---------------------------------------------------- SPLIT / EDITORIAL */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: center;
}
.split--wide-media { grid-template-columns: 1.15fr 0.85fr; }
@media (max-width: 860px) { .split, .split--wide-media { grid-template-columns: 1fr; } }
.split__media .placeholder { min-height: 340px; height: 100%; }
/* The 340px floor exists to give the media presence in the two-column layout.
   Once stacked it stops helping and starts hurting: combined with aspect-ratio
   it drives WIDTH, not just height, so a 4:3 crop demands 340 × 4/3 = 453px and
   overflows the viewport. Below the breakpoint let the ratio govern. */
@media (max-width: 860px) { .split__media .placeholder { min-height: 0; } }
/* Alternating rows. The column widths swap along with the order, so the media
   stays the wider of the two either way round; below the breakpoint both
   collapse to one column and the media leads on source order. */
@media (min-width: 861px) {
  .split--reverse.split--wide-media { grid-template-columns: 0.85fr 1.15fr; }
  .split--reverse .split__media { order: 2; }
}

/* ----------------------------------------------------- RANGE LIST */
/* Name and descriptor on one baseline, hairline-separated.

   Two bugs, same root cause, fixed in two passes:

   1. flex-wrap used to sit on the ROW: the moment label + descriptor were
      too wide for the column, the whole descriptor dropped to its own line
      below the label. "Caledonia Kitchens" is the longest of the three
      names, so it was the one row that visibly broke.
   2. Moving to flex-basis on the descriptor (flex: 1 1 160px) fixed #1 but
      created a second bug: "Caledonia Kitchens" (18 characters) still ate
      far more of the row's fixed total width than "Schüller" or "next125"
      (flex: 0 0 auto has no cap), leaving its OWN descriptor's flex-basis
      squeezed well below 160px — so "Hand-built in the north of Scotland
      since 1986" wrapped anyway, orphaning "1986" alone on its own line.

   Grid fixes both at once: each row sizes its own label column to its own
   content (no shared cap to fight over), and the descriptor column always
   gets 1fr of whatever remains — the full width, not a fixed guess. */
.range-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--c-line); }
.range-list li {
  display: grid;
  grid-template-columns: max-content 1fr;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--c-line);
}
/* Body font, not the display serif: Cormorant sets oldstyle figures, so
   "next125" came out with the numerals dropped below the baseline. */
.range-list strong { font-size: 1rem; font-weight: 600; letter-spacing: 0.01em; color: var(--c-heading); }
/* text-wrap: balance is a safety net, not the fix: the split above was
   widened so none of the three rows need to wrap at typical desktop widths
   at all. In the narrower band where the column still runs out of room
   (roughly 900-1100px), this stops whatever DOES wrap from orphaning a
   single short word on its own line, breaking evenly across two instead.
   Unsupported browsers just get normal wrapping, same as before. */
.range-list span { color: var(--c-text-muted); font-size: var(--fs-small); min-width: 0; text-wrap: balance; }

/* ------------------------------------------------------- BRAND ROWS */
/* Logos are flattened to a single ink silhouette for the same reason as the
   homepage partner wall — four unrelated brand colours otherwise fight the
   palette, and the near-black marks vanish on a dark ground. Remove the
   filter pair to show the artwork in full colour instead. */
.brand__logo {
  display: block;
  width: auto;
  max-width: min(100%, 220px);
  max-height: clamp(26px, 3vw, 36px);
  object-fit: contain;
  filter: grayscale(1) brightness(0);
}
[data-mode="dark"] .brand__logo { filter: grayscale(1) brightness(0) invert(1); }
.brand__logo--sm { max-height: clamp(20px, 2.4vw, 26px); max-width: min(100%, 150px); }
/* A stacked lockup (Caledonia is 2.6:1, the wordmarks are 4.3:1+) has to be
   sized by its own taller cap height or it renders at roughly half the width
   of the marks beside it. Same correction as .logo-wall__item--stacked.

   Matching the BOUNDING BOX width wasn't enough on its own: a two-line mark
   reads as lighter/smaller than a single bold wordmark even at the same
   width, because each line's individual letters are still shorter. Sized up
   further, and given its own (wider) max-width, so it actually reads as at
   least as prominent as Schüller and next125 beside it, not just numerically
   as wide. */
.brand__logo--stacked { max-height: clamp(64px, 7vw, 90px); max-width: min(100%, 280px); }
.brand-tile .brand__logo { margin-bottom: var(--space-5); }
.brand-tile .placeholder { margin-bottom: var(--space-5); }

.overline-num {
  font-family: var(--font-display);
  font-size: 0.9rem; color: var(--c-accent); letter-spacing: 0.1em;
}

/* Intro two-column */
.intro-grid { display: grid; grid-template-columns: 0.9fr 1.1fr; gap: clamp(2rem, 6vw, 5rem); align-items: start; }
@media (max-width: 860px) { .intro-grid { grid-template-columns: 1fr; } }

/* Featured projects — asymmetric first item */
.featured-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(1.25rem, 3vw, 2rem); }
@media (max-width: 900px) { .featured-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 600px) { .featured-grid { grid-template-columns: 1fr; } }

/* Section heading row with link on the right */
.section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-5); margin-bottom: var(--space-7); flex-wrap: wrap; }
.section-head .measure { margin: 0; }

/* Process steps */
.process { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(1.5rem, 3vw, 2.5rem); counter-reset: step; }
@media (max-width: 900px) { .process { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .process { grid-template-columns: 1fr; } }
.process__step { padding-top: var(--space-5); border-top: 1px solid var(--c-line); }
.section--inverse .process__step { border-top-color: var(--c-inverse-line); }
.process__step .n { font-family: var(--font-display); font-size: 1.6rem; color: var(--c-accent); display: block; margin-bottom: var(--space-3); }

/* Services list */
.service-row {
  display: grid; grid-template-columns: 0.4fr 1.2fr 1fr; gap: clamp(1.5rem, 4vw, 3.5rem);
  padding-block: var(--space-7);
  border-top: 1px solid var(--c-line);
  align-items: start;
}
@media (max-width: 860px) { .service-row { grid-template-columns: 1fr; gap: var(--space-4); } }
.service-row__index { font-family: var(--font-display); font-size: 2.4rem; color: var(--c-accent); line-height: 1; }
.service-row ul { margin: var(--space-3) 0 0; padding-left: 1.1rem; color: var(--c-text-muted); }
.service-row ul li { margin-bottom: var(--space-2); }

/* Team grid */
.team-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(1.5rem, 3vw, 2rem); }
@media (max-width: 900px) { .team-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .team-grid { grid-template-columns: 1fr; } }
.team-card .placeholder { margin-bottom: var(--space-4); }
.team-card h3 { margin: 0; font-size: 1.15rem; }
.team-card .role { color: var(--c-accent); font-size: var(--fs-small); letter-spacing: 0.06em; text-transform: uppercase; margin-top: 2px; }
.team-card p { margin-top: var(--space-3); font-size: 0.95rem; color: var(--c-text-muted); }

/* Values list */
/* Flex, not grid: the appliance section has 5 tiles, which a 3-column grid
   always breaks 3-then-2, with the trailing pair stuck left under columns 1
   and 2 and an empty gap under column 3. Grid has no way to centre a
   partial row without restructuring the markup around it. Flex-wrap does
   this for free — justify-content: center applies PER LINE, so a full row
   of 3 sits flush and a trailing row of 2 (or any count) centres itself
   under it automatically, at three tiles or five. */
.values { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(1.5rem, 3vw, 2.5rem); }
.values > * { flex: 1 1 calc(33.333% - clamp(1.5rem, 3vw, 2.5rem) * 2 / 3); max-width: calc(33.333% - clamp(1.5rem, 3vw, 2.5rem) * 2 / 3); }
@media (max-width: 820px) { .values > * { flex-basis: 100%; max-width: 100%; } }

/* Portfolio masonry-ish grid */
.portfolio-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(1.5rem, 3vw, 2.5rem); }
@media (max-width: 640px) { .portfolio-grid { grid-template-columns: 1fr; } }

/* Contact layout */
.contact-layout { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: clamp(2rem, 6vw, 4.5rem); align-items: start; }
@media (max-width: 900px) { .contact-layout { grid-template-columns: 1fr; } }

/* Two showroom pins side by side rather than one big map — see the note
   in contact.html on why (no keyless way to plot two independent markers
   on a single embed). Stacks to one column once there isn't room for two
   readable tiles side by side. */
.map-slot {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3);
}
@media (max-width: 480px) {
  .map-slot { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------- MAP LINK
   The embedded map itself is decorative (pointer-events:none, tabindex:-1,
   aria-hidden) — the wrapping <a> is what's actually interactive, so a
   click anywhere on the tile opens that one showroom in Google Maps rather
   than the visitor getting stuck panning a tiny iframe. The label is a
   permanent caption bar, not a hover reveal — this is how the visitor tells
   the two tiles apart before choosing one, so it can't depend on hover. */
.map-link {
  position: relative; display: block; min-height: 170px;
  border-radius: var(--radius-md); overflow: hidden;
  border: 1px solid var(--c-line);
}
.map-link__frame {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
  pointer-events: none;
}
.map-link__label {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 0.5rem var(--space-3);
  font-size: var(--fs-small); color: #fff; text-align: center;
  background: linear-gradient(to top, rgba(0,0,0,0.75), rgba(0,0,0,0));
}

/* ------------------------------------------------------------- MAP GATE
   Shown instead of the maps until cookie consent is given (see chrome.js's
   initCookieConsent) — the .map-slot underneath stays display:none so
   nothing in it fetches until then, regardless of `loading="lazy"`. */
[data-cookie-gate] .map-slot { display: none; }
[data-cookie-gate].is-loaded .map-slot { display: grid; }
[data-cookie-gate].is-loaded .map-gate { display: none; }
.map-gate {
  display: flex; flex-direction: column; gap: var(--space-3);
  align-items: flex-start; padding: var(--space-5);
  border: 1px dashed var(--c-line); border-radius: var(--radius-md);
  background: var(--c-surface-alt);
}

/* FAQ / accordion */
.accordion { border-top: 1px solid var(--c-line); }
.accordion__item { border-bottom: 1px solid var(--c-line); }
.accordion__trigger {
  width: 100%; text-align: left; background: transparent; border: none;
  padding: var(--space-5) 0; display: flex; justify-content: space-between; align-items: center; gap: var(--space-4);
  font-family: var(--font-display); font-size: 1.25rem; color: var(--c-heading);
}
.accordion__icon { flex-shrink: 0; transition: transform var(--transition); color: var(--c-accent); font-size: 1.4rem; }
.accordion__trigger[aria-expanded="true"] .accordion__icon { transform: rotate(45deg); }
/* Open/close is pure CSS, keyed off the trigger's aria-expanded — the JS only
   flips that attribute. grid-template-rows 0fr -> 1fr animates to the content's
   natural height, so nothing has to be measured and nothing goes stale when the
   text reflows. The inner element needs min-height:0 or the row won't collapse,
   and its padding has to animate too: `overflow: hidden` clips the text but a
   padding-bottom still contributes its own height, leaving the panel ajar. */
.accordion__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--transition);
}
.accordion__trigger[aria-expanded="true"] + .accordion__panel { grid-template-rows: 1fr; }
.accordion__panel-inner {
  overflow: hidden;
  min-height: 0;
  padding-bottom: 0;
  color: var(--c-text-muted);
  max-width: 70ch;
  transition: padding-bottom var(--transition);
}
.accordion__trigger[aria-expanded="true"] + .accordion__panel .accordion__panel-inner {
  padding-bottom: var(--space-5);
}

/* Pill row */
.pill-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.pill { border: 1px solid var(--c-line); border-radius: 100px; padding: 0.4rem 0.9rem; font-size: var(--fs-small); color: var(--c-text-muted); }

/* ----------------------------------------------------- SERVICE TAGS
   A short list of what's covered, laid out as a fixed 2-column grid rather
   than flex-wrap: five uneven-length labels in a wrapping row never break
   into tidy lines on their own, so it read as a ragged row of pill buttons.
   A grid keeps every row aligned regardless of label length or viewport,
   and the accent dash (not a button-shaped chip) reads as a plain list —
   correct, since none of these are actually clickable. */
.service-tags {
  list-style: none; margin: var(--space-4) 0 var(--space-2); padding: 0;
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3) var(--space-5);
}
.service-tags li {
  position: relative; padding-left: 1.4rem;
  font-size: 0.95rem; color: var(--c-text);
}
.service-tags li::before {
  content: ""; position: absolute; left: 0; top: 0.65em;
  width: 0.85rem; height: 2px; background: var(--c-accent);
}
/* The fifth item is the odd one out in a 2-column grid — span both columns
   rather than leave it stranded alone under column one. */
.service-tags li:last-child:nth-child(odd) { grid-column: 1 / -1; }
@media (max-width: 480px) { .service-tags { grid-template-columns: 1fr; } }

/* ============================================================================
   EDITORIAL LAYER
   ----------------------------------------------------------------------------
   Three changes that break the page out of a single repeating rhythm. Every
   section before this was: centred container, eyebrow, h2, evenly-divided
   grid. Correct, but each section carried the same weight, so nothing led.
   ========================================================================== */

/* ---- 1. HERO ---------------------------------------------------------
   Consolidated into the main HOME HERO block above (top of this file) —
   this file previously carried two independent, silently-conflicting hero
   designs (a centred one and a later top-centred one layered over it via
   cascade order). There is now exactly one. */

/* ---- 2. FEATURED GRID: asymmetric ----------------------------------------
   Three equal cards give the eye nowhere to land. One dominant frame with two
   stacked beside it sets a clear reading order and looks composed rather than
   generated. Below 900px it falls back to the old even grid, where the
   asymmetry would just make one image arbitrarily bigger. */
@media (min-width: 901px) {
  .featured-grid--editorial { grid-template-columns: 1.32fr 1fr; align-items: start; }
  .featured-grid--editorial > :first-child { grid-row: 1 / span 2; }
  /* The two column heights are matched by choosing crops that add up, rather
     than by stretching the lead image to fill. Stretching it produced a frame
     so tall that the photograph cropped to mostly ceiling. A 3:4 portrait
     beside two 4:3 landscapes lands within ~20px of the same height, and every
     image keeps a crop a photographer would recognise. */
  .featured-grid--editorial > :first-child .placeholder { aspect-ratio: 3 / 4; }
  .featured-grid--editorial > :not(:first-child) .placeholder { aspect-ratio: 4 / 3; }
}

/* ---- 3. FULL-BLEED STATEMENT BAND ----------------------------------------
   Every image on the page until now sat inside the 1280px container. One
   edge-to-edge frame gives the scroll a moment of scale, and it is carrying a
   real review rather than an empty flourish. Built like the hero — media
   absolutely positioned behind a scrim — so it needs no 100vw negative-margin
   trick and cannot cause horizontal overflow. */
.band {
  position: relative;
  display: flex;
  align-items: center;
  min-height: min(78vh, 660px);
  overflow: hidden;
  background: var(--c-inverse-bg);
}
.band__media { position: absolute; inset: 0; }
.band__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Two passes only, and both kept light. Stacking a strong black gradient on
   top of a palette tint crushed the photograph to a flat near-black — the
   band lost the image entirely. The type sits in the left third, so the
   darkening is weighted there and the right side stays open. */
.band__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(to right, rgba(0,0,0,0.58) 0%, rgba(0,0,0,0.34) 42%, rgba(0,0,0,0.06) 78%, rgba(0,0,0,0) 100%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--c-inverse-bg) 8%, transparent) 0%,
      color-mix(in srgb, var(--c-inverse-bg) 14%, transparent) 100%);
}
/* Same problem as the hero at phone width: the type spans the full column, so
   a left-to-right falloff leaves the end of every line unprotected. Swap the
   horizontal pass for an even wash. */
@media (max-width: 700px) {
  .band__scrim {
    background:
      linear-gradient(180deg, rgba(0,0,0,0.42) 0%, rgba(0,0,0,0.52) 100%),
      linear-gradient(180deg,
        color-mix(in srgb, var(--c-inverse-bg) 10%, transparent) 0%,
        color-mix(in srgb, var(--c-inverse-bg) 18%, transparent) 100%);
  }
}
.band__inner { position: relative; z-index: 2; padding-block: clamp(4rem, 9vw, 7rem); }
.band blockquote {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.2vw, 2.75rem);
  line-height: 1.22;
  color: #fff;
  max-width: 26ch;
  text-wrap: balance;
  text-shadow: 0 1px 24px rgba(10, 15, 30, 0.45);
}
.band__attr { margin-top: var(--space-5); color: rgba(255,255,255,0.86); font-size: var(--fs-small); letter-spacing: 0.04em; }
.band__stars { color: var(--c-accent); letter-spacing: 0.14em; margin-bottom: var(--space-3); }
/* On the band the rating sits on a dark ground, so the muted base and heading
   colours the component normally inherits would disappear. */
.band .rating-summary { margin-top: var(--space-6); }
.band .rating-summary__score { color: #fff; }
.band .rating-summary__meta { color: rgba(255,255,255,0.72); }
.band .rating-stars__base { color: #fff; opacity: 0.28; }

/* --------------------------------------------------------- HERO SCROLL CUE
   A circle that widens on hover to reveal the word "Scroll", with a downward
   arrow held at the right edge.

   Because the anchor is centred with translateX(-50%), a percentage of its
   OWN width, the pill grows symmetrically from the middle as the width
   animates — it does not drift off-centre. That is why the width lives here
   and the hover state only changes one number. */
.scroll-dot {
  position: absolute;
  z-index: 3;                          /* above .hero__scrim */
  left: 50%;
  /* Was clamp(1.5rem, 3.5vw, 2.5rem) — sat close enough to the hero's own
     bottom edge that on some real window sizes the browser's actual visible
     height fell a little short of the 92vh the hero's min-height assumes,
     and the dot got clipped by the fold before anyone saw it move. More
     clearance here costs nothing the animation needs. */
  bottom: clamp(2.5rem, 5vw, 4rem);
  transform: translateX(-50%);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border-radius: 999px;
  overflow: hidden;
  text-decoration: none;

  /* The scrim is weighted to the TOP of the hero for the headline, so the foot
     of the frame is the least-darkened part — exactly where this sits. The
     wash, blur and shadow are what keep it readable over a bright crop. */
  background: rgba(0, 0, 0, 0.24);
  /* The ring is an INSET SHADOW rather than a border. A border sits between
     the padding box and the border box, and the arrow below is pinned with
     `right`, which resolves against the PADDING box — so a 1px border pushed
     the arrow 1px left of the circle's true centre. An inset shadow paints
     the same hairline without taking part in layout. */
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.38),
    0 2px 16px rgba(0, 0, 0, 0.32);
  color: #fff;

  transition:
    width 320ms cubic-bezier(0.22, 1, 0.36, 1),
    background-color var(--transition),
    box-shadow var(--transition),
    color var(--transition);
}

/* --p-d-accent, not --c-accent: the hero is a dark ground in BOTH modes, so
   the light-mode gold (deliberately darkened to pass contrast on paper) goes
   muddy here. Same reason .hero h1 is hardcoded white. */
.scroll-dot:hover,
.scroll-dot:focus-visible {
  width: 132px;
  background: var(--p-d-accent, var(--c-accent));
  color: var(--p-d-bg, #22262D);
  /* Ring tinted to the fill so the edge does not read as a separate outline
     once the pill is open. Outer shadow kept. */
  box-shadow:
    inset 0 0 0 1px var(--p-d-accent, var(--c-accent)),
    0 2px 16px rgba(0, 0, 0, 0.32);
}

.scroll-dot__label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  transform: translateX(0.5rem);
  transition: opacity 200ms ease, transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
}
.scroll-dot:hover .scroll-dot__label,
.scroll-dot:focus-visible .scroll-dot__label {
  opacity: 1;
  transform: translateX(-0.55rem);
}

/* Pinned to the right edge rather than laid out in flow, so the arrow holds
   still while the pill grows around it and the label slides in beside it. */
.scroll-dot__icon {
  position: absolute;
  right: 14px;
  display: grid;
  place-items: center;
}
.scroll-dot__icon svg { width: 20px; height: 20px; display: block; }

/* At rest the arrow nudges down and back — enough to read as an invitation
   without becoming a distraction on a page whose argument is restraint. The
   bob is on the icon, never the anchor: the anchor owns the centring
   translate and the two would overwrite each other. */
.scroll-dot__icon { animation: scroll-dot-bob 2.2s ease-in-out infinite; }
@keyframes scroll-dot-bob {
  0%, 100% { transform: translateY(-2px); }
  50%      { transform: translateY(2px); }
}
/* Once it is open the arrow settles — it has done its job. */
.scroll-dot:hover .scroll-dot__icon,
.scroll-dot:focus-visible .scroll-dot__icon { animation-play-state: paused; }

/* Touch has no hover, so the pill would never open. Show it in its expanded
   state instead, which also gives a comfortably sized tap target. */
@media (pointer: coarse) {
  .scroll-dot { width: 132px; }
  .scroll-dot__label { opacity: 1; transform: translateX(-0.55rem); }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-dot,
  .scroll-dot__label { transition: none; }
  .scroll-dot__icon { animation: none; transform: none; }
}

/* Short viewport: the hero copy runs close to the bottom edge and this would
   collide with the buttons. It is an affordance, not content — drop it. */
@media (max-height: 620px) {
  .scroll-dot { display: none; }
}
