/* 01-tokens.css */
/**
 * Design tokens.
 *
 * The palette is the app's, unchanged — src/theme/tokens.ts is still the source
 * of truth for colour, type, spacing and radii. What is new here is everything
 * the app does not need: glass surfaces, an elevation ladder, motion curves,
 * and the atmospheric gradients those surfaces sit on.
 *
 * Nothing outside this file may hardcode a colour.
 */

:root {
  color-scheme: light dark;

  /* ── Surfaces (from the app) ──────────────────────────────────────────── */
  --color-background: #fbf7f0;
  --color-card: #ffffff;
  --color-surface: #f4ede4;
  --color-tint: #f1e4e0;

  /* ── Brand (from the app) ─────────────────────────────────────────────── */
  --color-primary: #8b1e2d;
  --color-primary-pressed: #73151f;
  --color-accent: #a8323f;
  --color-on-primary: #fff6f2;

  /**
   * Brand colour for TEXT and ICONS as opposed to fills. Crimson reads
   * beautifully on cream (8.5:1) but only manages 2.6–3.5:1 on the dark
   * surfaces. In dark this resolves to the palette's existing `accent`, which
   * tokens.ts already describes as "lighter red — small highlights, icon
   * accents", and which clears 5.5:1 everywhere.
   */
  --color-brand-text: #8b1e2d;

  /* ── Text ─────────────────────────────────────────────────────────────── */
  --color-text-primary: #3b2a24;
  --color-text-secondary: #6b564e;
  --color-text-tertiary: #9a8a82;

  --color-border: rgba(59, 42, 36, 0.12);
  --color-border-strong: rgba(59, 42, 36, 0.16);

  --color-success: #1f7a3e;
  --color-success-surface: #e7f6ec;
  --color-success-border: #9fd3ae;

  /* ═══ GLASS ═══════════════════════════════════════════════════════════
     Glass is only convincing with three things at once: a translucent fill,
     a lit top edge, and a shadow that separates it from what it floats over.
     One without the others reads as a flat grey box.                       */
  --glass-fill: rgba(255, 255, 255, 0.62);
  --glass-fill-strong: rgba(255, 255, 255, 0.78);
  --glass-fill-faint: rgba(255, 255, 255, 0.4);
  --glass-border: rgba(255, 255, 255, 0.85);
  --glass-border-soft: rgba(139, 30, 45, 0.1);
  /* The lit edge: a bright inset hairline along the top of the surface. */
  --glass-sheen: inset 0 1px 0 rgba(255, 255, 255, 0.9);
  --glass-blur: 22px;

  /* ── Elevation: ambient spread + tight contact shadow ─────────────────── */
  --elev-1: 0 1px 2px rgba(59, 42, 36, 0.05), 0 4px 14px rgba(59, 42, 36, 0.05);
  --elev-2: 0 2px 4px rgba(59, 42, 36, 0.06), 0 10px 30px rgba(59, 42, 36, 0.08);
  --elev-3: 0 4px 8px rgba(59, 42, 36, 0.07), 0 22px 56px rgba(59, 42, 36, 0.13);
  --elev-glow: 0 18px 48px rgba(139, 30, 45, 0.22);

  /* ── Atmosphere ───────────────────────────────────────────────────────── */
  --aurora-1: rgba(139, 30, 45, 0.16);
  --aurora-2: rgba(193, 58, 72, 0.13);
  --aurora-3: rgba(230, 138, 148, 0.16);
  --grain-opacity: 0.032;

  --map-filter: saturate(0.82) sepia(0.12) brightness(1.02);

  /* ── Type ─────────────────────────────────────────────────────────────── */
  --font-serif: 'Fraunces', ui-serif, Georgia, 'Times New Roman', serif;
  --font-body: 'Montserrat', ui-sans-serif, system-ui, -apple-system,
    'Segoe UI', Roboto, sans-serif;

  --text-hero: clamp(2.375rem, 1.35rem + 4.1vw, 4.5rem);
  --text-display: clamp(2rem, 1.3rem + 2.9vw, 3.25rem);
  --text-section: clamp(1.375rem, 1.2rem + 0.7vw, 1.625rem);
  --text-lead: clamp(1.0625rem, 1rem + 0.4vw, 1.3125rem);
  --text-body-lg: 1rem;
  --text-body: 0.9375rem;
  --text-label: 0.8125rem;
  --text-caption: 0.75rem;
  --text-tag: 0.71875rem;

  /* ── Spacing / radii (4px scale, from the app) ────────────────────────── */
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 0.75rem;
  --space-lg: 1rem;
  --space-xl: 1.25rem;
  --space-xxl: 1.5rem;
  --space-xxxl: 2rem;
  --space-huge: 2.5rem;
  --space-section: clamp(4rem, 2.6rem + 5.6vw, 8.5rem);

  --radius-sm: 10px;
  --radius-md: 14px;
  --radius-lg: 18px;
  --radius-xl: 22px;
  --radius-xxl: 28px;
  --radius-3xl: 36px;
  --radius-pill: 999px;

  --measure: 34rem;
  --measure-wide: 46rem;
  --page-max: 75rem;
  --page-gutter: clamp(1.25rem, 0.8rem + 2vw, 2.75rem);
  --header-height: 4.5rem;

  /* ═══ MOTION ══════════════════════════════════════════════════════════
     Two curves do almost all the work: `out-expo` for things arriving, and
     `spring` for things responding to a pointer. Durations stay short —
     motion should feel like physics, not like waiting.                     */
  --dur-fast: 160ms;
  --dur-mid: 320ms;
  --dur-slow: 620ms;
  /* Long enough that the eye tracks the light instead of being startled by it. */
  --dur-sweep: 900ms;
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  /* Sine-based ease-in-out: gentle at both ends, near constant speed through
     the middle. For anything that TRAVELS across a surface rather than
     arriving at a stop, this is the curve; --ease-out-expo front-loads so
     hard that a moving highlight reads as a flash. */
  --ease-sweep: cubic-bezier(0.37, 0, 0.63, 1);
}

/* ── Dark ─────────────────────────────────────────────────────────────────
   Restated in full for both the system preference and the explicit override,
   so an explicit choice always wins in either direction.                    */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --color-background: #17120f;
    --color-card: #241f1d;
    --color-surface: #332b29;
    --color-tint: #2c2421;

    --color-primary: #c13a48;
    --color-primary-pressed: #a83240;
    --color-accent: #e68a94;
    --color-on-primary: #fff1f0;
    --color-brand-text: #e68a94;

    --color-text-primary: #f4eeea;
    --color-text-secondary: #c9bdb8;
    --color-text-tertiary: #6e5e58;

    --color-border: rgba(245, 237, 230, 0.1);
    --color-border-strong: rgba(245, 237, 230, 0.18);

    --color-success: #7cc598;
    --color-success-surface: #1e2e24;
    --color-success-border: #33543f;

    --glass-fill: rgba(255, 246, 242, 0.055);
    --glass-fill-strong: rgba(255, 246, 242, 0.085);
    --glass-fill-faint: rgba(255, 246, 242, 0.032);
    --glass-border: rgba(255, 246, 242, 0.14);
    --glass-border-soft: rgba(255, 246, 242, 0.08);
    --glass-sheen: inset 0 1px 0 rgba(255, 246, 242, 0.14);

    --elev-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 14px rgba(0, 0, 0, 0.3);
    --elev-2: 0 2px 4px rgba(0, 0, 0, 0.45), 0 10px 30px rgba(0, 0, 0, 0.4);
    --elev-3: 0 4px 8px rgba(0, 0, 0, 0.5), 0 22px 56px rgba(0, 0, 0, 0.55);
    --elev-glow: 0 18px 52px rgba(193, 58, 72, 0.3);

    --aurora-1: rgba(193, 58, 72, 0.3);
    --aurora-2: rgba(139, 30, 45, 0.28);
    --aurora-3: rgba(230, 138, 148, 0.14);
    --grain-opacity: 0.045;

    --map-filter: saturate(0.72) brightness(0.82) contrast(1.04);
  }
}

:root[data-theme='dark'] {
  --color-background: #17120f;
  --color-card: #241f1d;
  --color-surface: #332b29;
  --color-tint: #2c2421;

  --color-primary: #c13a48;
  --color-primary-pressed: #a83240;
  --color-accent: #e68a94;
  --color-on-primary: #fff1f0;
  --color-brand-text: #e68a94;

  --color-text-primary: #f4eeea;
  --color-text-secondary: #c9bdb8;
  --color-text-tertiary: #6e5e58;

  --color-border: rgba(245, 237, 230, 0.1);
  --color-border-strong: rgba(245, 237, 230, 0.18);

  --color-success: #7cc598;
  --color-success-surface: #1e2e24;
  --color-success-border: #33543f;

  --glass-fill: rgba(255, 246, 242, 0.055);
  --glass-fill-strong: rgba(255, 246, 242, 0.085);
  --glass-fill-faint: rgba(255, 246, 242, 0.032);
  --glass-border: rgba(255, 246, 242, 0.14);
  --glass-border-soft: rgba(255, 246, 242, 0.08);
  --glass-sheen: inset 0 1px 0 rgba(255, 246, 242, 0.14);

  --elev-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 14px rgba(0, 0, 0, 0.3);
  --elev-2: 0 2px 4px rgba(0, 0, 0, 0.45), 0 10px 30px rgba(0, 0, 0, 0.4);
  --elev-3: 0 4px 8px rgba(0, 0, 0, 0.5), 0 22px 56px rgba(0, 0, 0, 0.55);
  --elev-glow: 0 18px 52px rgba(193, 58, 72, 0.3);

  --aurora-1: rgba(193, 58, 72, 0.3);
  --aurora-2: rgba(139, 30, 45, 0.28);
  --aurora-3: rgba(230, 138, 148, 0.14);
  --grain-opacity: 0.045;

  --map-filter: saturate(0.72) brightness(0.82) contrast(1.04);
}

/* 02-base.css */
/**
 * Reset, self-hosted variable fonts, and the type system.
 *
 * Fraunces is a variable font with an optical-size axis. Using it means the
 * display sizes get the tight, high-contrast cut the face was drawn for and
 * small sizes get the sturdier one — which is most of the difference between
 * type that looks set and type that looks defaulted.
 */

@font-face {
  font-family: 'Fraunces';
  src: url('../fonts/fraunces.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Montserrat';
  src: url('../fonts/montserrat.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

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

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-height) + 1.5rem);
}

body {
  min-height: 100vh;
  background: var(--color-background);
  color: var(--color-text-primary);
  font-family: var(--font-body);
  font-size: var(--text-body-lg);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img,
picture,
svg,
video {
  display: block;
  max-width: 100%;
}

input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

ul,
ol {
  list-style: none;
  padding: 0;
}

a {
  color: var(--color-brand-text);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/* ── Type ─────────────────────────────────────────────────────────────── */
h1,
h2,
h3 {
  font-family: var(--font-serif);
  color: var(--color-text-primary);
  text-wrap: balance;
}

h1 {
  font-size: var(--text-hero);
  /* opsz 96 = the display cut: tighter joins, finer hairlines. */
  font-variation-settings: 'opsz' 96, 'SOFT' 0, 'WONK' 1;
  font-weight: 700;
  line-height: 0.98;
  letter-spacing: -0.035em;
}

h2 {
  font-size: var(--text-display);
  font-variation-settings: 'opsz' 72, 'SOFT' 0, 'WONK' 1;
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.028em;
}

h3 {
  font-size: var(--text-section);
  font-variation-settings: 'opsz' 32, 'SOFT' 8, 'WONK' 0;
  font-weight: 600;
  line-height: 1.22;
  letter-spacing: -0.014em;
}

p {
  text-wrap: pretty;
}

strong {
  font-weight: 600;
}

/* ── Type roles ───────────────────────────────────────────────────────── */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-family: var(--font-body);
  font-size: var(--text-tag);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-brand-text);
}

/* Small pulsing dot — the only always-on motion, and it is 6px wide. */
.eyebrow::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 0 currentColor;
  animation: pulse-dot 2.6s var(--ease-in-out) infinite;
}

@keyframes pulse-dot {
  0%, 100% { box-shadow: 0 0 0 0 rgba(139, 30, 45, 0.45); }
  50% { box-shadow: 0 0 0 5px rgba(139, 30, 45, 0); }
}

.lead {
  font-size: var(--text-lead);
  line-height: 1.62;
  color: var(--color-text-secondary);
}

.body-text {
  font-size: var(--text-body-lg);
  line-height: 1.65;
  color: var(--color-text-secondary);
}

.caption {
  font-size: var(--text-caption);
  line-height: 1.45;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--color-text-secondary);
}

.measure {
  max-width: var(--measure);
}

/* A gradient wash across display type — used once per page at most. */
.text-gradient {
  background: linear-gradient(
    112deg,
    var(--color-text-primary) 12%,
    var(--color-brand-text) 58%,
    var(--color-accent) 96%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ── Layout ───────────────────────────────────────────────────────────── */
.wrap {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--page-gutter);
}

.section {
  position: relative;
  padding-block: var(--space-section);
}

.section--tight {
  padding-block: calc(var(--space-section) * 0.58);
}

.section-head {
  max-width: var(--measure);
  margin-bottom: var(--space-huge);
}

.section-head--tight {
  margin-bottom: var(--space-xxl);
}

.section-head .eyebrow {
  margin-bottom: var(--space-lg);
}

.section-head h2 + p,
.section-head h1 + p {
  margin-top: var(--space-lg);
}

.page-title {
  font-size: var(--text-display);
  font-variation-settings: 'opsz' 72, 'SOFT' 0, 'WONK' 1;
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.028em;
}

/* ── Accessibility ────────────────────────────────────────────────────── */
:focus-visible {
  outline: 2.5px solid var(--color-brand-text);
  outline-offset: 3px;
  border-radius: 6px;
}

.skip-link {
  position: absolute;
  left: var(--space-lg);
  top: -100%;
  z-index: 200;
  padding: var(--space-md) var(--space-lg);
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-size: var(--text-label);
  font-weight: 600;
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: top var(--dur-fast) var(--ease-out-expo);
}

.skip-link:focus {
  top: var(--space-lg);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* 03-atmosphere.css */
/**
 * The atmosphere layer.
 *
 * Glass is only glass if there is something behind it. This paints a slow,
 * drifting aurora in the brand reds plus a fine grain, fixed behind all
 * content, so every translucent surface has real colour to refract.
 *
 * Both layers are `position: fixed` and animate only `transform` and
 * `opacity`, so they stay on the compositor and never trigger layout.
 */

.atmosphere {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  /* Nothing inside ever affects layout or paints outside this box. */
  contain: strict;
}

/* Three drifting colour fields. Radial gradients rather than blurred divs —
   same look, a fraction of the cost, no filter on a huge surface. */
.atmosphere__aurora {
  position: absolute;
  inset: -25%;
  background:
    radial-gradient(38% 44% at 78% 12%, var(--aurora-1), transparent 62%),
    radial-gradient(34% 40% at 12% 34%, var(--aurora-2), transparent 66%),
    radial-gradient(44% 38% at 58% 88%, var(--aurora-3), transparent 64%);
  animation: aurora-drift 34s var(--ease-in-out) infinite alternate;
  will-change: transform;
}

/* A second, counter-drifting field keeps the movement from reading as a
   single sliding sheet. */
.atmosphere__aurora--alt {
  background:
    radial-gradient(30% 36% at 26% 8%, var(--aurora-3), transparent 60%),
    radial-gradient(40% 46% at 88% 62%, var(--aurora-2), transparent 64%);
  animation: aurora-drift-alt 46s var(--ease-in-out) infinite alternate;
  opacity: 0.75;
}

@keyframes aurora-drift {
  from { transform: translate3d(-3%, -2%, 0) scale(1); }
  to   { transform: translate3d(4%, 3%, 0) scale(1.12); }
}

@keyframes aurora-drift-alt {
  from { transform: translate3d(3%, 2%, 0) scale(1.08); }
  to   { transform: translate3d(-4%, -3%, 0) scale(1); }
}

/**
 * Film grain. Breaks up the gradient banding that large soft washes always
 * produce on 8-bit displays, and stops the background reading as flat CSS.
 * Inline SVG turbulence — no network request, no image to ship.
 */
.atmosphere__grain {
  position: absolute;
  inset: 0;
  opacity: var(--grain-opacity);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  mix-blend-mode: overlay;
}

/* A soft vignette pulls focus toward the centre column on wide screens. */
.atmosphere__vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    120% 80% at 50% 0%,
    transparent 42%,
    color-mix(in srgb, var(--color-background) 72%, transparent) 100%
  );
}

/* Hairline grid, barely visible — gives the eye something to register the
   glass panels against as they scroll past. */
.atmosphere__grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, var(--color-border) 1px, transparent 1px),
    linear-gradient(to bottom, var(--color-border) 1px, transparent 1px);
  background-size: 84px 84px;
  mask-image: radial-gradient(90% 60% at 50% 0%, #000 0%, transparent 72%);
  -webkit-mask-image: radial-gradient(90% 60% at 50% 0%, #000 0%, transparent 72%);
  opacity: 0.5;
}

@media (prefers-reduced-motion: reduce) {
  .atmosphere__aurora,
  .atmosphere__aurora--alt {
    animation: none;
  }
}

/* 04-glass.css */
/**
 * The glass system.
 *
 * Convincing glass needs four things together, and it looks cheap if any one
 * is missing:
 *   1. a translucent fill, so the atmosphere shows through
 *   2. a backdrop blur with saturation lift, so what shows through is richer
 *      rather than muddier
 *   3. a lit top edge — the inset sheen — because real glass catches light
 *      on its upper bevel
 *   4. a two-part shadow: a tight contact shadow plus a wide ambient one
 *
 * `.glass` is the base. Modifiers change weight, not technique.
 */

.glass {
  position: relative;
  background: var(--glass-fill);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.7);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.7);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--elev-2), var(--glass-sheen);
}

.glass--strong {
  background: var(--glass-fill-strong);
}

.glass--faint {
  background: var(--glass-fill-faint);
  border-color: var(--glass-border-soft);
  box-shadow: var(--elev-1), var(--glass-sheen);
}

.glass--flush {
  border-radius: var(--radius-3xl);
}

/*
 * There was a sweeping specular highlight here, applied to every card on the
 * site. It has been removed deliberately.
 *
 * A light sweep reads as "this surface was struck", which is the right feeling
 * for something you press and wrong for something you are only reading. On six
 * cards in a grid it also fired constantly as the pointer crossed the page,
 * and a page that flashes wherever the mouse goes is a page that is hard to
 * read. Cards keep .glass--lift, which is a hover response tied to depth
 * rather than to light.
 *
 * The sweep survives on .btn--primary alone, in 06-components.css.
 */

/* Interactive glass: lifts toward the pointer and warms its edge. */
.glass--lift {
  transition: transform var(--dur-mid) var(--ease-spring),
    box-shadow var(--dur-mid) var(--ease-out-expo),
    border-color var(--dur-mid) var(--ease-out-expo);
}

.glass--lift:hover {
  transform: translateY(-6px);
  box-shadow: var(--elev-3), var(--glass-sheen);
  border-color: color-mix(in srgb, var(--color-primary) 34%, var(--glass-border));
}

/**
 * Gradient hairline border. `border` cannot take a gradient, so this paints
 * one in a pseudo-element and punches the middle out with a mask — the
 * standard technique, and the only one that keeps the corner radius clean.
 */
.glass--edge::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(
    150deg,
    color-mix(in srgb, var(--color-primary) 46%, transparent),
    transparent 38%,
    transparent 62%,
    color-mix(in srgb, var(--color-accent) 32%, transparent)
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

/**
 * Backdrop-filter is the one piece with no graceful fallback — without it the
 * fill is simply translucent and the panel looks washed out. Where it is
 * unsupported, fall back to the app's solid card colour instead.
 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass,
  .glass--strong,
  .glass--faint {
    background: var(--color-card);
    border-color: var(--color-border);
  }
}

/* 05-motion.css */
/**
 * The motion system.
 *
 * Two rules govern everything here:
 *   1. Only `transform`, `opacity` and `filter` are animated, so nothing ever
 *      leaves the compositor.
 *   2. Every element that animates in is VISIBLE BY DEFAULT. The hidden state
 *      is applied by JavaScript (`.js-motion` on <html>) only once we know the
 *      observer is running. With JS off, blocked, or broken, the page is a
 *      normal readable page rather than a screen of invisible content.
 */

/* ── Scroll reveal ────────────────────────────────────────────────────── */
.js-motion [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition: opacity var(--dur-slow) var(--ease-out-expo),
    transform var(--dur-slow) var(--ease-out-expo);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: transform, opacity;
}

.js-motion [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* Directional variants for content that should arrive from the side. */
.js-motion [data-reveal='left'] { transform: translate3d(-26px, 0, 0); }
.js-motion [data-reveal='right'] { transform: translate3d(26px, 0, 0); }
.js-motion [data-reveal='scale'] { transform: scale(0.94); }

.js-motion [data-reveal='left'].is-revealed,
.js-motion [data-reveal='right'].is-revealed,
.js-motion [data-reveal='scale'].is-revealed {
  transform: none;
}

/**
 * Stagger. Children of [data-stagger] are delayed in sequence via a custom
 * property the JS sets per child, so a row of cards arrives as a cascade
 * rather than a block.
 */
.js-motion [data-stagger] > * {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  transition: opacity var(--dur-slow) var(--ease-out-expo),
    transform var(--dur-slow) var(--ease-out-expo);
  transition-delay: var(--stagger-delay, 0ms);
}

.js-motion [data-stagger].is-revealed > * {
  opacity: 1;
  transform: none;
}

/* ── Entrance (above the fold, plays on load rather than on scroll) ────── */
.js-motion [data-enter] {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  animation: enter-rise var(--dur-slow) var(--ease-out-expo) forwards;
  animation-delay: var(--enter-delay, 0ms);
}

@keyframes enter-rise {
  to {
    opacity: 1;
    transform: none;
  }
}

/* ── Continuous ───────────────────────────────────────────────────────── */

/* Slow rotation for the ring behind the hero mark. */
@keyframes spin-slow {
  to { transform: rotate(360deg); }
}

/**
 * Drift for the hero composition's satellite chips.
 *
 * A single vertical bob reads as mechanical — three of them bobbing on the
 * same axis read as a loading spinner. This walks each chip around a small
 * closed loop on both axes with a touch of rotation, so they behave more like
 * something suspended in air than something on a timer.
 *
 * Amplitude, period and phase come from per-chip custom properties (see
 * .hero__chip--n), which is what keeps the three from ever syncing up. Only
 * transform is animated, so it all stays on the compositor.
 */
@keyframes float-drift {
  0%,
  100% {
    transform: translate3d(0, 0, 0) rotate(0deg);
  }
  25% {
    transform: translate3d(calc(var(--drift-x) * 0.55), calc(var(--drift-y) * -1), 0)
      rotate(var(--drift-r));
  }
  50% {
    transform: translate3d(var(--drift-x), calc(var(--drift-y) * -0.4), 0) rotate(0deg);
  }
  75% {
    transform: translate3d(calc(var(--drift-x) * 0.25), calc(var(--drift-y) * -0.85), 0)
      rotate(calc(var(--drift-r) * -1));
  }
}

/* Marquee track — translated by exactly half its width, with the content
   duplicated, so the loop is seamless. */
@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-50%, 0, 0); }
}

/* ═══ REDUCED MOTION ══════════════════════════════════════════════════════
   Not a softening — a full stop. Everything lands in its final state
   immediately, including anything mid-animation.                           */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  html {
    scroll-behavior: auto;
  }

  .js-motion [data-reveal],
  .js-motion [data-stagger] > *,
  .js-motion [data-enter] {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* 06-components.css */
/**
 * Shared chrome: header, nav, buttons, badges, footer.
 */

/* ═══ HEADER ══════════════════════════════════════════════════════════════
   A floating glass bar rather than a full-bleed strip — it reads as an
   object over the page instead of a band welded to the top of it.          */
.site-header {
  position: sticky;
  top: var(--space-md);
  z-index: 100;
  margin-inline: auto;
  padding-inline: var(--page-gutter);
  max-width: var(--page-max);
  transition: top var(--dur-mid) var(--ease-out-expo);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  height: var(--header-height);
  padding-inline: var(--space-xl);
  border-radius: var(--radius-pill);
  background: var(--glass-fill-strong);
  -webkit-backdrop-filter: blur(20px) saturate(1.8);
  backdrop-filter: blur(20px) saturate(1.8);
  border: 1px solid var(--glass-border);
  box-shadow: var(--elev-1), var(--glass-sheen);
  transition: box-shadow var(--dur-mid) var(--ease-out-expo),
    background-color var(--dur-mid) var(--ease-out-expo);
}

/* Deepens once the page has scrolled — set by site.js. */
.is-scrolled .site-header__inner {
  box-shadow: var(--elev-2), var(--glass-sheen);
}

@media (max-width: 46rem) {
  .site-header__inner {
    padding-inline: var(--space-lg);
    border-radius: var(--radius-xxl);
  }
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  text-decoration: none;
  color: var(--color-text-primary);
  margin-right: auto;
  min-width: 0;
}

.brand__mark {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  object-fit: contain;
  transition: transform var(--dur-mid) var(--ease-spring);
}

.brand:hover .brand__mark {
  transform: scale(1.08) rotate(-4deg);
}

/**
 * .brand sets min-width: 0 so it can shrink, but the inner <span> keeps the
 * flex default of min-width: auto and refuses to. The name is nowrap, so it
 * painted straight out of its own shrunken box and under the language switch:
 * at 375px the header read "The Community C[EN]set". Giving the wrapper a
 * shrinkable floor is the actual fix; the clip below is the belt to its braces.
 */
.brand > span {
  min-width: 0;
  overflow: hidden;
}

.brand__name {
  display: block;
  font-family: var(--font-serif);
  font-variation-settings: 'opsz' 20, 'SOFT' 0, 'WONK' 1;
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.022em;
  line-height: 1.1;
  white-space: nowrap;
}

/* Scoped to the header on purpose: the footer renders the same class with room
   to spare, and an unscoped rule would blank the wordmark down there too. */
.site-header .brand__name {
  overflow: hidden;
  /* The descender of the 'y' clips at overflow: hidden without this pair. */
  padding-block: 2px;
  margin-block: -2px;
}

/**
 * Below this width there is no arrangement where the full name and the controls
 * both fit, so the name steps aside and the mark carries the brand.
 *
 * 30rem is measured, not guessed: the name needs 185px and the header gives it
 * 176px at a 460px viewport, so it fully fits from about 469px up. 30rem (480px)
 * clears that with margin and matches .brand__tagline, which already steps aside
 * at the same width, so the two never disagree about when the header is tight.
 *
 * VISUALLY hidden, never display: none. .brand__mark is alt="", so this text is
 * the entire accessible name of the home link. Removing it from the tree would
 * leave every phone visitor with an unlabelled link, which is a worse bug than
 * the one being fixed.
 */
@media (max-width: 30rem) {
  .site-header .brand__name {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

.brand__tagline {
  display: block;
  font-size: 0.5938rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-brand-text);
  margin-top: 3px;
  white-space: nowrap;
}

@media (max-width: 30rem) {
  .brand__tagline { display: none; }
}

/* ── Nav ──────────────────────────────────────────────────────────────── */
.nav {
  display: flex;
  align-items: center;
  gap: 2px;
}

.nav__link {
  position: relative;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-pill);
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--color-text-secondary);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out-expo);
}

/* The hover pill scales in from the centre rather than appearing. */
.nav__link::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
  opacity: 0;
  transform: scale(0.86);
  transition: opacity var(--dur-fast) var(--ease-out-expo),
    transform var(--dur-fast) var(--ease-spring);
}

.nav__link:hover {
  color: var(--color-text-primary);
}

.nav__link:hover::before {
  opacity: 1;
  transform: scale(1);
}

.nav__link[aria-current='page'] {
  color: var(--color-brand-text);
}

.nav__link[aria-current='page']::before {
  opacity: 1;
  transform: scale(1);
}

.nav__link > span {
  position: relative;
}

/**
 * 56rem, not 46rem. The desktop nav cannot yield: .nav__link is nowrap and
 * .nav is a non-wrapping flex row, so when the header runs short the only
 * thing that gives is .brand (the one item with min-width: 0). Measured at a
 * 768px viewport the wordmark collapsed to an 89px box around 185px of name
 * and the clip above truncated it, which is what "acts up when shrunk" was.
 *
 * Handing the nav a floor instead just moved the damage: the page itself
 * overflowed to 815px. So the nav has to step aside sooner. The desktop row
 * needs ~867px to sit beside the full brand: measured at 840px the wordmark
 * still had only 158px of the 185px it wants. 56rem (896px) clears it.
 *
 * Must stay in lockstep with the .nav-mobile query below — if the two ever
 * disagree there is a band of widths with no navigation at all.
 */
@media (max-width: 56rem) {
  .nav { display: none; }
}

.nav-mobile {
  display: none;
  margin-top: var(--space-md);
  padding-inline: var(--page-gutter);
  max-width: var(--page-max);
  margin-inline: auto;
}

.nav-mobile ul {
  display: flex;
  gap: var(--space-xs);
  overflow-x: auto;
  scrollbar-width: none;
  padding: var(--space-xs);
  border-radius: var(--radius-pill);
  background: var(--glass-fill);
  -webkit-backdrop-filter: blur(16px) saturate(1.6);
  backdrop-filter: blur(16px) saturate(1.6);
  border: 1px solid var(--glass-border);
}

.nav-mobile ul::-webkit-scrollbar { display: none; }

/* Lockstep partner of the .nav query above. Both are 56rem. */
@media (max-width: 56rem) {
  .nav-mobile { display: block; }
}

/* ── Icon button ──────────────────────────────────────────────────────── */
.icon-button {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  padding: 0;
  border: 1px solid var(--glass-border-soft);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-primary) 7%, transparent);
  color: var(--color-text-secondary);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out-expo),
    background-color var(--dur-fast) var(--ease-out-expo),
    transform var(--dur-fast) var(--ease-spring);
}

.icon-button:hover {
  color: var(--color-brand-text);
  background: color-mix(in srgb, var(--color-primary) 14%, transparent);
}

.icon-button:active { transform: scale(0.92); }

.icon-button svg { width: 17px; height: 17px; }

/* Exactly one theme icon shows. Every state is listed explicitly so a
   stale stylesheet can never leave two of them stacked. */
.theme-icon { display: none; }
:root[data-theme-pref='system'] .theme-icon--system { display: block; }
:root[data-theme-pref='light'] .theme-icon--light { display: block; }
:root[data-theme-pref='dark'] .theme-icon--dark { display: block; }
/* Before theme.js runs, and if it never does, show the system icon. */
:root:not([data-theme-pref]) .theme-icon--system { display: block; }

/* ═══ BUTTONS ═════════════════════════════════════════════════════════════ */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: 0.875rem var(--space-xxl);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 700;
  letter-spacing: 0.005em;
  text-decoration: none;
  cursor: pointer;
  overflow: hidden;
  isolation: isolate;
  transition: transform var(--dur-fast) var(--ease-spring),
    box-shadow var(--dur-mid) var(--ease-out-expo),
    border-color var(--dur-fast) var(--ease-out-expo),
    color var(--dur-fast) var(--ease-out-expo);
}

.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0) scale(0.985); }

.btn svg { width: 17px; height: 17px; flex-shrink: 0; }

.btn--primary {
  background: linear-gradient(
    142deg,
    var(--color-accent) 0%,
    var(--color-primary) 58%,
    var(--color-primary-pressed) 100%
  );
  color: var(--color-on-primary);
  box-shadow: var(--elev-2), inset 0 1px 0 rgba(255, 255, 255, 0.24);
}

.btn--primary:hover {
  box-shadow: var(--elev-glow), inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

/**
 * Light crossing the fill on hover.
 *
 * Three things make this read as a glide rather than a flash:
 *
 *  1. EASING. It used --ease-out-expo, which covers most of its distance in
 *     the first fifth of the duration. That is right for something arriving
 *     and settling, and wrong for something travelling: the light appeared to
 *     jump, then crawl. A near-symmetric curve moves at a closer to constant
 *     speed, which is how light actually crosses a surface.
 *  2. SHAPE. The band was narrow with a hard peak, so it read as a stripe. It
 *     is now wider, dimmer, and falls off through intermediate stops, so it
 *     is a bloom passing over rather than an edge sliding across.
 *  3. IT NEVER RUNS BACKWARDS. On mouse-out the old version transitioned the
 *     position back, sweeping the light in reverse, which nothing physical
 *     does. Now the opacity fades and the position resets with a 0s duration
 *     delayed until after that fade has finished, so the reset is never seen.
 */
.btn--primary::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    100deg,
    transparent 10%,
    rgba(255, 255, 255, 0.06) 30%,
    rgba(255, 255, 255, 0.2) 50%,
    rgba(255, 255, 255, 0.06) 70%,
    transparent 90%
  );
  background-size: 260% 100%;
  background-position: 150% 0;
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease-in-out),
    background-position 0s linear var(--dur-mid);
}

.btn--primary:hover::after {
  opacity: 1;
  background-position: -60% 0;
  transition: opacity var(--dur-mid) var(--ease-in-out),
    background-position var(--dur-sweep) var(--ease-sweep);
}

.btn--secondary {
  background: var(--glass-fill-strong);
  -webkit-backdrop-filter: blur(14px) saturate(1.6);
  backdrop-filter: blur(14px) saturate(1.6);
  color: var(--color-text-primary);
  border-color: var(--glass-border);
  box-shadow: var(--elev-1), var(--glass-sheen);
}

.btn--secondary:hover {
  border-color: color-mix(in srgb, var(--color-primary) 40%, transparent);
  color: var(--color-brand-text);
  box-shadow: var(--elev-2), var(--glass-sheen);
}

.btn--ghost {
  background: transparent;
  color: var(--color-brand-text);
  border-color: color-mix(in srgb, var(--color-primary) 26%, transparent);
}

.btn--ghost:hover {
  background: color-mix(in srgb, var(--color-primary) 9%, transparent);
  border-color: var(--color-brand-text);
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  align-items: center;
}

/* ═══ BADGES ══════════════════════════════════════════════════════════════ */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.375rem var(--space-md);
  border-radius: var(--radius-pill);
  font-size: var(--text-tag);
  font-weight: 700;
  letter-spacing: 0.035em;
  white-space: nowrap;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.badge--brand {
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
  color: var(--color-brand-text);
  border: 1px solid color-mix(in srgb, var(--color-primary) 26%, transparent);
}

.badge--muted {
  background: var(--glass-fill);
  color: var(--color-text-secondary);
  border: 1px solid var(--glass-border-soft);
}

.badge--success {
  background: color-mix(in srgb, var(--color-success) 14%, transparent);
  color: var(--color-success);
  border: 1px solid color-mix(in srgb, var(--color-success) 34%, transparent);
}

.badge svg { width: 13px; height: 13px; }

/* ═══ FOOTER ══════════════════════════════════════════════════════════════ */
.site-footer {
  position: relative;
  margin-top: var(--space-section);
  padding-block: var(--space-section) var(--space-xxxl);
  border-top: 1px solid var(--color-border);
  background: linear-gradient(
    to bottom,
    transparent,
    color-mix(in srgb, var(--color-primary) 5%, transparent)
  );
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.5fr minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-huge);
  padding-bottom: var(--space-huge);
  border-bottom: 1px solid var(--color-border);
}

@media (max-width: 46rem) {
  .footer-grid {
    /* minmax(0, 1fr): the bare 1fr took its floor from the longest link label,
       which pushed the footer past the viewport once a reader enlarged text. */
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-xxxl);
  }
}

.footer-brand__blurb {
  margin-top: var(--space-lg);
  max-width: 26rem;
  font-size: var(--text-body);
  line-height: 1.62;
  color: var(--color-text-secondary);
}

.footer-col h2 {
  font-family: var(--font-body);
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-lg);
}

/* Was 23px tall — one pixel under the WCAG 2.5.8 minimum target size. */
.footer-col li + li { margin-top: var(--space-sm); }

.footer-col a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding-block: 2px;
  font-size: var(--text-body);
  color: var(--color-text-secondary);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out-expo),
    transform var(--dur-fast) var(--ease-out-expo);
}

.footer-col a:hover {
  color: var(--color-brand-text);
  transform: translateX(3px);
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  justify-content: space-between;
  align-items: center;
  padding-top: var(--space-xxl);
}

.footer-bottom p {
  font-size: var(--text-caption);
  color: var(--color-text-secondary);
}

/* ═══ LANGUAGE SWITCH ═════════════════════════════════════════════════════
   Sits beside the appearance control in the header. Two short codes rather
   than a dropdown: with exactly two languages a segmented control is faster
   to use and needs no JavaScript at all — each option is a plain link to the
   same page in the other language.                                          */
.lang-switch {
  display: inline-flex;
  flex-shrink: 0;
  padding: 3px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--glass-border-soft);
  background: color-mix(in srgb, var(--color-primary) 6%, transparent);
}

.lang-switch__option {
  display: grid;
  place-items: center;
  min-width: 2rem;
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-pill);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--color-text-secondary);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out-expo),
    background-color var(--dur-fast) var(--ease-out-expo);
}

a.lang-switch__option:hover {
  color: var(--color-text-primary);
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}

.lang-switch__option.is-active {
  background: var(--color-primary);
  color: var(--color-on-primary);
}

@media (max-width: 26rem) {
  .lang-switch__option { min-width: 1.75rem; padding-inline: 0.375rem; }
}

/* 07-pages.css */
/**
 * Page composition: home, closets, school detail, app, contact.
 */

/* ═══ HERO ════════════════════════════════════════════════════════════════ */
.hero {
  position: relative;
  /* The audience router follows immediately and is the most important thing
     on the page, so the hero hands off to it rather than trailing whitespace. */
  padding-block: clamp(2rem, 1rem + 3.4vw, 4rem) calc(var(--space-section) * 0.45);
}

.hero > .wrap {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: clamp(2rem, 1rem + 4vw, 5rem);
  align-items: center;
}

@media (max-width: 58rem) {
  .hero > .wrap {
    grid-template-columns: 1fr;
    gap: var(--space-huge);
    text-align: center;
  }
  .hero .eyebrow,
  .hero .btn-row,
  .hero__trust { justify-content: center; }
  .hero__body { margin-inline: auto; }
}

.hero h1 {
  margin-top: var(--space-lg);
  white-space: pre-line;
}

/* Hero entrance cascade — each line arrives just behind the one above it,
   so the eye is led down the column rather than hit with the whole block. */
.hero__copy > [data-enter]:nth-child(1) { --enter-delay: 40ms; }
.hero__copy > [data-enter]:nth-child(2) { --enter-delay: 130ms; }
.hero__copy > [data-enter]:nth-child(3) { --enter-delay: 230ms; }
.hero__copy > [data-enter]:nth-child(4) { --enter-delay: 330ms; }
.hero__copy > [data-enter]:nth-child(5) { --enter-delay: 420ms; }
.hero__art[data-enter] { --enter-delay: 180ms; }

.hero__body {
  margin-top: var(--space-xl);
  max-width: 33rem;
  font-size: var(--text-lead);
  line-height: 1.6;
  color: var(--color-text-secondary);
}

.hero__actions { margin-top: var(--space-xxl); }

/* Trust strip under the CTAs — the quiet credibility line. */
.hero__trust {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md) var(--space-xl);
  margin-top: var(--space-xl);
  font-size: var(--text-caption);
  font-weight: 600;
  color: var(--color-text-secondary);
}

.hero__trust li {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.hero__trust svg {
  width: 15px;
  height: 15px;
  color: var(--color-brand-text);
}

/* ── Hero composition ─────────────────────────────────────────────────── */
.hero__art {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  max-width: 30rem;
  margin-inline: auto;
  width: 100%;
  /* Depth for the tilt. Large value = subtle perspective, not a funhouse. */
  perspective: 1100px;
}

/**
 * The interactive mark.
 *
 * site.js writes the pointer's position into --mx/--my (0–1 across the box)
 * and --tilt-x/--tilt-y (degrees). Everything below is driven by those four
 * numbers, so the whole interaction is one rAF-throttled property write —
 * no per-frame layout, no JS animation loop.
 */
.hero__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg))
    scale(var(--mark-scale, 1));
  transition: transform var(--dur-slow) var(--ease-out-expo);
  will-change: transform;
}

/* While the pointer is over it, track immediately; on leave, ease back. */
.hero__art.is-pointing .hero__mark {
  transition: transform 90ms linear;
  --mark-scale: 1.03;
}

/* Rotating conic ring — the one large moving element on the page. */
.hero__ring {
  position: absolute;
  inset: 4%;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg,
    transparent 0%,
    color-mix(in srgb, var(--color-primary) 62%, transparent) 18%,
    transparent 38%,
    color-mix(in srgb, var(--color-accent) 52%, transparent) 62%,
    transparent 84%
  );
  /* Hollow it out into a ring rather than a disc. */
  -webkit-mask: radial-gradient(circle, transparent 70%, #000 71.5%, #000 77%, transparent 78.5%);
  mask: radial-gradient(circle, transparent 70%, #000 71.5%, #000 77%, transparent 78.5%);
  animation: spin-slow 26s linear infinite;
  will-change: transform;
  opacity: 0.9;
}

.hero__halo {
  position: absolute;
  inset: 14%;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--color-primary) 20%, transparent),
    transparent 68%
  );
  filter: blur(42px);
}

.hero__logo {
  position: relative;
  grid-area: 1 / 1;
  width: 68%;
  height: auto;
  /* A transparent cutout, so the shadow follows the artwork's own silhouette
     instead of a plate behind it. The offset shifts with the pointer, as if
     the light source moved. */
  filter: drop-shadow(
    calc(var(--shadow-dx, 0px)) calc(16px + var(--shadow-dy, 0px)) 34px
      rgba(139, 30, 45, 0.26)
  );
  transition: filter var(--dur-mid) var(--ease-out-expo);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .hero__logo {
    filter: drop-shadow(
      calc(var(--shadow-dx, 0px)) calc(16px + var(--shadow-dy, 0px)) 38px
        rgba(0, 0, 0, 0.6)
    );
  }
}

:root[data-theme='dark'] .hero__logo {
  filter: drop-shadow(
    calc(var(--shadow-dx, 0px)) calc(16px + var(--shadow-dy, 0px)) 38px
      rgba(0, 0, 0, 0.6)
  );
}

/**
 * The specular highlight.
 *
 * The trick that sells it: this layer is MASKED BY THE LOGO ITSELF, so the
 * light only ever falls on the artwork rather than on an invisible rectangle
 * around it. It tracks the pointer, so moving across the mark reads as moving
 * a light source over an embossed object.
 */
.hero__sheen {
  grid-area: 1 / 1;
  position: relative;
  width: 68%;
  aspect-ratio: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease-out-expo);

  background: radial-gradient(
    38% 38% at calc(var(--mx, 0.5) * 100%) calc(var(--my, 0.5) * 100%),
    rgba(255, 255, 255, 0.95),
    rgba(255, 232, 214, 0.45) 42%,
    transparent 68%
  );

  -webkit-mask-image: url('../img/logo.webp');
  mask-image: url('../img/logo.webp');
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;

  mix-blend-mode: soft-light;
}

.hero__art.is-pointing .hero__sheen {
  opacity: 1;
  transition: opacity var(--dur-fast) var(--ease-out-expo);
}

/* On dark the artwork is already luminous, so lift rather than wash out. */
:root[data-theme='dark'] .hero__sheen,
:root:not([data-theme='light']) .hero__sheen {
  mix-blend-mode: plus-lighter;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) .hero__sheen { mix-blend-mode: soft-light; }
}

/* A faint edge-light that rakes across from the opposite side, which is what
   stops the highlight reading as a flat white blob. */
.hero__art.is-pointing .hero__ring {
  opacity: 1;
  animation-duration: 14s;
}

.hero__ring {
  transition: opacity var(--dur-mid) var(--ease-out-expo);
}

.hero__art.is-pointing .hero__halo {
  opacity: 1.35;
  transform: scale(1.06);
}

.hero__halo {
  transition: transform var(--dur-slow) var(--ease-out-expo),
    opacity var(--dur-slow) var(--ease-out-expo);
}

/* Touch has no hover, and a tilt that never resets looks broken. */
@media (hover: none) {
  .hero__sheen { display: none; }
  .hero__mark { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero__mark { transform: none !important; transition: none; }
  .hero__sheen { display: none; }
}

/* ── Satellite chips orbiting the medallion ───────────────────────────────
   The slot is the thing that drifts and never moves in the layout. Its inner
   .hero__chip__face is what site.js swaps out, so a change of wording never
   interrupts the motion.                                                    */
.hero__chip {
  position: absolute;
  display: inline-flex;
  align-items: center;
  padding: 0.5rem var(--space-md);
  border-radius: var(--radius-pill);
  background: var(--glass-fill-strong);
  -webkit-backdrop-filter: blur(14px) saturate(1.7);
  backdrop-filter: blur(14px) saturate(1.7);
  border: 1px solid var(--glass-border);
  box-shadow: var(--elev-2), var(--glass-sheen);
  font-size: var(--text-caption);
  font-weight: 700;
  color: var(--color-text-primary);
  white-space: nowrap;
  animation: float-drift var(--drift-dur) var(--ease-in-out) infinite;
  animation-delay: var(--drift-delay);
  will-change: transform;
}

.hero__chip__face {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  transition: opacity var(--dur-mid) var(--ease-out-expo),
    transform var(--dur-mid) var(--ease-out-expo);
}

/* Held here while the text underneath is replaced, so the width change that
   comes with a longer or shorter label happens while nothing is visible. */
.hero__chip__face.is-swapping {
  opacity: 0;
  transform: translate3d(0, -7px, 0) scale(0.94);
}

.hero__chip svg { width: 14px; height: 14px; color: var(--color-brand-text); }

/* Negative delays start each chip partway through its loop, so they are already
   out of phase on the first frame rather than drifting apart over time. */
.hero__chip--1 {
  top: 6%;
  right: 2%;
  --drift-x: 13px;
  --drift-y: 15px;
  --drift-r: 1.1deg;
  --drift-dur: 13s;
  --drift-delay: 0s;
}

.hero__chip--2 {
  bottom: 16%;
  left: -2%;
  --drift-x: -15px;
  --drift-y: 12px;
  --drift-r: -0.9deg;
  --drift-dur: 16s;
  --drift-delay: -3.2s;
}

.hero__chip--3 {
  bottom: 2%;
  right: 12%;
  --drift-x: 11px;
  --drift-y: 17px;
  --drift-r: 1.4deg;
  --drift-dur: 14.5s;
  --drift-delay: -6.7s;
}

/* On the narrowest screens the chips carry the same labels in a much smaller
   box, and the longest of them ("Nueva y en buen estado") plus a full drift
   swing was reaching a pixel past the left edge. Pull the insets in and damp
   the drift so no label in either language can touch the viewport edge. */
@media (max-width: 30rem) {
  .hero__chip {
    font-size: 0.6875rem;
    padding: 0.375rem var(--space-sm);
  }

  .hero__chip--1 { right: 0; --drift-x: 8px; --drift-y: 10px; }
  .hero__chip--2 { left: 0; --drift-x: -8px; --drift-y: 9px; }
  .hero__chip--3 { right: 8%; --drift-x: 7px; --drift-y: 11px; }
}

/* ═══ MARQUEE ═════════════════════════════════════════════════════════════
   The school names, scrolling. Content is duplicated in the markup and the
   track shifts exactly -50%, so the loop has no seam.                      */
.marquee {
  position: relative;
  overflow: hidden;
  padding-block: var(--space-xl);
  border-block: 1px solid var(--color-border);
  mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}

.marquee__track {
  display: flex;
  width: max-content;
  gap: var(--space-huge);
  animation: marquee 38s linear infinite;
  will-change: transform;
}

.marquee:hover .marquee__track { animation-play-state: paused; }

.marquee__item {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  font-family: var(--font-serif);
  font-variation-settings: 'opsz' 32, 'WONK' 1;
  font-size: clamp(1rem, 0.9rem + 0.5vw, 1.375rem);
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--color-text-secondary);
  white-space: nowrap;
}

.marquee__item em {
  font-family: var(--font-body);
  font-size: 0.6875rem;
  font-style: normal;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-brand-text);
  opacity: 0.85;
  margin-left: 0.5rem;
}

.marquee__item::after {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--color-brand-text);
  opacity: 0.5;
}

@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  .marquee { mask-image: none; -webkit-mask-image: none; }
}

/* ═══ STEPS ═══════════════════════════════════════════════════════════════ */
.steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-xl);
}

@media (max-width: 58rem) {
  .steps { grid-template-columns: 1fr; }
}

.step {
  position: relative;
  padding: var(--space-xxl);
  overflow: hidden;
}

.step__number {
  font-family: var(--font-serif);
  font-variation-settings: 'opsz' 96, 'WONK' 1;
  font-size: 3rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.04em;
  background: linear-gradient(
    150deg,
    var(--color-brand-text),
    color-mix(in srgb, var(--color-brand-text) 26%, transparent)
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.step__icon {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-block: var(--space-xl) var(--space-lg);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-primary) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-primary) 18%, transparent);
  color: var(--color-brand-text);
  transition: transform var(--dur-mid) var(--ease-spring),
    background-color var(--dur-mid) var(--ease-out-expo);
}

.step:hover .step__icon {
  transform: translateY(-3px) scale(1.06);
  background: color-mix(in srgb, var(--color-primary) 18%, transparent);
}

.step__icon svg { width: 24px; height: 24px; }

.step h3 { margin-bottom: var(--space-md); }

.step p {
  font-size: var(--text-body);
  line-height: 1.6;
  color: var(--color-text-secondary);
}

/* ═══ MISSION + STATS ═════════════════════════════════════════════════════ */
.mission-grid {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(2rem, 1rem + 4vw, 5rem);
  align-items: center;
}

@media (max-width: 58rem) {
  .mission-grid { grid-template-columns: 1fr; }
}

.mission-title { margin-block: var(--space-lg) var(--space-xl); }

.mission-body p {
  font-size: var(--text-body-lg);
  line-height: 1.68;
  color: var(--color-text-secondary);
}

.mission-body p + p { margin-top: var(--space-xl); }

/* Bento: the first stat spans the full width, the rest pair up. */
.stats {
  display: grid;
  /* minmax(0, 1fr) so a long label cannot set the column's floor. The Spanish
     stats are longer than the English ones and pushed the page 18px wide at
     320px, which is the only place the two locales laid out differently. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg);
}

/* Below this the two columns cannot hold a stat without clipping its label. */
@media (max-width: 22rem) {
  .stats { grid-template-columns: minmax(0, 1fr); }
}

.stat {
  padding: var(--space-xxl);
  text-align: left;
}

.stat--feature { grid-column: 1 / -1; }

.stat--feature .stat__value {
  font-size: clamp(2.75rem, 1.8rem + 3.4vw, 4rem);
}

.stat__value {
  display: block;
  font-family: var(--font-serif);
  font-variation-settings: 'opsz' 96, 'WONK' 1;
  font-size: clamp(2.25rem, 1.6rem + 2.4vw, 3.25rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.04em;
  background: linear-gradient(140deg, var(--color-brand-text), var(--color-accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.stat__label {
  display: block;
  margin-top: var(--space-md);
  font-size: var(--text-body);
  font-weight: 500;
  color: var(--color-text-secondary);
}

/* ═══ CTA PANEL (donate, chapter recruitment, app download) ═══════════════ */
.cta-panel {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-huge);
  align-items: center;
  padding: clamp(1.75rem, 1.1rem + 2.6vw, 3.25rem);
  overflow: hidden;
}

@media (max-width: 52rem) {
  .cta-panel { grid-template-columns: 1fr; gap: var(--space-xxl); }
}

.cta-panel h2 {
  font-size: var(--text-section);
  font-variation-settings: 'opsz' 32, 'SOFT' 8, 'WONK' 0;
  font-weight: 600;
  letter-spacing: -0.014em;
  line-height: 1.22;
}

.cta-panel p {
  margin-top: var(--space-md);
  font-size: var(--text-body);
  line-height: 1.62;
  color: var(--color-text-secondary);
  max-width: 36rem;
}

.cta-panel .caption { margin-top: var(--space-md); }

/* ═══ CLOSETS INDEX ═══════════════════════════════════════════════════════ */
.closet-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  gap: var(--space-xl);
}

.closet-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--space-xxl);
  text-decoration: none;
  color: inherit;
  overflow: hidden;
}

.closet-card__badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  min-height: 1.75rem;
  margin-bottom: var(--space-lg);
}

.closet-card__mark {
  width: 40px;
  height: 40px;
  color: var(--color-brand-text);
  margin-bottom: var(--space-lg);
  transition: transform var(--dur-mid) var(--ease-spring);
}

.closet-card:hover .closet-card__mark {
  transform: translateY(-3px) rotate(-6deg);
}

.closet-card h2 {
  font-size: 1.3125rem;
  font-variation-settings: 'opsz' 32, 'SOFT' 8, 'WONK' 0;
  font-weight: 600;
  line-height: 1.24;
  letter-spacing: -0.018em;
  margin-bottom: var(--space-sm);
}

.closet-card__place {
  font-size: var(--text-body);
  color: var(--color-text-secondary);
}

.closet-card__go {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-label);
  font-weight: 700;
  color: var(--color-brand-text);
}

.closet-card__go svg {
  width: 15px;
  height: 15px;
  transition: transform var(--dur-mid) var(--ease-spring);
}

.closet-card:hover .closet-card__go svg { transform: translateX(5px); }

/* ═══ SCHOOL DETAIL ═══════════════════════════════════════════════════════ */
.school-hero { padding-block: var(--space-huge) var(--space-xxxl); }

.breadcrumb {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-xl);
  font-size: var(--text-label);
  font-weight: 600;
  color: var(--color-text-secondary);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out-expo);
}

.breadcrumb:hover { color: var(--color-brand-text); }

.breadcrumb svg {
  width: 15px;
  height: 15px;
  transition: transform var(--dur-fast) var(--ease-spring);
}

.breadcrumb:hover svg { transform: translateX(-4px); }

.school-hero__badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-bottom: var(--space-lg);
}

.school-hero__place {
  margin-top: var(--space-md);
  font-size: var(--text-lead);
  color: var(--color-text-secondary);
}

.school-hero__description {
  margin-top: var(--space-xl);
  max-width: var(--measure);
  font-size: var(--text-body-lg);
  line-height: 1.68;
  color: var(--color-text-secondary);
}

/* ── Map ──────────────────────────────────────────────────────────────── */
.map-layout {
  display: grid;
  grid-template-columns: 1.4fr 0.6fr;
  gap: var(--space-xl);
  align-items: start;
}

@media (max-width: 58rem) {
  .map-layout { grid-template-columns: 1fr; }
}

.map-card { overflow: hidden; }

.map-card__bar {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-lg) var(--space-xl);
  border-bottom: 1px solid var(--color-border);
  color: var(--color-brand-text);
  font-size: var(--text-label);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.map-card__bar svg { width: 17px; height: 17px; flex-shrink: 0; }

.map-card__frame {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--color-surface);
}

.map-card__frame iframe {
  width: 100%;
  height: 100%;
  border: 0;
  filter: var(--map-filter);
}

@media (max-width: 34rem) {
  .map-card__frame { aspect-ratio: 4 / 3; }
}

.map-card__attribution {
  padding: var(--space-sm) var(--space-xl);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-caption);
  color: var(--color-text-secondary);
}

.map-card__attribution a { color: inherit; }

.address-card { padding: var(--space-xxl); }

.address-card h3 {
  font-family: var(--font-body);
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-lg);
}

.address-card address {
  font-style: normal;
  font-size: var(--text-body-lg);
  line-height: 1.6;
  color: var(--color-text-primary);
}

.address-card .btn-row {
  margin-top: var(--space-xl);
  flex-direction: column;
  align-items: stretch;
}

.booth-note {
  margin-top: var(--space-xl);
  padding-top: var(--space-xl);
  border-top: 1px solid var(--color-border);
}

.booth-note dt {
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
}

.booth-note dd {
  margin: var(--space-xs) 0 var(--space-md);
  font-size: var(--text-body);
  color: var(--color-text-secondary);
}

/* ── Team ───────────────────────────────────────────────────────────────
   Flex rather than grid, so the final row centres.

   A team is whatever size it is, and an auto-fill grid leaves the remainder
   hard against the left edge: nine people in four columns gives 4, 4, then a
   short row of three with a hole where a fourth would be. Grid has no way to
   centre a partial row, because the empty track is still a track. Flex has
   nothing in the gap to begin with, so justify-content does the work, and it
   keeps doing it at any headcount and any width.

   Growth is capped so the short row does not stretch its cards wider than the
   full rows above it. Without the cap, three cards sharing a row meant for
   four simply grow to fill it, and the row reads as stretched rather than
   centred.                                                                  */
.team-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-lg);
}

.team-member {
  flex: 1 1 14rem;
  max-width: 17rem;
  padding: var(--space-xl);

  /* Row, so an optional social link can sit at the trailing edge without the
     name and role reflowing. Members with no link are unaffected: the text
     block simply takes the full width. */
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

/* min-width: 0 so a long name wraps inside the card instead of forcing the
   flex item wider and pushing the icon out of the bubble. */
.team-member__text {
  flex: 1;
  min-width: 0;
}

.team-member__social {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-pill);
  border: 1px solid color-mix(in srgb, var(--color-primary) 18%, transparent);
  background: color-mix(in srgb, var(--color-primary) 8%, transparent);
  color: var(--color-brand-text);
  transition: transform var(--dur-fast) var(--ease-spring),
    background-color var(--dur-fast) var(--ease-out-expo);
}

.team-member__social:hover {
  background: color-mix(in srgb, var(--color-primary) 16%, transparent);
  transform: translateY(-2px);
}

.team-member__social svg {
  width: 17px;
  height: 17px;
}

/* Leads take roughly the two columns they used to span. */
.team-member--lead {
  flex: 1 1 30rem;
  max-width: 35rem;
  background: color-mix(in srgb, var(--color-primary) 9%, var(--glass-fill));
}

.team-member__name {
  font-family: var(--font-serif);
  font-variation-settings: 'opsz' 24, 'SOFT' 8, 'WONK' 0;
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: 1.3;
  color: var(--color-text-primary);
}

.team-member--lead .team-member__name {
  font-size: 1.3125rem;
  color: var(--color-brand-text);
}

.team-member__role {
  margin-top: var(--space-xs);
  font-size: var(--text-body);
  line-height: 1.45;
  color: var(--color-text-secondary);
}

/* ── Gallery ──────────────────────────────────────────────────────────── */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--space-lg);
}

.gallery__item {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}

@media (min-width: 46rem) {
  .gallery__item:nth-child(3n + 1) { grid-column: span 2; }
}

.gallery__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  aspect-ratio: 4 / 3;
  transition: transform var(--dur-slow) var(--ease-out-expo);
}

.gallery__item:hover img { transform: scale(1.045); }

.gallery__caption {
  padding: var(--space-md) var(--space-lg);
  font-size: var(--text-caption);
  font-weight: 500;
  color: var(--color-text-secondary);
  border-top: 1px solid var(--color-border);
}

.empty-note {
  padding: var(--space-xxl);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-lg);
  font-size: var(--text-body);
  color: var(--color-text-secondary);
  text-align: center;
}

/* ═══ APP PAGE ════════════════════════════════════════════════════════════ */
.store-badge {
  display: inline-block;
  border-radius: 9px;
  transition: transform var(--dur-mid) var(--ease-spring),
    box-shadow var(--dur-mid) var(--ease-out-expo);
}

.store-badge:hover {
  transform: translateY(-3px);
  box-shadow: var(--elev-2);
}

.store-badge img { height: 52px; width: auto; }

.app-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xl);
  margin-top: var(--space-xxl);
  padding-top: var(--space-xl);
  border-top: 1px solid var(--color-border);
}

.app-meta div { min-width: 6rem; }

.app-meta dt {
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
}

.app-meta dd {
  margin: var(--space-xs) 0 0;
  font-size: var(--text-body);
  font-weight: 700;
  color: var(--color-text-primary);
}

.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space-lg);
}

.feature { padding: var(--space-xxl); }

.feature__icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-primary) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-primary) 18%, transparent);
  color: var(--color-brand-text);
  margin-bottom: var(--space-lg);
  transition: transform var(--dur-mid) var(--ease-spring);
}

.feature:hover .feature__icon { transform: translateY(-3px) scale(1.06); }

.feature__icon svg { width: 22px; height: 22px; }

.feature h3 {
  font-family: var(--font-body);
  font-size: var(--text-body-lg);
  font-weight: 700;
  letter-spacing: -0.005em;
  margin-bottom: var(--space-sm);
}

.feature p {
  font-size: var(--text-body);
  line-height: 1.6;
  color: var(--color-text-secondary);
}

/* ── Phone frames ─────────────────────────────────────────────────────── */
.shots {
  display: flex;
  gap: var(--space-xl);
  overflow-x: auto;
  padding-block: var(--space-lg) var(--space-xxl);
  padding-inline: 2px;
  scroll-snap-type: x mandatory;

  /* The strip only overflows once there are four screenshots, and at that
     point Chrome paints its default grey track — a hard-edged band under the
     phones that reads as a rendering artifact rather than a control.
     The scrollbar is worth KEEPING, though: it is the only cue that there are
     more screens off to the right, and hiding it means the later shots may
     never be seen. So the track goes transparent and only a brand-tinted thumb
     remains, which is the affordance without the artifact. */
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--color-primary) 32%, transparent)
    transparent;
}

/* WebKit needs the longhand; Firefox uses scrollbar-color above. */
.shots::-webkit-scrollbar {
  height: 8px;
}

.shots::-webkit-scrollbar-track {
  background: transparent;
}

.shots::-webkit-scrollbar-thumb {
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--color-primary) 32%, transparent);
}

.shots::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--color-primary) 52%, transparent);
}

.phone {
  flex: 0 0 auto;
  width: 15rem;
  scroll-snap-align: center;
  border-radius: 2.5rem;
  border: 9px solid color-mix(in srgb, var(--color-text-primary) 88%, transparent);
  background: var(--color-surface);
  overflow: hidden;
  box-shadow: var(--elev-3);
  aspect-ratio: 9 / 19.5;
  transition: transform var(--dur-mid) var(--ease-spring);
}

.phone:hover { transform: translateY(-8px); }

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

.phone--empty {
  display: grid;
  place-items: center;
  padding: var(--space-xxl);
  text-align: center;
  color: var(--color-text-secondary);
}

.phone--empty svg {
  width: 38px;
  height: 38px;
  margin-inline: auto;
  margin-bottom: var(--space-lg);
  opacity: 0.55;
}

.phone--empty p { font-size: var(--text-caption); line-height: 1.5; }

/* ═══ CONTACT ═════════════════════════════════════════════════════════════ */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--space-xl);
}

.contact-card {
  display: flex;
  flex-direction: column;
  padding: var(--space-xxl);
}

.contact-card__icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-primary) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-primary) 18%, transparent);
  color: var(--color-brand-text);
  margin-bottom: var(--space-lg);
  transition: transform var(--dur-mid) var(--ease-spring);
}

.contact-card:hover .contact-card__icon { transform: translateY(-3px) scale(1.06); }

.contact-card__icon svg { width: 22px; height: 22px; }

.contact-card h2 {
  font-family: var(--font-body);
  font-size: var(--text-body-lg);
  font-weight: 700;
  letter-spacing: -0.005em;
  margin-bottom: var(--space-sm);
}

.contact-card p {
  font-size: var(--text-body);
  line-height: 1.6;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-xl);
}

.contact-card__action {
  margin-top: auto;
  font-size: var(--text-label);
  font-weight: 700;
  color: var(--color-brand-text);
  text-decoration: none;
  word-break: break-word;
}

.contact-card__action:hover { text-decoration: underline; }

/* ═══ APP HERO ════════════════════════════════════════════════════════════
   The app icon rather than the brand logo, so this page does not visually
   echo the home page. Presented the way iOS presents it: a squircle tile with
   a real shadow under it.                                                    */
.app-hero__art {
  position: relative;
  display: grid;
  place-items: center;
  gap: var(--space-xl);
  margin-inline: auto;
  width: 100%;
  max-width: 24rem;
}

.app-icon-tile {
  position: relative;
  width: min(74%, 17rem);
  aspect-ratio: 1;
  border-radius: 23.5%; /* Apple's squircle sits close to this */
  overflow: hidden;
  box-shadow: var(--elev-3), 0 0 0 1px color-mix(in srgb, var(--color-primary) 12%, transparent);
  transition: transform var(--dur-mid) var(--ease-spring),
    box-shadow var(--dur-mid) var(--ease-out-expo);
}

.app-icon-tile:hover {
  transform: translateY(-6px) scale(1.02);
  box-shadow: var(--elev-glow), 0 0 0 1px color-mix(in srgb, var(--color-primary) 22%, transparent);
}

.app-icon {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The app ships an adaptive icon; show whichever matches the active theme. */
.app-icon--dark { display: none; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .app-icon--light { display: none; }
  :root:not([data-theme='light']) .app-icon--dark { display: block; }
}

:root[data-theme='dark'] .app-icon--light { display: none; }
:root[data-theme='dark'] .app-icon--dark { display: block; }
:root[data-theme='light'] .app-icon--light { display: block; }
:root[data-theme='light'] .app-icon--dark { display: none; }

.app-hero__caption {
  font-size: var(--text-caption);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--color-text-secondary);
}

.app-hero .hero__halo { inset: 8%; }

@media (max-width: 58rem) {
  .app-hero__art { order: -1; max-width: 16rem; }
}

/* ═══ MOTION CONTROL ══════════════════════════════════════════════════════
   WCAG 2.2.2 (Level A): content that moves automatically for more than five
   seconds needs a mechanism to pause it. prefers-reduced-motion is honoured
   separately, but it is an OS setting rather than a control on the page —
   this button is the control, and it stops every loop, not just the marquee. */
.marquee { position: relative; }

.marquee__pause {
  position: absolute;
  right: var(--page-gutter);
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--glass-border-soft);
  border-radius: var(--radius-pill);
  background: var(--glass-fill-strong);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--color-text-secondary);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out-expo),
    border-color var(--dur-fast) var(--ease-out-expo);
}

.marquee__pause:hover {
  color: var(--color-brand-text);
  border-color: color-mix(in srgb, var(--color-primary) 34%, transparent);
}

.marquee__pause svg { width: 14px; height: 14px; }

.marquee__pause-icon--play { display: none; }
:root[data-motion='paused'] .marquee__pause-icon--pause { display: none; }
:root[data-motion='paused'] .marquee__pause-icon--play { display: block; }

/* One switch stops every looping animation on the page. */
:root[data-motion='paused'] *,
:root[data-motion='paused'] *::before,
:root[data-motion='paused'] *::after {
  animation-play-state: paused !important;
}

/* With motion off there is nothing to pause, so the control is redundant. */
@media (prefers-reduced-motion: reduce) {
  .marquee__pause { display: none; }
}

/* ── "Want to start your own?" ─────────────────────────────────────────────
   A two-column panel rather than the shared .cta-panel, because this one has
   to answer a question the CTA panels do not: what a school actually receives
   for saying yes. The lede argues, the list answers, and the button sits under
   the argument so the eye finishes on the list rather than past it.          */
.expand-panel {
  display: grid;
  grid-template-columns: 1fr 0.85fr;
  gap: clamp(2rem, 1rem + 4vw, 4.5rem);
  align-items: center;
  padding: clamp(1.75rem, 1.1rem + 2.6vw, 3.25rem);
  overflow: hidden;
}

@media (max-width: 52rem) {
  .expand-panel {
    grid-template-columns: 1fr;
    gap: var(--space-xxl);
  }
}

.expand-panel__lede p {
  margin-top: var(--space-md);
  font-size: var(--text-body);
  line-height: 1.62;
  color: var(--color-text-secondary);
}

.expand-panel__lede .btn { margin-top: var(--space-xl); }

.expand-panel__provides h3 {
  font-size: var(--text-tag);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-lg);
}

.expand-panel__provides ul {
  display: grid;
  gap: var(--space-md);
}

.expand-panel__provides li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  font-size: var(--text-body);
  font-weight: 500;
  line-height: 1.45;
  color: var(--color-text-primary);
}

.expand-panel__provides svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  /* Optical alignment with the cap height of the first line, not its box. */
  margin-top: 0.2em;
  color: var(--color-brand-text);
}

/* 08-audience.css */
/**
 * The three-audience router, and the donate page it feeds.
 *
 * The site has to answer three different questions in one screen — "can I get
 * clothes", "can I start one of these", "how do I help" — so these cards are
 * sized to be scanned, not read: icon, who it is for, one plain-language
 * title, then the detail.
 */

/* ═══ AUDIENCE ROUTER ═════════════════════════════════════════════════════ */
.audience-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-xl);
}

@media (max-width: 62rem) {
  .audience-grid { grid-template-columns: 1fr; }
}

.audience-card {
  display: flex;
  flex-direction: column;
  padding: var(--space-xxl);
  overflow: hidden;
}

.audience-card__icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: var(--space-lg);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-primary) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-primary) 18%, transparent);
  color: var(--color-brand-text);
  transition: transform var(--dur-mid) var(--ease-spring),
    background-color var(--dur-mid) var(--ease-out-expo);
}

.audience-card:hover .audience-card__icon {
  transform: translateY(-3px) scale(1.06);
  background: color-mix(in srgb, var(--color-primary) 18%, transparent);
}

.audience-card__icon svg { width: 26px; height: 26px; }

.audience-card__eyebrow {
  font-size: var(--text-tag);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-sm);
}

.audience-card h3 {
  font-size: 1.375rem;
  font-variation-settings: 'opsz' 32, 'SOFT' 8, 'WONK' 0;
  font-weight: 600;
  letter-spacing: -0.018em;
  line-height: 1.2;
  margin-bottom: var(--space-md);
}

.audience-card__body {
  font-size: var(--text-body);
  line-height: 1.62;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-xl);
}

.audience-card__cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  margin-top: auto;
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border);
  font-size: var(--text-label);
  font-weight: 700;
  color: var(--color-brand-text);
  text-decoration: none;
}

.audience-card__cta svg {
  width: 15px;
  height: 15px;
  transition: transform var(--dur-mid) var(--ease-spring);
}

.audience-card:hover .audience-card__cta svg { transform: translateX(5px); }

/* ═══ IMPACT BANNER ═══════════════════════════════════════════════════════
   One number, given the room a headline gets. It is the single most
   persuasive thing on the donate page, so nothing competes with it.        */
.impact-banner {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-huge);
  align-items: center;
  padding: clamp(1.75rem, 1.1rem + 2.6vw, 3rem);
  margin-bottom: var(--space-huge);
}

@media (max-width: 52rem) {
  .impact-banner { grid-template-columns: 1fr; gap: var(--space-lg); }
}

.impact-banner__value {
  display: block;
  font-family: var(--font-serif);
  font-variation-settings: 'opsz' 144, 'WONK' 1;
  font-size: clamp(3rem, 2rem + 4.4vw, 5rem);
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.045em;
  background: linear-gradient(140deg, var(--color-brand-text), var(--color-accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* Tabular figures stop the number jittering while it counts up. */
  font-variant-numeric: tabular-nums;
}

.impact-banner__label {
  display: block;
  margin-top: var(--space-md);
  max-width: 22rem;
  font-size: var(--text-body);
  font-weight: 600;
  line-height: 1.5;
  color: var(--color-text-primary);
}

.impact-banner__note {
  font-size: var(--text-body);
  line-height: 1.62;
  color: var(--color-text-secondary);
  border-left: 2px solid color-mix(in srgb, var(--color-primary) 30%, transparent);
  padding-left: var(--space-xl);
}

@media (max-width: 52rem) {
  .impact-banner__note { border-left: 0; padding-left: 0; }
}

/* ═══ WAYS TO GIVE ════════════════════════════════════════════════════════ */
.give-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-xl);
}

@media (max-width: 62rem) {
  .give-grid { grid-template-columns: 1fr; }
}

.give-card {
  display: flex;
  flex-direction: column;
  padding: var(--space-xxl);
  overflow: hidden;
}

.give-card__icon {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-lg);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-primary) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-primary) 18%, transparent);
  color: var(--color-brand-text);
  transition: transform var(--dur-mid) var(--ease-spring);
}

.give-card:hover .give-card__icon { transform: translateY(-3px) scale(1.06); }

.give-card__icon svg { width: 24px; height: 24px; }

.give-card h2 {
  font-size: 1.25rem;
  font-variation-settings: 'opsz' 32, 'SOFT' 8, 'WONK' 0;
  font-weight: 600;
  letter-spacing: -0.016em;
  line-height: 1.24;
  margin-bottom: var(--space-md);
}

.give-card p {
  font-size: var(--text-body);
  line-height: 1.62;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-xl);
}

.give-card__cta {
  margin-top: auto;
  width: 100%;
  text-align: center;
}

/* ═══ WHAT WE NEED ════════════════════════════════════════════════════════ */
.needs-layout {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: clamp(2rem, 1rem + 4vw, 5rem);
  align-items: center;
}

@media (max-width: 58rem) {
  .needs-layout { grid-template-columns: 1fr; }
}

.needs-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-md) var(--space-xl);
  padding: var(--space-xxl);
}

@media (max-width: 34rem) {
  .needs-list { grid-template-columns: 1fr; }
}

.needs-list li {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  font-size: var(--text-body);
  font-weight: 500;
  color: var(--color-text-primary);
}

.needs-list svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--color-brand-text);
}

/* ═══ TRANSPARENCY ════════════════════════════════════════════════════════
   Where the money goes, stated plainly. Parents look for this, and a page
   that dodges it reads worse than one that admits the limits.              */
.transparency {
  padding: clamp(1.75rem, 1.1rem + 2.4vw, 2.75rem);
  max-width: var(--measure-wide);
  border-left: 3px solid var(--color-brand-text);
}

.transparency h2 {
  font-size: var(--text-section);
  font-variation-settings: 'opsz' 32, 'SOFT' 8, 'WONK' 0;
  font-weight: 600;
  letter-spacing: -0.014em;
  margin-bottom: var(--space-lg);
}

.transparency p {
  font-size: var(--text-body);
  line-height: 1.68;
  color: var(--color-text-secondary);
}

.transparency p + p { margin-top: var(--space-md); }

.transparency__note {
  margin-top: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border);
}

/* ═══ ONLINE GIVING ═══════════════════════════════════════════════════════
   A panel that links out to HCB's hosted donation page. Deliberately not an
   embed: Apple Pay and Google Pay run on the Payment Request API, which
   browsers block inside cross-origin iframes unless the parent grants
   allow="payment" — so framing it risks silently killing the wallet buttons.
   Nothing about payment is implemented on this site, which is what keeps card
   data out of scope entirely.                                               */
.giving-layout {
  max-width: 44rem;
  margin-inline: auto;
}

.giving-secure,
.giving-ledger {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
  font-size: var(--text-caption);
  line-height: 1.5;
  color: var(--color-text-secondary);
  text-align: center;
}

.giving-secure svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  color: var(--color-success);
}

.giving-ledger a {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-weight: 600;
  color: var(--color-brand-text);
  text-decoration: none;
}

.giving-ledger a:hover { text-decoration: underline; }
.giving-ledger svg { width: 13px; height: 13px; }

/* Live panel, and the "opening soon" state shown until giving.slug is set.
   Same shape either way, so the section never jumps when giving goes live. */
.giving-panel,
.giving-pending {
  display: grid;
  justify-items: center;
  gap: var(--space-lg);
  padding: clamp(2rem, 1.3rem + 2.6vw, 3.25rem);
  text-align: center;
}

.giving-panel__icon,
.giving-pending__icon {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-primary) 11%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-primary) 18%, transparent);
  color: var(--color-brand-text);
}

.giving-panel__icon svg,
.giving-pending__icon svg { width: 28px; height: 28px; }

.giving-pending h3 {
  font-size: var(--text-section);
  font-variation-settings: 'opsz' 32, 'SOFT' 8, 'WONK' 0;
  font-weight: 600;
  letter-spacing: -0.014em;
}

.giving-pending p {
  max-width: 34rem;
  font-size: var(--text-body);
  line-height: 1.62;
  color: var(--color-text-secondary);
}

.giving-methods {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm) var(--space-md);
}

.giving-methods li {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.375rem var(--space-md);
  border-radius: var(--radius-pill);
  background: var(--glass-fill);
  border: 1px solid var(--glass-border-soft);
  font-size: var(--text-caption);
  font-weight: 600;
  color: var(--color-text-primary);
}

.giving-methods svg { width: 14px; height: 14px; color: var(--color-brand-text); }

.giving-panel__cta { margin-top: var(--space-sm); }

.giving-panel .giving-secure {
  max-width: 30rem;
  margin-top: 0;
}

/* 09-legal.css */
/**
 * Legal documents. These are long, dense, and legally load-bearing, so the
 * treatment here is deliberately quieter than the rest of the site: no glass
 * over body copy, generous measure, and a sticky contents rail.
 */

.legal-layout {
  display: grid;
  grid-template-columns: 15.5rem minmax(0, 1fr);
  gap: clamp(2rem, 1rem + 3vw, 4.5rem);
  align-items: start;
}

@media (max-width: 62rem) {
  /* minmax(0, 1fr), not 1fr. A bare 1fr track takes its minimum from the widest
     unbreakable token inside it, which here is the support address at 317px, so
     the column would not shrink below that and body { overflow-x: hidden }
     clipped the overflow with no scrollbar to recover it. The desktop rule
     above already gets this right; this one was the oversight. */
  .legal-layout { grid-template-columns: minmax(0, 1fr); }
}

.legal-toc {
  position: sticky;
  top: calc(var(--header-height) + var(--space-xl));
  max-height: calc(100vh - var(--header-height) - var(--space-huge));
  overflow-y: auto;
  padding: var(--space-xl);
  scrollbar-width: thin;
}

@media (max-width: 62rem) {
  .legal-toc { position: static; max-height: none; }
}

.legal-toc__title {
  font-family: var(--font-body);
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  margin-bottom: var(--space-lg);
}

.legal-toc li + li { margin-top: 2px; }

.legal-toc a {
  display: block;
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
  font-size: var(--text-caption);
  line-height: 1.45;
  color: var(--color-text-secondary);
  text-decoration: none;
  border-left: 2px solid transparent;
  transition: color var(--dur-fast) var(--ease-out-expo),
    background-color var(--dur-fast) var(--ease-out-expo),
    border-color var(--dur-fast) var(--ease-out-expo);
}

.legal-toc a:hover {
  color: var(--color-brand-text);
  background: color-mix(in srgb, var(--color-primary) 7%, transparent);
}

/* Set by the scroll observer in site.js, so no inline styles are needed and
   the CSP can stay at style-src 'self'. */
.legal-toc a.is-current {
  color: var(--color-brand-text);
  font-weight: 700;
  border-left-color: var(--color-brand-text);
  background: color-mix(in srgb, var(--color-primary) 9%, transparent);
}

.legal-doc { max-width: var(--measure-wide); }

.legal-title { margin-block: var(--space-lg) var(--space-xl); }

.legal-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-bottom: var(--space-xxl);
}

.legal-preamble {
  padding: var(--space-xxl);
  margin-bottom: var(--space-huge);
  border-left: 3px solid var(--color-brand-text);
}

.legal-preamble p {
  font-size: var(--text-body);
  line-height: 1.65;
  color: var(--color-text-secondary);
}

.legal-preamble p + p { margin-top: var(--space-md); }

.legal-intro p {
  font-size: var(--text-body-lg);
  line-height: 1.72;
  color: var(--color-text-secondary);
}

.legal-intro p + p { margin-top: var(--space-lg); }

.legal-section {
  margin-top: var(--space-huge);
  scroll-margin-top: calc(var(--header-height) + var(--space-xxl));
}

.legal-section h2 {
  font-size: 1.375rem;
  font-variation-settings: 'opsz' 32, 'SOFT' 8, 'WONK' 0;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.018em;
  color: var(--color-brand-text);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-lg);
}

.legal-section h3 {
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 700;
  letter-spacing: 0;
  color: var(--color-text-primary);
  margin-top: var(--space-xl);
  margin-bottom: var(--space-sm);
}

.legal-section p {
  font-size: var(--text-body-lg);
  line-height: 1.72;
  color: var(--color-text-secondary);
}

.legal-section p + p { margin-top: var(--space-lg); }

.legal-section ul {
  margin: var(--space-md) 0 var(--space-lg);
  padding-left: var(--space-sm);
}

.legal-section li {
  position: relative;
  padding-left: var(--space-xl);
  font-size: var(--text-body-lg);
  line-height: 1.66;
  color: var(--color-text-secondary);
}

.legal-section li + li { margin-top: var(--space-md); }

.legal-section li::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 0.62em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-brand-text);
  opacity: 0.55;
}

.legal-footer {
  margin-top: var(--space-huge);
  padding-top: var(--space-xxl);
  border-top: 1px solid var(--color-border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  justify-content: space-between;
  align-items: center;
}

.lang-toggle {
  display: inline-flex;
  padding: 3px;
  border-radius: var(--radius-pill);
  background: var(--glass-fill);
  border: 1px solid var(--glass-border-soft);
}

.lang-toggle a {
  padding: 0.375rem var(--space-md);
  border-radius: var(--radius-pill);
  font-size: var(--text-caption);
  font-weight: 700;
  color: var(--color-text-secondary);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out-expo),
    color var(--dur-fast) var(--ease-out-expo);
}

.lang-toggle a:hover { color: var(--color-text-primary); }

.lang-toggle a[aria-current='true'] {
  background: var(--color-primary);
  color: var(--color-on-primary);
}

@media print {
  .site-header,
  .nav-mobile,
  .site-footer,
  .legal-toc,
  .lang-toggle,
  .skip-link,
  .atmosphere {
    display: none !important;
  }

  .legal-layout { grid-template-columns: minmax(0, 1fr); }

  body { background: #fff; color: #000; }
}

/* An email address or a URL is one unbreakable token to the layout engine, and
   these documents are full of both. Without this, a reader who bumps their text
   size one notch loses up to 47px off the right of every paragraph on a small
   phone: a Reflow and Resize Text failure on the two documents a sponsor or a
   district is most likely to read end to end. */
.legal-doc,
.legal-section p,
.legal-section li {
  overflow-wrap: anywhere;
}
