/* Generated by _src/build.mjs — do not edit. Sources: tokens.css, base.css, components.css, pages.css */

/* ===== tokens.css ===== */
/* ==========================================================================
   tokens.css — the design token layer
   Generated from DESIGN.md v0.1.0. DESIGN.md is the source of truth; if the
   two disagree, DESIGN.md wins and this file is the bug.

   Every contrast ratio in the comments is stated with the ground it was
   measured against. A ratio without its ground is not a fact.
   ========================================================================== */

:root {
  /* --- Brand primitives — locked in BRAND.md:256-263. Never redefine. ----- */
  --c-azul-noche: #08182E;
  --c-azul-medio: #13304F;
  --c-cian:       #2FBBD6;
  --c-cian-claro: #9FE0EC;
  --c-hielo:      #EDF3F9;
  --c-abismo:     #040E1C;
  --c-blanco:     #FFFFFF;

  /* --- Inherited neutrals — NOT brand primitives. Named so they stop
         leaking in unnamed. See DESIGN.md §2. --------------------------- */
  --c-gris-linea: #E2E8F0;  /* decorative hairline ONLY */
  --c-gris-humo:  #CBD5E1;  /* decorative ONLY */
  --c-gris-medio: #94A3B8;  /* dark-ground ink only — fails on every light ground */
  --c-gris-tinta: #64748B;
  --c-tinta-2:    #475569;

  /* --- Surfaces ---------------------------------------------------------- */
  --bg-canvas:       var(--c-blanco);
  --bg-raised:       var(--c-hielo);      /* 1.12:1 on canvas — decorative; never a control's only boundary */
  --bg-inverse:      var(--c-azul-noche); /* 17.79:1 on canvas */
  --bg-inverse-deep: var(--c-abismo);     /* 19.24:1 on canvas */
  --bg-accent-soft:  var(--c-cian-claro); /* 1.46:1 on canvas — ground only, never text */

  /* --- Inks -------------------------------------------------------------- */
  --fg-primary:      var(--c-azul-noche); /* 17.79:1 on white · 15.92:1 on Hielo — AAA */
  --fg-secondary:    var(--c-tinta-2);    /*  7.58:1 on white ·  6.78:1 on Hielo — AAA */
  --fg-muted:        var(--c-gris-tinta); /*  4.75:1 on white — AA. FORBIDDEN on --bg-raised (4.25:1 fails) */
  --fg-link:         var(--c-azul-medio); /* 13.45:1 on white · 12.04:1 on Hielo — AAA */
  --fg-on-accent:    var(--c-azul-noche); /*  8.57:1 on Cian — AAA */
  --fg-on-inverse:   var(--c-hielo);      /* 15.92:1 on Azul noche — AAA */
  --fg-on-inverse-2: var(--c-gris-humo);  /* 11.92:1 on Azul noche — AAA */

  /* Cian is a DARK-GROUND ink. 2.30:1 on white — never text on light. */
  --accent:          var(--c-cian);

  /* --- Borders — DESIGN.md §4, the rule that earns the file -------------- */
  --border-hairline:    var(--c-gris-linea); /* content separation ONLY. Never an interactive boundary. */
  --border-control:     var(--c-gris-tinta); /* 4.75:1 on white · 4.25:1 on Hielo — every input, button
                                                outline, toggle track, checkbox, card-that-is-a-link */
  --border-control-inv: rgba(237, 243, 249, 0.45); /* ~4.45:1 on Azul noche — dark-ground twin */
  --border-strong:      var(--c-azul-medio); /* 13.45:1 — selected / active boundary */

  /* --- Semantics — no new hue except red (DEC-034) ----------------------- */
  --sem-danger-fg:     #DC2626;            /* 4.84:1 on white */
  --sem-danger-bg:     #FEF2F2;
  --sem-danger-border: #DC2626;
  --sem-danger-ink:    #991B1B;            /* 7.66:1 on --sem-danger-bg */
  --sem-success-fg:    var(--c-azul-medio); /* no green. 5.60:1 on --sem-success-bg */
  --sem-success-bg:    var(--c-cian-claro);
  --sem-warning-fg:    var(--c-azul-medio);
  --sem-info-fg:       var(--c-azul-medio);

  /* --- Type — Poppins 400/500/600/700/800. Weight 900 is NOT loaded. ----- */
  --font-sans: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  --fs-display:  clamp(2.5rem, 6vw, 4.5rem);
  --fs-h1:       clamp(2rem, 4.5vw, 3.25rem);
  --fs-h2:       clamp(1.625rem, 3.2vw, 2.375rem);
  --fs-h3:       1.375rem;
  --fs-h4:       1.125rem;
  --fs-lead:     1.1875rem;
  --fs-body:     1.0625rem;   /* 17px — not 16. BRAND.md:381 older-relative test. */
  --fs-body-sm:  0.9375rem;
  --fs-caption:  0.875rem;    /* 14px — THE FLOOR for any sentence */
  --fs-overline: 0.8125rem;   /* 13px — LABELS ONLY, 1-3 words, uppercase. Never a sentence. */

  --lh-display: 1.02;  --ls-display: -0.035em;
  --lh-h1:      1.08;  --ls-h1:      -0.03em;
  --lh-h2:      1.15;  --ls-h2:      -0.02em;
  --lh-h3:      1.25;  --ls-h3:      -0.015em;
  --lh-h4:      1.35;  --ls-h4:      -0.01em;
  --lh-lead:    1.55;
  --lh-body:    1.60;
  --lh-caption: 1.50;
  --lh-overline:1.40;  --ls-overline: 0.12em;

  --measure: 68ch;

  /* --- Spacing — 4px base. The app's scale is a SUBSET of this one. ------ */
  --sp-1:  0.25rem;  --sp-2:  0.5rem;   --sp-3: 0.75rem;  --sp-4:  1rem;
  --sp-5:  1.5rem;   --sp-6:  2rem;     --sp-7: 2.5rem;   --sp-8:  3rem;
  --sp-9:  4rem;     --sp-10: 5rem;     --sp-11: 6rem;    --sp-12: 8rem;

  --section-y:       var(--sp-10);
  --section-y-md:    var(--sp-11);
  --section-y-loose: var(--sp-12);
  /* The padding a section keeps on an edge where the ADJACENT section carries
     a different ground. The ground change is itself the separator, so the
     padding need not be. Named for the relationship, not the size. */
  --section-y-seam:  var(--sp-8);
  --gutter:          var(--sp-5);
  --gutter-md:       var(--sp-7);
  --container-max:   75rem;
  /* The sticky header's height. Was a bare 68px in components.css; anything
     that must clear the header — an anchor jump above all — reads it here, so
     the two consumers cannot drift apart. */
  --nav-h:           68px;

  /* --- Radii — adopted verbatim from src/theme/index.js ------------------ */
  --r-xs:   0.25rem;  /* legal stamps, tags — "document", not "app" */
  --r-sm:   0.5rem;
  --r-md:   0.75rem;  /* default card, default button */
  --r-lg:   1rem;
  --r-xl:   1.25rem;  /* large cards, screenshot masks */
  --r-2xl:  1.75rem;  /* hero card */
  --r-full: 999px;

  /* --- Elevation — shadow is Azul noche, not Tailwind slate ------------- */
  --elev-0: none;
  --elev-1: 0 1px 2px rgba(8, 24, 46, .04), 0 2px 8px rgba(8, 24, 46, .04);
  --elev-2: 0 1px 2px rgba(8, 24, 46, .04), 0 8px 24px rgba(8, 24, 46, .06);
  --elev-3: 0 12px 32px rgba(8, 24, 46, .10), 0 2px 6px rgba(8, 24, 46, .06);

  /* --- Motion ------------------------------------------------------------ */
  --dur-fast:  120ms;
  --dur-base:  180ms;
  --dur-slow:  320ms;
  --dur-enter: 480ms;
  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --reveal-y: 12px;

  /* --- Focus — two-tone, always. The tones are 15.92:1 apart, so at least
         one clears 3:1 against ANY palette ground. A single-colour ring
         cannot: the old `outline: 2px solid` Cian was 2.30:1 on white. ---- */
  --focus-ring-w: 3px;
  --focus-ring-offset: 3px;
  --focus-inner: var(--c-azul-noche);
  --focus-outer: var(--c-hielo);
}

/* --------------------------------------------------------------------------
   Dark ramp — AUTHORED, NOT SHIPPED on the web in v1 (DESIGN.md §5).

   Deliberately gated behind an explicit [data-theme="dark"] stamp and NOT
   behind prefers-color-scheme, so it cannot activate by accident. It lives
   here so the app's dark mode becomes auditable against a written source.

   Three corrections to src/theme/index.js are baked in:
     1. Dark mode has NO third ink. --fg-muted resolves to --fg-secondary.
        The app's darkColors.textMuted #64748B fails on BOTH dark grounds
        (4.08:1 on Abismo, 2.83:1 on Azul medio).
     2. Danger splits by ground — the light red is only 2.9:1 on dark.
     3. No token is named for its lightness. `primaryDark` inverting its own
        meaning between themes is how the session-59 login collapse happened.
   -------------------------------------------------------------------------- */
:root[data-theme="dark"] {
  --bg-canvas:       var(--c-abismo);
  --bg-raised:       var(--c-azul-medio); /* only 1.44:1 above canvas — MUST carry --border-control-inv */
  --bg-inverse:      var(--c-hielo);
  --bg-inverse-deep: var(--c-blanco);

  --fg-primary:      var(--c-hielo);      /* 17.33:1 on Abismo · 12.04:1 on Azul medio */
  --fg-secondary:    var(--c-gris-medio); /*  7.55:1 on Abismo ·  5.25:1 on Azul medio */
  --fg-muted:        var(--c-gris-medio); /* CORRECTION 1 — no third ink in dark */
  --fg-link:         var(--c-cian);       /*  8.40:1 on Abismo */
  --fg-on-inverse:   var(--c-azul-noche);
  --fg-on-inverse-2: var(--c-tinta-2);

  --border-hairline: rgba(237, 243, 249, 0.14);
  --border-control:  rgba(237, 243, 249, 0.45);
  --border-strong:   var(--c-cian);

  --sem-danger-fg:   #EF4444;             /* CORRECTION 2 — 4.73:1 on Azul noche */
  --sem-danger-bg:   rgba(239, 68, 68, 0.12);
  --sem-danger-ink:  #FCA5A5;

  --focus-inner: var(--c-hielo);
  --focus-outer: var(--c-azul-noche);

  --elev-1: none;  /* on dark, elevation is surface lightness, not shadow */
  --elev-2: none;
  --elev-3: none;
}

/* ===== base.css ===== */
/* ==========================================================================
   base.css — reset, document defaults, typography, focus, motion
   Depends on tokens.css. No component styling here.
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Guarded globally at the foot of this file — the current six pages set
     this unguarded while guarding only the reveal animation, so a
     vestibular-sensitive user who clicks an in-page link gets a full smooth
     scroll they asked the OS to suppress. */
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--fg-primary);
  background: var(--bg-canvas);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* --- Headings ------------------------------------------------------------ */
h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-weight: 800;
  color: var(--fg-primary);
  text-wrap: balance;
}
h1 { font-size: var(--fs-h1); line-height: var(--lh-h1); letter-spacing: var(--ls-h1); }
h2 { font-size: var(--fs-h2); line-height: var(--lh-h2); letter-spacing: var(--ls-h2); }
h3 { font-size: var(--fs-h3); line-height: var(--lh-h3); letter-spacing: var(--ls-h3); font-weight: 700; }
h4 { font-size: var(--fs-h4); line-height: var(--lh-h4); letter-spacing: var(--ls-h4); font-weight: 600; }

.display {
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  font-weight: 800;
}

/* --- Body copy ----------------------------------------------------------- */
p, li { margin: 0; }
p { color: var(--fg-secondary); }

.lead    { font-size: var(--fs-lead); line-height: var(--lh-lead); color: var(--fg-secondary); }
.body-sm { font-size: var(--fs-body-sm); }
.caption { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--fg-muted); }

/* 13px is for LABELS — one to three words, uppercase. Never a sentence.
   This rule is what stops the clinical scope line shipping at 13px muted
   again, which is how the most load-bearing sentence on the patient page
   became its smallest and faintest text. */
.overline {
  font-size: var(--fs-overline);
  line-height: var(--lh-overline);
  letter-spacing: var(--ls-overline);
  text-transform: uppercase;
  font-weight: 700;
  color: var(--fg-muted);
}

/* Prose blocks cap at 68ch. Cards and grids may use the full container.

   .container frames the PAGE — it owns the gutter and the outer cap.
   .prose sets the READING MEASURE. They are both width primitives and must
   never sit on one element: correct authoring is .prose nested INSIDE
   .container. `class="container prose"` shipped on /nosotros and rendered at
   75rem, because both selectors are (0,1,0) and .container is declared later
   in this file — a silent 150-character line length. The guard below makes
   the combination degrade to "measure" rather than to "container" regardless
   of source order; build.mjs rejects it outright.

   margin-inline:auto is load-bearing, not cosmetic. A 68ch column
   left-aligned in a 1200px white container reads as a MISSING RIGHT COLUMN.
   The same column centred inside a full-bleed ground reads as an editorial
   measure, because the ground draws the width and the content no longer
   claims to. The block centres; the text inside stays left-aligned, because
   centred body copy destroys the return sweep. */
.prose > * + * { margin-top: var(--sp-4); }
.prose { max-width: var(--measure); margin-inline: auto; }
.container.prose { max-width: var(--measure); }

/* --- Links --------------------------------------------------------------- */
a {
  color: var(--fg-link);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: color var(--dur-fast) var(--ease-in-out);
}
a:hover { color: var(--fg-primary); }

/* --- Media --------------------------------------------------------------- */
img, svg, video { max-width: 100%; height: auto; display: block; }

/* --- Focus — two-tone ring, applied once, globally ----------------------- */
:focus { outline: none; }
:focus-visible {
  outline: none;
  border-radius: var(--r-sm);
  box-shadow:
    0 0 0 var(--focus-ring-w) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-w) + 2px) var(--focus-outer);
}

/* --- Skip link — SC 2.4.1 Level A. All twelve current pages ship without
       one; this is the single element that fixes it site-wide. ------------ */
.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-4);
  z-index: 999;
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg-inverse);
  color: var(--fg-on-inverse);
  border-radius: var(--r-md);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(calc(-100% - var(--sp-6)));
  transition: transform var(--dur-base) var(--ease-out);
}
.skip-link:focus-visible { transform: translateY(0); color: var(--fg-on-inverse); }

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

/* --gutter-md was declared in tokens.css and referenced by nothing, so the
   container held 24px gutters at every width up to the 1200px cap. This is
   the line that makes the token real. */
@media (min-width: 56.25rem) { .container { padding-inline: var(--gutter-md); } }

.section { padding-block: var(--section-y); }
.section--inverse {
  background: var(--bg-inverse);
  color: var(--fg-on-inverse);
  padding-block: var(--section-y-loose);
}
.section--inverse h1,
.section--inverse h2,
.section--inverse h3,
.section--inverse h4 { color: var(--fg-on-inverse); }
.section--inverse p  { color: var(--fg-on-inverse-2); }
.section--raised { background: var(--bg-raised); }

/* --fg-muted is FORBIDDEN on a raised surface — 4.25:1 fails AA.
   Anything muted inside a raised band resolves up to --fg-secondary. */
.section--raised .caption,
.section--raised .overline { color: var(--fg-secondary); }

/* Wide content scrolls in its own container; the page body never scrolls
   sideways. */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* --- Reveal — visible resting state, animated only as an enhancement ----- */
.reveal {
  opacity: 0;
  transform: translateY(var(--reveal-y));
  transition:
    opacity var(--dur-enter) var(--ease-out),
    transform var(--dur-enter) var(--ease-out);
}
.reveal.is-in { opacity: 1; transform: none; }
/* No-JS and pre-hydration safety: if the observer never runs, content must
   still be readable. */
.no-js .reveal { opacity: 1; transform: none; }

/* --- Reduced motion — ONE global block, not a per-effect opt-in ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* --- Utilities ----------------------------------------------------------- */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.tabular { font-variant-numeric: tabular-nums; }

/* --- Spacing utilities --------------------------------------------------
   A deliberately tiny set. They exist so a page template never needs a
   style= attribute for a one-off margin — the failure mode that put five
   token systems on this site was exactly "just this once, inline". If a
   value is not on the scale, it does not belong on the page. */
.u-mt-3 { margin-top: var(--sp-3); }
.u-mt-4 { margin-top: var(--sp-4); }
.u-mt-8 { margin-top: var(--sp-8); }
.list-plain { list-style: none; margin: 0; padding: 0; }
.caption-left { text-align: left; padding-bottom: var(--sp-3); }

/* ===== components.css ===== */
/* ==========================================================================
   components.css — nav, footer, buttons, cards, forms, banners, figures
   Depends on tokens.css + base.css.

   The governing rule throughout: --border-hairline separates CONTENT.
   --border-control bounds anything a person can operate. Mixing them up is
   the defect class that shipped the app's invisible login fields and three
   web instances of the same bug.
   ========================================================================== */

/* --------------------------------------------------------------- Buttons -- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  min-height: 44px;                    /* SC 2.5.8 target size */
  border-radius: var(--r-md);
  border: 1.5px solid transparent;
  font-family: inherit;
  font-size: var(--fs-body-sm);
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-in-out),
    border-color var(--dur-fast) var(--ease-in-out),
    color var(--dur-fast) var(--ease-in-out);
}

.btn--primary {
  background: var(--bg-inverse);
  color: var(--fg-on-inverse);
  border-color: var(--bg-inverse);
}
.btn--primary:hover { background: var(--c-azul-medio); border-color: var(--c-azul-medio); color: var(--fg-on-inverse); }

/* Cian is a ground here, never an ink on light. Text on it is Azul noche at
   8.57:1. */
.btn--accent {
  background: var(--accent);
  color: var(--fg-on-accent);
  border-color: var(--accent);
}
.btn--accent:hover { background: var(--c-cian-claro); color: var(--fg-on-accent); }

.btn--ghost {
  background: transparent;
  color: var(--fg-primary);
  border-color: var(--border-control);   /* 4.75:1 — NOT the hairline */
}
.btn--ghost:hover { border-color: var(--border-strong); color: var(--fg-primary); }

.btn--on-inverse {
  background: transparent;
  color: var(--fg-on-inverse);
  border-color: var(--border-control-inv); /* 4.45:1 on Azul noche */
}
.btn--on-inverse:hover { background: rgba(237, 243, 249, 0.10); color: var(--fg-on-inverse); }

.btn--block { width: 100%; }

/* ------------------------------------------------------------------- Nav -- */
.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--bg-canvas);
  border-bottom: 1px solid var(--border-hairline);  /* content separation — correct use */
}
.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: var(--nav-h);
}
.nav__mark { display: block; height: 30px; width: auto; }
.nav__mark img, .nav__mark svg { height: 30px; width: auto; }

.nav__links {
  display: none;
  align-items: center;
  gap: var(--sp-5);
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav__link {
  font-size: var(--fs-body-sm);
  font-weight: 500;
  color: var(--fg-primary);
  text-decoration: none;
}
.nav__link:hover { color: var(--fg-link); }
.nav__link[aria-current="page"] { color: var(--fg-link); font-weight: 600; }

.nav__actions { display: flex; align-items: center; gap: var(--sp-3); }

.nav__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  background: transparent;
  border: 1.5px solid var(--border-control);
  border-radius: var(--r-md);
  cursor: pointer;
  color: var(--fg-primary);
}

/* The panel is display:none when closed, not opacity:0 — an opacity-hidden
   panel keeps its links in the tab order, which is SC 2.4.3 Level A and is
   live on the current pages. */
.nav__panel { display: none; }
.nav__panel[data-open="true"] {
  display: block;
  padding-block: var(--sp-4) var(--sp-6);
  border-top: 1px solid var(--border-hairline);
}
.nav__panel .nav__links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
}

@media (min-width: 56.25rem) {
  .nav__links { display: flex; }
  .nav__toggle { display: none; }
  .nav__panel { display: none !important; }
}

/* ---------------------------------------------------------------- Footer -- */
.footer {
  background: var(--bg-inverse);
  color: var(--fg-on-inverse);
  padding-block: var(--section-y) var(--sp-8);
}
.footer a { color: var(--fg-on-inverse); text-decoration-color: var(--border-control-inv); }
.footer a:hover { color: var(--accent); }
.footer__grid {
  display: grid;
  gap: var(--sp-8);
  grid-template-columns: 1fr;
}
.footer__col-title {
  font-size: var(--fs-overline);
  letter-spacing: var(--ls-overline);
  text-transform: uppercase;
  font-weight: 700;
  color: var(--fg-on-inverse-2);
  margin-bottom: var(--sp-3);
}
.footer__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
.footer__legal {
  margin-top: var(--sp-8);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border-control-inv);
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--fg-on-inverse-2);
}
@media (min-width: 37.5rem) { .footer__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 56.25rem) { .footer__grid { grid-template-columns: 2fr 1fr 1fr 1fr; } }

/* ----------------------------------------------------------------- Cards -- */
/* Not everything is a card. Border, fill, radius and shadow each say
   "separate object" — spend them by role. */
.card {
  background: var(--bg-canvas);
  border: 1px solid var(--border-hairline);
  border-radius: var(--r-xl);
  padding: var(--sp-6);
}
.card--raised { background: var(--bg-raised); border-color: transparent; }
.card--lifted { box-shadow: var(--elev-2); border-color: transparent; }

/* A card that is itself a link is an interactive control and takes the
   control boundary, not the hairline. */
.card--link { border-color: var(--border-control); text-decoration: none; display: block; }
.card--link:hover { border-color: var(--border-strong); }

.grid { display: grid; gap: var(--sp-5); }
@media (min-width: 37.5rem) { .grid--2 { grid-template-columns: repeat(2, 1fr); } }
/* .grid sets no align-items, so stretch applies and a short card is pulled to
   its partner's height — a box with content at the top and dead space at the
   bottom, which is exactly what an unfilled slot looks like. Size to content
   instead; the rag is honest and content-shaped.
   Deliberately NOT on .grid--3: the three price tiers must stay comparable at
   a glance, and ragged tier cards obstruct that comparison. Those stay
   stretched. */
@media (min-width: 37.5rem) { .grid--2 { align-items: start; } }
@media (min-width: 56.25rem) {
  .grid--3 { grid-template-columns: repeat(3, 1fr); }
  .grid--4 { grid-template-columns: repeat(4, 1fr); }
}

/* ----------------------------------------------------------------- Steps -- */
/* Numbered because these ARE a sequence — the order carries information the
   reader needs. Do not reuse this pattern for a non-sequence. */
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-6); counter-reset: step; }
/* 2.5rem, not 2rem: these counters are the only Cian on any light ground on
   the entire site, and at 32px they read as a bullet rather than as a design
   decision. Not controls, so no target-size implication. */
.step { counter-increment: step; display: grid; grid-template-columns: 2.5rem 1fr; gap: var(--sp-4); align-items: start; }
.step::before {
  content: counter(step);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem; height: 2.5rem;
  border-radius: var(--r-full);
  background: var(--bg-accent-soft);
  color: var(--fg-on-accent);
  font-size: var(--fs-body);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ----------------------------------------------------------------- Forms -- */
.field { display: grid; gap: var(--sp-2); }
.field__label { font-size: var(--fs-body-sm); font-weight: 600; color: var(--fg-primary); }

.input {
  width: 100%;
  min-height: 44px;
  padding: var(--sp-3) var(--sp-4);
  font-family: inherit;
  font-size: var(--fs-body);
  color: var(--fg-primary);
  background: var(--bg-canvas);
  border: 1.5px solid var(--border-control);  /* 4.75:1 — the whole point */
  border-radius: var(--r-md);
}
.input::placeholder { color: var(--fg-muted); opacity: 1; }  /* 4.75:1, not the 3.28:1 currently live */
.input:hover { border-color: var(--border-strong); }

.input--on-inverse {
  background: rgba(237, 243, 249, 0.06);
  color: var(--fg-on-inverse);
  border-color: var(--border-control-inv);   /* 4.45:1 — replaces the live 1.44:1 */
}
.input--on-inverse::placeholder { color: var(--fg-on-inverse-2); opacity: 1; }

/* --------------------------------------------------------------- Banners -- */
.banner {
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-md);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
}
.banner--info   { background: var(--bg-raised); color: var(--fg-secondary); }
.banner--danger {
  background: var(--sem-danger-bg);
  color: var(--sem-danger-ink);
  border: 1px solid var(--sem-danger-border);
}
.banner--danger strong { color: var(--sem-danger-ink); }

/* The 911 line. ONE rendering, site-wide — the current pages carry three
   different versions of this sentence (tú, usted, and REMEDIO in caps).
   Minimum 14px: it is a sentence, not a label. */
.emergency {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--fg-secondary);
}
.section--inverse .emergency, .footer .emergency { color: var(--fg-on-inverse-2); }

/* ----------------------------------------------------------------- Chips -- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-full);
  background: var(--bg-accent-soft);
  color: var(--fg-on-accent);
  font-size: var(--fs-caption);
  font-weight: 600;
}
.chip--outline { background: transparent; border: 1px solid var(--border-control); color: var(--fg-secondary); }

/* ----------------------------------------------------- Price / legal stamp -- */
/* --r-xs at 4px is deliberate here and nowhere else: a sharper corner does
   semantic work, reading as a document rather than an app surface.
   Treat as locked: changes to this element go through review. */
.stamp {
  display: inline-block;
  padding: var(--sp-4) var(--sp-5);
  border: 1.5px solid var(--border-strong);
  border-radius: var(--r-xs);
  background: var(--bg-canvas);
}
.stamp__label { font-size: var(--fs-overline); letter-spacing: var(--ls-overline); text-transform: uppercase; font-weight: 700; color: var(--fg-secondary); }
.stamp__value { font-size: var(--fs-h2); font-weight: 800; color: var(--fg-primary); font-variant-numeric: tabular-nums; }
/* The total-price note is a sentence and never drops below the 14px floor. */
.stamp__note  { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--fg-secondary); }

/* --------------------------------------------------------------- Figures -- */
/* Screenshots come from `originales 6.9/`, which have no burned-in overlay.
   Overlay text baked into a PNG is text-as-image (SC 1.4.5). */
.figure { margin: 0; display: grid; gap: var(--sp-3); justify-items: center; }
.figure__frame {
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: var(--elev-2);
  background: var(--bg-raised);
  max-width: 320px;
}
.figure figcaption {
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--fg-secondary);
  text-align: center;
  max-width: 34ch;
}

/* ----------------------------------------------------------------- Table -- */
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-body-sm); }
.table th, .table td { padding: var(--sp-3) var(--sp-4); text-align: left; border-bottom: 1px solid var(--border-hairline); }
.table th { font-weight: 700; color: var(--fg-primary); }
.table td { color: var(--fg-secondary); }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- Accordion */
/* NOT an accordion, and deliberately not becoming one. The questions are ten
   <h3> elements: no <details>, no aria-expanded, no JS anywhere in js/. This
   block used to carry the full button reset — width:100%, display:flex,
   min-height:44px, background:none, border:none, font-family:inherit,
   text-align:left, cursor:pointer — wired to nothing, so every question
   showed a hand cursor and did nothing on click. The affordance lied, and it
   spent ~92px of padding per row on an interaction that does not exist.

   The answers stay open, by decision, not by omission: on a page whose
   content the reader needs in order to decide, awareness beats convenience.
   Do not add a collapse.

   The <h3> also stays — a screen-reader user's primary navigation on a long
   FAQ is the heading list (VoiceOver rotor, H key in NVDA/JAWS). A <dl> would
   remove all ten from the document outline. */
.accordion { border-top: 1px solid var(--border-hairline); }
.accordion__item { border-bottom: 1px solid var(--border-hairline); }
.accordion__q {
  padding: var(--sp-5) 0;
  font-size: var(--fs-h4);
  font-weight: 600;
  color: var(--fg-primary);
}
.accordion__a { padding-bottom: var(--sp-5); max-width: var(--measure); }
.accordion__a p + p { margin-top: var(--sp-3); }

/* An anchor jump would otherwise land the target under the sticky header. */
.accordion__item { scroll-margin-top: calc(var(--nav-h) + var(--sp-5)); }

/* Ten identical single-column rows whose text stops at 68ch while their rules
   run the full container: ten consecutive empty right-hand rectangles, and a
   reader whose only way to find her question is to read all of them. The row
   was composed as one column when its content is two. Question left as a
   scannable rail, answer right at the measure — the fr track absorbs whatever
   the measure-capped answer does not use, so the row fills the container by
   construction and the full-width rule becomes honest. The rules stay at the
   container: a full-width rule is a promise the full width is composed, and
   the fix is to compose it, not to shorten the rule. */
@media (min-width: 56.25rem) {
  .accordion__item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, var(--measure));
    column-gap: var(--sp-8);
    align-items: start;
  }
  .accordion__q { padding: var(--sp-5) 0; }
  .accordion__a { padding: var(--sp-5) 0; }
}

/* ------------------------------------------------------------- Q&A index -- */
/* What carries the scent. Ten questions under one undifferentiated heading
   gave the reader no plan but "read all of it" — that is the drag, and it is
   a wayfinding failure, not a length failure. Labels are the ten question
   strings that already exist, so this costs zero advertising copy. Direct
   addressing beats category narrowing at this length; past ~15 questions,
   switch to group headings. */
.qa-index { margin-bottom: var(--sp-8); }
.qa-index a { color: var(--fg-link); }   /* 13.45:1 on canvas */

/* Footer bits that would otherwise be inline styles. The whole point of the
   token layer is that nothing needs a style= attribute; these three exist
   because the footer has one entity line, one link-shaped button and one
   copyright line, and each was briefly inlined during the migration. */
.footer__entity    { border: 0; margin-top: var(--sp-4); padding: 0; }
.footer__copyright { margin-top: var(--sp-3); }
.footer__linkbtn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
.footer__linkbtn:hover { color: var(--accent); }

/* ------------------------------------------------- Internal rhythm ------- */
/* base.css resets h1–h6 and p to margin:0. That is right for a reset and
   wrong for every component that stacks a heading on a body line: .card,
   .step and .stamp all rendered their title welded to their text, line-box
   touching line-box, on twenty-one instances across twelve pages. .prose was
   the only place in the stylesheet that ever re-established the spacing the
   reset removed. One rule, not twenty-one. */
.card > * + *,
.step > div > * + * { margin-top: var(--sp-3); }

/* The stamp is a document, not a card: label and value belong together, the
   note belongs slightly apart. Specificity is deliberate — the paired
   selectors are (0,0,2,0) and outrank the (0,0,1,0) sweep above, so nothing
   here needs !important. */
.stamp > * + *                { margin-top: var(--sp-1); }
.stamp__label + .stamp__value { margin-top: var(--sp-2); }
.stamp__value + .stamp__note  { margin-top: var(--sp-3); }
.card .stamp__value           { margin-top: var(--sp-4); }

/* -------------------------------------------------------- Showcase ------- */
/* The app IS the product and these screenshots are the only real visual
   asset the site owns. Live, each renders at 320px in a frame whose only
   separation from the page is --elev-2 at 6% alpha — and they are
   light-ground images on --bg-canvas, so the frame edge is ~1.05:1 and
   effectively absent. The frame is not too small so much as ungrounded.
   .showcase gives it a dark ground: the same image separates from #08182E
   at roughly 17:1 by its own content, which is why this panel carries no
   border and no shadow. DESIGN.md §7 — on dark grounds, elevation is
   surface lightness, not shadow. */
.showcase {
  background: var(--bg-inverse);
  display: grid;
  gap: var(--sp-5);
  justify-items: center;
  /* Full-bleed below --bp-sm. The panel's padding would otherwise cost the
     frame ~50px of width on a 360px phone, i.e. the dark ground would make
     the screenshot SMALLER than it is today. Only reachable while
     .container is still a plain block — .hero--split does not become a
     grid until 56.25rem — so the negative margin cannot escape a track. */
  margin-inline: calc(var(--gutter) * -1);
  padding: var(--sp-5) var(--sp-4);
  border-radius: 0;
}

/* One crop ratio for every frame in a showcase. Three reasons, in order:
   a full-height 1284x2778 capture at 420px wide is 909px tall and would
   make the hero ~1000px; the home captures are cropped (2064 / 2059) and
   the others are not, so side by side they rag; and the top of a phone
   screen is the part that carries meaning — the bottom is tab bar.
   Crop framing only. No new capture. */
.showcase .figure       { width: 100%; }
.showcase .figure__frame {
  width: 100%;
  max-width: 420px;
  aspect-ratio: 1284 / 1900;
  border-radius: var(--r-xl);   /* 20 — DESIGN.md §7: "screenshot masks" */
  background: transparent;
  box-shadow: none;             /* invisible on Azul noche — see above */
}
.showcase .figure__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
}

/* MANDATORY, not cosmetic. .figure figcaption is --fg-secondary #475569,
   which on --bg-inverse #08182E measures 2.35:1 and fails SC 1.4.3
   outright. --fg-on-inverse-2 #CBD5E1 on #08182E is 11.92:1 (DESIGN.md §3).
   Do not ship the panel without this line. */
.showcase figcaption { color: var(--fg-on-inverse-2); }

/* Two or three frames on one panel. */
.showcase--row { gap: var(--sp-5); }

@media (min-width: 37.5rem) {
  .showcase {
    margin-inline: 0;
    border-radius: var(--r-2xl);  /* 28 — DESIGN.md §7 defines this radius
                                     for exactly one thing: the hero card */
    padding: var(--sp-7);
  }
  /* A single frame on a full-width panel leaves a 1200px dark slab around a
     420px object. Cap it. In the hero the grid column already caps it, so
     --solo is only for doctores and any future single-frame section. */
  .showcase--solo { max-width: 34rem; margin-inline: auto; }
  .showcase--row {
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    align-items: start;
    gap: var(--sp-6);
  }
  .showcase--row .figure__frame { max-width: 300px; }
}

@media (min-width: 56.25rem) { .showcase { padding: var(--sp-8); } }

/* ------------------------------------------------------------ Band ------- */
/* Every inverse section on the site is one left-aligned column of two or
   three short lines inside --section-y-loose (8rem) spanning the full 75rem
   container. On a 1440px display the index coverage band is roughly
   1200 x 400 px carrying about 240 characters, all of it at the left edge.
   That is not a metaphor for empty. .band puts the argument on the left at
   a readable measure and one hard fact on the right, so the band holds two
   objects and earns its height. */
.band { display: grid; gap: var(--sp-7); align-items: start; }
.band__lede { max-width: 46ch; }
.band__lede .sec-head { max-width: none; margin-bottom: 0; }
.band__fact { display: grid; gap: var(--sp-3); justify-items: start; }
.band__fact-value {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  letter-spacing: var(--ls-h3);
  font-weight: 700;
  color: var(--fg-primary);
}

@media (min-width: 56.25rem) {
  .band { grid-template-columns: 1.1fr 0.9fr; gap: var(--sp-9); }
}

/* ------------------------------ Inverse-band ink re-assertion ------------ */
/* SYSTEMIC, and worth reading even if you skip the rest. base.css:128-132
   recolours every descendant p and h1–h4 inside .section--inverse. Those
   selectors are (0,0,1,1) and therefore outrank EVERY single-class
   component rule, which is (0,0,1,0). Any component that brings its own
   ground into an inverse band silently loses its ink to the band.
   Concretely: <p class="chip"> in an inverse band renders --fg-on-inverse-2
   #CBD5E1 on --bg-accent-soft #9FE0EC = 1.01:1 — invisible. This is not a
   hypothetical; it is what would have shipped tonight without these four
   lines. Two classes beat one class plus one element, so `.section--inverse
   .chip` is sufficient. */
.section--inverse .chip            { color: var(--fg-on-accent); }   /* #08182E on #9FE0EC = 12.15:1 */
.section--inverse .banner--info    { color: var(--fg-secondary); }   /* #475569 on #EDF3F9 = 6.78:1 */
.section--inverse .stamp p         { color: var(--fg-secondary); }
.section--inverse .stamp__value    { color: var(--fg-primary); }
.section--inverse p.band__fact-value { color: var(--fg-on-inverse); } /* #EDF3F9 on #08182E = 15.92:1 */

/* --------------------------------------------- Cards on a raised band ---- */
/* A white .card on --bg-raised is 1.12:1 by fill and its hairline is 1.10:1
   against that band — DESIGN.md §4 measures both. Four cards nobody can see
   is four cards' worth of "empty". .card--lifted already exists and is
   unused; on a Hielo band --elev-2 at 6% alpha is still marginal, so raise
   it to --elev-3 (10%) for exactly this case. --elev-3 is declared in
   tokens.css and referenced by nothing today. */
.section--raised .card--lifted { box-shadow: var(--elev-3); }

/* ------------------------------------------- Tier price in a card ------- */
/* .stamp__value is --fs-h2 (up to 38px), which is right for the single
   homepage stamp on a full-width block and wrong inside a three-up tier
   card ~230px wide. With the "desde" prefix added 2026-09-10 the string
   is "desde $1,256.55 MXN" and it wraps, stranding the word "desde"
   alone on a line above the number — seen rendered, not inferred.
   The qualifier must never read larger than, or separate from, the
   figure it qualifies. */
.card .stamp__value {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  letter-spacing: var(--ls-h3);
  text-wrap: nowrap;
}

/* ===== pages.css ===== */
/* ==========================================================================
   pages.css — page-specific layout only. Anything reused twice belongs in
   components.css instead.
   ========================================================================== */

/* ------------------------------------------------------------------ Hero -- */
.hero { padding-block: var(--sp-9) var(--section-y); }
.hero__kicker { margin-bottom: var(--sp-4); }
.hero__title  { margin-bottom: var(--sp-5); max-width: 18ch; }
.hero__sub    { max-width: 54ch; margin-bottom: var(--sp-4); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-6); }

/* The attribution line sits under every hero. Treat as locked: changes here
   go through review. Never below the 14px floor. */
.hero__attribution {
  margin-top: var(--sp-6);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border-hairline);
  font-size: var(--fs-caption);
  line-height: var(--lh-caption);
  color: var(--fg-secondary);
  max-width: 58ch;
}

@media (min-width: 56.25rem) {
  /* 6rem bottom + the next section's 5rem top left 11rem of unbroken ground
     between the attribution line and the first <h2> on every page. The hero
     always abuts a section carrying a different ground, and the ground change
     is the separator — so the seam value, not the loose one. Applied
     unconditionally rather than via :has(+ .section--raised): one line, six
     pages, no modifier anyone can forget on a new page. */
  .hero { padding-block: var(--sp-11) var(--section-y-seam); }
  /* The right column must hold a 420px frame plus --sp-8 of panel padding on
     each side = 516px. 0.85fr of (1120 - 48) gave ~490px, which squeezed the
     frame to ~394px — index's hero screenshot was already rendering below the
     420px it was specified at. 33rem = 528px clears it. */
  .hero--split {
    display: grid;
    grid-template-columns: 1fr minmax(0, 33rem);
    gap: var(--sp-8);
    align-items: center;
  }
}

/* --------------------------------------------------------- Section header -- */
.sec-head { max-width: var(--measure); margin-bottom: var(--sp-8); }
.sec-head__kicker { margin-bottom: var(--sp-3); }
.sec-head__title  { margin-bottom: var(--sp-4); }

/* ------------------------------------------------------------ Screenshots -- */
.shots {
  display: grid;
  gap: var(--sp-6);
  grid-template-columns: 1fr;
  justify-items: center;
}
@media (min-width: 37.5rem) { .shots--2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 56.25rem) { .shots--3 { grid-template-columns: repeat(3, 1fr); } }

/* ----------------------------------------------------------------- Price -- */
.price-block { display: grid; gap: var(--sp-5); justify-items: start; }

/* -------------------------------------------------------------- Contact ---- */
.contact { display: grid; gap: var(--sp-5); max-width: 34rem; }
