/* ==========================================================================
   Student Horizons — studenthorizons.org

   PALETTE IS ABSOLUTE. Three values, no tints, no greys, no intermediates:
     Pure Black  #000000
     Pure White  #FFFFFF
     Horizon Red #B92A2A  — identical in both themes, never lightened

   Dark theme  = black ground, white text.  Light theme = white ground, black text.
   Horizon Red carries every rule, border, button outline, fill and emphasis word.
   Hierarchy is made with size and weight only, because no third text colour
   is permitted. Horizon Gold appears only inside the logo artwork itself.

   Note: #B92A2A on #000000 is 3.1:1, under the WCAG AA 4.5:1 floor for body
   text. Red is therefore kept to display sizes, rules and emphasis — never
   small reading copy — so no body text on the page falls below the floor.

   FILLS ARE INK, NOT RED. Every control that fills on hover or click fills
   with --ink: white in dark theme, black in light. The text on it takes
   --ink-inv, so it is always the exact inverse of the plate it sits on and
   always measures 21:1. Red keeps every border, rule, label and emphasis
   word, so the accent is undiminished — it simply never becomes a plate
   that text has to survive on. This replaced red fills, which forced black
   text onto Horizon Red at 3.4:1 in dark theme.
   ========================================================================== */

/* ── typefaces (self-hosted) ──────────────────────────────────────────── */

@font-face {
  font-family: "DM Sans"; font-style: normal; font-weight: 100 1000; font-display: swap;
  src: url("/assets/fonts/dm-sans-latin-var.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "DM Sans"; font-style: normal; font-weight: 100 1000; font-display: swap;
  src: url("/assets/fonts/dm-sans-latin-ext-var.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: "Libre Baskerville"; font-style: italic; font-weight: 700; font-display: swap;
  src: url("/assets/fonts/libre-baskerville-700-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: "Libre Baskerville"; font-style: italic; font-weight: 700; font-display: swap;
  src: url("/assets/fonts/libre-baskerville-700-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* ── tokens ───────────────────────────────────────────────────────────── */

:root {
  --paper: #000000;
  --ink:   #FFFFFF;
  --red:   #B92A2A;
  --gold:  #D49E2A;                   /* the sun in the theme toggle only */
  --ink-inv: #000000;                 /* ink flipped, for hover on filled controls */
  --soft:  rgba(185, 42, 42, 0.20);   /* the cursor ring's wash over any target */

  --head: 4.5rem;
  --shell: 1160px;
  --gut: clamp(1.25rem, 4.5vw, 3rem);
  --sec: clamp(2.5rem, 4.6vw, 3.75rem);

  /* One gap governs the whole masthead row — nav links, Join, theme button.
     Setting it here rather than per-component is what keeps the spacing even
     across the row instead of even only among the words. */
  --navgap: clamp(1.125rem, 2.1vw, 2rem);
  /* The footer call-to-action's width. On phones the combomark is locked to
     the same measure, so the mark and the button read as one centred stack. */
  --foot-cta: 15.5rem;

  /* Rule system. Every horizontal line on the page is one of three kinds and
     each kind is defined once:
       --rule       thickness, shared by all three
       --rule-col   the inset measure: section rules and title rules both run
                    the width of the content column, so every inset line on
                    the page shares the same two x-coordinates
     Full-bleed rules (masthead bottom, footer top) run edge to edge and are
     the only lines that do not use --rule-col. */
  --rule: 1px;
  --hair: 2px;                        /* hover underline, header and footer alike */
  /* Title rules: the content column — the same measure that bounds the text
     of every section, so a title rule marks exactly where its section's
     copy starts and stops. */
  --rule-col: min(100% - (var(--gut) * 2), var(--shell) - (var(--gut) * 2));
  /* Section rules: the midpoint between a title rule and a full-bleed rule.
     Derived, not chosen — it is literally the average of the other two, so
     the three lengths read as one series rather than three decisions.
     Both custom properties resolve 100% against the element carrying them,
     which is why each is only ever used on a full-width box. */
  --rule-sec: calc((var(--rule-col) + 100%) / 2);

  /* Press depth. Cards keep the 1.014 → 1.004 pair they already had; the
     smaller controls need a larger factor to read as the same physical
     press, because perceived depth is absolute, not proportional. */
  --press: 0.97;
  --press-sm: 0.93;

  /* ── Fill/ink timing ────────────────────────────────────────────────
     Text on a filling control has exactly two safe states: base colour while
     it sits on the page, inverse colour while it sits on the plate. The only
     dangerous instants are the ones where it is half on each — so the colour
     swap is timed to run DURING that straddle and nothing else.

     For an ink band spanning [a, b] of the control's height, the plate's top
     edge sits at H(1.01 - progress) coming in and H(progress) going out, so:

       entering   straddle = progress 1.01-b  ->  1.01-a
       leaving    straddle = progress a       ->  b

     Those are different windows for anything not vertically centred, which is
     why entering and leaving get separate values rather than one symmetric
     delay. Feeding each progress through the inverse of --swipe and
     multiplying by the fill duration gives the times below. Bands were
     measured, not estimated.

     --ct is when the swap starts, --cd how long it runs. Both are read from
     the state being transitioned TO, so the base rule governs leaving and the
     :hover rule governs entering. */
                                         it at ~420ms, so the swap runs 370-460 */
  --glow: 0 0 0 1px var(--red), 0 0 26px 2px rgba(185, 42, 42, 0.45);

  --sans: "DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --serif: "Libre Baskerville", Georgia, "Times New Roman", serif;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --swipe: cubic-bezier(0.65, 0, 0.35, 1);
}

[data-theme="light"] { --paper: #FFFFFF; --ink: #000000; --ink-inv: #FFFFFF; }

*, *::before, *::after { box-sizing: border-box; }

/* The masthead is --head TALL PLUS its bottom rule, so the scroll padding has
   to include the rule too or every anchor lands with the section's first pixels
   tucked behind it. */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth;
       scroll-padding-top: calc(var(--head) + var(--rule)); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0; background: var(--paper); color: var(--ink);
  font-family: var(--sans);
  font-size: clamp(0.9375rem, 0.9rem + 0.2vw, 1rem);
  line-height: 1.55; -webkit-font-smoothing: antialiased;
  transition: background-color 0.35s var(--ease), color 0.35s var(--ease);
}

img, svg { max-width: 100%; }
img { height: auto; }
a { color: var(--red); text-underline-offset: 0.18em; }

:focus-visible { outline: 2.5px solid var(--red); outline-offset: 3px; }

.sr-only, .skip:not(:focus) {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip:focus {
  position: fixed; z-index: 200; top: 0.75rem; left: 0.75rem;
  background: var(--ink); color: var(--ink-inv); padding: 0.7rem 1.1rem;
  font-weight: 500; text-decoration: none;
}

.shell { width: 100%; max-width: var(--shell); margin-inline: auto; padding-inline: var(--gut); }

/* theme-paired artwork */
[data-theme="dark"]  .on-light { display: none; }
[data-theme="light"] .on-dark  { display: none; }

/* ── type ─────────────────────────────────────────────────────────────── */

/* 1.06 is tight enough that a Libre Baskerville italic descender on one line
   can reach the ascender of the next — the 'y' of Every into the 'd' of guide
   once the phone layout puts them on separate lines. The display faces get
   enough leading to clear their own extremes; see the phone block for the
   value used where headings actually wrap. Measured: the deepest ink extent
   across both faces is ~1.09em, so anything under ~1.10 guarantees a collision
   and 1.12 leaves only a fraction of a pixel. 1.18 holds a real gap at every
   size the type reaches. */
h1, h2, h3 { font-weight: 500; letter-spacing: -0.025em; line-height: 1.18; margin: 0; }
h1 { font-size: clamp(1.85rem, 5.5vw, 5rem); letter-spacing: -0.038em; white-space: nowrap; }
h2 { font-size: clamp(1.7rem, 1.15rem + 2.3vw, 3rem); }
h3 { font-size: 1.0625rem; letter-spacing: -0.012em; line-height: 1.25; }

h1 em, h2 em, h3 em {
  font-family: var(--serif); font-style: italic; font-weight: 700;
  color: var(--red); letter-spacing: -0.005em;
}
p { margin: 0; }

.label {
  font-size: clamp(1rem, 0.9rem + 0.45vw, 1.25rem);
  font-weight: 500; color: var(--red);
  padding-bottom: 0.55rem; margin-bottom: clamp(0.875rem, 1.8vw, 1.375rem);
  border-bottom: var(--rule) solid var(--red);
}
.tagline {
  font-size: clamp(1rem, 0.9rem + 0.5vw, 1.3125rem);
  font-weight: 500; color: var(--red); margin-bottom: clamp(0.625rem, 1.4vw, 1rem);
}
.blurb   { font-size: clamp(1rem, 0.94rem + 0.34vw, 1.1875rem); line-height: 1.5; max-width: 46ch; }
/* 33rem, not a ch value: the column itself is ~564px, so any ch measure
   above ~43ch never binds and the paragraph kept ending on a 171px stub.
   At 33rem the last line runs 512px against a 526px longest line. */
.body-lg {
  font-size: clamp(1rem, 0.94rem + 0.34vw, 1.1875rem); line-height: 1.55;
  max-width: 32.75rem; text-wrap: pretty;
}
.body-sm { font-size: clamp(0.9375rem, 0.91rem + 0.2vw, 1.0313rem); max-width: 52ch; margin-top: 0.625rem; }
/* Sits between .body-sm and h2 in weight and size. Stays on --ink, so it is
   pure white on dark and pure black on light — prominence comes from size,
   never from a third colour. */
.lede {
  font-size: clamp(1.125rem, 1rem + 0.85vw, 1.5rem);
  font-weight: 500; letter-spacing: -0.018em; line-height: 1.3;
  color: var(--ink); max-width: 46ch; margin-top: clamp(0.75rem, 1.4vw, 1rem);
}

/* ── panels ───────────────────────────────────────────────────────────── */
/* Sized by their content. Free scrolling — no snapping, no forced heights. */

.panel { padding-block: var(--sec); position: relative; }
.panel + .panel::before {
  content: ""; position: absolute; top: 0;
  left: 50%; transform: translateX(-50%);
  width: var(--rule-sec); height: var(--rule); background: var(--red);
}

/* ── masthead ─────────────────────────────────────────────────────────── */

.masthead {
  position: sticky; top: 0; z-index: 100;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: var(--rule) solid var(--red);
  transition: background-color 0.35s var(--ease);
}
.masthead-in { display: flex; align-items: center; gap: var(--navgap); height: var(--head); }

/* Shrinkable on purpose: at the narrow end of the tablet band the row has
   ~17px of slack, and a font that renders wider than expected (fallback still
   in use, different platform metrics) would otherwise push the row into
   overflow. The mark gives way first; the nav never does. */
.brand { display: flex; align-items: center; flex: 0 1 auto; min-width: 0; }
.brand img { width: clamp(168px, 19vw, 268px); transition: transform 0.45s var(--ease); }
.brand:hover img { transform: translateY(-2px); }

.nav { display: flex; flex: 0 0 auto; gap: var(--navgap); margin-left: auto; }
.nav a {
  position: relative; color: var(--ink); text-decoration: none;
  font-size: 0.875rem; font-weight: 500; padding-block: 0.35rem; white-space: nowrap;
}
.nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: var(--hair);
  background: var(--red); transform: scaleX(0); transform-origin: left;
  transition: transform 0.3s var(--swipe);
}
.nav a:hover::after { transform: scaleX(1); }

/* These used to cross-fade their background rather than sweep it. A cross-fade
   has no clean instant to swap the icon at: plate and ink pass through the same
   mid-grey together and the icon disappears at the midpoint. Sweeping like
   every other control gives the icon a crossing to be timed against. */
.theme-btn, .menu-btn {
  position: relative; z-index: 0; overflow: hidden;
  flex: 0 0 auto; width: 2.375rem; height: 2.375rem; padding: 0.35rem;
  background: transparent; border: 1px solid var(--red); cursor: pointer;
  display: grid; place-items: center;
  transition: transform 0.35s var(--ease);
}
.theme-btn::before, .menu-btn::before {
  content: ""; position: absolute; inset: 0; z-index: -1; background: var(--ink);
  transform: translateY(101%); transition: transform 0.25s var(--swipe);
}
.theme-btn:hover::before, .menu-btn:hover::before { transform: translateY(0); }
.theme-btn:active, .menu-btn:active { transform: scale(var(--press-sm)); }
.theme-btn svg { width: 78%; overflow: visible; }
.tb-sun { stroke: var(--gold); transform-origin: 20px 20px; transition: transform 0.6s var(--swipe); }
.tb-line { stroke: var(--ink); --ct: 0.099s; --cd: 0.053s;
           transition: stroke var(--cd) var(--ease) var(--ct); }
/* gold is the sun and never changes; the horizon line flips to its opposite */
.theme-btn:hover .tb-line { stroke: var(--ink-inv); --ct: 0.100s; --cd: 0.052s; }

.menu-btn { display: none; }
.menu-bars, .menu-bars::before, .menu-bars::after {
  display: block; width: 16px; height: 1.5px; background: var(--ink);
  margin-inline: auto; content: "";
  /* the bars are a 1.5px band, so the straddle is almost instantaneous */
  --ct: 0.123s; --cd: 0.012s;
  transition: transform 0.28s var(--ease),
              background-color var(--cd) var(--ease) var(--ct);
}
.menu-btn:hover .menu-bars, .menu-btn:hover .menu-bars::before, .menu-btn:hover .menu-bars::after { background: var(--ink-inv); --ct: 0.124s; --cd: 0.012s; }
.menu-bars::before { transform: translateY(-5px); }
.menu-bars::after { transform: translateY(3.5px); }
[aria-expanded="true"] .menu-bars { background: transparent !important; }
[aria-expanded="true"] .menu-bars::before { transform: rotate(45deg) translateY(0); }
[aria-expanded="true"] .menu-bars::after { transform: rotate(-45deg) translateY(-1.5px); }

/* Collapsed with max-height rather than `hidden` so it can animate. The
   value only needs to exceed the drawer's real height; the easing curve
   hides the fact that it is not the exact height. */
.mobile-nav {
  display: none; flex-direction: column;
  border-top: 1px solid transparent;
  max-height: 0; overflow: hidden; opacity: 0; visibility: hidden;
  transition: max-height 0.38s var(--ease), opacity 0.24s var(--ease),
              border-top-color 0.38s var(--ease), visibility 0s linear 0.38s;
}
.mobile-nav.open {
  max-height: 24rem; opacity: 1; visibility: visible;
  border-top-color: var(--red);
  transition: max-height 0.38s var(--ease), opacity 0.28s var(--ease) 0.04s,
              border-top-color 0.2s var(--ease), visibility 0s;
}
.mobile-nav a {
  padding: 0.875rem var(--gut); color: var(--ink); text-decoration: none;
  font-weight: 500; border-bottom: 1px solid var(--red);
}
.mobile-nav a:last-child { color: var(--red); }

/* ── buttons ──────────────────────────────────────────────────────────── */

.btn {
  position: relative; z-index: 0; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--sans); font-size: 0.9375rem; font-weight: 500; line-height: 1;
  padding: 0.9375rem 1.5rem; text-align: center;
  color: var(--ink); background: transparent;
  border: 1.5px solid var(--red); border-radius: 0;
  text-decoration: none; cursor: pointer;
  /* union of all five .btn variants' straddles; the widest is .btn-foot,
     which wraps to two lines and therefore has the deepest band */
  --ct: 0.169s; --cd: 0.082s;
  transition: color var(--cd) var(--ease) var(--ct),
              transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.btn::before {
  content: ""; position: absolute; inset: 0; z-index: -1; background: var(--ink);
  transform: translateY(101%); transition: transform 0.42s var(--swipe);
}
.btn:hover, .btn:focus-visible { color: var(--ink-inv); --ct: 0.172s; --cd: 0.081s; }
.btn:hover::before, .btn:focus-visible::before { transform: translateY(0); }
.btn:active { transform: scale(var(--press)); }
.btn-lg { padding: 1.1875rem 2.25rem; font-size: 1.0625rem; }
.btn-wide { width: 100%; padding-block: 1.1875rem; font-size: 1rem; }
.btn-nav { padding: 0.625rem 1.25rem; font-size: 0.8438rem; flex: 0 0 auto; }
/* wraps to two lines by design — the extra leading keeps it readable */
.btn-foot { width: 100%; max-width: var(--foot-cta); padding: 1rem 1.25rem; font-size: 0.9375rem; line-height: 1.5; }

/* ── hero ─────────────────────────────────────────────────────────────── */

.hero-grid {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 3.5vw, 3rem); align-items: start;
  margin-top: clamp(1rem, 2.4vw, 1.75rem);
}
.hero-side { display: flex; flex-direction: column; gap: 0.75rem; }

/* ── social buttons ───────────────────────────────────────────────────── */

.socials {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.625rem;
}
.socials a {
  position: relative; z-index: 0; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.35rem;
  padding: 0.75rem 0.25rem;
  border: 1.5px solid var(--red); color: var(--ink); text-decoration: none;
  transition: transform 0.35s var(--ease);
}
.socials a:active { transform: scale(var(--press)); }
.socials a::before {
  content: ""; position: absolute; inset: 0; z-index: -1; background: var(--ink);
  transform: translateY(101%); transition: transform 0.4s var(--swipe);
}
/* Coloured on the children, not the tile, because they cross the plate at
   different moments. */
.socials a svg  { color: var(--ink); --ct: 0.145s; --cd: 0.052s;
                  transition: color var(--cd) var(--ease) var(--ct), transform 0.4s var(--ease); }
.socials a span { color: var(--ink); --ct: 0.208s; --cd: 0.047s;
                  transition: color var(--cd) var(--ease) var(--ct); }
.socials a:hover svg,  .socials a:focus-visible svg  { color: var(--ink-inv); --ct: 0.205s; --cd: 0.053s; }
.socials a:hover span, .socials a:focus-visible span { color: var(--ink-inv); --ct: 0.147s; --cd: 0.047s; }
.socials a:hover::before, .socials a:focus-visible::before { transform: translateY(0); }
.socials svg {
  width: 20px; height: 20px; fill: none; stroke: currentColor;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
  transition: transform 0.4s var(--ease);
}
.socials svg .fill { fill: currentColor; stroke: none; }
.socials a:hover svg { transform: translateY(-2px) scale(1.08); }
.socials span { font-size: 0.7188rem; font-weight: 500; }
.socials-wide { margin-top: clamp(1rem, 2.4vw, 1.75rem); }

/* ── splits ───────────────────────────────────────────────────────────── */

.split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: clamp(1.25rem, 3.5vw, 3rem); align-items: start; }
.split-even { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

/* ── click cards ──────────────────────────────────────────────────────── */

.duo { margin-top: clamp(1.125rem, 2.4vw, 1.75rem); max-width: 44rem; }
.flip {
  position: relative; overflow: hidden; cursor: pointer;
  border: 1.5px solid var(--red); min-height: 13.5rem;
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
/* hover only bulges and glows — the fill is bound to click */
.flip:hover, .flip:focus-visible {
  transform: scale(1.014);
  box-shadow: var(--glow);
}
.flip:active { transform: scale(1.004); }
.face {
  padding: clamp(1.25rem, 2.6vw, 2rem);
  display: flex; flex-direction: column; justify-content: center; gap: 0.5rem;
  min-height: 13.5rem;
}
.face h3 { font-size: clamp(1.1875rem, 1rem + 0.8vw, 1.625rem); }
.face p { font-size: clamp(0.9375rem, 0.91rem + 0.22vw, 1.0313rem); line-height: 1.5; }
.face-b {
  position: absolute; inset: 0; background: var(--ink); color: var(--ink-inv);
  transform: translateY(100%); transition: transform 0.55s var(--swipe);
}
.flip.flipped .face-b { transform: translateY(0); }
.flip-hint {
  position: absolute; right: 0.875rem; top: 0.7rem; z-index: 2;
  font-size: 0.6875rem; font-weight: 500; letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--red);
}
/* Horizon Red in every state. It reads on the page ground and on either ink
   plate, so it never needs to swap — which also removes the lag that made it
   the one element trailing behind a theme change. */

/* ── carousel ─────────────────────────────────────────────────────────── */

.carousel { margin-top: clamp(1.125rem, 2.4vw, 1.75rem); }
.car-view {
  overflow: hidden; border: 1.5px solid var(--red); cursor: pointer;
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.car-view:hover {
  transform: scale(1.014);
  box-shadow: var(--glow);
}
.car-view:active { transform: scale(1.004); }
.car-track {
  list-style: none; margin: 0; padding: 0; display: flex;
  transition: transform 0.55s var(--swipe);
}
.car-card {
  flex: 0 0 100%; min-width: 100%;
  padding: clamp(1.375rem, 3vw, 2.25rem);
  display: grid; grid-template-columns: 4.5rem 1fr;
  grid-template-rows: auto auto; align-content: center;
  column-gap: 1rem; row-gap: 0.35rem; min-height: 11.5rem;
}
.car-n {
  grid-row: 1 / span 2; align-self: center;
  font-family: var(--serif); font-style: italic; font-weight: 700;
  font-size: clamp(2.5rem, 2rem + 2.2vw, 4rem); line-height: 0.9; color: var(--red);
}
.car-card h3 { font-size: clamp(1.125rem, 1rem + 0.6vw, 1.5rem); align-self: end; }
.car-track { will-change: transform; }
.car-card p { font-size: clamp(0.9375rem, 0.91rem + 0.22vw, 1.0313rem); line-height: 1.5; max-width: 58ch; }

/* Both rows wrap. The dots and arrows are sized in rem, so at a raised
   browser default font they outgrow a phone column even after the small-screen
   dot rule; wrapping is the safety valve that keeps them inside it. */
.car-ui { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem 1rem; margin-top: 0.875rem; }
.car-dots { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.car-dot {
  width: 2.25rem; height: 3px; padding: 0; border: 0; cursor: pointer;
  background: var(--red); opacity: 0.3;
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.car-dot:hover { opacity: 0.65; }
.car-dot.on { opacity: 1; transform: scaleY(1.8); }
.car-dot:active { transform: scaleX(var(--press-sm)); }
.car-dot.on:active { transform: scaleY(1.8) scaleX(var(--press-sm)); }
.car-arrows { display: flex; gap: 0.5rem; }
.car-nav {
  position: relative; z-index: 0; overflow: hidden;
  width: 2.5rem; height: 2.5rem; padding: 0.5rem;
  background: transparent; border: 1.5px solid var(--red); cursor: pointer;
  color: var(--ink); --ct: 0.135s; --cd: 0.082s;
  transition: color var(--cd) var(--ease) var(--ct), transform 0.35s var(--ease);
}
.car-nav:active { transform: scale(var(--press-sm)); }
.car-nav::before {
  content: ""; position: absolute; inset: 0; z-index: -1; background: var(--ink);
  transform: translateY(101%); transition: transform 0.35s var(--swipe);
}
.car-nav:hover { color: var(--ink-inv); --ct: 0.137s; --cd: 0.080s; }
.car-nav:hover::before { transform: translateY(0); }
.car-nav svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* Breaks that belong to one layout only. .d-br is the desktop/tablet break,
   .m-br the phone one; each is inert in the other band. Both carry an authored
   space beside them in the markup, so switching one off never joins two words. */
.m-br { display: none; }
.d-br { display: inline; }

/* Paragraphs broken deliberately with <br>. The measure is capped per
   paragraph so the wrapped line can never outrun the forced first line.
   All three selectors are single-class on purpose — keep them flat, or a
   descendant rule like `.face .two` will out-specify the modifiers.
   The breaks are switched off on narrow screens — see the 700px block. */
.two { max-width: 52ch; }
.two-42 { max-width: 42ch; }

/* ── cta ──────────────────────────────────────────────────────────────── */

.cta-in { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.25rem 2rem; }
.cta h2 { max-width: 20ch; }

/* ── form ─────────────────────────────────────────────────────────────── */

.hp { position: absolute; left: -9999px; }
.form-zone { position: relative; min-height: 22rem; display: grid; }
.form-zone > * { grid-area: 1 / 1; }
.form-zone .form-load, .form-zone .form-done { opacity: 0; pointer-events: none; }
.form-zone .form { opacity: 1; }
.form-zone[data-state="loading"] .form,
.form-zone[data-state="done"] .form { opacity: 0; pointer-events: none; }
.form-zone[data-state="loading"] .form-load { opacity: 1; }
.form-zone[data-state="done"] .form-done { opacity: 1; pointer-events: auto; }
.form-zone > * { transition: opacity 0.35s var(--ease); }

.field { margin-bottom: 0.8125rem; }
.field label {
  display: block; font-size: 0.75rem; font-weight: 500; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--red); margin-bottom: 0.3rem;
}
/* Three states, deliberately kept apart:
     hover  — the card's bulge and red radiance, nothing more
     active — the same press every other control gets
     focus  — the inward border, and only the inward border. A field is the
              one thing you stay inside rather than pass over, so the focus
              signal has to be distinguishable from the hover signal. Focus
              is declared last so it wins while the pointer is also over it. */
.field input, .field select, .field textarea {
  width: 100%; font-family: var(--sans); font-size: 0.9375rem; color: var(--ink);
  background: transparent; border: 1.5px solid var(--red); border-radius: 0;
  padding: 0.5625rem 0.75rem;
  transition: box-shadow 0.25s var(--ease), transform 0.35s var(--ease);
}
.field select option { background: var(--paper); color: var(--ink); }
.field textarea { resize: vertical; min-height: 4.75rem; }

.field input:hover, .field select:hover, .field textarea:hover {
  transform: scale(1.014); box-shadow: var(--glow);
}
.field input:active, .field select:active, .field textarea:active {
  transform: scale(var(--press));
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; transform: none; box-shadow: inset 0 0 0 2px var(--red);
}
.form-error { margin-top: 0.75rem; font-size: 0.875rem; color: var(--red); }

.form-load { display: grid; place-items: center; }
.spinner { width: 3rem; height: 3rem; animation: spin 1s linear infinite; }
.spinner circle {
  stroke: var(--red); stroke-linecap: round;
  stroke-dasharray: 90 126; transform-origin: center;
}
@keyframes spin { to { transform: rotate(360deg); } }

.form-done { display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 0.75rem; }
.form-done h3 { font-size: clamp(1.375rem, 1.15rem + 1vw, 2rem); color: var(--red); }
/* Held to a single line on purpose. The measure cap is removed and the
   size steps down with the viewport so the sentence never breaks; below
   820px the column is too narrow for that and it is allowed to wrap. */
.form-done p { font-size: clamp(0.8125rem, 0.52rem + 0.62vw, 1.0313rem); max-width: none; white-space: nowrap; }
.form-done .btn { margin-top: 0.5rem; }

/* ── footer ───────────────────────────────────────────────────────────── */

.foot { border-top: var(--rule) solid var(--red); padding-top: clamp(2rem, 4vw, 3rem); }
.foot-in { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr); gap: clamp(1.75rem, 4vw, 3.5rem); align-items: start; }
.foot-brand { display: block; }
.foot-brand img { width: clamp(210px, 26vw, 340px); display: block; transition: transform 0.5s var(--ease); }
.foot-brand:hover img { transform: translateY(-4px); }
/* content-width columns with space-between, so the gaps read even
   regardless of how long the longest link in a column happens to be */
.foot-cols { display: flex; justify-content: space-between; gap: 2rem; flex-wrap: wrap; }
.foot-cols h2 {
  font-size: 0.75rem; font-weight: 500; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--red); margin-bottom: 0.875rem;
}
.foot-cols ul { list-style: none; margin: 0; padding: 0; }
.foot-cols li + li { margin-top: 0.4375rem; }
.foot-cols ul a {
  position: relative; color: var(--ink); text-decoration: none; font-size: 0.9063rem;
}
.foot-cols ul a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: var(--hair);
  background: var(--red); transform: scaleX(0); transform-origin: left;
  transition: transform 0.3s var(--swipe);
}
.foot-cols ul a:hover::after { transform: scaleX(1); }

/* The rule above the legal line is the seventh section rule. It is drawn on a
   full-width band rather than bordered on the shell, because --rule-sec is
   wider than the shell and a border would be trapped at the shell's edge. */
.foot-legal-band { position: relative; margin-top: clamp(1.75rem, 3.5vw, 2.5rem); }
.foot-legal-band::before {
  content: ""; position: absolute; top: 0;
  left: 50%; transform: translateX(-50%);
  width: var(--rule-sec); height: var(--rule); background: var(--red);
}
.foot-legal {
  padding-block: 1.125rem;
  display: flex; flex-wrap: wrap; gap: 0.625rem 2rem; justify-content: space-between;
}
.foot-legal p { font-size: 0.75rem; max-width: 70ch; }

/* ── custom cursor ────────────────────────────────────────────────────── */
/* Horizon Red in every state, over every surface. This used to flip to black
   over red-filled controls, because red-on-red vanished. Fills are ink now, so
   the cursor only ever sits on the page ground (3.4:1) or on an ink plate
   (6.2:1) and is legible on both — which collapses the old fill/soft split
   into one interactive state. Fields keep the ring instead of an I-beam: the
   expanded ring is the "you can type here" signal. */

.cursor-dot, .cursor-ring {
  position: fixed; top: 0; left: 0; z-index: 300; pointer-events: none; opacity: 0;
  will-change: transform;
}
.cursor-dot {
  width: 6px; height: 6px; margin: -3px 0 0 -3px; border-radius: 50%;
  background: var(--red);
}
.cursor-ring {
  width: 30px; height: 30px; margin: -15px 0 0 -15px;
  border: 1.5px solid var(--red); border-radius: 50%; background: transparent;
  transition: width 0.22s var(--ease), height 0.22s var(--ease), margin 0.22s var(--ease),
              opacity 0.2s var(--ease), background-color 0.2s var(--ease);
}

body.has-cursor { cursor: none; }
body.has-cursor a, body.has-cursor button, body.has-cursor label,
body.has-cursor input, body.has-cursor textarea, body.has-cursor select,
body.has-cursor .flip, body.has-cursor .car-view, body.has-cursor .car-dot { cursor: none; }
body.has-cursor .cursor-dot, body.has-cursor .cursor-ring { opacity: 1; }

/* over anything interactive: the ring grows and takes a red wash */
body.has-cursor.cur-on .cursor-ring {
  width: 46px; height: 46px; margin: -23px 0 0 -23px; background: var(--soft);
}
body.has-cursor.cur-down .cursor-ring { width: 22px; height: 22px; margin: -11px 0 0 -11px; }
body.has-cursor.cur-out .cursor-dot, body.has-cursor.cur-out .cursor-ring { opacity: 0; }

/* ── reveal ───────────────────────────────────────────────────────────── */

/* NEVER put .reveal on an interactive element. These rules set `transition`
   wholesale, which overrides that element's own transitions — the CTA button
   carried .reveal and silently lost its ink timing, so its label flipped
   colour instantly and dropped into the background mid-fill. Wrap the control
   in a .reveal span instead; .rise keeps the wrapper out of the layout. */
.rise { display: inline-flex; }
body.anim .reveal { opacity: 0; transform: translateY(14px); }
body.anim .reveal.in {
  opacity: 1; transform: none;
  transition: opacity 0.55s var(--ease), transform 0.55s var(--ease);
}
body.anim .d1.in { transition-delay: 0.07s; }
body.anim .d2.in { transition-delay: 0.14s; }
body.anim .d3.in { transition-delay: 0.21s; }

@media (prefers-reduced-motion: reduce) {
  body.anim .reveal, body.anim .reveal.in { opacity: 1; transform: none; transition: none; }
  .car-track { transition: none; }
  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* ── responsive ───────────────────────────────────────────────────────────
   Two layouts, one boundary. Desktop and tablet are the SAME layout — two
   column splits, horizontal nav — and it holds all the way down to 48em,
   which is an iPad in portrait. Below that is the phone layout. Everything
   between the named breakpoints is handled by clamp() on the type, the
   gutter and the section rhythm, so the layout scales continuously rather
   than snapping between fixed sizes.

   The breakpoints are in EM, not PX, and that is deliberate. In a media query
   `em` resolves against the browser's default font size — the thing a person
   changes when they set larger text at the OS or browser level. With the
   default 16px these are exactly the pixel values they replace. When someone
   raises it, every breakpoint scales with it, so the phone layout arrives at a
   wider viewport instead of the tablet nav being asked to fit type it can no
   longer hold. Browser zoom is already handled, because zoom changes the CSS
   pixel width of the viewport and the px-equivalent boundaries move with it.
   ------------------------------------------------------------------------ */

/* Wide desktop → tablet: the masthead row is the only thing that genuinely
   runs out of room, so it tightens progressively instead of breaking. */
@media (max-width: 73.75em) {
  :root { --navgap: 1rem; }
  .nav a { font-size: 0.8125rem; }
  .brand img { width: clamp(150px, 16vw, 200px); }
}

/* The measure is retuned for this band: the column narrows faster than the
   type does, so the wide value stops binding and the paragraph falls back to
   a stub last line. 29.75rem holds the last line near full again. The band
   starts at 1179 rather than 1180 because at exactly 1180 the type is still
   at its capped size and the wide value is still the better fit. */
@media (max-width: 73.6875em) and (min-width: 58.8125em) {
  .body-lg { max-width: 29.75rem; }
}

/* The narrow end of the two-column band. Holding the contact column's
   two-up layout down to 768 leaves it ~340px wide, and two things sized for
   a desktop column stop fitting there:

     - the thank-you line is locked to one line, so its type has to track the
       column rather than the viewport, or it runs past the column edge and
       pushes the whole page into horizontal scroll;
     - the deliberate break in the contact paragraph was balanced against a
       desktop measure and turns ragged in a half-width column.

   Both are scoped to this band only. */
@media (max-width: 58.75em) and (min-width: 48em) {
  .body-lg { max-width: 26rem; }
  .form-done p { font-size: clamp(0.75rem, 0.087rem + 1.395vw, 1.0313rem); }
  #contact .two br { display: none; }
  #contact .two { max-width: none; text-wrap: pretty; }
}

/* Tablet portrait. Same layout, tighter fittings — the nav has to carry five
   links plus Join plus the toggle inside ~700px of usable width. */
@media (max-width: 58.75em) {
  :root { --navgap: 0.75rem; }
  /* 16vw matches the slope of the band above, so the mark grows continuously
     across 940 instead of stepping. The tablet mark cannot reach the desktop
     proportion — the row still has to carry five nav links, Join and the
     toggle — so it takes the largest size that leaves the nav uncrowded. */
  .brand img { width: clamp(126px, 16vw, 200px); }
  .nav a { font-size: 0.75rem; }
  .btn-nav { padding: 0.5rem 0.875rem; font-size: 0.75rem; }
  .socials { gap: 0.5rem; }
  .socials a { padding: 0.625rem 0.125rem; }
  .socials span { font-size: 0.6563rem; }
  .split { gap: clamp(1rem, 2.4vw, 1.75rem); }
  .hero-grid { gap: clamp(1rem, 2.4vw, 1.75rem); }
}

/* ── phone ────────────────────────────────────────────────────────────── */
@media (max-width: 47.9375em) {
  :root { --navgap: 0.625rem; }

  /* single column everywhere */
  .hero-grid { grid-template-columns: 1fr; }
  .split, .split-even { grid-template-columns: 1fr; }
  .foot-in { grid-template-columns: 1fr; }
  .body-lg { max-width: 31rem; }

  /* the nav collapses into the drawer; Join lives inside it, or the row
     overflows at 320px */
  .nav { display: none; }
  .btn-nav { display: none; }
  .menu-btn { display: grid; }
  .theme-btn { margin-left: auto; }
  .mobile-nav { display: flex; }

  /* headings wrap here, so they get the leading that clears the serif
     italic's own extremes — measured, not guessed: see README. */
  h1 { white-space: normal; font-size: clamp(1.9rem, 8.2vw, 2.75rem); line-height: 1.24; }
  /* 1.45rem floor, not 1.55: at 320px 'We turn new into known.' needs to
     stay on one line and the larger floor pushed it to two. */
  h2 { font-size: clamp(1.45rem, 6.6vw, 2.25rem); line-height: 1.26; }
  .m-br { display: inline; }
  .d-br { display: none; }

  .socials { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.625rem; }
  .socials a { padding: 0.75rem 0.25rem; }
  .socials span { font-size: 0.7188rem; }
  .car-card { grid-template-columns: 1fr; row-gap: 0.5rem; }
  .car-n { grid-row: auto; }
  .cta-in { flex-direction: column; align-items: flex-start; }
  .form-done p { white-space: normal; max-width: none; }
  .flip, .face { min-height: 16rem; }
  .btn-wide { max-width: none; }
  .form-zone { min-height: 24rem; }

  /* The wordmark takes whatever the row has left over rather than a fixed
     clamp, so it holds the same proportion to the 2.375rem toggle that it does
     on desktop (268:38) instead of collapsing to a third of it. Capped at the
     desktop maximum so it never overshoots what it is matching. */
  .brand { flex: 1 1 auto; min-width: 0; }
  .brand img { width: 100%; max-width: 268px; }

  /* footer: the mark and the form column centre on the screen; Sections and
     Socials stay on their own edges. The mark is locked to the CTA's width so
     the two centred elements share an edge. */
  .foot-brand { margin-inline: auto; }
  .foot-brand img { width: min(100%, var(--foot-cta)); margin-inline: auto; }
  .foot-cols { justify-content: space-between; row-gap: 2rem; }
  .foot-cols > div:last-child { flex-basis: 100%; text-align: center; }
  .btn-foot { margin-inline: auto; }
}

/* A forced break that balances two lines on a wide card turns into a ragged
   one-line-then-three-lines block once the column is phone width. Below 700px
   the breaks are switched off and the caps released so the copy simply sets to
   the column. The space authored before each <br> is what keeps the words
   apart once the break stops rendering. */
@media (max-width: 43.75em) {
  .two br, .two-42 br { display: none; }   /* .m-br is not inside these */
  .two, .two-42 { max-width: none; }
  .two, .two-42, .face p, .car-card p, .blurb, .body-sm { text-wrap: pretty; }
}

/* Pre-existing: five 2.25rem dots plus two arrows measure 316px, so the
   carousel controls pushed the page 16px wide at 320. */
@media (max-width: 26.25em) {
  .car-dot { width: 1.5rem; }
}
