/* ==========================================================================
   The Capital Workforce Innovation Consortium
   Single stylesheet. Order (docs/08_ARCHITECTURE.md):
     1. fonts  2. tokens  3. reset/base  4. typography
     5. layout primitives  6. components  7. sections  8. utilities
   Tokens are the approved set in docs/05_DESIGN_SYSTEM.md (Malena, 2026-08-25).
   Do not introduce colours, sizes or easings that are not declared in :root.
   ========================================================================== */

/* 1. FONTS ================================================================
   Self-hosted WOFF2, latin subset only. The site's content is ASCII, so the
   latin-ext subsets were dropped to stay inside the docs/05 budget.

   Open Sauce One carries the titles (Malena, 2026-08-31: the team rejected
   Bricolage Grotesque and asked for the face Skills Nation uses). It is the
   same pair of static instances that site ships — 600 for headings, 700 for
   the display numbers — copied from SkillsNation/public/fonts. Two weights,
   not a variable file, so every weight declared against --font-display must
   be one of those two or the browser will round it silently. SIL OFL 1.1,
   see fonts/OpenSauceOne-LICENSE.txt.

   Inter carries body copy and is preloaded alongside Open Sauce 600.

   Bricolage Grotesque and Red Hat Display are both still bundled and
   declared, and neither is applied by anything. A declared-and-unused
   @font-face is never fetched, so each costs 0 bytes on the page: Bricolage
   is kept so the previous titles are one token away if the team wants them
   back, Red Hat Display because it is the brand kit's own face and Malena
   asked for it to stay available via --font-brand. */

@font-face {
  font-family: 'Open Sauce One';
  src: url('../fonts/OpenSauceOne-latin-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Open Sauce One';
  src: url('../fonts/OpenSauceOne-latin-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Bricolage Grotesque';
  src: url('../fonts/bricolage-grotesque-latin.woff2') format('woff2');
  font-weight: 400 800;
  font-stretch: 100%;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  src: url('../fonts/inter-latin.woff2') format('woff2');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Red Hat Display';
  src: url('../fonts/RedHatDisplay-latin.woff2') format('woff2');
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Red Hat Display';
  src: url('../fonts/RedHatDisplay-latin-ext.woff2') format('woff2');
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* 2. TOKENS =============================================================== */

:root {
  /* --- Colour — OFFICIAL BRAND KIT (Malena, 2026-08-25) -------------------
     The six values below are the brand kit exactly as supplied. Do not
     substitute or "adjust" them. --blue-900 is the same #013DB0 that the
     logo SVGs are drawn in, so the logo now sits inside the palette rather
     than beside it. */
  --blue-900: #013db0;   /* Dark Blue  — brand bands, footer, buttons, links */
  --blue-500: #2c7cf5;   /* Full Blue  — display-size text, fills, borders    */
  --blue-100: #d0e7ff;   /* Light Blue — tint bands, chips, eyebrow on brand  */
  --pink-500: #ff5f7c;   /* Full Pink  — accents; as a fill it takes INK text */
  --pink-100: #ffddee;   /* Light Pink — alternate tint band                  */
  --ink:      #292929;   /* Black      — body text                           */

  /* Neutrals the kit does not define, derived so the palette stays closed:
     --ink-muted is Black lightened until it still clears AA on all four light
     grounds — paper 6.55, cream 6.15, Light Blue 5.44, Light Pink 5.53;
     --blue-900-dark is Dark Blue at 85% for the primary-button hover, because
     every lighter option fails against white. Neither is a new brand colour. */
  --ink-muted: #5a5a5a;
  --blue-900-dark: #013496;
  /* Azul del footer, pedido por Malena. No es un color inventado: es el mismo
     #012C80 con el que viene dibujada la sombra del gráfico oficial del kit
     (Group 1536), así que ya pertenece a la familia. Al ser más oscuro que
     --blue-900 sube el contraste de todo el footer. */
  --blue-950: #012c80;

  /* Warm neutrals KEPT from the approved 2026-08-25 tokens — Malena likes
     them and they stay as the page grounds. The brand kit supplies no
     background colour, so these are not in conflict with it. */
  --paper:     #faf9f6;   /* page background  */
  --cream:     #f4f2ea;   /* alternate band   */
  --line:      #e3e1d9;                    /* warm hairline, matches paper */
  --line-dark: rgba(255, 255, 255, 0.24);  /* hairline on brand blue */
  --white:     #ffffff;                    /* cards on cream */
  --error:     #c0392b;                    /* 5.17 paper / 4.85 cream */
  --success:   #1b7247;                    /* darkened: #1E7F4F was 4.46 on cream */

  /* Per-band theming. Sections override these; components read them, so a
     component works on any band without knowing which band it is on.

     CONTRAST RULES that the palette forces — all measured, see docs/05:
       · White on Full Blue is 3.96:1 and white on Full Pink is 2.92:1, so
         NEITHER can carry white text at body size. Dark Blue is the button
         fill and the text accent everywhere (8.69 on paper, 8.16 on cream).
       · Full Blue and Full Pink are display/decoration only — as text they
         reach just 3.76 and 2.78 on paper. A pink fill takes --ink (4.97:1),
         never white.
       · On the brand-blue band the accent flips to Light Blue (7.21:1). */
  --bg: var(--paper);
  --fg: var(--ink);
  --fg-muted: var(--ink-muted);
  --rule: var(--line);
  --accent: var(--blue-900);
  --accent-hover: var(--blue-900-dark);

  /* --- Type families ---
     Open Sauce One + Inter are the pairing. Red Hat Display falls in behind
     the display face rather than sitting on a shelf: if Open Sauce ever fails
     to load, the titles land on the brand kit's own face instead of on
     Segoe UI. It stays reachable on its own through --font-brand (Malena:
     "no será la principal, pero lo quiero tener disponible").
     --font-legacy-display is the pre-2026-08-31 title face, kept for one
     token's worth of undo. Nothing references it, so it is never fetched. */
  --font-display: 'Open Sauce One', 'Red Hat Display', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-brand: 'Red Hat Display', 'Segoe UI', system-ui, sans-serif;
  --font-legacy-display: 'Bricolage Grotesque', 'Segoe UI', system-ui, sans-serif;

  /* --- Type scale. clamp() interpolates 375px -> 1440px viewports;
     the min/max are the phone/desktop sizes in docs/05. --- */
  --t-hero:     clamp(2.375rem, 1.627rem + 3.19vw, 4.5rem);      /* 38 -> 72 */
  --t-h2:       clamp(1.875rem, 1.567rem + 1.31vw, 2.75rem);     /* 30 -> 44 */
  --t-h3:       clamp(1.375rem, 1.243rem + 0.56vw, 1.75rem);     /* 22 -> 28 */
  --t-stat:     clamp(2.5rem, 1.972rem + 2.25vw, 4rem);          /* 40 -> 64 */
  --t-body-lg:  clamp(1.0625rem, 1.019rem + 0.19vw, 1.1875rem);  /* 17 -> 19 */
  --t-body:     clamp(1rem, 0.989rem + 0.05vw, 1.03125rem);      /* 16 -> 16.5 */
  --t-eyebrow:  0.875rem;    /* 14px — mixed case, never uppercase (Kora rule) */
  --t-small:    0.84375rem;  /* 13.5px — compliance/footer floor, never lower */

  --lh-hero: 1.05;
  --lh-heading: 1.1;
  --lh-body: 1.6;
  /* One tracking value for every size of the display face, not a per-level
     ramp (Malena, 2026-08-31: "-1% a todos los que usen esa nueva font").
     The old ramp — hero -0.03em, h2 -0.02em, h3 -0.01em — was cut for
     Bricolage Grotesque, whose wide letterforms needed pulling in harder at
     72px. Open Sauce One is already tight, so the ramp read as three
     different typefaces down the page.

     Body copy is unaffected with one exception worth knowing: .ticker__title
     ("Our Partners") is an h2 that overrides the family to Inter but not the
     tracking, so it inherits this value and moved from -0.02em to -0.01em —
     0.14px at its 14px size. .eyebrow and .footer-heading declare their own
     positive tracking and are untouched. */
  --tr-display: -0.01em;

  /* --- Spacing --- */
  --s1: 4px;   --s2: 8px;   --s3: 12px;  --s4: 16px;  --s5: 24px;
  --s6: 32px;  --s7: 48px;  --s8: 64px;  --s9: 96px;  --s10: 128px;

  /* --- Layout --- */
  --maxw: 1200px;
  --maxw-prose: 720px;
  --pad-x: var(--s5);           /* 24px mobile; 40px from 1024 up */
  --band-y: clamp(3.5rem, 2rem + 6vw, 7.5rem);

  /* Sombra de elevación al hacer hover. DESVÍO CONSCIENTE de docs/05, que
     define el sistema como plano ("Shadows: none... Depth = borders + band
     contrast"). Malena la pidió expresamente tomando como referencia las
     tarjetas de RVA Builds. Misma geometría que allá (desplazada abajo,
     difuminado amplio, spread negativo: eso es lo que da el despegue), pero
     teñida con el ink del kit en vez del marrón cálido de RVA. Se usa solo
     en el hover de .card, en ningún estado de reposo. */
  --shadow-lift: 0 18px 40px -22px rgba(41, 41, 41, 0.35);

  /* --- Radii & borders. Flat by default: depth comes from hairlines and band
     contrast. The only exception is --shadow-lift on card hover. --- */
  --r-sm: 8px;
  --r-md: 16px;
  --r-lg: 28px;
  --r-pill: 999px;
  --focus-ring: 2px solid var(--blue-900);
  --focus-offset: 2px;

  /* --- Motion (docs/06). Ordina's confirmed curve. --- */
  --ease: cubic-bezier(0.6, 0, 0.4, 1);
  --dur-fast: 200ms;
  --dur-reveal: 500ms;

  /* --- Z-index --- */
  --z-nav: 100;
  --z-menu: 200;
  --z-toast: 300;
}

@media (min-width: 1024px) {
  :root { --pad-x: 40px; }
}

/* 3. RESET / BASE ========================================================= */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

/* Anchor targets must clear the sticky header. */
:target { scroll-margin-top: 6rem; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img,
picture,
svg,
video {
  display: block;
  max-width: 100%;
  height: auto;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

/* One focus treatment everywhere. Never remove it without a replacement. */
:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
  border-radius: var(--r-sm);
}

/* 4. TYPOGRAPHY =========================================================== */

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--fg, var(--ink));
}

h1, .t-hero {
  font-size: var(--t-hero);
  line-height: var(--lh-hero);
  letter-spacing: var(--tr-display);
}

h2, .t-h2 {
  font-size: var(--t-h2);
  line-height: var(--lh-heading);
  letter-spacing: var(--tr-display);
}

/* h4-h6 incluidos a propósito: sin ellos heredan el line-height 1.6 del
   cuerpo, que a tamaño de titular abre las líneas un 33% de más. Es lo que
   pasaba en las tarjetas de estrategias. */
h3, h4, h5, h6, .t-h3 {
  font-size: var(--t-h3);
  line-height: 1.2;
  letter-spacing: var(--tr-display);
}

p { margin: 0; }

p + p { margin-top: var(--s4); }

a { color: var(--accent); }

a:hover { color: var(--accent-hover); }

.lede {
  font-size: var(--t-body-lg);
  line-height: 1.55;
  color: var(--fg-muted);
  max-width: 56ch;
}

/* Mixed case by deliberate rule — the references' eyebrows are never
   uppercased, and uppercase hurts screen-reader pronunciation. */
.eyebrow {
  font-family: var(--font-body);
  font-size: var(--t-eyebrow);
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.4;
  color: var(--accent);
  text-transform: none;
}

.t-stat {
  font-family: var(--font-display);
  font-size: var(--t-stat);
  /* 700, not the 650 Bricolage carried: Open Sauce One ships as two static
     instances here, so 650 would resolve to 700 anyway. Declared, not left
     to the font-matching algorithm. */
  font-weight: 700;
  line-height: 1;
  letter-spacing: var(--tr-display);
}

.t-small {
  font-size: var(--t-small);
  line-height: 1.5;
}

/* 5. LAYOUT PRIMITIVES ==================================================== */

.container {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--pad-x);
}

.prose { max-width: var(--maxw-prose); }

/* A band is one full-width horizontal section. Themes swap the four
   context tokens so nested components need no band-specific rules. */
.band {
  background: var(--bg);
  color: var(--fg);
  padding-block: var(--band-y);
}

.band--paper {
  --bg: var(--paper); --fg: var(--ink); --fg-muted: var(--ink-muted);
  --rule: var(--line); --accent: var(--blue-900); --accent-hover: var(--blue-900-dark);
}

.band--cream {
  --bg: var(--cream); --fg: var(--ink); --fg-muted: var(--ink-muted);
  --rule: var(--line); --accent: var(--blue-900); --accent-hover: var(--blue-900-dark);
}

/* Light Blue tint band. Ink on it 11.47:1, Dark Blue on it 7.21:1. */
.band--tint {
  --bg: var(--blue-100); --fg: var(--ink); --fg-muted: var(--ink-muted);
  --rule: rgba(1, 61, 176, 0.14); --accent: var(--blue-900); --accent-hover: var(--blue-900-dark);
}

/* Light Pink tint band. Ink on it 11.66:1, Dark Blue on it 7.33:1. */
.band--pink {
  --bg: var(--pink-100); --fg: var(--ink); --fg-muted: var(--ink-muted);
  --rule: rgba(1, 61, 176, 0.14); --accent: var(--blue-900); --accent-hover: var(--blue-900-dark);
}

/* Brand blue band. The accent flips to Light Blue, the only value that
   clears AA against Dark Blue (7.21:1). */
.band--brand {
  --bg: var(--blue-900); --fg: var(--white); --fg-muted: rgba(255, 255, 255, 0.82);
  --rule: var(--line-dark); --accent: var(--blue-100); --accent-hover: var(--white);
}

/* The standard section opener: eyebrow -> heading -> lede. */
.section-head > * + * { margin-top: var(--s3); }
.section-head .lede { margin-top: var(--s4); }

.grid { display: grid; gap: var(--s5); }

/* 6. COMPONENTS =========================================================== */

/* --- Skip link: first focusable element on every page ------------------- */
.skip-link {
  position: absolute;
  left: var(--s4);
  top: var(--s4);
  z-index: calc(var(--z-menu) + 1);
  transform: translateY(-200%);
  background: var(--blue-900);
  color: var(--white);
  padding: 12px 20px;
  border-radius: var(--r-sm);
  font-weight: 600;
  text-decoration: none;
}

.skip-link:focus-visible {
  transform: none;
  color: var(--white);
}

/* --- Buttons ------------------------------------------------------------
   Pill, 44px minimum target. The primary fill is --blue-900 (Dark Blue),
   NOT --blue-500: white on Full Blue measures only 3.96:1 and fails AA at
   this size. On the brand band the roles invert to blue-on-white. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 44px;
  padding: 12px 26px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.25;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}

.btn--primary {
  background: var(--blue-900);
  color: var(--white);
}

.btn--secondary {
  background: transparent;
  color: var(--fg);
  border-color: currentColor;
}

/* On navy the primary button becomes navy-on-white for contrast. */
.band--brand .btn--primary,
.site-footer .btn--primary {
  background: var(--white);
  color: var(--blue-900);
}

@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover { background: var(--blue-900-dark); color: var(--white); }
  .band--brand .btn--primary:hover { background: var(--blue-100); color: var(--blue-900); }
  .btn--secondary:hover { border-color: var(--accent); color: var(--accent); }
}

/* --- Card --------------------------------------------------------------- */
.card {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s5);
  background: var(--white);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  transition: border-color var(--dur-fast) var(--ease),
              translate 350ms var(--ease),
              box-shadow 350ms var(--ease);
}

.band--brand .card {
  background: transparent;
  border-color: var(--line-dark);
}

/* --- Numeral de tarjeta ------------------------------------------------
   El número se llena de abajo hacia arriba al pasar el cursor. Portado del
   efecto de RVA Builds (rva-builds-site/public/styles.css, .step-card-num),
   que es la referencia que Malena aprobó.

   Cómo funciona: un degradado del DOBLE de alto con un corte duro al 50%
   (arriba el tono pálido, abajo el azul de marca), recortado al texto. Mover
   background-position de `0 0%` a `0 100%` desliza ese corte por las cifras,
   así que el número parece rellenarse. `width: max-content` es imprescindible:
   sin eso el degradado abarca todo el ancho del bloque y el barrido se ve
   descolocado.

   El relleno solo entra en dispositivos con cursor real. Fuera de esa media
   query el número se pinta directamente en azul de marca, así que en táctil
   no se queda para siempre en el tono pálido, que se vería desvaído.

   El número es decorativo (aria-hidden en el markup), así que no necesita
   equivalente de foco: no hay nada enfocable en estas tarjetas. Bajo
   prefers-reduced-motion el bloque global anula la transición y el relleno
   pasa a ser instantáneo, que es el comportamiento correcto. */
.card__index {
  font-family: var(--font-display);
  font-size: var(--t-stat);
  /* 700, not the 650 Bricolage carried: Open Sauce One ships as two static
     instances here, so 650 would resolve to 700 anyway. Declared, not left
     to the font-matching algorithm. */
  font-weight: 700;
  line-height: 1;
  letter-spacing: var(--tr-display);
  width: max-content;
  margin-bottom: var(--s4);
  color: var(--accent);
}

@media (hover: hover) and (pointer: fine) {
  /* Con `background-clip: text` el texto va en transparente, así que si el
     navegador no lo soporta el número desaparecería. De ahí el @supports. */
  @supports ((-webkit-background-clip: text) or (background-clip: text)) {
    .card__index {
      background-image: linear-gradient(
        to top,
        var(--accent) 50%,
        var(--blue-100) 50%
      );
      background-size: 100% 200%;
      background-position: 0 0%;
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
      color: transparent;
      transition: background-position 550ms var(--ease);
    }

    .card:hover .card__index { background-position: 0 100%; }
  }
}

.card__body {
  color: var(--fg-muted);
  font-size: 0.9375rem;
}

@media (hover: hover) and (pointer: fine) {
  /* Se aplica a .card, no solo a .card--interactive: el efecto que pidió
     Malena para las tarjetas de estrategias es el mismo que ya tenían las
     tarjetas enlazables, y tener dos lenguajes de hover distintos en la misma
     página se nota. */
  .card:hover {
    border-color: var(--accent);
    /* `translate` y no `transform`: las tarjetas llevan .reveal, y
       `.js-motion .reveal.in` declara `transform: none` con especificidad
       (0,3,0) frente a los (0,2,0) de `.card:hover`. Con transform el
       desplazamiento se perdía en cuanto la tarjeta estaba revelada y solo
       quedaban la sombra y el borde. `translate` es una propiedad aparte que
       se compone con transform, así que no hay pelea de especificidad. */
    translate: 0 -6px;
    box-shadow: var(--shadow-lift);
  }
}

/* A whole card is one tab stop: the title link is stretched over it. */
.card--interactive { position: relative; }

.card__link {
  color: inherit;
  text-decoration: none;
}

.card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.card--interactive:focus-within {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.card__link:focus-visible { outline: none; }

/* --- Site header --------------------------------------------------------
   Sticky, plain until scrolled. The .scrolled class is toggled by an
   IntersectionObserver on a 1px sentinel, so there is no scroll listener. */
.header-sentinel {
  position: absolute;
  top: 0;
  height: 1px;
  width: 1px;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  background: var(--white);
  border-bottom: 1px solid transparent;
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}

.site-header.scrolled {
  background: rgba(250, 249, 246, 0.85);
  border-bottom-color: var(--line);
}

@supports (backdrop-filter: blur(8px)) {
  .site-header.scrolled { backdrop-filter: blur(8px); }
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s5);
  min-height: 72px;
}

.site-header__logo {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}

.site-header__logo img {
  /* 46px = 34px + 35% (Malena, revisión de diseño). El tamaño vive SOLO aquí;
     los atributos width/height del <img> están para reservar el espacio y
     evitar CLS, no para dimensionarlo. */
  height: 46px;
  width: auto;
}

/* --- Navigation --------------------------------------------------------- */
.nav__list {
  display: flex;
  align-items: center;
  gap: var(--s5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav__link {
  display: inline-block;
  padding: 8px 0;
  color: var(--ink);
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
}

.nav__link[aria-current="page"] {
  color: var(--accent);
  font-weight: 600;
}

@media (hover: hover) and (pointer: fine) {
  .nav__link:hover { color: var(--accent); }
}

/* Burger: shown below 1024, where the labels no longer fit inline. */
.nav__toggle {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 10px;
  background: transparent;
  border: 0;
  cursor: pointer;
}

.nav__toggle-bar {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--ink);
  border-radius: 2px;
  transition: transform var(--dur-fast) var(--ease),
              opacity var(--dur-fast) var(--ease);
}

.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(2) { opacity: 0; }

.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* Mobile.
   Progressive enhancement, same philosophy as the motion gate: without JS the
   nav is a plain stacked list that is always reachable, and the burger is
   hidden. main.js sets data-nav-enhanced on <html>, which is what turns the
   list into a collapsible overlay. A JS failure must never hide the menu. */
@media (max-width: 1023px) {
  .site-header__inner { flex-wrap: wrap; }

  .nav__toggle { display: none; }

  .nav__panel {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s4);
    width: 100%;
    padding-bottom: var(--s4);
  }

  .nav__list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .nav__list li + li { border-top: 1px solid var(--line); }

  .nav__link {
    display: block;
    padding: var(--s4) 0;
    font-size: 1.125rem;
  }

  .nav__cta { width: 100%; }

  /* --- Enhanced (JS available): burger + overlay panel --- */
  [data-nav-enhanced="true"] .nav__toggle { display: inline-flex; }

  /* Anchored to the header itself, not to a hardcoded viewport offset, so the
     announcement bar above the header cannot push it out of alignment. */
  [data-nav-enhanced="true"] .nav__panel {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: var(--z-menu);
    justify-content: space-between;
    gap: var(--s6);
    height: calc(100vh - 72px);
    height: calc(100dvh - 72px);
    padding: var(--s6) var(--pad-x) var(--s8);
    background: var(--white);
    border-top: 1px solid var(--line);
    overflow-y: auto;
    opacity: 0;
    transform: translateY(-8px);
    visibility: hidden;
    /* visibility must flip immediately on open (a delayed flip leaves the
       panel unfocusable for the whole duration) and only on close after the
       fade has finished. */
    transition: opacity 250ms var(--ease), transform 250ms var(--ease),
                visibility 0s linear 250ms;
  }

  [data-nav-enhanced="true"] .nav__panel[data-open="true"] {
    opacity: 1;
    transform: none;
    visibility: visible;
    transition: opacity 250ms var(--ease), transform 250ms var(--ease),
                visibility 0s linear 0s;
  }
}

@media (min-width: 1024px) {
  .nav__toggle { display: none; }

  .nav__panel {
    display: flex;
    align-items: center;
    gap: var(--s5);
  }
}

/* Prevent background scroll while the overlay menu is open. */
body[data-menu-open="true"] { overflow: hidden; }

/* --- Announcement bar --------------------------------------------------- */
.announce {
  background: var(--blue-900);
  color: var(--white);
}

.announce__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 44px;
  padding-block: var(--s2);
  font-size: var(--t-small);
  text-align: center;
}

.announce a {
  color: var(--white);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* --- Site footer -------------------------------------------------------- */
.site-footer {
  --bg: var(--blue-950);
  --fg: var(--white);
  --fg-muted: rgba(255, 255, 255, 0.75);
  --rule: var(--line-dark);
  --accent: var(--blue-100);
  --accent-hover: var(--white);
  background: var(--bg);
  color: var(--fg);
  padding-block: var(--s8) var(--s6);
}

.site-footer a {
  color: var(--white);
  text-decoration: underline;
  text-underline-offset: 3px;
}

@media (hover: hover) and (pointer: fine) {
  .site-footer a:hover { color: var(--blue-100); }
}

.site-footer__top {
  display: grid;
  gap: var(--s7);
  padding-bottom: var(--s7);
  border-bottom: 1px solid var(--rule);
}

@media (min-width: 768px) {
  .site-footer__top { grid-template-columns: 1.3fr 1fr 1fr; }
}

.site-footer__logo img {
  /* 81px = 54px + 50% (Malena). Queda deliberadamente mayor que el del header
     (46px): en el footer el logo es cierre de marca, no navegación. */
  height: 81px;
  width: auto;
}

.footer-heading {
  margin-bottom: var(--s3);
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--fg-muted);
}

.footer-list {
  display: grid;
  gap: var(--s2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.9375rem;
}

/* Compliance copy never drops below 13.5px (docs/05). */
.site-footer__contact {
  font-size: var(--t-small);
  line-height: 1.7;
  color: var(--fg-muted);
}

.site-footer__contact a { color: var(--fg-muted); }

.site-footer__bottom {
  display: grid;
  gap: var(--s4);
  padding-top: var(--s6);
  font-size: var(--t-small);
  color: var(--fg-muted);
}

.site-footer__legal-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s5);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The funding disclosure is compliance copy — verbatim, never truncated. */
/* El bloque de financiación ocupa todo el ancho del contenedor (antes tenía
   un tope de 90ch que lo cortaba a media caja) y va un 10% más pequeño que
   --t-small por indicación de Malena.

   NOTA: eso lo deja en ~12.15px, por debajo del suelo de 13px que fija
   docs/05 para la copia de cumplimiento. No es un incumplimiento de WCAG
   (no exige tamaño mínimo), es una convención de la casa; queda anotado
   porque el texto es legal y conviene que la decisión sea consciente. */
.funding-disclosure {
  font-size: calc(var(--t-small) * 0.9);
  line-height: 1.7;
  color: var(--fg-muted);
}

.social-list {
  display: flex;
  gap: var(--s3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.social-list a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
}

.social-list img { width: 18px; height: 18px; }

@media (hover: hover) and (pointer: fine) {
  .social-list a:hover { border-color: var(--white); }
}

/* 7. PAGE SECTIONS ======================================================== */

/* --- Hero ---------------------------------------------------------------
   Mobile-first: copy then media, stacked. Side-by-side from 1024 up. */
.hero__inner {
  display: grid;
  gap: var(--s7);
  align-items: center;
}

.hero__copy > * + * { margin-top: var(--s4); }

.hero__copy .lede { margin-top: var(--s5); }

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s6);
}

.proof-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin-top: var(--s6);
  padding: 8px 16px;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-size: var(--t-small);
  color: var(--ink-muted);
}

.hero__media img {
  width: 100%;
  border-radius: var(--r-lg);
  object-fit: cover;
  aspect-ratio: 16 / 10;
}

@media (min-width: 1024px) {
  .hero__inner {
    grid-template-columns: 1.05fr 1fr;
    gap: var(--s8);
  }

  .hero__media img { aspect-ratio: 4 / 5; }
}

/* --- Partner wall ------------------------------------------------------
   Was a CSS marquee on a track that main.js duplicated. Malena, 2026-09-02:
   "no me gusta el ticker... quita todas las animaciones de tickers". Now a
   static centred wall of the same 13 logos, nothing moving.

   Three problems went out with the motion, which is the accessibility case
   for the change: 13 duplicated <li> for the loop seam (the copies were
   aria-hidden, but they were still 13 more images to load), a 35s infinite
   animation that WCAG 2.2.2 only tolerates because it could be paused, and a
   horizontally scrolling region that reflowed badly at 320px.

   Class names kept: renaming them would churn five HTML files for nothing,
   and the block is still the partner ticker as far as the markup goes. */
.ticker-band {
  padding-block: var(--s7);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.ticker__title {
  font-family: var(--font-body);
  font-size: var(--t-eyebrow);
  font-weight: 600;
  color: var(--ink-muted);
  margin-bottom: var(--s5);
}

/* The band is full-bleed, so the wall carries the container's own width and
   padding now that there is no track sliding past the edges. */
.ticker {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--pad-x);
}

.ticker__track {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  column-gap: var(--s8);
  row-gap: var(--s6);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ticker__track img {
  height: 40px;
  width: auto;
  max-width: 160px;
  object-fit: contain;
  filter: grayscale(1);
  opacity: 0.6;
  transition: filter var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .ticker__track img:hover { filter: none; opacity: 1; }
}

@media (max-width: 767px) {
  .ticker__track { column-gap: var(--s7); row-gap: var(--s5); }
}

/* --- Decorative band artwork -------------------------------------------
   Opt-in: add .band--art to a band and the Consortium wave mark is painted
   into its bottom-right corner, flush to the section edges and behind the
   content.

   Painted as a background-image on a pseudo-element on purpose: it is purely
   decorative, and a background is never announced by assistive tech and never
   lands in the tab order — no aria-hidden to remember. It is also anchored to
   the BAND, not to .container, so the container's side padding does not push
   it away from the edge. The artwork fills its own viewBox edge to edge, so
   right/bottom 0 really does sit flush.

   La opacidad esta topada por una razon medida. El trazo claro de este
   grafico es #81A2E0 (la version 'shaded', Group 1536, que ya viene entonada
   para el azul de marca y trae su propia sombra en #012C80). Texto blanco
   encima de ese trazo a plena opacidad cae a 2.57:1. Mezclado en la banda al
   0.35 mantiene el blanco en 5.88:1.

   Techo medido con estos colores: 0.50 aun pasa (4.82:1) y 0.60 ya falla
   (4.23:1). Hay margen para subir hasta ~0.45 si se quiere mas presencia,
   pero no mas alla sin volver a medir. El trazo oscuro no es problema: al ser
   mas oscuro que la banda solo sube el contraste. */
.band--art {
  position: relative;
  overflow: hidden;
}

.band--art::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: 0;
  width: min(58%, 780px);
  /* Debe seguir al viewBox del SVG: si no coinciden, `contain` deja aire
     dentro de la caja y el gráfico se despega del borde. */
  aspect-ratio: 1878 / 1133;
  background: url('../images/logo/consortium-mark-lines-shaded.svg') no-repeat right bottom / contain;
  opacity: 0.35;
  pointer-events: none;
}

/* Content sits above the artwork. */
.band--art > .container {
  position: relative;
  z-index: 1;
}

@media (max-width: 767px) {
  /* On phones the text spans the full width, so the mark is pulled down and
     made smaller rather than sitting behind the whole paragraph. */
  .band--art::after {
    width: 85%;
    opacity: 0.28;
  }
}

/* --- Two-column section intro ------------------------------------------- */
.split {
  display: grid;
  gap: var(--s5);
}

@media (min-width: 900px) {
  .split {
    grid-template-columns: 1fr 1fr;
    gap: var(--s8);
    align-items: start;
  }
}

.split__body { color: var(--fg-muted); }

/* --- Numbered strategy cards -------------------------------------------- */
.strategies__title {
  margin-top: var(--s8);
  margin-bottom: var(--s5);
}

.strategies {
  margin: 0;
  padding: 0;
  list-style: none;
  grid-template-columns: 1fr;
}

@media (min-width: 600px) {
  .strategies { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  .strategies { grid-template-columns: repeat(4, 1fr); }
}

/* --- Stats -------------------------------------------------------------- */
.stats {
  margin: var(--s7) 0 0;
  padding: 0;
  list-style: none;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s6) var(--s5);
}

.stat__caption {
  display: block;
  margin-top: var(--s3);
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg-muted);
}

@media (min-width: 1024px) {
  .stats { grid-template-columns: repeat(4, 1fr); }
}

/* --- Stats as cards (/training-funds hero) -------------------------------
   The bare .stats row gives each figure the full column width. Put the same
   figures in cards and 48px of padding comes off that width, at which point
   "$55,000" at the full --t-stat 64px overflows its card. So the figure is
   scaled down here and nowhere else. Measured, not guessed: 234px of inner
   width at four columns in a 1200px container.

   Cards on the brand band are already transparent with a --line-dark border
   (see .band--brand .card), so this needs no colour of its own. */
.stats--cards > .card {
  gap: 0;
  justify-content: flex-start;
}

.stats--cards .t-stat {
  font-size: clamp(2.25rem, 1.7rem + 1.6vw, 2.875rem);   /* 36 -> 46 */
}

/* The caption already carries the spacing; .card's own gap would double it. */
.stats--cards .stat__caption { margin-top: var(--s3); }

/* The NOFO release date. Not a fifth stat card: .stats is four columns, so a
   fifth would sit orphaned on a second row. Reads as a footnote to the dates
   above it, which is what it is. */
.tf-released {
  margin-top: var(--s6);
  color: var(--fg-muted);
}

.demand__body {
  margin-top: var(--s8);
  color: var(--fg-muted);
}

/* --- Quote --------------------------------------------------------------
   The measure used to be `max-width: 60ch` on .quote, but `ch` resolves
   against THAT element's font-size (body Inter, ~16px), not the 32px display
   size of the quote itself — so it computed to roughly 540px and left huge
   gutters. The cap now lives in rem and is wide enough to fill the band. */
.quote {
  max-width: min(100%, 60rem);
  margin: 0 auto;
  text-align: center;
}

.quote blockquote {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.375rem, 1.1rem + 1.4vw, 2rem);
  /* 600 for the same reason as the stats: 500 is not one of the two bundled
     Open Sauce instances and would round up to 600 on its own. */
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: var(--tr-display);

  /* Tokens del efecto de scroll, abajo. */
  --p: 1;
  --off: 0.16;
  /* main.js recalcula --ramp y --step según cuántas palabras haya; estos
     valores solo evitan un primer frame raro si algo tardara. */
  --ramp: 0.0625;
  --step: 0.02;
}

/* Las comillas viven en CSS para que la cita las tenga SIEMPRE, también sin
   JS y con reduced motion. Cuando el efecto se activa, main.js las mete como
   caracteres reales en la primera y la última palabra (para que se enciendan
   con ellas) y estas se apagan — de ahí la regla .is-split más abajo. */
.quote blockquote p::before { content: '\201C'; }
.quote blockquote p::after { content: '\201D'; }

.quote figcaption {
  /* Separación generosa: el pie es otra voz, no la continuación de la cita. */
  margin-top: clamp(var(--s6), 4vw, var(--s8));
  font-size: var(--t-small);
}

.quote__name { display: block; font-weight: 600; }
.quote__role { display: block; color: var(--fg-muted); margin-top: var(--s1); }

/* --- Scroll-write ------------------------------------------------------
   La cita se va rellenando PALABRA POR PALABRA con el scroll. Portado del
   efecto de Francachella (src/scripts/definicion.ts), que es la referencia
   que Malena aprobó:

     · palabra apagada a opacidad 0.16 — se lee que hay texto, no distrae;
     · la ola mide 3 palabras, así se ve "palabra por palabra" y no como un
       degradado. main.js deriva --ramp del número de palabras para que ese
       ancho se mantenga en cualquier frase (ver OLA_PALABRAS allá).

   Va con opacidad y no con máscaras por la misma razón que en la referencia:
   la frase tiene tildes y descendentes y una máscara al ras se las comería.

   Solo --p (0 -> 1) se escribe por frame en el padre; cada palabra deriva su
   propia opacidad de --p y de su --i.

   Degradación, en orden:
     · main.js solo parte el texto si hay movimiento permitido. Sin JS o con
       reduced motion no existe ningún .sw-w y la cita se ve entera y quieta,
       con sus comillas de CSS.
     · --p vale 1 por defecto, así que aunque el driver no llegue a correr,
       cada palabra resuelve a opacidad 1 en vez de quedarse apagada. */
.sw-w {
  display: inline-block;
  opacity: clamp(
    var(--off),
    calc(var(--off) + ((var(--p) - var(--i) * var(--step)) / var(--ramp)) * (1 - var(--off))),
    1
  );
}

/* Con el texto partido, las comillas ya son caracteres reales dentro de las
   palabras; las de CSS se quitan para no duplicarlas. */
.quote blockquote.is-split p::before,
.quote blockquote.is-split p::after { content: none; }

/* --- Feature split (Penn West) ------------------------------------------ */
.feature {
  display: grid;
  gap: var(--s7);
  align-items: center;
}

.feature__media img {
  width: 100%;
  border-radius: var(--r-lg);
  object-fit: cover;
  aspect-ratio: 4 / 3;
}

/* Portrait variant, for a photograph shot vertically. Without it the 4/3 box
   above plus object-fit: cover would crop a vertical frame down to a letterbox
   and throw most of it away. 4/5 rather than the source's own 2/3: in the
   ~510px media column, 2/3 stands ~765px tall against a copy block of roughly
   450px, and align-items: center then leaves the text floating in the middle
   of the band. */
.feature--portrait .feature__media img { aspect-ratio: 4 / 5; }

.feature__copy > * + * { margin-top: var(--s4); }

.feature__logo {
  height: 56px;
  width: auto;
  margin-top: var(--s6);
}

@media (min-width: 900px) {
  .feature {
    grid-template-columns: 1fr 1.1fr;
    gap: var(--s8);
  }

  .feature__media img { aspect-ratio: 1 / 1; }
}

/* --- Audience + resource card grids ------------------------------------- */
.audience,
.resources {
  margin: var(--s7) 0 0;
  padding: 0;
  list-style: none;
  grid-template-columns: 1fr;
}

@media (min-width: 600px) {
  .audience,
  .resources { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  /* Three columns, not four: the Apprenticeship Sponsor Incentive card came
     out on 2026-09-02 (Jennifer, #marketing) and a fixed repeat(4, 1fr) would
     leave the fourth column empty with the three survivors squeezed into it.
     If a fourth route ever returns, this goes back to repeat(4, 1fr). */
  .audience,
  .resources { grid-template-columns: repeat(3, 1fr); }
}

.audience h3,
.resources h3 {
  font-size: var(--t-h3);
  margin: 0;
}

/* --- Card media --------------------------------------------------------
   Las tres imágenes de recursos vienen normalizadas a 400x300 (4:3), todas
   iguales. Como la caja usa ESE MISMO ratio, `cover` la llena de borde a
   borde sin recortar nada y sin dejar aire: imagen y caja tienen la misma
   forma, así que no hay diferencia que compensar.

   Antes iba con `contain` + padding + fondo, que era el parche necesario
   cuando las imágenes tenían proporciones distintas entre sí (una foto y dos
   logos de tamaños dispares): entonces `cover` recortaba texto de los logos.
   Normalizadas en origen, el parche ya no hace falta.

   El sangrado va en el <picture>, no en el <img>: el reset da max-width:100%
   a los dos, y en el <img> ese tope anulaba el ensanchado del margen negativo
   dejando un hueco a la derecha.

   Si alguna vez llega un reemplazo con otra proporción, hay que recortarlo a
   4:3 o la tarjeta volverá a recortar. */
.card > picture {
  display: block;
  max-width: none;
  margin: calc(var(--s5) * -1) calc(var(--s5) * -1) var(--s4);
  border-radius: var(--r-md) var(--r-md) 0 0;
  overflow: hidden;
}

.card__media {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  /* La imagen ya trae su propio fondo, así que la lámina no necesita ni
     relleno ni color propio. */
}

.card__external {
  margin-top: auto;
  padding-top: var(--s3);
  font-size: var(--t-small);
  color: var(--fg-muted);
}

/* --- Final CTA band ------------------------------------------------------ */
.cta-band__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s4);
  text-align: center;
}

.cta-band__inner .lede { margin-bottom: var(--s3); }

/* Stagger siblings inside a revealing group (docs/06 §2). */
.js-motion .grid > .reveal:nth-child(2) { animation-delay: 80ms; }
.js-motion .grid > .reveal:nth-child(3) { animation-delay: 160ms; }
.js-motion .grid > .reveal:nth-child(4) { animation-delay: 240ms; }

/* --- Contact form -------------------------------------------------------- */
.form-layout {
  display: grid;
  gap: var(--s7);
  align-items: start;
}

@media (min-width: 900px) {
  .form-layout {
    grid-template-columns: 1fr 1.1fr;
    gap: var(--s8);
  }

  .form-layout__intro { position: sticky; top: 104px; }
}

.form-layout__intro > * + * { margin-top: var(--s4); }

.contact-block {
  margin-top: var(--s7);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}

.contact-block .footer-heading { color: var(--ink-muted); }

.form {
  display: grid;
  gap: var(--s5);
  padding: var(--s6);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}

.field { display: grid; gap: 6px; }

.field label {
  font-size: 0.9375rem;
  font-weight: 600;
}

/* Required/optional is stated in words, never colour or an asterisk alone. */
.field__req,
.field__optional {
  font-weight: 400;
  color: var(--ink-muted);
  font-size: var(--t-small);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 12px 14px;
  background: var(--white);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  /* 16px minimum stops iOS Safari zooming the viewport on focus. */
  font-size: 1rem;
  transition: border-color var(--dur-fast) var(--ease);
}

.field textarea {
  resize: vertical;
  min-height: 8rem;
}

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible { border-color: var(--blue-900); }

.field[data-invalid='true'] input,
.field[data-invalid='true'] select,
.field[data-invalid='true'] textarea {
  border-color: var(--error);
}

.field__error {
  margin: 0;
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--error);
}

/* Honeypot: out of sight, out of the tab order, out of the a11y tree. */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__submit { justify-self: start; }

.form__status {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 500;
}

.form__status[data-state='error'] { color: var(--error); }
.form__status[data-state='success'] { color: var(--success); }

/* Success panel that replaces the form once a submission lands. */
.form-success {
  display: grid;
  gap: var(--s3);
  padding: var(--s7) var(--s6);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}

.form-success__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-pill);
  background: var(--blue-100);
  color: var(--blue-900);
  font-size: 1.5rem;
}

/* --- Job cards (/open-opportunities) ------------------------------------- */
.page-intro > * + * { margin-top: var(--s4); }

.jobs {
  display: grid;
  gap: var(--s5);
  margin: var(--s7) 0 0;
  padding: 0;
  list-style: none;
  grid-template-columns: 1fr;
}

@media (min-width: 700px) {
  .jobs { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1100px) {
  .jobs { grid-template-columns: repeat(3, 1fr); }
}

.job__org {
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 600;
  color: var(--accent);
}

.job__title {
  font-size: var(--t-h3);
  margin: 0;
}

.job__actions { margin-top: auto; padding-top: var(--s4); }

/* Shown instead of a dead button when a job has no destination yet. */
.job__no-link {
  font-size: var(--t-small);
  color: var(--fg-muted);
}

.jobs-empty {
  margin-top: var(--s7);
  padding: var(--s7);
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
  text-align: center;
  color: var(--ink-muted);
}

/* --- 404 ----------------------------------------------------------------- */
.error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s4);
  text-align: center;
  padding-block: var(--s10);
}

.error-page__code {
  font-family: var(--font-display);
  font-size: var(--t-stat);
  /* 700, not the 650 Bricolage carried: Open Sauce One ships as two static
     instances here, so 650 would resolve to 700 anyway. Declared, not left
     to the font-matching algorithm. */
  font-weight: 700;
  line-height: 1;
  letter-spacing: var(--tr-display);
  color: var(--accent);
}

.error-page__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  justify-content: center;
  margin-top: var(--s4);
}

/* 8. UTILITIES ============================================================ */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Motion is opt-in. `.js-motion` is added to <html> by main.js only when the
   user has NOT asked for reduced motion, so with JS disabled or motion
   reduced the page renders fully visible with no transitions at all. */
/* El reveal usa ANIMATION y no TRANSITION a propósito.

   Con transition, `.js-motion .reveal.in` declaraba el shorthand `transition`
   con especificidad (0,3,0) y le ganaba a cualquier componente que quisiera
   animar lo suyo: una .card revelada perdía su propia transición de
   translate/box-shadow/border-color y el hover pasaba a saltar de golpe. Y no
   se arregla subiendo especificidad, porque son dos intereses distintos
   peleando por la MISMA propiedad.

   Con una animación el reveal ya no toca `transition`, así que cada componente
   conserva la suya. Además el reveal se queda en `transform` y los hovers usan
   `translate`, que son propiedades independientes y se componen. */
.js-motion .reveal {
  opacity: 0;
}

.js-motion .reveal.in {
  animation: reveal-rise var(--dur-reveal) var(--ease) forwards;
}

@keyframes reveal-rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}
