/* =============================================================
   SLLM — Sánchez-Labrador y López Martínez
   Single stylesheet. No frameworks, no web fonts, no build step.
   Brand tokens live in :root — swap them to rebrand centrally.
   ============================================================= */

/* ---- Design tokens ---------------------------------------- */
:root {
  /* Brand palette (placeholder — replace with real brand colors) */
  --navy-900: #0a1421;
  --navy-800: #0e1a2b;
  --navy-700: #132743;
  /* Accent — navy blue (used for links, eyebrows, icons, buttons) */
  --blue-500: #2a5298;
  --blue-600: #1f3f78;
  /* Deep blue for body copy inside white blocks */
  --blue-700: #17305c;

  --ink: #1b2431;
  --body: #3a4553;
  --muted: #6b7686;
  /* Neutrals are cool (blue-grey), never warm/beige */
  --line: #dfe4ec;
  --surface: #ffffff;
  --surface-alt: #ffffff;
  --surface-cream: #ffffff;

  /* Type: system fonts only → zero network requests */
  --font-serif: Georgia, "Times New Roman", "Noto Serif", serif;
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, "Noto Sans", sans-serif;

  /* Fluid type scale */
  --step--1: clamp(0.83rem, 0.79rem + 0.2vw, 0.94rem);
  --step-0: clamp(1rem, 0.95rem + 0.25vw, 1.13rem);
  --step-1: clamp(1.2rem, 1.1rem + 0.5vw, 1.5rem);
  --step-2: clamp(1.5rem, 1.3rem + 1vw, 2.1rem);
  --step-3: clamp(1.9rem, 1.55rem + 1.7vw, 3rem);
  --step-4: clamp(2.4rem, 1.85rem + 2.75vw, 4rem);

  /* Spacing & layout */
  --space: clamp(1rem, 0.8rem + 1vw, 1.5rem);
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --maxw: 1200px;
  --header-h: 100px;
  --radius: 4px;
  /* Only the two stacks — practice areas and programas — use this: softer
     corners than the rest of the site, which stays deliberately square at
     --radius. */
  --radius-lg: 16px;
  /* Cool tint behind both stacks, so the white panels read as stacked sheets
     rather than dissolving into the page. */
  --surface-tint: #eff3f8;
  --shadow: 0 1px 2px rgba(14, 26, 43, 0.06), 0 8px 24px rgba(14, 26, 43, 0.08);
  --ring: 0 0 0 3px rgba(42, 82, 152, 0.45);
}

/* ---- Reset / base ----------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

body {
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.65;
  color: var(--body);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; height: auto; }
a { color: var(--blue-600); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--blue-500); }

h1, h2, h3, h4 {
  font-family: var(--font-serif);
  color: var(--ink);
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: -0.01em;
}
h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-1); }

:focus-visible { outline: 2px solid var(--blue-500); outline-offset: 2px; }

/* ---- Utilities -------------------------------------------- */
.container { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: clamp(3rem, 2rem + 5vw, 6rem); }
/* Formerly cream/beige bands: now white blocks with blue type inside,
   separated from neighbouring white sections by a hairline rule. */
.section--alt, .section--cream {
  background: var(--surface);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  color: var(--blue-700);
}
.section--alt h2, .section--alt h3, .section--alt h4,
.section--cream h2, .section--cream h3, .section--cream h4 { color: var(--blue-600); }
.section--alt p, .section--cream p,
.section--alt li, .section--cream li { color: var(--blue-700); }
.section--alt .lede, .section--cream .lede { color: var(--blue-500); }
.section--alt .card p, .section--cream .card p { color: var(--blue-700); }
.section--alt .chips li, .section--cream .chips li { color: var(--blue-700); }

/* Blue backgrounds: everything on top of them is white */
.section--dark { background: var(--navy-800); color: #fff; }
.section--dark h2, .section--dark h3, .section--dark h4, .section--dark p, .section--dark li { color: #fff; }
.section--dark .card__icon, .section--dark .card__num { color: #fff; }

.eyebrow {
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--blue-600);
  margin-bottom: 0.75rem;
}
.section--dark .eyebrow { color: #fff; }
.hero .eyebrow { color: #fff; }
.page-hero .eyebrow { color: #fff; }

.lede { font-size: var(--step-1); color: var(--muted); font-family: var(--font-serif); }
.measure { max-width: 62ch; }
.center { text-align: center; margin-inline: auto; }

.skip-link {
  position: absolute; left: 0.5rem; top: -3rem;
  background: var(--navy-800); color: #fff; padding: 0.6rem 1rem;
  border-radius: var(--radius); z-index: 100; transition: top 0.2s;
}
.skip-link:focus { top: 0.5rem; color: #fff; }

/* ---- Buttons ---------------------------------------------- */
.btn {
  display: inline-block;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: var(--step-0);
  padding: 0.8em 1.6em;
  border-radius: var(--radius);
  border: 1.5px solid var(--navy-800);
  background: var(--navy-800);
  color: #fff;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.2s, color 0.2s, transform 0.1s;
}
.btn:hover { background: var(--navy-700); color: #fff; transform: translateY(-1px); }
.btn--blue { background: var(--blue-500); border-color: var(--blue-500); color: #fff; }
.btn--blue:hover { background: var(--blue-600); border-color: var(--blue-600); color: #fff; }
.btn--ghost { background: transparent; color: var(--ink); }
.btn--ghost:hover { background: var(--navy-800); color: #fff; }
.section--dark .btn--ghost { color: #fff; border-color: rgba(255,255,255,0.4); }
.section--dark .btn--ghost:hover { background: #fff; color: var(--navy-800); }
/* On blue backgrounds the button block turns white with blue lettering */
.site-header .btn--blue, .section--dark .btn--blue, .cta-band .btn--blue {
  background: #fff; border-color: #fff; color: var(--blue-600);
}
.site-header .btn--blue:hover, .section--dark .btn--blue:hover, .cta-band .btn--blue:hover {
  background: #eaf0fa; border-color: #eaf0fa; color: var(--blue-600);
}

/* ---- Header / nav -----------------------------------------
   The isotipo is the whole header: a three-column grid keeps it dead centre
   whatever sits beside it. The menu is a panel that drops out of the header on
   hover (desktop) or from the toggle (mobile) — no page-level nav links. */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: rgba(14, 26, 43, 0.92);
  backdrop-filter: saturate(160%) blur(8px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.nav {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: 1rem; min-height: var(--header-h);
}
.brand { grid-column: 2; justify-self: center; color: #fff; }
.brand svg { width: 168px; height: auto; }
.brand img { height: 68px; width: auto; display: block; }
.nav-toggle { grid-column: 1; justify-self: start; }

.nav-toggle {
  display: inline-flex; flex-direction: column; gap: 5px;
  width: 44px; height: 44px; align-items: center; justify-content: center;
  background: none; border: 1px solid rgba(255, 255, 255, 0.3); border-radius: var(--radius);
  cursor: pointer;
}
.nav-toggle span { width: 22px; height: 2px; background: #fff; transition: transform 0.25s, opacity 0.2s; }

.nav-menu { list-style: none; padding: 0; margin: 0; }
.nav-menu a {
  font-family: var(--font-sans); font-weight: 500; font-size: var(--step--1);
  letter-spacing: 0.02em; text-transform: uppercase; text-decoration: none;
  color: #fff; padding: 0.5rem 0; position: relative;
}
.nav-menu a::after {
  content: ""; position: absolute; left: 0; bottom: -2px; height: 2px; width: 0;
  background: #fff; transition: width 0.2s;
}
.nav-menu a:hover::after, .nav-menu a[aria-current="page"]::after { width: 100%; }

/* Desktop: hover (or keyboard focus) anywhere on the header drops the panel.
   :focus-within is what makes it reachable without a pointer — no JS involved.
   The panel is only as wide as the links it holds (width: max-content, centred
   under the header) and drops on a soft ease-out so it settles rather than
   snaps. */
@media (min-width: 900px) {
  .nav-toggle { display: none; }
  .nav-menu {
    position: absolute; top: 100%; left: 50%;
    width: max-content; max-width: calc(100% - var(--gutter) * 2);
    /* wrap is a safety net, not a layout: the six links fit on one line at
       900px, but a longer label added later must fold onto a second row inside
       the panel rather than run out past its rounded edge. */
    display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
    gap: clamp(1.25rem, 3vw, 2.75rem);
    padding: 1.1rem clamp(1.5rem, 3vw, 2.5rem);
    background: rgba(14, 26, 43, 0.97);
    backdrop-filter: saturate(160%) blur(8px);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-top: 0;
    border-radius: 0 0 16px 16px;
    box-shadow: var(--shadow);
    opacity: 0; visibility: hidden;
    transform: translateX(-50%) translateY(-10px);
    transition:
      opacity 0.45s cubic-bezier(0.22, 0.61, 0.36, 1),
      transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1),
      visibility 0.45s;
  }
  .site-header:hover .nav-menu,
  .site-header:focus-within .nav-menu {
    opacity: 1; visibility: visible;
    transform: translateX(-50%) translateY(0);
  }
}
/* Just above the mobile breakpoint the panel is capped at 100% minus two
   gutters — 810px at 900px wide — and six links at the roomy gap measure a
   little over 800px of that. «Servicios» is a short enough sixth label to clear
   it on its own, so this is margin rather than rescue now; it stays because the
   margin is what a longer label would spend, and the roomier spacing returns
   untouched at 1100px. */
@media (min-width: 900px) and (max-width: 1100px) {
  .nav-menu { gap: 1.25rem; padding-inline: 1.25rem; }
}
/* Mobile: no hover to work with, so the toggle stays. */
@media (max-width: 899px) {
  .nav-menu {
    position: fixed; inset: 100px 0 auto 0;
    display: grid; gap: 0.25rem;
    background: var(--navy-800); border-bottom: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 0 0 16px 16px;
    padding: 1rem var(--gutter) 1.5rem;
    box-shadow: var(--shadow);
    transform: translateY(-10px); opacity: 0; visibility: hidden;
    transition:
      opacity 0.4s cubic-bezier(0.22, 0.61, 0.36, 1),
      transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1),
      visibility 0.4s;
  }
  .nav-menu.is-open { transform: translateY(0); opacity: 1; visibility: visible; }
  .nav-menu li { border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
  .nav-menu li:last-child { border-bottom: 0; }
  .nav-menu a { display: block; padding: 0.9rem 0; font-size: var(--step-0); }
  .nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
  .nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
}

/* Anchor targets have to clear the sticky header when scrolled to. */
[id] { scroll-margin-top: 116px; }

/* ---- Hero ------------------------------------------------- */
.hero {
  position: sticky; top: 0; color: #eef2f7; overflow: hidden;
  background: var(--navy-800);
  min-height: 100vh; min-height: 100svh;
  /* Two rows: the copy takes the slack and stays centred in it, the card band
     is the second row. Reserving the band this way (instead of padding chosen
     by hand) keeps it right on short viewports, and because the cards only ever
     move with transform/visibility, raising them shifts no layout. */
  display: grid; grid-template-rows: 1fr auto;
}
.hero__bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0; background: var(--navy-800);
}
/* Scrim: the video is busier than a still photo, so the headline needs a floor
   of contrast in every frame. Heaviest on the left, where the text sits. */
.hero__scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(10, 20, 33, 0.35) 0%, rgba(10, 20, 33, 0) 45%, rgba(10, 20, 33, 0.45) 100%),
    linear-gradient(90deg, rgba(10, 20, 33, 0.45) 0%, rgba(10, 20, 33, 0.62) 50%, rgba(10, 20, 33, 0.45) 100%);
}
.hero__inner {
  position: relative; z-index: 2; align-self: center; text-align: center;
  /* Top padding only clears the sticky header the hero now slides under; the
     breathing room comes from the 1fr row, which centres the copy in whatever
     space is left. Keeping the padding tight is what lets the whole hero —
     copy and card band — fit inside one viewport on a laptop screen. */
  padding-block: calc(var(--header-h) + 0.5rem) 0.5rem;
  transition: transform 0.6s cubic-bezier(0.22, 0.8, 0.24, 1), opacity 0.4s ease;
}
.hero-stage.is-cards-in .hero__inner { opacity: 0.95; }

/* Sound toggle — muted autoplay is a browser requirement, this is the way out of it. */
.hero__sound {
  position: absolute; z-index: 4; right: 1.25rem; top: calc(var(--header-h) + 1rem);
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.55rem 0.9rem;
  font: inherit; font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: #fff; background: rgba(10, 20, 33, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.4); border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.hero__sound:hover { background: rgba(10, 20, 33, 0.75); border-color: rgba(255, 255, 255, 0.75); }
.hero__sound-icon { display: block; flex: none; }
.hero__sound[aria-pressed="false"] .hero__sound-icon--on { display: none; }
.hero__sound[aria-pressed="true"] .hero__sound-icon--off { display: none; }
@media (max-width: 599px) {
  /* Icon only on small screens; aria-label still names the button. */
  .hero__sound { padding: 0.6rem; }
  .hero__sound-text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}
.hero h1 {
  color: #fff; margin-inline: auto;
  transition: font-size 0.6s cubic-bezier(0.22, 0.8, 0.24, 1);
}

/* The wordmark. Its spacing is tracking, not spaces typed between the letters,
   so the name stays one selectable, readable, indexable string. The padding-left
   cancels the trailing gap tracking leaves after the last letter — without it a
   centred line sits half a letter-space to the left of true centre. */
.hero__wordmark {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: clamp(0.06em, 0.6vw, 0.18em);
  padding-left: clamp(0.06em, 0.6vw, 0.18em);
  /* Its own scale rather than h1's --step-4: tracked-out uppercase runs far
     wider than the sentence-case headline that size was picked for, and this
     name has to settle on two lines, never three. */
  font-size: clamp(1.6rem, 0.9rem + 2.6vw, 3.25rem);
  line-height: 1.25;
}
/* The ampersand travels with the first surname and each surname stays whole, so
   the name breaks in one place — between the two families — instead of inside
   "Sánchez-Labrador" or onto a line of its own. Only from 480px up: below that
   the hyphen break is the one that keeps the name on screen. */
@media (min-width: 480px) {
  .hero__wordmark span { white-space: nowrap; }
}
/* Grey against the wordmark's white — the pair is the whole hero, so the second
   line has to sit clearly below the first without competing with it. Beats the
   `.hero p { color: #fff }` above on specificity (0,2,0 over 0,1,1). */
.hero .hero__tagline {
  margin-top: clamp(0.75rem, 1.6vh, 1.25rem);
  /* One line wherever it fits — the tracking makes it far wider than its
     character count suggests, so no ch-based cap here. */
  max-width: none;
  font-family: var(--font-sans);
  font-size: clamp(0.78rem, 0.6rem + 0.6vw, 1.05rem);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: clamp(0.16em, 1vw, 0.3em);
  padding-left: clamp(0.16em, 1vw, 0.3em);
  color: #b4bfcd;
}
.btn-row { display: flex; flex-wrap: wrap; gap: 0.9rem; }

/* ---- Hero stage: the scroll-up cards -----------------------
   The stage is taller than the viewport and the hero sticks to its top, so the
   first stretch of scrolling doesn't move the video at all — it only crosses
   the sentinel, and that is what raises the cards. Scrolling back up past the
   same line drops them again, as many times as the visitor likes. Everything
   below is a plain class toggle plus CSS transitions: no per-frame JS. */
/* Pulled up under the sticky header so the video starts at the very top of the
   page and the hero is exactly one viewport tall from the first frame.

   The height is the one knob for how long the video holds: the hero is 100svh,
   so the stage's extra 75svh is exactly the run it stays pinned for. That run is
   deliberately long enough to be two gestures rather than one — the first raises
   the cards, and the video is still held afterwards, so it takes a second scroll
   to actually leave the hero. Halve it and both beats collapse into a single
   flick. Past the run the stage gives out from under the hero and the section
   below rides up and pushes it off the top of the screen — and because that is
   sticky positioning and nothing else, scrolling back up costs the same 75svh to
   bring the full video back. Move this one number to retune both directions. */
.hero-stage {
  position: relative;
  height: 175vh; height: 175svh;
  /* header height + its 1px bottom border */
  margin-top: calc(-1 * (var(--header-h) + 1px));
  /* Never seen: the pinned hero covers the stage for its whole run, and once it
     starts moving the section below has already taken the space. It is here so
     that any sliver that does show is the hero's own navy, not the white of the
     page. */
  background: var(--navy-800);
}

/* One line, both directions. Sitting at 38svh — halfway through the run the
   hero stays pinned for — it is crossed by the first scroll down, and the hero
   is still pinned for as much again afterwards. That second half is what makes
   the cards a stop of their own instead of something glimpsed on the way past. */
.hero-stage__sentinel {
  position: absolute; left: 0;
  top: 38vh; top: 38svh;
  width: 100%; height: 1px; pointer-events: none;
}

/* Second row of the hero, and its own clipping box: parked cards sit below its
   edge whatever height the band has.
   The clip is a clip-path rather than `overflow: hidden` because a card sits
   flush with the top of this box at rest, so the 3px lift it takes on hover and
   focus was being shaved off — border, rounded corners and all. Negative insets
   open the top and sides for the lift and its glow; the 0 keeps the bottom edge
   clipping the parked cards exactly as before. */
.hero-cards {
  position: relative; z-index: 3;
  clip-path: inset(-16px -16px 0 -16px);
  padding-bottom: clamp(1.25rem, 4vh, 3rem);
}
/* Flex lines stretch, so one row fills the band on a desktop and the wrapped
   rows on a phone split it evenly. */
.hero-cards__grid,
.hero-cards__event {
  list-style: none; margin: 0;
  width: 100%; max-width: var(--maxw); margin-inline: auto;
  padding: 0 var(--gutter);
  display: flex; flex-wrap: wrap; justify-content: center;
  align-content: stretch;
  gap: clamp(0.75rem, 1.6vw, 1.25rem);
}
/* flex-basis over a fixed column count: the three shortcuts centre themselves
   whatever the viewport, without a column count to keep in step with the markup. */
.hero-card { flex: 1 1 210px; max-width: 280px; }
.hero-card[hidden] { display: none; }


.hero-card__link {
  /* Bottom-aligned: the event card's extra badge grows upwards, so titles and
     arrows stay on the same line across the row. */
  display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; text-align: center;
  gap: 0.3rem; height: 100%;
  padding: clamp(0.9rem, 2vw, 1.2rem);
  color: #fff; text-decoration: none;
  background: linear-gradient(180deg, rgba(10, 20, 33, 0.42) 0%, rgba(10, 20, 33, 0.8) 100%);
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 20px;
  backdrop-filter: saturate(160%) blur(8px);
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.hero-card__link:hover {
  background: linear-gradient(180deg, rgba(10, 20, 33, 0.62) 0%, rgba(10, 20, 33, 0.92) 100%);
  border-color: rgba(255, 255, 255, 0.6);
  color: #fff; transform: translateY(-3px);
}
/* Only the tall panels are roomy enough to carry it. */
.hero-card__icon { display: none; color: #fff; }
.hero-card__flag {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase;
  color: #fff; opacity: 0.85;
}
.hero-card__title { font-family: var(--font-serif); font-size: var(--step-1); line-height: 1.2; }
.hero-card__text { font-size: var(--step--1); color: rgba(255, 255, 255, 0.82); }
.js .hero-card__text { min-height: 3.3em; }
.hero-card__arrow { margin-top: 0.35rem; color: #fff; transition: transform 0.2s ease; }
.hero-card__link:hover .hero-card__arrow { transform: translateX(3px); }
.hero-card--event .hero-card__link {
  background: linear-gradient(180deg, rgba(42, 82, 152, 0.55) 0%, rgba(31, 63, 120, 0.88) 100%);
  border-color: rgba(255, 255, 255, 0.5);
}
.hero-card--event .hero-card__link:hover {
  background: linear-gradient(180deg, rgba(42, 82, 152, 0.75) 0%, rgba(31, 63, 120, 0.96) 100%);
}

/* Sparkle on pointer — every card in the row, not just the event one. Three
   layers, all pseudo-elements, so they cost no markup and never intercept the
   click: a wide band that sweeps diagonally across the panel (the shine) and
   two fields of small points that twinkle over it. The fields run at different
   speeds and start out of step, so the points never blink in unison. */
.hero-card { position: relative; }
.hero-card__link {
  position: relative; overflow: hidden;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease,
              box-shadow 0.35s ease;
}
/* The text sits above the shine; the second spark field sits above the text. */
.hero-card__link > * { position: relative; z-index: 2; }
.hero-card__link::before,
.hero-card__link::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  pointer-events: none; opacity: 0;
  transition: opacity 0.4s ease;
}
.hero-card__link::before {
  background-image: linear-gradient(115deg,
    transparent 36%, rgba(255, 255, 255, 0.34) 50%, transparent 64%);
  background-size: 260% 100%;
  background-repeat: no-repeat;
  background-position: 130% 0;
}
/* Each point is a lit core inside a soft halo — that halo is what makes it
   read as a spark against the video instead of a stray dot. */
.hero-card__link::after {
  background-image:
    radial-gradient(circle 3.4px at 17% 23%, rgba(255, 255, 255, 0.98) 0 22%, rgba(255, 255, 255, 0.4) 50%, transparent 100%),
    radial-gradient(circle 2.6px at 71% 15%, rgba(255, 255, 255, 0.95) 0 22%, rgba(255, 255, 255, 0.36) 50%, transparent 100%),
    radial-gradient(circle 4px at 86% 57%, rgba(255, 255, 255, 0.98) 0 20%, rgba(255, 255, 255, 0.42) 48%, transparent 100%),
    radial-gradient(circle 3px at 33% 71%, rgba(255, 255, 255, 0.96) 0 22%, rgba(255, 255, 255, 0.38) 50%, transparent 100%),
    radial-gradient(circle 2.4px at 55% 43%, rgba(255, 255, 255, 0.92) 0 24%, rgba(255, 255, 255, 0.34) 52%, transparent 100%),
    radial-gradient(circle 3.6px at 9% 61%, rgba(255, 255, 255, 0.96) 0 20%, rgba(255, 255, 255, 0.4) 48%, transparent 100%);
}
/* Second field, hosted on the <li> so it can twinkle on its own clock. The
   points sit well inside the panel, so nothing needs clipping. */
.hero-card::before {
  content: ""; position: absolute; inset: 0; z-index: 3;
  pointer-events: none; opacity: 0;
  transition: opacity 0.4s ease;
  background-image:
    radial-gradient(circle 3.2px at 27% 12%, rgba(255, 255, 255, 0.98) 0 22%, rgba(255, 255, 255, 0.4) 50%, transparent 100%),
    radial-gradient(circle 2.4px at 62% 66%, rgba(255, 255, 255, 0.94) 0 24%, rgba(255, 255, 255, 0.36) 52%, transparent 100%),
    radial-gradient(circle 4.2px at 44% 31%, rgba(255, 255, 255, 0.98) 0 18%, rgba(255, 255, 255, 0.42) 46%, transparent 100%),
    radial-gradient(circle 2.8px at 80% 84%, rgba(255, 255, 255, 0.96) 0 22%, rgba(255, 255, 255, 0.38) 50%, transparent 100%),
    radial-gradient(circle 3px at 13% 86%, rgba(255, 255, 255, 0.96) 0 22%, rgba(255, 255, 255, 0.38) 50%, transparent 100%);
}
.hero-card__link:hover,
.hero-card__link:focus-visible {
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2), 0 10px 30px rgba(10, 20, 33, 0.45);
}
.hero-card--event .hero-card__link:hover,
.hero-card--event .hero-card__link:focus-visible {
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.24), 0 10px 34px rgba(42, 82, 152, 0.45);
}
/* Reduced-motion visitors get the same three layers, simply held still. */
.hero-card__link:hover::before,
.hero-card__link:focus-visible::before { opacity: 0.8; background-position: 40% 0; }
.hero-card__link:hover::after,
.hero-card__link:focus-visible::after { opacity: 0.85; }
.hero-card:hover::before,
.hero-card:focus-within::before { opacity: 0.85; }

@media (prefers-reduced-motion: no-preference) {
  .hero-card__link:hover::before,
  .hero-card__link:focus-visible::before {
    opacity: 1; animation: hero-card-sheen 2.2s ease-in-out infinite;
  }
  .hero-card__link:hover::after,
  .hero-card__link:focus-visible::after {
    animation: hero-card-sparkle 2s ease-in-out infinite;
  }
  .hero-card:hover::before,
  .hero-card:focus-within::before {
    animation: hero-card-sparkle 2.7s ease-in-out 0.35s infinite;
  }
}
@keyframes hero-card-sheen {
  0% { background-position: 130% 0; }
  /* The tail of the cycle is dead time, so the sweeps stay spaced out. */
  65%, 100% { background-position: -30% 0; }
}
@keyframes hero-card-sparkle {
  0%, 100% { opacity: 0.18; transform: scale(0.92); }
  45% { opacity: 1; transform: scale(1.06); }
}

/* Parked below the hero's bottom edge until the sentinel is crossed. Gated on
   .js so the cards are simply there when the script never runs, and on
   no-preference so reduced-motion visitors get them static too. */
@media (prefers-reduced-motion: no-preference) {
  .js .hero-card {
    opacity: 0; visibility: hidden; transform: translateY(calc(100% + 4rem));
  }
  /* Transitions are switched on a frame after load: without this the cards would
     animate themselves out of view on arrival instead of just starting parked. */
  .js .hero-stage.is-ready .hero-card {
    transition: transform 0.65s cubic-bezier(0.22, 0.8, 0.24, 1), opacity 0.4s ease,
      visibility 0.65s;
  }
  .js .hero-stage.is-cards-in .hero-card { opacity: 1; visibility: visible; transform: none; }
  /* Staggered on the way up only — on the way down they leave together. The
     event card is first in a list of its own, so it takes the fourth beat by
     name rather than by position. */
  .js .hero-stage.is-cards-in .hero-cards__grid .hero-card:nth-child(2) { transition-delay: 80ms; }
  .js .hero-stage.is-cards-in .hero-cards__grid .hero-card:nth-child(3) { transition-delay: 160ms; }
  .js .hero-stage.is-cards-in .hero-card--event { transition-delay: 240ms; }

  /* The hero has no exit of its own: once the stage runs out from under it, the
     section below rides up and pushes it off the top of the screen, and the
     video plays the whole way out. Nothing to hide and nothing to fade — the
     card links leave the viewport with the hero that carries them, so they stop
     being reachable the way anything scrolled past does. Anything added here
     must not put a transform on .hero-stage: that would kill the sticky hero
     inside it. On .hero itself a transform is safe. */

  /* Two thirds of the hero when the cards are up, no band at all when they are
     parked — both ends are lengths, so the band grows and shrinks smoothly and
     the resting hero looks exactly as it does without the cards. */
  .js .hero-cards { padding-bottom: 0; }
  .js .hero-cards__grid,
  .js .hero-cards__event { height: 0; }
  .js .hero-stage.is-cards-in .hero-cards { padding-bottom: clamp(1.25rem, 4vh, 3rem); }
  /* The two rows split one band, and the sum is the number that has to hold: the
     wordmark and the tagline want the rest of the hero's single viewport. So the
     event row's share is doubled out of the grid's and not out of the hero —
     both pairs below still add up to what they added up to before. */
  .js .hero-stage.is-cards-in .hero-cards__grid { height: clamp(9rem, 41svh, 30rem); }
  .js .hero-stage.is-cards-in .hero-cards__event { height: clamp(8rem, 18svh, 12.5rem); }
  .js .hero-stage.is-ready .hero-cards { transition: padding-bottom 0.65s cubic-bezier(0.22, 0.8, 0.24, 1); }
  .js .hero-stage.is-ready .hero-cards__grid,
  .js .hero-stage.is-ready .hero-cards__event { transition: height 0.65s cubic-bezier(0.22, 0.8, 0.24, 1); }

  /* Panels that size carry bigger type, fill the container width, and take an
     icon at the top — the auto margin is what holds it there. */
  .js .hero-card { max-width: none; }
  .js .hero-card__link { padding: clamp(1.25rem, 2.5vw, 2rem); gap: 0.45rem; }
  .js .hero-card__icon { display: block; margin-bottom: auto; }
  .js .hero-card__icon svg { width: clamp(34px, 3.2vw, 46px); height: auto; }
  .js .hero-card__title { font-size: var(--step-2); }
  .js .hero-card__text { font-size: var(--step-0); }
  .js .hero-card__arrow { margin-top: 0.6rem; }
  .js .hero-card__arrow svg { width: 28px; height: 28px; }
  .js .hero-card__flag { font-size: 0.72rem; }

  /* …and the copy gives up the room: the wordmark drops a step so it and the
     tagline sit as one compact block above the cards. */
  .js .hero-stage.is-cards-in .hero h1 { font-size: var(--step-2); }
}

/* ---- The event row --------------------------------------------------
   One wide card on the line under the three, and the whole line goes when the
   edition is over. The gap above it rides on the card and not on the <ul>: a
   gap set on the row would outlive the card it was spacing and leave a hole in
   the band. :has() takes the empty row out outright where it is supported, and
   where it is not, the margin leaving with the card already means nothing shows.

   After the .js block on purpose, not before it. Those rules are what dress the
   three tall panels — icon on top, roomy padding, arrow below the copy — and
   they carry the same weight as these; source order is the only thing that
   settles it, and the banner has to win. */
.hero-cards__event:has(.hero-card[hidden]) { display: none; }
/* No cap: the row already carries the grid's own max-width and gutter — they are
   declared together above — so the banner runs edge to edge with the three cards
   over it without a width of its own. */
.hero-cards__event .hero-card--event {
  flex: 0 0 100%; max-width: none;
  margin-block-start: clamp(0.75rem, 1.6vw, 1.25rem);
}
/* A banner and not a fourth panel: still the one line read across, with the
   badge and the arrow at either end of it, but set at the size a card this tall
   and this wide asks for. */
.hero-cards__event .hero-card__link {
  flex-flow: row wrap; align-items: center; justify-content: center;
  gap: 0.4rem 1.4rem; text-align: left;
  padding: clamp(1rem, 2.2vw, 1.75rem) clamp(1.5rem, 3.2vw, 2.5rem);
}
.hero-cards__event .hero-card__icon { display: none; }
.hero-cards__event .hero-card__flag { font-size: 0.8rem; letter-spacing: 0.2em; }
.hero-cards__event .hero-card__title { font-size: var(--step-3); }
.hero-cards__event .hero-card__text { min-height: 0; font-size: var(--step-0); }
.hero-cards__event .hero-card__arrow { margin-top: 0; margin-left: auto; }
.hero-cards__event .hero-card__arrow svg { width: 32px; height: 32px; }

/* Short viewports: the band still takes its share, but the type inside it and
   the headline above it come down a step so both fit. */
@media (max-height: 780px) and (min-width: 721px) {
  .hero h1 { font-size: var(--step-2); }
  .hero__inner { padding-block: calc(var(--header-h) + 0.75rem) 0.75rem; }
  .js .hero-stage.is-cards-in .hero h1 { font-size: var(--step-1); }
  .js .hero-stage.is-cards-in .hero-cards__grid { height: clamp(6rem, 38svh, 15rem); }
  .js .hero-stage.is-cards-in .hero-cards__event { height: clamp(6.5rem, 16svh, 9.5rem); }
  .js .hero-card__link { padding: clamp(0.9rem, 2vw, 1.25rem); }
  .js .hero-card__title { font-size: var(--step-1); }
  .js .hero-card__text { font-size: var(--step--1); }
  .js .hero-card__arrow { margin-top: 0.35rem; }
  .js .hero-card__arrow svg { width: 22px; height: 22px; }
  .js .hero-card__icon svg { width: 28px; }
}

/* Two per row on a phone: the band splits evenly between the wrapped lines. */
@media (max-width: 720px) {
  .hero-stage { height: 155vh; height: 155svh; }
  .hero-card { flex: 1 1 140px; }
  .hero-card__text { display: none; }
  .hero-card__title { font-size: var(--step-0); }
  .js .hero-card__title { font-size: var(--step-1); }
  .js .hero-card__link { padding: clamp(0.9rem, 3vw, 1.25rem); }
  .js .hero-card__arrow svg { width: 22px; height: 22px; }
  .js .hero-card__icon svg { width: 30px; }
}
@media (max-width: 420px) {
  .hero-cards__grid { gap: 0.6rem; }
  .hero-card { flex: 1 1 100%; max-width: none; }
  .hero-card__link {
    padding: 0.75rem 0.9rem;
    flex-flow: row wrap; align-items: center; justify-content: space-between;
    text-align: left;
  }
  .hero-card__arrow { margin-top: 0; }
  /* Three stacked panels share the band: one line each, no icon. */
  .js .hero-card__link {
    flex-flow: row wrap; align-items: center; justify-content: space-between;
    padding: 0.9rem 1rem;
    text-align: left;
  }
  .js .hero-card__icon { display: none; }
  .js .hero-card__arrow { margin-top: 0; }
}

/* ---- Section headers -------------------------------------- */
.section-head { max-width: 60ch; margin-bottom: clamp(2rem, 4vw, 3rem); }
.section-head.center { margin-inline: auto; text-align: center; }

/* ---- Grid / cards ----------------------------------------- */
.grid { display: grid; gap: clamp(1.25rem, 2.5vw, 2rem); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid--split { grid-template-columns: 1fr; align-items: center; }
@media (min-width: 860px) { .grid--split { grid-template-columns: 1.1fr 0.9fr; gap: clamp(2rem, 5vw, 4.5rem); } }

.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: clamp(1.5rem, 3vw, 2.25rem);
  transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
}
.card:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: #c3cfe3; }
.card h3 { margin-bottom: 0.5rem; }
.card p { color: var(--body); }
.card__icon { color: var(--blue-600); margin-bottom: 1rem; }
.card__icon svg { width: 40px; height: 40px; }
.card__num {
  font-family: var(--font-serif); font-size: var(--step-2); color: var(--blue-500);
  font-weight: 700; line-height: 1; margin-bottom: 0.75rem; display: block;
}

/* ---- Nuestra esencia: el timón y sus tres cartas -----------
   The «¿Quiénes somos?» band closes on the firm's own principles, and they are
   not laid out — they are steered. The copy holds the left of the band and the
   timón stands on the right, turned off square, with misión, visión and valores
   dealt from its upper rim: one card in hand, the other two turned and
   translucent behind it. One click turns the wheel three handles and the next
   card comes round with the turn.

   Everything below the .essence-js guard depends on main.js having wired the
   wheel. Until it has, the three cards are an ordinary column and the wheel is
   not on the page at all — press.js and star-ci-hub.js build their controls in
   JS for the same reason, and this one is only in the markup because an SVG
   assembled by createElement would be unreadable. */
.essence { display: grid; gap: clamp(1.5rem, 4vw, 3.5rem); align-items: center; }
@media (min-width: 860px) {
  .essence {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
    margin-top: clamp(1rem, 2vw, 2rem);
  }
}

.essence-stack { display: grid; gap: clamp(1rem, 2vw, 1.5rem); }
.essence-card {
  width: 100%;
  padding: clamp(1.25rem, 2.4vw, 2rem);
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
}
.essence-card h3 { color: var(--blue-600); margin-bottom: 0.75rem; }
.essence-card p { color: var(--blue-700); }
.essence-card__values {
  list-style: none; padding: 0;
  display: grid; gap: 0.6rem;
  color: var(--blue-700);
}

/* ---- With JS: the wheel and the hand of cards --------------
   The three cards stop being a column and become three states of one hand:
   .is-active in front, .is-next turned behind it, .is-out on its way off.
   main.js rotates the three round on every turn of the wheel. */
.essence-js .essence-stack {
  gap: 0;
  /* Each card at its own height. The cell keeps the height of the tallest
     (valores) so nothing jumps between turns, but without this the short cards
     stretch to fill it and their border closes around empty space. */
  align-items: start;
}
.essence-js .essence-card { grid-area: 1 / 1; }

.essence__wheel { display: none; }
.essence-js .essence__wheel {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.9rem;
}

.wheel {
  display: block;
  width: 100%;
  max-width: clamp(220px, 30vw, 360px);
  aspect-ratio: 1;
  padding: 0; border: 0; background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.wheel:focus-visible { outline: none; box-shadow: var(--ring); border-radius: 50%; }

.wheel__scene {
  display: block; position: relative;
  width: 100%; height: 100%;
  /* Short perspective on purpose: at 30° of yaw it is what makes the near
     handles come at the reader and the far ones fall away, instead of the
     whole wheel simply looking squashed. */
  perspective: 760px;
}
/* Two copies of the same drawing, stacked in the scene's perspective: the face,
   and fifteen pixels of wood behind it. Both are absolutely positioned so they
   share the scene exactly, and both read --wheel-turn, which main.js sets on the
   scene so the far face never lags the near one. */
.wheel__art {
  display: block;
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  transform-origin: 50% 50%;
  /* The turn is the LAST rotation, so it happens inside the tilt: the wheel
     turns on its axle and the handles foreshorten, rather than the whole
     scene skating around flat. */
  transform: rotateY(-30deg) rotateX(15deg) rotateZ(var(--wheel-turn, 0deg));
  /* Long enough to watch. A helm is heavy: it is thrown, coasts, and settles on
     a spoke — and the point of the gesture is that the reader sees the wheel do
     it, and sees the cards change because it did. At the old 1.05s the turn was
     over before the eye had found it. */
  transition: transform 1.9s cubic-bezier(0.18, 0.72, 0.18, 1), filter 0.3s ease;
  will-change: transform;
}
/* The far face of the wood, pushed back along the wheel's own axle rather than
   offset on the screen: at 30° of yaw that is what shows as thickness at the
   silhouette, and it stays correct through every degree of the turn. */
.wheel__art--back {
  transform: rotateY(-30deg) rotateX(15deg) rotateZ(var(--wheel-turn, 0deg)) translateZ(-15px);
  filter: brightness(0.32) saturate(0.6);
  pointer-events: none;
}
/* The face only: brightening the back plate would flatten the very depth it is
   there to give. */
.wheel:hover .wheel__art:not(.wheel__art--back) { filter: brightness(1.03); }
/* A sibling, never rotated: the wheel turns, its shadow on the ground does not.
   Its width follows the yaw: the more open the wheel stands, the more of its
   own width it comes down on. */
.wheel__shadow {
  position: absolute;
  left: 11%; right: 11%; bottom: 3%; height: 8%;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(14, 26, 43, 0.28), rgba(14, 26, 43, 0) 70%);
}

.essence__hint {
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--blue-500);
}
.essence__hint[hidden] { display: none; }

/* ---- Desktop: the hand rides the upper right of the rim ----
   The wheel takes the left of its column, the cards ride high and right,
   overlapping its upper quadrant. Translucent over the artwork — but blurred
   behind, because a card carrying the firm's misión has to be read through
   nothing but itself. */
@media (min-width: 860px) {
  /* justify-items has to stay stretch here: on a start-aligned grid the
     column's items size to their content, and the wheel's own width: 84%
     would then be resolving against itself. */
  .essence-js .essence__wheel { justify-items: stretch; min-height: 520px; }
  .essence-js .wheel { max-width: none; width: 84%; }

  /* Hung off the top right corner of the column and pushed out past it, into
     the container's own gutter — which is clamp(1.25rem, 5vw, 4rem), so both
     offsets stay well inside it at every width and the page never gains a
     horizontal scrollbar. */
  .essence-js .essence-stack {
    position: absolute;
    top: clamp(-3rem, -2.4vw, -1rem);
    right: clamp(-2.25rem, -1.6vw, -0.5rem);
    /* A floor as well as a cap: at the narrow end of the desktop range 56%
       of the column leaves the card too thin to set the copy in, and too thin
       to reach far enough over the rim to read as resting on it. */
    width: clamp(15rem, 56%, 305px);
    z-index: 2;
    /* The hand covers the middle of the wheel, so left interactive it would
       swallow the click aimed at the very thing it is sitting on. Click
       through instead: anywhere on the wheel or its cards turns the wheel,
       which is the only gesture this band has. */
    pointer-events: none;
  }
  .essence-js .essence-card {
    background: rgba(255, 255, 255, 0.85);
    backdrop-filter: blur(10px) saturate(1.15);
    -webkit-backdrop-filter: blur(10px) saturate(1.15);
    transition:
      opacity 0.5s ease,
      transform 0.62s cubic-bezier(0.2, 0.82, 0.26, 1);
  }
  /* Without an explicit order the last card in the markup paints over the one
     in hand, and its text shows through. The states carry the stacking. */
  .essence-js .essence-card.is-active {
    opacity: 1; transform: none; z-index: 3;
  }
  /* Faint enough that where a taller card outruns the one in hand it reads as
     the edge of a card behind, not as loose words on the page. */
  .essence-js .essence-card.is-next {
    opacity: 0.32; transform: translate(-8%, -10%) rotate(-7deg) scale(0.95); z-index: 2;
  }
  .essence-js .essence-card.is-out {
    opacity: 0; transform: translate(11%, 12%) rotate(8deg) scale(0.95); z-index: 1;
  }
  /* A card leaving the bottom of the hand for the top travels behind the wheel;
     main.js hangs this on it for that one frame so the move does not sweep
     across the face of the card in hand. */
  .essence-js .essence-card.no-anim { transition: none; }

  .essence-js .essence__hint { position: relative; z-index: 3; justify-self: start; }
}

/* ---- Below 860px: no theatre -------------------------------
   Nothing overlaps and nothing fans out. The wheel goes above, one opaque card
   goes below it at full width, and the card wins — it is the content. */
@media (max-width: 859px) {
  .essence-js .wheel { max-width: 260px; }
  .essence-js .essence-card { transition: opacity 0.45s ease; }
  .essence-js .essence-card:not(.is-active) { opacity: 0; visibility: hidden; }
  .essence-js .essence-card.is-active { opacity: 1; visibility: visible; transition-delay: 0.35s; }
}

/* On a phone the wheel is 260px wide and turned most of the way onto its edge,
   so the one noise left in the drawing — the slow mottle that keeps eight
   identical spokes from looking identical — lands under a pixel and buys
   nothing for the offscreen buffer it costs. The CSS filter property overrides
   the filter attribute the group carries, so dropping it here needs no second
   version of the markup. */
@media (max-width: 640px) {
  .tim-wood { filter: none; }
}

@media (prefers-reduced-motion: reduce) {
  .wheel__art { transition: none; }
  .essence-js .essence-card { transition: none; }
  .essence-js .essence-card.is-active { transition-delay: 0s; }
}

/* The band arrives on scroll as one piece — copy, wheel and card together. */
@media (prefers-reduced-motion: no-preference) {
  .js .essence.reveal {
    transform: translateY(2.5rem);
    transition: transform 0.65s cubic-bezier(0.22, 0.8, 0.24, 1), opacity 0.4s ease;
  }
  .js .essence.reveal.is-visible { transform: none; }
}

/* ---- Chips (sectors) -------------------------------------- */
.chips { display: flex; flex-wrap: wrap; gap: 0.6rem; padding: 0; list-style: none; }
.chips li {
  font-size: var(--step--1); font-weight: 500;
  padding: 0.4rem 0.9rem; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line); color: var(--ink);
}
.section--dark .chips li { background: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.14); color: #fff; }

/* ---- Áreas de práctica: sticky stack -----------------------
   Every card pins under the header and the next one slides over it. The whole
   effect is `position: sticky` plus a `top` that steps down one notch per card
   — that step is the lip of the cards already parked, and it is what turns the
   pile into a pile instead of one card covering the last completely.

   The offsets and the paint order are written out per :nth-child rather than
   derived from a custom property set on the element: the CSP serves
   `style-src 'self'` with no 'unsafe-inline', so a style="--i:3" attribute is
   dropped in production and the whole stack would collapse to one offset.

   Nothing between the sticky <li> and the scrolling page may clip or transform,
   or the sticky stops sticking — hence the rounding and overflow live on the
   inner __panel, and .reveal is kept off these cards. */
.section--stack { background: var(--surface-tint); }

.area-stack {
  list-style: none; padding: 0; margin: 0;
  /* Where the first card parks, and the lip each later card leaves of the one
     it covers. The offsets below are still written out per :nth-child for the
     reason above, but the two numbers that shape the whole ladder live here, so
     a media query can retune the stack without touching seven rules. */
  --lip-base: 1.25rem;
  --lip-step: 0.8rem;
}
.area-card { position: sticky; }

.area-card:nth-child(1) { top: calc(var(--header-h) + var(--lip-base)); z-index: 1; }
.area-card:nth-child(2) { top: calc(var(--header-h) + var(--lip-base) + 1 * var(--lip-step)); z-index: 2; }
.area-card:nth-child(3) { top: calc(var(--header-h) + var(--lip-base) + 2 * var(--lip-step)); z-index: 3; }
.area-card:nth-child(4) { top: calc(var(--header-h) + var(--lip-base) + 3 * var(--lip-step)); z-index: 4; }
.area-card:nth-child(5) { top: calc(var(--header-h) + var(--lip-base) + 4 * var(--lip-step)); z-index: 5; }
.area-card:nth-child(6) { top: calc(var(--header-h) + var(--lip-base) + 5 * var(--lip-step)); z-index: 6; }
.area-card:nth-child(7) { top: calc(var(--header-h) + var(--lip-base) + 6 * var(--lip-step)); z-index: 7; }

.area-card__panel {
  display: grid; grid-template-columns: 1.05fr 0.95fr;
  /* Sized to the copy: with the "Qué incluye" lists gone the panels only carry
     a title and a paragraph, but they stay tall enough that the pinned stack
     still reads as a stack rather than a plain list. */
  min-height: clamp(18rem, 55svh, 30rem);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  /* Cast upward: the card reads as sitting on top of the one it just covered. */
  box-shadow: 0 -10px 28px rgba(14, 26, 43, 0.10);
}

.area-card__body {
  display: flex; flex-direction: column;
  padding: clamp(1.75rem, 4vw, 3rem);
}
.area-card__num {
  align-self: flex-end;
  font-family: var(--font-sans); font-size: var(--step--1); font-weight: 600;
  letter-spacing: 0.12em; color: var(--muted); line-height: 1;
}
.area-card h3 { font-size: var(--step-2); margin-top: 0.75rem; }
/* Only one area has one: the three disciplines it merges, named under the title. */
.area-card__sub {
  margin-top: 0.6rem;
  font-family: var(--font-sans); font-size: var(--step--1); font-weight: 600;
  letter-spacing: 0.06em; color: var(--blue-500); max-width: 46ch;
}
.area-card__lede {
  margin-top: 1rem; max-width: 52ch;
  /* A quarter down from the body size: the paragraph is the only copy left on
     the card, and it reads as supporting text under the title rather than
     competing with it. The measure widens to keep the line length sensible. */
  font-size: calc(var(--step-0) * 0.75); color: var(--blue-700);
}

/* Bleeds to the panel edge; the area's own icon, oversized and washed out,
   stands in for a photograph — no new assets, no extra requests. */
.area-card__visual {
  display: grid; place-items: center; padding: 2rem;
  background: linear-gradient(150deg, var(--navy-700), var(--blue-500));
}
.area-card:nth-child(2) .area-card__visual { background: linear-gradient(200deg, var(--blue-600), var(--navy-800)); }
.area-card:nth-child(3) .area-card__visual { background: linear-gradient(150deg, var(--blue-500), var(--navy-700)); }
.area-card:nth-child(4) .area-card__visual { background: linear-gradient(200deg, var(--navy-800), var(--blue-600)); }
.area-card:nth-child(5) .area-card__visual { background: linear-gradient(150deg, var(--navy-900), var(--blue-500)); }
.area-card:nth-child(6) .area-card__visual { background: linear-gradient(200deg, var(--blue-500), var(--navy-900)); }
.area-card:nth-child(7) .area-card__visual { background: linear-gradient(150deg, var(--navy-700), var(--blue-600)); }
.area-card__visual svg {
  width: min(52%, 240px); height: auto;
  color: rgba(255, 255, 255, 0.30);
  /* Beats the stroke-width="1.6" presentation attribute on the markup. */
  stroke-width: 1;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Where an area has a photograph it takes the well whole, edge to edge, and the
   gradient above stays underneath as the colour the card wears while the photo
   loads or if its Storage object goes missing. All seven cards carry one today;
   the icon rules above are what a card falls back to if one is ever taken away.

   A 16:9 photo never matches this well, so every one of them loses something:
   on desktop the well is close to square and the crop takes the sides, while on
   a phone it is a wide band and the crop takes the top and bottom. Which part
   survives is the __img modifier's business — centre unless one says otherwise.
   It has to be a class rather than a style="object-position: …" attribute: the
   CSP serves style-src 'self' with no 'unsafe-inline', so an inline one is
   dropped in production and every photo would silently snap back to centre. */
.area-card__visual--photo { display: block; padding: 0; }
.area-card__img {
  width: 100%; height: 100%; display: block;
  object-fit: cover;
  object-position: 50% 50%;
}
.area-card__img--top    { object-position: 50% 0%; }
.area-card__img--bottom { object-position: 50% 100%; }
.area-card__img--left   { object-position: 0% 50%; }
.area-card__img--right  { object-position: 100% 50%; }
/* object-position is one property, so a photo that needs both — keep the
   bottom AND the left — cannot wear two of the modifiers above. The corners
   are the pairs written out. */
.area-card__img--top-left     { object-position: 0% 0%; }
.area-card__img--top-right    { object-position: 100% 0%; }
.area-card__img--bottom-left  { object-position: 0% 100%; }
.area-card__img--bottom-right { object-position: 100% 100%; }

/* Room for the last card to rest before the section leaves. */
.area-stack__end { height: clamp(2rem, 12vh, 8rem); }

/* Scrolling is what drives the stack, so anyone who asked for less motion gets
   the panels laid out plainly instead. The two-column panel survives — only
   the pinning goes — and the cards lose the height they only needed to fill a
   viewport they no longer take over. */
@media (prefers-reduced-motion: reduce) {
  .area-card { position: static; }
  .area-card + .area-card { margin-top: 1.25rem; }
  .area-card__panel {
    min-height: clamp(18rem, 38svh, 24rem);
    box-shadow: var(--shadow);
  }
  .area-stack__end { display: none; }
}

/* A pinned card can only ever show as much of itself as the room beneath the
   header, and that room is what a short viewport takes away — browser zoom is
   the usual way to get one, since ~150% on a laptop lands around 900×510. Left
   alone the panel outgrows the band and its last lines sit below the fold, with
   the next card sliding over them before they can be reached.

   So on a short viewport the ladder and the padding give ground, not the copy:
   the cards park closer together and the panels carry less air, which is enough
   to bring the whole card back inside the band. The lips thin from 13px to 6px
   — still plainly a stack, and the trade that buys the full card.

   `em` in a media query follows the browser's default font size, so a reader who
   has set a larger one crosses the threshold sooner, which is exactly when they
   need to. min-width keeps this clear of the narrow-screen block below, which
   lays the stack out flat and must keep winning. */
@media (max-height: 40em) and (min-width: 860px) {
  .area-stack { --lip-base: 0.75rem; --lip-step: 0.35rem; }
  .area-card__body { padding: clamp(1rem, 2.2vw, 1.5rem); }
  .area-card__panel { min-height: clamp(12rem, 40svh, 20rem); }
  .area-card__lede { line-height: 1.6; }
}

/* Past a point no amount of tightening fits a card into the band, and a stack
   that hides its own copy is worse than no stack. */
@media (max-height: 29em) and (min-width: 860px) {
  .area-card { position: static; }
  .area-card + .area-card { margin-top: 1.25rem; }
  .area-stack__end { display: none; }
}

/* Narrow screens have no room for a card that tall, and a stack that overlaps
   itself on a phone hides the copy it is meant to show. The visual leads and
   the panel becomes one column. */
@media (max-width: 859px) {
  .area-card { position: static; }
  .area-card + .area-card { margin-top: 1.25rem; }
  .area-card__panel {
    grid-template-columns: 1fr; min-height: 0;
    box-shadow: var(--shadow);
  }
  .area-card__visual { min-height: 8rem; padding: 1.25rem; order: -1; }
  .area-card__visual svg { width: min(30%, 120px); }
  /* Tall enough that the photo reads as a photo instead of a letterbox slot:
     at a phone's width this lands near the photos' own 16:9, so the band takes
     only a little off the sides. */
  .area-card__visual--photo { min-height: 14rem; padding: 0; }
  .area-stack__end { display: none; }
}

/* ---- The band the programmes ride on -----------------------
   The photograph misión/visión/valores used to stand on. When that block moved
   into ¿Quiénes somos? the six programas took its place in the page but not its
   background, and the band went flat; this puts the picture back exactly as it
   was — cover, centred, white underneath while the JPEG loads.

   Scoped to the id, not to .section--stack: that class is shared with #areas,
   which keeps the flat tint. An id (1,0,0) outranks .section--alt and
   .section--stack (0,1,0), so nothing here needs !important. */
#programas {
  background-color: var(--surface);
  background-image: url("https://firebasestorage.googleapis.com/v0/b/sllm-ai.firebasestorage.app/o/landing%2Flanding-highlines.jpg?alt=media");
  background-size: cover;
  background-position: 50% 50%;
}
/* Nothing stood on that photograph bare: the misión/visión/valores cards floated
   on it at 88% white over a small blur, and that is what let dark blue type read
   over the brightest part of the picture. The head gets the same plate, for the
   same reason. */
#programas .section-head {
  max-width: 46rem;
  padding: clamp(1.25rem, 3vw, 2rem) clamp(1.5rem, 3vw, 2.5rem);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  background: rgba(255, 255, 255, 0.88);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

/* ---- Programas: sideways sticky rail -----------------------
   The practice-area stack, turned on its side. Everything the block above says
   still holds — the pile is `position: sticky` plus an offset that steps one
   notch per card, and the offsets and paint order are written out per
   :nth-child because the CSP serves `style-src 'self'` and would drop a
   style="--i:3" attribute in production. The one difference is the axis: `left`
   instead of `top`, inside a strip that scrolls horizontally instead of the
   page that scrolls down.

   Which also changes what the pile is measured against. A sticky offset is read
   from the scrollport — here the rail's own padding box, not the header — so
   --lip-base is the gutter and nothing else: that is exactly where the first
   card already sits at rest, so it pins without a jump and the ladder grows
   from there.

   Nothing between the sticky <li> and the rail may clip or transform, or the
   sticky stops sticking — hence the rounding and overflow live on the inner
   __panel, and .reveal is kept off these cards. */
/* ---- Programas: the pin that turns page-scroll sideways ----
   The rail below is a horizontal scroller and always was; this is only what
   drives it. The pin is a tall empty box, the stage inside it sticks under the
   header for the pin's whole length, and main.js reads how far down the pin the
   page has come and sets the rail's scrollLeft to exactly that. So one ordinary
   downward scroll walks the six cards past, and the section holds the screen
   until the last of them has parked — the same bargain #areas strikes with its
   vertical stack, on the other axis.

   The height is the whole mechanism: --pin-run is written by main.js as the
   number of pixels the rail still has to travel, so the run down the page and
   the run along the rail are the same distance and the two cannot drift apart
   however many cards the list grows to. It is a custom property set from script
   rather than a style="" attribute because the CSP serves `style-src 'self'`
   and would drop the attribute in production.

   All of it hangs off .has-programa-pin, which main.js adds once it is wired.
   Without the script — or under the flatten conditions further down, where the
   six are laid out plainly down the page — none of this applies and the rail is
   the plain sideways scroller with arrows that it has always been. */
/* The stage's own height plus the travel, and not a pixel more: a sticky element
   is stuck for exactly the difference between its box and its container's, so
   any extra here is scrolling that holds the screen with the rail already at the
   end of its run. */
.has-programa-pin .programa-pin {
  position: relative;
  height: calc(100vh - var(--header-h) + var(--pin-run, 0px));
  height: calc(100svh - var(--header-h) + var(--pin-run, 0px));
}
.has-programa-pin .programa-pin__stage {
  position: sticky; top: var(--header-h);
  height: calc(100vh - var(--header-h));
  height: calc(100svh - var(--header-h));
  display: flex; align-items: center;
}
.has-programa-pin .programa-rail-wrap { width: 100%; }
/* The page is the only thing that moves the rail now. Hidden rather than auto so
   a sideways trackpad flick cannot race the handler and be snapped back on the
   next frame — it is still a scrollport, so the cards keep sticking and
   scrollLeft still sets — and `smooth` comes off because it would animate every
   assignment the scroll handler makes. */
.has-programa-pin .programa-rail {
  overflow-x: hidden; scroll-behavior: auto; touch-action: pan-y;
}
/* Cover on a box five viewports tall would blow the photograph up to nothing;
   pinned to the viewport it stays the picture it is behind the moving cards. */
.has-programa-pin #programas { background-attachment: fixed; }
/* Held on the screen, a card sized by its copy leaves most of the screen under
   it empty. It takes the stage instead, less a margin at top and bottom so the
   parked lips and the shadow still have somewhere to fall. */
.has-programa-pin .programa-card__panel {
  min-height: min(44rem, calc(100vh - var(--header-h) - 4rem));
  min-height: min(44rem, calc(100svh - var(--header-h) - 4rem));
}

.programa-rail-wrap { position: relative; }

.programa-rail {
  --lip-base: var(--gutter);
  --lip-step: 0.8rem;
  /* No inline padding, deliberately: --lip-base already holds the first card a
     gutter in from the edge and holds it there from the first pixel, so padding
     would only push it a second gutter over — and padding at the far end of a
     horizontal scroller is not reliably part of the scroll range. The spacer at
     the end of the list is what buys the last card its room instead. */
  list-style: none; margin: 0; padding: 0;
  display: flex; align-items: stretch;
  /* No gap: the cards overlap, and the lip is the only spacing there is. */
  gap: 0;
  overflow-x: auto;
  /* The page below should not start scrolling when the rail runs out. */
  overscroll-behavior-x: contain;
  scroll-behavior: smooth;
  scrollbar-width: thin;
  scrollbar-color: #c3cfe3 transparent;
}
@media (prefers-reduced-motion: reduce) { .programa-rail { scroll-behavior: auto; } }
.programa-rail::-webkit-scrollbar { height: 8px; }
.programa-rail::-webkit-scrollbar-track { background: transparent; }
.programa-rail::-webkit-scrollbar-thumb { background: #c3cfe3; border-radius: 999px; }
/* The rail takes focus so it can be scrolled with the arrow keys; say so. */
.programa-rail:focus-visible { outline: 2px solid var(--blue-500); outline-offset: -2px; }

/* Wide enough to hold three labelled paragraphs beside a photograph, and still
   narrow enough that the next card shows at the right edge on a laptop. */
.programa-card { position: sticky; flex: 0 0 min(86vw, 900px); }

.programa-card:nth-child(1) { left: var(--lip-base); z-index: 1; }
.programa-card:nth-child(2) { left: calc(var(--lip-base) + 1 * var(--lip-step)); z-index: 2; }
.programa-card:nth-child(3) { left: calc(var(--lip-base) + 2 * var(--lip-step)); z-index: 3; }
.programa-card:nth-child(4) { left: calc(var(--lip-base) + 3 * var(--lip-step)); z-index: 4; }
.programa-card:nth-child(5) { left: calc(var(--lip-base) + 4 * var(--lip-step)); z-index: 5; }
.programa-card:nth-child(6) { left: calc(var(--lip-base) + 5 * var(--lip-step)); z-index: 6; }

.programa-card__panel {
  display: grid; grid-template-columns: 1.15fr 0.85fr;
  height: 100%;
  /* Taller than an area panel: these cards carry three labelled blocks rather
     than one paragraph, and the copy is what sets the floor. */
  min-height: clamp(22rem, 62svh, 34rem);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  /* Cast leftward, the direction the card came from: it reads as sitting on top
     of the one it just covered. The vertical stack casts upward for the same
     reason. */
  box-shadow: -10px 0 28px rgba(14, 26, 43, 0.10);
}

.programa-card__body {
  display: flex; flex-direction: column;
  padding: clamp(1.5rem, 3vw, 2.5rem);
}
.programa-card__num {
  align-self: flex-end;
  font-family: var(--font-sans); font-size: var(--step--1); font-weight: 600;
  letter-spacing: 0.12em; color: var(--muted); line-height: 1;
}
.programa-card h3 { font-size: var(--step-2); margin-top: 0.75rem; color: var(--blue-600); }
/* The line the programme sells itself in, where it has one — four of the six do. */
.programa-card__sub {
  margin-top: 0.6rem;
  font-family: var(--font-sans); font-size: var(--step--1); font-weight: 600;
  letter-spacing: 0.06em; color: var(--blue-500); max-width: 46ch;
}
/* Para quién / Qué incluye / Promesa de valor. Same size as the area lede: it is
   supporting copy under the title, not competing with it. */
.programa-card__line {
  margin-top: 0.9rem; max-width: 60ch;
  font-size: calc(var(--step-0) * 0.75); color: var(--blue-700);
}
.programa-card__line strong { color: var(--blue-600); }
/* Only PRIME has one: the footnote about how the certification relates to the
   other five programmes. Set apart by a rule and a smaller size so it does not
   read as a fourth block. */
.programa-card__note {
  margin-top: 1.1rem; padding-top: 0.9rem; max-width: 60ch;
  border-top: 1px solid var(--line);
  font-size: calc(var(--step-0) * 0.68); color: var(--muted);
}

/* Same well as the area cards: the photograph takes it whole, and the gradient
   stays underneath as the colour the card wears while the picture loads or if
   its Storage object is missing — which is every card until the six are
   uploaded to the landing/ prefix. */
.programa-card__visual {
  display: grid; place-items: center; padding: 0;
  background: linear-gradient(150deg, var(--navy-700), var(--blue-500));
}
.programa-card:nth-child(2) .programa-card__visual { background: linear-gradient(200deg, var(--blue-600), var(--navy-800)); }
.programa-card:nth-child(3) .programa-card__visual { background: linear-gradient(150deg, var(--blue-500), var(--navy-700)); }
.programa-card:nth-child(4) .programa-card__visual { background: linear-gradient(200deg, var(--navy-800), var(--blue-600)); }
.programa-card:nth-child(5) .programa-card__visual { background: linear-gradient(150deg, var(--navy-900), var(--blue-500)); }
.programa-card:nth-child(6) .programa-card__visual { background: linear-gradient(200deg, var(--blue-500), var(--navy-900)); }
.programa-card__visual--photo { display: block; }
.programa-card__img {
  width: 100%; height: 100%; display: block;
  object-fit: cover;
  object-position: 50% 50%;
}

/* Room for the last card to come to rest before the rail ends. */
.programa-rail__end { flex: 0 0 clamp(2rem, 10vw, 8rem); }

/* Arrows, built by main.js and positioned against the wrap. Same object as the
   S.T.A.R hub's, so the two horizontal walls on this site are paged the same
   way. */
.programa-rail__nav {
  position: absolute; top: calc(50% - 1.5rem); z-index: 7;
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  font-size: 1.6rem; line-height: 1; color: var(--ink);
  background: rgba(255,255,255,0.94); border: 1px solid var(--line); border-radius: 999px;
  box-shadow: var(--shadow); cursor: pointer;
  transition: background 0.2s, opacity 0.2s;
}
.programa-rail__nav--prev { left: 0.5rem; }
.programa-rail__nav--next { right: 0.5rem; }
.programa-rail__nav:hover { background: #fff; }
.programa-rail__nav:disabled { opacity: 0; pointer-events: none; }
.programa-rail__nav[hidden] { display: none; }

/* A short viewport takes the height the panel needs, so the padding and the lip
   give ground before the copy does — the same trade the vertical stack makes. */
@media (max-height: 40em) and (min-width: 860px) {
  .programa-rail { --lip-step: 0.35rem; }
  .programa-card__body { padding: clamp(1rem, 2.2vw, 1.5rem); }
  .programa-card__panel { min-height: clamp(16rem, 46svh, 24rem); }
  .programa-card__line { margin-top: 0.6rem; line-height: 1.6; }
}

/* Sideways scrolling is what drives the pile, so anyone who asked for less
   motion gets the six laid out plainly down the page instead — and so does
   every narrow screen, where a card wide enough for two columns does not fit
   and a pile would hide the copy it exists to show. Same flattening either way,
   so the two conditions share one block. */
@media (max-width: 859px), (prefers-reduced-motion: reduce) {
  /* Laid out down the page the rail is no longer a full-bleed strip, so it takes
     .container's own width and gutter and the cards line up with everything
     else on the page. */
  .programa-rail {
    display: block; overflow-x: visible;
    width: 100%; max-width: var(--maxw); margin-inline: auto;
    padding-inline: var(--gutter);
  }
  .programa-card { position: static; }
  /* No pin here either: the six are a plain column, so there is nothing to hold
     the screen for. main.js drops the flag when it sees this breakpoint, but the
     unwinding is written out anyway so that the flattened layout never depends
     on a listener having fired — and it carries .has-programa-pin so it outweighs
     the rules it is undoing. */
  .has-programa-pin .programa-pin,
  .has-programa-pin .programa-pin__stage { height: auto; position: static; display: block; }
  .has-programa-pin .programa-rail { overflow-x: visible; touch-action: auto; }
  .has-programa-pin #programas { background-attachment: scroll; }
  .programa-card + .programa-card { margin-top: 1.25rem; }
  .programa-card__panel {
    grid-template-columns: 1fr; min-height: 0;
    box-shadow: var(--shadow);
  }
  /* Tall enough that the photo reads as a photo instead of a letterbox slot. */
  .programa-card__visual { order: -1; min-height: 14rem; }
  .programa-rail__end { display: none; }
  .programa-rail__nav { display: none; }
}

/* ---- Reconocimientos: the roll ------------------------------
   Four identical tracks side by side, slid the width of exactly one of them —
   from -25% of the four to 0. At the end of the run the second track stands
   where the first started, so the loop closes on itself with no seam and nothing
   has to be measured, cloned or re-ordered from script. Towards zero, so the
   marks drift to the right.

   Sliding one track and covering the band are two different requirements, and
   getting them confused is what opened a gap at the right-hand edge once a
   cycle when this was two tracks: the travel was right, but at the top of the
   cycle the pair only reached one track's width across a band wider than that,
   and the rest of it had nothing under it. Four tracks keep three under the
   band at every phase, and the min-width below keeps a track at half the band
   however wide the screen and however few the marks — so three of them cover it
   with half a band to spare, and the roll cannot run out.

   Slow on purpose — a little over a minute and a half for a full turn. It is a
   band a visitor reads past, not something asking to be watched, and a marquee at
   any brisker pace pulls the eye off the page around it.

   The number to hold steady is the speed, not the duration, and the two part
   company as soon as the contents change: the travel is one track's width
   whatever that width, so a wider track covered in the same time reads faster,
   and the same track given longer reads slower. What this band has always run at
   is 12–18 px/s, rising with the viewport — six fixed plates over 72s. The nine
   fitted ones make a track about 1.4x as wide, so 100s puts them back on that
   same 12–18 px/s at every width. 140s was the first guess here and was wrong the
   other way: it assumed the track had doubled, and left the band crawling at 8–13
   px/s, slower than it had ever run. Measure it again if the row ever changes
   again — a duration carried forward is a speed quietly changed.

   The mask is what keeps it from looking like a strip that got cut: the marks
   fade out at both ends of the band instead of stopping at an edge. */
.allies {
  /* The plate geometry, in one place — see the plate rule below for what each
     one does and why everything there is derived from them. */
  --mark-h: clamp(2.9rem, 4.1vw, 3.95rem);
  --plate-pad-block: clamp(0.5rem, 1.2vw, 0.9rem);
  --plate-pad-inline: clamp(0.75rem, 1.6vw, 1.25rem);

  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.allies__marquee {
  display: flex; width: max-content;
  animation: allies-roll 100s linear infinite;
}
/* Held still while it is being looked at or tabbed through. */
.allies:hover .allies__marquee,
.allies:focus-within .allies__marquee { animation-play-state: paused; }
@keyframes allies-roll {
  from { transform: translateX(-25%); }
  to { transform: translateX(0); }
}
.allies__track {
  display: flex; align-items: center; justify-content: space-around;
  /* Never narrower than half the band, so three tracks always cover it. On any
     ordinary screen the nine marks are already wider than this and it does
     nothing; it is what holds the loop together on a very wide one. */
  min-width: 50vw;
  list-style: none; margin: 0; padding: 0;
  gap: clamp(1.25rem, 3vw, 2.5rem);
  padding-inline: clamp(0.6rem, 1.5vw, 1.25rem);
}
/* A plate under every mark. The band is navy and the files come in whatever
   colours their owners use them in; a light card is what lets any of them read
   here without anyone having to touch the artwork.

   The three lengths below are the whole geometry, and everything else is derived
   from them so the pieces cannot drift apart:

   --mark-h is the optical line — the height no mark rises above, which is what
   makes nine files of nine proportions read as one row.
   --plate-pad-* is the air around a mark.
   The plate's height is those added up, so it is always exactly the line plus
   its air; the plate's width is left to `auto` and comes out at whatever the
   mark inside it turned out to need. That is the point of the arrangement: these
   run from a 4.8:1 wordmark (Latin Lawyer) to a square badge (Prime), and a
   single box wide enough for the first strands the second in white space. Fixed
   height, fitted width, `place-items: center` holding the mark in the middle of
   it.

   Absolute lengths and not percentages, which is worth saying because the
   obvious version does not work: `max-height: 100%` on the mark resolves against
   an indefinite grid area and comes out as `none`, and half of these then hang
   out of their own plate — Tops México at 127px in an 88px box. Give the mark a
   real length and the browser resolves it in one pass. */
.ally {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: auto;
  /* A floor for the day a mark does not arrive. Fitted width means the plate is
     only as wide as what is inside it, and an image that fails to load is
     nothing wide — the plate would come out a 40px sliver of its own padding
     with a gap in the row where a mark should be. Set below the narrowest plate
     any of the nine actually produces at any width, so it never binds while they
     are all there. */
  min-width: calc(var(--mark-h) + var(--plate-pad-inline));
  height: calc(var(--mark-h) + 2 * var(--plate-pad-block));
  padding: var(--plate-pad-block) var(--plate-pad-inline);
  /* Flat #fff and not the softened white a plate would normally take here: every
     one of these files is an opaque raster on its own white field, so anything
     less than pure white draws a visible rectangle where the file ends and the
     plate carries on. At #fff the two whites meet and the pair reads as the one
     box the mark is centred in — the padding is then just the air that keeps the
     artwork off the rounded corners. */
  background: #fff;
  border-radius: var(--radius);
  box-shadow: 0 6px 18px rgba(6, 14, 26, 0.18);
}
/* Two ceilings and no floor: a mark comes down to whichever it meets first, keeps
   its own proportions doing it, and the plate is drawn around the result. The
   width ceiling is the stop for the very wide ones — Latin Lawyer at 4.8:1 would
   otherwise run past 18rem and turn the row into one long banner and eight
   afterthoughts. No mark is scaled up past its own pixels either, which is the
   other thing max- buys over a set size: none of these are large files.

   Both autos are load-bearing. Every mark carries its true pixel size in width=
   and height= so the band does not shift as the files arrive, and those
   attributes land on the CSS width and height — leave them standing and the two
   ceilings clamp each axis on its own, which squashes the mark instead of
   scaling it. Reset to auto they go back to being an aspect ratio, which is what
   they are wanted for. */
.ally__logo {
  width: auto; height: auto;
  max-height: var(--mark-h);
  max-width: clamp(8rem, 13vw, 11rem);
}
/* The square ones — HRC, Tops México, Prime, Hecho en México. A badge run to the
   same height as a wordmark reads heavier than it, because a wordmark is a line
   of type with air in it and a badge is a solid mark; a little shorter and they
   sit level down the row instead of punching out of it. Only a little: these
   carry fine detail and are the first thing to go illegible. */
.ally--badge .ally__logo { max-height: calc(var(--mark-h) * 0.88); }

/* Nothing drifting for anyone who asked for less motion: the nine sit still and
   the band scrolls by hand instead. The readable track is the one that stays —
   it is the one a screen reader is meant to reach. */
@media (prefers-reduced-motion: reduce) {
  .allies { overflow-x: auto; }
  .allies__marquee { animation: none; transform: none; }
  .allies__track[aria-hidden="true"] { display: none; }
}

/* ---- Media + text ----------------------------------------- */
.media { border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); background: var(--surface); }
.media img { width: 100%; height: 100%; object-fit: cover; }

/* ---- Team ------------------------------------------------- */
.team-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.team-card img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; background: var(--surface); }
.team-card .team-body { padding: 1.25rem 1.5rem 1.75rem; }
.team-card h3 { margin-bottom: 0.15rem; }
.team-card .role { color: var(--blue-600); font-weight: 600; font-size: var(--step--1); letter-spacing: 0.04em; text-transform: uppercase; }
.team-card p { margin-top: 0.75rem; color: var(--body); font-size: var(--step-0); }

/* ---- Prose lists ------------------------------------------ */
.tick { list-style: none; padding: 0; display: grid; gap: 0.75rem; }
.tick li { position: relative; padding-left: 1.9rem; }
.tick li::before {
  content: ""; position: absolute; left: 0; top: 0.55em; width: 0.7rem; height: 0.4rem;
  border-left: 2px solid var(--blue-500); border-bottom: 2px solid var(--blue-500);
  transform: rotate(-45deg);
}

/* ---- Contact / forms -------------------------------------- */
.form { display: grid; gap: 1.1rem; }
.field { display: grid; gap: 0.4rem; }
.field label { font-weight: 600; font-size: var(--step--1); color: var(--ink); }
.field input, .field textarea, .field select {
  font: inherit; color: var(--ink);
  padding: 0.75rem 0.9rem; border: 1px solid #c6d0e0; border-radius: var(--radius);
  background: var(--surface); width: 100%;
}
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--blue-500); box-shadow: var(--ring); }
.field textarea { min-height: 140px; resize: vertical; }
.form-note { font-size: var(--step--1); color: var(--muted); }
.form-status { font-size: var(--step-0); font-weight: 600; }
.form-status[data-state="ok"] { color: #1f7a4d; }
.form-status[data-state="err"] { color: #b3261e; }
/* Honeypot: a field only a bot fills in. Off the page for everyone else, and
   rejected server-side when it comes back with a value. Shared by the contact
   form and the «Evalúa tu empresa» identification step. */
.form-hp, .eval-hp { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* One column: the offices aside that used to sit beside the form is gone, so the
   form gets the width. Capped rather than let loose across the full container —
   past ~52rem the labels drift too far from the fields they name. */
.contact-grid { display: grid; gap: clamp(2rem, 5vw, 4rem); }
/* The cap goes on the column, not on .contact-grid — that element is also the
   .container, and capping it there would fight its margin-inline: auto and
   centre the form away from the page hero it sits under. */
.contact-grid > * { max-width: 52rem; }
.contact-intro { margin: 0.75rem 0 1.5rem; }

/* ---- CTA band --------------------------------------------- */
.cta-band { text-align: center; }
.cta-band h2 { color: #fff; max-width: 22ch; margin-inline: auto; }
.cta-band p { color: #fff; max-width: 52ch; margin: 1rem auto 0; }


/* ---- Loading gift -----------------------------------------
   The sailboat is the site-wide loading indicator. Sits on a --navy-800 band,
   which is the GIF's own background, so there is no visible frame around it. */
/* A dark band straight after the page header is the same navy, so their stacked
   padding reads as dead space rather than separation. */
.page-hero + .section--dark { padding-top: 0; }

.loader { display: grid; justify-items: center; gap: 1rem; text-align: center; }
.loader__img { width: clamp(160px, 32vw, 220px); height: auto; }
.loader__text {
  font-family: var(--font-sans); font-size: var(--step--1); font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
}
.loader__note { font-family: var(--font-serif); font-size: var(--step-1); max-width: 44ch; }

/* ---- Page loading curtain ---------------------------------
   Press and the S.T.A.R C.I. HUB open on a full-bleed carousel, so the first
   thing a visitor used to see was the page assembling itself: empty slide
   boxes, a header logo arriving late, and on the hub a carousel rebuilt a
   second time once /api/starhub answered. The curtain covers all of that and
   lifts once the photographs are on the page.

   Same sailboat as .loader above, on the same --navy-800 that band uses. The
   GIF's own background is #0e1a2a — one step off this token and invisible at
   that distance — so it reads as a boat on the curtain rather than a picture
   of one. It reuses .loader__img for the sizing rather than restating it.

   Everything is behind .has-page-loader, a class only js/page-loader.js adds:
   with no JavaScript the curtain never shows and the page is exactly what it
   was before. z-index clears the sticky header (50) and the skip link (100). */
.page-loader {
  display: none; place-items: center;
  position: fixed; inset: 0; z-index: 200;
  background: var(--navy-800);
  opacity: 1; visibility: visible;
  transition: opacity 0.5s ease;
}
.has-page-loader .page-loader { display: grid; }

/* Nothing scrolls behind the curtain — partly so the visitor cannot scroll a
   page that is not ready yet, and partly because js/page-loader.js decides
   which images to wait for by testing them against the viewport. */
.has-page-loader:not(.is-page-loaded) { overflow: hidden; }

.has-page-loader.is-page-loaded .page-loader {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.5s ease, visibility 0s linear 0.5s;
}

@media (prefers-reduced-motion: reduce) {
  .page-loader,
  .has-page-loader.is-page-loaded .page-loader { transition: none; }
}

/* «Evalúa tu empresa» builds its whole tool in JavaScript — 182 indicators —
   and keeps [data-eval-root] hidden until it is done, so the page sat empty
   under the hero while evalua.js worked. Its curtain starts below the site
   header rather than covering the window: the nav stays visible and usable
   while the tool is being built, which is the only difference from Press and
   the hub. --header-h is the same constant the sticky offsets on this page
   already use, and it sits under the header's own z-index: 50. */
.page-loader--below-header { top: var(--header-h); z-index: 40; }

/* ---- IBA page: cover video + reversed split ---------------
   The video sits just under the header rather than fully below it: --cover-lift
   pulls the section up by that much, so the top edge of the frame tucks behind
   the header band and the whole page rides up with it. Same technique the home
   page uses on .hero-stage, and the one number to change if the crop wants a few
   pixels more or less. The header is 92% opaque, so the strip behind it is dimmed
   rather than gone — exactly as it already is behind the home hero; past the
   header's own 101px the lift would start cropping the frame outright, which is
   the ceiling on this number.

   The clip is a designed frame — the Copenhagen photograph, the IBA lettering
   and the SLLM mark are composed inside it — so none of it may be cropped. The
   section therefore takes the clip's own shape rather than a screen height:
   landing/IBA-header.mp4 is 1920x1080, so 16/9 at full width shows the whole
   frame and nothing else. Reserving the ratio in CSS also means the box is the
   right size before the video loads, so the page below it never jumps.

   `contain` rather than `cover` for the same reason: the box already matches the
   clip, so it changes nothing today, but if the video is ever re-exported at
   another shape it letterboxes against the navy instead of silently cropping
   the artwork. The last frame freezes exactly where it was either way. */
.video-cover {
  position: relative; overflow: hidden;
  aspect-ratio: 16 / 9;
  background: var(--navy-800);
  --cover-lift: 60px;
  margin-top: calc(-1 * var(--cover-lift));
}
.video-cover__el { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }

/* Screen-reader-only text (same technique .hero__sound-text uses inline). */
.u-visually-hidden {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* Copy column of a --split band: space between stacked paragraphs without an
   inline style (the CSP forbids those). */
.split-copy p + p { margin-top: 1.25rem; }

/* ---- Quiénes somos ----------------------------------------
   A plain white band carrying the whole passage about the firm: the copy on the
   left, and misión / visión / valores stacked beside it (see .essence-card).
   The two used to be separate bands with a photograph between them; the type
   here is the blues those cards already wore, which is what kept them reading
   as one passage and is now simply what they are. */
/* The base rules margin-reset everything inside, so the heading and the serif
   lede under it would otherwise run together — the same breath the cards take. */
/* The card hangs out past the container into the gutter, and the one leaving
   the hand travels further right still before it fades. Between 860px and the
   1200px cap the container is already touching the gutter, so that departing
   card would otherwise add a horizontal scrollbar to the whole page. Clip, not
   hidden: clip does not make this a scroll container, and the band is full
   width so nothing inside the viewport is lost. */
.about-intro { overflow-x: clip; overflow-y: visible; }
.about-intro h2 { color: var(--blue-600); margin-bottom: 1.25rem; }
.about-intro p { color: var(--blue-700); }
.about-intro .lede { color: var(--blue-500); }

/* Same reason: a paragraph that needs room above it, and a button row that needs
   centring, without a style="" attribute the CSP would drop. */
.stack-copy { margin-top: 1.25rem; }
.btn-row--center { justify-content: center; margin-top: 1.75rem; }

/* Image on the left, copy on the right. The DOM keeps copy first so the
   stacked phone layout still reads heading -> copy -> image; only the desktop
   band flips, the same way .area-card__visual does. */
@media (min-width: 860px) {
  .grid--split--reverse { grid-template-columns: 0.9fr 1.1fr; }
  .grid--split--reverse .media { order: -1; }
}

/* ---- Footer ----------------------------------------------- */
.site-footer { background: var(--navy-900); color: #fff; padding-block: clamp(1.5rem, 3vw, 2rem); }
.site-footer a { color: #fff; text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; }

/* Social row above the privacy/copyright bar. Monochrome by design: the marks
   inherit currentColor from the footer, so no brand colours land on the navy. */
.footer-social { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.footer-social a {
  display: inline-flex; padding: 0.4rem; margin: -0.4rem 0;
  color: rgba(255,255,255,0.78);
}
.footer-social a:hover, .footer-social a:focus-visible { color: #fff; text-decoration: none; }
.footer-bottom {
  display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: space-between;
  font-size: var(--step--1); color: rgba(255,255,255,0.78);
}

/* ---- Breadcrumb / page header ----------------------------- */
.page-hero { background: var(--navy-800); color: #eef2f7; }
.page-hero .container { padding-block: clamp(3rem, 2rem + 5vw, 5rem); }
.page-hero h1 { color: #fff; }
.page-hero p { color: #fff; max-width: 60ch; margin-top: 1rem; font-family: var(--font-serif); font-size: var(--step-1); }

/* Reveal-on-scroll (progressive; visible by default if no JS) */
.reveal { opacity: 1; }
.js .reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.5s ease, transform 0.5s ease; }
.js .reveal.is-visible { opacity: 1; transform: none; }

/* ---- S.T.A.R C.I. HUB --------------------------------------
   Three pieces, in page order: a cross-fading band of four featured slides, a
   filter bar, and the wall of articles.

   Two rules govern everything below and neither is negotiable:

   1. The CSP serves `style-src 'self'` with no 'unsafe-inline', so a
      style="--span:8" on a card is dropped in production. Every card shape is
      therefore driven by a :nth-child cycle, the same reason the practice-area
      pile writes its offsets out per :nth-child too.
   2. The slides and the cards are real markup, not rendered by script. With JS
      off the first slide and the entire wall are still on screen and every link
      still works; only the timer, the search box and the arrows go away. */

/* ---- Featured slides -------------------------------------- */
/* The slides ARE the page's header: there is no title band above them. The
   negative margin pulls the band up under the sticky header the same way
   .video-cover does on the IBA page, so the first screen is the photograph from
   its first pixel and the nav floats over it (the header carries z-index: 50,
   so it keeps painting on top without anything else changing). */
.hub-slider {
  position: relative;
  background: var(--navy-900);
  height: 100vh; height: 100svh;
  min-height: 420px;
  /* header height + its 1px bottom border */
  margin-top: calc(-1 * (var(--header-h) + 1px));
}
.hub-slider__track { list-style: none; padding: 0; margin: 0; height: 100%; }

/* Slides are stacked, so only opacity separates them. `visibility` rides along
   to keep the three hidden ones out of the tab order and the a11y tree; its
   transition is delayed to the end of the fade so it never cuts one short. */
.hub-slide {
  position: absolute; inset: 0;
  opacity: 0; visibility: hidden;
  transition: opacity 0.6s ease, visibility 0s linear 0.6s;
}
.hub-slide:first-child { opacity: 1; visibility: visible; transition: opacity 0.6s ease; }
/* From here on the script owns which slide shows. Until it runs the rule above
   holds, which is what leaves slide 1 up for a visitor without JavaScript. */
.hub-js .hub-slide:first-child { opacity: 0; visibility: hidden; transition: opacity 0.6s ease, visibility 0s linear 0.6s; }
.hub-js .hub-slide.is-active { opacity: 1; visibility: visible; transition: opacity 0.6s ease; }

.hub-slide__link { display: block; position: relative; height: 100%; text-decoration: none; color: #fff; }
.hub-slide__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Photographs vary; the wash is what guarantees the caption stays legible. */
.hub-slide__link::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(10,20,33,0.15) 0%, rgba(10,20,33,0.55) 55%, rgba(10,20,33,0.88) 100%);
}
.hub-slide__body {
  position: absolute; inset-inline: 0; bottom: 0; z-index: 2;
  width: 100%; max-width: var(--maxw); margin-inline: auto;
  padding: var(--gutter);
  padding-bottom: clamp(3rem, 6vw, 4.5rem);
}
/* .eyebrow defaults to --blue-600, which is unreadable over a darkened photo;
   the same override .hero and .page-hero make for themselves. */
.hub-slider .eyebrow { color: #fff; }
.hub-slide__title { color: #fff; font-size: var(--step-3); max-width: 20ch; }
.hub-slide__text { color: rgba(255,255,255,0.86); font-family: var(--font-serif); font-size: var(--step-1); max-width: 52ch; margin-top: 0.75rem; }
.hub-slide__link:hover .hub-slide__title { text-decoration: underline; text-underline-offset: 4px; }

.hub-slider__dots {
  position: absolute; z-index: 3; left: 0; right: 0; bottom: clamp(1rem, 2.5vw, 1.75rem);
  display: flex; gap: 0.5rem; justify-content: center;
}
.hub-dot {
  width: 34px; height: 4px; padding: 0; border: 0; border-radius: 999px;
  background: rgba(255,255,255,0.35); cursor: pointer;
  transition: background 0.25s, transform 0.25s;
}
.hub-dot:hover { background: rgba(255,255,255,0.6); }
.hub-dot[aria-current="true"] { background: #fff; transform: scaleY(1.6); }

/* ---- Filter bar -------------------------------------------
   Written into the markup with `hidden` and revealed by the script, so the
   control never appears for a visitor whose JS is blocked — for them the wall
   is simply unfiltered, which is the whole catalogue and still correct. */
.hub-filter[hidden] { display: none; }
/* Dos renglones: los tres botones de tipo arriba, el buscador debajo. Cada
   renglón es su propio flex, así que el buscador y el contador siguen
   alineándose entre sí sin que los botones entren en esa cuenta. */
.hub-filter {
  display: flex; flex-direction: column; align-items: stretch;
  gap: 0.9rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
}
.hub-filter__search {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.75rem 1rem;
}
.hub-filter__input {
  flex: 1 1 260px; max-width: 380px;
  font-family: var(--font-sans); font-size: var(--step-0); color: var(--ink);
  padding: 0.7rem 0.95rem;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  transition: border-color 0.2s, box-shadow 0.2s;
}
.hub-filter__input::placeholder { color: var(--muted); }
.hub-filter__input:focus { outline: none; border-color: var(--blue-500); box-shadow: var(--ring); }
.hub-filter__tags { display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0; margin: 0; list-style: none; }
/* Same shape as .chips, but interactive — and carrying the hero cards' hover:
   the diagonal sheen sweep plus a field of sparks. Two things follow from
   borrowing it here.

   One: the sparks are white, so the chip has to go dark before they can be
   seen. Hover therefore takes the same navy the pressed state already uses,
   which is why hovering an inactive chip previews exactly what pressing it
   looks like.

   Two: the label needs an element to live in. On a hero card the copy is
   already wrapped in <p> and <h3>, so `> *` lifts it above the layers; a chip's
   text is a bare node, and a bare text node cannot be given a z-index. That is
   what the <span class="hub-chip__label"> in the markup is for.

   The keyframes are the hero cards' own — see @keyframes hero-card-sheen and
   hero-card-sparkle above. Deliberately reused, not copied: the two hovers are
   meant to be the same gesture, so they should drift together or not at all. */
.hub-chip {
  position: relative; overflow: hidden;
  font-family: var(--font-sans); font-size: var(--step--1); font-weight: 500;
  padding: 0.4rem 0.9rem; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line); color: var(--ink);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease,
              transform 0.2s ease, box-shadow 0.35s ease;
}
.hub-chip__label { position: relative; z-index: 2; }
.hub-chip::before,
.hub-chip::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  pointer-events: none; opacity: 0;
  transition: opacity 0.4s ease;
}
.hub-chip::before {
  background-image: linear-gradient(115deg,
    transparent 36%, rgba(255, 255, 255, 0.34) 50%, transparent 64%);
  background-size: 260% 100%;
  background-repeat: no-repeat;
  background-position: 130% 0;
}
/* Fewer points than a hero card and tighter to the pill: at this size six
   would read as noise rather than as sparks. Each one is still a lit core
   inside a soft halo, which is what keeps it from looking like a stray dot. */
.hub-chip::after {
  background-image:
    radial-gradient(circle 2.6px at 14% 28%, rgba(255, 255, 255, 0.98) 0 22%, rgba(255, 255, 255, 0.4) 50%, transparent 100%),
    radial-gradient(circle 2px at 47% 72%, rgba(255, 255, 255, 0.94) 0 24%, rgba(255, 255, 255, 0.36) 52%, transparent 100%),
    radial-gradient(circle 3px at 78% 34%, rgba(255, 255, 255, 0.98) 0 20%, rgba(255, 255, 255, 0.42) 48%, transparent 100%),
    radial-gradient(circle 2.2px at 92% 66%, rgba(255, 255, 255, 0.94) 0 24%, rgba(255, 255, 255, 0.36) 52%, transparent 100%);
}
.hub-chip:hover,
.hub-chip:focus-visible,
.hub-chip[aria-pressed="true"] {
  background: var(--navy-800); border-color: var(--navy-800); color: #fff;
}
.hub-chip:hover,
.hub-chip:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2), 0 8px 20px rgba(10, 20, 33, 0.28);
}
/* Reduced-motion visitors get the same two layers, simply held still. */
.hub-chip:hover::before,
.hub-chip:focus-visible::before { opacity: 0.8; background-position: 40% 0; }
.hub-chip:hover::after,
.hub-chip:focus-visible::after { opacity: 0.85; }

@media (prefers-reduced-motion: no-preference) {
  .hub-chip:hover::before,
  .hub-chip:focus-visible::before {
    opacity: 1; animation: hero-card-sheen 2.2s ease-in-out infinite;
  }
  .hub-chip:hover::after,
  .hub-chip:focus-visible::after {
    animation: hero-card-sparkle 2s ease-in-out infinite;
  }
}
.hub-filter__count { flex: 0 0 auto; margin-left: auto; font-size: var(--step--1); color: var(--muted); }

/* ---- The wall ---------------------------------------------
   Newest article first in the source. Because the grid flows down a column and
   then across, "first in the DOM" is "top-left", and adding one at the top of
   the list pushes every other card to the right — the behaviour is the flow
   direction, so no script is involved in it.

   The wall deliberately runs past .container to the viewport edges: a card
   half-cut by the right edge is the only honest signal that the history keeps
   going that way. */
.hub-wall-wrap { position: relative; }
.hub-wall {
  --hub-row: clamp(38px, 5.2vh, 58px);
  display: grid;
  grid-template-rows: repeat(12, var(--hub-row));
  grid-auto-flow: column dense;
  grid-auto-columns: clamp(210px, 22vw, 300px);
  gap: clamp(0.6rem, 1.4vw, 1rem);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-behavior: smooth;
  padding-inline: var(--gutter);
  scroll-padding-inline: var(--gutter);
  padding-bottom: 1rem;
  scrollbar-width: thin;
  scrollbar-color: #c3cfe3 transparent;
}
@media (prefers-reduced-motion: reduce) { .hub-wall { scroll-behavior: auto; } }
.hub-wall::-webkit-scrollbar { height: 8px; }
.hub-wall::-webkit-scrollbar-track { background: transparent; }
.hub-wall::-webkit-scrollbar-thumb { background: #c3cfe3; border-radius: 999px; }

/* The shapes. A card's height is NOT chosen by whoever writes it: it comes from
   the card's position, in a cycle of twelve that repeats down the list. Each
   line below is one column, and every one of them adds up to exactly the 12
   rows a column has:

     posición  1        12            una tarjeta llena la columna
               2-4       4 + 5 + 3    tres
               5-6       7 + 5        dos
               7-9       3 + 6 + 3    tres
               10-12     4 + 3 + 5    tres

   Six different heights and five column shapes that do not repeat: that is what
   keeps the wall from reading as a pattern. The arithmetic is what keeps it
   tidy — because every group lands exactly on 12, auto-placement fills a column
   and moves to the next with nothing left over, and all the columns close on
   the same bottom line.

   Position 1 is deliberately the tall one: the newest article always leads.

   A per-card class could not promise the flush bottom — one article published
   with the wrong size and the grid goes ragged — and an inline style is not an
   option at all, since the CSP serves style-src 'self'.

   Only the last column can come up short, when the total is not a multiple of
   twelve. That is the far right end of the wall, the oldest article.

   `dense` above is no longer what builds the grid (there are no holes to fill);
   it stays as the safety net for a filtered wall, where :nth-child still counts
   the hidden cards and the visible ones have no reason to sum to 12. */
.hub-card { scroll-snap-align: start; }
.hub-card:nth-child(12n + 1)  { grid-row: span 12; }
.hub-card:nth-child(12n + 5)  { grid-row: span 7; }
.hub-card:nth-child(12n + 8)  { grid-row: span 6; }
.hub-card:nth-child(12n + 3),
.hub-card:nth-child(12n + 6),
.hub-card:nth-child(12n + 12) { grid-row: span 5; }
.hub-card:nth-child(12n + 2),
.hub-card:nth-child(12n + 10) { grid-row: span 4; }
.hub-card:nth-child(12n + 4),
.hub-card:nth-child(12n + 7),
.hub-card:nth-child(12n + 9),
.hub-card:nth-child(12n + 11) { grid-row: span 3; }
/* The filter hides cards with the `hidden` attribute; state it here so no later
   display rule can outweigh the UA default and leave a filtered card on screen. */
.hub-card[hidden] { display: none; }

.hub-card__link {
  position: relative; display: block; height: 100%; overflow: hidden;
  border-radius: var(--radius);
  background: var(--navy-800);
  text-decoration: none; color: #fff;
}
.hub-card__img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transition: transform 0.5s ease, filter 0.35s ease;
}
/* Veil and copy are separate layers so the photo can darken while the text
   fades in over it, the way .hero-card__link layers its sheen. */
.hub-card__veil {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(10,20,33,0.25) 0%, rgba(10,20,33,0.72) 60%, rgba(10,20,33,0.92) 100%);
  transition: opacity 0.35s ease;
}
.hub-card__info {
  position: absolute; inset-inline: 0; bottom: 0; z-index: 2;
  padding: clamp(0.85rem, 1.6vw, 1.15rem);
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.hub-card__meta {
  display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: baseline;
  font-family: var(--font-sans); font-size: var(--step--1);
  color: rgba(255,255,255,0.78); margin-bottom: 0.35rem;
}
.hub-card__source { font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #fff; }
.hub-card__title { color: #fff; font-size: var(--step-1); line-height: 1.2; }
.hub-card__text {
  color: rgba(255,255,255,0.85); font-size: var(--step--1);
  font-family: var(--font-serif); margin-top: 0.4rem;
}
.hub-card__tags { display: flex; flex-wrap: wrap; gap: 0.35rem; padding: 0; margin-top: 0.6rem; list-style: none; }
.hub-card__tags li {
  font-family: var(--font-sans); font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.04em;
  padding: 0.2rem 0.55rem; border-radius: 999px;
  background: rgba(255,255,255,0.16); color: #fff;
}
/* The two shortest heights (3 and 4 rows) cannot hold a summary as well as a
   title; drop the summary rather than let it overflow the photo. */
.hub-card:nth-child(12n + 2) .hub-card__text,
.hub-card:nth-child(12n + 4) .hub-card__text,
.hub-card:nth-child(12n + 7) .hub-card__text,
.hub-card:nth-child(12n + 9) .hub-card__text,
.hub-card:nth-child(12n + 10) .hub-card__text,
.hub-card:nth-child(12n + 11) .hub-card__text { display: none; }

/* An external card leaves the site: say so rather than let the reader find out. */
.hub-card--external .hub-card__source::after { content: " ↗"; }

/* Hover is the interaction the design is built on, so it is scoped to devices
   that actually have one. On touch there is no hover to wait for: veil and copy
   are simply always up (see below), which is why they are not opacity: 0 by
   default — that default is the touch state. */
@media (hover: hover) {
  .hub-card__veil { opacity: 0; }
  .hub-card__info { opacity: 0; transform: translateY(8px); }
  .hub-card__link:hover .hub-card__veil,
  .hub-card__link:focus-visible .hub-card__veil,
  .hub-card__link:focus-within .hub-card__veil { opacity: 1; }
  .hub-card__link:hover .hub-card__info,
  .hub-card__link:focus-visible .hub-card__info,
  .hub-card__link:focus-within .hub-card__info { opacity: 1; transform: none; }
  /* "the original photo will be made darker": the veil is the tint, this is the
     photo itself giving way underneath it. */
  .hub-card__link:hover .hub-card__img,
  .hub-card__link:focus-visible .hub-card__img,
  .hub-card__link:focus-within .hub-card__img { filter: brightness(0.55); transform: scale(1.04); }
}
@media (hover: hover) and (prefers-reduced-motion: reduce) {
  .hub-card__link:hover .hub-card__img,
  .hub-card__link:focus-visible .hub-card__img,
  .hub-card__link:focus-within .hub-card__img { transform: none; }
}

/* Arrows. Appended by the script, so they never exist without a handler. */
.hub-wall__nav {
  position: absolute; top: calc(50% - 1.5rem); z-index: 4;
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  font-size: 1.6rem; line-height: 1; color: var(--ink);
  background: rgba(255,255,255,0.94); border: 1px solid var(--line); border-radius: 999px;
  box-shadow: var(--shadow); cursor: pointer;
  transition: background 0.2s, opacity 0.2s;
}
.hub-wall__nav--prev { left: 0.5rem; }
.hub-wall__nav--next { right: 0.5rem; }
.hub-wall__nav:hover { background: #fff; }
.hub-wall__nav:disabled { opacity: 0; pointer-events: none; }
.hub-wall__nav[hidden] { display: none; }

.hub-empty { padding: clamp(2rem, 5vw, 3.5rem) 0; text-align: center; color: var(--muted); }
.hub-empty[hidden] { display: none; }

/* Vertical on a phone: same idea, other axis. Two columns wide, so the cycle is
   three cards to a band — one tall on the left, two stacked on the right that
   add up to the same height (8 = 5 + 3). The heights still vary, and every band
   closes level. These rules come after the desktop ones on purpose: same
   specificity, so it is source order that lets them win. */
@media (max-width: 720px) {
  .hub-wall {
    grid-template-rows: none;
    grid-template-columns: 1fr 1fr;
    grid-auto-rows: var(--hub-row);
    grid-auto-columns: auto;
    grid-auto-flow: row dense;
    overflow-x: visible;
    padding-bottom: 0;
  }
  /* Four bands, none the same, and the tall card changes sides. Each band is
     three cards where one side holds a single card and the other holds two
     that add up to it:

       posición  1-3     8   | 5 + 3    alta a la izquierda
                 4-6     4+3 |   7      alta a la derecha
                 7-9     9   | 4 + 5    alta a la izquierda
                 10-12   3+3 |   6      alta a la derecha

     Which side gets the tall card is decided by the ORDER, not by a column
     rule: placement always fills the leftmost free slot, so leading with the
     tall card puts it left, and leading with a short one pushes it right. */
  .hub-card:nth-child(12n + 7)  { grid-row: span 9; }
  .hub-card:nth-child(12n + 1)  { grid-row: span 8; }
  .hub-card:nth-child(12n + 5)  { grid-row: span 7; }
  .hub-card:nth-child(12n + 11) { grid-row: span 6; }
  .hub-card:nth-child(12n + 2),
  .hub-card:nth-child(12n + 9)  { grid-row: span 5; }
  .hub-card:nth-child(12n + 4),
  .hub-card:nth-child(12n + 8)  { grid-row: span 4; }
  .hub-card:nth-child(12n + 3),
  .hub-card:nth-child(12n + 6),
  .hub-card:nth-child(12n + 10),
  .hub-card:nth-child(12n + 12) { grid-row: span 3; }
  /* The short cards are not the same ones as on the desktop wall, so the
     desktop rule has to be undone first. The reset needs :nth-child(n) to match
     the specificity of what it is undoing — a plain .hub-card would lose to it
     and the wrong summaries would stay hidden. */
  .hub-card:nth-child(n) .hub-card__text { display: block; }
  .hub-card:nth-child(12n + 3) .hub-card__text,
  .hub-card:nth-child(12n + 4) .hub-card__text,
  .hub-card:nth-child(12n + 6) .hub-card__text,
  .hub-card:nth-child(12n + 8) .hub-card__text,
  .hub-card:nth-child(12n + 10) .hub-card__text,
  .hub-card:nth-child(12n + 12) .hub-card__text { display: none; }
  .hub-wall__nav { display: none; }
  .hub-slider { height: min(70svh, 460px); }
  .hub-slide__title { font-size: var(--step-2); }
  .hub-slide__text { font-size: var(--step-0); }
  .hub-filter__count { margin-left: 0; flex-basis: 100%; }
}
@media (max-width: 460px) {
  .hub-wall { grid-template-columns: 1fr; }
  /* One column: every card is full width, so every summary fits. */
  .hub-card:nth-child(n) .hub-card__text { display: block; }
}

/* =============================================================
   PRESS — press.html (js/press.js)

   Same architecture as the S.T.A.R C.I. HUB above — a full-bleed
   carousel of featured items, a filter bar, and a wall of cards —
   and a deliberate, standalone COPY of it rather than a shared set
   of classes.

   Why the duplication is on purpose: the hub is the firm's own
   writing and Press is what other people publish about the firm.
   They will drift — different card mix, different filters, maybe a
   different rhythm on the wall — and the two pages have to be able
   to drift without either one being able to break the other. Every
   selector here is prefixed `press-` and every hook in press.html
   is `data-press-*`, so nothing on this page shares a name with
   anything on star-ci-hub.html. Editing one page's block never
   touches the other's.

   The two rules that govern the hub govern this too, and neither is
   negotiable:

   1. The CSP serves `style-src 'self'` with no 'unsafe-inline', so a
      style="--span:8" on a card is dropped in production. Every card
      shape is therefore driven by a :nth-child cycle.
   2. The slides and the cards are real markup, not rendered by
      script. With JS off the first slide and the entire wall are
      still on screen and every link still works; only the timer, the
      search box and the arrows go away.
   ============================================================= */

/* ---- Featured slides -------------------------------------- */
/* The slides ARE the page's header: there is no title band above them. The
   negative margin pulls the band up under the sticky header, so the first
   screen is the photograph from its first pixel and the nav floats over it
   (the header carries z-index: 50, so it keeps painting on top). */
.press-slider {
  position: relative;
  background: var(--navy-900);
  height: 100vh; height: 100svh;
  min-height: 420px;
  /* header height + its 1px bottom border */
  margin-top: calc(-1 * (var(--header-h) + 1px));
}
.press-slider__track { list-style: none; padding: 0; margin: 0; height: 100%; }

/* Slides are stacked, so only opacity separates them. `visibility` rides along
   to keep the hidden ones out of the tab order and the a11y tree; its
   transition is delayed to the end of the fade so it never cuts one short. */
.press-slide {
  position: absolute; inset: 0;
  opacity: 0; visibility: hidden;
  transition: opacity 0.6s ease, visibility 0s linear 0.6s;
}
.press-slide:first-child { opacity: 1; visibility: visible; transition: opacity 0.6s ease; }
/* From here on the script owns which slide shows. Until it runs the rule above
   holds, which is what leaves slide 1 up for a visitor without JavaScript. */
.press-js .press-slide:first-child { opacity: 0; visibility: hidden; transition: opacity 0.6s ease, visibility 0s linear 0.6s; }
.press-js .press-slide.is-active { opacity: 1; visibility: visible; transition: opacity 0.6s ease; }

.press-slide__link { display: block; position: relative; height: 100%; text-decoration: none; color: #fff; }
.press-slide__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Photographs vary; the wash is what guarantees the caption stays legible. */
.press-slide__link::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(10,20,33,0.15) 0%, rgba(10,20,33,0.55) 55%, rgba(10,20,33,0.88) 100%);
}
.press-slide__body {
  position: absolute; inset-inline: 0; bottom: 0; z-index: 2;
  width: 100%; max-width: var(--maxw); margin-inline: auto;
  padding: var(--gutter);
  padding-bottom: clamp(3rem, 6vw, 4.5rem);
}
/* .eyebrow defaults to --blue-600, which is unreadable over a darkened photo. */
.press-slider .eyebrow { color: #fff; }
/* A slide that leaves the site says so where the source is named, the same way
   .press-card--external marks its card on the wall below. */
.press-slide--external .eyebrow::after { content: " ↗"; }
.press-slide__title { color: #fff; font-size: var(--step-3); max-width: 20ch; }
.press-slide__text { color: rgba(255,255,255,0.86); font-family: var(--font-serif); font-size: var(--step-1); max-width: 52ch; margin-top: 0.75rem; }
.press-slide__link:hover .press-slide__title { text-decoration: underline; text-underline-offset: 4px; }

.press-slider__dots {
  position: absolute; z-index: 3; left: 0; right: 0; bottom: clamp(1rem, 2.5vw, 1.75rem);
  display: flex; gap: 0.5rem; justify-content: center;
}
.press-dot {
  width: 34px; height: 4px; padding: 0; border: 0; border-radius: 999px;
  background: rgba(255,255,255,0.35); cursor: pointer;
  transition: background 0.25s, transform 0.25s;
}
.press-dot:hover { background: rgba(255,255,255,0.6); }
.press-dot[aria-current="true"] { background: #fff; transform: scaleY(1.6); }

/* ---- Filter bar -------------------------------------------
   Written into the markup with `hidden` and revealed by the script, so the
   control never appears for a visitor whose JS is blocked — for them the wall
   is simply unfiltered, which is the whole catalogue and still correct.

   Search box and result count, and nothing else: unlike the hub, Press has no
   row of tag chips (see press.html). The tags printed on a card are labels
   here, not buttons, so there is no .press-chip to style.

   The field takes the whole content column rather than sitting in it as a box
   of its own: it lines up with the section heading above it and reads as part
   of the page. A 100% flex-basis against the wrap already declared is what
   drops the count onto the next line, left aligned under the field it is
   describing — which is what the phone layout used to have to ask for. */
.press-filter[hidden] { display: none; }
.press-filter {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.75rem 1rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
}
.press-filter__input {
  flex: 1 1 100%; max-width: none;
  font-family: var(--font-sans); font-size: var(--step-0); color: var(--ink);
  padding: 0.7rem 0.95rem;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  transition: border-color 0.2s, box-shadow 0.2s;
}
.press-filter__input::placeholder { color: var(--muted); }
.press-filter__input:focus { outline: none; border-color: var(--blue-500); box-shadow: var(--ring); }
.press-filter__count { flex: 0 0 auto; margin-left: 0; font-size: var(--step--1); color: var(--muted); }

/* ---- The wall ---------------------------------------------
   Newest mention first in the source. Because the grid flows down a column and
   then across, "first in the DOM" is "top-left", and adding one at the top of
   the list pushes every other card to the right — the behaviour is the flow
   direction, so no script is involved in it.

   The wall deliberately runs past .container to the viewport edges: a card
   half-cut by the right edge is the only honest signal that the history keeps
   going that way. */
.press-wall-wrap { position: relative; }
.press-wall {
  --press-row: clamp(38px, 5.2vh, 58px);
  display: grid;
  grid-template-rows: repeat(12, var(--press-row));
  grid-auto-flow: column dense;
  grid-auto-columns: clamp(210px, 22vw, 300px);
  gap: clamp(0.6rem, 1.4vw, 1rem);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-behavior: smooth;
  padding-inline: var(--gutter);
  scroll-padding-inline: var(--gutter);
  padding-bottom: 1rem;
  scrollbar-width: thin;
  scrollbar-color: #c3cfe3 transparent;
}
@media (prefers-reduced-motion: reduce) { .press-wall { scroll-behavior: auto; } }
.press-wall::-webkit-scrollbar { height: 8px; }
.press-wall::-webkit-scrollbar-track { background: transparent; }
.press-wall::-webkit-scrollbar-thumb { background: #c3cfe3; border-radius: 999px; }

/* The shapes. A card's height is NOT chosen by whoever writes it: it comes from
   the card's position, in a cycle of twelve that repeats down the list. Each
   line below is one column, and every one of them adds up to exactly the 12
   rows a column has:

     posición  1        12            una tarjeta llena la columna
               2-4       4 + 5 + 3    tres
               5-6       7 + 5        dos
               7-9       3 + 6 + 3    tres
               10-12     4 + 3 + 5    tres

   Six different heights and five column shapes that do not repeat: that is what
   keeps the wall from reading as a pattern. The arithmetic is what keeps it
   tidy — because every group lands exactly on 12, auto-placement fills a column
   and moves to the next with nothing left over, and all the columns close on
   the same bottom line.

   Position 1 is deliberately the tall one: the newest mention always leads.

   Only the last column can come up short, when the total is not a multiple of
   twelve. That is the far right end of the wall, the oldest mention.

   `dense` above is the safety net for a filtered wall, where :nth-child still
   counts the hidden cards and the visible ones have no reason to sum to 12. */
.press-card { scroll-snap-align: start; }
.press-card:nth-child(12n + 1)  { grid-row: span 12; }
.press-card:nth-child(12n + 5)  { grid-row: span 7; }
.press-card:nth-child(12n + 8)  { grid-row: span 6; }
.press-card:nth-child(12n + 3),
.press-card:nth-child(12n + 6),
.press-card:nth-child(12n + 12) { grid-row: span 5; }
.press-card:nth-child(12n + 2),
.press-card:nth-child(12n + 10) { grid-row: span 4; }
.press-card:nth-child(12n + 4),
.press-card:nth-child(12n + 7),
.press-card:nth-child(12n + 9),
.press-card:nth-child(12n + 11) { grid-row: span 3; }
/* The filter hides cards with the `hidden` attribute; state it here so no later
   display rule can outweigh the UA default and leave a filtered card on screen. */
.press-card[hidden] { display: none; }

.press-card__link {
  position: relative; display: block; height: 100%; overflow: hidden;
  border-radius: var(--radius);
  background: var(--navy-800);
  text-decoration: none; color: #fff;
}
.press-card__img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transition: transform 0.5s ease, filter 0.35s ease;
}
/* Veil and copy are separate layers so the photo can darken while the text
   fades in over it. */
.press-card__veil {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(10,20,33,0.25) 0%, rgba(10,20,33,0.72) 60%, rgba(10,20,33,0.92) 100%);
  transition: opacity 0.35s ease;
}
.press-card__info {
  position: absolute; inset-inline: 0; bottom: 0; z-index: 2;
  padding: clamp(0.85rem, 1.6vw, 1.15rem);
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.press-card__meta {
  display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: baseline;
  font-family: var(--font-sans); font-size: var(--step--1);
  color: rgba(255,255,255,0.78); margin-bottom: 0.35rem;
}
.press-card__source { font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #fff; }
.press-card__title { color: #fff; font-size: var(--step-1); line-height: 1.2; }
.press-card__text {
  color: rgba(255,255,255,0.85); font-size: var(--step--1);
  font-family: var(--font-serif); margin-top: 0.4rem;
}
.press-card__tags { display: flex; flex-wrap: wrap; gap: 0.35rem; padding: 0; margin-top: 0.6rem; list-style: none; }
.press-card__tags li {
  font-family: var(--font-sans); font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.04em;
  padding: 0.2rem 0.55rem; border-radius: 999px;
  background: rgba(255,255,255,0.16); color: #fff;
}
/* The two shortest heights (3 and 4 rows) cannot hold a summary as well as a
   title; drop the summary rather than let it overflow the photo. */
.press-card:nth-child(12n + 2) .press-card__text,
.press-card:nth-child(12n + 4) .press-card__text,
.press-card:nth-child(12n + 7) .press-card__text,
.press-card:nth-child(12n + 9) .press-card__text,
.press-card:nth-child(12n + 10) .press-card__text,
.press-card:nth-child(12n + 11) .press-card__text { display: none; }

/* Almost every card here leaves the site — that is what Press is — but say so
   on each one rather than let the reader find out on the click. */
.press-card--external .press-card__source::after { content: " ↗"; }

/* Hover is the interaction the design is built on, so it is scoped to devices
   that actually have one. On touch there is no hover to wait for: veil and copy
   are simply always up, which is why they are not opacity: 0 by default. */
@media (hover: hover) {
  .press-card__veil { opacity: 0; }
  .press-card__info { opacity: 0; transform: translateY(8px); }
  .press-card__link:hover .press-card__veil,
  .press-card__link:focus-visible .press-card__veil,
  .press-card__link:focus-within .press-card__veil { opacity: 1; }
  .press-card__link:hover .press-card__info,
  .press-card__link:focus-visible .press-card__info,
  .press-card__link:focus-within .press-card__info { opacity: 1; transform: none; }
  .press-card__link:hover .press-card__img,
  .press-card__link:focus-visible .press-card__img,
  .press-card__link:focus-within .press-card__img { filter: brightness(0.55); transform: scale(1.04); }
}
@media (hover: hover) and (prefers-reduced-motion: reduce) {
  .press-card__link:hover .press-card__img,
  .press-card__link:focus-visible .press-card__img,
  .press-card__link:focus-within .press-card__img { transform: none; }
}

/* Arrows. Appended by the script, so they never exist without a handler. */
.press-wall__nav {
  position: absolute; top: calc(50% - 1.5rem); z-index: 4;
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  font-size: 1.6rem; line-height: 1; color: var(--ink);
  background: rgba(255,255,255,0.94); border: 1px solid var(--line); border-radius: 999px;
  box-shadow: var(--shadow); cursor: pointer;
  transition: background 0.2s, opacity 0.2s;
}
.press-wall__nav--prev { left: 0.5rem; }
.press-wall__nav--next { right: 0.5rem; }
.press-wall__nav:hover { background: #fff; }
.press-wall__nav:disabled { opacity: 0; pointer-events: none; }
.press-wall__nav[hidden] { display: none; }

.press-empty { padding: clamp(2rem, 5vw, 3.5rem) 0; text-align: center; color: var(--muted); }
.press-empty[hidden] { display: none; }

/* Vertical on a phone: same idea, other axis. Two columns wide, so the cycle is
   three cards to a band — one tall on the left, two stacked on the right that
   add up to the same height. The heights still vary, and every band closes
   level. These rules come after the desktop ones on purpose: same specificity,
   so it is source order that lets them win. */
@media (max-width: 720px) {
  .press-wall {
    grid-template-rows: none;
    grid-template-columns: 1fr 1fr;
    grid-auto-rows: var(--press-row);
    grid-auto-columns: auto;
    grid-auto-flow: row dense;
    overflow-x: visible;
    padding-bottom: 0;
  }
  /* Four bands, none the same, and the tall card changes sides:

       posición  1-3     8   | 5 + 3    alta a la izquierda
                 4-6     4+3 |   7      alta a la derecha
                 7-9     9   | 4 + 5    alta a la izquierda
                 10-12   3+3 |   6      alta a la derecha

     Which side gets the tall card is decided by the ORDER, not by a column
     rule: placement always fills the leftmost free slot. */
  .press-card:nth-child(12n + 7)  { grid-row: span 9; }
  .press-card:nth-child(12n + 1)  { grid-row: span 8; }
  .press-card:nth-child(12n + 5)  { grid-row: span 7; }
  .press-card:nth-child(12n + 11) { grid-row: span 6; }
  .press-card:nth-child(12n + 2),
  .press-card:nth-child(12n + 9)  { grid-row: span 5; }
  .press-card:nth-child(12n + 4),
  .press-card:nth-child(12n + 8)  { grid-row: span 4; }
  .press-card:nth-child(12n + 3),
  .press-card:nth-child(12n + 6),
  .press-card:nth-child(12n + 10),
  .press-card:nth-child(12n + 12) { grid-row: span 3; }
  /* The short cards are not the same ones as on the desktop wall, so the
     desktop rule has to be undone first. The reset needs :nth-child(n) to match
     the specificity of what it is undoing — a plain .press-card would lose. */
  .press-card:nth-child(n) .press-card__text { display: block; }
  .press-card:nth-child(12n + 3) .press-card__text,
  .press-card:nth-child(12n + 4) .press-card__text,
  .press-card:nth-child(12n + 6) .press-card__text,
  .press-card:nth-child(12n + 8) .press-card__text,
  .press-card:nth-child(12n + 10) .press-card__text,
  .press-card:nth-child(12n + 12) .press-card__text { display: none; }
  .press-wall__nav { display: none; }
  .press-slider { height: min(70svh, 460px); }
  .press-slide__title { font-size: var(--step-2); }
  .press-slide__text { font-size: var(--step-0); }
}
@media (max-width: 460px) {
  .press-wall { grid-template-columns: 1fr; }
  /* One column: every card is full width, so every summary fits. */
  .press-card:nth-child(n) .press-card__text { display: block; }
}

/* ---- Article pages (articulos/*.html) ---------------------- */
.article-cover { background: var(--navy-900); }
.article-cover img { width: 100%; height: min(52svh, 460px); object-fit: cover; }
.article-cover figcaption {
  max-width: var(--maxw); margin-inline: auto; padding: 0.75rem var(--gutter);
  font-size: var(--step--1); color: var(--muted);
}

/* Scoped to .page-hero because that is where it lives, and because the plain
   `.page-hero p` rule (serif, --step-1) would otherwise outweigh it. */
.page-hero .article-meta {
  display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; align-items: baseline;
  max-width: none; margin-top: 1.25rem;
  font-family: var(--font-sans); font-size: var(--step--1);
  color: rgba(255,255,255,0.82);
}
.page-hero .article-meta strong { color: #fff; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; }

/* The first long-form prose on the site: a measure to read at, and space
   between blocks rather than inside them. */
.article-body { max-width: 68ch; margin-inline: auto; }
.article-body > * + * { margin-top: 1.25rem; }
.article-body h2 { font-size: var(--step-2); margin-top: 2.5rem; }
.article-body h3 { font-size: var(--step-1); margin-top: 2rem; }
.article-body p { color: var(--body); }
.article-body .lede { color: var(--blue-700); font-size: var(--step-1); }
.article-body ul, .article-body ol { padding-left: 1.25rem; display: grid; gap: 0.5rem; }
.article-body li { color: var(--body); }
.article-body blockquote {
  border-left: 3px solid var(--blue-500); padding-left: 1.25rem;
  font-family: var(--font-serif); font-size: var(--step-1); color: var(--blue-700);
}
.article-body figure img { border-radius: var(--radius); }
.article-body figcaption { margin-top: 0.5rem; font-size: var(--step--1); color: var(--muted); }

.article-foot {
  max-width: 68ch; margin: clamp(2.5rem, 5vw, 4rem) auto 0;
  padding-top: 1.5rem; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; justify-content: space-between;
}
.article-back { font-family: var(--font-sans); font-weight: 600; font-size: var(--step--1); text-decoration: none; }
.article-back:hover { text-decoration: underline; }

/* =============================================================
   Evalúa tu empresa — autodiagnóstico de compliance
   Used only by evalua-tu-empresa.html (js/evalua.js).

   The landing site's CSP is `style-src 'self'` with no 'unsafe-inline',
   so nothing here may be driven by a `style=` attribute. Values that
   depend on data are passed as custom properties set from JS via
   element.style.setProperty(), which is a DOM call and is allowed.
   ============================================================= */

/* Answer scale: 0 is the worst (no visibility), 4 the best. Colour alone
   never carries the meaning — every dot also shows its numeral and an
   aria-label with the full Spanish wording of the scale. */
:root {
  --dot-0: #8c1c1c;
  --dot-1: #d9534f;
  --dot-2: #9aa3b0;
  --dot-3: var(--blue-500);
  --dot-4: var(--navy-800);
  --dot-na: #b8c1cf;

  /* The matriz de áreas is drawn in one flat blue rather than the answer
     palette: it is a shape, not a scale. */
  --radar-blue: #0f4c81;
}

/* Nothing renders until evalua.js has hydrated the shell, otherwise the
   empty scaffolding flashes before the questions arrive.
   The `!important` is load-bearing: .btn and .section set an explicit
   `display`, and a class selector outranks the UA stylesheet's `[hidden]`
   rule, so without it every element the script hides stays on screen. */
[data-eval-root][hidden],
[data-eval-root] [hidden] { display: none !important; }

/* The site header is sticky, so a step scrolled to the top of the viewport
   would begin underneath it. */
[data-eval-step] { scroll-margin-top: var(--header-h); }

.eval-note { font-size: var(--step--1); color: var(--muted); }
.eval-note--center { text-align: center; }

/* ---- Intro -------------------------------------------------- */
.eval-intro { display: grid; gap: clamp(2rem, 5vw, 3.5rem); align-items: start; }
@media (min-width: 900px) { .eval-intro { grid-template-columns: 1.05fr 0.95fr; } }
.eval-intro__lede p { font-family: var(--font-serif); font-size: var(--step-1); color: var(--blue-700); }
.eval-intro__lede p + p { margin-top: 1rem; }
.eval-intro__lede .eval-note { font-family: var(--font-sans); font-size: var(--step--1); color: var(--muted); margin-top: 1.5rem; }
.eval-intro__scale { background: var(--surface-tint); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: clamp(1.5rem, 4vw, 2.25rem); }
.eval-intro__scale h2 { font-size: var(--step-2); }
.eval-intro__scale > p { margin-top: 0.75rem; }
.eval-intro__scale .eval-note { margin-top: 1.25rem; }

.eval-legend { list-style: none; padding: 0; margin: 1.5rem 0 0; display: grid; gap: 0.85rem; }
.eval-legend li { display: grid; grid-template-columns: auto 1fr; gap: 0.75rem; align-items: start; }
.eval-legend b { display: block; font-size: var(--step-0); color: var(--ink); }
.eval-legend span { display: block; font-size: var(--step--1); color: var(--body); }
.eval-legend__swatch {
  width: 22px; height: 22px; border-radius: 50%; margin-top: 0.2rem;
  background: var(--swatch, var(--dot-2)); border: 1.5px solid rgba(14, 26, 43, 0.18);
}
.eval-legend__swatch--na { background: transparent; border-style: dashed; border-color: var(--dot-na); }

/* The compact legend above the question list repeats the key without the
   prose, so the dots never need to be decoded from memory. */
.eval-legend--inline {
  /* Flex rather than the grid the full legend uses: six entries do not fit on
     one line at most widths, and wrapping beats a horizontal scroll area that
     silently clips the N/A key off the right edge. */
  display: flex; flex-wrap: wrap; column-gap: 1.1rem; row-gap: 0.5rem;
  padding-bottom: 0.5rem; margin: 0 0 1.75rem;
}
.eval-legend--inline li { display: flex; align-items: center; gap: 0.45rem; }
.eval-legend--inline b { font-size: var(--step--1); white-space: nowrap; }
.eval-legend--inline span { display: none; }
.eval-legend--inline .eval-legend__swatch { width: 15px; height: 15px; margin-top: 0; }

/* ---- Scope: integral vs. selectivo ---------------------------- */
.eval-modes { display: grid; gap: 1rem; margin-top: clamp(1.5rem, 4vw, 2.25rem); }
@media (min-width: 800px) { .eval-modes { grid-template-columns: 1fr 1fr; } }
.eval-mode {
  display: block; cursor: pointer; position: relative;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: clamp(1.25rem, 3vw, 1.75rem);
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.eval-mode:hover { border-color: #c3cfe3; transform: translateY(-2px); box-shadow: var(--shadow); }
.eval-mode input { position: absolute; top: 1.25rem; right: 1.25rem; width: 18px; height: 18px; accent-color: var(--blue-500); }
.eval-mode:has(input:checked) { border-color: var(--blue-500); box-shadow: inset 0 0 0 1px var(--blue-500); }
.eval-mode:has(input:focus-visible) { outline: 2px solid var(--blue-500); outline-offset: 3px; }
.eval-mode__body { display: grid; gap: 0.4rem; padding-right: 2rem; }
.eval-mode__body b { font-family: var(--font-serif); font-size: var(--step-1); color: var(--ink); }
.eval-mode__body span { font-size: var(--step--1); color: var(--body); }
.eval-mode__meta { color: var(--muted) !important; font-weight: 600; }

.eval-scope {
  margin-top: clamp(1.5rem, 4vw, 2.25rem);
  background: var(--surface-tint); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: clamp(1.25rem, 3vw, 1.75rem);
}
.eval-scope[hidden] { display: none; }
.eval-scope__head h3 { font-size: var(--step-1); }
.eval-scope__head .eval-note { margin-top: 0.35rem; font-variant-numeric: tabular-nums; }
.eval-scope__head .btn-row { margin-top: 0.9rem; }
.eval-scope__head .btn { padding: 0.5rem 0.9rem; font-size: var(--step--1); }
.eval-scope__list { list-style: none; padding: 0; margin: 1.25rem 0 0; display: grid; gap: 0.6rem; }
@media (min-width: 700px) { .eval-scope__list { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1100px) { .eval-scope__list { grid-template-columns: repeat(3, 1fr); } }
.eval-scope__item label {
  display: grid; grid-template-columns: auto 1fr; gap: 0.7rem; align-items: start;
  cursor: pointer; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 0.8rem 0.9rem; height: 100%;
  transition: border-color 0.2s ease;
}
.eval-scope__item label:hover { border-color: #c3cfe3; }
.eval-scope__item label:has(input:checked) { border-color: var(--blue-500); background: var(--surface); }
.eval-scope__item label:has(input:focus-visible) { outline: 2px solid var(--blue-500); outline-offset: 2px; }
.eval-scope__item input { width: 17px; height: 17px; margin-top: 0.15rem; accent-color: var(--blue-500); }
.eval-scope__body { display: grid; gap: 0.2rem; }
.eval-scope__body b { font-size: var(--step--1); color: var(--ink); font-weight: 600; }
.eval-scope__meta { font-size: 0.78rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* «Continuar» is inert until at least one dimension is chosen, and has to look
   it — the site has no global disabled-button style. */
.eval-nav .btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
.eval-nav .btn:disabled:hover { background: var(--blue-500); border-color: var(--blue-500); transform: none; }

.eval-results__scope {
  font-size: var(--step--1); color: var(--body);
  border-left: 2px solid var(--blue-500); padding-left: 0.85rem; margin-top: 0.85rem; max-width: 62ch;
}

/* ---- Progress ------------------------------------------------ */
.eval-progress {
  position: sticky; top: var(--header-h); z-index: 5;
  background: var(--surface); border-bottom: 1px solid var(--line);
  padding-block: 0.85rem; margin-bottom: clamp(1.75rem, 4vw, 2.75rem);
}
.eval-progress__inner { display: grid; gap: 0.5rem; }
.eval-progress__meta { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: space-between; align-items: baseline; }
.eval-progress__meta strong { font-size: var(--step-0); color: var(--ink); }
.eval-progress__meta span { font-size: var(--step--1); color: var(--muted); font-variant-numeric: tabular-nums; }
.eval-progress__track { height: 5px; border-radius: 999px; background: var(--surface-tint); overflow: hidden; }
.eval-progress__fill { height: 100%; width: var(--progress, 0%); background: var(--blue-500); transition: width 0.25s ease; }

.eval-quiz { padding-top: 0; }
.eval-dim { max-width: 62ch; margin-bottom: 1.5rem; }
.eval-dim h2 { font-size: var(--step-2); }

/* ---- Question rows -------------------------------------------- */
.eval-items { list-style: none; padding: 0; margin: 0; display: grid; gap: 0; }
.eval-item {
  padding-block: clamp(1.25rem, 3vw, 1.75rem); border-top: 1px solid var(--line);
  /* Clears the sticky site header plus the sticky progress bar, so a row
     scrolled into view by the keyboard is not hidden underneath them. */
  scroll-margin-top: calc(var(--header-h) + 5rem);
}
.eval-item:first-child { border-top: 0; padding-top: 0; }
.eval-item[data-answered="no"] .eval-item__num { color: var(--dot-1); }
.eval-item__q { display: grid; grid-template-columns: auto 1fr; gap: 0.75rem; max-width: 78ch; }
.eval-item__num { font-weight: 600; font-size: var(--step--1); color: var(--muted); font-variant-numeric: tabular-nums; padding-top: 0.15rem; }
.eval-item__text { color: var(--ink); }

.eval-answer { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin-top: 1rem; }
.eval-dots { display: flex; gap: 0.5rem; }

/* Real radios, visually replaced by the dots: keyboard arrow-key navigation
   and screen-reader semantics come for free. */
.eval-dot { position: relative; display: block; }
.eval-dot input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer;
  /* The hover transform below gives the span a stacking context; without an
     explicit z-index it would paint over the input and swallow the click. */
  z-index: 1;
}
.eval-dot span {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 50%;
  border: 1.5px solid #c6d0e0; background: var(--surface);
  color: var(--muted); font-size: var(--step--1); font-weight: 600;
  transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.1s;
}
.eval-dot:hover span { border-color: var(--dot); transform: translateY(-1px); }
.eval-dot input:focus-visible + span { outline: none; box-shadow: var(--ring); }
/* Selected state is fill + ring + weight, never colour on its own. */
.eval-dot input:checked + span {
  background: var(--dot); border-color: var(--dot); color: #fff;
  box-shadow: 0 0 0 3px rgba(14, 26, 43, 0.10);
}
/* Grey is light enough that white numerals on it fail contrast. */
.eval-dot--2 input:checked + span { color: var(--ink); }

.eval-na { position: relative; display: block; margin-left: 0.25rem; }
.eval-na input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 1; }
.eval-na span {
  display: block; padding: 0.35rem 0.75rem; border-radius: var(--radius);
  border: 1.5px dashed var(--dot-na); color: var(--muted);
  font-size: var(--step--1); font-weight: 600;
}
.eval-na:hover span { border-color: var(--blue-500); color: var(--blue-600); }
.eval-na input:focus-visible + span { outline: none; box-shadow: var(--ring); }
.eval-na input:checked + span { background: var(--surface-tint); border-style: solid; border-color: var(--blue-500); color: var(--blue-600); }

.eval-nav { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; justify-content: space-between; margin-top: clamp(2rem, 4vw, 3rem); padding-top: 1.5rem; border-top: 1px solid var(--line); }
.eval-nav .eval-note { flex: 1 1 14ch; text-align: center; margin: 0; }
.eval-nav button[hidden] { display: none; }

/* ---- Identification ------------------------------------------- */
.eval-identify { max-width: 46rem; margin-inline: auto; }
.eval-identify .eval-nav { border-top: 0; padding-top: 0.5rem; }

/* ---- Results --------------------------------------------------- */
.eval-results { display: grid; gap: clamp(2.5rem, 6vw, 4.5rem); }
/* Grid items default to min-width:auto, which resolves to their content's
   min-content width — for the blocks holding the 23-dimension table that is
   ~433px, so the whole page gained a horizontal scrollbar on a phone and the
   table's own overflow-x never engaged. Letting the items shrink hands the
   scrolling back to .eval-table__wrap, where it belongs. */
.eval-results > * { min-width: 0; }
.eval-table__wrap { min-width: 0; }
.eval-results h2 { font-size: var(--step-2); }
.eval-results h3 { font-size: var(--step-1); }

.eval-score { display: grid; gap: clamp(1.75rem, 5vw, 3rem); align-items: center; }
@media (min-width: 820px) { .eval-score { grid-template-columns: auto 1fr; } }
.eval-gauge { width: clamp(200px, 45vw, 260px); height: auto; justify-self: center; }
.eval-gauge__track, .eval-gauge__value { fill: none; stroke-width: 14; }
.eval-gauge__track { stroke: var(--surface-tint); }
.eval-gauge__value { stroke: var(--band); stroke-linecap: round; }
.eval-gauge__num { fill: var(--ink); font-family: var(--font-sans); font-weight: 700; font-size: 46px; text-anchor: middle; }
.eval-gauge__of { fill: var(--muted); font-family: var(--font-sans); font-size: 13px; letter-spacing: 0.08em; text-anchor: middle; }
.eval-score__band { display: inline-block; padding: 0.3rem 0.85rem; border-radius: 999px; background: var(--band); color: #fff; font-weight: 600; font-size: var(--step--1); }
.eval-score__band[data-band="Definido"] { color: var(--ink); }
.eval-score__reading { font-family: var(--font-serif); font-size: var(--step-1); color: var(--blue-700); margin-top: 1rem; }
.eval-score__meta { margin-top: 1rem; }

.eval-counters { list-style: none; padding: 0; margin: 0; display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.eval-counter { border: 1px solid var(--line); border-left: 4px solid var(--dot); border-radius: var(--radius); padding: 1rem 1.15rem; }
.eval-counter b { display: block; font-size: var(--step-2); color: var(--ink); line-height: 1.15; font-variant-numeric: tabular-nums; }
.eval-counter span { display: block; font-size: var(--step--1); color: var(--body); margin-top: 0.25rem; }

/* 23 rows of label + bar. Plain divs rather than SVG so the labels stay
   selectable text and reflow on narrow screens.

   The name sits on its own line above the bar rather than inside it: a bar in
   the Gestionado or Avanzado band is dark blue or navy, and dark label text on
   that fill fails contrast badly. */
.eval-bars { list-style: none; padding: 0; margin: 0; display: grid; gap: 1rem; }
.eval-bar { display: grid; grid-template-columns: 3rem minmax(0, 1fr) auto; gap: 0.3rem 0.75rem; align-items: baseline; }
@media (max-width: 620px) { .eval-bar { grid-template-columns: 2.4rem minmax(0, 1fr) auto; } }
.eval-bar__id { font-size: var(--step--1); font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.eval-bar__name { font-size: var(--step--1); color: var(--ink); }
.eval-bar__score { font-size: var(--step--1); font-weight: 600; color: var(--ink); text-align: right; font-variant-numeric: tabular-nums; }
.eval-bar__track { grid-column: 1 / -1; height: 10px; background: var(--surface-tint); border-radius: 999px; overflow: hidden; }
.eval-bar__fill { height: 100%; width: var(--score, 0%); background: var(--band); border-radius: 999px; }
.eval-bar[data-pending="yes"] .eval-bar__track { background: transparent; border: 1px dashed var(--line); }
.eval-bar[data-pending="yes"] .eval-bar__score,
.eval-bar[data-pending="yes"] .eval-bar__name { color: var(--muted); font-weight: 400; }

/* Matriz de áreas: one axis per evaluated dimension, 0 at the centre and 100 on
   the dotted ring. Hand-built SVG (js/evalua.js) — the shape is always the one
   blue, so it reads as a single figure; the maturity band still colours the
   gauge, the badge and every bar below it. */
.eval-radar__figure { position: relative; margin: 0 0 clamp(1.5rem, 4vw, 2.25rem); }
/* At 23 axes the names need ~950 units of width. Rather than shrink them to
   nothing on a phone, the figure scrolls the way .eval-table__wrap does — the
   overflow stays inside the block instead of on the page. */
.eval-radar__scroll { overflow-x: auto; }
.eval-radar { display: block; width: 100%; max-width: 60rem; min-width: 46rem; height: auto; margin-inline: auto; }
.eval-radar__ring, .eval-radar__spoke { fill: none; stroke: var(--line); stroke-width: 1; }
.eval-radar__max { fill: none; stroke: var(--ink); stroke-width: 1.5; stroke-dasharray: 2 4; stroke-linecap: round; }
.eval-radar__area { fill: var(--radar-blue); fill-opacity: 0.55; stroke: var(--radar-blue); stroke-width: 2; stroke-linejoin: round; }
.eval-radar__dot { fill: var(--radar-blue); }
.eval-radar__hit { fill: transparent; pointer-events: all; cursor: pointer; }
.eval-radar__label { fill: var(--ink); font-family: var(--font-sans); font-size: 12px; pointer-events: none; }
.eval-radar__dot { pointer-events: none; }
.eval-radar__figure figcaption { text-align: center; margin-top: 0.75rem; }

/* The vertex popup. Positioned from JS through --tip-x / --tip-y, since the
   CSP forbids a style attribute. */
.eval-radar__tip {
  position: absolute; left: var(--tip-x, 0); top: var(--tip-y, 0);
  transform: translate(-50%, calc(-100% - 12px));
  z-index: 2; pointer-events: none; max-width: 17rem; width: max-content;
  padding: 0.5rem 0.7rem; border-radius: var(--radius);
  background: var(--radar-blue); color: #fff; box-shadow: 0 6px 20px rgba(14, 26, 43, 0.28);
}
.eval-radar__tip-name { font-size: var(--step--1); line-height: 1.3; color: rgba(255, 255, 255, 0.78); }
.eval-radar__tip-value { font-size: var(--step-0); font-weight: 600; margin-top: 0.15rem; font-variant-numeric: tabular-nums; }

.eval-table { width: 100%; border-collapse: collapse; font-size: var(--step--1); }
.eval-table th, .eval-table td { text-align: left; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--line); vertical-align: top; }
.eval-table th { font-weight: 600; color: var(--ink); }
.eval-table td:last-child, .eval-table th:last-child { text-align: right; white-space: nowrap; }
.eval-table__wrap { overflow-x: auto; }

.eval-findings { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.75rem; }
.eval-finding { display: grid; grid-template-columns: auto 1fr; gap: 0.75rem; padding: 0.9rem 1.1rem; border: 1px solid var(--line); border-left: 4px solid var(--dot); border-radius: var(--radius); }
.eval-finding__tag { font-size: var(--step--1); font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.eval-finding__text { color: var(--ink); font-size: var(--step--1); }

.eval-alerts { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.6rem; }
.eval-alerts li { display: grid; grid-template-columns: auto 1fr; gap: 0.7rem; color: var(--ink); }
.eval-alerts li::before { content: "●"; color: var(--dot-1); font-size: 0.7em; line-height: 2.1; }

.eval-actions { list-style: none; counter-reset: eval-action; padding: 0; margin: 0; display: grid; gap: 1.25rem; }
@media (min-width: 760px) { .eval-actions { grid-template-columns: 1fr 1fr; } }
.eval-actions li { counter-increment: eval-action; }
.eval-actions li h4::before { content: counter(eval-action) ". "; color: var(--blue-500); }
.eval-actions li p { font-size: var(--step--1); margin-top: 0.35rem; }

.eval-details { border-top: 1px solid var(--line); padding-top: 1rem; }
.eval-details summary { cursor: pointer; font-weight: 600; color: var(--blue-600); }
.eval-details summary:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius); }
.eval-details > *:not(summary) { margin-top: 1.25rem; }

.eval-sent { border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem 1.15rem; font-size: var(--step--1); }
.eval-sent[data-state="err"] { border-color: #e6b4b0; background: #fdf6f5; }
.eval-sent[data-state="ok"] { border-color: #b7ddc7; background: #f4faf6; }
.eval-sent .btn { margin-top: 0.75rem; }

/* ---- Alianzas: el mar -------------------------------------
   La página entera es una carta de navegación vista desde el aire: el agua
   sale del primer pixel — el margen negativo la mete debajo del encabezado
   sticky, el mismo recurso de .hub-slider y .video-cover — y encima flotan
   veinticuatro islas, una por alianza.

   El mar se genera aquí, sin depender de nada: degradados para el cuerpo del
   agua y una capa de turbulencia enmascarada para el destello del sol. Si el
   despacho sube la foto aérea a Storage, .mar__foto la pone encima; si no
   existe, ese fondo simplemente no pinta y el mar generado queda solo. No hay
   nada que detectar ni de qué caerse.

   Dos reglas de rendimiento que no se pueden romper: la baseFrequency del
   filtro nunca se anima (recalcular la turbulencia cada cuadro tira la página),
   y las islas no se transforman al pasar el cursor — mover un SVG filtrado lo
   obliga a rasterizarse de nuevo. Por eso el hover mueve la bandera, que no
   lleva filtro, y deja la isla quieta. */
/* La paleta del agua vive en :root y no en .mar porque el directorio de abajo
   también la usa: cuando estaba en .mar, su degradado quedaba con una variable
   sin resolver y la sección se pintaba en blanco. */
:root {
  --mar-hondo: #051c37;
  --mar-medio: #0a3a6c;
  --mar-claro: #1a6ea8;
}

.mar {
  /* Geometría de la bandera. Es fija para todas las islas aunque las islas
     cambien de tamaño: el logotipo tiene que leerse igual en la isla chica que
     en la grande, y así ninguna bandera alcanza a la isla del renglón de
     arriba. */
  --tela-w: clamp(54px, 5.6vw, 94px);
  --tela-h: calc(var(--tela-w) * 0.5);
  --asta-h: calc(var(--tela-h) * 1.95);

  position: relative;
  isolation: isolate;
  overflow: hidden;
  height: 100vh; height: 100svh;
  margin-top: calc(-1 * (var(--header-h) + 1px));
  background: var(--mar-hondo);
  color: #fff;
}

.mar__agua {
  position: absolute; inset: 0; z-index: 0;
  background:
    radial-gradient(120% 92% at 26% 10%, rgba(86, 172, 219, 0.9) 0%, rgba(26, 110, 168, 0.45) 26%, rgba(10, 58, 108, 0) 60%),
    radial-gradient(70% 55% at 78% 34%, rgba(23, 96, 152, 0.55) 0%, rgba(10, 58, 108, 0) 72%),
    radial-gradient(85% 70% at 88% 96%, rgba(4, 22, 44, 0.45) 0%, rgba(5, 28, 55, 0) 62%),
    linear-gradient(168deg, #0f5090 0%, #0b4076 34%, #093260 68%, #072747 100%);
}

/* Opcional. El objeto puede no existir todavía: un fondo que no carga no deja
   hueco, no rompe el diseño y no cuesta más que una petición fallida. */
.mar__foto {
  position: absolute; inset: 0; z-index: 0;
  background-image: url("https://firebasestorage.googleapis.com/v0/b/sllm-ai.firebasestorage.app/o/landing%2Falianzas-mar.jpg?alt=media");
  background-size: cover;
  background-position: center;
  opacity: 0.8;
}

/* El destello del sol. Se calcula una vez y después solo se desliza: la
   animación toca transform, nunca el filtro. El sobredimensionado (inset
   negativo) es lo que deja correr ese deslizamiento sin descubrir una orilla. */
.mar__brillo {
  position: absolute; inset: -14%; z-index: 1;
  width: auto; height: auto;
  /* La regla global `svg { max-width: 100% }` recortaba esta capa al ancho de
     .mar y dejaba una costura vertical donde se acababa la textura. Aquí el
     sobredimensionado es el punto: es el margen que la deriva necesita. */
  max-width: none; max-height: none;
  opacity: 0.7;
  mix-blend-mode: screen;
  pointer-events: none;
}
.mar__brillo-capa { fill: #fff; }
@media (prefers-reduced-motion: no-preference) {
  .mar__brillo-capa { animation: mar-deriva 96s ease-in-out infinite alternate; }
}
@keyframes mar-deriva {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-4.5%, 2.4%, 0); }
}

/* El título va abajo a la izquierda, como el pie de las diapositivas del hub:
   arriba no cabe (el encabezado se come los primeros 100 px y el primer
   renglón de banderas empieza enseguida), y abajo hay agua abierta porque el
   archipiélago termina en el 79 %. */
.mar__velo {
  position: absolute; inset: auto 0 0 0; z-index: 3;
  height: 46%;
  background: linear-gradient(to top, rgba(3, 18, 38, 0.82) 0%, rgba(3, 18, 38, 0.42) 34%, rgba(3, 18, 38, 0) 100%);
  -webkit-mask-image: linear-gradient(to right, #000 0%, #000 34%, transparent 72%);
          mask-image: linear-gradient(to right, #000 0%, #000 34%, transparent 72%);
  pointer-events: none;
}

.mar__titulo {
  position: absolute; z-index: 4;
  left: max(var(--gutter), calc(50% - var(--maxw) / 2));
  bottom: clamp(1.5rem, 4vh, 3rem);
  width: min(38rem, calc(100% - var(--gutter) * 2));
  text-shadow: 0 2px 20px rgba(3, 16, 32, 0.6);
  pointer-events: none;
}
.mar .eyebrow { color: rgba(255, 255, 255, 0.82); }
.mar__titulo h1 { color: #fff; font-size: var(--step-3); }
.mar__lede {
  color: rgba(255, 255, 255, 0.88);
  font-family: var(--font-serif); font-size: var(--step-1);
  margin-top: 0.6rem; max-width: 46ch;
}
.mar__pista {
  margin-top: 1rem;
  font-family: var(--font-sans); font-size: var(--step--1); font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
}
.mar__pista[hidden] { display: none; }

/* ---- El escenario -----------------------------------------
   Un lienzo 16:9 centrado en el agua, no el viewport entero. Es lo que
   garantiza que la composición del archipiélago sea idéntica en cualquier
   pantalla: las coordenadas están puestas a mano sobre este 16:9, así que
   ninguna isla se encima con otra a ningún ancho, y una pantalla más ancha
   solo gana mar abierto a los lados. */
.mar__escena {
  position: absolute; inset: 0; margin: auto; z-index: 2;
  width: min(100%, calc(100svh * 16 / 9));
  height: min(100%, calc(100vw * 9 / 16));
}

.isla {
  position: absolute;
  left: var(--x); top: var(--y);
  width: calc(var(--size) * 7%);
  aspect-ratio: 1 / 1;
  margin: 0; padding: 0; border: 0; background: none;
  transform: translate(-50%, -50%);
  /* Las islas se leen en orden de renglón, así que --i crece hacia abajo: las
     de abajo pintan encima, que es la profundidad correcta vista desde el aire. */
  z-index: calc(var(--i) + 1);
  overflow: visible;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.isla:hover, .isla:focus-visible { z-index: 60; }
.isla:focus { outline: none; }
.isla:focus-visible .isla__tela { outline: 2px solid var(--blue-500); outline-offset: 3px; }

.isla__arte {
  width: 100%; height: 100%;
  overflow: visible;
  transform: rotate(var(--rot)) scaleX(var(--flip));
}

/* El agua alrededor de la isla: la sombra del bajo y el halo de agua somera.
   El desenfoque va en unidades del lienzo, así que escala con la isla. */
.isla__agua { opacity: 0.9; }
.isla__sombra { fill: #03172d; opacity: 0.4; }
.isla__halo { fill: #4fc2d8; opacity: 0.18; }
.isla__somero { fill: #58cfe0; opacity: 0.3; }

/* La tierra. Las cinco bandas comparten un solo filtro de costa — el
   desplazamiento por turbulencia — para que se desordenen juntas y la orilla
   quede coherente en lugar de cinco anillos concéntricos. */
.isla__banco { fill: #4cc0d0; opacity: 0.82; }
.isla__mojado { fill: #cbbd93; }
.isla__arena { fill: #ecdfb9; }
.isla__matorral { fill: #9aad66; }
.isla__monte { fill: #5d8442; }
.isla__claro { fill: #3e6b34; }

/* ---- La bandera -------------------------------------------
   Asta y tela, ancladas al centro de la isla. El plato es blanco puro por la
   misma razón que el de la banda de reconocimientos: los logotipos que llegan
   son mapas de bits opacos sobre blanco. Y width/height en auto es
   indispensable — hay que anular los atributos width/height del <img> o los
   dos topes max- aplastan la marca. */
.isla__bandera {
  position: absolute; left: 50%; bottom: 52%;
  width: 0; height: 0;
  z-index: 2;
  pointer-events: none;
  transform-origin: 0 100%;
  transition: transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.isla__asta {
  position: absolute; left: 0; bottom: 0;
  width: 2px; height: var(--asta-h);
  background: linear-gradient(180deg, #ffffff, #aebfd2);
  border-radius: 1px;
  box-shadow: 0 2px 6px rgba(3, 18, 36, 0.55);
}
.isla__tela {
  position: absolute;
  left: 2px;
  bottom: calc(var(--asta-h) - var(--tela-h));
  width: var(--tela-w); height: var(--tela-h);
  display: grid; place-items: center;
  padding: 4px 6px;
  background: #fff;
  border-radius: 0 2px 2px 0;
  box-shadow: 0 8px 22px rgba(3, 18, 36, 0.5);
  transform-origin: left center;
}
.isla__logo { width: auto; height: auto; max-width: 100%; max-height: 100%; }

/* El ondeo vive en la tela y el realce en la bandera entera, a propósito: una
   animación gana sobre una declaración normal, así que si el hover escalara la
   misma tela el ondeo se lo comería. Escalando desde el pie del asta crece
   además el conjunto, que es como se levantaría una bandera de verdad. */
@media (prefers-reduced-motion: no-preference) {
  .isla__tela {
    animation: bandera-ondear calc(7s + var(--i) * 0.19s) ease-in-out infinite;
  }
}
@keyframes bandera-ondear {
  0%, 100% { transform: perspective(280px) rotateY(0deg); }
  34% { transform: perspective(280px) rotateY(-14deg); }
  68% { transform: perspective(280px) rotateY(7deg); }
}
.isla:hover .isla__bandera,
.isla:focus-visible .isla__bandera { transform: scale(1.16); }
.isla:hover .isla__tela,
.isla:focus-visible .isla__tela { box-shadow: 0 14px 32px rgba(3, 18, 36, 0.62); }
.isla:hover .isla__halo,
.isla:focus-visible .isla__halo { opacity: 0.5; }

/* El nombre al pasar por encima. Con veinticuatro islas hace falta poder
   recorrer el mapa sin abrir veinticuatro fichas. */
.isla__nombre {
  position: absolute; left: 50%; top: calc(100% + 4px);
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: var(--font-sans); font-size: var(--step--1);
  font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: #fff;
  background: rgba(5, 22, 44, 0.78);
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  opacity: 0;
  transition: opacity 0.2s;
  pointer-events: none;
}
.isla:hover .isla__nombre,
.isla:focus-visible .isla__nombre { opacity: 1; }

/* ---- El velero --------------------------------------------
   El cursor sobre el agua. Una imagen de cursor no puede girar, así que el
   cursor nativo se apaga y este elemento ocupa su lugar: el div traslada, el
   casco gira, y así el orden de las transformaciones nunca se enreda.
   js/alianzas.js pone velero-js solo cuando de verdad armó el barco — si no,
   el cursor nativo se queda y nadie se queda sin puntero. */
@media (hover: hover) and (pointer: fine) {
  .velero-js .mar,
  .velero-js .mar .isla { cursor: none; }
  .velero-js.alianza-abierta .mar,
  .velero-js.alianza-abierta .mar .isla { cursor: auto; }
}
.velero {
  position: fixed; left: 0; top: 0; z-index: 60;
  width: 56px; height: 77px;
  /* El puntero cae en el centro del CASCO, que en el lienzo 80x110 está en
     x = 31 (38.75 %) y no a la mitad: la mayor ocupa toda la banda de estribor
     y correría la caja hacia un lado si se centrara la caja en vez del barco. */
  margin: -39px 0 0 -22px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.22s ease;
  transform: translate3d(var(--boat-x, 0px), var(--boat-y, 0px), 0);
  will-change: transform;
}
.velero.is-a-flote { opacity: 1; }
.alianza-abierta .velero { opacity: 0; }
.velero__casco {
  position: relative;
  width: 100%; height: 100%;
  transform: rotate(var(--boat-rot, 0deg));
  /* Vira sobre el casco, no sobre la caja, o el barco orbitaría al girar. */
  transform-origin: 38.75% 50%;
}
/* La estela. Va en el casco y no en el contenedor para que gire con el barco,
   y su opacidad la escribe el mismo rAF que mueve todo lo demás. */
.velero__casco::before {
  content: "";
  position: absolute; left: 38.75%; top: 92%;
  width: 46px; height: 150px;
  transform: translateX(-50%);
  background: radial-gradient(48% 50% at 50% 0%, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0) 74%);
  opacity: calc(var(--boat-speed, 0) * 0.95);
  pointer-events: none;
}
.velero__arte {
  position: relative;
  width: 100%; height: 100%;
  filter: drop-shadow(0 5px 9px rgba(2, 14, 30, 0.6));
}
/* Los contornos oscuros son lo que separa la vela blanca del casco blanco: sin
   ellos, a tamaño de cursor todo se funde en una misma mancha sobre el agua. */
.velero__obra { fill: #eef4f9; stroke: rgba(9, 27, 47, 0.55); stroke-width: 3; }
.velero__cubierta { fill: #c08f56; }
.velero__banera { fill: #1d3145; }
.velero__mayor { fill: #ffffff; stroke: rgba(9, 27, 47, 0.45); stroke-width: 2.6; }
.velero__foque { fill: #f4f8fc; stroke: rgba(9, 27, 47, 0.4); stroke-width: 2.6; }
.velero__palo { stroke: #5c6f83; stroke-width: 3.4; stroke-linecap: round; }

/* ---- La ficha de la alianza -------------------------------
   <dialog> nativo por la trampa de foco, Escape y la capa superior, que es lo
   que le gana al encabezado sticky sin pelear con z-index. Va centrada y no
   anclada a su isla: anclar veinticuatro globos dispersos pide lógica de
   colisión y se corta en las orillas. */
.alianza-dialogo {
  padding: 0; border: 0; background: none;
  width: min(100% - 2rem, 34rem);
  max-height: 88svh;
  color: var(--body);
}
.alianza-dialogo[open] { position: fixed; inset: 0; margin: auto; z-index: 200; }
.alianza-dialogo:not([open]) { display: none; }
.alianza-dialogo::backdrop {
  background: rgba(4, 18, 36, 0.62);
  backdrop-filter: saturate(120%) blur(4px);
}
.alianza-dialogo__caja {
  display: grid; gap: 1rem; justify-items: start;
  padding: clamp(1.5rem, 3vw, 2.5rem);
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 30px 70px rgba(3, 16, 32, 0.5);
}
.alianza-dialogo__plato {
  display: grid; place-items: center;
  width: 100%; max-width: 15rem; height: 6.5rem;
  padding: 0.9rem 1.2rem;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.alianza-dialogo__logo { width: auto; height: auto; max-width: 100%; max-height: 100%; }
.alianza-dialogo__nombre { font-size: var(--step-2); color: var(--ink); }
.alianza-dialogo__texto { font-family: var(--font-serif); color: var(--body); max-width: 56ch; }
.alianza-dialogo__cerrar {
  justify-self: start;
  font-family: var(--font-sans); font-size: var(--step--1); font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: #fff; background: var(--blue-600);
  border: 0; border-radius: var(--radius);
  padding: 0.7rem 1.4rem;
  cursor: pointer;
  transition: background 0.2s;
}
.alianza-dialogo__cerrar:hover { background: var(--blue-500); }

/* ---- El directorio ----------------------------------------
   Es la fuente de la verdad de la que js/alianzas.js saca las islas, y también
   el piso de la página: sin JavaScript esta lista ES la sección de alianzas,
   completa e indexable. Arriba de 860 px, cuando el script sí armó el mapa, se
   quita; abajo de 860 px veinticuatro islas no caben en una pantalla y esta
   misma retícula de banderas es la presentación. Nada se vuelve a pintar al
   cambiar de tamaño: las dos existen desde el principio y solo el CSS decide. */
.alianzas-directorio {
  background: linear-gradient(180deg, var(--mar-hondo) 0%, var(--navy-900) 100%);
}
.alianzas-directorio__titulo { color: #fff; }
.alianzas-lista {
  list-style: none; margin: 2rem 0 0; padding: 0;
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.alianza {
  display: grid; gap: 0.7rem; align-content: start;
  padding: 1.1rem;
  background: #fff;
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(4, 18, 36, 0.28);
}
.alianza__logo {
  width: auto; height: auto;
  max-height: 3.4rem; max-width: 70%;
  margin-inline: auto;
}
.alianza__nombre { font-size: var(--step-0); color: var(--ink); text-align: center; }
.alianza__texto { font-size: var(--step--1); color: var(--body); font-family: var(--font-serif); }

@media (min-width: 861px) {
  .alianzas-js .alianzas-directorio { display: none; }
}

/* Abajo de 860 px el mar se queda como portada y el archipiélago se retira:
   veinticuatro islas en 390 px no son un mapa, son un borrón. */
@media (max-width: 860px) {
  .mar {
    height: 62vh; height: 62svh;
    min-height: 340px;
  }
  .mar__escena { display: none; }
  .mar__titulo {
    left: 50%; right: auto;
    top: 50%; bottom: auto;
    width: min(100% - var(--gutter) * 2, 34rem);
    transform: translate(-50%, -42%);
    text-align: center;
  }
  .mar__lede { margin-inline: auto; }
  .mar__pista { display: none; }
}

/* Pantallas muy bajas: el 16:9 del escenario ya se encoge solo, pero el título
   y el último renglón de islas se juntan, así que el lede se retira. */
@media (min-width: 861px) and (max-height: 640px) {
  .mar__titulo h1 { font-size: var(--step-2); }
  .mar__lede, .mar__pista { display: none; }
}

/* ---- Print: the results page doubles as a board document ------- */
@media print {
  .site-header, .site-footer, .skip-link, .eval-progress,
  [data-eval-step="intro"], [data-eval-step="mode"], [data-eval-step="quiz"],
  [data-eval-step="identify"],
  .eval-sent, .eval-results .btn-row { display: none !important; }
  .section { padding-block: 0; }
  .eval-results { gap: 1.5rem; }
  .eval-details > *:not(summary) { display: block !important; }
  .eval-bar__fill, .eval-gauge__value, .eval-score__band, .eval-counter,
  .eval-radar__area, .eval-radar__dot {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
}
