/* Defensa SI landing — styles.css
 * GENERATED by tools/build.mjs from src/styles.css against tokens@1.0.0. DO NOT EDIT.
 * Every value is a var(--ds-*) reference from tokens/tokens.css (Contract A). Zero colour literals.
 * Token references used: 63 of 76 declared.
 */
/* ---------------------------------------------------------------------------
 * Defensa SI landing — authored stylesheet (src). Every value is a var(--ds-*)
 * reference produced by tokens@1.0.0; there is no colour literal in this file.
 * Contract B §1/§2 GATE B1 · design-spec §2.
 * Media queries are mobile-first min-width in px (Contract B §3 — media queries
 * cannot use custom properties; the px values mirror layout.breakpoint.*).
 *
 * P7 (2026-10-11) — art-direction.md §1–§5 implemented:
 *   §1 typography  · lead to text.primary, body to text.secondary; hero h1 at display;
 *                    hero subhead a flat h2 (snug); section titles keep h1; scale frozen.
 *   §2 colour      · amarillo in exactly four places; verde = secondary action + the
 *                    #gobernanza ring; azul = one 4 px full-bleed footer rule; rojo =
 *                    the #riesgo panel + the SUGEF container's 4 px border-inline-start.
 *   §3 rhythm      · alternating bg.base / bg.surface; the 2 px border.subtle section rule
 *                    (#hero exempt — P10 removed the #nodo exemption); space.5 above the
 *                    lead at 768+; B-1 stacking.
 *   §4 motion      · opacity/transform (+ fill/stroke/border-color for #nodo), one-shot,
 *                    every duration reads a --ds-motion-* variable, visible baseline.
 *   §5 sections    · the nine-block table.
 * ------------------------------------------------------------------------- */

/* Inter (body) and Space Grotesk (display) — 2 files, latin subset, woff2, variable
 * (design-spec §8: ≤ 2 font files; both preloaded from index.html). */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("assets/fonts/inter-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("assets/fonts/space-grotesk-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

/* ------------------------------------------------------------------ base */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background-color: var(--ds-color-bg-base);
  color: var(--ds-color-text-primary);
  font-family: var(--ds-type-family-body);
  font-size: var(--ds-type-size-body);
  font-weight: var(--ds-type-weight-regular);
  line-height: var(--ds-type-line-height-normal);
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--ds-type-family-display);
  font-weight: var(--ds-type-weight-semibold);
  line-height: var(--ds-type-line-height-tight);
  letter-spacing: var(--ds-type-tracking-tight);
  /* FINDING F-1 — the fluid clamp on `.fs-h1` below is the fix (see P3.1-check.md); this
   * `overflow-wrap` still guards any residual single word longer than the content box. */
  overflow-wrap: break-word;
}

p, ul, ol, dl, dd, figure { margin: 0; }

ul, ol { padding-inline-start: var(--ds-space-5); }

a { color: var(--ds-color-brand-celeste); text-decoration-thickness: 2px; text-underline-offset: 3px; }

img, svg { max-width: 100%; }

img { height: auto; }

::selection { background-color: var(--ds-color-brand-amarillo); color: var(--ds-color-text-inverse); }

/* One focus indicator, used everywhere (design-spec §6.4): the 2 px offset is
 * load-bearing — the celeste ring always sits on the dark base, never on a light fill. */
:focus-visible {
  outline: 2px solid var(--ds-color-focus-ring);
  outline-offset: 2px;
  border-radius: var(--ds-radius-sm);
}

/* ------------------------------------------------------------------ layout */

.ds-container {
  width: 100%;
  max-width: var(--ds-layout-container-max);
  margin-inline: auto;
  padding-inline: var(--ds-layout-gutter);
}

/* §3.2 — each section opens with a 2 px border.subtle full-bleed rule above its content.
 * The hero (a <header>, not a .ds-section) is the one exemption: it is the section the page
 * opens with, so there is nothing above it to be divided from. `#nodo`'s exemption was
 * removed in P10 — its ground change already marks that seam, and one missing rule among
 * eight read as a defect (S3). */
.ds-section {
  padding-block: var(--ds-space-8);
  border-block-start: 2px solid var(--ds-color-border-subtle);
}
.ds-section--base { background-color: var(--ds-color-bg-base); }
.ds-section--surface { background-color: var(--ds-color-bg-surface); }

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

/* the drawing column and the prose column (Contract B §3, as amended by B-1, §3.3):
 * stacked 768–1279, two columns 440 / 648 at lg (1280 px). */
.ds-section__inner { display: grid; gap: var(--ds-space-6); }

.ds-stack > * + * { margin-block-start: var(--ds-space-4); }

/* -------------------------------------------------------------------- type */

/* ------------------------------------------------------- P3.1 · fluid type (F-1)
 * Andler-direct ruling, 2026-10-11: the type scales DOWN on very low viewports instead of
 * overflowing. Bounds are frozen tokens (Contract A) only — the one literal in each clamp is
 * the vw coefficient, the pattern design-spec §2.1 already sanctions for the display row.
 * Every clamp reaches its frozen size by 420 px, so nothing at or above 420 px is shrunk. */

/* the one display moment (§1.2) */
.fs-display {
  font-size: clamp(var(--ds-type-size-h1), 10.5vw, var(--ds-type-size-display));
  font-weight: var(--ds-type-weight-bold);
}
/* section titles keep type.size.h1 (§1.2) — the fluid floor only bites below ~419 px, and it
 * is what closes finding F-1 (at 280 px «Superinteligencia» is 196.5 px, inside the box). */
.fs-h1 { font-size: clamp(var(--ds-type-size-h3), 8.6vw, var(--ds-type-size-h1)); }
.fs-h2 { font-size: var(--ds-type-size-h2); line-height: var(--ds-type-line-height-snug); }
.fs-h3 { font-size: var(--ds-type-size-h3); line-height: var(--ds-type-line-height-snug); }

/* §1.2 — the lead outranks the body: lead bodyLg / relaxed / text.primary; body steps
 * back to text.secondary. No new token; one colour swap per element. */
.fs-lead {
  font-size: var(--ds-type-size-body-lg);
  line-height: var(--ds-type-line-height-relaxed);
  color: var(--ds-color-text-primary);
}
/* the body paragraphs step back, and the lead / eyebrow / chips step back forward. */
.ds-prose > p { color: var(--ds-color-text-secondary); }
.ds-prose > p.fs-lead { color: var(--ds-color-text-primary); }
.ds-prose > p.ds-eyebrow { color: var(--ds-color-text-muted); }
.ds-prose > p.ds-gate { color: var(--ds-color-text-primary); }
.ds-prose > p.ds-cta-line { color: var(--ds-color-text-primary); }
.ds-prose > p.ds-sugef { color: var(--ds-color-state-danger-text); }
.fs-small { font-size: var(--ds-type-size-small); }
.fs-caption { font-size: var(--ds-type-size-caption); }

.ds-eyebrow {
  font-family: var(--ds-type-family-body);
  font-size: var(--ds-type-size-small);
  font-weight: var(--ds-type-weight-medium);
  line-height: var(--ds-type-line-height-snug);
  letter-spacing: var(--ds-type-tracking-wide);
  text-transform: uppercase;
  color: var(--ds-color-text-muted);
}
/* the hero eyebrow is the long canton line — uppercase would wrap it three times at 320 px.
 * It keeps tracking.wide and becomes sentence case (§3.4). */
.ds-eyebrow--quiet { text-transform: none; }

/* §2.2 (3) — the especulativo marker, one of the four permitted uses of amarillo. The word
 * carries it inside the #si eyebrow, and the star inside the drawing carries it again. */
.ds-mark-espec { color: var(--ds-color-brand-amarillo); }

.ds-caption {
  margin-block-start: var(--ds-space-4);
  font-size: var(--ds-type-size-body);
  font-weight: var(--ds-type-weight-semibold);
  line-height: var(--ds-type-line-height-snug);
  color: var(--ds-color-text-primary);
}

/* --------------------------------------------------------------- a11y util */

.ds-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* First focusable element of the document (design-spec §6.3). */
.ds-skip {
  position: absolute;
  inset-block-start: var(--ds-space-2);
  inset-inline-start: var(--ds-space-2);
  z-index: 10;
  padding: var(--ds-space-3) var(--ds-space-4);
  background-color: var(--ds-color-bg-elevated);
  color: var(--ds-color-text-primary);
  border: 2px solid var(--ds-color-focus-ring);
  border-radius: var(--ds-radius-sm);
  font-size: var(--ds-type-size-small);
  font-weight: var(--ds-type-weight-semibold);
  transform: translateY(-200%);
}
.ds-skip:focus-visible { transform: none; }

/* -------------------------------------------------------------------- hero */

.ds-hero {
  /* P8/D3 — the hero owns the first screen. svh, never dvh: dvh changes as the mobile
   * chrome hides, which would make the canton band (pinned to the bottom edge) jump. */
  display: flex;
  flex-direction: column;
  min-height: 100svh;
  /* P13/L1 (P12-DESIGN-DIRECTION §1.1) — the hero carries NO block padding. It is an
   * ownership move, not a value change: the padding lives on .ds-hero__inner (the hero's
   * own .ds-container) so the canton band can sit FLUSH on the hero's bottom edge and be
   * the hero's delimiter, not a header rule. `min-height`/`svh` (P8/D3) are untouched. */
}

/* single column below lg: TEXT leads and the device follows (P8/D3 — the claim and the CTA are
 * what the reader meets first on a small screen). At 768 and up the device leads again, §3.4.
 * DOM order stays text-first — the reading order is the narrative. */
/* P13/L1 — this IS the hero's .ds-container, and it now OWNS the hero's block padding.
 * The values are the shipped ones, re-homed: 24 / 96 px to 767 px, 64 / 128 px from 768. */
.ds-hero__inner {
  display: grid;
  gap: var(--ds-space-8);
  flex: 1 0 auto;
  align-content: center;
  padding-block-start: var(--ds-space-5);
  padding-block-end: var(--ds-space-9);
}
/* P8/D3 - below 768 the vertical space is scarce and the claim plus the CTA must be what the
 * reader meets first, so the TEXT leads and the device follows. This supersedes 3.4's
 * device-first order on small screens only; at 768 and up the device still leads. */
.ds-hero__text { grid-row: 1; }
.ds-hero__device { grid-row: 2; }

/* §3.4 — the canton band closes the hero from below: the ONE place the flag is literal.
 * P13/L1 — it sits FLUSH on the hero's bottom edge (band.bottom === hero.bottom ===
 * #defensa-si.top). `flex: none` is defensive hardening: the three spans are fixed-height,
 * so the band itself must never shrink (F6). "Each line" = the three band lines: 8 -> 12 px. */
.ds-band { display: flex; flex-direction: column; width: 100%; flex: none; }
.ds-band > span { display: block; height: var(--ds-space-3); }
.ds-band__celeste { background-color: var(--ds-color-brand-celeste); }
.ds-band__blanco { background-color: var(--ds-color-brand-blanco); }
.ds-band__verde { background-color: var(--ds-color-brand-verde); }

.ds-hero__subhead {
  /* §1.2 — a flat h2 with snug leading: the hero reads as two display lines, not a
   * headline with a caption under it. (Supersedes the F-1 clamp on this one element.) */
  margin-block-start: var(--ds-space-4);
  font-size: var(--ds-type-size-h2);
  font-weight: var(--ds-type-weight-semibold);
  line-height: var(--ds-type-line-height-snug);
  color: var(--ds-color-text-primary);
}

.ds-hero__lead { margin-block-start: var(--ds-space-4); max-width: 46ch; }

/* the primary action, above the fold (§3.4) */
.ds-hero__actions { margin-block-start: var(--ds-space-6); }

.ds-hero__cue {
  display: block;
  margin-block-start: var(--ds-space-6);
  color: var(--ds-color-text-muted);
}
.ds-hero__chevron { display: block; width: 32px; height: 20px; }

/* §3.5 — the hero device. Geometry only; the centre is empty on purpose. */
.ds-hero__device-svg {
  display: block;
  width: 100%;
  height: auto;
  max-width: 280px;
  margin-inline: auto;
}
.ds-hero__device-edge {
  stroke: var(--ds-color-brand-celeste);
  stroke-width: 3;
  /* 180 ≥ the pentagon edge length (176.34), so the baseline draw is a solid line;
   * only `html.js` + the un-ready state hides it, and the draw-in is one shot. */
  stroke-dasharray: 180;
  stroke-dashoffset: 0;
}
.ds-hero__device-node { fill: var(--ds-color-brand-celeste); }

/* ------------------------------------------------------- infographic figure */

.ds-figure { margin: 0; }

.ds-inf__svg {
  display: block;
  width: 100%;
  height: auto;
  color: var(--ds-color-text-secondary);
}

/* on-canvas type: token sizes used as SVG user units. At the 320 px breakpoint the
 * drawing column is 272 px wide, so h2 (28u) renders at ≈11.9 px — legible at the
 * contract's minimum viewport. */
.ds-inf__t {
  font-family: var(--ds-type-family-body);
  font-size: var(--ds-type-size-h2);
  font-weight: var(--ds-type-weight-semibold);
  letter-spacing: var(--ds-type-tracking-normal);
}
.ds-inf__t--lead { font-size: var(--ds-type-size-h1); }

/* token inks — the drawing's palette, never a literal */
.ds-ink-celeste { fill: var(--ds-color-brand-celeste); }
.ds-ink-amarillo { fill: var(--ds-color-brand-amarillo); }
.ds-ink-rojo { fill: var(--ds-color-accent-cr-rojo); }
.ds-ink-verde { fill: var(--ds-color-brand-verde); }
.ds-ink-blanco { fill: var(--ds-color-brand-blanco); }

.ds-stroke-celeste { stroke: var(--ds-color-brand-celeste); }
.ds-stroke-amarillo { stroke: var(--ds-color-brand-amarillo); }
.ds-stroke-rojo { stroke: var(--ds-color-accent-cr-rojo); }
.ds-stroke-verde { stroke: var(--ds-color-brand-verde); }
.ds-stroke-blanco { stroke: var(--ds-color-brand-blanco); }
.ds-stroke-strong { stroke: var(--ds-color-border-strong); }

.ds-fill-elevated { fill: var(--ds-color-bg-elevated); }

/* ------------------------------- #nodo — the constellation (nodo-interaction.md §4)
 * Every value is a --ds-* variable, transition durations included. The pairing is two
 * channels: a colour and a rule weight. Nothing is conveyed by colour alone. */

.ds-figure--nodo { margin-inline: auto; }

.ds-nodo__spokes,
.ds-nodo__stars { /* grouping hooks — the styling lives on the children */ }

.ds-nodo__spoke {
  stroke: var(--ds-color-border-strong);
  stroke-width: 2;
  stroke-dasharray: 6 6;
  transition: stroke var(--ds-motion-duration-base) var(--ds-motion-ease-standard),
              stroke-width var(--ds-motion-duration-base) var(--ds-motion-ease-standard);
}
/* P13/L2 — the VISITED trail is celeste and KEEPS the rest weight (2, dashed); the trail is
 * a colour, not a weight. The SELECTED spoke is the one that moves a second channel
 * (rule weight 2 -> 3, dashed -> solid). Nothing here is colour-only.
 * P13.4/A1 (Andler-direct, 2026-10-12 00:47) — the SELECTION is AMARILLO, in all four
 * channels: celeste read as "a secondary alike colour" beside celeste chrome. amarillo =
 * the selection + the four static places + the d7 flag; celeste = the visited trail + the
 * secondary action + the preview. The trail stays celeste: it is the trail, not the selection. */
.ds-nodo__spoke.is-on { stroke: var(--ds-color-brand-celeste); }
.ds-nodo__spoke.is-active {
  stroke: var(--ds-color-brand-amarillo);
  stroke-width: 3;
  stroke-dasharray: none;
}

.ds-nodo__core { fill: var(--ds-color-brand-celeste); }

.ds-nodo__core-label {
  font-family: var(--ds-type-family-body);
  font-size: var(--ds-type-size-body);        /* 16 user units */
  font-weight: var(--ds-type-weight-semibold);
  fill: var(--ds-color-text-inverse);         /* on the celeste core: 9.41:1 */
}

.ds-nodo__star {
  fill: var(--ds-color-bg-elevated);
  stroke: var(--ds-color-border-strong);
  stroke-width: 3;
  transform-box: fill-box;
  transform-origin: center;
  transition: fill var(--ds-motion-duration-base) var(--ds-motion-ease-standard),
              stroke var(--ds-motion-duration-base) var(--ds-motion-ease-standard),
              transform var(--ds-motion-duration-base) var(--ds-motion-ease-standard);
}
/* P13/L2 — the VISITED star is celeste, NOT amarillo (F1: the sweep used to paint seven gold
 * stars, six over the static amarillo budget, art-direction.md §2.2). This pass SPENDS LESS
 * amarillo, not more — and P13.4/A1 spends it back in exactly ONE place: the live selection. */
.ds-nodo__star.is-on { stroke: var(--ds-color-brand-celeste); }
/* P13.1/F2 — the PREVIEW (pointer hover, transient). The hovered node's star and spoke light
 * without touching the SELECTION (`is-active`) or the VISITED trail (`is-on`). Two channels,
 * never colour alone: the spoke goes solid at weight 3 and the star scales — one step weaker
 * than the selected state (no ring, no item marker, a smaller scale). The d7 flag still wins in
 * every state. main.js never sets is-preview together with is-active, so the ordering here is
 * belt-and-braces.
 * P13.4/A1 — the preview STAYS celeste. It is the secondary/transient channel, which is what
 * celeste now names beside an amarillo selection (preview != selection); it is also kept
 * distinct by a second channel, not by hue: no ring, no item marker, scale 1.06 vs 1.15. */
.ds-nodo__spoke.is-preview { stroke: var(--ds-color-brand-celeste); stroke-width: 3; stroke-dasharray: none; }
.ds-nodo__star.is-preview { stroke: var(--ds-color-brand-celeste); transform: scale(1.06); }
.ds-nodo__dim.is-preview { border-inline-start-color: var(--ds-color-brand-celeste); }
.ds-nodo__star.is-preview.ds-nodo__star--flag { fill: var(--ds-color-brand-amarillo); stroke: var(--ds-color-brand-amarillo); }
.ds-nodo__dim.is-preview.ds-nodo__dim--flag { border-inline-start-color: var(--ds-color-brand-amarillo); }
.ds-nodo__star.is-active {
  fill: var(--ds-color-brand-amarillo);
  stroke: var(--ds-color-brand-amarillo);
  transform: scale(1.15);
}
/* the flag wins in EVERY state, at matching specificity (see the item-rule note below). */
.ds-nodo__star--flag,
.ds-nodo__star.is-on.ds-nodo__star--flag,
.ds-nodo__star.is-active.ds-nodo__star--flag {
  fill: var(--ds-color-brand-amarillo);
  stroke: var(--ds-color-brand-amarillo);
}
/* P13.6/D2 — the flag's PERSISTENT HALO TREATMENT (the drawing half of its form channel).
 * d7's star is amarillo in every state, and in the COLLISION case (d7 is both the flag and the
 * selection) the selection's ring (.ds-nodo__ring, r=21, opacity .85, SOLID) and this halo
 * (r=23, opacity .6) sit 2 user units apart: two concentric amarillo rings, indistinguishable by
 * colour and nearly so by radius. The DASH is the flag's own channel — DASHED halo = flagged,
 * SOLID ring = selected — and both stay legible when the two states land on the same star.
 * P13.11/A21 — this dash pattern (4 4) is the SAME TEXTURE the flagged row carries on its rule
 * (`border-inline-start-style: dashed`, below): one texture means "flagged" in both places. */
.ds-nodo__halo {
  fill: none;
  stroke: var(--ds-color-brand-amarillo);
  stroke-width: 2.5;
  opacity: .6;
  stroke-dasharray: 4 4;
}
/* P13/L2 §2.5 — the ring: a THIRD channel for the selected star (one element per star, so
 * there is no JS position maths and no foreignObject). Paint-only: opacity + one-shot scale. */
.ds-nodo__ring {
  fill: none;
  stroke: var(--ds-color-brand-amarillo);
  stroke-width: 2;
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity var(--ds-motion-duration-base) var(--ds-motion-ease-standard),
              transform var(--ds-motion-duration-base) var(--ds-motion-ease-standard);
}
.ds-nodo__ring.is-active { opacity: .85; transform: scale(1.06); }

/* the list */
.ds-nodo__dims { display: grid; gap: var(--ds-space-4); }
.ds-nodo__dim {
  position: relative;                 /* P13/L2 — anchors the tooltip + the ::before marker */
  padding-inline-start: var(--ds-space-4);
  border-inline-start: 2px solid var(--ds-color-border-default);
  transition: border-color var(--ds-motion-duration-base) var(--ds-motion-ease-standard);
}
.ds-nodo__dim dt {
  font-size: var(--ds-type-size-body);
  font-weight: var(--ds-type-weight-semibold);
  line-height: var(--ds-type-line-height-snug);
}
.ds-nodo__dim dd {
  margin: 0;
  font-size: var(--ds-type-size-small);
  line-height: var(--ds-type-line-height-snug);
  color: var(--ds-color-text-secondary);
}
/* P13/L2 §2.5 — the item rule follows the SELECTION only; the VISITED trail lives on the
 * drawing, not on the list. Selected = a rule AND a 6x6 px SQUARE marker in the inline-start
 * gutter: colour + a glyph, never colour alone (P13.4/A1: both are now amarillo, matching the
 * star the rule belongs to). P13.11/A20 supersedes P13.6/D2: the flag's item-level channel is the
 * TEXTURE OF THIS RULE (dashed, below) — not a second glyph in the inline-END gutter, which sat
 * 482 px from its row's text and so read as a mark, not as "this row's flag".
 * The flag wins in every state (nodo-interaction.md §4 writes
 * `.ds-nodo__dim.is-on` { celeste }, but §3/§5/§7 require d7's ITEM to carry `is-on` in the
 * served markup — (0,2,0) would beat the (0,1,0) flag — so the combined selectors are stated
 * explicitly. Reported, as shipped.) */
.ds-nodo__dim.is-active { border-inline-start-color: var(--ds-color-brand-amarillo); }
.ds-nodo__dim.is-active::before {
  content: "";
  position: absolute;
  inset-block-start: 0.35em;
  inset-inline-start: -0.75rem;
  inline-size: 6px;
  block-size: 6px;
  background-color: var(--ds-color-brand-amarillo);
}
.ds-nodo__dim--flag,
.ds-nodo__dim.is-active.ds-nodo__dim--flag { border-inline-start-color: var(--ds-color-brand-amarillo); }
.ds-nodo__dim.is-active.ds-nodo__dim--flag::before { background-color: var(--ds-color-brand-amarillo); }
.ds-nodo__dim--flag dt { color: var(--ds-color-brand-amarillo); }
/* P13.11/A20 — the FLAG's item-level FORM CHANNEL is the ROW'S OWN RULE, not a glyph in a second
 * gutter. Thari's ruling (P13.9, correcting P13.6/D2): the 6x6 diamond at `inset-inline-end:
 * -0.75rem` measured 482.0 px from its row's text (459.3 px on the widest row), alone in the page
 * margin, while the selection's square sits 22 px from the text and 10 px from the rule — "one is
 * attached, the other is adrift". The reader's eye is on the START edge, where every rule and the
 * selection's square already live. The `::after` diamond that stood here is therefore RETIRED and
 * the channel moves onto the row's own rule: 2 px DASHED amarillo.
 *
 * P13.11/A21 — ONE TEXTURE MEANS "FLAGGED" IN BOTH PLACES, so the mark reads the same wherever it
 * appears: the drawing carries it on the halo (`stroke-dasharray: 4 4`, r=23) and the row carries
 * it on its rule (`border-style: dashed`). The selection's ring (r=21) and its rule stay SOLID.
 *
 * The flag WINS THE TEXTURE IN EVERY STATE, extending the colour rule directly above (the flag
 * already wins the rule COLOUR in every state, including the collision). A flagged row is therefore
 * dashed at EVERY scroll position: if the dash vanished when d7 became the selection, the flag would
 * disappear at exactly the moment D2 set out to make it legible. On that one row the selection stays
 * fully readable by SQUARE + SOLID ring (r=21) + `scale(1.15)`.
 *
 * Channels, >= 2 non-colour in every state:
 *   · selection      2 px SOLID amarillo rule + 6x6 SQUARE (start gutter) + SOLID ring r=21 + scale(1.15)
 *   · flag (always)  2 px DASHED amarillo rule + DASHED halo r=23 + amarillo `dt`
 *   · collision      DASHED rule (flag wins) + SQUARE, and BOTH concentric rings (dashed + solid)
 * A border-style change adds no box, so the list's own geometry is untouched (CLS stays 0). */
.ds-nodo__dim--flag,
.ds-nodo__dim.is-active.ds-nodo__dim--flag { border-inline-start-style: dashed; }

/* P14 — THE TOOLTIP ITSELF MOVED TO THE ISLAND. `.ds-nodo__tip`, its absolutely-positioned
 * anchor and the `[data-tip-place="start"|"end"]` inset rules are no longer authored here:
 * the gloss is rendered by shadcn/UI's Tooltip over Radix primitives (`island/` ->
 * `assets/island.js` + `assets/island.css`), which owns collision, placement, the arrow, the
 * open state and the a11y wiring. Its BRAND is still this token sheet — bg.elevated, the
 * celeste inline-start rule, radius.sm, type.size.small, the dissolve duration — carried as
 * Tailwind utilities mapped to `var(--ds-*)`. Radix names the side it chose in `data-side`;
 * the island mirrors it into `data-tip-place`, so the contract's own vocabulary survives. */

/* P13/L2 §2.3 + P13.11/A22 — the seven <dd> are VISUALLY collapsed at >=lg, where the tooltip
 * exists to replace them, using the visually-hidden technique: out of the visual flow, never
 * display:none/hidden. The tooltip is then the only VISIBLE gloss. Below lg, with JS off, or under
 * prefers-reduced-motion (main.js never adds is-live) all seven glosses stay visible.
 *
 * P14.1 — THIS COMMENT WAS FALSE AND IS CORRECTED (P14-check.md §6.6, accepted). It used to claim
 * the <dd> "stay in the DOM and in the accessibility tree either way". They stay in the DOM, but at
 * >=lg — where the tip is live — the island marks them `aria-hidden="true"`
 * (island/src/components/nodo-dims.tsx), so exactly ONE accessible description exists per node: the
 * tip's, wired by Radix via `aria-describedby` on the row's own trigger. Two descriptions of one
 * node in the accessibility tree is the defect that avoids. Below lg they are exposed as written. */
.ds-nodo__dims.is-live dd {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------- P13 · L3 — per-section motion
 * P12-DESIGN-DIRECTION §3 — three gestures and nothing else. No parallax, no
 * scroll-jacking, no autoplay loop, no filter, and no width/height/top/left anywhere.
 *
 * G1 · Arrive — opacity only, one-shot, on the EXISTING [data-reveal] observer (no new
 * observer). Expressed as `.js <scope> .ds-anim` -> `.js <scope>.is-visible .ds-anim`, so
 * the SHIPPED reduced-motion path makes it inert with NO change to the frozen block:
 * main.js force-adds is-visible under reduced motion, and tokens.css zeroes every duration
 * (0 ms), which also zeroes the stagger below. With no JS, html.js is absent, so every part
 * is simply at opacity 1.
 *
 * G2 · Respond — ONE paint-only property per drawing, on :hover / :focus-within. Nothing
 * moves and nothing layout. It conveys no information (a liveness cue), which is why it is
 * pointer-only and adds no focusable element. Every duration/easing is a --ds-* token, and
 * no G2 target introduces amarillo (§3.3). */
.js [data-reveal] .ds-anim {
  opacity: 0;
  transition: opacity var(--ds-motion-duration-base) var(--ds-motion-ease-standard);
}
.js [data-reveal].is-visible .ds-anim { opacity: 1; }

/* the reading order of each drawing (§3.2): each step starts one duration.fast after the last */
.js [data-reveal].is-visible [data-anim="ia-box"],
.js [data-reveal].is-visible [data-anim="si-frontera"],
.js [data-reveal].is-visible [data-anim="riesgo-arrows"],
.js [data-reveal].is-visible [data-anim="riesgo-ring"],
.js [data-reveal].is-visible [data-anim="jpc-app"],
.js [data-reveal].is-visible [data-anim="gob-frontera"] {
  transition-delay: calc(var(--ds-motion-duration-fast) * 1);
}
.js [data-reveal].is-visible [data-anim="ia-out"],
.js [data-reveal].is-visible [data-anim="si-espec"],
.js [data-reveal].is-visible [data-anim="jpc-sinpe"],
.js [data-reveal].is-visible [data-anim="gob-vecinos"] {
  transition-delay: calc(var(--ds-motion-duration-fast) * 2);
}
.js [data-reveal].is-visible [data-anim="gob-coop"] {
  transition-delay: calc(var(--ds-motion-duration-fast) * 3);
}

/* G2 · #ia and #jpc — the connector arrows gain one unit of weight. Nothing else changes. */
.ds-figure [data-anim="ia-path"],
.ds-figure [data-anim="jpc-path"] {
  transition: stroke-width var(--ds-motion-duration-fast) var(--ds-motion-ease-standard);
}
.ds-figure:hover [data-anim="ia-path"],
.ds-figure:focus-within [data-anim="ia-path"],
.ds-figure:hover [data-anim="jpc-path"],
.ds-figure:focus-within [data-anim="jpc-path"] { stroke-width: 4; }

/* G2 · #si — the speculative star scales, one-shot on enter. */
.ds-figure [data-anim="si-espec"] path {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform var(--ds-motion-duration-base) var(--ds-motion-ease-emphasized);
}
.ds-figure:hover [data-anim="si-espec"] path,
.ds-figure:focus-within [data-anim="si-espec"] path { transform: scale(1.06); }

/* G2 · #riesgo — pointing at a panel states which panel is being compared. */
.ds-figure [data-anim="riesgo-panel-a"] > g[stroke-width],
.ds-figure [data-anim="riesgo-panel-b"] > g[stroke-width],
.ds-figure [data-anim="riesgo-panel-b"] > path[stroke-width] {
  transition: stroke-width var(--ds-motion-duration-fast) var(--ds-motion-ease-standard);
}
.ds-figure:hover [data-anim="riesgo-panel-a"] > g[stroke-width],
.ds-figure:hover [data-anim="riesgo-panel-b"] > g[stroke-width],
.ds-figure:hover [data-anim="riesgo-panel-b"] > path[stroke-width],
.ds-figure:focus-within [data-anim="riesgo-panel-a"] > g[stroke-width],
.ds-figure:focus-within [data-anim="riesgo-panel-b"] > g[stroke-width],
.ds-figure:focus-within [data-anim="riesgo-panel-b"] > path[stroke-width] { stroke-width: 4; }

/* G2 · #gobernanza — the cooperation edges gain weight, the border firms up. */
.ds-figure [data-anim="gob-coop"] > g[stroke-width],
.ds-figure [data-anim="gob-frontera"] line {
  transition: stroke-width var(--ds-motion-duration-fast) var(--ds-motion-ease-standard),
              opacity var(--ds-motion-duration-fast) var(--ds-motion-ease-standard);
}
.ds-figure:hover [data-anim="gob-coop"] > g[stroke-width],
.ds-figure:focus-within [data-anim="gob-coop"] > g[stroke-width] { stroke-width: 4; }
.ds-figure:hover [data-anim="gob-frontera"] line,
.ds-figure:focus-within [data-anim="gob-frontera"] line { opacity: 1; }

/* -------------------------------------------------------------------- cta */

.ds-cta-block {
  padding: var(--ds-space-8);
  margin-inline: auto;
  background-color: var(--ds-color-bg-elevated);
  border: 0;
  border-radius: var(--ds-radius-lg);
}

.ds-cta-line {
  margin-block-start: var(--ds-space-4);
  font-size: var(--ds-type-size-body-lg);
  font-weight: var(--ds-type-weight-medium);
}

/* §2.2 (1) — the install button fill: amarillo, text.inverse, 13.35:1. §2.2 forbids amarillo
 * on a hover state, so the hover moves the *border* to celeste and never the fill. */
.ds-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: var(--ds-space-3) var(--ds-space-6);
  background-color: var(--ds-color-brand-amarillo);
  color: var(--ds-color-text-inverse);
  border: 2px solid var(--ds-color-brand-amarillo);
  border-radius: var(--ds-radius-md);
  font-family: var(--ds-type-family-body);
  font-size: var(--ds-type-size-body-lg);
  font-weight: var(--ds-type-weight-bold);
  text-decoration: none;
  transition: border-color var(--ds-motion-duration-fast) var(--ds-motion-ease-standard);
}
.ds-cta:hover { border-color: var(--ds-color-brand-celeste); }


/* §2.3 — the secondary action: celeste text (an inherited link colour), the verdeDeep
 * underline (§2.3 moves the old button's verdeDeep to this underline). */
.ds-cta-fallback {
  display: inline-block;
  font-size: var(--ds-type-size-small);
  text-decoration-color: var(--ds-color-brand-verde-deep);
}

.ds-vigia {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-4);
  margin-block-start: var(--ds-space-6);
  padding-block-start: var(--ds-space-5);
  border-block-start: 2px solid var(--ds-color-border-subtle);
}
.ds-vigia__avatar {
  flex: none;
  width: 72px;
  height: 72px;
  border: 2px solid var(--ds-color-border-strong);
  border-radius: var(--ds-radius-md);
}
.ds-vigia__text { font-size: var(--ds-type-size-small); color: var(--ds-color-text-secondary); }

/* `acceso por invitación / registro aprobado` — the licence control, made visible.
 * §2.2 (4) — the 4 px amarillo border-inline-start is the fourth and last permitted use. */
.ds-gate {
  display: inline-block;
  padding: var(--ds-space-1) var(--ds-space-3);
  background-color: var(--ds-color-bg-elevated);
  border-inline-start: 4px solid var(--ds-color-brand-amarillo);
  border-radius: var(--ds-radius-none);
  font-size: var(--ds-type-size-small);
  font-weight: var(--ds-type-weight-medium);
  color: var(--ds-color-text-primary);
}

/* --------------------------------------------------------- the sugef notice */

/* Reserved container, present-but-empty until P2.5 clears legal (gate C8). §2.4 — its 4 px
 * rojo border-inline-start is the second permitted use of rojo, and it does not appear
 * while the container is empty. `:empty` keeps an empty box off the page. */
.ds-sugef {
  margin-block-start: var(--ds-space-5);
  padding: var(--ds-space-4);
  background-color: var(--ds-color-bg-surface);
  border-inline-start: 4px solid var(--ds-color-accent-cr-rojo);
  border-radius: var(--ds-radius-none);
  font-size: var(--ds-type-size-small);
  line-height: var(--ds-type-line-height-snug);
  color: var(--ds-color-state-danger-text);
}
.ds-sugef:empty { display: none; }

/* ----------------------------------------------------------------- footer */

/* §2.4 — azul gets exactly one place: a 4 px full-bleed rule on the footer's top edge.
 * One rule, 4 px, no text on it, no other use. */
.ds-footer {
  padding-block: var(--ds-space-7);
  background-color: var(--ds-color-bg-elevated);
  border-block-start: 4px solid var(--ds-color-accent-cr-azul);
}
.ds-footer__legal {
  margin-block-start: var(--ds-space-4);
  max-width: var(--ds-layout-measure-prose);
  font-size: var(--ds-type-size-caption);
  color: var(--ds-color-text-muted);
}
.ds-credit {
  margin-block-start: var(--ds-space-4);
  font-size: var(--ds-type-size-caption);
  color: var(--ds-color-text-muted);
}
.ds-credit:empty { display: none; }
.ds-footer__meta { margin-block-start: var(--ds-space-5); font-size: var(--ds-type-size-small); }

/* ------------------------------------------------------------ progressive enhancement
 * §4.2 — the baseline is VISIBLE (opacity 1 / transform none). Only <html class="js"> plus
 * the observed class hides anything, so a flash of invisible content is structurally
 * impossible. One-shot; nothing reverses on scroll-up. */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(var(--ds-space-4));
}
.js [data-reveal="cta"] { transform: scale(0.98); } /* ≤ 2 %, the CTA block only */

.js [data-reveal].is-visible {
  opacity: 1;
  transform: none;
  transition: opacity var(--ds-motion-scroll-duration) var(--ds-motion-ease-emphasized),
    transform var(--ds-motion-scroll-duration) var(--ds-motion-ease-emphasized);
}

/* §4.1 — the hero fade, and the edge draw-in, both one-shot on load. The delay sequences
 * them behind the loader's own fade (loader covers → fades out → hero fades in); it is a
 * token, not a number. */
html.js .ds-hero__inner {
  opacity: 0;
  transition: opacity var(--ds-motion-duration-slow) var(--ds-motion-ease-standard)
    var(--ds-motion-duration-slow);
}
html.js .ds-hero.is-ready .ds-hero__inner { opacity: 1; }

html.js .ds-hero__device-edge {
  transition: stroke-dashoffset var(--ds-motion-scroll-duration) var(--ds-motion-ease-emphasized);
}
html.js .ds-hero:not(.is-ready) .ds-hero__device-edge { stroke-dashoffset: 180; }
/* The five edges draw in sequence round the ring. The base offset is the loader's own fade
 * (motion.duration.slow), kept from the shipped rule: without it the draw happens behind the
 * overlay and the reader never sees it. Each edge then starts fast (150 ms) after the one
 * before, and each takes scroll.duration (500 ms) to draw, so the edges overlap into one
 * continuous sweep. The ring closes at slow + fast x 4 + scroll = 400 + 600 + 500 = 1500 ms. */
html.js .ds-hero__device-edge:nth-child(1) { transition-delay: var(--ds-motion-duration-slow); }
html.js .ds-hero__device-edge:nth-child(2) { transition-delay: calc(var(--ds-motion-duration-slow) + var(--ds-motion-duration-fast)); }
html.js .ds-hero__device-edge:nth-child(3) { transition-delay: calc(var(--ds-motion-duration-slow) + var(--ds-motion-duration-fast) * 2); }
html.js .ds-hero__device-edge:nth-child(4) { transition-delay: calc(var(--ds-motion-duration-slow) + var(--ds-motion-duration-fast) * 3); }
html.js .ds-hero__device-edge:nth-child(5) { transition-delay: calc(var(--ds-motion-duration-slow) + var(--ds-motion-duration-fast) * 4); }
/* The five nodes bloom as the last edge closes the ring, not after it: the fifth edge starts
 * at slow + fast x 4 = 1000 ms and the nodes start with it, so the ring closing and the
 * network arriving are one gesture. The whole sequence ends at 1500 ms. */
html.js .ds-hero__device-node {
  transform-box: fill-box;
  transform-origin: center;
  opacity: 0;
  transform: scale(0.85);
  transition: opacity var(--ds-motion-duration-base) var(--ds-motion-ease-standard)
      calc(var(--ds-motion-duration-slow) + var(--ds-motion-duration-fast) * 4),
    transform var(--ds-motion-duration-base) var(--ds-motion-ease-emphasized)
      calc(var(--ds-motion-duration-slow) + var(--ds-motion-duration-fast) * 4);
}
html.js .ds-hero.is-ready .ds-hero__device-node { opacity: 1; transform: none; }

/* Reduced motion is a first-class state (§4.2): the page is complete and still. Belt and
 * braces on top of the motion.reduced token, which zeroes every duration in tokens.css. */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal],
  .js [data-reveal="cta"] { opacity: 1; transform: none; }
  .js [data-reveal].is-visible { transition: none; }
  html.js .ds-hero__inner { opacity: 1; transition: none; }
  html.js .ds-hero__device-edge { stroke-dashoffset: 0; transition: none; transition-delay: 0ms; }
  html.js .ds-hero__device-node { opacity: 1; transform: none; transition: none; }
  /* N3 (P10) — restated at MATCHING specificity: the sticky rule at :622 is
   * `.ds-section__inner--nodo > .ds-figure--nodo` (0,2,0) and sits in a later media block,
   * so the old (0,1,0) `.ds-figure--nodo` line lost and the figure stayed sticky under
   * `prefers-reduced-motion: reduce`. The rule below is (0,3,0) and wins. */
  html.js .ds-figure--nodo,
  html.js .ds-section__inner--nodo > .ds-figure--nodo { position: static; }
}

/* ------------------------------------------------------------- breakpoints
 * layout.breakpoint.md / layout.breakpoint.lg. A fourth breakpoint is a Contract A
 * change, not a style decision. */

@media (min-width: 768px) {
  .ds-section { padding-block: var(--ds-space-9); }
  /* §3.1 — the section title gets space.5 of air above its lead, not space.4. */
  .ds-stack > .fs-h1 + * { margin-block-start: var(--ds-space-5); }
  /* §3.4 — the hero device cap rises to 400 px; the hero's vertical padding widens.
   * P13/L1 — the widened padding is re-homed onto .ds-hero__inner, not .ds-hero. */
  .ds-hero__inner { padding-block-start: var(--ds-space-8); padding-block-end: var(--ds-space-10); }
  /* §3.4 restored: from 768 up there is room for the device to lead again. */
  .ds-hero__device { grid-row: 1; }
  .ds-hero__text { grid-row: 2; }
  /* B-1 (§3.3) — the media sections STACK 768–1279: figure above prose, figure capped
   * 400 px and centred. Two columns would leave the prose column ≈34 characters. */
  .ds-section__inner--media { grid-template-columns: minmax(0, 1fr); }
  .ds-section__inner--media > .ds-figure { grid-row: 1; justify-self: center; max-width: 400px; }
  .ds-section__inner--media > .ds-prose { grid-row: 2; }
  .ds-vigia { flex-direction: row; align-items: center; }
}

@media (min-width: 1280px) {
  /* the container gutter stays layout.gutter: 1200 − 2 × 24 = 1152 px of content, which is
   * the arithmetic §3.3 relies on (1152 content, 64 gap, 440 figure, 648 prose). */
  .ds-section__inner { gap: var(--ds-space-8); }
  /* two columns at lg: figure left, prose right (B-1) */
  .ds-section__inner--media { grid-template-columns: 440px minmax(0, 1fr); gap: var(--ds-space-8); align-items: start; }
  .ds-section__inner--media > .ds-figure { grid-column: 1; grid-row: 1; justify-self: stretch; max-width: none; }
  .ds-section__inner--media > .ds-prose { grid-column: 2; grid-row: 1; }
  /* the hero composes: two columns 5fr 4fr, aligned on the optical centre (§3.4) */
  .ds-hero__inner { grid-template-columns: 5fr 4fr; align-items: center; }
  .ds-hero__text { grid-column: 1; grid-row: 1; }
  .ds-hero__device { grid-column: 2; grid-row: 1; }
  .ds-hero__device-svg { max-width: 400px; margin-inline: 0; }
  /* §5 #nodo — figure minmax(0, 440 px) left and sticky, list right */
  .ds-section__inner--nodo > .ds-figure--nodo {
    position: sticky;
    inset-block-start: var(--ds-space-6);
    max-width: 440px;
  }
}
