/* ===========================================================================
   Pesito — Marketing site, single source of truth for styles.

   Palette (2026-07-16, Lotería): traditional Mexican Lotería card
   language — flat, bright, highly saturated hues (verde, amarillo,
   azul cobalto, rosa mexicano, coral), every colored surface outlined
   in near-black ink, on warm cream card stock. Supersedes the
   2026-07-11 mono-verde rationalization; the editorial panel system
   stays, recolored. Rojo survives ONLY as the semantic negative
   (errors, legal notices, comparison "cons"); festive warmth comes
   from coral/rosa/amarillo instead, so error-red keeps its meaning.

   Source of truth: docs/design-system/MASTER.md (contrast-validated).
   Design tokens started from docs/website-prompt.md §3–§4.
   =========================================================================== */

:root {
  /* ═════════════════════════════════════════════════════════════════
     THEMING MODEL — 2026-08 (dark mode + high contrast).

     Every neutral is declared twice: a plain light value first, then
     the same token restated with `light-dark()`. Engines that don't
     know `light-dark()` (pre-2024) drop the second declaration and
     keep the light site exactly as it shipped; everything else
     resolves against the element's `color-scheme`, which `html`
     sets to `light dark` and the `data-theme` attribute pins.

     The dark theme is not a separate skin — it is the SAME Lotería
     grammar inverted: flat saturated fills inside a BONE frame on
     warm ink card stock, instead of inside a black frame on warm
     cream card stock. That is why the saturated hues below carry a
     single value: verde, amarillo, cobalto, rosa and coral are the
     deck, and the deck does not change color when you turn the
     lights off. Only the paper and the ink swap.
     ═════════════════════════════════════════════════════════════ */

  /* Color tokens — the verde ladder */
  --verde-900: #003D26;
  --verde-700: #006847;
  --verde-500: #00A651;
  --verde-300: #6FCF97;
  /* The two lightest rungs are TINTS (surfaces), not fills, so they
     invert with the paper. */
  --verde-100: #DFF2E7;
  --verde-100: light-dark(#DFF2E7, #0A2B1A);
  --verde-050: #F4FAF6;
  --verde-050: light-dark(#F4FAF6, #0A2015);

  /* Semantic negative only — never decorative. 700/500 are type,
     200/100/050 are surfaces; both ends invert. */
  --rojo-700: #A50F20;
  --rojo-700: light-dark(#A50F20, #FDA19B);
  --rojo-500: #CE1126;
  --rojo-500: light-dark(#CE1126, #F66D67);
  --rojo-200: #F7C9CE;
  --rojo-200: light-dark(#F7C9CE, #4B1D1B);
  --rojo-100: #FBD8DC;
  --rojo-100: light-dark(#FBD8DC, #3B1816);
  --rojo-050: #FDECEE;
  --rojo-050: light-dark(#FDECEE, #2B1311);

  /* `--blanco` is the RAISED surface (cards, open FAQ rows, the
     calculator), one step brighter than the canvas — not "the color
     white". In dark it is the one step lighter than the ink stock. */
  --blanco: #FFFFFF;
  --blanco: light-dark(#FFFFFF, #2E2921);
  /* Canvas + band neutrals: warm cream — Lotería card stock. */
  --hueso: #FAF7F2;
  --hueso: light-dark(#FAF7F2, #14100A);
  /* Slightly deeper-than-hueso backgrounds for legal callouts and
     inline sidebars. Sits between `--hueso` and `--crema` so it reads
     as "a card on the hueso canvas" rather than a muddy second tone. */
  --hueso-100: #F6F3EB;
  --hueso-100: light-dark(#F6F3EB, #201C16);
  --hueso-200: #F2EEE0;
  --hueso-200: light-dark(#F2EEE0, #2E2921);
  --crema: #F2EEE6;
  --crema: light-dark(#F2EEE6, #211C14);
  /* Ink ramp — neutral; tinta-900 doubles as the Lotería outline.
     Dark values are warm bone, not pure white: a #FFF frame on near
     -black halates against the saturated fills. */
  --tinta-900: #0B0B0B;
  --tinta-900: light-dark(#0B0B0B, #F7F1E6);
  --tinta-700: #2A2A2E;
  --tinta-700: light-dark(#2A2A2E, #DCD7CD);
  --tinta-500: #55555C;
  --tinta-500: light-dark(#55555C, #B3ADA3);
  /* `--tinta-400` fills the gap between `--tinta-500` (body copy) and
     `--tinta-300` (hairlines). Used for dimmed metadata such as
     "Vigente a partir de …" on legal pages. Held at ≥4.5:1 on hueso. */
  --tinta-400: #62626A;
  --tinta-400: light-dark(#62626A, #A49E94);
  --tinta-300: #C7C7CC;
  --tinta-300: light-dark(#C7C7CC, #413C35);

  /* Lotería accents — flat, saturated, always inside an ink outline.
     Contrast pairs validated in docs/design-system/MASTER.md:
     ink type on amarillo/coral/verde-500; white type on azul-700/
     verde-700/rosa. Rosa-600 is the body-copy-safe rosa. */
  --amarillo-500: #FFC400;
  --azul-700: #0A45A6;
  --rosa-500: #E4007C;
  --rosa-600: #C4006B;
  --coral-500: #FF5A36;
  --lila-300: #C7B9F2;

  --app-green: #00D632;

  /* The green period that closes the hero headline. Deep verde reads
     as ink-adjacent on cream but sinks into a near-black canvas, so
     dark mode steps it up to the app green. */
  --brand-dot: #006847;
  --brand-dot: light-dark(#006847, #00CC64);

  /* Brand green wherever it has to behave like TYPE or an ICON rather
     than a fill: the wordmark, and every stroked glyph on a card.
     `--verde-700` is 5.3:1 on cream but only 1.9:1 on ink stock, which
     is what made the logo look washed out in dark mode. Mint is the
     same hue family read at the other end of the ramp — 10.7:1 there.
     Calmer than `--brand-dot` on purpose: the dot is punctuation and
     wants to pop, a row of six icons does not. */
  --accent-verde: #006847;
  --accent-verde: light-dark(#006847, #6FCF97);
  /* Hover/emphasis step for accent-verde text: darker in light,
     lighter in dark. */
  --accent-verde-strong: light-dark(#003D26, #9ADFB8);

  /* Environment fills for LARGE surfaces (panels, bars) — dark mode
     pulls the three loud Lotería fields down a step so a full-width
     card is festive, not glaring, on the near-black canvas. Small
     pops (chips, rail cards, footer cells, buttons) keep the bright
     values: tiny areas carry brightness well; big fields don't.
     Ink stays >=4.5:1 on every deepened fill (verde 4.9, amarillo
     9.4); white reads 10.4:1 on the deep cobalt. */
  /* Plain hex first, light-dark() second: browsers without
     light-dark() (Safari ≤16 — real devices in the remittance-sender
     demographic) drop the second declaration and keep the light hex,
     so the big editorial panels never lose their fill under their
     fixed-white ink. The dark values are restated in the dark-theme
     blocks at the bottom of this file for those same engines. */
  --fill-verde-vivo: #00A651;
  --fill-amarillo: #FFC400;
  --fill-azul: #0A45A6;
  --fill-verde-vivo: light-dark(#00A651, #0C9247);
  --fill-amarillo: light-dark(#FFC400, #E7B41E);
  --fill-azul: light-dark(#0A45A6, #05378C);

  /* ---- Theme-invariant ink pair -------------------------------
     The dark theme flips the neutral ramp (`--tinta-*` becomes
     light, `--hueso`/`--blanco` become dark) so 4k lines of
     surface + outline rules retheme with no markup surgery. The
     ONE thing that must not flip is type that sits on a saturated
     Lotería fill: verde-500 and amarillo-500 stay bright in both
     themes, so their label stays near-black; azul-700, verde-700
     and rosa stay deep, so their label stays white.

     Rule of thumb when adding CSS:
       • foreground on a BRIGHT fill  → var(--ink-fixed)
       • foreground on a DEEP fill    → var(--paper-fixed)
       • everything else              → var(--tinta-*) / var(--hueso) */
  --ink-fixed: #0B0B0B;
  --paper-fixed: #FFFFFF;

  /* ---- Secondary note ink -------------------------------------
     The ONE token for de-emphasised body copy: compliance
     mouseprint, plan notes, form disclosures, slider scales, unit
     tags. Previously each of these dimmed itself with `opacity`,
     which is not a color operation — it composites the text toward
     whatever is behind it, so the SAME rule measured 8.8:1 on cream
     and 3.7:1 on the verde pricing panel. Contrast then depended on
     which panel the note happened to land in, and dark mode made it
     worse (the deepened verde-vivo fill leaves ink at 4.89:1 with
     zero headroom — ANY dimming drops it under AA).

     A token instead of opacity means the surface decides the value
     and each value is contrast-validated once. Never re-introduce
     `opacity` to dim text; it silently re-couples legibility to the
     background. Decorative pseudo-element washes may still use it. */
  --note-ink: var(--tinta-500);

  /* ---- Hairlines ----------------------------------------------
     Sub-outline dividers (table rows, sheet links, card edges).
     Declared as tokens because a literal rgba(0,0,0,0.06) is
     invisible on a dark canvas. The 2px Lotería outline is
     `--panel-border`; these are the quiet rules underneath it. */
  --hairline: rgba(11, 11, 11, 0.09);
  --hairline: light-dark(rgba(11, 11, 11, 0.09), rgba(246, 242, 232, 0.14));
  --hairline-strong: rgba(11, 11, 11, 0.16);
  --hairline-strong: light-dark(rgba(11, 11, 11, 0.16), rgba(246, 242, 232, 0.24));

  /* ---- Canvas -------------------------------------------------
     The page ground, built from color tokens so the gradient itself
     needs no theme-specific restatement. */
  --canvas-top: #FBF9F4;
  --canvas-top: light-dark(#FBF9F4, #19150F);
  --canvas-bottom: #F4EFE6;
  --canvas-bottom: light-dark(#F4EFE6, #0E0B06);
  --canvas: linear-gradient(180deg, var(--canvas-top) 0%, var(--hueso) 40%, var(--canvas-bottom) 100%);
  /* Grain is a blend mode + opacity, not a color, so it is the one
     thing `light-dark()` can't carry — the theme block near the
     bottom of this file restates it. */
  --grain-opacity: 0.55;
  --grain-blend: overlay;

  /* Typography. Apple's macOS 26 / iOS 26 Liquid Glass aesthetic is
     inseparable from SF Pro's optical sizing — so on Apple devices we
     let the platform serve the real family (`-apple-system`, which
     resolves to SF Pro Display above ~20pt and SF Pro Text below).
     Inter is the self-hosted fallback for Windows, Linux, and Android;
     it shares the same width, weight axis, and x-height as SF, so
     switches between platforms feel continuous.

     `ui-rounded` first-in-stack gives Apple hardware (iOS 13+ / macOS
     Big Sur+) the SF Rounded variant at display sizes, which is what
     iOS 26's "Dynamic Island" and Control Center glass panels use.
     Falls through silently to SF Pro / Inter elsewhere. */
  --font-body: ui-rounded, -apple-system, BlinkMacSystemFont,
               'SF Pro Display', 'SF Pro Text',
               'Inter', 'Segoe UI Variable', 'Segoe UI',
               Roboto, 'Helvetica Neue', sans-serif;
  --font-display: ui-rounded, -apple-system, BlinkMacSystemFont,
                  'SF Pro Display', 'Inter Display', 'Inter',
                  'Segoe UI Variable', 'Segoe UI',
                  Roboto, 'Helvetica Neue', sans-serif;
  --font-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas,
               'Liberation Mono', monospace;

  /* Fluid type scale — min / fluid / max via clamp(). */
  --fs-display-xxl: clamp(56px, 7.4vw, 120px);
  --fs-display-xl: clamp(44px, 5.6vw, 84px);
  --fs-display-l:  clamp(38px, 4.8vw, 72px);
  --fs-display-m:  clamp(30px, 3.6vw, 52px);
  --fs-title:      clamp(26px, 2.6vw, 40px);
  --fs-headline: 24px;
  --fs-body-l: 18px;
  --fs-body: 16px;
  --fs-body-s: 14px;
  --fs-caption: 13px;
  --fs-eyebrow: 12px;

  /* Icon masks. Colourless by construction: the shape lives here, the
     colour comes from whatever paints the element, so these carry no
     fixed fill to drift from the theme. */
  --icon-cap: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'><path d='M22 8.5 12 3.5 2 8.5l10 5z'/><path d='M6 11v5.2c0 1.4 2.7 2.8 6 2.8s6-1.4 6-2.8V11'/></svg>");

  /* Layout */
  --container-max: 1200px;
  --container-inner: 1040px;
  --reading-max: 640px;
  --container-pad: 24px;
  /* Fluid section rhythm (2026-08-26): min at 375px, max at ~1430px,
     one clamp instead of a @media step - no 56px cliff at 768px. The
     ed-layer's --panel-pad/--panel-gap set the precedent; these carry
     the legacy shells. */
  --space-section: clamp(72px, 9vw, 128px);
  --space-section-head: clamp(48px, 5vw, 72px);

  /* Measure for type that sits outside the reading column. Capped in
     `ch` so each cap tracks its own type size, and not shared between
     the two roles: a 58px poster headline and 13px fine print reach the
     same physical column at very different character counts, so one
     value would leave the headline in three-word lines or the fine print
     at 120 characters. Running body copy uses `--reading-max` instead —
     it shares a column with images and cards, so it needs to agree with
     them in pixels rather than characters. */
  --measure-display: 26ch;   /* poster headline; text-wrap: balance does the rest */
  --measure-note: 84ch;      /* fine print, ≈ the prose column physically */

  /* Radius */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 24px;
  --radius-pill: 9999px;

  /* Shadow */
  --shadow-card: 0 2px 12px rgba(11, 11, 11, 0.06);
  --shadow-hover: 0 12px 32px rgba(0, 61, 38, 0.12);
  --shadow-modal: 0 24px 64px rgba(11, 11, 11, 0.24);

  /* Motion */
  --dur-instant: 100ms;
  --dur-fast: 180ms;
  --dur-base: 240ms;
  --dur-slow: 360ms;
  --dur-deliberate: 520ms;
  --dur-entrance: 620ms;

  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --ease-decelerate: cubic-bezier(0, 0, 0, 1);
  --ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
  --ease-emphasized: cubic-bezier(0.2, 0, 0, 1.2);
  --ease-entrance: cubic-bezier(0.16, 1, 0.3, 1);

  /* ═════════════════════════════════════════════════════════════════
     Liquid Glass — iOS 26 / macOS 26 "Tahoe" material language.

     Real refraction is done at the platform level (CanvasKit shaders
     on the Flutter side, display compositor on Apple). On the web,
     we approximate with a layered stack:

       1. backdrop-filter: blur + saturate + brightness  (the physics)
       2. surface tint                                    (the color)
       3. inset top-highlight + bottom-shade             (edge lighting)
       4. a thin gradient stroke + outer soft shadow     (the ring)
       5. optional SVG feTurbulence/feDisplacementMap     (true warp)

     Tokens are declared here so the whole site + docs + in-app web
     shell can reach for the same numbers. Flutter mirrors these in
     mobile/lib/core/design/liquid_glass.dart — if you change a value
     here, match it there.
     ═════════════════════════════════════════════════════════════════ */

  /* Backdrop physics */
  --glass-blur-xs: 8px;
  --glass-blur-sm: 14px;
  --glass-blur-md: 22px;
  --glass-blur-lg: 36px;
  --glass-blur-xl: 56px;
  --glass-saturate: 1.8;
  --glass-brightness: 1.06;

  /* Nav chrome — its own blur pair so the scroll-triggered
     "materialize" moment reads as a deliberate step up in
     thickness, not a subtle tween. Matched to iOS 26 Finder chrome
     (~28px at rest) and Control Center surface (~40px). */
  --glass-blur-nav: 28px;
  --glass-blur-nav-scrolled: 40px;
  --glass-saturate-nav: 1.92;

  /* Floating nav capsule material (2026-08-26). Both themes are their
     own glass: light = hueso-tinted white with warm white edge light;
     dark = deep smoked glass with a faint cool edge. The old single
     material painted white lighting over dark content and let the
     1.92x saturation smear whatever hue scrolled beneath — dark mode
     backs saturation off to a whisper and drops the brightness lift
     entirely (brightening blacks reads as haze, not glass). */
  --nav-glass-tint: light-dark(rgba(250, 247, 242, 0.5), rgba(23, 20, 16, 0.52));
  --nav-glass-edge: light-dark(rgba(255, 255, 255, 0.65), rgba(255, 255, 255, 0.14));
  --nav-glass-edge-strong: light-dark(rgba(255, 255, 255, 0.78), rgba(255, 255, 255, 0.2));
  --nav-glass-edge-under: light-dark(rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.04));
  --nav-glass-ring: light-dark(rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0.07));
  --nav-glass-ring-strong: light-dark(rgba(255, 255, 255, 0.56), rgba(255, 255, 255, 0.1));
  --nav-glass-specular: light-dark(rgba(255, 255, 255, 0.32), rgba(255, 255, 255, 0.07));
  --nav-glass-vignette: light-dark(rgba(11, 11, 11, 0.03), rgba(0, 0, 0, 0.18));
  --nav-glass-drop: light-dark(rgba(11, 11, 11, 0.16), rgba(0, 0, 0, 0.55));
  --nav-glass-drop-scrolled: light-dark(rgba(11, 11, 11, 0.24), rgba(0, 0, 0, 0.7));
  /* Non-color knobs can't ride light-dark(); the dark theme blocks
     near the end of this file override them. */
  --nav-glass-saturate: 1.6;
  --nav-glass-saturate-scrolled: 1.85;
  --nav-glass-brightness: 1.05;

  /* Surface tints — layered ON TOP of whatever the backdrop filter
     reveals. Hueso-biased (green-tinted white) so the glass belongs
     to the brand, not generic Apple-on-white. */
  --glass-surface-light: rgba(250, 247, 242, 0.62);
  --glass-surface-light: light-dark(rgba(250, 247, 242, 0.62), rgba(28, 24, 20, 0.66));
  --glass-surface-regular: rgba(250, 247, 242, 0.36);
  --glass-surface-regular: light-dark(rgba(250, 247, 242, 0.36), rgba(28, 24, 20, 0.44));
  --glass-surface-clear: rgba(255, 255, 255, 0.12);
  --glass-surface-chrome: rgba(250, 247, 242, 0.74);
  --glass-surface-chrome: light-dark(rgba(250, 247, 242, 0.74), rgba(21, 18, 14, 0.8));
  --glass-surface-dark: rgba(11, 11, 11, 0.42);
  --glass-surface-dark-regular: rgba(11, 11, 11, 0.58);

  /* Brand-tinted variants — glass that picks up verde.
     Use sparingly: CTAs, active nav chip, "confirm" sheets. */
  --glass-tint-verde: rgba(0, 166, 81, 0.18);
  --glass-tint-verde-strong: rgba(0, 104, 71, 0.32);

  /* Edge lighting — simulates a 1px specular band on the top edge
     and a softer return on the bottom, faking light-from-above. */
  --glass-edge-top: rgba(255, 255, 255, 0.62);
  --glass-edge-top-dark: rgba(255, 255, 255, 0.18);
  --glass-edge-bottom: rgba(255, 255, 255, 0.14);
  --glass-edge-stroke: rgba(255, 255, 255, 0.42);
  --glass-edge-stroke-dark: rgba(255, 255, 255, 0.1);
  --glass-edge-stroke-warm: rgba(250, 247, 242, 0.5);

  /* Outer shadows — softer, longer, more layers than a flat box-shadow.
     Always compose 2-3 stops: a tight 1-3px hair for crispness,
     a mid 8-14px for presence, and an ambient 24-48px for lift. */
  --glass-shadow-sm:
    0 1px 1px rgba(11, 11, 11, 0.04),
    0 2px 8px rgba(11, 11, 11, 0.06);
  --glass-shadow-md:
    0 1px 2px rgba(11, 11, 11, 0.06),
    0 6px 18px rgba(11, 11, 11, 0.08),
    0 24px 48px -12px rgba(11, 11, 11, 0.14);
  --glass-shadow-lg:
    0 1px 3px rgba(11, 11, 11, 0.08),
    0 14px 36px rgba(11, 11, 11, 0.14),
    0 36px 72px -16px rgba(11, 11, 11, 0.22);
  --glass-shadow-verde-md:
    0 1px 2px rgba(0, 0, 0, 0.08),
    0 8px 24px rgba(0, 61, 38, 0.18),
    0 24px 56px -16px rgba(0, 61, 38, 0.28);

  /* Continuous-ish corner radii — Apple's squircle is ~48% rounder
     than CSS `border-radius`, so bump nominal sizes up one step to
     visually match iOS 26 geometry. */
  --glass-radius-xs: 12px;
  --glass-radius-sm: 16px;
  --glass-radius-md: 22px;
  --glass-radius-lg: 28px;
  --glass-radius-xl: 36px;
  --glass-radius-pill: 9999px;

  /* ═════════════════════════════════════════════════════════════════
     Aurora atmosphere — v2 (2026-04-23).

     The "Liquid Glass redesign" pass. Where v1 tokens described the
     glass PANE itself (blur, tint, edge, shadow), these tokens
     describe the LIGHT that lands on the pane — the cinematic
     backdrop that makes every glass surface read as if it exists
     in a real lit room rather than a flat CSS stage.

     Three conceptual layers:

       1. Aurora blooms   — large, ultra-soft radial gradient washes
                            of brand hue, sized in vw so they scale
                            with the viewport. Use as body backdrop
                            layers or as section accents. Alpha stays
                            ≤ 14% so text contrast is never harmed.

       2. Halo shadows    — short outer-glow stacks, brand-tinted,
                            that ride alongside neutral shadows for
                            cards, CTAs, devices. Add dimension
                            without dominating the surface.

       3. Section shells  — prebuilt multi-stop background recipes
                            for sections (hero, cta-final, pricing,
                            trust) so every section shares one
                            atmospheric vocabulary.

     Designed to compose: a hero can layer [aurora-verde +
     aurora-oro + section-hero-shell] without any recipe fighting
     another — every bloom is radial, alpha-stacked, and fades out
     past 70% of its own radius.
     ═════════════════════════════════════════════════════════════════ */

  /* ---- Aurora bloom colors — raw rgba stops ---------------------
     Verde-only: light on this site is always green light. */
  --aurora-verde-core: rgba(0, 166, 81, 0.22);
  --aurora-verde-soft: rgba(0, 166, 81, 0.12);
  --aurora-verde-trace: rgba(0, 104, 71, 0.08);
  --aurora-menta-core: rgba(111, 207, 151, 0.22);
  --aurora-menta-soft: rgba(111, 207, 151, 0.12);
  --aurora-cream:      rgba(233, 242, 236, 0.35);

  /* ---- Ambient halo shadows — outer glow rings ------------------
     Layered with the existing neutral `--glass-shadow-*` stack.
     The trick: a tinted shadow at 20-40px blur, low opacity, no
     offset. Reads as "the element is emitting a soft corona",
     not "the element has a colored drop shadow". */
  --halo-verde-sm: 0 0 24px -4px rgba(0, 166, 81, 0.22);
  --halo-verde-md: 0 0 40px -8px rgba(0, 166, 81, 0.28),
                   0 0 80px -24px rgba(0, 104, 71, 0.22);
  --halo-verde-lg: 0 0 60px -12px rgba(0, 166, 81, 0.32),
                   0 0 120px -32px rgba(0, 104, 71, 0.28);

  /* ---- Luminous surface tints — brighter, more chromatic ------
     Use sparingly for hero-class surfaces that want to read as
     internally lit rather than merely translucent. The key is
     the warm white base + a faint cream overtone so the surface
     never greys out against the colored backdrop. */
  --glass-surface-luminous: rgba(250, 254, 251, 0.66);
  --glass-surface-luminous: light-dark(rgba(250, 254, 251, 0.66), rgba(32, 28, 23, 0.7));
  --glass-surface-luminous-strong: rgba(250, 254, 251, 0.82);
  --glass-surface-luminous-strong: light-dark(rgba(250, 254, 251, 0.82), rgba(32, 28, 23, 0.86));
  --glass-edge-luminous: rgba(255, 255, 255, 0.85);

  /* ---- Section atmosphere shells ------------------------------
     Copy-paste-friendly multi-stop recipes. Use as
     `background: var(--shell-aurora-warm);` on a section.
     Each stacks 3–4 radial blooms + a faint linear base. */

  /* Light aurora — soft verde top-left, menta mid-right. The default
     for hero + hueso sections. Verde-only light. */
  --shell-aurora-warm:
    radial-gradient(82vw 52vw at 8% -6%, var(--aurora-verde-soft) 0%, transparent 58%),
    radial-gradient(68vw 48vw at 96% 22%, var(--aurora-menta-soft) 0%, transparent 60%),
    linear-gradient(180deg, var(--canvas-top) 0%, var(--hueso) 50%, var(--canvas-bottom) 100%);

  /* Cool verde shell — for the pricing / CTA sections that live on
     a verde ground. Bright-green bloom + a softer verde corona. */
  --shell-aurora-verde:
    radial-gradient(95vw 58vw at 50% -12%, rgba(0, 214, 104, 0.28) 0%, transparent 60%),
    radial-gradient(70vw 48vw at 12% 92%, var(--aurora-menta-core) 0%, transparent 62%),
    radial-gradient(70vw 48vw at 88% 108%, rgba(0, 132, 85, 0.24) 0%, transparent 60%),
    linear-gradient(180deg, #004e31 0%, var(--verde-700) 50%, var(--verde-900) 100%);

  /* Deep verde shell — footer + dark sections. Same posture as
     verde shell but dimmer, with the bloom pushed to a thin top rim
     so the footer reads as a continuation of the CTA aurora rather
     than a flat cut. */
  --shell-aurora-deep:
    radial-gradient(120vw 44vw at 50% -8%, rgba(0, 132, 85, 0.4) 0%, transparent 52%),
    radial-gradient(60vw 40vw at 10% 110%, var(--aurora-verde-trace) 0%, transparent 70%),
    var(--verde-900);
}

/* Mobile scale overrides — tighter headlines + body. The clamp()
   floors already cover the worst case; these just tune the micro
   sizes that don't warrant a full fluid curve. */
@media (max-width: 768px) {
  :root {
    --fs-headline: 20px;
    --fs-body-l: 17px;
    --fs-body: 16px;
  }
}

/* ---------- Reset ---------- */

/* ═══════════════════════════════════════════════════════════════════
   Cross-Document View Transitions — the web platform's native answer
   to SwiftUI's `matchedGeometryEffect`.

   Supported in Chrome 126+, Edge 126+, Safari 18.2+; browsers without
   support silently fall back to a hard navigation. That's the whole
   progressive-enhancement story — no JS needed for the fallback.

   How it works:
     1. `@view-transition { navigation: auto }` opts the origin into
        cross-document transitions for same-origin same-type nav.
     2. Any element with a `view-transition-name` is captured on BOTH
        the outgoing and incoming page.
     3. Between unload and first paint of the new page the browser
        interpolates the shared element's position + size + opacity,
        and crossfades the remainder via the implicit `root` group.

   We tag THREE things:
     • `.lang-toggle-indicator` — the dark ES/EN pill slides from the
        old language's position to the new one's, natively, regardless
        of what JavaScript did or didn't do.
     • `.brand-mark-icon` + `.brand-mark-text` — the nav wordmark
        stays pinned across page loads so the nav reads as continuous
        chrome rather than a reloaded element.

   Animation speeds are tuned via the `::view-transition-group(…)`
   pseudo-elements below; they use the Apple sheet-enter curve
   (`cubic-bezier(0.16, 1, 0.3, 1)`) so the feel matches the
   hover-anticipation motion the pill already uses within a page.
   ═══════════════════════════════════════════════════════════════════ */

@view-transition {
  navigation: auto;
}

/* Default root transition — a soft crossfade, ~260ms, slightly
   asymmetric (old fades faster than new so the incoming content
   "arrives" rather than "swaps"). */
::view-transition-old(root) {
  animation: pesito-vt-fade-out 200ms cubic-bezier(0.3, 0, 1, 1) both;
}
::view-transition-new(root) {
  animation: pesito-vt-fade-in 280ms cubic-bezier(0, 0, 0, 1) both;
}
@keyframes pesito-vt-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}
@keyframes pesito-vt-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* The lang-toggle pill — the centerpiece shared element. Animation
   timing matches the in-page hover slide so the commit moment feels
   like a direct continuation of the anticipation motion. */
::view-transition-group(lang-indicator) {
  animation-duration: 460ms;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}

/* The brand wordmark — stays EXACTLY in place across nav. If the
   icon+text don't shift between pages (they shouldn't — the nav is
   fixed), the group animates effectively to a no-op, but it's still
   captured so the element doesn't repaint. */
::view-transition-group(brand-icon),
::view-transition-group(brand-text) {
  animation-duration: 300ms;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}

/* Reduced motion: browsers should already short-circuit view
   transitions, but we set a `prefers-reduced-motion` block to be
   explicit — durations drop to near-zero so the transition still
   fires (preserves layout stability) but is imperceptibly fast. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root),
  ::view-transition-group(lang-indicator),
  ::view-transition-group(brand-icon),
  ::view-transition-group(brand-text) {
    animation-duration: 1ms !important;
  }
}

*, *::before, *::after { box-sizing: border-box; }
html {
  scroll-behavior: smooth;
  scroll-padding-top: 80px;
  /* Drives both the UA chrome (form controls, scrollbars, the
     pre-paint canvas) AND every `light-dark()` token above. With no
     `data-theme` attribute the OS preference wins, so dark mode
     works with JavaScript disabled; the toggle pins it per-visitor
     by writing `data-theme="light|dark"` onto this element. */
  color-scheme: light dark;
  /* Never let mobile Safari inflate type on rotate — the fluid
     clamp() scale already handles small viewports, and the browser's
     own adjustment fights the 200%-zoom requirement (WCAG 1.4.4). */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
/* Defensive horizontal-overflow clamp.

   `position: fixed` elements (`.nav`, `.nav-sheet`, `body::after`'s
   aurora, etc.) resolve `left: 0 / right: 0` against the initial
   containing block, which in most browsers widens to the document's
   scrollable width when ANY descendant triggers horizontal overflow.
   A single stray grid gap or oversized shape was enough to push the
   nav + sheet out to the scroll width, producing the visible
   "footer shifted left of the header" misalignment on mobile.

   `overflow-x: clip` pins the ICB to the layout-viewport width
   without introducing a horizontal scroll container (unlike
   `hidden`, which would also clobber `position: sticky` on
   descendants). Both html and body get it so that paint clipping
   kicks in regardless of which is the scroller in a given browser. */
html, body { overflow-x: clip; }
body {
  margin: 0;
  /* Base layer only — a subtle top-to-bottom hueso wash. The
     real atmospheric work lives on `body::after` (the cinematic
     aurora bloom stack) and `body::before` (paper grain).
     Splitting it across pseudo-elements lets the stack layer
     cleanly: grain in OVERLAY blend mode, blooms in normal mode
     — neither fighting the other for alpha.

     Why not a single `background:` shorthand like v1?
     The old stack used `background-attachment: fixed` on three
     radials inside one composite. That forces the browser to
     repaint the entire viewport-sized gradient on any scroll
     invalidation (even when nothing moved). By moving blooms to
     a fixed pseudo-element, only that one layer owns repaint,
     and the browser composites it once per viewport resize. */
  background: var(--canvas);
  color: var(--tinta-900);
  font-family: var(--font-body);
  font-feature-settings: "tnum" 1, "cv11" 1, "ss01" 1;
  font-weight: 500;
  font-size: var(--fs-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  /* Min-height guards against short pages (legal docs, 404) where
     the aurora layer would otherwise clip to content height. */
  min-height: 100vh;
}

/* ═══════════════════════════════════════════════════════════════════
   Cinematic aurora backdrop — v2 (2026-04-23).

   Five positioned radial blooms + a warm wash, all on one fixed
   viewport-sized layer that ONLY repaints on resize. Positioned
   so the blooms span the full scroll range without ever feeling
   repetitive:

     • verde  top-left      (8% -6%)      — primary brand, establishes
                                            the page's "morning" light
     • menta  mid-right     (96% 22%)     — lighter counterpoint, sits
                                            behind the hero CTAs
     • cream  mid-center    (52% 48%)     — a soft center bloom so the
                                            upper-fold doesn't read as
                                            empty between the blobs
     • verde  bottom-left   (-6% 84%)     — anchors the FAQ / audience
                                            sections so content reveals
                                            against a lit ground
     • verde  bottom-right  (108% 112%)   — trace echo, closes the loop

   All blooms use ≤ 22% alpha so body text contrast is never at
   risk — every pixel still has enough hueso under it to hit
   AA/AAA on tinta-900/-700 copy.                                  */
/* Lotería (2026-07-16): the aurora is retired. Folk-art flatness means
   the canvas is plain card stock — no atmospheric blooms. The grain
   overlay below stays (it reads as paper, not atmosphere). The rule is
   kept as a disabled stub so the bloom stack can be recovered from git
   if a future direction wants it. */
body::after {
  content: none;
}

/* Paper grain — a top-level fixed overlay on OVERLAY blend so it
   adds film-like tactility without shifting hue. Independent of
   `body::after` so the grain's blend-mode doesn't wrestle with
   the bloom stack's alpha. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.04 0 0 0 0 0.04 0 0 0 0 0.04 0 0 0 0.05 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 240px 240px;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
}

/* Honor prefers-reduced-transparency — users who've opted out of
   Apple's translucency get a plain hueso canvas (blooms drop out,
   grain stays for tactility). All the glass primitives have their
   own reduced-transparency fallbacks, so removing the aurora
   leaves them on a clean warm base. */
@media (prefers-reduced-transparency: reduce) {
  body::after { display: none; }
}
.money, .tabular { font-variant-numeric: tabular-nums; }
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; border: none; background: none; }
ul, ol { padding: 0; margin: 0; list-style: none; }
h1, h2, h3, h4 { margin: 0; }
p { margin: 0; }

/* ---------- Inter self-host ---------- */

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

/* ---------- Utilities ---------- */

.container {
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 24px;
}
.container-inner {
  width: 100%;
  max-width: var(--container-inner);
  margin: 0 auto;
  padding: 0 24px;
}
.reading {
  width: 100%;
  max-width: var(--reading-max);
  margin: 0 auto;
}

/* Eyebrow — the small uppercase label that sits above every section
   title. v2 promotes it to a glass chip: a translucent pill with a
   soft verde halo so it reads as a "lit nameplate" floating over
   the section, matching the rest of the Liquid Glass material
   family. Remains an inline element so existing HTML (`<span
   class="eyebrow">`) doesn't need to change.
   ─────────────────────────────────────────────────────────────── */
.eyebrow {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px 6px 12px;
  border-radius: var(--glass-radius-pill);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--verde-700);
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(0, 166, 81, 0.18);
  -webkit-backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-saturate));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.78),
    inset 0 -1px 0 rgba(11, 11, 11, 0.02),
    0 1px 2px rgba(0, 61, 38, 0.06),
    0 0 24px -8px rgba(0, 166, 81, 0.22);
  font-feature-settings: "ss01" 1;
}
.eyebrow::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: currentColor;
  box-shadow: 0 0 0 3px rgba(0, 166, 81, 0.18);
  flex-shrink: 0;
}
/* Dark-section eyebrows (pricing, cta-final, gallery): flip the
   glass tint to a dim menta so it reads as a lit ornament on
   the verde ground rather than a bright hueso chip that
   fights the backdrop. */
.pricing .eyebrow,
.cta-final .eyebrow,
.trust .eyebrow,
.how-gallery .eyebrow {
  background: rgba(250, 247, 242, 0.1);
  border-color: rgba(111, 207, 151, 0.4);
  color: var(--verde-300);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -1px 0 rgba(0, 0, 0, 0.12),
    0 1px 2px rgba(0, 0, 0, 0.1),
    0 0 24px -8px rgba(0, 166, 81, 0.24);
}

/* Display classes — heroic type. On Apple devices these resolve to
   SF Pro Display (or SF Pro Rounded where `ui-rounded` is available),
   matching macOS 26 / iOS 26 headline rendering. Tight tracking +
   `text-wrap: balance` to avoid orphans at breakpoints. */
.display-xxl { font-family: var(--font-display); font-size: var(--fs-display-xxl); font-weight: 800; letter-spacing: -0.035em; line-height: 0.98; text-wrap: balance; }
.display-xl  { font-family: var(--font-display); font-size: var(--fs-display-xl);  font-weight: 800; letter-spacing: -0.03em;  line-height: 1.02; text-wrap: balance; }
.display-l   { font-family: var(--font-display); font-size: var(--fs-display-l);   font-weight: 800; letter-spacing: -0.025em; line-height: 1.04; text-wrap: balance; }
.display-m   { font-family: var(--font-display); font-size: var(--fs-display-m);   font-weight: 800; letter-spacing: -0.02em;  line-height: 1.08; text-wrap: balance; }
.title       { font-family: var(--font-display); font-size: var(--fs-title);       font-weight: 700; letter-spacing: -0.01em;  line-height: 1.15; color: var(--tinta-700); text-wrap: balance; }
.headline    { font-size: var(--fs-headline);    font-weight: 700; line-height: 1.25; color: var(--tinta-700); text-wrap: balance; }
.body-l      { font-size: var(--fs-body-l);      font-weight: 500; line-height: 1.55; color: var(--tinta-700); }
.caption     { font-size: var(--fs-caption);     font-weight: 500; letter-spacing: 0.01em; line-height: 1.4; color: var(--tinta-500); }

/* The green dot is Pesito's signature — used sparingly on headlines
   and paragraph breaks. (Renamed from .brand-dot in the 2026-07-11
   palette rationalization: the punctuation is now brand verde.) */
.brand-dot { color: var(--brand-dot); font-style: normal; display: inline-block; transform: translateY(-0.05em); }

/* Pull-quote — Inter italic, bold weight, still distinct from body copy
   but keeps the site's sans-serif consistency. */
.pullquote {
  font-style: italic;
  font-weight: 600;
  font-size: clamp(20px, 2.2vw, 30px);
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--tinta-900);
  padding: 4px 0;
  max-width: 56ch;
  text-wrap: pretty;
}
.pullquote::before {
  content: "“";
  display: block;
  font-style: normal;
  font-weight: 800;
  font-size: 1.6em;
  line-height: 0.6;
  color: var(--verde-500);
  margin-bottom: 8px;
}
.pullquote cite {
  display: block;
  margin-top: 12px;
  font-style: normal;
  font-size: var(--fs-caption);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--tinta-500);
}

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

/* Screen-reader-only utility — hides the visuals while keeping the
   element in the accessibility tree for form labels and aria hints. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Keeps a hyphenated compound or abbreviation (PRE-LAUNCH, 2-minute,
   É.-U.) on one line inside eyebrows, headings and pills, so the break
   never lands on the hyphen. */
.nobr { white-space: nowrap; }
/* .eyebrow is an inline-flex row with a gap, so a .nobr placed directly
   inside one would become its own flex item and add a gap before it.
   display: contents keeps the nowrap (its text inherits it) and drops the
   gap, as .eyebrow-seg does in editorial-system.css. */
.eyebrow > .nobr { display: contents; }

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 16px 28px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-body-l);
  font-weight: 700;
  transition: transform var(--dur-fast) var(--ease-emphasized),
              background var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard);
}
.btn:active { transform: scale(0.98); }

/* `.btn-primary`: Liquid Glass filled pill. The verde fill stays solid
   (this is the anchor CTA — not a translucent surface) but picks up
   the same top-edge specular, warm inner highlight, and layered
   shadow stack as the glass primitives so it reads as one material
   family. v2: added a soft verde halo at rest (so the button "emits"
   even before hover) and a brighter halo on hover. */
.btn-primary {
  position: relative;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0) 42%),
    linear-gradient(180deg, var(--verde-500) 0%, #008A46 100%);
  color: var(--paper-fixed);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.42),
    inset 0 -1px 0 rgba(0, 0, 0, 0.14),
    0 1px 2px rgba(0, 0, 0, 0.08),
    0 8px 18px rgba(0, 61, 38, 0.22),
    0 22px 44px -14px rgba(0, 61, 38, 0.28),
    var(--halo-verde-sm);
  border: 1px solid rgba(0, 104, 71, 0.45);
  letter-spacing: -0.005em;
}
.btn-primary:hover {
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.52),
    inset 0 -1px 0 rgba(0, 0, 0, 0.14),
    0 1px 3px rgba(0, 0, 0, 0.1),
    0 12px 24px rgba(0, 61, 38, 0.28),
    0 30px 54px -14px rgba(0, 61, 38, 0.36),
    var(--halo-verde-md);
}

/* `.btn-outline`: clear glass pill. Text reads as tinta-700 on warm
   contexts, flips to hueso when the parent has `.on-dark`. */
.btn-outline {
  position: relative;
  background: var(--glass-surface-clear);
  -webkit-backdrop-filter:
    blur(var(--glass-blur-sm)) saturate(var(--glass-saturate)) brightness(var(--glass-brightness));
  backdrop-filter:
    blur(var(--glass-blur-sm)) saturate(var(--glass-saturate)) brightness(var(--glass-brightness));
  color: var(--tinta-700);
  border: 1px solid var(--glass-edge-stroke-warm);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 -1px 0 rgba(0, 0, 0, 0.04),
    var(--glass-shadow-sm);
}
.btn-outline:hover {
  background: rgba(255, 255, 255, 0.42);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.65),
    inset 0 -1px 0 rgba(0, 0, 0, 0.05),
    var(--glass-shadow-md);
}
/* Ghost button — text-only with a subtle glass chip on hover so it
   has a "clicked target" feel without competing with the primary
   verde-filled pill. Always text-first. */
.btn-ghost {
  color: var(--verde-700);
  padding: 12px 16px;
  border-radius: var(--glass-radius-pill);
  transition:
    color var(--dur-fast) var(--ease-standard),
    background var(--dur-fast) var(--ease-standard),
    box-shadow var(--dur-fast) var(--ease-standard);
}
/* Hover-only fills sit behind (hover: hover): a touch screen keeps
   :hover on the last tapped element, which left pills, rows and the
   theme toggle painted in their hover state after a tap. */
@media (hover: hover) {
  .btn-ghost:hover {
    color: var(--verde-900);
    background: rgba(0, 166, 81, 0.1);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.5),
      0 0 24px -6px rgba(0, 166, 81, 0.22);
  }
}

/* Focus rings — ALWAYS visible, restyled only.

   2026-09-02: was `2px solid var(--verde-500)`, which measures 2.99:1 on
   --hueso — a hair under the 3:1 that WCAG 2.2 SC 1.4.11 requires of a
   non-text indicator, and the weaker of the two rings this file already
   shipped (the button-scoped override below used verde-700 at 6.4:1).
   The default now matches the override, and cobalto is the ring the other
   two Pesito surfaces use, so focus looks the same across all three. */
:focus-visible {
  outline: 3px solid var(--azul-700);
  outline-offset: 2px;
}

/* ═══════════════════════════════════════════════════════════════════
   Iridescent sheen — Pesito's "oil on warm glass" hover effect.

   Ported from the reference neumorphic-iridescent button
   (https://freefrontend.com/code/iridescent-3d-neumorphic-button-2026-03-12/,
   credit Simey / inspired by Wojciech Zieliński) but re-tinted to
   the brand's heritage spectrum — mint → verde → teal rather
   than the reference's cool cyan/magenta — so it reads as Pesito,
   not generic glassmorphism.

   Applied as a utility over ANY button that has `position: relative`
   and a non-transparent `border-radius`. Works by layering two
   pseudo-elements OVER the existing button fill:

     1. `::after`  — inner glassy glow. Brightens the bottom edge on
                     hover, sells the "lit from below" depth.
     2. `::before` — the iridescent sheen. Blurred diagonal gradient
                     that slides IN from the top-right on hover; on
                     rest it's invisible (opacity 0, translated out).

   Both layers are strictly DECORATIVE: `pointer-events: none` so
   hit-testing is unchanged; `mix-blend-mode: lighten` so they never
   make text harder to read (can only BRIGHTEN the underlying pixel,
   not darken).

   The `oklab` color-space gradient interpolates perceptually — no
   muddy greys in the middle of the spectrum. Requires Chrome 111+ /
   Safari 16.2+ / Firefox 113+; older browsers fall through to the
   base button fill with no sheen (graceful degrade).

   Applied below to `.btn-primary`, `.btn-rojo`, `.store-badge`,
   `.finale-store`, `.finale-signin-cta`, and `.cta-final
   .waitlist-submit` — every tier-1 CTA on the site. Secondary /
   ghost / outline buttons deliberately skip it so the iridescence
   stays a premium signal, not wallpaper.
   ═══════════════════════════════════════════════════════════════════ */

.iridescent {
  position: relative;
  isolation: isolate; /* contain the mix-blend to the button */
}

.iridescent::before,
.iridescent::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  transition:
    opacity 600ms cubic-bezier(0.32, 0, 0.67, 0),
    transform 600ms cubic-bezier(0.32, 0, 0.67, 0),
    filter 600ms cubic-bezier(0.32, 0, 0.67, 0);
}

/* Inner glassy glow — a soft lit bottom edge. Present (low opacity)
   at rest; intensifies on hover. */
.iridescent::after {
  opacity: 0.28;
  box-shadow:
    inset 0 -0.25em 2px 1px rgba(255, 255, 255, 0.42),
    inset 0 -0.3em 0.5em rgba(255, 255, 255, 0.25),
    inset 0 -0.3em 1em rgba(255, 255, 255, 0.18);
  mix-blend-mode: lighten;
  z-index: 2;
}

/* Iridescent sheen — the headline effect. Hidden at rest (opacity 0,
   translated off-screen to upper-right, scaled down), animates IN on
   hover from the top-right corner like a light hitting the surface.
   The mask ensures the sheen only shows on the lower-right portion
   of the button, leaving the top-left clean for legibility. */
.iridescent::before {
  opacity: 0;
  transform: translate(1.1em, 0) scale(0.85);
  background: linear-gradient(
    98deg in oklab,
    oklch(0.95 0.08 145) -5%,      /* pale mint */
    oklch(0.9 0.17 152),            /* brand verde */
    oklch(0.88 0.15 172) 65%,       /* teal-verde */
    oklch(0.92 0.08 195) 140%       /* cool aqua tail */
  );
  /* Diagonal mask: sheen fades in from top-right, absent top-left */
  -webkit-mask: linear-gradient(166deg, transparent 55%, black 95%);
          mask: linear-gradient(166deg, transparent 55%, black 95%);
  filter: blur(6px) contrast(1.25);
  mix-blend-mode: lighten;
  z-index: 3;
}

/* Hover / focus / shine states — the magic moment. */
.iridescent:hover::before,
.iridescent:focus-visible::before,
.iridescent.is-shining::before {
  opacity: 0.55;
  transform: translate(0, 0) scale(1);
  transition-duration: 320ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}
.iridescent:hover::after,
.iridescent:focus-visible::after,
.iridescent.is-shining::after {
  opacity: 0.75;
  transition-duration: 320ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}

/* Dark-surface variant — buttons with a dark body (finale-signin-cta,
   finale-store) need a brighter sheen to punch through the tint.
   Applied via `.iridescent--dark`. */
.iridescent--dark::before {
  background: linear-gradient(
    98deg in oklab,
    oklch(0.97 0.07 145) -5%,
    oklch(0.95 0.16 150),
    oklch(0.93 0.2 170) 65%,
    oklch(0.95 0.1 200) 140%
  );
  filter: blur(7px) contrast(1.3) brightness(1.05);
}
.iridescent--dark::after {
  box-shadow:
    inset 0 -0.25em 2px 1px rgba(255, 255, 255, 0.28),
    inset 0 -0.3em 0.5em rgba(255, 255, 255, 0.18),
    inset 0 -0.3em 1em rgba(255, 255, 255, 0.12);
}
.iridescent--dark:hover::before,
.iridescent--dark:focus-visible::before,
.iridescent--dark.is-shining::before {
  opacity: 0.42;
}

/* Reduced-motion + reduced-transparency: skip the sheen entirely.
   The base button styling is intact and still signals interactivity
   via the existing Liquid Glass hover transform. */
@media (prefers-reduced-motion: reduce), (prefers-reduced-transparency: reduce) {
  .iridescent::before,
  .iridescent::after {
    display: none;
  }
}

/* Apply the utility to every tier-1 CTA on the site. No HTML
   changes required — just CSS composition. Buttons that already
   share `position: relative` (all the primary CTAs via the `.btn`
   base, the store badges via their own rules, the finale-* via their
   own rules) inherit the sheen automatically. */
.btn-primary,
.btn-rojo,
.cta-final .waitlist-submit,
.store-badge,
.finale-signin-cta {
  position: relative;
  isolation: isolate;
}
.btn-primary::before,
.btn-primary::after,
.btn-rojo::before,
.btn-rojo::after,
.cta-final .waitlist-submit::before,
.cta-final .waitlist-submit::after,
.store-badge::before,
.store-badge::after,
.finale-signin-cta::before,
.finale-signin-cta::after,
.finale-store::before,
.finale-store::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  transition:
    opacity 600ms cubic-bezier(0.32, 0, 0.67, 0),
    transform 600ms cubic-bezier(0.32, 0, 0.67, 0),
    filter 600ms cubic-bezier(0.32, 0, 0.67, 0);
}

/* Light/brand-colored buttons — mint→verde→teal sheen. */
.btn-primary::before,
.btn-rojo::before,
.cta-final .waitlist-submit::before,
.store-badge::before {
  opacity: 0;
  transform: translate(1.1em, 0) scale(0.85);
  background: linear-gradient(
    98deg in oklab,
    oklch(0.86 0.16 30) -5%,
    oklch(0.92 0.15 85),
    oklch(0.9 0.18 165) 65%,
    oklch(0.88 0.2 280) 140%
  );
  -webkit-mask: linear-gradient(166deg, transparent 55%, black 95%);
          mask: linear-gradient(166deg, transparent 55%, black 95%);
  filter: blur(6px) contrast(1.25);
  mix-blend-mode: lighten;
  z-index: 3;
}
.btn-primary::after,
.btn-rojo::after,
.cta-final .waitlist-submit::after,
.store-badge::after {
  opacity: 0.28;
  box-shadow:
    inset 0 -0.25em 2px 1px rgba(255, 255, 255, 0.42),
    inset 0 -0.3em 0.5em rgba(255, 255, 255, 0.25),
    inset 0 -0.3em 1em rgba(255, 255, 255, 0.18);
  mix-blend-mode: lighten;
  z-index: 2;
}

/* Dark variant — jet-black or dark-translucent backgrounds get a
   brighter sheen + subtler inner glow so the iridescence still reads
   against the dark canvas. */
.finale-signin-cta::before,
.finale-store::before {
  opacity: 0;
  transform: translate(1.1em, 0) scale(0.85);
  background: linear-gradient(
    98deg in oklab,
    oklch(0.92 0.2 30) -5%,
    oklch(0.98 0.18 90),
    oklch(0.95 0.22 170) 65%,
    oklch(0.95 0.24 275) 140%
  );
  -webkit-mask: linear-gradient(166deg, transparent 55%, black 95%);
          mask: linear-gradient(166deg, transparent 55%, black 95%);
  filter: blur(7px) contrast(1.3) brightness(1.05);
  mix-blend-mode: lighten;
  z-index: 3;
}
.finale-signin-cta::after,
.finale-store::after {
  opacity: 0.22;
  box-shadow:
    inset 0 -0.25em 2px 1px rgba(255, 255, 255, 0.28),
    inset 0 -0.3em 0.5em rgba(255, 255, 255, 0.18),
    inset 0 -0.3em 1em rgba(255, 255, 255, 0.12);
  mix-blend-mode: lighten;
  z-index: 2;
}

/* Hover / focus — unify all variants through a single set of
   transitions to the same target state. */
.btn-primary:hover::before,
.btn-primary:focus-visible::before,
.btn-rojo:hover::before,
.btn-rojo:focus-visible::before,
.cta-final .waitlist-submit:hover::before,
.cta-final .waitlist-submit:focus-visible::before,
.store-badge:hover::before,
.store-badge:focus-visible::before {
  opacity: 0.58;
  transform: translate(0, 0) scale(1);
  transition-duration: 320ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}
.btn-primary:hover::after,
.btn-primary:focus-visible::after,
.btn-rojo:hover::after,
.btn-rojo:focus-visible::after,
.cta-final .waitlist-submit:hover::after,
.cta-final .waitlist-submit:focus-visible::after,
.store-badge:hover::after,
.store-badge:focus-visible::after {
  opacity: 0.78;
  transition-duration: 320ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}
.finale-signin-cta:hover::before,
.finale-signin-cta:focus-visible::before,
.finale-store:hover::before,
.finale-store:focus-visible::before {
  opacity: 0.46;
  transform: translate(0, 0) scale(1);
  transition-duration: 320ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}
.finale-signin-cta:hover::after,
.finale-signin-cta:focus-visible::after,
.finale-store:hover::after,
.finale-store:focus-visible::after {
  opacity: 0.6;
  transition-duration: 320ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}

/* One-time load shimmer — every tier-1 CTA briefly pulses on first
   paint so the user sees the iridescent material exists before any
   hover. Controlled by the `is-shining` class that `scripts/main.js`
   adds on DOMContentLoaded + removes after the shimmer completes. */
.btn-primary.is-shining::before,
.btn-rojo.is-shining::before,
.cta-final .waitlist-submit.is-shining::before,
.store-badge.is-shining::before {
  opacity: 0.52;
  transform: translate(0, 0) scale(1);
  transition-duration: 900ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}
.btn-primary.is-shining::after,
.btn-rojo.is-shining::after,
.cta-final .waitlist-submit.is-shining::after,
.store-badge.is-shining::after {
  opacity: 0.7;
  transition-duration: 900ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}
.finale-signin-cta.is-shining::before,
.finale-store.is-shining::before {
  opacity: 0.42;
  transform: translate(0, 0) scale(1);
  transition-duration: 900ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}
.finale-signin-cta.is-shining::after,
.finale-store.is-shining::after {
  opacity: 0.55;
  transition-duration: 900ms;
  transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
}

/* Reduced-motion + reduced-transparency: skip sheen entirely. */
@media (prefers-reduced-motion: reduce), (prefers-reduced-transparency: reduce) {
  .btn-primary::before,
  .btn-primary::after,
  .btn-rojo::before,
  .btn-rojo::after,
  .cta-final .waitlist-submit::before,
  .cta-final .waitlist-submit::after,
  .store-badge::before,
  .store-badge::after,
  .finale-signin-cta::before,
  .finale-signin-cta::after,
  .finale-store::before,
  .finale-store::after {
    display: none;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   Liquid Glass — primitives

   Three things make this feel like a real iOS 26 / macOS 26 surface
   rather than "frosted glass, AI edition":

     1. The ::before top-edge specular. One hair-thin 1px gradient
        from white 0.6 down to transparent, draped over the top 40%
        of the element. This is what makes glass look "lit".
     2. The ::after bottom vignette. A subtle inverse gradient on
        the bottom edge, darker by ~8%, so the element has weight.
     3. box-shadow stacks of 2-3 layers. Flat 8px shadows always
        read as cheap; real Apple glass has a hair layer + a mid
        layer + a long ambient.

   Use `.glass` as the baseline, then compose variants (`.glass--dark`,
   `.glass--verde`, `.glass--clear`) for tone. Wrap interactive glass
   with `.glass--interactive` to get hover squeeze + shadow bloom.
   ═══════════════════════════════════════════════════════════════════ */

.glass {
  position: relative;
  background: var(--glass-surface-regular);
  -webkit-backdrop-filter:
    blur(var(--glass-blur-md))
    saturate(var(--glass-saturate))
    brightness(var(--glass-brightness));
  backdrop-filter:
    blur(var(--glass-blur-md))
    saturate(var(--glass-saturate))
    brightness(var(--glass-brightness));
  border-radius: var(--glass-radius-md);
  border: 1px solid var(--glass-edge-stroke);
  box-shadow:
    inset 0 1px 0 var(--glass-edge-top),
    inset 0 -1px 0 var(--glass-edge-bottom),
    var(--glass-shadow-md);
  isolation: isolate;
  transition:
    transform var(--dur-base) var(--ease-emphasized),
    box-shadow var(--dur-base) var(--ease-standard),
    background var(--dur-base) var(--ease-standard),
    border-color var(--dur-base) var(--ease-standard);
}

/* Top-edge specular — a lit arc across the top 42% of the card. */
.glass::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.28) 0%,
    rgba(255, 255, 255, 0.04) 42%,
    transparent 70%
  );
  mix-blend-mode: plus-lighter;
  z-index: 0;
}

/* Bottom vignette — weight. */
.glass::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    transparent 0%,
    transparent 65%,
    rgba(11, 11, 11, 0.06) 100%
  );
  z-index: 0;
}

/* Direct children sit above the specular/vignette layers. */
.glass > * { position: relative; z-index: 1; }


/* Interactive glass — pointer state responds with a subtle lift and
   a deeper shadow + a widened specular, mirroring iOS 26's "breathing"
   button state. */
.glass--interactive {
  cursor: pointer;
  will-change: transform, box-shadow;
}
.glass--interactive:hover {
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 var(--glass-edge-top),
    inset 0 -1px 0 var(--glass-edge-bottom),
    var(--glass-shadow-lg);
}
.glass--interactive:active {
  transform: translateY(0) scale(0.985);
  transition-duration: var(--dur-instant);
}


/* ---- Liquid Glass "goo" container ---------------------------------
   Port of SwiftUI's `GlassEffectContainer(spacing:)`. When applied to
   a parent, any `.glass` or `.glass--pill` children inside it that
   come within ~spacing pixels of each other will PHYSICALLY MERGE
   into one continuous shape (like mercury beads fusing). As they
   pull apart, the fused shape splits.

   Implementation is an SVG filter trick:

     1. Gaussian blur softens the edges of child glass shapes.
     2. feColorMatrix thresholds alpha to near-binary, turning the
        blurred halos into hard-edged fused regions.
     3. feComposite re-overlays the original SourceGraphic so the
        interior of each shape (type, icons) stays crisp — only the
        EDGES participate in the merge.

   The trade-off: children rendered through the filter lose their
   own drop shadows and backdrop-filter effects (filter eats them).
   So `.glass-container` children use an INTERNAL tint + hair border,
   not backdrop-blur. Render the container on a parent that itself
   has glass/backdrop if you want blur-through-the-group.

   Use `.glass-container--tight` for a shorter merge reach (buttons,
   chips) and `.glass-container` for the default (cards, store
   badges). Spacing in SwiftUI lives on the container; we encode it
   in the filter's stdDeviation.                                     */

.glass-container {
  filter: url(#liquid-goo);
  /* isolate so the goo filter only composites its own subtree,
     not the surrounding page. */
  isolation: isolate;
}
.glass-container--tight {
  filter: url(#liquid-goo-tight);
  isolation: isolate;
}


/* Reduced motion / reduced transparency: keep the goo off when the
   user has disabled animations — the merge IS the animation. */
@media (prefers-reduced-motion: reduce), (prefers-reduced-transparency: reduce) {
  .glass-container,
  .glass-container--tight { filter: none; }
}

/* Older browsers / contexts without backdrop-filter support fall
   back to a heavier opaque tint so the element still has identity.
   (`.nav` fallbacks live alongside the `.nav` rule below — source
   order matters for cascade.) */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .glass { background: rgba(250, 247, 242, 0.92); }
}

/* Honor prefers-reduced-transparency (macOS/iOS accessibility toggle). */
@media (prefers-reduced-transparency: reduce) {
  .glass { background: var(--hueso); }
  .glass::before, .glass::after { display: none; }
}

/* ---------- Navigation ----------

   Apple macOS 26 / iOS 26 "Tahoe" Liquid Glass chrome. Two things
   make this a real pane of glass rather than a CSS frost effect:

     1. Proper blur depth. 28px rest, 40px scrolled — paired with
        1.92x saturation so the warm hueso backdrop colours punch
        through instead of bleaching to grey frost.
        ponytail: the SVG feTurbulence warp (`url(#nav-liquid-glass)`
        chained into backdrop-filter, injected by a since-deleted
        `injectNavGlassFilter` IIFE in scripts/main.js) cost 5x scroll
        frame time over the hero video — it forces software filtering
        on every scroll frame. Plain GPU-composited blur is the
        ceiling here; don't reintroduce the url() component.

     2. Layered edge lighting. Four inset shadows, ported from the
        macOS 26 chrome spec:
          • upper-left 2px specular seam         (the lit edge)
          • lower-right 2px softer return        (ambient bounce)
          • inner 3px ring glow at 0.42 alpha    (the "etched" look)
          • outer ambient drop shadow            (depth off surface)
        Together they read as a single continuous 1px bevel of
        refracted light — the detail that separates "glass" from
        "translucent box".

   Before scroll: soft lighting, ~22% hueso tint so the hero art
   shows through. After scroll: the `nav--scrolled` class (set by
   scripts/main.js when window.scrollY > 8) bumps blur, thickens the
   tint to ~72% chrome, intensifies every inset, and pops the
   hair-thin bottom divider. */
/* Floating Liquid Glass capsule (2026-08-26). The nav detaches from
   the viewport edge into a rounded pill that content scrolls UNDER —
   a floating functional layer, not a consumed strip. Every lighting
   value is theme-aware: dark mode is its own dark glass (deep tint,
   gentler saturation boost, no brightness lift, faint top edge
   light), never the light material's white film over dark content. */
.nav {
  position: fixed;
  top: max(10px, env(safe-area-inset-top, 0px));
  left: 12px;
  right: 12px;
  margin-inline: auto;
  max-width: var(--container-max);
  z-index: 100;
  height: 68px;
  border-radius: 999px;
  overflow: clip;
  background: var(--nav-glass-tint);
  -webkit-backdrop-filter:
    blur(var(--glass-blur-nav))
    saturate(var(--nav-glass-saturate))
    brightness(var(--nav-glass-brightness));
  backdrop-filter:
    blur(var(--glass-blur-nav))
    saturate(var(--nav-glass-saturate))
    brightness(var(--nav-glass-brightness));
  /* Edge-light stack: a bright top hairline (light catching the
     material), a soft inner ring (the etched lip), and an ambient
     drop that gives the capsule float. All themed. */
  box-shadow:
    inset 0 1px 0 var(--nav-glass-edge),
    inset 0 -1px 0 var(--nav-glass-edge-under),
    inset 0 0 3px 1px var(--nav-glass-ring),
    0 8px 24px -14px var(--nav-glass-drop);
  transition:
    background var(--dur-base) var(--ease-standard),
    backdrop-filter var(--dur-base) var(--ease-standard),
    -webkit-backdrop-filter var(--dur-base) var(--ease-standard),
    box-shadow var(--dur-base) var(--ease-standard);
}
/* Top-edge specular — a lit arc across the upper 55% of the capsule.
   Visible at rest so the nav always reads as lit glass, full at
   scroll for the materialize moment. */
.nav::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 55%;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    var(--nav-glass-specular) 0%,
    transparent 100%
  );
  opacity: 0.55;
  transition: opacity var(--dur-base) var(--ease-standard);
  z-index: 0;
}
/* Bottom vignette — gives the capsule weight against what scrolls
   beneath it. */
.nav::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 40%;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    transparent 0%,
    var(--nav-glass-vignette) 100%
  );
  opacity: 0.5;
  transition: opacity var(--dur-base) var(--ease-standard);
  z-index: 0;
}
/* Scroll: the material thickens — deeper tint, more blur, heavier
   drop — so the same surface reads as gaining substance, not as a
   different bar fading in. */
.nav.nav--scrolled {
  background: var(--glass-surface-chrome);
  -webkit-backdrop-filter:
    blur(var(--glass-blur-nav-scrolled))
    saturate(var(--nav-glass-saturate-scrolled))
    brightness(var(--nav-glass-brightness));
  backdrop-filter:
    blur(var(--glass-blur-nav-scrolled))
    saturate(var(--nav-glass-saturate-scrolled))
    brightness(var(--nav-glass-brightness));
  box-shadow:
    inset 0 1px 0 var(--nav-glass-edge-strong),
    inset 0 -1px 0 var(--nav-glass-edge-under),
    inset 0 0 3px 1px var(--nav-glass-ring-strong),
    0 16px 40px -18px var(--nav-glass-drop-scrolled);
}
.nav.nav--scrolled::before { opacity: 1; }
.nav.nav--scrolled::after { opacity: 1; }

/* Nav fallbacks — placed AFTER the base `.nav` rule so source order
   lets them override. Without backdrop-filter a half tint is too
   transparent to read as chrome; lift to near-opaque. The reduced-
   transparency variant goes frosty-solid with a defined hairline —
   accessibility trumps aesthetics; the capsule shape stays. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .nav {
    /* Plain color first: the engines that lack backdrop-filter are
       exactly the ones that may lack light-dark() too — without this
       line the whole declaration drops and the fallback is inert. */
    background: rgba(250, 247, 242, 0.96);
    background: light-dark(rgba(250, 247, 242, 0.96), rgba(24, 21, 17, 0.96));
    box-shadow:
      inset 0 1px 0 var(--nav-glass-edge),
      0 8px 24px -14px var(--nav-glass-drop);
  }
  .nav.nav--scrolled {
    background: var(--hueso);
  }
}
@media (prefers-reduced-transparency: reduce) {
  .nav {
    background: var(--hueso);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border: 1px solid var(--hairline-strong);
    box-shadow: 0 8px 24px -14px var(--nav-glass-drop);
  }
  .nav::before,
  .nav::after { display: none; }
}

.nav-inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  height: 100%;
  gap: 32px;
}
.nav-links {
  display: flex;
  gap: 32px;
  margin: 0 auto;
}
.nav-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 4px;
  font-size: var(--fs-body);
  color: var(--tinta-700);
  font-weight: 600;
  transition: color var(--dur-fast) var(--ease-standard);
}
.nav-links a:hover { color: var(--verde-700); }
.nav-links a[aria-current="page"] { color: var(--verde-ink); border-bottom: 2px solid var(--verde-700); }
.nav-actions { display: flex; align-items: center; gap: 12px; }
/* Liquid Glass segmented control — Apple `GlassEffectContainer` port.
   --------------------------------------------------------------------
   Apple's GlassEffectContainer behavior (per developer docs):
     > As shapes near one another, their paths start to blend into one
     > another. The higher the spacing, the sooner blending begins as
     > the shapes approach each other.

   In SwiftUI, each `glassEffect(...)` child in a container contributes
   a shape to one rendered material. When the ACTIVE state moves, the
   dark indicator slides between positions and — because neighbors are
   inside the goo threshold — its path literally morphs continuously.

   CSS port:
     1. `.lang-toggle-indicator` is the single animated shape — a
        dark absolute-positioned pill that JS slides over whichever
        anchor is active.
     2. Under the `#liquid-goo-tight` filter, as the indicator slides
        past the hovered anchor, the two shapes' alpha halos fuse into
        one continuous contour for the duration of the transit.
     3. Hover anticipation: pointer on the idle anchor moves the
        indicator fully over IT first, then the page load completes
        the navigation. Users see the commit before the URL changes.

   See `scripts/main.js` for the positioning logic.                   */

.lang-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 4px;
  font-size: var(--fs-caption);
  font-weight: 600;
  isolation: isolate;
  /* Baseline toggle-container background — glass hueso pill. The goo
     filter is INTENTIONALLY not applied here: it would threshold the
     anchor glyphs into invisibility (labels are alpha-antialiased and
     don't survive the 18/-7 alpha clamp). The sliding indicator alone
     gives Apple's segmented-control feel; we keep `#liquid-goo-tight`
     available via `.glass-container--tight` for surfaces where the
     children are solid shapes without text. */
  /* Theme-aware: light = milky glass chip; dark = a recessed smoked
     well inside the capsule. The old hardcoded white chip made the
     dark theme's pale inactive label (tinta-500) sit on milky white
     at ~1.6:1 - the unreadable ES next to an active EN. */
  background: light-dark(rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.08));
  border: 1px solid light-dark(rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.14));
  box-shadow:
    inset 0 1px 0 light-dark(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.08)),
    inset 0 -1px 0 rgba(0, 0, 0, 0.04),
    0 1px 2px light-dark(rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.3));
  border-radius: var(--glass-radius-pill);
  -webkit-backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-saturate));
}

/* The animated dark indicator.

   Two animation contexts both drive this element:
     • IN-PAGE (hover preview):  CSS transition animates --x / --w
       via `scripts/main.js` mouseover.
     • CROSS-PAGE (commit):  the `view-transition-name` below tags
       the element as a shared surface. When the user clicks to
       switch language, the browser snapshots the indicator's
       position on the old page and interpolates to the new page's
       position natively — no JS bridge required.

   Both paths use the same cubic-bezier so they feel like one motion.

   The initial-paint flash we had (indicator rendering at 0,34 for
   one frame before JS corrects it) is prevented by the new
   `.lang-toggle[data-ready="false"]` rule: transitions are suppressed
   until the first rAF, then enabled. */
.lang-toggle-indicator {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 0;
  width: var(--w, 34px);
  transform: translate3d(var(--x, 0px), 0, 0);
  background: var(--tinta-900);
  border-radius: var(--glass-radius-pill);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -1px 0 rgba(0, 0, 0, 0.3),
    0 2px 6px rgba(0, 0, 0, 0.22);
  pointer-events: none;
  z-index: 1;
  will-change: transform, width;
  transition:
    transform 440ms cubic-bezier(0.16, 1, 0.3, 1),
    width 440ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* Only the NAV's toggle participates in the cross-document view
   transition — a `view-transition-name` must be globally unique per
   document, and the footer also contains a `.lang-toggle-indicator`.
   The nav version is above-fold, visible on every scroll depth, and
   is the one users interact with most; the footer toggle still works,
   it just doesn't do the cross-page slide. */
.nav .lang-toggle-indicator {
  view-transition-name: lang-indicator;
}
/* Suppress the initial-paint glide. The JS in main.js sets
   data-ready="true" after it has positioned the indicator over the
   active anchor; only then do in-page hover transitions activate.
   (The cross-document transition is unaffected — it uses view-
   transition snapshots, not the CSS `transition` property.) */
.lang-toggle[data-ready="false"] .lang-toggle-indicator {
  transition: none;
}

.lang-toggle a {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 8px;
  border-radius: var(--glass-radius-pill);
  color: var(--tinta-500);
  z-index: 2;
  transition: color 320ms cubic-bezier(0.2, 0, 0, 1);
}
.lang-toggle a:hover { color: var(--tinta-900); }
.lang-toggle a.lang-toggle--active { color: var(--blanco); }
/* Drawn flags of the Americas (inline SVG — emoji flags render as bare
   letter pairs on Windows). A hairline keeps the white Canadian field
   visible on light surfaces and the dark canton visible on dark ones. */
.lang-flag {
  display: block;
  flex: none;
  border-radius: 2.5px;
  box-shadow: 0 0 0 0.5px light-dark(rgba(26, 18, 6, 0.22), rgba(255, 255, 255, 0.25));
}

/* Five-language dropdown for the nav (landing pages). A native
   <details>: tap toggles it with no JS (mobile), main.js adds
   hover-open on fine pointers plus outside-click and Escape to close.
   The panel materializes from its trigger (transform-origin at the
   summary) and drops the motion under prefers-reduced-motion. */
.lang-menu { position: relative; }
/* The nav capsule clips its contents (overflow: clip) to keep the glass
   edges clean — which would also clip the dropdown panel. Un-clip only
   while the menu is open; the capsule's own contents never overflow, so
   nothing else changes. */
.nav:has(details.lang-menu[open]) { overflow: visible; }
.lang-menu summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Padding sets the visual pill; `min-height` guarantees the HIG
     44pt touch target without inflating the pill's look on wide
     viewports where the flag + code + chevron already fill it. */
  min-height: 44px;
  padding: 7px 12px;
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--tinta-900);
  cursor: pointer;
  list-style: none;
  background: light-dark(rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.08));
  border: 1px solid light-dark(rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.14));
  box-shadow:
    inset 0 1px 0 light-dark(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.08)),
    inset 0 -1px 0 rgba(0, 0, 0, 0.04),
    0 1px 2px light-dark(rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.3));
  border-radius: var(--glass-radius-pill);
  -webkit-backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-saturate));
}
.lang-menu summary::-webkit-details-marker { display: none; }
.lang-menu summary:active { transform: scale(0.97); }
.lang-menu .lang-menu-chev { transition: transform 180ms cubic-bezier(0.2, 0, 0, 1); }
.lang-menu[open] .lang-menu-chev { transform: rotate(180deg); }
.lang-menu ul {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  translate: -50% 0;
  margin: 0;
  padding: 6px;
  list-style: none;
  min-width: 176px;
  z-index: 60;
  background: light-dark(rgba(255, 252, 245, 0.92), rgba(28, 26, 23, 0.92));
  border: 1px solid light-dark(rgba(26, 18, 6, 0.12), rgba(255, 255, 255, 0.14));
  border-radius: 16px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.16), 0 2px 8px rgba(0, 0, 0, 0.08);
  -webkit-backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur-sm)) saturate(var(--glass-saturate));
  transform-origin: top center;
  animation: lang-menu-in 170ms cubic-bezier(0.2, 0, 0, 1);
}
@keyframes lang-menu-in {
  from { opacity: 0; transform: scale(0.94) translateY(-4px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .lang-menu ul { animation: none; }
  .lang-menu .lang-menu-chev { transition: none; }
  .lang-menu summary:active { transform: none; opacity: 0.8; }
}
.lang-menu ul a {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 44px; display: flex; align-items: center; padding: 10px 12px;
  border-radius: 10px;
  text-decoration: none;
  color: var(--tinta-900);
  font-weight: 600;
  font-size: var(--fs-caption);
}
@media (hover: hover) {
  .lang-menu ul a:hover { background: light-dark(rgba(26, 18, 6, 0.06), rgba(255, 255, 255, 0.08)); }
}
.lang-menu ul a[aria-current="true"] { font-weight: 800; }
.lang-menu ul a[aria-current="true"]::after {
  content: "";
  margin-left: auto;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--verde-500);
}
/* Very small phones: the nav capsule runs out of room and starts
   clipping its own controls. The pill compacts to flag + chevron
   (the flag is the mode's identity here) and the spacing tightens. */
@media (max-width: 419.98px) {
  .lang-menu .lang-menu-code { display: none; }
  .lang-menu summary { padding: 7px 9px; }
  .nav-actions { gap: 8px; }
  .nav-inner { gap: 14px; }
}
/* Fold-class widths (Galaxy Fold cover screen is 280px): the wordmark
   text yields and the app icon carries the brand. The link then
   shrinks to the icon's 28px, so give it the 44pt width back — this
   is the narrowest screen we support and the easiest place to mistap
   the home link into the theme toggle beside it. */
@media (max-width: 339.98px) {
  .brand-mark-text { display: none; }
  .brand-mark { min-width: 44px; justify-content: center; }
}
/* While the indicator is over a non-active anchor (hover anticipation),
   lighten the hovered link to show focus intent without flickering
   the non-hovered link's color. JS sets data-preview="<code>" on the
   host when a hover is in flight. */
.lang-toggle[data-preview="es"] a[data-lang="es"],
.lang-toggle[data-preview="en"] a[data-lang="en"],
.lang-toggle[data-preview="pt"] a[data-lang="pt"],
.lang-toggle[data-preview="nah"] a[data-lang="nah"],
.lang-toggle[data-preview="fr"] a[data-lang="fr"] { color: var(--blanco); }
.lang-toggle[data-preview]:not([data-preview="es"]) a[data-lang="es"].lang-toggle--active,
.lang-toggle[data-preview]:not([data-preview="en"]) a[data-lang="en"].lang-toggle--active,
.lang-toggle[data-preview]:not([data-preview="pt"]) a[data-lang="pt"].lang-toggle--active,
.lang-toggle[data-preview]:not([data-preview="nah"]) a[data-lang="nah"].lang-toggle--active,
.lang-toggle[data-preview]:not([data-preview="fr"]) a[data-lang="fr"].lang-toggle--active {
  color: var(--tinta-500);
}

/* Reduced motion: the indicator still reflects the active anchor's
   position, but without the transit animation — jumps to it. Still
   visually a glass pill, still readable, just no morph. */
@media (prefers-reduced-motion: reduce) {
  .lang-toggle-indicator { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .lang-toggle { filter: none; }
  .lang-toggle-indicator { box-shadow: none; }
}
.nav-hamburger {
  display: none;
  /* 44×44 HIG minimum; the 22px glyph inside is unchanged. */
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  color: var(--tinta-900);
}
.nav-hamburger:hover { background: var(--verde-050); }

/* The legacy bar only ever shows its link list in the narrow band
   between the mobile hamburger (<1024px) and the rail (≥1280px).
   Five links plus the actions cluster don't fit there at the
   desktop 32px rhythm, so tighten the gap and drop the secondary
   sign-in link — it's still in the sheet, the footer and the final
   CTA. */
@media (min-width: 1024px) and (max-width: 1279.98px) {
  .nav-links { gap: 22px; }
  .nav-links a { font-size: var(--fs-body-s); }
  .nav-actions .btn-ghost { display: none; }
}

@media (max-width: 1023px) {
  .nav-links { display: none; }
  .nav-hamburger { display: flex; }
  .nav-actions .btn-primary { display: none; }
  /* Desktop relies on `.nav-links { margin: 0 auto }` to push the
     actions to the far right. With .nav-links hidden on tablet /
     mobile that margin disappears, so the actions collapse next to
     the logo. Re-anchor them to the right edge so the hamburger
     sits flush with the container. */
  .nav-actions { margin-left: auto; }
}

@media (max-width: 480px) {
  .nav-inner { gap: 16px; }
  .nav-actions { gap: 12px; }
  .nav-actions .btn-ghost { display: none; }
}

.nav-sheet {
  position: fixed;
  inset: 0;
  background: var(--hueso);
  z-index: 200;
  display: flex;
  flex-direction: column;
  padding: 88px 24px 32px;
  transform: translateY(-100%);
  /* `visibility` is what actually closes this sheet. Sliding it out
     of view with `transform` alone left its six links, two buttons
     and the close control in the tab order on EVERY page at EVERY
     viewport — keyboard users tabbed into an invisible dialog, and
     `role="dialog" aria-modal="true"` told screen readers a modal
     was permanently open. `visibility: hidden` removes it from both
     the tab order and the accessibility tree; the 0s delay equal to
     the slide duration keeps the close animation watchable instead
     of snapping the sheet away on the first frame. */
  visibility: hidden;
  transition: transform var(--dur-slow) var(--ease-decelerate),
              visibility 0s linear var(--dur-slow);
  overflow-y: auto;
}
.nav-sheet.nav-sheet--open {
  transform: translateY(0);
  visibility: visible;
  transition: transform var(--dur-slow) var(--ease-decelerate),
              visibility 0s;
}
@media (prefers-reduced-motion: reduce) {
  .nav-sheet { transition: visibility 0s; }
}
/* Direct-child selector only — the actions-row anchors inside
   .nav-sheet-actions are buttons, not menu links, and should NOT
   inherit the 22px / hairline-border list styling that would
   clobber their padding, colour, and add stray underlines. */
.nav-sheet > a {
  font-size: 22px;
  font-weight: 700;
  padding: 18px 0;
  border-bottom: 1px solid var(--hairline);
  color: var(--tinta-900);
}
.nav-sheet > a[aria-current="page"] { color: var(--verde-ink); box-shadow: inset 4px 0 0 var(--verde-700); padding-left: 12px; }
.nav-sheet > a:first-of-type { border-top: 1px solid var(--hairline); }
.nav-sheet-actions {
  margin-top: 32px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.nav-sheet-actions .btn { justify-content: center; width: 100%; padding: 16px 24px; }

/* ---------- Hero ---------- */

.hero {
  position: relative;
  padding: 128px 0 96px;
  /* The hero has its own bloom pass laid on top of the body's
     aurora — a tight verde halo under the copy, a soft oro
     behind the device, a cream wash down the middle. These read
     as "the product sits in a lit room" rather than "floats on
     a gradient." Alpha stays ≤ 18% so the body copy never loses
     contrast. */
  isolation: isolate;
}
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  background:
    radial-gradient(68vw 42vw at 18% 32%, rgba(0, 166, 81, 0.14) 0%, transparent 60%),
    radial-gradient(54vw 42vw at 80% 56%, rgba(111, 207, 151, 0.14) 0%, transparent 62%),
    radial-gradient(40vw 32vw at 48% 12%, rgba(250, 254, 251, 0.5) 0%, transparent 65%);
  mask-image: linear-gradient(180deg, #000 0%, #000 85%, transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 85%, transparent 100%);
}
.hero-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 24px;
  align-items: start;
  position: relative;
}
.hero-copy { grid-column: span 7; }
.hero-art { grid-column: span 5; position: relative; }
.hero h1 {
  margin-top: 24px;
  color: var(--tinta-900);
  /* A whisper-fine text-shadow anchors the display type over the
     translucent aurora without making it look "shadowed". Only
     kicks in where the bloom brightens the surface under big type. */
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
}
.hero h1 .brand-dot { color: var(--brand-dot); }
.hero-sub {
  margin-top: 24px;
  color: var(--tinta-500);
  max-width: 480px;
}
.hero-ctas { margin-top: 32px; display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
/* Hero store badges — glass pill. Clear variant so the hero art
   behind them still reads (when present), with a hair-thin warm
   stroke so the edges define against the hueso canvas. */
.store-badge {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  padding: 10px 18px;
  border-radius: var(--glass-radius-pill);
  border: 1px solid var(--glass-edge-stroke-warm);
  background: rgba(255, 255, 255, 0.55);
  -webkit-backdrop-filter:
    blur(var(--glass-blur-sm)) saturate(var(--glass-saturate)) brightness(var(--glass-brightness));
  backdrop-filter:
    blur(var(--glass-blur-sm)) saturate(var(--glass-saturate)) brightness(var(--glass-brightness));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    inset 0 -1px 0 rgba(0, 0, 0, 0.04),
    var(--glass-shadow-sm);
  font-size: 11px;
  line-height: 1.2;
  color: var(--tinta-500);
  font-weight: 600;
  min-width: 160px;
  transition: transform var(--dur-fast) var(--ease-emphasized),
              box-shadow var(--dur-fast) var(--ease-standard),
              background var(--dur-fast) var(--ease-standard);
}
.store-badge:hover {
  transform: translateY(-1px);
  background: rgba(255, 255, 255, 0.72);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.8),
    inset 0 -1px 0 rgba(0, 0, 0, 0.04),
    var(--glass-shadow-md);
}
/* Phone mockup composition — three independent motion channels,
   each on a separate element so they don't fight over `transform`:
   ──────────────────────────────────────────────────────────────
     1. `.hero-device-wrap`  — perspective parent; owns the ambient
                               float (translateY, pre-existing).
     2. `.hero-device`       — owns the cursor-tracking tilt
                               (rotateY/rotateX via CSS custom props
                               `--tilt-x` / `--tilt-y`, written by JS).
     3. `[data-balance]` / `[data-contact]` — per-element SVG
                               animations (balance pulse, staggered
                               avatar heartbeat) that compose over
                               the top without touching the parent
                               transform stack.

   This separation is deliberate: putting float + tilt on the SAME
   element would require both to write to `transform`, causing one
   to clobber the other. Split across parent → child, they multiply
   cleanly.                                                       */
.hero-device-wrap {
  /* Transparent to layout — just an inline-block that gives the
     child SVG a perspective context + a non-transform-y animation
     channel so the float doesn't fight the tilt. Inherits sizing
     from its parent `.hero-art` grid column. */
  display: block;
  perspective: 1200px;
  transform-style: preserve-3d;
  position: relative;
  animation: hero-float 6s ease-in-out infinite;
  will-change: transform;
}
.hero-device {
  /* `--tilt-x` / `--tilt-y` are written by `scripts/main.js` from
     pointermove. Both default to 0 so the phone is neutral at rest.
     The 320ms transition smooths abrupt input (pointer entering /
     leaving the hero) without feeling laggy during continuous move. */
  --tilt-x: 0deg;
  --tilt-y: 0deg;
  width: 100%;
  max-width: 520px;
  margin-left: auto;
  transform: rotateY(var(--tilt-x)) rotateX(var(--tilt-y));
  transform-style: preserve-3d;
  transition: transform 320ms cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
@keyframes hero-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-6px); }
}
@keyframes saldo-pulse {
  0%, 100% {
    opacity: 1;
    filter: drop-shadow(0 0 0 rgba(0, 214, 50, 0));
  }
  45% {
    opacity: 1;
    filter: drop-shadow(0 0 4px rgba(0, 214, 50, 0.35));
  }
}
@keyframes contact-heartbeat {
  0%, 18%, 100% {
    transform: scale(1);
    filter: brightness(1);
  }
  8% {
    transform: scale(1.08);
    filter: brightness(1.12);
  }
}

/* Reduced motion: strip ALL phone animations. The phone becomes a
   static image — still readable, still beautiful, just silent. */
@media (prefers-reduced-motion: reduce) {
  .hero-device,
  .hero-device [data-balance],
  .hero-device [data-contact] {
    animation: none !important;
    transform: none !important;
  }
}
/* Add a deep ground-shadow under the device so it has weight.
   Sits on the device-wrap so it tracks the float animation. */
.hero-device-wrap::after {
  content: "";
  position: absolute;
  left: 12%;
  right: 12%;
  bottom: -28px;
  height: 48px;
  background: radial-gradient(ellipse at center, rgba(0, 61, 38, 0.32) 0%, transparent 70%);
  filter: blur(18px);
  pointer-events: none;
  z-index: -1;
  opacity: 0.8;
}
@media (prefers-reduced-transparency: reduce) {
  .hero::before { display: none; }
}

@media (max-width: 1023px) {
  .hero-copy, .hero-art { grid-column: span 12; }
  .hero-device { max-width: 340px; margin: 0 auto; }
}

@media (max-width: 768px) {
  .hero { padding: 88px 0 32px; min-height: 100svh; }
  .hero h1 { margin-top: 16px; }
  .hero-sub { margin-top: 20px; }
  .hero-ctas { margin-top: 24px; gap: 12px; }
  .hero-art { margin-top: 28px; height: auto; }
  .hero-device { max-width: 240px; height: auto; aspect-ratio: unset; }
}


/* ---------- Trust strip ---------- */

.trust {
  position: relative;
  background:
    radial-gradient(80vw 40vw at 50% 0%, rgba(0, 166, 81, 0.38) 0%, transparent 60%),
    radial-gradient(60vw 34vw at 10% 100%, rgba(111, 207, 151, 0.18) 0%, transparent 60%),
    linear-gradient(180deg, var(--verde-900) 0%, #002A1A 100%);
  color: var(--paper-fixed);
  overflow: hidden;
  isolation: isolate;
}
.trust::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 0.85 0 0 0 0.05 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 240px 240px;
  mix-blend-mode: overlay;
  opacity: 0.4;
  pointer-events: none;
  z-index: 0;
}
.trust > * { position: relative; z-index: 1; }
/* Legacy `.trust` override (solid verde-900) — retired. */
.trust-inner {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  padding: clamp(40px, 4vw, 56px) 0;
}
.trust-value {
  color: var(--paper-fixed);
  margin-bottom: 6px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.24);
}
.trust-label {
  color: rgba(250, 247, 242, 0.78);
  font-size: var(--fs-caption);
  letter-spacing: 0.02em;
  font-weight: 500;
}
@media (max-width: 768px) {
  .trust-inner { grid-template-columns: repeat(2, 1fr); gap: 32px; }
}

/* ---------- Generic section header ---------- */

.section-head {
  text-align: center;
  margin-bottom: var(--space-section-head);
  max-width: 760px;
  margin-left: auto;
  margin-right: auto;
  padding: 0 24px;
}
.section-head .eyebrow {
  /* Inline chips already float; just center + add a little breathing
     room above the big title. */
  margin-bottom: 20px;
}
.section-head h2 {
  color: var(--tinta-900);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.32);
}
.section-head .kicker {
  max-width: 560px;
  margin: 18px auto 0;
  color: var(--tinta-500);
  line-height: 1.55;
}


/* ---------- How it works (home page mini-version) ---------- */

.how {
  position: relative;
  background: transparent;
}
.how::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  background:
    radial-gradient(60vw 40vw at 50% 50%, rgba(111, 207, 151, 0.08) 0%, transparent 60%),
    radial-gradient(60vw 40vw at 0% 50%, rgba(0, 166, 81, 0.06) 0%, transparent 60%);
}
.how-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}
.how-num {
  font-size: var(--fs-display-m);
  font-weight: 800;
  color: transparent;
  background: linear-gradient(135deg, var(--verde-500) 0%, var(--verde-900) 100%);
  -webkit-background-clip: text;
          background-clip: text;
  line-height: 1;
  letter-spacing: -0.02em;
  margin-bottom: 4px;
  filter: drop-shadow(0 2px 8px rgba(0, 104, 71, 0.18));
}
.how-title { color: var(--tinta-900); }
.how-body { color: var(--tinta-500); }
.how-device {
  width: 120px;
  aspect-ratio: 9 / 18;
  margin-top: 8px;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--tinta-300);
}
.how-cta { text-align: center; margin-top: 56px; }

@media (max-width: 768px) {
  .how-grid { grid-template-columns: 1fr; }
}

/* ---------- App showcase ----------
   The `.showcase-device` rules that lived here styled hand-drawn SVG
   approximations of the app. Those are gone: the showcase now renders
   the real captures inside `.device-frame`, further down this file. */


/* ---------- Pricing ----------
   The "$0. In every column that matters" section. A verde stage
   with a gold spotlight bloom behind the headline, glass pill
   surrounds on the $0 columns, and a subtle paper grain to keep
   the gradient from reading as flat CSS.                           */

.pricing {
  position: relative;
  background:
    radial-gradient(85vw 55vw at 50% -14%, rgba(0, 214, 104, 0.28) 0%, transparent 58%),
    radial-gradient(70vw 48vw at 20% 100%, rgba(111, 207, 151, 0.22) 0%, transparent 62%),
    radial-gradient(70vw 48vw at 80% 100%, rgba(0, 132, 85, 0.24) 0%, transparent 60%),
    linear-gradient(180deg, var(--verde-900) 0%, var(--verde-700) 40%, #00583C 100%);
  color: var(--paper-fixed);
  text-align: center;
  overflow: hidden;
  isolation: isolate;
}
/* Paper grain for the pricing ground — same recipe as cta-final::before. */
.pricing::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 0.85 0 0 0 0.05 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 240px 240px;
  mix-blend-mode: overlay;
  opacity: 0.5;
  pointer-events: none;
  z-index: 0;
}
/* Gold spotlight bloom centered behind the headline — mirrors the
   cta-final gold spotlight so the page's aurora language carries. */
.pricing::after {
  content: "";
  position: absolute;
  top: -10%;
  left: 50%;
  transform: translateX(-50%);
  width: 68vw;
  max-width: 720px;
  aspect-ratio: 1;
  background: radial-gradient(
    circle,
    rgba(111, 207, 151, 0.32) 0%,
    rgba(111, 207, 151, 0.14) 35%,
    transparent 72%
  );
  pointer-events: none;
  z-index: 0;
  filter: blur(1px);
}
.pricing > * { position: relative; z-index: 1; }
.pricing .eyebrow { color: var(--verde-300); }
.pricing .eyebrow::before { background: var(--verde-300); box-shadow: 0 0 0 3px rgba(111, 207, 151, 0.22); }
.pricing h2 {
  color: var(--paper-fixed);
  margin-top: 16px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.pricing-amount {
  font-size: var(--fs-display-l);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.025em;
  color: var(--paper-fixed);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.22);
}
.pricing-label {
  margin-top: 10px;
  color: rgba(250, 247, 242, 0.82);
  font-size: var(--fs-body-l);
  font-weight: 500;
}
.pricing-note {
  margin-top: 48px;
  max-width: 620px;
  margin-left: auto;
  margin-right: auto;
  font-size: var(--fs-caption);
  color: rgba(230, 244, 236, 0.72);
  line-height: 1.6;
}


/* ---------- Security ---------- */

.security {
  position: relative;
  background: transparent;
}
.security-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 64px;
  align-items: start;
}
.security-body {
  margin-top: 20px;
  color: var(--tinta-500);
  max-width: 560px;
}
.security-badges {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  /* `minmax(0, 1fr)` so the tracks may shrink below their intrinsic
     content width. Without this, the longest badge label (e.g. the
     "AES-256-GCM" run, which the browser treats as one unbreakable
     token) sets a minimum track width, and on narrow viewports the
     grid spills past the container — which on the /en/ homepage
     was dragging 2–3px of horizontal overflow into the document. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 520px) {
  /* Stack badges on a phone. Two-wide at 375–390px cramped the text
     to ~145px/track once icon + padding + gap were accounted for;
     the resulting two-line clamp looked like a layout bug rather
     than a deliberate card. */
  .security-badges { grid-template-columns: 1fr; }
}
.security-badge-icon {
  color: var(--paper-fixed);
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  padding: 6px;
  border-radius: 10px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.42) 0%, rgba(255, 255, 255, 0) 58%),
    linear-gradient(135deg, var(--verde-500) 0%, var(--verde-700) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.38),
    inset 0 -1px 0 rgba(0, 0, 0, 0.16),
    0 4px 12px -2px rgba(0, 104, 71, 0.32);
}
.security-badge-text {
  font-size: var(--fs-body);
  color: var(--tinta-900);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: -0.003em;
}

@media (max-width: 1023px) {
  .security-grid { grid-template-columns: 1fr; gap: 40px; }
}


/* ---------- FAQ ---------- */

.faq-list {
  margin-top: 48px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* Each FAQ is a glass pill. Closed: a tight glassy row. Open:
   expands, switches the border stroke to an oro tint, and picks
   up a warm inner highlight so the answer panel reads as lit
   from within. */
.faq-item {
  position: relative;
  background: var(--glass-surface-luminous);
  -webkit-backdrop-filter: blur(var(--glass-blur-md)) saturate(var(--glass-saturate)) brightness(var(--glass-brightness));
  backdrop-filter: blur(var(--glass-blur-md)) saturate(var(--glass-saturate)) brightness(var(--glass-brightness));
  border: 1px solid var(--glass-edge-luminous);
  border-radius: var(--glass-radius-sm);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.72),
    inset 0 -1px 0 rgba(0, 0, 0, 0.03),
    var(--glass-shadow-sm);
  overflow: hidden;
  transition:
    background var(--dur-base) var(--ease-standard),
    box-shadow var(--dur-base) var(--ease-standard),
    border-color var(--dur-base) var(--ease-standard),
    transform var(--dur-base) var(--ease-emphasized);
}
.faq-item:hover {
  transform: translateY(-1px);
  background: var(--glass-surface-luminous-strong);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.82),
    inset 0 -1px 0 rgba(0, 0, 0, 0.03),
    var(--glass-shadow-md),
    var(--halo-verde-sm);
}
.faq-item[open] {
  background: rgba(250, 254, 251, 0.88);
  border-color: rgba(111, 207, 151, 0.42);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.85),
    inset 0 -1px 0 rgba(0, 0, 0, 0.04),
    0 1px 2px rgba(0, 0, 0, 0.05),
    0 16px 36px -10px rgba(11, 11, 11, 0.12),
    var(--halo-verde-sm);
}
.faq-item[open]:hover {
  transform: none;
}
.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 24px;
  font-size: var(--fs-body-l);
  font-weight: 600;
  color: var(--tinta-900);
  cursor: pointer;
  list-style: none;
  letter-spacing: -0.005em;
}
.faq-item summary::-webkit-details-marker { display: none; }
@media (hover: hover) {
  .faq-item summary:hover { color: var(--verde-900); }
}
/* `summary` is a flex row, so EVERY inline child becomes its own flex
   item and both the 16px gap and `space-between` land between them. A
   question carrying region-swapped figures rendered as
   "Why 1% with a    $1    USD    cap?" for that reason. `.faq-q` keeps
   the question one item; wrap any summary whose text contains markup. */
.faq-item summary .faq-q { min-width: 0; }
.faq-chev {
  flex-shrink: 0;
  transition: transform var(--dur-slow) var(--ease-emphasized);
  color: var(--tinta-500);
}
.faq-item[open] .faq-chev { transform: rotate(180deg); color: var(--verde-700); }
.faq-answer {
  padding: 0 24px 22px;
  color: var(--tinta-500);
  line-height: 1.65;
  animation: faq-fade var(--dur-slow) var(--ease-emphasized);
}
@keyframes faq-fade {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ---------- FAQ hub (/preguntas, /en/faq) ----------

   Editorial-directory layout. The old hub squeezed three white boxes
   into a 640px `.container.reading` column, creating a 2+1 orphan
   grid and reading like a generic admin shell rather than Pesito
   chrome. The new hub treats the page as a book's table of contents:

     1. A wide `.container-inner` (1040px) so the three category
        cards have room to breathe side-by-side on desktop.
     2. Big display-font tabular numerals as chapter markers, inked
        in a verde-700 → oro gradient so each card feels stamped by
        the brand rather than numbered by a spreadsheet.
     3. Three glass cards (the same material family as `.pillar` and
        `.scenario`) with hover lift + oro hairline + arrow slide.
     4. An ambient verde aurora bleeding in from the top-right,
        mirroring `.how-hero`, so the two "guide" pages read as one
        chapter family.
     5. Most-asked list as a quiet companion strip beneath a single
        gold capsule ornament — no bolted-on oro-bordered box.
   ═══════════════════════════════════════════════════════════════ */

.faq-hub {
  padding: clamp(96px, 9vw, 128px) 0 clamp(64px, 7vw, 96px);
  background:
    radial-gradient(
      1200px 600px at 100% -10%,
      rgba(0, 166, 81, 0.10) 0%,
      transparent 60%
    ),
    var(--hueso);
}

/* Hero block — breadcrumb, eyebrow, h1, lede. Width-capped so the
   display title wraps at a readable 2-3 lines rather than stretching
   across the 1040px container into one long river. */
.faq-hub-head {
  max-width: 720px;
  margin-bottom: clamp(48px, 5vw, 72px);
}

.faq-hub-crumb {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Standalone back-control, not a link in running prose, so it
     carries the 44pt target. Padding rather than min-height alone,
     so the hit area extends past the short "Volver" label. */
  min-height: 44px;
  padding: 4px 8px;
  margin-inline-start: -8px;
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--tinta-500);
  margin-bottom: 24px;
  transition: color var(--dur-fast) var(--ease-standard);
}
.faq-hub-crumb:hover { color: var(--verde-700); }
.faq-hub-crumb svg {
  transition: transform var(--dur-fast) var(--ease-emphasized);
}
/* Arrow slides LEFT on hover — signalling "going back". Inverse of
   the forward-arrow pattern used on primary CTAs. */
.faq-hub-crumb:hover svg { transform: translateX(-2px); }

.faq-hub-title {
  margin-top: 16px;
  color: var(--tinta-900);
  max-width: 20ch;
  text-wrap: balance;
}
.faq-hub-lede {
  margin-top: 24px;
  color: var(--tinta-700);
  max-width: 58ch;
}
.faq-hub-mail {
  color: var(--accent-verde);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.faq-hub-mail:hover { color: var(--accent-verde-strong); }

/* Category grid — explicit 3-col so rhythm holds. `auto-fit` would
   drop to 2+1 on sub-1040px widths, which is the exact "weird
   orphan" bug the old layout had. Collapse to 1-col at tablet. */
.faq-hub-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
@media (max-width: 960px) {
  .faq-hub-grid { grid-template-columns: 1fr; gap: 16px; }
}


/* Most-asked strip — a second table of contents for visitors who
   know what they want to find. Anchored by a single oro capsule
   ornament at the top-left of the divider rule, which matches the
   `.eyebrow::before` hairline language elsewhere on the site. */
.faq-hub-most {
  margin-top: clamp(64px, 7vw, 96px);
  padding-top: clamp(36px, 3.5vw, 48px);
  border-top: 1px solid rgba(111, 207, 151, 0.35);
  position: relative;
}
.faq-hub-most::before {
  content: "";
  position: absolute;
  top: -5px;
  left: 0;
  width: 48px;
  height: 9px;
  background: var(--verde-500);
  border-radius: 9999px;
  box-shadow: 0 0 0 4px rgba(111, 207, 151, 0.14);
}
.faq-hub-most-head {
  max-width: 560px;
  margin-bottom: 32px;
}
.faq-hub-most-title {
  margin-top: 12px;
  font-size: clamp(22px, 2vw, 28px);
  font-weight: 700;
  color: var(--tinta-900);
  letter-spacing: -0.01em;
  text-wrap: balance;
}
.faq-hub-most-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0 40px;
  list-style: none;
  padding: 0;
}
@media (max-width: 720px) {
  .faq-hub-most-grid { grid-template-columns: 1fr; gap: 0; }
}
.faq-hub-most-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 4px;
  border-bottom: 1px solid var(--hairline);
  color: var(--tinta-900);
  font-weight: 600;
  font-size: var(--fs-body);
  text-decoration: none;
  transition:
    color var(--dur-fast) var(--ease-standard),
    border-color var(--dur-fast) var(--ease-standard),
    padding-left var(--dur-fast) var(--ease-emphasized);
}
/* Hanging arrow — tinta-300 at rest, verde on hover. Slides 4px
   right on hover while the item itself indents 8px left-padding,
   creating a subtle pull-toward-you effect. */
.faq-hub-most-item::after {
  content: "→";
  flex-shrink: 0;
  color: var(--tinta-300);
  font-weight: 400;
  font-size: 18px;
  transition:
    color var(--dur-fast) var(--ease-standard),
    transform var(--dur-fast) var(--ease-emphasized);
}
.faq-hub-most-item:hover {
  color: var(--verde-900);
  border-color: var(--verde-500);
  padding-left: 8px;
}
.faq-hub-most-item:hover::after {
  color: var(--verde-500);
  transform: translateX(4px);
}
.faq-hub-most-item:focus-visible {
  outline: 2px solid var(--verde-500);
  outline-offset: 2px;
  border-radius: 4px;
}


.cta-final {
  position: relative;
  background:
    radial-gradient(90vw 60vw at 50% -8%, rgba(0, 214, 104, 0.28) 0%, transparent 60%),
    radial-gradient(70vw 48vw at 14% 100%, rgba(111, 207, 151, 0.22) 0%, transparent 62%),
    radial-gradient(70vw 48vw at 86% 110%, rgba(0, 132, 85, 0.28) 0%, transparent 62%),
    linear-gradient(180deg, #005E3E 0%, var(--verde-700) 48%, var(--verde-900) 100%);
  color: var(--paper-fixed);
  padding: clamp(88px, 10vw, 140px) 0 clamp(72px, 8.5vw, 120px);
  overflow: hidden;
  isolation: isolate;
}

.cta-final::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 0.85 0 0 0 0.06 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 240px 240px;
  opacity: 0.45;
  mix-blend-mode: overlay;
  pointer-events: none;
  z-index: 0;
}
/* Gold spotlight behind the headline — bigger, softer, more
   dimensional than v1. Two stacked gradients (warm oro core + a
   wider cream halo) so the bloom reads as a real projector rather
   than a flat gradient. */
.cta-final::after {
  content: "";
  position: absolute;
  top: -14%;
  left: 50%;
  width: 78vw;
  max-width: 880px;
  aspect-ratio: 1;
  transform: translateX(-50%);
  background:
    radial-gradient(circle at 50% 50%, rgba(111, 207, 151, 0.22) 0%, transparent 55%),
    radial-gradient(circle at 50% 52%, rgba(111, 207, 151, 0.34) 0%, rgba(111, 207, 151, 0.14) 32%, transparent 70%);
  pointer-events: none;
  z-index: 0;
  filter: blur(2px);
  animation: cta-spot-breathe 12s ease-in-out infinite;
}
@keyframes cta-spot-breathe {
  0%, 100% { opacity: 0.9;  transform: translateX(-50%) scale(1); }
  50%      { opacity: 1;    transform: translateX(-50%) scale(1.04); }
}
@media (prefers-reduced-motion: reduce) {
  .cta-final::after { filter: none; animation: none; }
}



.cta-final h2 {
  color: var(--paper-fixed);
  margin-top: 20px;
  max-width: 16ch;
  margin-left: auto;
  margin-right: auto;
}





/* Legacy waitlist form rules kept for /lista-espera dedicated page. */
.cta-final .waitlist-error {
  margin-top: 10px;
  padding: 10px 14px;
  border-radius: var(--radius-md);
  background: var(--rojo-050);
  color: var(--rojo-700);
  font-size: var(--fs-body-s);
  font-weight: 600;
  line-height: 1.45;
  border: 1px solid var(--rojo-200);
  text-align: left;
}
.cta-final .waitlist.is-error .waitlist-row {
  border-color: var(--rojo-500);
  box-shadow: 0 0 0 4px rgba(206, 17, 38, 0.12);
}
.cta-final .waitlist-success {
  display: none;
  margin-top: 14px;
  padding: 12px 16px;
  border-radius: var(--radius-md);
  background: var(--verde-100);
  color: var(--verde-900);
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.45;
  border: 1px solid rgba(0, 104, 71, 0.15);
  text-align: left;
}
.cta-final .waitlist.is-submitting .waitlist-submit { opacity: 0.6; cursor: wait; }
.cta-final .waitlist.is-success .waitlist-row,
.cta-final .waitlist.is-success .form-consent { display: none; }
/* `~` rather than `+` — a `.waitlist-error` element may sit between the
   form and the success banner, so adjacent-sibling would miss. */
.cta-final .waitlist.is-success ~ .waitlist-success { display: block; }

/* ----- Sign-in half (right) ----- */


.finale-signin-cta {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 22px;
  padding: 14px 18px 14px 22px;
  border-radius: var(--glass-radius-pill);
  /* Dark glass pill — tint-900 with a 60% layer and a saturate-2x
     backdrop so the verde behind the card modulates through. A 1px
     top-highlight makes it read as a "lit" jet-black surface, not
     flat paint. */
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 50%),
    linear-gradient(180deg, #0e0e11 0%, #050506 100%);
  color: var(--hueso);
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: -0.005em;
  align-self: flex-start;
  border: 1px solid rgba(255, 255, 255, 0.08);
  transition: transform var(--dur-fast) var(--ease-emphasized),
              background var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    inset 0 -1px 0 rgba(0, 0, 0, 0.4),
    0 1px 2px rgba(0, 0, 0, 0.1),
    0 10px 24px -6px rgba(0, 0, 0, 0.45);
}
.finale-signin-cta:hover {
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.28),
    inset 0 -1px 0 rgba(0, 0, 0, 0.4),
    0 1px 3px rgba(0, 0, 0, 0.12),
    0 18px 32px -10px rgba(0, 0, 0, 0.55);
}
.finale-signin-cta:active { transform: scale(0.98); }


/* ----- Store badges row (under the card) ----- */

.finale-store {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 10px 20px 10px 16px;
  border-radius: var(--glass-radius-pill);
  background: rgba(11, 11, 11, 0.32);
  -webkit-backdrop-filter:
    blur(var(--glass-blur-md)) saturate(var(--glass-saturate)) brightness(var(--glass-brightness));
  backdrop-filter:
    blur(var(--glass-blur-md)) saturate(var(--glass-saturate)) brightness(var(--glass-brightness));
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 -1px 0 rgba(0, 0, 0, 0.2),
    0 1px 2px rgba(0, 0, 0, 0.12),
    0 10px 24px -8px rgba(0, 0, 0, 0.35);
  color: var(--hueso);
  transition: transform var(--dur-fast) var(--ease-emphasized),
              background var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard);
  min-width: 172px;
}
.finale-store:hover {
  transform: translateY(-1px);
  background: rgba(11, 11, 11, 0.44);
  border-color: rgba(255, 255, 255, 0.22);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.26),
    inset 0 -1px 0 rgba(0, 0, 0, 0.24),
    0 1px 3px rgba(0, 0, 0, 0.14),
    0 18px 32px -10px rgba(0, 0, 0, 0.45);
}


/* ----- Responsive collapse ----- */

@media (max-width: 860px) {
  .finale-signin-cta { width: 100%; justify-content: space-between; }
}
@media (max-width: 520px) {
  .finale-store { flex: 1 1 0; min-width: 0; padding: 10px 14px; justify-content: center; }
}

/* ---------- Footer ---------- */

.footer {
  position: relative;
  background: #000;
  color: var(--hueso);
  padding: clamp(56px, 6.2vw, 88px) 0 clamp(32px, 3vw, 40px);
  overflow: hidden;
}
.footer::after { content: none; }
.footer::before { content: none; }
.footer > * { position: relative; z-index: 1; }
@media (max-width: 768px) {
  .footer-bottom { flex-direction: column; align-items: flex-start; }
}
.footer-cols {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 32px;
}
/* h3, not h4: the last heading before the footer is an h2 on every
   page, so an h4 here skipped a level and broke the document outline
   (WCAG 1.3.1). The visual treatment is unchanged — these are still the
   smallest labels on the page; only the rank they claim in the outline
   moved. `h4` stays in the selector for any page not yet re-synced. */
.footer-col :is(h3, h4) {
  font-size: var(--fs-caption);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(250, 247, 242, 0.6);
  margin-bottom: 16px;
}
.footer-col a {
  /* Footer links are the site's secondary navigation and on mobile
     they're a stack of flush-adjacent targets — the case the 44pt
     floor exists for. `display:block` + 6px padding gave 35.3px with
     no gap between neighbours, so a mistap landed on the wrong page.
     Flex + min-height grows the target without moving the label. */
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 6px 0;
  font-size: var(--fs-body);
  color: var(--hueso);
  transition: color var(--dur-fast) var(--ease-standard);
}
.footer-col a:hover { color: var(--verde-300); }
.footer-bottom {
  margin-top: clamp(40px, 4.5vw, 64px);
  padding-top: 32px;
  border-top: 1px solid rgba(250, 247, 242, 0.1);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}
.footer-mouseprint {
  margin-top: 24px;
  /* Legal copy is the text most likely to be read start-to-finish by
     someone who needs it, so it gets a measure like any other prose.
     Uncapped it ran past 140 characters on a desktop footer. */
  max-width: 96ch;
  font-size: var(--fs-caption);
  color: rgba(250, 247, 242, 0.5);
  line-height: 1.6;
}

@media (max-width: 1023px) {
  .footer-cols { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
  .footer-cols { grid-template-columns: 1fr; }
}

/* ---------- Wordmark (inline SVG-free, just type) ---------- */

.wordmark {
  display: inline-flex;
  align-items: baseline;
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--accent-verde);
}
.wordmark-dot {
  width: 6px;
  height: 6px;
  background: var(--brand-dot);
  border-radius: 50%;
  margin-left: 3px;
  align-self: flex-end;
  margin-bottom: 6px;
}
/* "On dark" means on a SATURATED dark ground (the legacy verde
   footer), not "in dark mode" — so it must not ride the neutral
   ramp, or it turns near-black on a near-black footer. */
.wordmark--on-dark { color: var(--paper-fixed); }

/* Brand mark: the app icon + Pesito wordmark lockup used in chrome
   (nav + footer). The icon has its own identity — bright grass-green
   gradient + peso-₱ glyph — so the heritage `--verde-700` of the
   wordmark needs to drop one step to avoid clashing with the brighter
   green beside it. That's what `.brand-mark-text` does. */
.brand-mark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* Home link in the nav: a control, so it carries the 44pt target.
     The 32px icon and the wordmark are unchanged — only the hit area
     grows, using the nav's existing 68px height. */
  min-height: 44px;
  text-decoration: none;
}
.brand-mark-icon {
  width: 32px;
  height: 32px;
  display: block;
  flex-shrink: 0;
  border-radius: 7px;
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.08),
    0 6px 14px rgba(0, 104, 71, 0.18);
}
/* Only the NAV's brand icon is a shared element across navigations.
   `view-transition-name` must be globally unique per document, so we
   intentionally scope this to `.nav .brand-mark-icon`. If you ever
   render a second brand-mark inside the page body, it'll simply not
   participate in the cross-document morph (still works visually). */
.nav .brand-mark-icon {
  view-transition-name: brand-icon;
}
.brand-mark-icon--sm {
  width: 26px;
  height: 26px;
  border-radius: 6px;
}
.brand-mark-text {
  font-weight: 800;
  letter-spacing: -0.025em;
  color: var(--tinta-900);
}
.nav .brand-mark-text {
  view-transition-name: brand-text;
}
.brand-mark--on-dark .brand-mark-text { color: var(--hueso); }
@media (max-width: 480px) {
  .brand-mark-icon { width: 28px; height: 28px; border-radius: 6px; }
}

/* ---------- Reveal on scroll ----------
   Implemented as CSS scroll-driven animations in the motion layer at
   the end of this file (search "MOTION LAYER"). Content is visible by
   default — the old JS-gated `.is-revealed` transition shipped blank
   sections in headless renderers and is retired. main.js still adds
   the class; it is now a no-op. */

/* ---------- Legal documents (privacidad, terminos, en/privacy, en/terms) ----
   Shared typography + callout vocabulary so legal pages don't each ship
   their own inline `<style>` block. Typography follows the same ladder
   the rest of the site uses (`--fs-*`), but with a tighter column
   (`--reading-max`) and a slightly wider line-height for long-form
   reading. The callout variants map to the three content types legal
   pages need: neutral placeholder, ARCO / rights box, and transfers
   table chrome.

   Anchor: `<main class="legal-main"><article class="legal-body">…`.
   ------------------------------------------------------------------ */

.legal-main {
  padding-top: clamp(96px, 9vw, 128px);
  padding-bottom: clamp(64px, 8.5vw, 120px);
}

.legal-body h2 {
  margin-top: 48px;
  margin-bottom: 12px;
  color: var(--tinta-900);
  font-size: var(--fs-headline);
}
.legal-body h3 {
  margin-top: 32px;
  margin-bottom: 8px;
  color: var(--tinta-700);
  font-size: var(--fs-body-l);
  font-weight: 600;
}
.legal-body p,
.legal-body li {
  color: var(--tinta-500);
  font-size: var(--fs-body-l);
  line-height: 1.7;
  margin-top: 12px;
}
.legal-body ul,
.legal-body ol { padding-left: 24px; margin-top: 12px; }
.legal-body a {
  color: var(--accent-verde);
  text-decoration: underline;
}
.legal-body .legal-effective {
  color: var(--tinta-400);
  margin-top: 8px;
}

/* Neutral callout: "review required", TODOs, draft notices. */
.legal-placeholder {
  margin-top: 24px;
  padding: 16px 20px;
  border: 1px solid var(--tinta-300);
  background: var(--hueso-200);
  color: var(--tinta-500);
  font-size: var(--fs-body);
  border-radius: var(--radius-sm);
}

/* Red "important" callout — legal notice, binding clause. Semantic
   red: full border + tint, never a side stripe. */
.legal-notice {
  margin-top: 24px;
  padding: 16px 20px;
  border: 1px solid var(--rojo-200);
  background: var(--rojo-050);
  color: var(--tinta-700);
  font-size: var(--fs-body);
  border-radius: var(--radius-sm);
}


@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .hero-device { animation: none; }
  [data-reveal] { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------------
   FX calculator (en-US hero right column + SEO landing pages)
   --------------------------------------------------------------- */

.fx-card {
  background: var(--blanco);
  border: 1px solid var(--hairline);
  border-radius: 24px;
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.04),
    0 24px 48px -24px rgba(0, 60, 30, 0.18);
  padding: 28px;
  max-width: 480px;
  width: 100%;
  /* The card lives in a half-width grid column on the landing pages, so
     its own width, not the viewport's, decides whether a four-column
     table fits. Everything inside it sizes against this container. */
  container: fx-card / inline-size;
}
.fx-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
/* The funding note and the recipient select side by side inside a
   ~450px card leave each about 215px, which clips option labels
   mid-word. A <select> cannot ellipsize gracefully, so it gets the
   full width. */
@container fx-card (max-width: 560px) {
  .fx-form { grid-template-columns: 1fr; }
}
.fx-field {
  display: block;
}
.fx-field:first-child {
  grid-column: 1 / -1;
}
.fx-label {
  display: block;
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--tinta-500);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 6px;
}
.fx-input-row {
  display: flex;
  align-items: center;
  background: var(--hueso);
  border: 1px solid var(--hairline-strong);
  border-radius: 12px;
  padding: 10px 14px;
  transition: border-color 160ms ease, box-shadow 160ms ease;
  cursor: text;
}
.fx-input-row:focus-within {
  border-color: var(--verde-700);
  box-shadow: 0 0 0 3px rgba(0, 166, 81, 0.18);
}
.fx-prefix {
  font-weight: 600;
  color: var(--tinta-500);
  margin-right: 6px;
}
.fx-input-row input {
  flex: 1;
  border: none;
  background: transparent;
  font: inherit;
  font-size: 24px;
  font-weight: 700;
  color: var(--tinta-900);
  outline: none;
  width: 100%;
  -moz-appearance: textfield;
}
.fx-input-row input::-webkit-outer-spin-button,
.fx-input-row input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.fx-field select {
  width: 100%;
  background: var(--hueso);
  border: 1px solid var(--hairline-strong);
  border-radius: 12px;
  padding: 12px 14px;
  font: inherit;
  font-size: var(--fs-body);
  font-weight: 600;
  color: var(--tinta-900);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23737373' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 36px;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.fx-field select:focus {
  outline: none;
  border-color: var(--verde-700);
  box-shadow: 0 0 0 3px rgba(0, 166, 81, 0.18);
}
.fx-hint {
  display: block;
  font-size: var(--fs-caption);
  color: var(--tinta-500);
  margin-top: 6px;
  line-height: 1.4;
}
.fx-result {
  margin-top: 24px;
  padding: 16px 18px;
  background: linear-gradient(180deg, var(--hueso) 0%, var(--blanco) 100%);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  display: grid;
  gap: 8px;
}
/* Phone: the result box sits inside a carta inside a panel, three
   paddings deep; the notes in it get the width back. */
@media (max-width: 640px) {
  .fx-result { padding: 12px; }
}
.fx-result-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: var(--fs-body-s);
  color: var(--tinta-500);
}
.fx-result-row .fx-result-val { color: var(--tinta-900); font-weight: 600; }
.fx-result-row--total {
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--hairline);
  font-size: var(--fs-body);
}
.fx-result-row--total .fx-result-key { color: var(--tinta-700); font-weight: 600; }
.fx-result-row--total .fx-result-val {
  font-size: 22px;
  color: var(--accent-verde);
  font-weight: 800;
}
.fx-compare {
  margin-top: 24px;
  border-top: 1px solid rgba(0, 0, 0, 0.1);
  padding-top: 20px;
}
/* Mounted in its own section rather than inside the quote card, where
   the four columns finally have room. It names the same container as
   `.fx-card` so one set of rules governs the stacked fallback. */
.fx-compare-standalone {
  margin-top: 0;
  border-top: 0;
  padding-top: 0;
  container: fx-card / inline-size;
}
.fx-compare-standalone .fx-compare-table { font-size: var(--fs-body); }
.fx-compare-standalone .fx-compare-fee,
.fx-compare-standalone .fx-compare-eta { font-size: var(--fs-body-s); }
.fx-compare-standalone .fx-compare-row td { padding: 14px 8px; }
.fx-compare-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  /* The "more on Pesito" pill is a full sentence in Spanish and cannot
     shrink, so on a narrow card it has to be allowed onto its own line. */
  flex-wrap: wrap;
  gap: 8px;
  font-size: var(--fs-caption);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--tinta-500);
  margin-bottom: 12px;
}
.fx-compare-better {
  color: var(--accent-verde);
  background: rgba(0, 166, 81, 0.1);
  padding: 4px 10px;
  border-radius: 999px;
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-caption);
}
.fx-compare-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-body-s);
}
.fx-compare-row td {
  padding: 10px 0;
  border-bottom: 1px solid var(--hairline);
  vertical-align: middle;
}
.fx-compare-row:last-child td { border-bottom: none; }
/* Flat tint, not a gradient: with `border-collapse: collapse` a
   gradient repeats per cell, which reads as uneven stripes across the
   row once the table is wide. */
.fx-compare-row--self td { background: rgba(0, 166, 81, 0.08); }
.fx-compare-row--self td:first-child { border-radius: 8px 0 0 8px; }
.fx-compare-row--self td:last-child { border-radius: 0 8px 8px 0; }
.fx-compare-name {
  font-weight: 600;
  color: var(--tinta-900);
  padding-left: 8px !important;
  padding-right: 8px;
}
.fx-compare-method {
  display: block;
  font-weight: 500;
  color: var(--tinta-500);
  font-size: var(--fs-caption);
  margin-top: 2px;
}
.fx-compare-fee {
  color: var(--tinta-500);
  font-size: var(--fs-caption);
  padding: 0 8px !important;
}
.fx-compare-net {
  font-weight: 700;
  color: var(--tinta-900);
  text-align: right;
  white-space: nowrap;
  padding: 0 8px !important;
}
.fx-compare-row--self .fx-compare-net { color: var(--accent-verde); }
.fx-compare-eta {
  color: var(--tinta-500);
  font-size: var(--fs-caption);
  text-align: right;
  padding-right: 8px !important;
}
.fx-compare-foot {
  font-size: var(--fs-caption);
  color: var(--tinta-500);
  margin-top: 12px;
  line-height: 1.45;
}

/* Column headers. The table shipped without a <thead>, which made the
   four columns guessable-at-best on desktop and unlabelled once the
   layout collapses on mobile. */
.fx-compare-head-row th {
  padding: 0 8px 8px;
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tinta-500);
  border-bottom: 2px solid var(--hairline-strong);
  white-space: nowrap;
}
.fx-compare-head-row th:nth-child(3),
.fx-compare-head-row th:nth-child(4) { text-align: right; }

/* ---------- Narrow card: stop being a table ----------
   A four-column price comparison is unreadable in a container this
   size, and `overflow-x: auto` on the wrapper only hides the two
   columns that carry the argument (what it costs, what they receive)
   behind a sideways scroll most people never try.

   The trigger is the CARD's width, not the viewport's. On the landing
   pages this card sits in a half-width grid column, so a 1440px laptop
   gives it roughly 600px: wide enough to pass a viewport media query,
   far too narrow for four columns of prose.

   Each row becomes its own bordered card with the column name printed
   beside each value (from the `data-label` the renderer emits). The
   markup stays a real <table>, so it is still announced as tabular
   data and still copies out as a table. */
@container fx-card (max-width: 640px) {
  .fx-compare-head-row { display: none; }
  .fx-compare-table,
  .fx-compare-table tbody,
  .fx-compare-row,
  .fx-compare-row td { display: block; width: 100%; }
  .fx-compare-row {
    border: 2px solid var(--frame-ink);
    border-radius: var(--panel-radius-sm);
    padding: 14px 16px;
    margin-bottom: 10px;
    background: var(--blanco);
  }
  .fx-compare-row--self {
    background: var(--verde-100);
    box-shadow: var(--card-lift);
  }
  .fx-compare-row--self td { background: none; }
  .fx-compare-row td {
    padding: 6px 0 !important;
    border-bottom: 1px solid var(--hairline);
    text-align: left;
    white-space: normal;
  }
  .fx-compare-row td:last-child { border-bottom: none; }
  /* The provider name is the card's title, so it keeps no label. */
  .fx-compare-name {
    font-size: var(--fs-body);
    padding-bottom: 10px !important;
  }
  .fx-compare-row td[data-label]:not(.fx-compare-name) {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 4px 14px;
    /* Spanish labels plus a peso figure can exceed a 320px card, so the
       value drops to its own line rather than spilling out of it. */
    flex-wrap: wrap;
  }
  .fx-compare-row td[data-label]:not(.fx-compare-name)::before {
    content: attr(data-label);
    flex-shrink: 0;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--tinta-500);
  }
  /* The received amount is the number people actually compare. */
  .fx-compare-net { font-size: var(--fs-body-l); }
}
.fx-fallback {
  padding: 24px;
  background: var(--hueso);
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 12px;
  color: var(--tinta-500);
  font-size: var(--fs-body-s);
  text-align: center;
}

@media (max-width: 1023px) {
  .fx-card { max-width: 100%; padding: 24px; }
  .fx-form { grid-template-columns: 1fr; }
}
@media (max-width: 540px) {
  .fx-input-row input { font-size: 20px; }
}

/* Dropping the cost and speed columns to make the table fit was the old
   way to survive a narrow card. The stacked layout above shows every
   field with its own label, so the data stays. Engines without container
   queries never reach that layout, so they keep the old escape hatch. */
@supports not (container-type: inline-size) {
  @media (max-width: 1023px) { .fx-compare-eta { display: none; } }
  @media (max-width: 540px) { .fx-compare-fee { display: none; } }
}

/* ------------------------------------------------------------------
   Investor / team page chrome — sober, document-style cards.
   --------------------------------------------------------------- */

.investor-hero {
  padding: clamp(96px, 8.5vw, 120px) 0 clamp(40px, 4.5vw, 64px);
  background:
    radial-gradient(60vw 30vw at 50% 0%, rgba(0, 166, 81, 0.12) 0%, transparent 60%),
    var(--hueso);
}
.investor-stat-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  margin-top: 40px;
}
.investor-stat {
  background: var(--blanco);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  padding: 20px;
}
.investor-stat-value {
  font-size: 28px;
  font-weight: 800;
  color: var(--tinta-900);
  letter-spacing: -0.5px;
  margin-bottom: 4px;
}
.investor-stat-label {
  font-size: var(--fs-caption);
  color: var(--tinta-500);
  font-weight: 500;
  line-height: 1.4;
}
.investor-section {
  padding: clamp(44px, 4.5vw, 64px) 0;
  border-top: 1px solid var(--hairline);
}
.investor-section h2 { color: var(--tinta-900); margin-bottom: 12px; }
.investor-section .kicker { color: var(--tinta-500); margin-bottom: 32px; }

.team-card {
  background: var(--blanco);
  border: 1px solid var(--hairline-strong);
  border-radius: 20px;
  padding: 28px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.team-card .team-name { font-size: 20px; font-weight: 700; color: var(--tinta-900); }
.team-card .team-role { font-size: var(--fs-body-s); color: var(--accent-verde); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; }
.team-card .team-bio { font-size: var(--fs-body); color: var(--tinta-700); line-height: 1.6; }

/* Alma mater, sitting directly under the name. The card is a 12px-gap
   flex column, which reads as a separate field; the negative margin pulls
   the line back up so it hangs off the name the way a subtitle should.
   The cap is a mask rather than an <svg> or a coloured data URI, so a
   single rule covers four founders on five localised pages and the glyph
   inherits the themed text colour instead of pinning a fixed fill. */
.team-card .team-school {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: -6px;
  font-size: var(--fs-body-s);
  color: var(--tinta-500);
  line-height: 1.4;
}
.team-card .team-school::before {
  content: "";
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  background-color: currentColor;
  -webkit-mask: var(--icon-cap) center / contain no-repeat;
          mask: var(--icon-cap) center / contain no-repeat;
}

/* Profile variant: headshot beside the copy rather than above it.
   The founder cards live in a 640px `.reading` column, so a side-by-side
   grid pins them to their track minimum and the bios wrap to a ~28ch
   ribbon. Stacking the cards and laying each one out horizontally keeps
   the bio at a readable measure and stays aligned with the editorial
   column the rest of the page uses. It also degrades cleanly: a card
   with no headshot yet just gives the copy the full width instead of
   sitting lower than its neighbour. */
.team-card--profile {
  flex-direction: row;
  align-items: flex-start;
  gap: 20px;
}
.team-card--profile .team-card-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
/* The <picture> wrapper is the flex item, not the <img> inside it, so the
   sizing has to be pinned here or the headshot gets shrunk and squashed. */
.team-card--profile > picture {
  display: block;
  flex: 0 0 auto;
}
.team-card .team-photo {
  display: block;
  width: 128px;
  height: 128px;
  border-radius: 16px;
  border: 2px solid var(--hairline-strong);
  object-fit: cover;
  background: var(--hueso);
}

@media (max-width: 560px) {
  .team-card--profile { flex-direction: column; }
  .team-card .team-photo { width: 104px; height: 104px; }
}

@media (max-width: 1023px) {
  .investor-stat-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 540px) {
  .investor-stat-grid { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------------
   Comparison-table chrome shared by /vs/ and /pesito-vs-* pages.
   --------------------------------------------------------------- */

.compare-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-body);
}
.compare-table thead tr {
  border-bottom: 1px solid var(--tinta-300);
}
.compare-table thead th {
  text-align: left;
  padding: 12px 0;
  font-weight: 700;
  color: var(--tinta-900);
}
.compare-table thead th:first-child {
  font-weight: 600;
  color: var(--tinta-500);
  font-size: var(--fs-body-s);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.compare-table tbody td {
  padding: 16px 8px;
  border-bottom: 1px solid var(--hairline);
  vertical-align: top;
}
.compare-table tbody td:first-child { font-weight: 600; padding-left: 0; }
.compare-table .compare-pos { color: var(--accent-verde); font-weight: 600; }
.compare-table .compare-neg { color: var(--rojo-500); }

/* ---------- .compare-table--cards: stack instead of scroll ----------
   Four columns of sentence-length cells cannot shrink below their
   min-content width, so a narrow screen either scrolls sideways or
   squeezes every column into a two-word ribbon. Below 720px each row
   becomes its own card with the column name printed beside each value.

   Opt-in, because it needs `data-label` on every cell to stay readable:
   a table without labels would stack into unlabelled values. */
@media (max-width: 720px) {
  .compare-table--cards thead { display: none; }
  .compare-table--cards,
  .compare-table--cards tbody,
  .compare-table--cards tr,
  .compare-table--cards td { display: block; width: 100%; }
  .compare-table--cards tr {
    border: var(--panel-border);
    border-radius: var(--panel-radius-sm);
    padding: 4px 16px 8px;
    margin-bottom: 12px;
    background: var(--blanco);
  }
  .compare-table--cards tbody td {
    padding: 10px 0;
    border-bottom: 1px solid var(--hairline);
  }
  .compare-table--cards tbody td:last-child { border-bottom: none; }
  /* First cell is the row's title, so it carries no label. */
  .compare-table--cards tbody td:first-child {
    font-size: var(--fs-body-l);
    font-weight: 700;
    padding-top: 12px;
  }
  .compare-table--cards td[data-label]:not(:first-child)::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 2px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--tinta-500);
  }
}

/* Related-searches footer used by SEO landing pages */
.related-searches {
  padding: 48px 0;
  background: var(--crema);
  border-top: 1px solid var(--hairline);
}
.related-searches h2 {
  font-size: var(--fs-headline);
  color: var(--tinta-900);
  margin-bottom: 16px;
}
.related-searches ul {
  list-style: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 8px;
}
.related-searches li a {
  display: block;
  padding: 12px 16px;
  background: var(--blanco);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  color: var(--tinta-700);
  text-decoration: none;
  transition: border-color 160ms ease, color 160ms ease;
  cursor: pointer;
}
.related-searches li a:hover {
  border-color: var(--verde-700);
  color: var(--verde-700);
}



/* ═══════════════════════════════════════════════════════════════════
   Entrance Animations — page load choreography

   Pesito's brand register demands intentional entrances: the marketing
   site IS the product, and first impressions carry trust. These
   utilities implement a staggered fade-up entrance matching Apple's
   sheet-enter curve (iOS 26 modal presentation rhythm).

   Implemented as pure CSS keyframe animations that autoplay on load —
   NO JavaScript gate. The old pattern (start at opacity 0, wait for JS
   to add `.is-visible`) left the hero invisible whenever JS failed;
   `main.js` still adds the class and it is now a harmless no-op.

   Variants:
   - `.enter-delay-1` through `.enter-delay-5` add stagger (120ms steps)
   - `.enter-hero` skips the blur, hits faster (480ms vs 620ms)
   - `.enter-from-left` / `.enter-from-right` for horizontal slides

   Accessibility: prefers-reduced-motion jumps to the final state.
   ═══════════════════════════════════════════════════════════════════ */

@keyframes enter-rise {
  from { opacity: 0; transform: translateY(16px) scale(0.98); filter: blur(4px); }
  to   { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}
@keyframes enter-rise-hero {
  from { opacity: 0; transform: translateY(24px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes enter-from-left {
  from { opacity: 0; transform: translateX(-32px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes enter-from-right {
  from { opacity: 0; transform: translateX(32px); }
  to   { opacity: 1; transform: translateX(0); }
}

.enter {
  animation: enter-rise var(--dur-entrance) var(--ease-entrance) both;
}
.enter-hero {
  animation: enter-rise-hero 480ms var(--ease-entrance) both;
}
.enter-from-left {
  animation: enter-from-left var(--dur-entrance) var(--ease-entrance) both;
}
.enter-from-right {
  animation: enter-from-right var(--dur-entrance) var(--ease-entrance) both;
}

/* Stagger delays — applied ON TOP of .enter for cascading reveals.
   Each step is 120ms, matching the gap between items in a well-paced
   list entrance. Max 5 steps; beyond that the last items feel laggy. */
.enter-delay-1 { animation-delay: 120ms; }
.enter-delay-2 { animation-delay: 240ms; }
.enter-delay-3 { animation-delay: 360ms; }
.enter-delay-4 { animation-delay: 480ms; }
.enter-delay-5 { animation-delay: 600ms; }

/* Reduced motion: skip all entrance work. Elements start at final state. */
@media (prefers-reduced-motion: reduce) {
  .enter,
  .enter-hero,
  .enter-from-left,
  .enter-from-right {
    animation: none;
  }
}

/* (The old `.page-loaded` gate is gone: `.enter` / `.enter-hero`
   autoplay on load. main.js still adds the class; it is a no-op.) */


/* ═══════════════════════════════════════════════════════════════════
   Scroll Parallax — subtle depth on scroll

   The hero phone mockup (.hero-art) moves slower than the scroll speed,
   creating depth without being distracting. Applied via CSS custom
   property updated by IntersectionObserver in main.js.

   Pattern:
   - Element marked with `.parallax` gets a `--parallax-offset` custom
     property set by JS (0 at top, increasing as you scroll down).
   - CSS translates the element based on that value.
   - `will-change: transform` hints to compositor for smooth 60fps.

   Accessibility: Disabled entirely on prefers-reduced-motion.
   ═══════════════════════════════════════════════════════════════════ */

.parallax {
  will-change: transform;
  transform: translateY(calc(var(--parallax-offset, 0) * -0.15));
  transition: transform 80ms linear;
}

@media (prefers-reduced-motion: reduce) {
  .parallax {
    transform: none;
    will-change: auto;
    transition: none;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   EDITORIAL LAYER — 2026-07 redesign.

   Design language: composed rounded panels on the hueso canvas — outlined
   copy panels, saturated color environments, media panels — with a chunky
   poster grotesk (Archivo Black, self-hosted, OFL) for display type, a
   numbered rail nav on wide viewports, full-width color divider bars, stat
   chips, and a grid-paper footer. Benchmarked against an editorial
   student-housing reference (see docs/compliance/website-copy-spec.md
   sibling docs for the claims side; this layer is purely visual).

   This layer deliberately OVERRIDES the shared classes used by every page
   (headings, .eyebrow, .btn, .faq-item, .footer) so subpages inherit the
   system without markup surgery, and defines new `ed-*` components used by
   the art-directed pages. It loads last, so equal-specificity rules here
   win over the legacy layer above.
   ═══════════════════════════════════════════════════════════════════════ */

@font-face {
  font-family: 'Archivo Black';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/ArchivoBlack.woff2') format('woff2');
}

:root {
  /* Editorial environments (2026-07-16, Lotería): the color-blocked
     sections alternate saturated card hues — verde (flag green),
     bosque→azul cobalto (trust), menta→amarillo (highlight),
     verde-vivo (the money moment), tinta (near-black). Class names
     keep their mono-verde-era names so no markup changes; the fills
     are Lotería. Every panel wears the ink outline — flat bright
     color inside a black frame is the whole grammar. Rojo stays
     reserved for legal / error contexts. */

  --font-black: 'Archivo Black', 'Arial Black', 'Archivo', -apple-system,
                'Helvetica Neue', sans-serif;

  /* Display ceiling per accessibility/typography guidance: ≤ ~92px. */
  --fs-display-xxl: clamp(44px, 6.2vw, 92px);
  --fs-display-xl: clamp(38px, 4.9vw, 72px);
  --fs-display-l: clamp(32px, 4vw, 58px);
  --fs-display-m: clamp(26px, 3vw, 42px);

  /* Panel geometry — one radius for the big blocks, one for chips/pills.
     The 2px ink border is the Lotería card outline; --card-lift is the
     only elevation in the system (a hard folk-poster offset, no blur). */
  --panel-radius: 28px;
  --panel-radius-sm: 18px;
  --panel-gap: clamp(10px, 1.2vw, 16px);
  /* A panel's own inset. Named because the article column has to match
     it: prose that sits outside a card still has to start on the same
     text edge as the copy inside one. */
  --panel-pad: clamp(24px, 3.4vw, 56px);
  --panel-border-width: 2px;
  /* The Loteria frame ink. In light it IS the ink; in dark it dims to
     printed-ink-on-dark-stock (OKLCH L64 C0.015 H82). Inverting the
     frame to full text-bone turned dark mode into a glowing wireframe:
     16:1 outlines competing with the type. At L64 every frame still
     reads (5.6:1 on canvas, 4.3:1 on cards) but ranks BELOW even
     footnote text (L70). Text keeps --tinta-900; fills that use the
     bone as a surface (ghost hover, lang indicator) keep it too -
     this token is only for strokes and the hard offset lift. */
  --frame-ink: light-dark(#0B0B0B, #918B82);
  --panel-border: var(--panel-border-width) solid var(--frame-ink);
  --card-lift: 3px 3px 0 var(--frame-ink);

  /* Semantic z-scale (never arbitrary). */
  --z-rail: 40;
  --z-sticky: 50;
  --z-sheet-backdrop: 60;
  --z-sheet: 70;
  --z-toast: 80;
}

/* ---------- Display typography: the poster grotesk ---------- */

h1, h2,
.display-xxl, .display-xl, .display-l, .display-m {
  font-family: var(--font-black);
  font-weight: 400;              /* Archivo Black has a single weight */
  letter-spacing: -0.01em;
  line-height: 1.02;
  text-wrap: balance;
}
h3, .headline { letter-spacing: -0.005em; }
p { text-wrap: pretty; }

/* Light display type on dark panels breathes a little more. */
.ed-panel--verde h2, .ed-panel--bosque h2, .ed-panel--tinta h2 { line-height: 1.08; }

/* ---------- Kickers: outlined pills replace tracked eyebrows ---------- */

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-size: var(--fs-body-s);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--tinta-900);
  background: transparent;
  border: 2px solid var(--frame-ink);
  border-radius: var(--radius-pill);
  padding: 6px 14px;
}
/* Kickers sitting on dark/saturated panels invert. verde + bosque
   keep deep saturated fills in both themes, so their kicker is
   always white; tinta is the one panel that inverts wholesale, so
   it rides the neutral ramp. */
.ed-panel--verde .eyebrow, .ed-panel--bosque .eyebrow,
.eyebrow--invert {
  color: var(--paper-fixed);
  border-color: rgba(255, 255, 255, 0.75);
}
.ed-panel--tinta .eyebrow {
  color: var(--blanco);
  border-color: var(--blanco);
}
/* Bright FIXED fills (verde-vivo, menta) never theme, so their kicker
   pins to fixed ink: the themed ramp turned it bone-on-bright in dark
   mode. */
.ed-panel--verde-vivo .eyebrow, .ed-panel--menta .eyebrow {
  color: var(--ink-fixed);
  border-color: var(--ink-fixed);
}

/* ---------- Buttons: decisive pills ---------- */

.btn {
  border-radius: var(--radius-pill);
  font-weight: 700;
  padding: 14px 26px;
  transition: transform var(--dur-fast) var(--ease-standard),
              background-color var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.btn-primary {
  background: var(--verde-500);
  color: var(--ink-fixed);
  /* The frame is the same Lotería outline every panel/chip/bar wears:
     themed ink. Fixed ink here left the pill frameless on the dark
     canvas (black ring on near-black) while its hover lift flipped to
     bone: a mismatched double frame. The LABEL stays fixed ink: the
     verde fill never themes, so its text must not either. */
  border: 2px solid var(--frame-ink);
  box-shadow: none;
}
.btn-primary:hover { background: var(--verde-300); transform: translateY(-1px); }
.btn-ghost {
  background: transparent;
  color: var(--tinta-900);
  border: 2px solid var(--frame-ink);
}
@media (hover: hover) {
  .btn-ghost:hover { background: var(--tinta-900); color: var(--blanco); }
}
/* Ghosts on FIXED-fill panels leave the themed ramp: those fills never
   change with the theme, so their pills pin to the fixed ink/paper
   pair. Without this, dark mode painted a bone ghost on bright verde
   (2.8:1) inside verde-vivo panels. Deep fills (verde/bosque) mirror
   the eyebrow inversion. */
.ed-panel--verde-vivo .btn-ghost, .ed-panel--menta .btn-ghost {
  color: var(--ink-fixed);
  border-color: var(--ink-fixed);
}
@media (hover: hover) {
  .ed-panel--verde-vivo .btn-ghost:hover, .ed-panel--menta .btn-ghost:hover {
    background: var(--ink-fixed);
    color: var(--paper-fixed);
  }
}
.ed-panel--verde .btn-ghost, .ed-panel--bosque .btn-ghost {
  color: var(--paper-fixed);
  border-color: var(--paper-fixed);
}
@media (hover: hover) {
  .ed-panel--verde .btn-ghost:hover, .ed-panel--bosque .btn-ghost:hover {
    background: var(--paper-fixed);
    color: var(--ink-fixed);
  }
}
.btn:focus-visible,
.ed-rail-cta:focus-visible {
  outline: 3px solid var(--verde-700);
  outline-offset: 2px;
}
/* On deep saturated panels the green ring vanishes — flip to white. */
.ed-panel--verde .btn:focus-visible,
.ed-panel--bosque .btn:focus-visible {
  outline-color: var(--paper-fixed);
}
.ed-panel--tinta .btn:focus-visible {
  outline-color: var(--blanco);
}
/* verde-vivo is a BRIGHT fill: a white ring on verde-500 lands at
   2.5:1, below the 3:1 non-text minimum (WCAG 2.2 SC 1.4.11). Ink
   reads at 15.6:1 on the same fill. */
.ed-panel--verde-vivo .btn:focus-visible,
.ed-panel--menta .btn:focus-visible {
  outline-color: var(--ink-fixed);
}

/* ---------- Panels: the compositional unit ---------- */

.ed-panel {
  border-radius: var(--panel-radius);
  padding: clamp(24px, 3.4vw, 56px);
  position: relative;
  /* Lotería: every panel is a card — flat fill inside an ink frame. */
  border: var(--panel-border);
}
.ed-panel--outline { background: var(--hueso); }
.ed-panel--blanco { background: var(--blanco); }
/* verde: the brand environment — flag green, white type. */
.ed-panel--verde { background: var(--verde-700); color: var(--paper-fixed); }
.ed-panel--verde h2, .ed-panel--verde h3 { color: var(--paper-fixed); }
.ed-panel--verde p { color: rgba(250, 247, 242, 0.92); }
/* verde-vivo: the conversion environment — brand green, ink type.
   The loud money moment (pricing, final CTA). */
.ed-panel--verde-vivo { background: var(--fill-verde-vivo); color: var(--ink-fixed); }
.ed-panel--verde-vivo h2, .ed-panel--verde-vivo h3 { color: var(--ink-fixed); }
/* Body copy needs the same restatement the verde/bosque panels give
   their <p>: classes like .body-l and .wl-legal set an explicit THEMED
   gray that lands bone-on-bright-green in dark mode (the fill is
   fixed). Element+class outranks the single-class color rules. */
.ed-panel--verde-vivo p, .ed-panel--menta p { color: var(--ink-fixed); }
/* A nested themed card (the FX calculator) paints its own themed
   surface, so its copy goes back on the themed ramp. */
.ed-panel--verde-vivo .fx-card p, .ed-panel--menta .fx-card p { color: var(--tinta-500); }
/* bosque: the trust environment — Lotería cobalt, white type (security,
   institutional sections). Class keeps its historical name. */
.ed-panel--bosque { background: var(--fill-azul); color: var(--paper-fixed); }
.ed-panel--bosque h2, .ed-panel--bosque h3 { color: var(--paper-fixed); }
.ed-panel--bosque p { color: rgba(255, 255, 255, 0.94); }
/* menta: the festive highlight environment — golden amarillo, ink type.
   Class keeps its historical name. */
.ed-panel--menta { background: var(--fill-amarillo); color: var(--ink-fixed); }
.ed-panel--menta h2, .ed-panel--menta h3 { color: var(--ink-fixed); }
/* tinta: the only panel that inverts wholesale with the theme —
   near-black card in light mode, bone card in dark mode. It rides
   the neutral ramp on purpose. */
.ed-panel--tinta { background: var(--tinta-900); color: var(--blanco); }

/* ---- Note ink per environment -----------------------------------
   `--note-ink` defaults to the themed gray ramp, which is correct on
   cream/blanco/hueso. On a saturated Lotería fill the gray ramp is
   the wrong family entirely, so each environment restates the token
   to the same fixed ink/paper its body copy already uses. Secondary
   copy then reads as secondary through SIZE and WEIGHT rather than
   through a washed-out color that fails AA on the loud fills.

   Measured on the fills these notes actually land on:
     ink on verde-500  6.16:1 · ink on verde-vivo dark  4.89:1
     ink on amarillo  12.32:1 · paper on azul-700      10.4:1 */
.ed-panel--verde-vivo,
.ed-panel--menta { --note-ink: var(--ink-fixed); }
.ed-panel--verde,
.ed-panel--bosque { --note-ink: var(--paper-fixed); }
.ed-panel--tinta { --note-ink: var(--blanco); }
/* A nested themed card paints its own surface, so notes inside it go
   back on the themed ramp — the same carve-out `.fx-card p` gets. */
.ed-panel--verde-vivo .fx-card,
.ed-panel--menta .fx-card { --note-ink: var(--tinta-500); }
/* Product-preview showcases: the stage stays INK in both themes. The
   clips are flattened onto #0B0B0B at encode time, so on a themed
   tinta panel the dark theme flipped the card to bone and left the
   dark phone floating in a blinding white frame. Pinned, the phone
   melts into the panel in both themes (same trick as
   .ed-media--contain below) and the card disappears around the
   animation instead of containing it. */
.ed-panel--tinta.ed-showcase,
.ed-panel--tinta.ed-stage { background: #0B0B0B; color: #F6F2E8; }
.ed-panel--tinta.ed-showcase h2,
.ed-panel--tinta.ed-showcase .title,
.ed-panel--tinta.ed-showcase .headline,
.ed-panel--tinta.ed-showcase .body-l,
.ed-panel--tinta.ed-showcase .caption,
.ed-panel--tinta.ed-stage h2,
.ed-panel--tinta.ed-stage .title,
.ed-panel--tinta.ed-stage .headline,
.ed-panel--tinta.ed-stage .body-l,
.ed-panel--tinta.ed-stage .caption { color: #F6F2E8; }
.ed-panel--tinta.ed-showcase .eyebrow,
.ed-panel--tinta.ed-stage .eyebrow {
  color: #F6F2E8;
  border-color: rgba(255, 255, 255, 0.75);
}
.ed-panel--media {
  padding: 0;
  overflow: hidden;
  background: var(--crema);
}

/* Links inside saturated panels stay legible. */
.ed-panel--verde a:not(.btn), .ed-panel--bosque a:not(.btn) {
  color: var(--paper-fixed);
  text-decoration: underline;
}
.ed-panel--tinta a:not(.btn) {
  color: var(--blanco);
  text-decoration: underline;
}
/* .title, .headline, .body-l and .caption each set an explicit ink
   colour, which beats the `color: var(--blanco)` inherited from the
   panel and renders dark text on the dark fill. Panels used to dodge
   this by writing bare <p> and by repeating `color: var(--blanco)`
   inline on every .title, so the trap only surfaced the first time
   somebody reached for a size class in here. Restate the colour once. */
.ed-panel--tinta .title,
.ed-panel--tinta .headline,
.ed-panel--tinta .body-l,
.ed-panel--tinta .caption {
  color: var(--blanco);
}
.ed-panel--verde-vivo a:not(.btn), .ed-panel--menta a:not(.btn) {
  color: var(--ink-fixed);
  text-decoration: underline;
}

/* Links inside running prose must not rely on colour alone (WCAG
   1.4.1). `--accent-verde` against `--tinta-700` body copy is only
   2.09:1 — well under the 3:1 needed to distinguish a link by hue —
   so an inline `style="color: var(--accent-verde)"` on a link in a
   paragraph was invisible as a link to anyone who can't separate the
   two greens. Underline the link instead of recolouring the copy:
   it carries the affordance at any contrast, in both themes, and
   needs no change to the 20 pages that set that inline colour.

   Scoped to running prose only. Navigation, footer, sheet and rail
   links live inside <li> too, but they're identified by position and
   shape rather than by underline — so those containers opt out and
   the rule lands on body copy, exactly where colour alone fails.
   The opt-out uses :is() so it outranks the (0,2,2) of the prose
   rule above; a plain `nav a` (0,0,2) would silently lose.

   Components that already draw their own bottom border (the hero
   kicker, FAQ hub rows, the demo mail link) opt out too — they
   satisfy 1.4.1 by that border, and adding the underline gives them
   two stacked rules. Those are listed rather than detected because
   CSS cannot ask "does this element already have a border".

   Both opt-outs are written with a doubled `:is()` so they clear the
   prose rule's (0,2,2) outright instead of tying it and depending on
   source order — a tie here silently re-underlines everything. */
p a:not(.btn):not(.store-badge),
li a:not(.btn):not(.store-badge),
dd a:not(.btn):not(.store-badge),
figcaption a:not(.btn):not(.store-badge) {
  text-decoration: underline;
  text-underline-offset: 0.16em;
  text-decoration-thickness: from-font;
}
:is(nav, footer, .nav, .nav-sheet, .ed-rail, .lang-menu, .footer-col, .ed-panel--media) a:not(.btn):not(.store-badge),
:is(.hero-kicker, .faq-hub-most-grid, .inv-demo, .how-toc):is(.hero-kicker, .faq-hub-most-grid, .inv-demo, .how-toc) a:not(.btn),
a:is(.faq-hub-most-item, .inv-demo-mail, .store-badge, .faq-hub-crumb):is(.faq-hub-most-item, .inv-demo-mail, .store-badge, .faq-hub-crumb):not(.btn) {
  text-decoration: none;
}

/* A brand-green button disappears on the brand-green panel: inside
   verde-vivo AND verde the primary CTA inverts to ink, the strongest
   thing on either surface. (--verde is verde-700: a verde-500 pill on
   it was green-on-green at ~1.9:1, the exact defect this inversion
   exists for. /lista-espera's form panel hit it.) */
.ed-panel--verde-vivo .btn-primary,
.ed-panel--verde .btn-primary {
  background: var(--ink-fixed);
  color: var(--paper-fixed);
  border-color: var(--ink-fixed);
}
.ed-panel--verde-vivo .btn-primary:hover,
.ed-panel--verde .btn-primary:hover { background: #2A2A2E; }
.ed-panel--verde-vivo .brand-dot { color: var(--ink-fixed); }

/* ---------- Divider bars: the section rhythm ---------- */

.ed-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  border: var(--panel-border);
  border-radius: var(--panel-radius);
  padding: clamp(14px, 2vw, 22px) clamp(20px, 3vw, 36px);
  margin: clamp(28px, 4vw, 56px) 0 var(--panel-gap);
}
.ed-bar h2, .ed-bar .ed-bar-title {
  font-family: var(--font-black);
  font-size: clamp(22px, 2.6vw, 38px);
  line-height: 1.05;
  margin: 0;
  text-align: center;
  flex: 1;
}
.ed-bar--verde { background: var(--fill-verde-vivo); color: var(--ink-fixed); }
.ed-bar--menta { background: var(--fill-amarillo); color: var(--ink-fixed); }
.ed-bar--blanco { background: var(--blanco); color: var(--tinta-900); }
/* Arrows: rosa mexicano on light bars, ink on saturated fills. */
.ed-bar-arrows {
  display: inline-flex;
  gap: 4px;
  color: var(--rosa-500);
  flex-shrink: 0;
}
.ed-bar--verde .ed-bar-arrows, .ed-bar--menta .ed-bar-arrows { color: var(--ink-fixed); }
.ed-bar-arrows svg { display: block; }
@media (max-width: 640px) {
  .ed-bar-arrows { display: none; }
}

/* ---------- Stat chips: big number, small label ---------- */

.ed-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--panel-gap);
}
.ed-chip {
  display: flex;
  align-items: baseline;
  gap: 12px;
  background: var(--amarillo-500);
  color: var(--ink-fixed);
  border: var(--panel-border);
  border-radius: var(--panel-radius-sm);
  padding: 14px 20px;
  min-width: 0;
}
.ed-chip--menta { background: var(--amarillo-500); }
.ed-chip--verde { background: var(--verde-500); }
/* blanco + outline chips ride the neutral ramp, so their label has
   to come back to the themed ink rather than the fixed near-black. */
.ed-chip--blanco { background: var(--blanco); color: var(--tinta-900); }
.ed-chip--outline { background: transparent; color: var(--tinta-900); }

/* ---------- FAQ: outlined rounded rows ---------- */

.faq-list, .ed-faq-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
  gap: var(--panel-gap);
  align-items: start;
}
.faq-item {
  border: var(--panel-border);
  border-radius: var(--panel-radius-sm);
  background: var(--hueso);
  padding: 0;
  box-shadow: none;
  transition: background-color var(--dur-fast) var(--ease-standard);
}
.faq-item[open] { background: var(--blanco); }
.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 22px;
  font-weight: 700;
  cursor: pointer;
  list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '+';
  font-family: var(--font-black);
  font-size: 22px;
  line-height: 1;
  flex-shrink: 0;
  transition: transform var(--dur-base) var(--ease-standard);
}
.faq-item[open] summary::after { transform: rotate(45deg); }
.faq-item .faq-chev { display: none; }
.faq-answer { padding: 0 22px 20px; max-width: 70ch; }

/* ---------- Rail navigation (≥1280px) ---------- */

.ed-rail {
  display: none;
}
@media (min-width: 1280px) {
  .ed-rail {
    display: flex;
    flex-direction: column;
    gap: var(--panel-gap);
    position: fixed;
    top: var(--panel-gap);
    left: var(--panel-gap);
    bottom: var(--panel-gap);
    width: 128px;
    z-index: var(--z-rail);
  }
  body.has-rail .nav { display: none; }
  body.has-rail main, body.has-rail .cta-footer-wrap {
    margin-left: calc(128px + var(--panel-gap) * 2);
  }
}
.ed-rail-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px 8px;
  border: var(--panel-border);
  border-radius: var(--panel-radius-sm);
  background: var(--hueso);
  text-decoration: none;
}
.ed-rail-item {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 18px;
  flex: 1;
  min-height: 84px;
  padding: 12px;
  border: var(--panel-border);
  border-radius: var(--panel-radius-sm);
  text-decoration: none;
  color: var(--tinta-900);
  font-weight: 700;
  font-size: var(--fs-body-s);
  line-height: 1.15;
  transition: transform var(--dur-fast) var(--ease-standard),
              filter var(--dur-fast) var(--ease-standard);
}
.ed-rail-item:hover { transform: translateX(3px); filter: brightness(1.06); }
.ed-rail-item:focus-visible { outline: 3px solid var(--verde-700); outline-offset: 2px; }
.ed-rail-num {
  font-family: var(--font-black);
  font-size: 15px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
/* Lotería card figures (2026-08-26). Each rail card gets its figure
   between the number and the caption, the way a lotería card stacks
   number / figure / name: El Nopal for how-it-works, La Moneda for
   pricing, El Candado for security, La Ruta for the roadmap, La
   Pregunta for the FAQ. Drawn as inline-SVG alpha masks painted with
   currentColor, so one drawing follows every colorway (ink on card
   stock and bright fills, paper on cobalt) and both themes, with no
   markup change on the 18 pages that carry the rail. Selected by
   href, not position, so reordering the deck can't miscast a card. */
.ed-rail-item::before { content: none; }
.ed-rail-item > span:last-child { order: 2; text-align: center; }
.ed-rail-item[href$="como-funciona"]::before,
.ed-rail-item[href$="how-it-works"]::before,
.ed-rail-item[data-rail-page="how"]::before,
.ed-rail-item[href$="enviar-dinero-a-mexico"]::before,
.ed-rail-item[href$="send-money-to-mexico"]::before,
.ed-rail-item[data-rail-page="price"]::before,
.ed-rail-item[href$="seguridad-y-cuenta"]::before,
.ed-rail-item[href$="safety-and-account"]::before,
.ed-rail-item[data-rail-page="faqSafety"]::before,
.ed-rail-item[href$="/ruta"]::before,
.ed-rail-item[href$="roadmap"]::before,
.ed-rail-item[data-rail-page="roadmap"]::before,
.ed-rail-item[href$="/preguntas"]::before,
.ed-rail-item[href$="/en/faq"]::before,
.ed-rail-item[data-rail-page="faq"]::before {
  content: "";
  order: 1;
  width: 26px;
  height: 26px;
  flex: none; /* a flex child with no shrink guard: tight cards were crushing the figure (to 0 on laptops) */
  align-self: center;
  background: currentColor;
  -webkit-mask: var(--rail-icon) center / contain no-repeat;
  mask: var(--rail-icon) center / contain no-repeat;
}
/* The figure adds a third row; tighten the card's internal rhythm so
   five cards still deal onto a 900px-tall viewport. */
.ed-rail-item[href$="como-funciona"],
.ed-rail-item[href$="how-it-works"],
.ed-rail-item[href$="enviar-dinero-a-mexico"],
.ed-rail-item[href$="send-money-to-mexico"],
.ed-rail-item[href$="seguridad-y-cuenta"],
.ed-rail-item[href$="safety-and-account"],
.ed-rail-item[href$="/ruta"],
.ed-rail-item[href$="roadmap"],
.ed-rail-item[href$="/preguntas"],
.ed-rail-item[href$="/en/faq"],
.ed-rail-item[data-rail-page="faq"] {
  gap: 10px;
}
/* El Nopal */
.ed-rail-item[href$="como-funciona"],
.ed-rail-item[href$="how-it-works"],
.ed-rail-item[data-rail-page="how"] {
  --rail-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 26V8'/%3E%3Cpath d='M16 18h-5.5v-6'/%3E%3Cpath d='M16 21h5.5v-4.5'/%3E%3Cpath d='M10 26h12'/%3E%3C/svg%3E");
  --rail-anim: rail-wiggle;
}
/* La Moneda — Iconoir "coins" (two coin stacks), owner-picked
   2026-08-26. Stroke bumped 1.5 -> 1.8 so its weight matches the
   other figures' 2.4-per-32 at the 26px render size. */
.ed-rail-item[href$="enviar-dinero-a-mexico"],
.ed-rail-item[href$="send-money-to-mexico"],
.ed-rail-item[data-rail-page="price"] {
  --rail-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 13c-2.76 0-5-1.12-5-2.5S13.24 8 16 8s5 1.12 5 2.5-2.24 2.5-5 2.5Z'/%3E%3Cpath d='M11 14.5c0 1.38 2.24 2.5 5 2.5s5-1.12 5-2.5'/%3E%3Cpath d='M3 9.5C3 10.88 5.24 12 8 12c1.13 0 2.16-.19 3-.5'/%3E%3Cpath d='M3 13c0 1.38 2.24 2.5 5 2.5 1.13 0 2.16-.19 3-.5'/%3E%3Cpath d='M3 5.5v11C3 17.88 5.24 19 8 19c1.13 0 2.16-.19 3-.5'/%3E%3Cpath d='M13 8.5v-3'/%3E%3Cpath d='M11 10.5v8c0 1.38 2.24 2.5 5 2.5s5-1.12 5-2.5v-8'/%3E%3Cpath d='M8 8C5.24 8 3 6.88 3 5.5S5.24 3 8 3s5 1.12 5 2.5S10.76 8 8 8Z'/%3E%3C/svg%3E");
  --rail-anim: rail-spin;
}
/* El Candado. Two drawings: shut (rest) and open (shackle raised,
   right leg free). The hover keyframes swap the mask between them -
   mask-image animates discretely, flipping midway between keyframes,
   which is exactly the snap a padlock should have. */
.ed-rail-item[href$="seguridad-y-cuenta"],
.ed-rail-item[href$="safety-and-account"],
.ed-rail-item[data-rail-page="faqSafety"] {
  --rail-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='14.5' width='14' height='10.5' rx='2.5'/%3E%3Cpath d='M12 14.5V11a4 4 0 0 1 8 0v3.5'/%3E%3Cpath d='M16 18.8v2.6'/%3E%3C/svg%3E");
  --rail-icon-open: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='14.5' width='14' height='10.5' rx='2.5'/%3E%3Cpath d='M12 14.5V9.5a4 4 0 0 1 7.6-1.8'/%3E%3Cpath d='M16 18.8v2.6'/%3E%3C/svg%3E");
  --rail-anim: rail-unlock;
  --rail-anim-dur: 960ms;
}
/* La Ruta */
.ed-rail-item[href$="/ruta"],
.ed-rail-item[href$="roadmap"],
.ed-rail-item[data-rail-page="roadmap"] {
  --rail-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 27c8 0 12-4 8-8s-4-8 4-8' stroke-dasharray='3.6 3.2'/%3E%3Ccircle cx='23' cy='8' r='2.6'/%3E%3C/svg%3E");
  --rail-anim: rail-bob;
}
/* La Pregunta */
.ed-rail-item[href$="/preguntas"],
.ed-rail-item[href$="/en/faq"],
.ed-rail-item[data-rail-page="faq"] {
  --rail-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 12.2a4.2 4.2 0 1 1 6.5 3.5c-1.6 1-2.5 1.8-2.5 3.3v.6'/%3E%3Ccircle cx='16' cy='24' r='1.7' fill='black' stroke='none'/%3E%3C/svg%3E");
  --rail-anim: rail-pop;
}
/* Each figure acknowledges the pointer with its own small gesture —
   the cactus sways, the coin spins, the lock springs open and clicks
   shut, the route bobs,
   the question pops. One beat per hover, motion-tolerant only. */
@media (prefers-reduced-motion: no-preference) {
  .ed-rail-item:hover::before,
  .ed-rail-item:focus-visible::before {
    animation: var(--rail-anim, none) var(--rail-anim-dur, 640ms) var(--ease-emphasized);
  }
  /* Locked -> unlocked -> locked. The mask flips discretely between
     the 18/28 and 62/72 keyframes; the lift and tilt around each flip
     sell the shackle popping free and clicking home. */
  @keyframes rail-unlock {
    0%, 18% {
      -webkit-mask-image: var(--rail-icon);
      mask-image: var(--rail-icon);
      transform: none;
    }
    28%, 62% {
      -webkit-mask-image: var(--rail-icon-open);
      mask-image: var(--rail-icon-open);
      transform: translateY(-2px) rotate(-4deg);
    }
    72%, 90% {
      -webkit-mask-image: var(--rail-icon);
      mask-image: var(--rail-icon);
      transform: translateY(0.5px);
    }
    100% {
      -webkit-mask-image: var(--rail-icon);
      mask-image: var(--rail-icon);
      transform: none;
    }
  }
  @keyframes rail-wiggle {
    0% { transform: rotate(0); }
    30% { transform: rotate(-7deg); }
    65% { transform: rotate(5deg); }
    100% { transform: rotate(0); }
  }
  @keyframes rail-spin {
    0% { transform: scaleX(1); }
    50% { transform: scaleX(-1); }
    100% { transform: scaleX(1); }
  }
  @keyframes rail-jiggle {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-2.5px) rotate(-3deg); }
    55% { transform: translateX(2.5px) rotate(3deg); }
    80% { transform: translateX(-1px); }
  }
  @keyframes rail-bob {
    0%, 100% { transform: translateY(0); }
    40% { transform: translateY(-3px) rotate(4deg); }
    75% { transform: translateY(1px); }
  }
  @keyframes rail-pop {
    0%, 100% { transform: scale(1); }
    45% { transform: scale(1.22) rotate(6deg); }
  }
}
/* On short viewports the deck tightens instead of losing its figures —
   most laptops live under 860px of viewport, and El Candado's hover
   flourish is a signature, not a garnish. Figures drop to 20px, the
   cards close ranks, and the rail may scroll internally as the last
   resort (this replaces both the old figure-hiding rule here and the
   old 720px scroll rule further down). */
@media (min-width: 1280px) and (max-height: 859.98px) {
  /* overflow-y:auto makes the rail a scroll container, which clips both
     axes at its padding box. Two consequences handled here: (1) the 3px
     hover slide was getting its right edge shaved off, so the rail grows
     a 3px right-hand lane (width + padding) that keeps every child at
     128px while giving the slide room inside the clip; (2) overflow-x is
     pinned to hidden so no horizontal scrollbar can ever appear under
     the deck. The focus ring draws inset so the left clip edge cannot
     shave it. */
  .ed-rail { overflow-y: auto; overflow-x: hidden; width: 131px; padding-right: 3px; gap: 10px; }
  /* No scrollbar, ever (owner decision). Below ~728px the deck
     physically cannot fit, and the rail hands off to the nav header
     instead of scrolling — see the next media block. This pair is the
     belt-and-braces for the sliver where content and viewport race. */
  .ed-rail { scrollbar-width: none; }
  .ed-rail::-webkit-scrollbar { display: none; }
  /* The card keeps the tall layout's exact anatomy — número+arrow row,
     figure alone and centered on its own band, name beneath — because
     that IS the Lotería deal. What changes is only how the figure loses
     height when the deck runs out of room: instead of squashing flat
     (production renders it 20x12 on laptops), it shrinks PROPORTIONALLY
     — flex-basis 24px down to a 16px floor, aspect-ratio keeping it
     square, the mask scaling crisply. Full 24px figures survive to
     ~768px viewports; below ~720px the rail scrolls as the last
     resort. */
  .ed-rail-item[href] {
    gap: 4px;
    padding: 8px 10px;
    /* Never below the card's own content: label lengths vary by
       language (Nahuatl runs long), and equal flex sharing was
       clipping words mid-card. When the deck outgrows the viewport
       the rail scrolls — its designed last resort — but text never
       gets cut. */
    min-height: max-content;
  }
  .ed-rail-item[href] .ed-rail-num { font-size: 13px; }
  .ed-rail-item[href] > span:last-child { overflow-wrap: break-word; min-width: 0; }
  .ed-rail-item[href]::before {
    width: auto;
    height: auto;
    aspect-ratio: 1;
    flex: 0 1 24px;
    min-height: 16px;
  }
  .ed-rail-item:focus-visible { outline-offset: -3px; }
}
/* Below ~728px of viewport the full deck (logo + five uncut cards +
   CTA + foot) physically cannot fit, and the owner's constraints are
   firm: no clipped words, no scrolling, no scrollbar. So the rail
   bows out and the regular nav header takes over — brand, menu, and
   the language dropdown all live there and work at any height. */
@media (min-width: 1280px) and (max-height: 727.98px) {
  .ed-rail { display: none; }
  body.has-rail .nav { display: block; }
  body.has-rail main, body.has-rail .cta-footer-wrap { margin-left: 0; }
  /* clearance for the fixed nav capsule, same as the FAQ pages */
  body.has-rail main { padding-top: 92px; }
}

/* Rail items deal the Lotería deck — now the FULL hand: coral, verde,
   cobalt, amarillo, rosa. Each card wears a lithograph-sky gradient
   (stops computed in OKLCH: top +6L, base, bottom -5L on one hue, so
   no muddy mid) the way printed Lotería decks put a graded sky behind
   the figure. Fixed stops in both themes: rail cards are the small
   festive pops that keep full brightness in dark mode. Text contrast
   verified per stop where the type actually sits (numeral on the top
   stop, caption on the bottom): worst case 4.56:1, the white numeral
   on the rosa top. Owner-requested gradient experiment 2026-08-26;
   themed panels stay flat per MASTER. */
.ed-rail-item--outline { background: var(--hueso); }
.ed-rail-item--menta {
  background: linear-gradient(180deg, #FFDD91 0%, #FFC400 55%, #EBB500 100%);
}
.ed-rail-item--verde-vivo, .ed-rail-item--verde {
  background: linear-gradient(180deg, #2EB963 0%, #00A651 55%, #009448 100%);
}
.ed-rail-item--bosque {
  background: linear-gradient(180deg, #1F57BA 0%, #0A45A6 55%, #003791 100%);
  color: var(--paper-fixed);
}
.ed-rail-item--coral {
  background: linear-gradient(180deg, #FF8367 0%, #FF5A36 55%, #ED4823 100%);
}
.ed-rail-item--rosa {
  background: linear-gradient(180deg, #DA2A7C 0%, #C4006B 55%, #AC005D 100%);
  color: var(--paper-fixed);
}
.ed-rail-item--menta, .ed-rail-item--verde-vivo, .ed-rail-item--verde,
.ed-rail-item--coral { color: var(--ink-fixed); }
/* The legacy rail link. Since 2026-09-22 the rail's waitlist CTA is a
   real .btn.btn-primary (owner: "it needs to be btn btn"), so this paint
   only applies to a rail CTA that is not a button. */
.ed-rail-cta:not(.btn) {
  display: block;
  text-align: center;
  background: var(--verde-500);
  color: var(--ink-fixed);
  /* Themed frame, fixed label: same reasoning as .btn-primary. */
  border: 2px solid var(--frame-ink);
  border-radius: var(--radius-pill);
  padding: 12px 8px;
  font-weight: 700;
  font-size: var(--fs-body-s);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-standard);
}
.ed-rail-cta:not(.btn):hover { background: var(--verde-300); }
/* Language + appearance share the last row so the five numbered
   cards keep their height on a 900px-tall viewport. */
.ed-rail-foot {
  display: flex;
  gap: var(--panel-gap);
}
.ed-rail-foot > * { flex: 1 1 0; min-width: 0; }
.ed-rail-lang {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--rosa-500);
  color: var(--paper-fixed);
  border: 2px solid var(--ink-fixed);
  border-radius: var(--radius-pill);
  padding: 10px 8px;
  font-weight: 700;
  font-size: var(--fs-caption);
  text-decoration: none;
}
/* Five-language menu for the rail foot (landing pages). A native
   <details> so it works with no JS; opens upward inside the rail. */
.ed-rail-langmenu { position: relative; flex: 1; min-width: 0; }
.ed-rail-langmenu summary {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--rosa-500);
  color: var(--paper-fixed);
  border: 2px solid var(--ink-fixed);
  border-radius: var(--radius-pill);
  padding: 10px 8px;
  font-weight: 700;
  font-size: var(--fs-caption);
  cursor: pointer;
  list-style: none;
}
.ed-rail-langmenu summary::-webkit-details-marker { display: none; }
.ed-rail-langmenu ul {
  /* FIXED, not absolute: the rail becomes a scroll container on short
     viewports, and an absolute panel inside it gets squeezed by the
     scrollbar, clipped by overflow-x, and painted over the deck. A
     fixed panel escapes the scroll container entirely (the rail has no
     transform/filter, so the viewport is its containing block) and can
     be wider than the rail. The foot only ever opens while visible at
     the rail's bottom edge, so viewport-anchored coordinates hold. */
  position: fixed;
  left: 16px;
  bottom: 68px;
  width: 184px;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: var(--hueso);
  border: 2px solid var(--frame-ink);
  border-radius: 14px;
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.18);
  z-index: 5;
  /* Same entrance as the nav's language menu, grown from the chip. */
  transform-origin: bottom left;
  animation: lang-menu-in 170ms cubic-bezier(0.2, 0, 0, 1);
}
/* The chip holds its lifted look while its list is open, so the
   control itself says open or closed (the nav pill flips a chevron). */
.ed-rail-langmenu[open] summary {
  background: var(--rosa-600);
  box-shadow: 3px 3px 0 var(--frame-ink);
}
@media (prefers-reduced-motion: reduce) {
  .ed-rail-langmenu ul { animation: none; }
}
.ed-rail-langmenu ul a { min-height: 44px; display: flex; align-items: center;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px;
  border-radius: 9px;
  text-decoration: none;
  color: var(--tinta-900);
  font-weight: 600;
  font-size: var(--fs-caption);
}
@media (hover: hover) {
  .ed-rail-langmenu ul a:hover { background: light-dark(rgba(26, 18, 6, 0.06), rgba(255, 255, 255, 0.08)); }
}
.ed-rail-langmenu ul a[aria-current="true"] { font-weight: 800; }
.ed-rail-langmenu ul a[aria-current="true"]::after {
  content: "";
  margin-left: auto;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--verde-500);
}

/* ---------- Editorial page grid ---------- */

.ed-wrap {
  padding: var(--panel-gap) var(--panel-gap) 0;
  display: flex;
  flex-direction: column;
  gap: var(--panel-gap);
  max-width: 1720px;
  margin: 0 auto;
}
.ed-grid {
  display: grid;
  gap: var(--panel-gap);
}
@media (min-width: 960px) {
  .ed-grid--split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: stretch; }
  .ed-grid--split-wide { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); align-items: stretch; }
  .ed-grid--thirds { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ---------- Grid-paper footer with giant wordmark ---------- */

.ed-footer-paper {
  position: relative;
  margin-top: clamp(32px, 4vw, 64px);
  border-top: var(--panel-border);
  background:
    linear-gradient(var(--tinta-300) 1px, transparent 1px),
    linear-gradient(90deg, var(--tinta-300) 1px, transparent 1px);
  background-size: 56px 56px;
  background-color: var(--hueso);
  overflow: hidden;
}
.ed-footer-wordmark {
  font-family: var(--font-black);
  font-size: clamp(72px, 17vw, 260px);
  line-height: 0.9;
  letter-spacing: -0.01em;
  color: var(--tinta-900);
  padding: clamp(32px, 6vw, 96px) 24px 0;
  margin: 0;
  user-select: none;
}
.ed-footer-cell {
  position: absolute;
  width: 28px;
  height: 28px;
  border: 2px solid var(--frame-ink);
}
/* Lotería deal: the scattered cells cycle the full card palette.
   Overrides the per-page inline verde values (decorative, aria-hidden)
   so 40+ pages retheme without markup edits. */
.ed-footer-paper > div.ed-footer-cell:nth-of-type(1) { background: var(--amarillo-500) !important; }
.ed-footer-paper > div.ed-footer-cell:nth-of-type(2) { background: var(--coral-500) !important; }
.ed-footer-paper > div.ed-footer-cell:nth-of-type(3) { background: var(--azul-700) !important; }
.ed-footer-paper > div.ed-footer-cell:nth-of-type(4) { background: var(--verde-500) !important; }
.ed-footer-paper > div.ed-footer-cell:nth-of-type(5) { background: var(--rosa-500) !important; }
@media (max-width: 640px) {
  .ed-footer-paper { background-size: 40px 40px; }
  .ed-footer-cell { width: 20px; height: 20px; }
}

/* The legacy dark footer becomes a light editorial footer sitting on the
   grid paper. Scoped to .ed-footer so legacy pages keep the dark footer
   until they migrate. */
.ed-footer .footer {
  background: transparent;
  color: var(--tinta-700);
  border-top: none;
}
.ed-footer .footer a { color: var(--tinta-900); }
.ed-footer .footer :is(h3, h4) { color: var(--tinta-900); }
.ed-footer .footer-mouseprint { color: var(--tinta-500); }
.ed-footer .wordmark--on-dark { color: var(--tinta-900); }

/* ---------- Motion: respect reduced motion for the new pieces ---------- */

@media (prefers-reduced-motion: reduce) {
  .ed-rail-item, .ed-rail-item:hover { transition: none; transform: none; filter: none; }
  .faq-item summary::after { transition: none; }
  .btn, .btn:hover { transition: none; transform: none; }
}

/* ---------- Editorial layer: legacy-bleed fixes (post-QA) ---------- */

/* The legacy eyebrow carried a ::before status dot and glass shadows;
   both bleed into the new outlined pill. Kill them here (this layer
   loads last). */
.eyebrow::before { display: none; }
.eyebrow, .section-head .eyebrow, .how-gallery .eyebrow {
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background-image: none;
}

/* Legacy glass button skin: flatten to the editorial pill. */
.btn-primary, .btn-primary:hover {
  background-image: none;
  box-shadow: none;
}
.btn-primary::before, .btn-primary::after { display: none; }
/* The legacy ghost hover carried a glass sheen + soft verde halo;
   the Lotería spec allows no halos. Kill the bleed. */
.btn-ghost:hover { box-shadow: none; }
/* `.btn-outline` was the Liquid Glass secondary (blur, rgba surface,
   1px glass stroke) and never got an editorial skin, so the mobile
   nav sheet and the story pages still rendered a glass pill next to
   Lotería ones. Flatten it to the ghost grammar. */
.btn-outline, .btn-outline:hover {
  background: transparent;
  color: var(--tinta-900);
  border: 2px solid var(--frame-ink);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
}
.btn-outline:hover { background: var(--tinta-900); color: var(--blanco); }

/* Legacy [open] halo on FAQ rows. */
.faq-item[open] { box-shadow: none; }

/* ═══════════════════════════════════════════════════════════════════
   MOTION LAYER — 2026-07-11.

   Principle: reveal must enhance, never gate. Content is visible by
   default everywhere (no-JS, print, headless renderers, Firefox).
   Scroll choreography is implemented with CSS scroll-driven animations
   behind @supports — engines without `animation-timeline: view()`
   simply show the finished page. No JavaScript is involved; the old
   IntersectionObserver `.is-revealed` class in main.js is a no-op.

   Vocabulary (kept deliberately small):
     • Panels / cells `[data-reveal]` — rise + settle as they enter.
     • Stat chips — same rise, sibling-staggered via animation-range.
     • Divider bars `.ed-bar` — a center-out clip wipe: the bar
       "opens" like a counter shutter. The one signature move.
   ═══════════════════════════════════════════════════════════════════ */

[data-reveal] {
  opacity: 1;
  transform: none;
  transition: none;
}

/* The from-state NEVER hides content: opacity floors at 0.72 and the
   travel is pure transform. A static capture (print, full-page
   screenshot, engine without scroll-driven support mid-scroll) shows
   readable content at worst slightly dimmed and shifted — never a
   blank section. That constraint is why there is no clip/fade-from-
   zero anywhere in this layer. */
@supports (animation-timeline: view()) {
  @media screen and (prefers-reduced-motion: no-preference) {
    @keyframes ed-reveal-rise {
      from { opacity: 0.72; transform: translateY(20px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    [data-reveal] {
      animation: ed-reveal-rise var(--dur-entrance) var(--ease-entrance) both;
      animation-timeline: view();
      animation-range: entry 5% entry 40%;
    }

    /* Cards sharing one grid row stagger left-to-right the same way. */
    .ed-grid > [data-reveal]:nth-child(2) { animation-range: entry 11% entry 46%; }
    .ed-grid > [data-reveal]:nth-child(3) { animation-range: entry 17% entry 52%; }
    .ed-grid > [data-reveal]:nth-child(4) { animation-range: entry 23% entry 58%; }

    /* Divider bars: the bar rises with the page; its arrow pairs slide
       outward from the title — a small "opening" gesture on elements
       that are pure decoration (safe at any static state). */
    .ed-bar {
      animation: ed-reveal-rise var(--dur-deliberate) var(--ease-entrance) both;
      animation-timeline: view();
      animation-range: entry 10% entry 55%;
    }
    @keyframes ed-bar-arrows-l { from { transform: translateX(14px); } to { transform: translateX(0); } }
    @keyframes ed-bar-arrows-r { from { transform: translateX(-14px); } to { transform: translateX(0); } }
    .ed-bar .ed-bar-arrows:first-of-type {
      animation: ed-bar-arrows-l var(--dur-deliberate) var(--ease-entrance) both;
      animation-timeline: view();
      animation-range: entry 10% entry 60%;
    }
    .ed-bar .ed-bar-arrows:last-of-type {
      animation: ed-bar-arrows-r var(--dur-deliberate) var(--ease-entrance) both;
      animation-timeline: view();
      animation-range: entry 10% entry 60%;
    }

    /* App-showcase device art: a quiet settle behind the panel rise,
       so the phone reads as "landing" on the black canvas. */
    @keyframes ed-device-settle {
      from { opacity: 0.75; transform: translateY(14px) scale(0.98); }
      to   { opacity: 1; transform: translateY(0) scale(1); }
    }
    .ed-showcase-art .device-frame,
    .ed-showcase-art .showcase-stage {
      animation: ed-device-settle var(--dur-entrance) var(--ease-entrance) both;
      animation-timeline: view();
      animation-range: entry 15% entry 60%;
    }
  }
}

/* ---------- Signature: the brand dot stamps the headline ----------
   The hero h1 ends in Pesito's green dot. It lands ~200ms after the
   headline settles — a single decisive stamp (one overshoot frame on
   the emphasized curve, not a bounce). Scoped to h1 so section
   headings and the nav wordmark stay still. */
@keyframes brand-dot-stamp {
  from { opacity: 0; transform: translateY(-0.05em) scale(0); }
  70%  { opacity: 1; transform: translateY(-0.05em) scale(1.16); }
  to   { opacity: 1; transform: translateY(-0.05em) scale(1); }
}
h1 .brand-dot {
  animation: brand-dot-stamp 380ms var(--ease-entrance) 860ms both;
}
@media (prefers-reduced-motion: reduce) {
  h1 .brand-dot { animation: none; }
}

/* ---------- Micro-interactions: feedback layer ---------- */

/* Press: every pill compresses under the finger. 100ms class —
   instant feedback, no transition needed on the way down. */
.btn:active,
.ed-rail-cta:active,
.store-badge:active {
  transform: translateY(0) scale(0.97);
  transition-duration: var(--dur-instant);
}

/* Lotería lift: the conversion button rises off the table on a hard
   ink offset — folk-poster elevation, no blur, no glow. */
.btn-primary:hover {
  box-shadow: var(--card-lift);
}
/* Inverted (ink) primary inside verde-vivo/verde panels: same lift,
   but pinned to fixed ink. The themed --card-lift turned the offset
   bone in dark mode while the panel fill stayed fixed green, which
   framed the ink pill in white. */
.ed-panel--verde-vivo .btn-primary:hover,
.ed-panel--verde .btn-primary:hover {
  box-shadow: 3px 3px 0 var(--ink-fixed);
}

/* FAQ rows acknowledge the pointer before the click. */
.faq-item summary {
  transition: background-color var(--dur-fast) var(--ease-standard);
  border-radius: var(--panel-radius-sm);
}
@media (hover: hover) {
  .faq-item:not([open]) summary:hover {
    background-color: rgba(0, 166, 81, 0.07);
  }
}

/* Store badges lift like buttons (they are buttons). */
.store-badge {
  transition: transform var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard);
}
.store-badge:hover {
  transform: translateY(-1px);
}

/* Rail language pill + logo get the same acknowledgement. */
.ed-rail-lang:hover { background: var(--rosa-600); }
.ed-rail-logo { transition: transform var(--dur-fast) var(--ease-standard); }
.ed-rail-logo:hover { transform: translateY(-1px); }

@media (prefers-reduced-motion: reduce) {
  .btn:active,
  .ed-rail-cta:active,
  .store-badge:active,
  .store-badge:hover,
  .ed-rail-logo:hover {
    transform: none;
  }
  .faq-item summary,
  .store-badge,
  .ed-rail-logo {
    transition: none;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   WAITLIST CTA — Lotería flow (2026-07-16, deepened 2026-07-18).

   Hover treatment for primary waitlist CTAs only, applied as
   `.btn-flow` by scripts/main.js (progressive enhancement: without
   JS, or on touch / reduced-motion, the button is exactly the
   standard editorial pill — every rule below is gated behind
   hover-capable + motion-tolerant media queries, so hover can never
   go "sticky" on touch and reduced-motion keeps the plain fast
   color fade).

   The interaction: FOUR oversized sheets pour upward through the
   pill in a stagger, shifting through the Lotería palette —
   amarillo leads, coral chases, rosa flashes, mint settles as the
   hover fill. Every sheet's top edge is a true sine wave (an SVG
   mask tile repeated across the crest; four different wavelengths
   and amplitudes so the layers read as breaking surf, not one
   shape re-timed), and every sheet drifts laterally while it
   rises, alternating directions, so the crests travel like water.
   Each hover seeds a fresh random wave phase per sheet
   (--flow-p1..4, JS on pointerenter) — no two pours break alike.
   A soft highlight bloom enters at the pointer position and
   follows it (--px/--py, rAF-throttled).

   Hover-out does NOT rewind: JS swaps `:hover` for `.is-exiting`
   and the sheets accelerate on through the TOP edge — mint first,
   amarillo last — then reset to the bottom with transitions
   disabled (`.is-resetting`). Liquid pours up and through, both
   directions, never back down. Entrance tail ≈ 1410 ms; exit ≈ 980.

   Ink label: ≥6:1 at rest (verde-500 6.2) and settle (mint 10.4);
   amarillo 12.3 and coral ~6.9 in transit; the rosa pass dips for
   ~95 ms by design — the transient the reference motion accepts.
   Inverted variant inside verde-vivo panels keeps its white label
   readable on EVERY frame (azul cobalto 8.7 → rosa-600 5.6 →
   verde-700 7.0 → tinta settle). Rojo stays semantic-only.
   Validated in docs/design-system/MASTER.md.
   ═══════════════════════════════════════════════════════════════════ */

.btn-flow {
  position: relative;
  overflow: hidden;
  /* The pour sheets are far larger than the pill (±16% wide, 210% tall,
     translated below it). overflow:hidden clips their PAINT, but their
     composited transforms can still leak into an ancestor scroll
     container's overflow accounting mid-pour — inside the rail that
     summoned a horizontal scrollbar on hover. Paint containment makes
     the clip airtight for overflow accounting too. */
  contain: paint;
  isolation: isolate;
  -webkit-tap-highlight-color: transparent;
}
/* The legacy glass-sheen pseudo skin still cascades onto
   .btn-primary pseudos (opacity/mask/blend dance at :hover). The
   sweep now lives in real child spans, so the pseudos are retired
   outright wherever the flow applies. */
.btn-flow::before,
.btn-flow::after {
  content: none;
}
/* JS wraps the label so it stacks above the sweep layers. */
.btn-flow .btn-flow-label {
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: inherit;
}
/* Sheet container + sheets exist only when JS ran (which already
   bails on touch and reduced motion), but every visual rule is
   still media-gated below, belt and braces. */
.btn-flow .btn-flow-layers {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  /* The sheet is ONLY a pour carrier now: its transform/transition
     runs the rest → hover → exit → reset state machine, nothing is
     painted on it. All paint (background + wave masks) lives on the
     inner .btn-flow-water so the lateral drift can run as a pure
     compositor transform — animating mask-position here repainted
     the sheet every frame (measured 79% dropped frames on CTA
     hover). */
  .btn-flow .btn-flow-layers > span {
    position: absolute;
    left: -16%;
    right: -16%;
    top: 0;
    height: 210%;
    background: none;
    transform: translate3d(var(--flow-dx, 0%), 103%, 0);
    will-change: transform;
    transition: transform 820ms cubic-bezier(0.45, 0.05, 0.2, 1);
  }
  /* The water: a double-width paint layer inside each sheet
     (injected by scripts/main.js next to the sheets). The crest: a
     true sine wave, cut by an SVG mask tile repeated across the top
     22px (layer 1); a plain gradient keeps the body opaque from
     21px down (layer 2, 1px overlap so no seam). --flow-p* shifts
     the tile's phase — set once per hover by JS, never animated.
     The drift keyframes below translate the water ±half a tile, so
     the crests travel like water with zero repaint; sheets already
     overflow the button ±16%, so the 200%-wide water keeps the pill
     fully covered through the whole excursion. */
  .btn-flow .btn-flow-water {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 200%;
    left: -50%;
    -webkit-mask-repeat: repeat-x, no-repeat, repeat-x;
    mask-repeat: repeat-x, no-repeat, repeat-x;
  }
  /* Four sine crests at four wavelengths/amplitudes — layered surf,
     not one shape re-timed. Sheet order = paint order: amarillo
     under coral under rosa under mint. Lateral drift alternates. */
  .btn-flow .btn-flow-layers > span:nth-child(1) {
    --flow-dx: 4%;
    transition-delay: 0ms;
  }
  .btn-flow .btn-flow-layers > span:nth-child(1) .btn-flow-water {
    background: var(--amarillo-500);
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 22'%3E%3Cpath d='M0 11 C18 2 37 2 55 11 C73 20 92 20 110 11 V22 H0 Z'/%3E%3C/svg%3E"), linear-gradient(#000, #000), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 22'%3E%3Cpath d='M0 11 C18 2 37 2 55 11 C73 20 92 20 110 11 V0 H0 Z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 22'%3E%3Cpath d='M0 11 C18 2 37 2 55 11 C73 20 92 20 110 11 V22 H0 Z'/%3E%3C/svg%3E"), linear-gradient(#000, #000), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 22'%3E%3Cpath d='M0 11 C18 2 37 2 55 11 C73 20 92 20 110 11 V0 H0 Z'/%3E%3C/svg%3E");
    -webkit-mask-size: 110px 22px, 100% calc(100% - 42px), 110px 22px;
    mask-size: 110px 22px, 100% calc(100% - 42px), 110px 22px;
    -webkit-mask-position: var(--flow-p1, 0px) 0, 0 21px, var(--flow-p1, 0px) 100%;
    mask-position: var(--flow-p1, 0px) 0, 0 21px, var(--flow-p1, 0px) 100%;
  }
  .btn-flow .btn-flow-layers > span:nth-child(2) {
    --flow-dx: -4.5%;
    transition-delay: 170ms;
  }
  .btn-flow .btn-flow-layers > span:nth-child(2) .btn-flow-water {
    background: var(--coral-500);
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 150 22'%3E%3Cpath d='M0 11 C25 4 50 4 75 11 C100 18 125 18 150 11 V22 H0 Z'/%3E%3C/svg%3E"), linear-gradient(#000, #000), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 150 22'%3E%3Cpath d='M0 11 C25 4 50 4 75 11 C100 18 125 18 150 11 V0 H0 Z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 150 22'%3E%3Cpath d='M0 11 C25 4 50 4 75 11 C100 18 125 18 150 11 V22 H0 Z'/%3E%3C/svg%3E"), linear-gradient(#000, #000), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 150 22'%3E%3Cpath d='M0 11 C25 4 50 4 75 11 C100 18 125 18 150 11 V0 H0 Z'/%3E%3C/svg%3E");
    -webkit-mask-size: 150px 22px, 100% calc(100% - 42px), 150px 22px;
    mask-size: 150px 22px, 100% calc(100% - 42px), 150px 22px;
    -webkit-mask-position: var(--flow-p2, 0px) 0, 0 21px, var(--flow-p2, 0px) 100%;
    mask-position: var(--flow-p2, 0px) 0, 0 21px, var(--flow-p2, 0px) 100%;
  }
  .btn-flow .btn-flow-layers > span:nth-child(3) {
    --flow-dx: 3.5%;
    transition-delay: 340ms;
  }
  .btn-flow .btn-flow-layers > span:nth-child(3) .btn-flow-water {
    background: var(--rosa-500);
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 90 22'%3E%3Cpath d='M0 11 C15 1 30 1 45 11 C60 21 75 21 90 11 V22 H0 Z'/%3E%3C/svg%3E"), linear-gradient(#000, #000), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 90 22'%3E%3Cpath d='M0 11 C15 1 30 1 45 11 C60 21 75 21 90 11 V0 H0 Z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 90 22'%3E%3Cpath d='M0 11 C15 1 30 1 45 11 C60 21 75 21 90 11 V22 H0 Z'/%3E%3C/svg%3E"), linear-gradient(#000, #000), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 90 22'%3E%3Cpath d='M0 11 C15 1 30 1 45 11 C60 21 75 21 90 11 V0 H0 Z'/%3E%3C/svg%3E");
    -webkit-mask-size: 90px 22px, 100% calc(100% - 42px), 90px 22px;
    mask-size: 90px 22px, 100% calc(100% - 42px), 90px 22px;
    -webkit-mask-position: var(--flow-p3, 0px) 0, 0 21px, var(--flow-p3, 0px) 100%;
    mask-position: var(--flow-p3, 0px) 0, 0 21px, var(--flow-p3, 0px) 100%;
  }
  /* Mint settles as the hover state; the white bloom rides on its
     water, centered on the pointer (falls back to a top-center
     bloom for keyboard focus). --px/--py are button-relative, so
     the 25% water offset (= half the sheet width) re-anchors the
     gradient where the sheet's own origin used to be. */
  .btn-flow .btn-flow-layers > span:nth-child(4) {
    --flow-dx: -2.5%;
    transition-duration: 900ms;
    transition-delay: 510ms;
  }
  .btn-flow .btn-flow-layers > span:nth-child(4) .btn-flow-water {
    background:
      radial-gradient(140px circle at calc(25% + var(--px, 25%)) var(--py, 30%),
        rgba(255, 255, 255, 0.38), rgba(255, 255, 255, 0) 72%),
      var(--verde-300);
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 22'%3E%3Cpath d='M0 11 C30 3 60 3 90 11 C120 19 150 19 180 11 V22 H0 Z'/%3E%3C/svg%3E"), linear-gradient(#000, #000), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 22'%3E%3Cpath d='M0 11 C30 3 60 3 90 11 C120 19 150 19 180 11 V0 H0 Z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 22'%3E%3Cpath d='M0 11 C30 3 60 3 90 11 C120 19 150 19 180 11 V22 H0 Z'/%3E%3C/svg%3E"), linear-gradient(#000, #000), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 22'%3E%3Cpath d='M0 11 C30 3 60 3 90 11 C120 19 150 19 180 11 V0 H0 Z'/%3E%3C/svg%3E");
    -webkit-mask-size: 180px 22px, 100% calc(100% - 42px), 180px 22px;
    mask-size: 180px 22px, 100% calc(100% - 42px), 180px 22px;
    -webkit-mask-position: var(--flow-p4, 0px) 0, 0 21px, var(--flow-p4, 0px) 100%;
    mask-position: var(--flow-p4, 0px) 0, 0 21px, var(--flow-p4, 0px) 100%;
  }

  /* Entrance: hover or keyboard focus pours the sheets up to a
     -30% overshoot, each drifting across to the opposite side of
     its resting offset — the wave travels sideways while it rises.
     The overshoot is deep because the 22px wave strip is a FIXED
     height while the sheet is proportional: even the small nav
     pill (~40px) must carry every layer's crest fully past its
     top edge at settle, or slivers of the sheet below peek
     through the valleys. */
  .btn-flow:hover .btn-flow-layers > span,
  .btn-flow:focus-visible .btn-flow-layers > span {
    transform: translate3d(calc(-1 * var(--flow-dx, 0%)), -33%, 0);
  }

  /* Exit: pour on through the top, reverse stagger (mint leads the
     way out, amarillo trails), slightly quicker and with an
     accelerating curve — liquid leaving, not rewinding. The drift
     keeps carrying in the same direction. */
  .btn-flow.is-exiting .btn-flow-layers > span {
    transform: translate3d(calc(-1.5 * var(--flow-dx, 0%)), -126%, 0);
    transition: transform 650ms cubic-bezier(0.5, 0.05, 0.45, 1);
  }
  .btn-flow.is-exiting .btn-flow-layers > span:nth-child(4) { transition-delay: 0ms; }
  .btn-flow.is-exiting .btn-flow-layers > span:nth-child(3) { transition-delay: 110ms; }
  .btn-flow.is-exiting .btn-flow-layers > span:nth-child(2) { transition-delay: 220ms; }
  .btn-flow.is-exiting .btn-flow-layers > span:nth-child(1) { transition-delay: 330ms; }

  /* Reset: sheets teleport back below the pill with transitions off
     (JS forces a reflow between adding and removing this class). */
  .btn-flow.is-resetting .btn-flow-layers > span {
    transition: none;
    transform: translate3d(var(--flow-dx, 0%), 103%, 0);
  }

  /* The pill itself: keep the editorial lift, add a whisper of
     scale on hover and a press on :active (covers .ed-rail-cta,
     which the legacy .btn:active rule never reached). */
  .btn-flow {
    transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1);
  }
  .btn-flow:hover,
  .btn-flow:focus-visible {
    transform: translateY(-1px) scale(1.015);
  }
  .btn-flow:active {
    transform: translateY(0) scale(0.985);
    transition-duration: 120ms;
  }
  /* Keep the base fill steady beneath the sweep — the legacy hover
     recolor would double up with the layers. */
  .btn-flow.btn-primary:hover, .btn-flow.ed-rail-cta:hover {
    background: var(--verde-500);
  }

  /* Inverted context: the ink CTA on the loud verde-vivo money
     panel. The Lotería shift picks the deep half of the palette so
     the white label stays readable on every single frame: cobalt →
     rosa-600 → verde-700 → tinta settle with a deep-verde bloom. */
  .ed-panel--verde-vivo .btn-flow .btn-flow-layers > span:nth-child(1) .btn-flow-water,
  .ed-panel--verde .btn-flow .btn-flow-layers > span:nth-child(1) .btn-flow-water {
    background: var(--azul-700);
  }
  .ed-panel--verde-vivo .btn-flow .btn-flow-layers > span:nth-child(2) .btn-flow-water,
  .ed-panel--verde .btn-flow .btn-flow-layers > span:nth-child(2) .btn-flow-water {
    background: var(--rosa-600);
  }
  .ed-panel--verde-vivo .btn-flow .btn-flow-layers > span:nth-child(3) .btn-flow-water,
  .ed-panel--verde .btn-flow .btn-flow-layers > span:nth-child(3) .btn-flow-water {
    background: var(--verde-700);
  }
  /* Settle sheet: FIXED deep tinta (#2A2A2E = light-mode tinta-700),
     never the themed token. Themed, the settle went bone in dark mode
     under the fixed white label: white on bone at hover settle. */
  .ed-panel--verde-vivo .btn-flow .btn-flow-layers > span:nth-child(4) .btn-flow-water,
  .ed-panel--verde .btn-flow .btn-flow-layers > span:nth-child(4) .btn-flow-water {
    background:
      radial-gradient(140px circle at calc(25% + var(--px, 25%)) var(--py, 30%),
        rgba(0, 166, 81, 0.35), rgba(0, 166, 81, 0) 72%),
      #2A2A2E;
  }
  .ed-panel--verde-vivo .btn-flow.btn-primary:hover,
  .ed-panel--verde .btn-flow.btn-primary:hover {
    background: var(--ink-fixed);
  }

  /* The waterline: while the button is poured (hover/focus) or
     pouring out, every wave drifts slowly sideways — alternating
     directions and speeds per sheet. Rebuilt as transform keyframes
     on the water (mask-position keyframes repainted every frame:
     79% dropped frames on CTA hover). Each loop travels exactly one
     mask tile width, centered ±half a tile around rest so the
     double-width water never exposes an edge — the mask repeats per
     tile, so from == to visually and the loop is seamless. Liquid,
     not wallpaper. Lives inside the hover+motion media gate, so
     touch and reduced-motion never run it, and at rest the sheets
     are off-screen with no animation at all. */
  @keyframes btn-flow-drift-1 {
    from { transform: translateX(-55px); }
    to { transform: translateX(55px); }
  }
  .btn-flow:hover .btn-flow-layers > span:nth-child(1) .btn-flow-water,
  .btn-flow:focus-visible .btn-flow-layers > span:nth-child(1) .btn-flow-water,
  .btn-flow.is-exiting .btn-flow-layers > span:nth-child(1) .btn-flow-water {
    animation: btn-flow-drift-1 7s linear infinite;
  }
  @keyframes btn-flow-drift-2 {
    from { transform: translateX(75px); }
    to { transform: translateX(-75px); }
  }
  .btn-flow:hover .btn-flow-layers > span:nth-child(2) .btn-flow-water,
  .btn-flow:focus-visible .btn-flow-layers > span:nth-child(2) .btn-flow-water,
  .btn-flow.is-exiting .btn-flow-layers > span:nth-child(2) .btn-flow-water {
    animation: btn-flow-drift-2 9.5s linear infinite;
  }
  @keyframes btn-flow-drift-3 {
    from { transform: translateX(-45px); }
    to { transform: translateX(45px); }
  }
  .btn-flow:hover .btn-flow-layers > span:nth-child(3) .btn-flow-water,
  .btn-flow:focus-visible .btn-flow-layers > span:nth-child(3) .btn-flow-water,
  .btn-flow.is-exiting .btn-flow-layers > span:nth-child(3) .btn-flow-water {
    animation: btn-flow-drift-3 8s linear infinite;
  }
  @keyframes btn-flow-drift-4 {
    from { transform: translateX(90px); }
    to { transform: translateX(-90px); }
  }
  .btn-flow:hover .btn-flow-layers > span:nth-child(4) .btn-flow-water,
  .btn-flow:focus-visible .btn-flow-layers > span:nth-child(4) .btn-flow-water,
  .btn-flow.is-exiting .btn-flow-layers > span:nth-child(4) .btn-flow-water {
    animation: btn-flow-drift-4 11s linear infinite;
  }
}


/* ═══════════════════════════════════════════════════════════════════
   BELOW-THE-FOLD RENDER DEFERRAL — 2026-07-19 perf audit.

   The editorial pages style ~90 below-fold panels in one startup
   layout pass (measured 1.6s desktop / FCP 21-34s on 3G).
   `content-visibility: auto` lets the engine skip layout+paint for
   off-screen section shells until they approach the viewport; the
   remembered-size placeholder (auto 900px) keeps the scrollbar sane.

   Explicit shell list only — the top-level blocks used below the
   hero on the two homepages (ed-* grids, section-level panels,
   showcases, FAQ, footer paper + footer). Never a universal
   selector, and never the hero (.ed-grid--hero), nav, or rail —
   those must render in the first frame.

   Scroll-driven reveals (`animation-timeline: view()` above) stay
   compatible: the animation starts when the shell first renders,
   which is before it enters the viewport. Nested shells (ES wraps
   grids in section.ed-panel--tinta) are fine — the outer skip
   covers the inner. */
.ed-grid--thirds,
.ed-grid--split,
.ed-grid--split-wide,
section.ed-panel--verde,
section.ed-panel--tinta,
section.ed-panel--bosque,
.faq-list,
.ed-footer,
.footer {
  content-visibility: auto;
  contain-intrinsic-size: auto 900px;
}


/* ═══════════════════════════════════════════════════════════════════
   THEME LAYER — 2026-08.

   Three appearance axes, in priority order:

     1. `data-theme` on <html>        — the visitor's explicit pick,
                                        written by the nav toggle and
                                        replayed pre-paint by the
                                        inline script in every <head>.
     2. `prefers-color-scheme`        — the OS default, honored with
                                        no JavaScript at all because
                                        html carries `color-scheme:
                                        light dark`.
     3. `prefers-contrast` /
        `forced-colors`               — accessibility overrides that
                                        win over both.

   Nearly all of the retheming already happened up in `:root` via
   `light-dark()`. What's left here is the handful of things that
   aren't colors (blend modes) plus a few places where the dark
   inversion changes which element needs the outline.
   ═══════════════════════════════════════════════════════════════ */

html[data-theme="light"] { color-scheme: light; }
html[data-theme="dark"]  { color-scheme: dark; }

/* Explicit toggle only: soften the dark<->light brightness jump with a
   short cross-ease. scripts/main.js holds `.theme-easing` on <html>
   for ~360ms around the swap; page loads and OS-preference changes
   never get it, so nothing transitions on first paint.
   Scoped to the surfaces that carry area: a universal `*` (plus both
   pseudo-elements) put four transitions on ~1,600 elements and held the
   main thread for over a second on a throttled phone. Text inherits
   the eased colour from body; text inside a panel snaps with it. */
@media (prefers-reduced-motion: no-preference) {
  html.theme-easing,
  html.theme-easing body,
  html.theme-easing .nav,
  html.theme-easing .nav-inner,
  html.theme-easing .ed-rail,
  html.theme-easing .ed-panel,
  html.theme-easing .ed-bar,
  html.theme-easing .carta,
  html.theme-easing .banda,
  html.theme-easing .footer,
  html.theme-easing .ed-footer-paper {
    transition:
      background-color 300ms var(--ease-standard),
      color 300ms var(--ease-standard) !important;
  }
}
/* Where View Transitions exist, scripts/main.js runs the swap inside
   document.startViewTransition and holds `.theme-vt` on <html> for its
   length: one snapshot cross-fade replaces the per-element ease. Only
   the theme swap is retimed; page navigations keep their own fade
   above, and the reduced-motion block there still cuts both to 1ms. */
html.theme-vt::view-transition-old(root),
html.theme-vt::view-transition-new(root) {
  animation-duration: 300ms;
  animation-timing-function: var(--ease-standard);
}

/* Paper grain. On cream stock an OVERLAY pass at 0.55 reads as
   fibre; on ink stock the same pass lifts the blacks into a flat
   grey haze, so dark drops to a gentler SOFT-LIGHT at half strength
   — still tactile, no lift. Not expressible in `light-dark()`.
   Screen only: print always resolves the light tokens. */
@media screen {
  html[data-theme="dark"] {
    --grain-opacity: 0.3;
    --grain-blend: soft-light;
    /* Dark glass: saturation to a whisper, no brightness lift (see the
       nav capsule tokens up top). */
    --nav-glass-saturate: 1.15;
    --nav-glass-saturate-scrolled: 1.3;
    --nav-glass-brightness: 1;
    /* Plain-hex dark fills for engines without light-dark() — mirrors
       the light-dark() second values declared in :root. */
    --fill-verde-vivo: #0C9247;
    --fill-amarillo: #E7B41E;
    --fill-azul: #05378C;
  }
}
@media screen and (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) {
    --grain-opacity: 0.3;
    --grain-blend: soft-light;
    --nav-glass-saturate: 1.15;
    --nav-glass-saturate-scrolled: 1.3;
    --nav-glass-brightness: 1;
    --fill-verde-vivo: #0C9247;
    --fill-amarillo: #E7B41E;
    --fill-azul: #05378C;
  }
}

/* The app-showcase device art used to need a theme-dependent outline
   here, because a bare SVG screenshot with a near-white background
   dissolved into the tinta panel once dark mode inverted it to bone.
   `.device-frame` now wraps every mockup in real hardware — a bezel
   dark in both themes — so the screen is separated from the panel by
   the phone itself and no theme-specific rule is needed. */

/* ---------- High contrast (prefers-contrast: more) ----------
   Users asking for more contrast get the ink ramp pushed to its
   ends, translucency removed, and every hairline promoted to a
   real 2px rule so structure is never carried by a 9%-alpha line. */
@media (prefers-contrast: more) {
  :root {
    --tinta-700: light-dark(#000000, #FFFFFF);
    --tinta-500: light-dark(#1A1A1F, #EDEAE1);
    --tinta-400: light-dark(#1A1A1F, #EDEAE1);
    --hairline: light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.55));
    --hairline-strong: light-dark(#000000, #FFFFFF);
    --canvas-top: light-dark(#FFFFFF, #000000);
    --canvas-bottom: light-dark(#FFFFFF, #000000);
    --hueso: light-dark(#FFFFFF, #000000);
    --blanco: light-dark(#FFFFFF, #0D0D0D);
    --grain-opacity: 0;
  }
  .ed-panel, .ed-bar, .ed-chip, .faq-item, .ed-rail-item, .btn {
    border-width: 2px;
  }
  .ed-panel--verde p, .ed-panel--bosque p { color: var(--paper-fixed); }
  :focus-visible { outline-width: 3px; }
  /* Translucent chrome becomes opaque so text never rides a blur. */
  .nav, .nav-sheet {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: var(--hueso);
  }
}

/* ---------- Windows High Contrast / forced-colors ----------
   In forced-colors the UA replaces every color we set, so the job
   here is purely structural: make sure nothing depends on a color
   we no longer control. Decorative fills are dropped, outlines are
   restated in system keywords, and the focus ring is guaranteed. */
@media (forced-colors: active) {
  .ed-panel, .ed-bar, .ed-chip, .faq-item, .ed-rail-item,
  .ed-rail-cta, .ed-rail-lang, .btn, .fx-card, .fee-ladder {
    border: 2px solid CanvasText;
    forced-color-adjust: none;
    background: Canvas;
    color: CanvasText;
  }
  .btn-primary, .ed-rail-cta {
    background: Highlight;
    color: HighlightText;
    border-color: Highlight;
  }
  :focus-visible { outline: 3px solid Highlight; outline-offset: 2px; }
  /* Pure decoration that carries no meaning. */
  body::before,
  .ed-footer-cell,
  .ed-bar-arrows,
  .btn-flow .btn-flow-layers { display: none; }
  .ed-footer-paper { background-image: none; }
}


/* ═══════════════════════════════════════════════════════════════════
   SKIP LINK — WCAG 2.2 SC 2.4.1 (Bypass Blocks).

   Visually hidden until focused, then lands as a Lotería pill in the
   top-left gutter, above the rail (z-toast). It is the first
   focusable node in the document on every page.
   ═══════════════════════════════════════════════════════════════ */

.skip-link {
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--z-toast, 80);
  /* Off-canvas rather than `display:none` so it stays focusable. */
  transform: translateY(-200%);
  margin: var(--panel-gap, 12px);
  padding: 12px 22px;
  background: var(--amarillo-500);
  color: var(--ink-fixed);
  border: 2px solid var(--ink-fixed);
  border-radius: var(--radius-pill);
  font-weight: 700;
  font-size: var(--fs-body-s);
  text-decoration: none;
  box-shadow: var(--card-lift);
  transition: transform var(--dur-fast) var(--ease-entrance);
}
/* `:focus`, not `:focus-visible`. A skip link is unreachable by
   pointer, so every focus it ever receives is a keyboard focus —
   and gating on `:focus-visible` means it stays hidden when focus
   arrives programmatically (screen-reader virtual cursor, some
   switch-access drivers, automated a11y checks). */
.skip-link:focus {
  transform: translateY(0);
  outline: 3px solid var(--verde-700);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}
/* The landing target must not eat the ring under the fixed nav. */
#contenido, #content { scroll-margin-top: 96px; }


/* ═══════════════════════════════════════════════════════════════════
   APPEARANCE TOGGLE — the sun/moon control in the nav chrome.

   One button, two icons, CSS-only state: the button carries
   `aria-pressed` (true = dark is active) and the icons swap off that
   attribute, so the accessible state and the visual state cannot
   drift apart. Label text is supplied per-locale in the markup.
   ═══════════════════════════════════════════════════════════════ */

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* 44×44 is the HIG minimum touch target. The glyph inside stays
     18px — the target grows, the icon does not. The nav is 68px
     tall and neighbours sit 12px apart, so this fits with 8px of
     clear space either side (adjacent targets must not go flush). */
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  padding: 0;
  background: var(--hueso);
  color: var(--tinta-900);
  border: 2px solid var(--frame-ink);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-standard),
              transform var(--dur-fast) var(--ease-standard);
}
@media (hover: hover) {
  .theme-toggle:hover { background: var(--amarillo-500); color: var(--ink-fixed); }
}
.theme-toggle:active { transform: scale(0.94); }
.theme-toggle:focus-visible { outline: 3px solid var(--verde-700); outline-offset: 2px; }
.theme-toggle svg {
  width: 18px;
  height: 18px;
  transition: opacity var(--dur-fast) var(--ease-standard),
              transform var(--dur-fast) var(--ease-standard);
}
/* Sun shows while light is active, moon while dark is active. Both
   stay in flow and the moon is pulled back over the sun (its 18px plus
   the 6px gap), so the glyphs cross-fade in place while the page eases
   instead of swapping in one frame. The sheet's label keeps its spot. */
.theme-toggle .theme-icon-dark {
  margin-inline-start: -24px;
  opacity: 0;
  transform: rotate(-60deg) scale(0.7);
}
.theme-toggle[aria-pressed="true"] .theme-icon-dark { opacity: 1; transform: none; }
.theme-toggle[aria-pressed="true"] .theme-icon-light { opacity: 0; transform: rotate(60deg) scale(0.7); }
/* No JS → no working toggle → don't advertise one. */
html:not(.js) .theme-toggle { display: none; }

/* Rail variant: full-width card in the deck, matching .ed-rail-lang. */
.ed-rail-theme {
  width: auto;
  height: auto;
  flex: 1 1 0;
  padding: 10px 8px;
  background: var(--lila-300);
  color: var(--ink-fixed);
  border-color: var(--ink-fixed);
  border-radius: var(--radius-pill);
}
@media (hover: hover) {
  .ed-rail-theme:hover { background: var(--amarillo-500); }
}

/* Sheet variant: reads as a labelled row, not a bare icon. */
.nav-sheet .theme-toggle {
  width: 100%;
  height: auto;
  padding: 12px 18px;
  font-weight: 700;
  font-size: var(--fs-body-s);
}
.nav-sheet .theme-toggle .theme-toggle-text { display: inline; }
.theme-toggle-text { display: none; }
/* The sheet row prints its label ("Switch to day mode") beside the
   glyph, so there the glyph names the same destination: the sun beside
   the day label (dark active), the moon beside the night label. The
   icon-only rail and capsule buttons keep the state glyph and their
   Luna/Sol titles. Same in-place cross-fade, mirrored. */
.nav-sheet .theme-toggle .theme-icon-dark { opacity: 1; transform: none; }
.nav-sheet .theme-toggle .theme-icon-light { opacity: 0; transform: rotate(60deg) scale(0.7); }
.nav-sheet .theme-toggle[aria-pressed="true"] .theme-icon-dark { opacity: 0; transform: rotate(-60deg) scale(0.7); }
.nav-sheet .theme-toggle[aria-pressed="true"] .theme-icon-light { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .theme-toggle, .theme-toggle:active { transition: none; transform: none; }
  /* The glyphs still cross-fade, without the turn. */
  .theme-toggle svg { transition: opacity var(--dur-fast) var(--ease-standard); }
  .theme-toggle .theme-icon-dark,
  .theme-toggle[aria-pressed] :is(.theme-icon-light, .theme-icon-dark),
  .nav-sheet .theme-toggle :is(.theme-icon-light, .theme-icon-dark),
  .nav-sheet .theme-toggle[aria-pressed] :is(.theme-icon-light, .theme-icon-dark) { transform: none; }
}
.fee-calc-cap {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 8px;
  padding: 3px 9px;
  border: 2px solid var(--ink-fixed);
  border-radius: var(--radius-pill);
  background: var(--paper-fixed);
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* The cap badge is meaning, not decoration — it must not be the
   only channel, so the fee row also restates it in the label copy
   (WCAG 1.4.1). Hidden until the cap actually binds. */
.fee-calc-cap[hidden] { display: none; }
.region-switch-label {
  display: flex;
  align-items: center;
  padding: 0 14px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-fixed);
  border-right: 2px solid var(--ink-fixed);
  white-space: nowrap;
}
.fee-ladder thead th {
  background: var(--ink-fixed);
  color: var(--paper-fixed);
  font-size: var(--fs-caption);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}


/* ═══════════════════════════════════════════════════════════════════
   ROADMAP TIMELINE — three phases as a hand of Lotería cards.

   Desktop lays the phases out along a single ink rule with a marker
   per phase; mobile stacks them and the rule becomes vertical. The
   status of each phase is carried by a worded pill, never by the
   card's fill alone.
   ═══════════════════════════════════════════════════════════════ */

.roadmap {
  display: grid;
  gap: var(--panel-gap);
  position: relative;
}
@media (min-width: 900px) {
  .roadmap { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.roadmap-phase {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: clamp(22px, 2.6vw, 34px);
  border: var(--panel-border);
  border-radius: var(--panel-radius);
  background: var(--hueso);
  min-width: 0;
}
.roadmap-phase--now { background: var(--verde-500); color: var(--ink-fixed); }
.roadmap-phase--next { background: var(--blanco); }
.roadmap-phase--later { background: var(--hueso); }
.roadmap-phase--now a:not(.btn) { color: var(--ink-fixed); }

.roadmap-marker {
  display: flex;
  align-items: center;
  gap: 10px;
}
.roadmap-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  border: 2px solid currentColor;
  border-radius: var(--radius-pill);
  font-family: var(--font-black);
  font-size: 16px;
}
.roadmap-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border: 2px solid currentColor;
  border-radius: var(--radius-pill);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.roadmap-phase h3 {
  font-family: var(--font-black);
  font-size: clamp(22px, 2.2vw, 30px);
  line-height: 1.08;
}
.roadmap-goal { font-size: var(--fs-body-s); font-weight: 600; }
.roadmap-list {
  display: grid;
  gap: 9px;
  font-size: var(--fs-body-s);
  line-height: 1.45;
}
.roadmap-list li {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
/* Bullet is a drawn square, not a text glyph, so it never gets read
   out and never inherits a font that lacks it. */
.roadmap-list li::before {
  content: "";
  flex-shrink: 0;
  width: 9px;
  height: 9px;
  margin-top: 7px;
  border: 2px solid currentColor;
  border-radius: 50%;
}
.roadmap-metric {
  margin-top: auto;
  padding-top: 14px;
  border-top: 2px solid currentColor;
  font-size: var(--fs-caption);
  line-height: 1.5;
}
.roadmap-metric strong { display: block; margin-bottom: 2px; }


/* ═══════════════════════════════════════════════════════════════════
   TRUST SIGNAL GRID — encryption / receipts / disclosure / support.
   A quieter sibling of `.ed-sec-badges` for use on light panels.
   ═══════════════════════════════════════════════════════════════ */

.trust-grid {
  display: grid;
  gap: var(--panel-gap);
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
}
.trust-signal {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 18px 20px;
  border: 2px solid var(--frame-ink);
  border-radius: var(--panel-radius-sm);
  background: var(--blanco);
  min-width: 0;
}
.trust-signal svg { color: var(--accent-verde); }
.trust-signal h3 { font-size: var(--fs-body); font-weight: 700; }
.trust-signal p { font-size: var(--fs-body-s); line-height: 1.5; color: var(--tinta-700); }
.security-badge-icon {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  color: var(--accent-verde);
}
.security-badge-text { font-size: var(--fs-body-s); }

/* ---------- Stat chips ----------
   Was: value and label side by side on a shared baseline. That reads
   fine when the value is "1%" and falls apart when it is "No markup"
   or "Fee + rate" — the phrase eats half the chip and squeezes the
   label into a four-line ribbon. Stacking makes every chip the same
   shape regardless of how long its value is, and the grid gives the
   row equal columns instead of flex-basis guesswork. */
.ed-chips {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: var(--panel-gap);
}
.ed-chip {
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: 18px 20px;
}

/* ═══════════════════════════════════════════════════════════════════
   INVESTOR BLOCK — lives inside the roadmap page (2026-08).

   The investor page was merged into the roadmap because they were
   answering the same question from two directions: "where is this
   going" for a customer and "where is this going" for a cheque. One
   page, one narrative, one URL to send.

   Every number in this block is sourced inline. A stat without a
   citation is a liability in an investor conversation, so the source
   line is part of the component rather than a footnote.
   ═══════════════════════════════════════════════════════════════ */

.inv-metrics {
  display: grid;
  gap: var(--panel-gap);
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
}
.inv-metric {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 20px 22px;
  border: 2px solid var(--frame-ink);
  border-radius: var(--panel-radius-sm);
  background: var(--blanco);
  min-width: 0;
}
.inv-metric-value {
  font-family: var(--font-black);
  font-size: clamp(28px, 3vw, 42px);
  line-height: 1;
  letter-spacing: -0.02em;
}
.inv-metric-label { font-size: var(--fs-body-s); font-weight: 600; line-height: 1.4; }
.inv-metric-source {
  margin-top: auto;
  padding-top: 8px;
  font-size: var(--fs-caption);
  line-height: 1.45;
  color: var(--tinta-500);
}

/* ---------- The market-size ladder ----------
   TAM → SAM → per-transaction reality, as one continuous argument
   rather than three disconnected numbers. The last rung is
   deliberately the least flattering one; see the copy. */
.inv-ladder {
  display: grid;
  gap: 0;
  border: 2px solid var(--ink-fixed);
  border-radius: var(--panel-radius-sm);
  overflow: hidden;
  counter-reset: rung;
}
.inv-rung {
  display: grid;
  gap: 4px;
  padding: 20px 22px;
  border-bottom: 2px solid var(--ink-fixed);
  background: var(--paper-fixed);
  color: var(--ink-fixed);
}
.inv-rung:last-child { border-bottom: none; }
.inv-rung--tam { background: var(--verde-500); }
.inv-rung--sam { background: var(--amarillo-500); }
/* Fourth rung, added for the expansion ladder on the investor section.
   Sits between the two saturated rungs and the plain closing rung so a
   four-step ladder still reads as a descent rather than two pairs. */
.inv-rung--wallet { background: var(--verde-300); }
.inv-rung--unit { background: var(--paper-fixed); }
.inv-rung-key {
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.inv-rung-value {
  font-family: var(--font-black);
  font-size: clamp(26px, 2.8vw, 38px);
  line-height: 1.05;
  text-wrap: balance;
}
.inv-rung-note { font-size: var(--fs-body-s); line-height: 1.5; max-width: 62ch; }
.inv-rung-math {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  color: var(--note-ink);
}
/* Every rung is a FIXED fill (verde-500, amarillo-500, verde-300,
   paper), so its type is fixed ink in both themes. The themed note gray
   turned bone in dark mode and measured 1.0–2.2:1 on these fills. */
.inv-rung--tam, .inv-rung--sam, .inv-rung--wallet, .inv-rung--unit {
  color: var(--ink-fixed);
  --note-ink: var(--ink-fixed);
}

/* ---------- Numbered "why now" list ---------- */
.inv-now { display: grid; gap: var(--panel-gap); counter-reset: inv-now; }
.inv-now > li {
  counter-increment: inv-now;
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
  padding: 20px 22px;
  border: 2px solid var(--frame-ink);
  border-radius: var(--panel-radius-sm);
  background: var(--hueso);
}
.inv-now > li::before {
  content: counter(inv-now, decimal-leading-zero);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 2px solid currentColor;
  border-radius: var(--radius-pill);
  font-family: var(--font-black);
  font-size: 15px;
}
.inv-now h4 { font-size: var(--fs-body-l); font-weight: 700; margin-bottom: 6px; }
.inv-now p { font-size: var(--fs-body-s); line-height: 1.55; }
.inv-now cite {
  display: block;
  margin-top: 8px;
  font-size: var(--fs-caption);
  font-style: normal;
  color: var(--tinta-500);
}
/* Phone: the 44px counter column cut the copy to ~30 characters a
   line. The counter sits above the text instead, and the text takes
   the card's full measure. */
@media (max-width: 640px) {
  .inv-now > li {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 12px;
    padding: 18px;
  }
}

/* ---------- Book-a-demo ---------- */
.inv-demo-mail {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 18px;
  padding: 14px 22px;
  border: 2px solid var(--ink-fixed);
  border-radius: var(--radius-pill);
  background: var(--paper-fixed);
  color: var(--ink-fixed);
  font-family: var(--font-mono);
  font-size: var(--fs-body);
  font-weight: 700;
  text-decoration: none;
  word-break: break-all;
}
.inv-demo-mail:hover { box-shadow: var(--card-lift); }

/* ---------- App-showcase device art ----------
   Was: a bare flat SVG floating on the panel, which read as a
   screenshot rather than a phone. The SVGs are the screen content
   only, so the hardware goes around them in CSS: a machined bezel, a
   Dynamic Island and a side button. Nothing here is theme-dependent —
   a phone is the same object with the lights off. */
.device-frame {
  position: relative;
  display: block;
  width: 100%;
  max-width: 280px;
  margin: 0 auto;
  padding: 11px;
  border-radius: 48px;
  background:
    linear-gradient(150deg, #4a4a52 0%, #17171b 38%, #0e0e11 62%, #3a3a42 100%);
  box-shadow:
    inset 0 0 0 1.5px rgba(255, 255, 255, 0.22),
    inset 0 0 0 6px #0d0d10,
    0 26px 52px -20px rgba(0, 0, 0, 0.65);
}
/* The screen. `.device-shot` is a <picture> wrapping the real app
   screenshot; the frame supplies the hardware around it. */
.device-frame .device-shot { display: block; }
.device-frame .device-shot img {
  display: block;
  width: 100%;
  max-width: none;
  height: auto;
  border: none;
  border-radius: 38px;
  /* The screenshots are captured at the device's own aspect ratio, so
     `contain` is the honest fit — `cover` would crop the status bar or
     the tab bar depending on the container. */
  object-fit: contain;
  background: #000;
  box-shadow: none;
}

/* Region-aware home screen. There are two real captures of the same
   screen — one holding MXN, one holding USD — and which one is true
   for a visitor depends on where they are sending from. Each page sets
   its own default in a page-scoped <style> so this still resolves with
   JavaScript disabled; `data-region` from scripts/region.js overrides
   it once resolved, and it outranks the page default on specificity
   rather than source order. */
html[data-region="us"] .device-shot--mxn { display: none; }
html[data-region="us"] .device-shot--usd { display: block; }
html[data-region="mx"] .device-shot--mxn { display: block; }
html[data-region="mx"] .device-shot--usd { display: none; }

/* Dynamic Island. Sits 10px into the screen, clear of the topmost
   label in every mockup (the SVGs start their content at y=50). */
.device-frame::after {
  content: "";
  position: absolute;
  top: 21px;
  left: 50%;
  transform: translateX(-50%);
  width: 82px;
  height: 23px;
  border-radius: var(--radius-pill);
  background: #08080a;
  z-index: 2;
  pointer-events: none;
}
/* Side button — the detail that stops the bezel reading as a border. */
.device-frame::before {
  content: "";
  position: absolute;
  right: -2px;
  top: 132px;
  width: 3px;
  height: 58px;
  border-radius: 0 2px 2px 0;
  background: linear-gradient(180deg, #55555e 0%, #23232a 100%);
}


/* ═══════════════════════════════════════════════════════════════════════
   FERIA LAYER (2026-08-12), section 1 of 4: the token block.

   What this layer is: the Lotería system re-toned to the vibra feria
   palette. Same grammar, flat saturated fills inside a 2px ink outline
   on warm paper, hard offset lift, round bubble shape language, liquid
   glass chrome. Only the pigment changes: rosa, azul, ámbar and
   esmeralda replace the 2026-07 cobalto/coral deck, and the paper turns
   from grey cream to real bone.

   Three jobs:
     1. Re-tone. Every neutral and every accent moves to the
        contrast-verified feria values.
     2. Repair dark mode. The old dark ramp had `--hueso-200` and
        `--blanco` landing on the SAME #211D18, so the deepest paper band
        and the raised card were indistinguishable, and `--verde-700` as
        TYPE sat at 2.75:1 on the ink stock. Both are fixed here, the
        second with a new `--verde-ink`.
     3. Add two voices, el pregón (Playfair Display) and la carta
        (Cinzel), both already self hosted.

   This layer is APPEND ONLY. It never deletes and never renames a custom
   property: `marketing/scripts/lint-tokens.mjs` fails CI the moment a
   token referenced from an inline `style=` attribute stops existing, and
   40+ pages reference these names. Values change, names are added, names
   are never removed.

   Because it loads last it wins every equal-specificity tie by source
   order, so `!important` appears only where a genuinely higher
   specificity legacy selector has to be beaten, with the reason spelled
   out beside it. There is none in this section: `:root` re-declarations
   need nothing but source order.

   Custom properties resolve at USE time, so re-toning `--tinta-900` here
   also re-tones `--panel-border` and `--card-lift`, which embed it, and
   re-toning `--canvas-top` / `--canvas-bottom` re-mixes `--canvas`. None
   of those three need restating.
   ═══════════════════════════════════════════════════════════════════════ */

/* ---------- The two new voices ----------
   Both files already live in /assets/fonts. The site is script-src
   'self' and self hosts every face, so a Google Fonts link is not an
   option here, it is a CSP violation. `swap` matches Inter and Archivo
   Black above: the poster reads in the fallback and re-flows once, we
   never hold the headline hostage to a 38KB download. */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('/assets/fonts/PlayfairDisplayVar-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Cinzel';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/assets/fonts/CinzelVar-latin.woff2') format('woff2');
}

:root {
  /* ═════════════════════════════════════════════════════════════════
     PAPEL: the surface ladder.

     Five rungs, warm at both ends: bone cream in light, warm near-black
     in dark. The two-line idiom from the top of this file is kept
     exactly: a plain light value first for pre-2024 engines that drop
     `light-dark()`, then the pair. Never collapse these to one line.

     Bone type (`--tinta-900`) on every dark rung: 16.44 / 15.08 / 14.07
     / 15.11 / 14.63:1. Ink type on every light rung: 16.08 / 14.40 /
     13.87 / 17.18 / 17.49:1. The ladder never costs us contrast.
     ═════════════════════════════════════════════════════════════ */

  /* El papel: the page canvas. */
  --hueso: #FAF5EA;
  --hueso: light-dark(#FAF5EA, #14110D);
  /* Deeper paper bands, for legal callouts, inline sidebars and the
     alternating section rhythm. */
  --hueso-100: #F3E8D3;
  --hueso-100: light-dark(#F3E8D3, #1F1A14);
  /* The old dark value here was #211D18, byte for byte the same as
     `--blanco`, so a card on the deepest band vanished into it. The
     deepest band now sits a real step ABOVE hueso in dark and a real
     step BELOW the card, which is the only way the ladder reads. */
  --hueso-200: #EFE4CE;
  --hueso-200: light-dark(#EFE4CE, #262019);
  /* La carta: the raised card stock. Brighter than the canvas in light,
     lighter than the canvas in dark, in both cases one clear step. */
  --crema: #F1E9D8;
  --crema: light-dark(#F1E9D8, #1A1611);
  /* `--blanco` is still the RAISED surface, not "the colour white". */
  --blanco: #FFFFFF;
  --blanco: light-dark(#FFFFFF, #2E2820);

  /* Vitela: the vellum section band. Same tone as `--hueso-100` in
     light and as `--hueso-200` in dark, but named for its job so a
     section band and a legal callout can drift apart later without a
     find-and-replace across 40 pages. */
  /* The carta face: the reference's warm card white. It gets its own
     name so --crema can stay the recessed band it has always been. */
  --papel-carta: #FFFDF6;
  --papel-carta: light-dark(#FFFDF6, #332C23);

  --vitela: #F3E8D3;
  --vitela: light-dark(#F3E8D3, #262019);

  /* Marcador: the highlighter sweep that runs behind display type. It
     carries the biggest type on the page, so it is the one surface
     tuned against `--tinta-900` rather than against the canvas:
     15.71:1 in light, 10.56:1 in dark. */
  --marcador: #FEF3C7;
  --marcador: light-dark(#FEF3C7, #43340F);

  /* ═════════════════════════════════════════════════════════════════
     TINTA: the ink ramp.

     900 is type and the 2px Lotería outline. 700 is headlines, 500 is
     body and captions, 400 is dimmed metadata, 300 is the quiet rule.
     Warm-neutral now instead of blue-grey, because a cool grey on bone
     paper reads as dirt. Every rung from 900 to 400 clears 4.5:1 on the
     WORST surface in the ladder, in both themes.
     ═════════════════════════════════════════════════════════════ */

  /* The old #0B0B0B is retired as a TOKEN value. It survives untouched
     as a literal at two places in this file, where it is flattened into
     encoded video by ffmpeg at build time. Do not chase those two. */
  --tinta-900: #1C1917;
  --tinta-900: light-dark(#1C1917, #F5EFE3);
  --tinta-700: #3F3A34;
  --tinta-700: light-dark(#3F3A34, #E4DCCC);
  --tinta-500: #57534E;
  --tinta-500: light-dark(#57534E, #B8AF9C);
  /* Still held at ≥ 4.5:1 on every paper rung, including the deepest:
     4.58:1 light, 4.61:1 dark. */
  --tinta-400: #6B655C;
  --tinta-400: light-dark(#6B655C, #A99F8A);
  /* 300 is a RULE, not type. It is deliberately quiet (1.48:1 light,
     1.78:1 dark) and matches the posture it already had. Anything that
     needs a visible 3:1 edge takes `--panel-border`, not this. */
  --tinta-300: #D5CBB6;
  --tinta-300: light-dark(#D5CBB6, #453E33);

  /* ═════════════════════════════════════════════════════════════════
     LA FERIA, part 1: the FILL family.

     A fill plus the type that sits on it is a self-contained pair, so
     these are mode INDEPENDENT: one value, no `light-dark()`, ever. The
     deck does not change colour when you turn the lights off.

       white on rosa-500     4.70:1
       white on rosa-600     6.29:1
       white on azul-700     6.70:1
       ink on amarillo-500   8.14:1
       ink on amarillo-600   5.49:1
       ink on verde-500      4.64:1
       white on verde-700    6.84:1

     FORBIDDEN: white type on `--verde-500` (3.77:1). Esmeralda fills
     carry ink, never paper.
     ═════════════════════════════════════════════════════════════ */
  --amarillo-500: #F59E0B;
  /* New rung: the ámbar that still takes ink at body size when 500 is
     too bright next to it, and the hover state for ámbar fills. */
  --amarillo-600: #D97706;
  --azul-700: #1D4ED8;
  --rosa-500: #E11D48;
  --rosa-600: #BE123C;
  /* Pedro's colours from his card art, for the scroll chapter sprite.
     Fixed fills, never type colours, and never themed: he stays cream
     with green gills in dark mode too. */
  --pedro-crema: #FDFAF0;
  --pedro-oliva: #6C7845;
  --pedro-verde: #A9C34E;
  --pedro-rubor: #FEB4AD;
  --verde-500: #059669;
  /* Brand constant. Restated, not re-toned, and never a pair: the flag
     green is the flag green in both themes. */
  --verde-700: #006847;

  /* ═════════════════════════════════════════════════════════════════
     LA FERIA, part 2: the INK family.

     The same four hues used as TYPE, ICON STROKE or CARD FRAME on the
     page canvas rather than as a fill. These MUST flip by mode. The
     reference palette fails outright as type on paper (ámbar 1.98:1,
     esmeralda 3.47:1, rosa 4.32:1) which is exactly why the two
     families are split.

     The trap this closes: a carta frame drawn with `--azul-700` lands
     at 2.81:1 on the dark canvas, under the 3:1 graphic floor.
     `--azul-ink` is 9.15:1 there. Frames take the INK family.

       rosa-ink   5.78:1 light / 6.99:1 dark, on hueso
       azul-ink   6.16:1 light / 9.15:1 dark
       ámbar-ink  4.62:1 light / 11.27:1 dark
       esm-ink    5.04:1 light /  9.79:1 dark
       verde-ink  6.29:1 light /  9.79:1 dark

     Caveat worth knowing before you place ámbar or esmeralda TYPE:
     those two lose about 0.6 of a point on the deepest paper band
     (3.98:1 and 4.35:1 on `--hueso-200`). On the deep bands they are
     display-size only (3:1 large-text floor), or hand the copy to
     `--tinta-900`. Rosa and azul are safe on every rung.
     ═════════════════════════════════════════════════════════════ */
  --rosa-ink: #BE123C;
  --rosa-ink: light-dark(#BE123C, #FB7185);
  --azul-ink: #1D4ED8;
  --azul-ink: light-dark(#1D4ED8, #93B4FF);
  --ambar-ink: #B45309;
  --ambar-ink: light-dark(#B45309, #FBBF24);
  --esmeralda-ink: #047857;
  --esmeralda-ink: light-dark(#047857, #34D399);
  /* The brand green as TYPE. `--verde-700` is 6.29:1 on bone paper but
     2.75:1 on ink stock, which is what made the wordmark and every
     stroked verde glyph disappear in dark mode. This is the same hue
     family read from the other end of the ramp. */
  --verde-ink: #006847;
  --verde-ink: light-dark(#006847, #34D399);

  /* Wordmark and stroked glyphs. Points at the verde-ink pair by hand
     rather than by `var()` so the plain fallback line still stands on
     its own for engines that drop `light-dark()`. */
  --accent-verde: #006847;
  --accent-verde: light-dark(#006847, #34D399);
  /* The green period that closes the hero headline. It is punctuation,
     so it is TYPE, so it takes the ink pair too. Dark used to jump to
     the #00D632 app green, which shouted from outside the deck. */
  --brand-dot: #006847;
  --brand-dot: light-dark(#006847, #34D399);

  /* ---- Theme-invariant ink pair -------------------------------
     Unchanged in role, re-toned in value: the ink is now the same warm
     near-black as `--tinta-900` light, so a label on an ámbar fill is
     the exact colour of body copy on paper. These two must NEVER become
     pairs, they are what keeps ink-on-ámbar and white-on-rosa correct
     through a theme flip.
       • foreground on a BRIGHT fill  → var(--ink-fixed)
       • foreground on a DEEP fill    → var(--paper-fixed)
       • everything else              → the tinta ramp / hueso */
  --ink-fixed: #1C1917;
  --paper-fixed: #FFFFFF;

  /* ---- Pastel tints -------------------------------------------
     The round icon bubbles on a carta, one per colourway. Light values
     are the palest rung of each hue, dark values are that hue mixed
     into the ink stock rather than a dimmed pastel, which is what keeps
     them from turning to mud. Bone type on the four dark tints:
     15.03 / 14.13 / 13.33 / 13.36:1. Each hue's own ink on its own
     bubble: 5.72 / 6.16 / 4.51 / 5.21 light, 6.39 / 7.87 / 9.14 /
     7.96 dark. */
  --rosa-050: #FFF1F2;
  --rosa-050: light-dark(#FFF1F2, #2C1418);
  --azul-050: #EFF6FF;
  --azul-050: light-dark(#EFF6FF, #14203A);
  --ambar-050: #FEF3C7;
  --ambar-050: light-dark(#FEF3C7, #2E2410);
  --esmeralda-050: #ECFDF5;
  --esmeralda-050: light-dark(#ECFDF5, #0F2A20);

  /* ---- Hairlines ----------------------------------------------
     Re-mixed from the new ink and the new bone, because the old ones
     were literal rgba(11, 11, 11, …) and rgba(246, 242, 232, …) and now
     read a shade cool against feria paper. Alphas are nudged up in dark
     where a 14% bone line on near-black was doing nothing: flattened,
     the quiet rule is 1.22:1 light / 1.54:1 dark and the strong rule is
     1.52:1 light / 2.50:1 dark. Structural edges still take
     `--panel-border`. */
  --hairline: rgba(28, 25, 23, 0.10);
  --hairline: light-dark(rgba(28, 25, 23, 0.10), rgba(245, 239, 227, 0.16));
  --hairline-strong: rgba(28, 25, 23, 0.20);
  --hairline-strong: light-dark(rgba(28, 25, 23, 0.20), rgba(245, 239, 227, 0.30));

  /* ---- Canvas -------------------------------------------------
     Top rung is carta white, bottom rung is vitela, so the page ground
     runs pale to warm exactly like a printed sheet lit from the top.
     `--canvas` itself is a gradient over these plus `--hueso` and needs
     no restatement. Ink on the light ends: 17.18:1 and 14.40:1. Bone on
     the dark ends: 15.73:1 and 17.06:1. */
  --canvas-top: #FFFDF6;
  --canvas-top: light-dark(#FFFDF6, #1A1610);
  --canvas-bottom: #F3E8D3;
  --canvas-bottom: light-dark(#F3E8D3, #0E0C09);

  /* ---- Halftone dot grid --------------------------------------
     The feria texture: a rosa and azul dot screen for hero canvases,
     the printed-poster tell. Pure texture, so no contrast floor
     applies, but the alphas still flip by mode because a 14% rosa dot
     that is visible on bone is invisible on ink stock. Flattened over
     the canvas the dots land at 1.20:1 to 1.33:1, which is texture you
     feel rather than pattern you read, and leaves every type pairing
     above untouched. */
  --dot-rosa: rgba(225, 29, 72, 0.14);
  --dot-rosa: light-dark(rgba(225, 29, 72, 0.14), rgba(251, 113, 133, 0.18));
  --dot-azul: rgba(29, 78, 216, 0.12);
  --dot-azul: light-dark(rgba(29, 78, 216, 0.12), rgba(147, 180, 255, 0.16));
  --dot-cell: 26px;

  /* ---- Typography voices --------------------------------------
     `--font-black` (Archivo Black) stays el cartel: display, bars,
     wordmark. `--font-body` stays body copy. These two are new:

       el pregón, the editorial serif that announces a section,
       la carta, the engraved smallcaps of a Lotería card: the N
       numeral, the rule labels, the PASO feet.

     Both fallback stacks are local serifs only, in the same spirit as
     the body stack: Apple hardware gets Iowan Old Style, Windows gets
     Palatino Linotype, everything else falls through to Georgia, so a
     slow font load degrades to a serif rather than to a grotesk. */
  --font-pregon: 'Playfair Display', 'Iowan Old Style', 'Palatino Linotype',
                 Palatino, 'Book Antiqua', Georgia, 'Times New Roman', serif;
  --font-carta: 'Cinzel', 'Optima', 'Palatino Linotype', Palatino,
                'Book Antiqua', Georgia, serif;

  /* ---- Icon sizing --------------------------------------------
     Iconoir paths carry stroke="currentColor", so an icon themes itself
     from whatever `color` its container already resolved: no icon needs
     a colour token, only a size. 24 is the default beside body copy, 18
     rides inside chips and table cells, 32 heads a carta, 44 fills a
     bubble. `--icon-stroke` is unitless on purpose, it is a
     `stroke-width` in the SVG's own user units. */
  --icon-size: 24px;
  --icon-size-s: 18px;
  --icon-size-l: 32px;
  --icon-size-xl: 44px;
  --icon-stroke: 1.6;
  --icon-bubble: 44px;

  /* ---- Liquid glass, re-toned not removed ---------------------
     The glass physics (blur, saturate, edge lighting, the btn-flow
     sweep) are untouched. Only the TINT moves: every surface above was
     mixed from the old rgba(250, 247, 242) grey-cream and the old
     rgba(11, 11, 11) ink, both of which now read cool against feria
     paper. Same alphas, so nothing about the material changes, only
     which paper it is standing on. */
  --glass-surface-light: rgba(250, 245, 234, 0.62);
  --glass-surface-light: light-dark(rgba(250, 245, 234, 0.62), rgba(31, 26, 20, 0.66));
  --glass-surface-regular: rgba(250, 245, 234, 0.36);
  --glass-surface-regular: light-dark(rgba(250, 245, 234, 0.36), rgba(31, 26, 20, 0.44));
  --glass-surface-chrome: rgba(250, 245, 234, 0.74);
  --glass-surface-chrome: light-dark(rgba(250, 245, 234, 0.74), rgba(20, 17, 13, 0.82));
  --glass-surface-luminous: rgba(255, 253, 246, 0.66);
  --glass-surface-luminous: light-dark(rgba(255, 253, 246, 0.66), rgba(38, 32, 25, 0.70));
  --glass-surface-luminous-strong: rgba(255, 253, 246, 0.82);
  --glass-surface-luminous-strong: light-dark(rgba(255, 253, 246, 0.82), rgba(38, 32, 25, 0.86));
  --glass-surface-dark: rgba(28, 25, 23, 0.42);
  --glass-surface-dark-regular: rgba(28, 25, 23, 0.58);
  /* The brand-tinted glass followed the old #00A651 / #006847 pair.
     Re-mixed onto the feria greens so a verde CTA and a verde glass
     chip are the same green. */
  --glass-tint-verde: rgba(5, 150, 105, 0.18);
  --glass-tint-verde-strong: rgba(4, 120, 87, 0.32);
}

/* ---------- High contrast (prefers-contrast: more) ----------
   RESTATED, not duplicated by accident. The original block sits earlier
   in this file, so the `:root` above would silently defeat it: same
   specificity, later source order, media queries carry no extra weight.
   Every token the feria block re-tones has to be answered here or the
   high-contrast experience quietly reverts to the normal palette.

   The posture is unchanged: push the ink ramp to its ends, drop
   translucency, promote every hairline to a real rule. What is new is
   the paper. The neutrals the old block never covered (`--tinta-900`,
   `--tinta-300`, `--crema`, `--hueso-100`, `--hueso-200`) are the ones
   that carry the feria ladder, and left alone they would keep five
   near-identical creams under a pure white canvas.

   So the ladder collapses to two rungs: the canvas, and one visibly
   deeper band. Five creams separated by 3% luminance are noise at this
   setting, but structure still has to be visible, which is why
   `--hueso-200` and `--vitela` keep a real step instead of flattening
   to white. Ink on those: 19.26:1 and 18.43:1 light, 18.73:1 and
   19.17:1 dark.

   The greys below (#0A0A0A, #0F0F0F, #121212, #F0F0F0, #F5F5F5,
   #1F1B18, #F7F2E8) are high-contrast-mode neutrals, not palette
   additions, exactly like the #1A1A1F and #EDEAE1 this block already
   used. No feria hue is redefined here: hardening the paper to pure
   white and pure black RAISES the whole ink family on its own, ámbar
   from 4.62:1 to 5.02:1 in light and 11.27:1 to 12.58:1 in dark, so
   the accents need no separate promotion.

   `--panel-border` and `--card-lift` embed `--tinta-900`, so pinning it
   to pure ink or pure bone here hardens every outline and every lift in
   the system with one line. */
@media (prefers-contrast: more) {
  :root {
    --tinta-900: light-dark(#000000, #FFFFFF);
    --tinta-700: light-dark(#000000, #FFFFFF);
    --tinta-500: light-dark(#1F1B18, #F7F2E8);
    --tinta-400: light-dark(#1F1B18, #F7F2E8);
    /* The quiet rule stops being quiet: 1px dividers become real ink. */
    --tinta-300: light-dark(#000000, #FFFFFF);
    --hairline: light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.55));
    --hairline-strong: light-dark(#000000, #FFFFFF);
    --canvas-top: light-dark(#FFFFFF, #000000);
    --canvas-bottom: light-dark(#FFFFFF, #000000);
    --hueso: light-dark(#FFFFFF, #000000);
    --hueso-100: light-dark(#FFFFFF, #0A0A0A);
    --hueso-200: light-dark(#F5F5F5, #121212);
    --crema: light-dark(#FFFFFF, #0A0A0A);
    --vitela: light-dark(#F0F0F0, #0F0F0F);
    --blanco: light-dark(#FFFFFF, #0D0D0D);
    --grain-opacity: 0;
  }
  /* The `--grain-opacity: 0` above never actually reached dark-mode
     users, and still would not, because the grain override lives on
     `html[data-theme="dark"]` and `html:not([data-theme="light"])`
     (0,1,1) while `:root` is only (0,0,1). Specificity, not source
     order, was beating it. Matching that specificity here fixes it
     without an `!important`: someone who asked for MORE contrast in
     dark mode was still being served a soft-light grain pass over
     their type. */
  html[data-theme="dark"],
  html:not([data-theme="light"]) {
    --grain-opacity: 0;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   FERIA LAYER, 2 of 4: COMPONENT RETONE.

   Section 1 re-tones everything that already consumes tokens. This
   section is the residue: rules that pinned a literal, froze a stale
   neutral, or survived from a pre-editorial layer and would sail
   straight through a token retone unchanged. Nothing below restates a
   rule that already works via tokens.

   Depends on section 1 declaring three names: --font-carta,
   --font-pregon and --esmeralda-ink. Every other token used here
   already exists in the file, so lint-tokens.mjs stays green.

   This block is appended last, so equal-specificity rules win on
   source order and !important is not needed for them. It appears in
   exactly two places below, both against styles/how-it-works.css,
   which is a SEPARATE stylesheet loaded AFTER main.css: source order
   cannot reach it and matching its specificity still loses.
   ═══════════════════════════════════════════════════════════════ */

/* ---------- 2.1 Frames that pinned --ink-fixed ---------- */

/* The rule, stated once. A 2px frame that sits ON a saturated fill
   belongs to that fill's fixed pair and stays --ink-fixed: the fill
   does not invert, so neither may its outline or its label. A frame
   that separates an element from the PAGE CANVAS follows --tinta-900
   and flips to bone in dark, which is the signature of the system.
   Every frame below is labelled with which case it is, because the
   two look identical in a diff and are not interchangeable. */

/* ON a fill: the same esmeralda pill, in the rail. */
.ed-rail-cta:not(.btn) { border: 2px solid var(--ink-fixed); }

/* ON a fill: rosa is a deep fixed fill carrying white type, and ink
   on rosa is 3.72:1, over the 3:1 graphic floor, in both themes. */
.ed-rail-lang { border: 2px solid var(--ink-fixed); }

/* ON a fill: --lila-300 is a single fixed pastel with no light-dark
   pair, so the toggle stays a light chip in both themes and its frame
   and glyph stay fixed ink with it. Bone on lila would land near
   1.3:1. Its hover fill (--amarillo-500) is fixed-bright too, so the
   same ink holds through the whole interaction. */
.ed-rail-theme { border-color: var(--ink-fixed); }

/* ON the CANVAS, and ALREADY CORRECT: this one reads --tinta-900, not
   --ink-fixed. Recorded here because it looks like the odd one out
   and is not. The cells are 28px squares with no content, so the
   frame IS the object's edge against the grid paper rather than part
   of a fill. It is also load-bearing in dark, where the azul cell on
   its own reads 2.81:1 against the canvas, under the 3:1 floor: the
   bone frame is what rescues it. Do not pin this to --ink-fixed. */
.ed-footer-cell { border: 2px solid var(--tinta-900); }

/* ---------- 2.2 Frozen neutrals: two literals that lied ---------- */

/* Both hexes were copied out of the neutral ramp back when the ramp
   had light values only, so they are stale twice over: they miss the
   feria retone AND they cannot theme. Pointing them straight at
   --tinta-700 / --tinta-500 is the obvious fix and it is wrong. Both
   sit inside a FIXED pair, on a surface that deliberately does not
   invert, so the themed ramp would flip them to bone on white:
   --tinta-500 on fixed white in dark is 2.57:1. The correct
   expression is a fixed neutral derived from the two fixed tokens,
   which drifts with nothing and is still not a magic hex. */

/* Was #2A2A2E, a frozen light --tinta-700. Hover step for the ink
   pill that verde-vivo inverts its primary CTA into: paper-fixed type
   on fixed ink, so the hover has to stay in the fixed family. 12%
   toward paper is a 1.43:1 lift off the rest state and still holds
   12.2:1 under the label. */
.ed-panel--verde-vivo .btn-primary:hover {
  background: color-mix(in srgb, var(--ink-fixed) 88%, var(--paper-fixed));
}

/* ---------- 2.3 Legacy eyebrow bleed on the dark sections ---------- */

/* The legacy eyebrow shells for .pricing, .cta-final, .trust and
   .how-gallery were removed: those four class names match zero of the
   43 pages, so the rules were dead weight. The feria kicker is defined
   once, above. */


/* ---------- 2.4 Ghost button: the last glass bleed ---------- */

/* The editorial layer gave .btn-ghost an ink outline and an invert on
   hover, but its override at ~3866 sets background and color only, so
   the legacy hover at ~891 still contributes an inset white specular
   and a green corona: a mint glow around a near-black pill. The base
   also still asks for --glass-radius-pill. Same 9999px, but the
   system has one pill token and this should be it. */
.btn-ghost { border-radius: var(--radius-pill); }
.btn-ghost:hover {
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* Ghost on the deep panels. verde-700 and azul-700 never invert, so
   an ink outline and ink label on them are unreadable in BOTH themes.
   The kicker standing next to this button already inverts; the button
   was missed. White on either fill is 6.84:1 and 6.70:1, and the
   hover flips to the fill's own fixed pair. */
.ed-panel--verde .btn-ghost,
.ed-panel--bosque .btn-ghost {
  color: var(--paper-fixed);
  border-color: var(--paper-fixed);
}
@media (hover: hover) {
  .ed-panel--verde .btn-ghost:hover,
  .ed-panel--bosque .btn-ghost:hover {
    background: var(--paper-fixed);
    color: var(--ink-fixed);
  }
}

/* ---------- 2.5 .btn-outline: never touched, still glass ---------- */

/* The editorial layer skipped this one entirely, so the sign-in
   button in the nav is still a 1px translucent blurred pill on a page
   with no other glass buttons left on it. Bring it in as the middle
   rung of a three step hierarchy: primary is a saturated fill,
   outline is a raised carta-white card, ghost is the bare outlined
   pill. --tinta-900 on --crema is 16.44:1 light and 15.11:1 dark, and
   --card-lift is the system's only elevation, a hard folk-poster
   offset, never a blur.
   Hover borrows the "turns golden" gesture the theme toggle already
   owns, and is 2.1's rule applied dynamically: at rest the frame sits
   on a themed surface so it follows --tinta-900, on hover it sits on
   a fixed bright amarillo so frame and label both go to fixed ink.
   The lift stays --tinta-900 in both states because it is a shadow on
   the paper, not part of the pill. */
.btn-outline {
  background: var(--crema);
  color: var(--tinta-900);
  border: 2px solid var(--tinta-900);
  border-radius: var(--radius-pill);
  box-shadow: var(--card-lift);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.btn-outline:hover {
  background: var(--amarillo-500);
  color: var(--ink-fixed);
  border-color: var(--ink-fixed);
  box-shadow: var(--card-lift);
  transform: translateY(-1px);
}

/* ---------- 2.6 .btn-rojo: the missing semantic ---------- */

/* scripts/main.js has listed .btn-rojo in its shimmer selector since
   the iridescent layer landed, and the class has never had a single
   declaration: all it currently inherits are the sheen pseudo-
   elements at ~1052 to ~1114. Rojo is SEMANTIC ONLY, errors and
   legal, so this is the destructive control (cancel a transfer, close
   an account, revoke a device), never a second primary and never
   decoration. It is therefore built as a warning card rather than a
   filled button, which is the same grammar the inline error callouts
   at ~2715 and ~3041 already speak: rojo frame and rojo type on the
   rojo tint. Label 6.84:1 light and 8.63:1 dark, frame 4.93:1 and
   6.02:1 against its own tint. The tint is deliberately close to the
   paper, so on a bare hueso canvas it is the frame that carries the
   shape, at 5.18:1. */
.btn-rojo {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 14px 26px;
  background: var(--rojo-050);
  color: var(--rojo-700);
  border: 2px solid var(--rojo-500);
  border-radius: var(--radius-pill);
  font-weight: 700;
  text-decoration: none;
  box-shadow: none;
  transition: background-color var(--dur-fast) var(--ease-standard);
}
.btn-rojo:hover { background: var(--rojo-100); }
/* Beats the shared .btn:focus-visible verde ring at ~3867 on source
   order: a destructive control keeps its own colour through focus. */
.btn.btn-rojo:focus-visible,
.btn-rojo.btn-rojo:focus-visible { outline: 3px solid var(--rojo-500); outline-offset: 2px; }
/* A destructive control must not glitter. Same kill the editorial
   layer already applies to .btn-primary at ~4349, and it turns the
   is-shining class main.js adds into a deliberate no-op here. */
.btn-rojo::before,
.btn-rojo::after { display: none; }

/* ---------- 2.8 Typography voices, applied conservatively ---------- */

/* Three faces, three jobs. The split matters more than either new
   face does, so it is written down rather than implied:

     Archivo Black (--font-black) REMAINS the structural display face:
       h1, h2, .display-*, the .ed-bar titles, the wordmark, the big
       .ed-chip-num figures. Nothing in this section touches it. The
       site's display voice is not being swapped.
     Cinzel (--font-carta) is the engraved label: kickers, chip
       labels, PASO numerals. Its lowercase glyphs ARE small capitals,
       so these keep text-transform: none and let the face do the work
       instead of uppercasing on top of it.
     Playfair Display (--font-pregon) is the editorial serif and has
       exactly two homes in the system: carta titles (the .carta
       primitive, section 3) and pull quotes. It is not a body face
       and it is not a display face. */

.eyebrow {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: 0.045em;
  text-transform: none;
}

/* The giant PASO numeral heading each how-it-works step: those
   sections are literally id="paso-1" and up. Two faults at once. It
   hardcodes 'Inter', and it is painted --verde-300, a fixed mint that
   reads 1.75:1 on the light canvas, under the 3:1 floor even at
   132px. --esmeralda-ink is the INK-family answer to a green that has
   to behave as type: 5.04:1 light, 9.79:1 dark.
   This is the second and last place !important is used. Two
   declarations compete, both parsed after main.css: .how-step-num in
   styles/how-it-works.css, and .hiw-copy .how-step-num at (0,2,0) in
   a page <style>. All four properties carry the flag because the
   competing blocks set face, weight and tracking together, and
   winning only part of that would hang Archivo tracking on Cinzel. */
.how-step-num {
  font-family: var(--font-carta) !important;
  font-weight: 700 !important;
  letter-spacing: 0.02em !important;
  color: var(--esmeralda-ink) !important;
}

/* Pull quote: the one editorial-serif slot outside the .carta
   primitive. Playfair ships here as a weight axis only, there is no
   italic file, so the previous font-style: italic would have been
   synthesised into a machine skew. Weight carries the emphasis
   instead. The opening mark drops --verde-500, which is 3.47:1 as
   type on paper, for the INK-family esmeralda.
   Deliberately NO border: three .pullquote elements on /en/help carry
   an inline border-color that is inert today because nothing declares
   a border-style. Adding one here would switch on three surprise
   outlines. */
.pullquote {
  font-family: var(--font-pregon);
  font-style: normal;
  font-weight: 600;
  letter-spacing: 0;
}
.pullquote::before {
  color: var(--esmeralda-ink);
  font-weight: 700;
}
/* The attribution is a caption, not part of the quote, so it stays in
   the body face rather than inheriting Playfair from the parent. */
.pullquote cite { font-family: var(--font-body); }

/* ═══════════════════════════════════════════════════════════════════
   FERIA LAYER, SECTION 3: DARK MODE REPAIR.

   Dark mode shipped as a token flip: every neutral got a `light-dark()`
   partner and 4k lines of surface rules retheme for free. What did NOT
   flip is everything written as a LITERAL: the cream veil on the nav
   at rest, ~90 `rgba(255,255,255,alpha)` specular values in the Liquid
   Glass layer, a handful of `rgba(0,0,0,0.1)` hairlines, and the deep
   verde ramp used as TYPE. Deep verde is 6.4:1 on cream paper and
   2.73:1 on ink stock, so every place it carries meaning goes illegible
   the moment the lights go out.

   This section repairs those, and only those. It changes VALUES and
   ADDS tokens; it never deletes or renames one, because
   scripts/lint-tokens.mjs fails CI on a vanished name and 40+ pages
   reference tokens from inline style attributes.

   Two cascade facts govern everything below:

     1. This layer is appended last, so an equal-specificity rule wins
        by source order. Nothing in this section needs `!important`;
        the one cross-file case is settled with a `:root ` prefix.
     2. Source order cuts both ways. `@supports`, `@media
        (prefers-contrast)` and `@media (forced-colors)` add NO
        specificity, so a plain rule written down here silently
        outranks the accessibility overrides written up there. Every
        rule below that lands on a selector those blocks also touch is
        restated at the end of this section under "re-assert".
   ═══════════════════════════════════════════════════════════════ */

:root {
  /* ---- Specular family --------------------------------------------
     The Liquid Glass layer fakes light-from-above with white at low
     alpha. On cream stock that is a highlight; on ink stock it is a
     smear of daylight sitting on a night surface. The dark partner is
     bone (the dark value of `--tinta-900`) at roughly a sixth of the
     alpha: on a dark ground a highlight only has to be a whisper
     before it stops reading as light and starts reading as a stroke.

     Six rungs, named for the job rather than the number, so a new
     rule picks the right one by reading it out loud. Light values are
     the ones already in the file, so nothing shifts on paper. */
  --specular-veil: rgba(255, 255, 255, 0.18);
  --specular-veil: light-dark(rgba(255, 255, 255, 0.18), rgba(245, 239, 227, 0.05));
  --specular-wash: rgba(255, 255, 255, 0.32);
  --specular-wash: light-dark(rgba(255, 255, 255, 0.32), rgba(245, 239, 227, 0.06));
  --specular-ring: rgba(255, 255, 255, 0.42);
  --specular-ring: light-dark(rgba(255, 255, 255, 0.42), rgba(245, 239, 227, 0.08));
  --specular-surface: rgba(255, 255, 255, 0.55);
  --specular-surface: light-dark(rgba(255, 255, 255, 0.55), rgba(245, 239, 227, 0.09));
  --specular-seam: rgba(255, 255, 255, 0.65);
  --specular-seam: light-dark(rgba(255, 255, 255, 0.65), rgba(245, 239, 227, 0.11));
  --specular-crest: rgba(255, 255, 255, 0.78);
  --specular-crest: light-dark(rgba(255, 255, 255, 0.78), rgba(245, 239, 227, 0.12));

  /* ---- The glass edge tokens, given their dark partner -------------
     Highest-leverage fix in this section. These eight were declared
     once, in white, with no `light-dark()` restatement, and they feed
     `.glass`, `.glass-panel`, `.btn-outline`, `.faq-item`,
     `.security-badge` and every caption. Re-declaring them here fixes
     every consumer at once without touching a single call site. Light
     values are byte-identical to what shipped. */
  --glass-surface-clear: rgba(255, 255, 255, 0.12);
  --glass-surface-clear: light-dark(rgba(255, 255, 255, 0.12), rgba(245, 239, 227, 0.05));
  --glass-edge-top: rgba(255, 255, 255, 0.62);
  --glass-edge-top: light-dark(rgba(255, 255, 255, 0.62), rgba(245, 239, 227, 0.1));
  --glass-edge-top-dark: rgba(255, 255, 255, 0.18);
  --glass-edge-top-dark: light-dark(rgba(255, 255, 255, 0.18), rgba(245, 239, 227, 0.05));
  --glass-edge-bottom: rgba(255, 255, 255, 0.14);
  --glass-edge-bottom: light-dark(rgba(255, 255, 255, 0.14), rgba(245, 239, 227, 0.04));
  --glass-edge-stroke: rgba(255, 255, 255, 0.42);
  --glass-edge-stroke: light-dark(rgba(255, 255, 255, 0.42), rgba(245, 239, 227, 0.08));
  --glass-edge-stroke-dark: rgba(255, 255, 255, 0.1);
  --glass-edge-stroke-dark: light-dark(rgba(255, 255, 255, 0.1), rgba(245, 239, 227, 0.04));
  --glass-edge-stroke-warm: rgba(250, 245, 234, 0.5);
  --glass-edge-stroke-warm: light-dark(rgba(250, 245, 234, 0.5), rgba(245, 239, 227, 0.1));
  --glass-edge-luminous: rgba(255, 255, 255, 0.85);
  --glass-edge-luminous: light-dark(rgba(255, 255, 255, 0.85), rgba(245, 239, 227, 0.14));


  /* ---- The keyboard ring -------------------------------------------
     Every ring built from `--verde-700` is 2.73:1 on ink stock and the
     bare `:focus-visible` default built from `--verde-500` is 2.99:1
     on cream paper, so the site failed the 3:1 non-text minimum (WCAG
     2.2 SC 1.4.11) in one theme or the other everywhere. This is the
     contract's esmeralda ink pair: 5.04:1 on papel, 9.79:1 on ink
     stock, both canvases clear with room to spare.

     Rings sitting ON a saturated panel are NOT repointed: those are
     the `--paper-fixed` / `--ink-fixed` rings a few rules further up
     the file, and they are correct because their fill does not flip. */
  --focus-ring: #047857;
  --focus-ring: light-dark(#047857, #34D399);

  /* ---- Select chevron ----------------------------------------------
     A data URI cannot take a `light-dark()` colour: the colour lives
     inside the encoded SVG, so the whole URL has to swap. Both strokes
     are the two halves of `--tinta-500`, which is what the chevron was
     reaching for with its hardcoded `%23737373`. The dark URL is
     assigned in the two theme blocks directly below, using the same
     data-theme plus prefers-color-scheme pair the theme layer already
     uses for the paper grain. */
  --fx-chevron: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%2357534E' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
}

@media screen {
  html[data-theme="dark"] {
    --fx-chevron: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23B8AF9C' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  }
}
@media screen and (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) {
    --fx-chevron: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23B8AF9C' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  }
}


/* ───────────────────────────────────────────────────────────────────
   1. THE NAV.

   The capsule is main.css's own (the PR #116 nav, tint and edge light
   themed at the source): nothing in this layer restyles it (owner,
   2026-09-22). Only the backdrop-filter fallback for .glass is
   restated here, because this layer re-tints .glass below the
   original fallback and source order is what lets a fallback win.
   ─────────────────────────────────────────────────────────────── */

@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .glass { background: var(--hueso); }
}

/* The language segmented control is nav chrome and was a 55% WHITE
   pill: in dark it floated as a bright slab in the middle of a dark
   bar. Same glass, themed. */
.lang-toggle {
  background: var(--specular-surface);
  border-color: var(--specular-crest);
  box-shadow:
    inset 0 1px 0 var(--specular-crest),
    inset 0 -1px 0 rgba(0, 0, 0, 0.04),
    0 1px 2px rgba(0, 0, 0, 0.04);
}

/* `.btn-outline` is one of the few surfaces the editorial layer never
   flattened, so it is still real glass on 10 pages. Its rest tint and
   both edge strokes now come from tokens repaired above; the two
   remaining literals are its inset seam and its hover fill. */


/* Ghost buttons keep their verde tint (a tint of a fill is fine in
   both themes) but lose the white seam, which vanishes against the
   ink fill this button flips to on hover. */



/* ───────────────────────────────────────────────────────────────────
   2. FAQ ROWS. 116 rows across 18 pages, three separate defects.
   ─────────────────────────────────────────────────────────────── */

/* (a) The question text VANISHED on hover. `--verde-900` is 1.51:1 on
   ink stock, so pointing at a question erased it. `--verde-ink` is
   the mode-flipping brand green and stays legible on both papers. */
@media (hover: hover) {
  .faq-item summary:hover { color: var(--verde-ink, var(--accent-verde)); }
}

/* (b) The open row wore a translucent mint frame. At 0,2,0 it outranked
   the editorial `border: var(--panel-border)` at 0,1,0, so the one
   place the 2px Loteria outline mattered most was the one place it
   never appeared. Open rows now take the ink frame like every other
   panel on the site.

   Ink alone would make open and closed identical, so the separation
   moves to elevation: the open row rises on the hard ink offset. That
   reads in BOTH themes (the lift is bone on ink stock), where the old
   mint frame only ever read on cream. It also replaces the green
   corona halo that the legacy rule stacked underneath. */
.faq-item[open] {
  border-color: var(--tinta-900);
  box-shadow: var(--card-lift);
}

/* (c) The hover state still fired the legacy glass hover: a white seam
   plus a soft blurred shadow plus a verde corona. Feria allows a hard
   offset and nothing else on a themed surface, so the corona and the
   blur go and the seam becomes specular. The 1px rise stays: it is the
   pointer acknowledgement, and reduced motion already neutralises it. */
.faq-item:hover {
  box-shadow:
    inset 0 1px 0 var(--specular-crest),
    var(--card-lift);
}


/* ───────────────────────────────────────────────────────────────────
   3. DEEP VERDE USED AS TYPE.

   `--verde-700` is 2.73:1 and `--verde-900` is 1.51:1 on ink stock.
   Both are fine as FILLS (white type on verde-700 is 6.84:1) and both
   are wrong as foreground. Everything below is a genuine foreground:
   type, an icon stroke, or a 1px frame that has to clear 3:1.

   Verified and deliberately NOT repointed, because a later rule
   already governs them and repointing would resurrect dead code or,
   worse, win over the editorial layer and undo it:
     `.eyebrow` colour           (the outlined kicker rule restates it)
     `.btn-ghost` and its hover  (the editorial pill restates both)
     `.faq-item[open] .faq-chev` (the chevron is display:none now)
   Fills left alone: `.faq-hub-most-item` hover border, the verde-500
   focus underline, every gradient stop, every panel background.

   `--verde-ink` arrives with the token section. The fallback is
   `--accent-verde`, which already exists and already flips
   (#006847 light, 6.4:1 / #6FCF97 dark, 9.8:1), so a naming drift
   between sections degrades to a correct colour instead of dropping
   the declaration and inheriting the surrounding ink. */

.nav-links a:hover { color: var(--verde-ink, var(--accent-verde)); }
.faq-hub-crumb:hover { color: var(--verde-ink, var(--accent-verde)); }
.faq-hub-mail { color: var(--verde-ink, var(--accent-verde)); }
/* Rest and hover were two rungs of the same dead ramp, so the hover
   delta is re-expressed as weight rather than a second green: it
   survives both themes and does not depend on colour at all. */
.faq-hub-mail:hover {
  color: var(--verde-ink, var(--accent-verde));
  text-decoration-thickness: 2px;
}
.faq-hub-most-item:hover { color: var(--verde-ink, var(--accent-verde)); }
.cta-final .waitlist-success { color: var(--verde-ink, var(--accent-verde)); }
.legal-body a { color: var(--verde-ink, var(--accent-verde)); }
/* The back crumb above a legal body is a control, not a prose link:
   muted ink like every other crumb, green on hover. */
.legal-body a.faq-hub-crumb { color: var(--tinta-500); text-decoration: none; }
.legal-body a.faq-hub-crumb:hover { color: var(--verde-ink, var(--accent-verde)); }
.fx-result-row--total .fx-result-val { color: var(--verde-ink, var(--accent-verde)); }
.fx-compare-better { color: var(--verde-ink, var(--accent-verde)); }
.fx-compare-row--self .fx-compare-net { color: var(--verde-ink, var(--accent-verde)); }
.team-card .team-role { color: var(--verde-ink, var(--accent-verde)); }
.compare-table .compare-pos { color: var(--verde-ink, var(--accent-verde)); }
.related-searches li a:hover {
  border-color: var(--verde-ink, var(--accent-verde));
  color: var(--verde-ink, var(--accent-verde));
}


/* ───────────────────────────────────────────────────────────────────
   4. FOCUS RINGS.

   Only `outline-color` is set, never the `outline` shorthand, so the
   width the high-contrast block bumps to 3px and every offset already
   tuned per component survive untouched.
   ─────────────────────────────────────────────────────────────── */

:focus-visible { outline-color: var(--focus-ring); }
.btn:focus-visible,
.ed-rail-cta:focus-visible { outline-color: var(--focus-ring); }
.ed-rail-item:focus-visible { outline-color: var(--focus-ring); }
.skip-link:focus { outline-color: var(--focus-ring); }
.theme-toggle:focus-visible { outline-color: var(--focus-ring); }
.faq-hub-most-item:focus-visible { outline-color: var(--focus-ring); }
/* Same failure wearing a different property: these two draw their
   keyboard affordance as a border rather than an outline, so they
   went from 6.4:1 to 2.73:1 in dark like the rest. */
.fx-input-row:focus-within { border-color: var(--focus-ring); }
.fx-field select:focus { border-color: var(--focus-ring); }


/* ───────────────────────────────────────────────────────────────────
   5. LITERAL ALPHA HAIRLINES.

   An ink hairline at 6% to 10% alpha is invisible on ink stock and a
   cream hairline is invisible on cream paper. `--hairline` and
   `--hairline-strong` exist precisely so structure never rides a
   literal, and the high-contrast block promotes both to real rules.
   ─────────────────────────────────────────────────────────────── */

.fx-compare { border-top-color: var(--hairline); }
.fx-fallback { border-color: var(--hairline); }

/* The footer splits in two. On the 29 editorial pages `.ed-footer
   .footer` is transparent over themed paper, so its rule must follow
   the paper. On the 14 pages still carrying the legacy footer the
   ground is a literal `#000` in BOTH themes, so a themed hairline
   would disappear there in light mode: that one keeps a fixed bone
   line, which is correct on black whichever theme is active. */
.footer-bottom { border-top-color: rgba(245, 239, 227, 0.16); }
.ed-footer .footer-bottom { border-top-color: var(--hairline-strong); }

/* waitlist.css is a second stylesheet loaded AFTER main.css, so source
   order does not help here. `:root ` prefixes buy one class of
   specificity, which beats `.wl-card` / `.wl-legal` without reaching
   for `!important`. It deliberately does NOT beat the per-page
   `.ed-panel--verde .wl-legal` overrides in those documents' own
   <style> blocks: those are equal specificity and later still, and
   they are correct, because a saturated panel does not invert. */




/* ───────────────────────────────────────────────────────────────────
   6. THE SELECT CHEVRON.

   Hardcoded `stroke='%23737373'` inside the data URI: a mid grey
   picked for cream paper, 2.4:1 on ink stock. The URL now comes from
   a custom property that the theme blocks at the top of this section
   swap wholesale.
   ─────────────────────────────────────────────────────────────── */

.fx-field select { background-image: var(--fx-chevron); }


/* ───────────────────────────────────────────────────────────────────
   7. FOOTER LINK HOVER.

   `.footer-col a:hover` (0,2,1) and `.ed-footer .footer a` (0,2,1) tie
   on specificity, and the editorial rule is written later, so on 29
   pages the hover colour never applied and footer links had no hover
   feedback at all. Scoped to `.footer-col` on purpose: `.ed-footer
   .footer a` also matches the language pills, the wordmark and the
   mouseprint links, and none of those want an underline. The
   underline carries the state without relying on colour. */
.ed-footer .footer-col a:hover,
.ed-footer .footer-col a:focus-visible {
  color: var(--verde-ink, var(--accent-verde));
  text-decoration: underline;
  text-underline-offset: 3px;
}


/* ───────────────────────────────────────────────────────────────────
   RE-ASSERT: the accessibility overrides this layer would otherwise
   outrank.

   `@media` and `@supports` contribute no specificity, so the plain
   rules above, written thousands of lines later, silently win over
   the prefers-contrast and forced-colors blocks. Each rule here pays
   back exactly one of the rules above. Two of them (the FAQ frame and
   the button ring) were ALREADY losing to the legacy mint border and
   the legacy verde ring before this layer existed, so this is a
   repair as much as a restatement.
   ─────────────────────────────────────────────────────────────── */

@media (prefers-contrast: more) {
  .lang-toggle {
    background: var(--hueso);
    border-color: var(--tinta-900);
  }
  .btn-outline:hover { background: var(--hueso); }
}

@media (forced-colors: active) {
  /* In forced colours the UA owns every colour, so the job is to hand
     the ring and the frames back to the system keywords. `.btn` and
     `.faq-item[open]` carry `forced-color-adjust: none` from the block
     upstream, which is why their shadow has to be cleared by hand. */
  :focus-visible,
  .btn:focus-visible,
  .ed-rail-cta:focus-visible,
  .ed-rail-item:focus-visible,
  .theme-toggle:focus-visible,
  .faq-hub-most-item:focus-visible,
  .skip-link:focus {
    outline-color: Highlight;
  }
  .fx-input-row:focus-within,
  .fx-field select:focus { border-color: Highlight; }
  .faq-item[open] {
    border-color: CanvasText;
    box-shadow: none;
  }
  .faq-item:hover { box-shadow: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   FERIA LAYER · 4 : NEW COMPONENTS

   Everything below is ADDITIVE. Every selector is a class that does
   not exist anywhere else in this file, so a page that never opts in
   renders byte-for-byte as it did before the feria layer landed.

   Two rules govern the colour in this block:

     * a FRAME, a NUMERAL, a LABEL or an ICON STROKE sitting on the
       page canvas is TYPE, so it takes the INK family (`--rosa-ink`
       and friends), which flips by mode. The FILL family is for
       filled surfaces only: `--azul-700` as a 2px frame lands at
       2.81:1 on the dark canvas, under the 3:1 graphic floor. That
       is the single trap in this palette and it is why the carta
       colourways below never reference a `-500`/`-700` fill.
     * every surface here is a token that already carries its own
       light/dark pair, so nothing needs a `data-theme` override.

   This part declares NO tokens. It consumes the ones part 1 adds:
   the four `*-ink` accents, the four `*-050` pastels, `--crema`,
   `--marcador`, `--dot-rosa` / `--dot-azul` / `--dot-cell`, and the
   `--font-pregon` / `--font-carta` faces. Re-stating any of them
   here would silently win on source order and undo the tuning done
   upstream, so the only custom properties below are component-scoped
   (`--carta-ink`, `--carta-tint`, `--icon-size`, `--bubble-size`).
   ═══════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════
   LA CARTA : the loteria card, the signature component.

   Anatomy, top to bottom: a 2px accent frame, a header row pairing a
   Cinzel `N` numeral with a small icon over a 1.5px rule, a round
   pastel bubble holding the glyph, a Playfair title, body copy, and
   a ruled foot carrying a Cinzel smallcaps label (PASO UNO).

   The colourway is carried by two locally-scoped properties rather
   than by four copies of every rule. `--carta-ink` is the accent as
   TYPE (frame, numeral, foot label, glyph stroke); `--carta-tint` is
   its pastel, used only as the bubble fill. A `.carta` with no
   modifier is the plain ink card, which is a real variant.
   ═══════════════════════════════════════════════════════════════ */

.cartas {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* +3px because `--card-lift` is drawn OUTSIDE the border box: at a
     bare `--panel-gap` the lift of one carta touches the frame of
     the next, and nothing in this system is allowed to touch. */
  gap: calc(var(--panel-gap) + 3px);
  align-items: stretch;
}
/* Two up before the title starts breaking mid-word, one up on phones. */
@media (max-width: 900px) {
  .cartas { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .cartas { grid-template-columns: minmax(0, 1fr); }
}

.carta {
  --carta-ink: var(--tinta-900);
  --carta-tint: var(--hueso-100);

  position: relative;
  display: flex;
  flex-direction: column;
  /* A loteria card centres its emblem and its label. The emblem (bubble
     or pixel tile) and the title keep that axis; only running prose
     opts out below, where a centred rag costs more than the symmetry
     buys. */
  align-items: center;
  text-align: center;
  padding: clamp(18px, 2vw, 22px) clamp(18px, 2.4vw, 24px) 18px;
  border: 2px solid var(--carta-ink);
  border-radius: 20px;
  background: var(--papel-carta);
  /* Stated, not inherited: a carta is a raised card and stays ink-on
     -paper even when it is dropped inside a saturated ed-panel. */
  color: var(--tinta-900);
  /* The only elevation in the system. Hard offset, never a blur. */
  box-shadow: var(--card-lift);
}

/* Body copy follows the card's own axis (owner, 2026-09-22: "center all
   the text"). A centred card centres its prose under the emblem and the
   title, in the 30ch column below so the rag stays calm; a card variant
   that reads left (wide, list, media, interactive) keeps its prose left,
   because the prose inherits whatever the card sets. Lists keep their
   markers aligned and sit on the card's axis as one block. `:where()`
   keeps all of this at zero specificity so a one-off card can override. */
:where(.carta) > :is(p, dl) {
  text-wrap: pretty;
}
:where(.carta) > :is(ul, ol) {
  text-align: start;
  max-width: 100%;
}

/* Header: numeral left, glyph right, hairline plate rule under both.
   The rule is ink and not the accent, so it holds its weight on both
   canvases without depending on the colourway. */
.carta-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding-bottom: 10px;
  margin-bottom: 18px;
  border-bottom: 1.5px solid var(--tinta-900);
}
.carta-head .n,
.carta-num {
  font-family: var(--font-carta);
  font-weight: 700;          /* Cinzel ships here as a single 700 cut */
  font-size: 17px;
  line-height: 1;
  letter-spacing: 0.06em;
  color: var(--carta-ink);
}
/* The header glyph is the same symbol as the bubble, read small. It
   takes the accent as a stroke, which is why the accent has to be an
   ink value: a 1.5px line needs the 3:1 graphic floor. Pair it with
   `.icon--s`, which is the 18px below by design. */
.carta-head svg { color: var(--carta-ink); }
:where(.carta-head) > svg { width: 18px; height: 18px; }

/* The bubble. Round-cornered, not circular: 20px on a 56 to 64px box
   is the same squircle the rest of the liquid-glass layer uses. */
.carta-bubble {
  display: grid;
  place-items: center;
  width: clamp(56px, 5.4vw, 64px);
  height: clamp(56px, 5.4vw, 64px);
  margin-bottom: 14px;
  border-radius: 20px;
  background: var(--carta-tint);
  color: var(--carta-ink);
}
/* `:where()` zeroes the specificity of this default so an explicit
   `.icon--l` on the glyph still wins. Bare inline SVGs get 30px. */
:where(.carta-bubble) > svg { width: 30px; height: 30px; }

/* El pregon. Scoped by the carta, so it outranks the display block's
   `h3, .headline` on specificity and Playfair lands here without an
   !important and without touching headings anywhere else. */
.carta-title,
.carta h3,
.carta h4 {
  font-family: var(--font-pregon);
  font-weight: 900;
  font-size: clamp(19px, 1.5vw, 22px);
  line-height: 1.15;
  letter-spacing: 0.01em;
  text-wrap: balance;
  margin-bottom: 8px;
}

.carta p {
  max-width: 40ch;
  font-size: var(--fs-body);
  line-height: 1.5;
  /* Space above the foot rule when there is a foot; collapses away
     when the paragraph is the last thing in the card. */
  margin-bottom: 16px;
}
.carta p:last-child { margin-bottom: 0; }

/* `margin-top: auto` pins the foot to the bottom, so a row of cartas
   of unequal copy length still lines its foot rules up on one line. */
.carta-foot {
  width: 100%;
  margin-top: auto;
  padding-top: 10px;
  border-top: 1.5px solid var(--tinta-900);
}
.carta-foot span,
.carta-foot .carta-label {
  font-family: var(--font-carta);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--carta-ink);
}

/* Colourways. Ink accent + its own pastel, both mode-flipping, so
   each pair is legible on cream and on ink stock without a second
   set of rules. Ink on its own pastel measures 5.3 to 9.4:1. */
.carta--rosa      { --carta-ink: var(--rosa-ink);      --carta-tint: var(--rosa-050); }
.carta--azul      { --carta-ink: var(--azul-ink);      --carta-tint: var(--azul-050); }
.carta--ambar     { --carta-ink: var(--ambar-ink);     --carta-tint: var(--ambar-050); }
.carta--esmeralda { --carta-ink: var(--esmeralda-ink); --carta-tint: var(--esmeralda-050); }

/* Interactive cartas only. A static card must not animate under the
   pointer, so this is scoped to the elements that actually navigate.
   The hover deepens the same hard lift, it does not add a blur. */
@media (hover: hover) and (pointer: fine) {
  a.carta,
  button.carta {
    transition:
      transform var(--dur-fast) var(--ease-standard),
      box-shadow var(--dur-fast) var(--ease-standard);
  }
  a.carta:hover,
  button.carta:hover {
    transform: translate(-1px, -1px);
    box-shadow: 4px 4px 0 var(--tinta-900);
  }
}

/* ═══════════════════════════════════════════════════════════════════
   .dot-grid : the halftone canvas texture.

   Two radial layers offset by half a cell give the alternating
   rosa/azul weave of the reference card without a bitmap. Sets
   `background-image` only, so it composes onto whatever background
   COLOUR the host panel already has instead of replacing it.

   The alphas live in `--dot-rosa` / `--dot-azul` upstream: light
   leans on the saturated fills because they sit on cream, dark on
   the ink values, both low enough that the weave reads as paper
   texture and never as a light source. `--dot-cell` is overridable
   per element, so a tall hero can tighten the pitch inline.
   ═══════════════════════════════════════════════════════════════ */

.dot-grid {
  background-image:
    radial-gradient(var(--dot-rosa) 1.2px, transparent 1.3px),
    radial-gradient(var(--dot-azul) 1.2px, transparent 1.3px);
  background-size: var(--dot-cell) var(--dot-cell);
  background-position: 0 0, calc(var(--dot-cell) / 2) calc(var(--dot-cell) / 2);
  background-repeat: repeat;
}
/* Texture behind copy is exactly what someone asking for more
   contrast is asking us to remove. */
@media (prefers-contrast: more) {
  .dot-grid { background-image: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   .marcador : the highlighter sweep behind display type.
   ═══════════════════════════════════════════════════════════════ */

.marcador {
  /* `clone` re-draws the padding and both rounded ends on EVERY line
     box, so a sweep that wraps looks like two marker strokes rather
     than one long bar with a chopped-off end. */
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  padding: 0 0.12em;
  border-radius: 0.14em;
  background: var(--marcador);
  /* Stated on purpose. The sweep usually sits inside an h1 that may
     have been recoloured white by a saturated panel, and white on
     the amber sweep is unreadable. Forcing ink keeps it at 15.9:1 in
     light and 10.4:1 in dark, on both marcador values. */
  color: var(--tinta-900);
}
/* The poster grotesk runs at 1.02 leading, which is tighter than the
   sweep is tall: on a wrapped headline the band of one line laps the
   descenders of the line above. `:has()` opens the leading ONLY on a
   heading that actually carries a sweep, so no existing headline
   moves. Specificity: `:is()` plus `:has()` is 0,2,0, which clears
   the 0,0,1 of the `h1, h2` display rule without an !important. */
:is(h1, h2, h3, .display-xxl, .display-xl, .display-l, .display-m):has(.marcador) {
  line-height: 1.16;
}

/* ═══════════════════════════════════════════════════════════════════
   ICON PRIMITIVES : Iconoir on a 24 grid.

   The sprite's symbols carry `stroke-width="1.5"` and their paths
   carry `stroke="currentColor"`. A presentation attribute on the
   symbol wins over anything we inherit through the `<use>` shadow
   boundary, so the drawn weight is 1.5 on the 24 grid at every
   modifier by construction; the modifiers only resize the box. The
   declarations below restate the same contract for icons that are
   pasted inline instead of referenced, so both spellings match.
   ═══════════════════════════════════════════════════════════════ */

.icon {
  --icon-size: 24px;
  width: var(--icon-size);
  height: var(--icon-size);
  /* Beats the global `img, svg { display: block }` so an icon can sit
     in a sentence; `middle` seats it on the parent's x-height, which
     is the one alignment that stays right at 18px and at 44px. */
  display: inline-block;
  vertical-align: middle;
  flex: none;              /* never squashed by a flex row */
  color: inherit;          /* what makes stroke="currentColor" work */
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon--s  { --icon-size: 18px; }
.icon--l  { --icon-size: 32px; }
.icon--xl { --icon-size: 44px; }
/* Iconoir's solid set (the `ins-` ids) is filled, not stroked. Only
   needed when the glyph is pasted inline: through `<use>` the symbol
   carries its own fill attribute and this never reaches it. */
.icon--solid { fill: currentColor; stroke: none; }

/* The round tinted container. This is the bubble shape language the
   system already speaks, kept fully round here so it reads as a
   sibling of the nav chrome rather than of the carta squircle.
   Pair the sizes: --s with .icon--s, default with .icon,
   --l with .icon--l. Each pairing lands the glyph at ~55% of the
   bubble, which is the ratio the reference draws. */
.icon-bubble {
  --bubble-size: 44px;
  display: inline-grid;
  place-items: center;
  width: var(--bubble-size);
  height: var(--bubble-size);
  flex: none;
  border-radius: var(--radius-pill);
  background: var(--hueso-100);
  color: var(--tinta-900);
}
.icon-bubble--s { --bubble-size: 32px; }
.icon-bubble--l { --bubble-size: 56px; }
:where(.icon-bubble) > svg { width: 24px; height: 24px; }
:where(.icon-bubble--s) > svg { width: 18px; height: 18px; }
:where(.icon-bubble--l) > svg { width: 32px; height: 32px; }

/* Same ink-on-its-own-pastel pairs as the carta bubbles. */
.icon-bubble--rosa      { background: var(--rosa-050);      color: var(--rosa-ink); }
.icon-bubble--azul      { background: var(--azul-050);      color: var(--azul-ink); }
.icon-bubble--ambar     { background: var(--ambar-050);     color: var(--ambar-ink); }
.icon-bubble--esmeralda { background: var(--esmeralda-050); color: var(--esmeralda-ink); }

/* ═══════════════════════════════════════════════════════════════════
   .lista-icono : the "what you get" list.

   A ruled list whose rows lead with a bubble. Keeps the 1px hairline
   rhythm the rest of the site uses, and drops the last rule so the
   list ends on air the way the compare tables do.
   ═══════════════════════════════════════════════════════════════ */

/* Restated rather than leaning on the global `ul, ol` reset, so the
   pattern survives being lifted into a page-scoped <style> block. */
.lista-icono {
  margin: 0;
  padding: 0;
  list-style: none;
}
.lista-icono > li {
  display: flex;
  align-items: flex-start;   /* the bubble stays with the first line */
  gap: 14px;
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline);
  /* Restated as a mix of the inherited colour so the rhythm survives
     when the list is dropped on a saturated ed-panel, where a fixed
     ink hairline would disappear. Engines without color-mix keep the
     line above, which is correct on paper. */
  border-bottom-color: color-mix(in srgb, currentColor 14%, transparent);
}
.lista-icono > li:last-child { border-bottom: none; }

.lista-icono-texto { min-width: 0; }   /* lets long strings wrap, not overflow */
.lista-icono-texto > :first-child { font-weight: 700; }
.lista-icono-texto > :first-child:not(:last-child) {
  display: block;
  margin-bottom: 2px;
}
/* The secondary line steps down in SIZE only, never in colour: a
   dimmed grey would be a second contrast problem on every coloured
   panel this list can land on. */
.lista-icono-texto > :last-child:not(:first-child) {
  display: block;
  font-size: var(--fs-body-s);
  font-weight: 500;
}

@media (max-width: 480px) {
  .lista-icono > li { gap: 12px; padding: 12px 0; }
}
/* Phone: the bubble column cut the copy to ~33 characters a line. The
   bubble leads on its own line and the text takes the full measure. */
@media (max-width: 640px) {
  .lista-icono > li { flex-wrap: wrap; }
  .lista-icono > li > :last-child:not(:first-child) { flex-basis: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════
   Accessibility passes for the components above.
   ═══════════════════════════════════════════════════════════════ */

/* The carta hover is the only motion introduced in this section. The
   colour change stays: it is the affordance, not the animation. */
@media (prefers-reduced-motion: reduce) {
  a.carta,
  button.carta { transition: none; }
  a.carta:hover,
  button.carta:hover { transform: none; }
}

/* Forced colours strips backgrounds and box-shadows, which would
   leave the bubbles as invisible holes and the cartas as flat text.
   Restate both in system keywords, matching the block above. */
@media (forced-colors: active) {
  .carta {
    border: 2px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    forced-color-adjust: none;
  }
  .carta-bubble,
  .icon-bubble {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
  }
  .marcador {
    background: Canvas;
    color: CanvasText;
    outline: 1px solid CanvasText;
  }
  .dot-grid { background-image: none; }
}


/* ═══════════════════════════════════════════════════════════════════
   FERIA LAYER, RECONCILIATION.

   Written last on purpose. Everything here repairs a defect that only
   becomes visible once the four sections above sit in one file, or that
   the adversarial review of that assembly turned up. Each rule names the
   defect it closes.
   ═══════════════════════════════════════════════════════════════════ */

/* A brand dot is a FLIPPING token, and in light mode it resolves to the same
   #006847 as the verde panel it sits on: the full stop disappears at 1.00:1.
   On saturated fills the dot takes the fill's fixed counterpart. */
.ed-panel--verde .brand-dot,
.ed-panel--verde-vivo .brand-dot,
.ed-bar--verde .brand-dot {
  color: var(--ink-fixed);
}
.ed-panel--verde .brand-dot,
.ed-panel--tinta .brand-dot {
  color: var(--paper-fixed);
}

/* The legacy footer ground is a literal #000 in BOTH themes while its type is
   the flipping --hueso, which the feria retone takes to #14110D in dark: that
   is near-black on black. Every one of the 30 footer pages currently wraps it
   in .ed-footer, so this is latent rather than live, but a page that ever
   drops the wrapper would ship an invisible footer. Pin the fixed pair. */
.footer:not(.ed-footer .footer) {
  color: var(--paper-fixed);
}
.footer:not(.ed-footer .footer) a,
.footer:not(.ed-footer .footer) :is(h3, h4) {
  color: var(--paper-fixed);
}

@media (prefers-contrast: more) {
  .btn-outline:hover {
    background: var(--amarillo-500);
    color: var(--ink-fixed);
    border-color: var(--ink-fixed);
  }
}

/* Forced colours strips author colour entirely. These rules are written after
   the upstream @media (forced-colors: active) block, and media queries carry
   no specificity, so each surface has to hand itself back to the system
   palette here or it outranks the accessibility override. */
@media (forced-colors: active) {
  .carta,
  .icon-bubble,
  .ed-panel,
  .ed-bar,
  .ed-chip,
  .btn,
  .btn-outline,
  .btn-rojo {
    forced-color-adjust: none;
    background: Canvas;
    color: CanvasText;
    border-color: CanvasText;
    box-shadow: none;
  }
  .icon { color: CanvasText; }
  .btn:focus-visible,
  .btn.btn-rojo:focus-visible,
  .ed-rail-item:focus-visible { outline-color: Highlight; }
}

/* The icon size and stroke tokens were declared and documented but never
   consumed, so the two spellings could drift apart silently. */
.icon { stroke-width: var(--icon-stroke); }
.icon--s  { --icon-size: var(--icon-size-s); }
.icon--l  { --icon-size: var(--icon-size-l); }
.icon--xl { --icon-size: var(--icon-size-xl); }

/* --ambar-ink is 3.98:1 and --esmeralda-ink 4.35:1 on the deepest light paper
   band, both under the 4.5:1 floor for small type. Make the guard mechanical
   instead of advisory: on a deep band those two colourways hand their small
   Cinzel labels back to the ink ramp. The frames keep the accent, because a
   frame only owes 3:1. */
.ed-panel--outline .carta--ambar .carta-foot span,
.ed-panel--outline .carta--esmeralda .carta-foot span,
.carta--ambar .carta-foot span,
.carta--esmeralda .carta-foot span {
  color: var(--tinta-700);
}

/* Text utilities carry the tinta ramp, which FLIPS, so on a saturated panel
   whose fill does NOT flip they win over the panel's own fixed colour and
   land at 2.8 to 3.0:1. A panel with a fixed fill owns the colour of every
   utility-classed child. Specificity 0,2,0 beats the bare utility at 0,1,0. */
.ed-panel--verde-vivo > p, .ed-panel--verde-vivo > .body-l,
.ed-panel--verde-vivo > .body-s, .ed-panel--verde-vivo > .caption,
.ed-panel--menta > p, .ed-panel--menta > .body-l,
.ed-panel--menta > .body-s, .ed-panel--menta > .caption {
  color: var(--ink-fixed);
}
.ed-panel--verde > p, .ed-panel--verde > .body-l,
.ed-panel--bosque > p, .ed-panel--bosque > .body-l,
.ed-panel--verde > .body-s, .ed-panel--bosque > .body-s {
  color: var(--paper-fixed);
}

/* Two last fixed-colour-on-flipping-surface spots the sweep found.
   .fx-hint pinned --ink-fixed while its card surface inverts to near-black,
   and the showcase code block pinned a light mint on a ground that becomes
   bone. Both take tokens that flip with the surface they sit on. */
.fx-hint { color: var(--tinta-500); }
/* The calculator's fine print renders as <p class="fx-hint"> inside a
   carta, and `.carta p` (0,1,1) set it at body size, 40ch, beating
   `.fx-hint` (0,1,0): in the pricing page's nested frames the two notes
   grew to 16px in a 240px column (the disclaimer ran 336px tall at 390),
   larger than the result rows they annotate. Back to caption size, like
   the field hints above them. */
.fx-result p.fx-hint {
  font-size: var(--fs-caption);
  line-height: 1.45;
  max-width: none;
}
.code-block { color: var(--verde-ink); background: var(--hueso-100); }

/* ═══════════════════════════════════════════════════════════════════
   FERIA LAYER, PART TWO: THE STRUCTURAL REDESIGN.

   Part one re-toned the skin. This part changes the bones: the
   editorial serif becomes the section voice, pages gain the papel and
   vitela band rhythm of the reference, and the loteria carta grows the
   variants it needs to become the card language of the whole site.
   Append-only, like part one: nothing above it was edited.
   ═══════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════
   FERIA LAYER, SECTION 6: LA VOZ.

   The typographic split, stated once and enforced mechanically.

     EL CARTEL   Archivo Black (--font-black). The brand anchor. h1 in
                 every form, .display-xxl / .display-xl / .display-l,
                 the .ed-bar band titles, both wordmarks, and every big
                 figure (.ed-chip-num, .fee-calc-amount, .inv-*-value).
                 Untouched, and guarded below so the pregon cannot
                 creep into it through a shared class.
     EL PREGON   Playfair Display (--font-pregon). SECTION HEADINGS:
                 h2, .title, .display-m. This is the change that makes
                 the site read editorial instead of poster.
     LA CARTA    Cinzel (--font-carta). Every small label: kickers,
                 chip labels, step numerals, carta numerals and feet,
                 table column heads, uppercase micro-labels.
     EL CUERPO   --font-body. Body copy. Untouched.

   Nothing here declares a colour, so both themes are satisfied by
   construction: a face has no mode, and every value below is either a
   face, a weight, a tracking, a leading or a calc() on a token that
   already carries its own light/dark pair. The single conditional
   block at the end only ever hands a face BACK, never forward.

   Both faces are already declared @font-face with font-display: swap
   at the top of this layer. They are NOT re-declared here: a second
   @font-face for the same family and weight range would shadow the
   tuned original for no gain.

   Subset audit, run against the two shipped woff2 files before any of
   this was written (fontTools, cmap):
     PlayfairDisplayVar-latin.woff2  229 glyphs, wght axis 400 to 900
     CinzelVar-latin.woff2           220 glyphs, single 700 cut
   Both carry the full es-MX set we depend on: aacute eacute iacute
   oacute uacute, Aacute Eacute Iacute Oacute Uacute, ntilde Ntilde,
   udieresis Udieresis, questiondown and exclamdown. Nothing below has
   to fall back mid-word on a Spanish page.
   ═══════════════════════════════════════════════════════════════ */

:root {
  /* ---- El pregon -------------------------------------------------
     Playfair is a DIDONE, so it does not swap in one for one against
     the grotesque. Three measured compensations, in order of how much
     they matter.

     SIZE. Playfair's x-height is 0.514em against Archivo Black's
     0.528em, so at an identical font-size the serif reads about 3%
     smaller, and the hairline/stem contrast costs it another few
     percent of apparent mass. 1.06 is the multiplier that puts the
     two back on the same optical line. It is applied with calc()
     against the existing clamp() tokens rather than by editing them,
     because those same tokens still size Archivo elsewhere.

     WEIGHT. The axis runs 400 to 900 and MUST be stated: at the 400
     default a didone next to Archivo Black looks broken rather than
     editorial. 800 is the section-heading weight. It is set through
     `font-weight`, never `font-variation-settings`, so the value
     stays in the cascade and a fallback serif can answer it.

     TRACKING. The grotesque runs -0.035em to -0.02em. A didone at
     display size wants a fraction of that: its sidebearings are
     already tight and its hairlines close up first. -0.01em on
     section titles, -0.016em on the largest.
     ---------------------------------------------------------------- */
  --voz-pregon-weight: 800;
  --voz-pregon-scale: 1.06;
  --voz-pregon-track: -0.01em;
  --voz-pregon-track-display: -0.016em;

  /* LEADING. Measured, not guessed. Playfair's ink runs from +0.936em
     (Aacute) to -0.188em (descenders), so 1.124em of the line box is
     live glyph. Anything under that lets a descender touch the acute
     of the line below, which is a Spanish-language problem long
     before it is an English one. 1.16 is tight for a serif (a serif
     normally wants 1.3+) and still clears by 0.036em. The one place
     the site runs tighter is the marcador rule further down, which is
     handled explicitly. */
  --voz-pregon-leading: 1.16;

  /* ---- La carta --------------------------------------------------
     Cinzel's lowercase codepoints ARE small capitals: 'a' draws at
     0.627em against 'A' at 0.720em. That gives two register modes
     from one face, and they want different tracking.

       CAPS      text-transform: uppercase, full capitals, 0.09em.
                 Capitals have no ascender/descender rhythm to read
                 by, so they need the wider channel.
       VERSALES  text-transform: none, cap plus small caps, 0.05em.
                 The small caps already carry a little sidebearing of
                 their own, so the same 0.09em would shred a word.
     ---------------------------------------------------------------- */
  --voz-carta-track: 0.09em;
  --voz-carta-track-sc: 0.05em;
}

/* ═══════════════════════════════════════════════════════════════════
   6.1  EL PREGON : section headings become the editorial serif.

   Cascade notes, because the specificities here are load bearing.

   * Bare `h2` is 0,0,1 on purpose. Everything that must STAY cartel
     already outranks it: `.ed-bar h2` is 0,1,1, and rule
     `h1, h2, .display-xxl, .display-xl, .display-l, .display-m` up in
     the editorial layer matches an `h2.display-l` through its CLASS
     at 0,1,0. A selector list takes the specificity of its most
     specific MATCHING selector, so `h2.display-l` and `h2.display-xl`
     keep Archivo Black without a single exception being written.
   * `.title` and `.display-m` are 0,1,0 and win by source order over
     their originals. `.title` is the bigger visual jump of the two:
     it was never in the Archivo list at all, so it has been rendering
     in the rounded SANS. It lands on the serif now.
   * Every h1 is excluded in 6.2 at 0,1,1.
   ═══════════════════════════════════════════════════════════════ */

h2 {
  font-family: var(--font-pregon);
  font-weight: var(--voz-pregon-weight);
  letter-spacing: var(--voz-pregon-track);
  line-height: var(--voz-pregon-leading);

  /* Inert against our own file, which has a wght axis and no opsz,
     but correct for any system serif in the fallback stack that does
     carry one. Stated so the intent survives a font swap. */
  font-optical-sizing: auto;

  /* 800 is a real instance of the variable axis, and every named
     fallback (Iowan Old Style, Palatino, Georgia) ships a real bold.
     Synthesis therefore has nothing to contribute and everything to
     ruin: a smeared didone hairline is the single ugliest failure
     mode this face has. There is no italic file either, so style
     synthesis is refused for the same reason. */
  font-synthesis-weight: none;
  font-synthesis-style: none;

  /* `body` sets "tnum" "cv11" "ss01", which are Inter's tags, and
     font-feature-settings INHERITS as one opaque list. Neither new
     face has any of them, but the fallback serifs might, and tabular
     figures inside an editorial headline space like a spreadsheet.
     Restate the list with only what Playfair actually ships. */
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
}

/* No font-size here on purpose. An unclassed h2 (the legal pages set
   their section heads that way) keeps the UA 1.5em, which already
   lands on the 24px headline step, and adding a 0,0,1 font-size would
   put a new competitor in front of every page-level override on the
   site for a 1.4px gain. The scale is applied only where this layer
   owns the size token outright. */

.title {
  font-family: var(--font-pregon);
  font-weight: var(--voz-pregon-weight);
  font-size: calc(var(--fs-title) * var(--voz-pregon-scale));
  letter-spacing: var(--voz-pregon-track);
  line-height: var(--voz-pregon-leading);
  font-optical-sizing: auto;
  font-synthesis-weight: none;
  font-synthesis-style: none;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
}

/* The largest pregon step. Tighter tracking and tighter leading than
   .title, which is the normal size/leading inverse; 1.12 leaves a
   0.004em theoretical overlap that only a descender sitting exactly
   above an accented capital could ever spend, and buys a display
   headline that actually looks set rather than listed. Declared after
   .title so `h3.title.display-m` resolves to the display step. */
.display-m {
  font-family: var(--font-pregon);
  font-weight: var(--voz-pregon-weight);
  font-size: calc(var(--fs-display-m) * var(--voz-pregon-scale));
  letter-spacing: var(--voz-pregon-track-display);
  line-height: 1.12;
  font-optical-sizing: auto;
  font-synthesis-weight: none;
  font-synthesis-style: none;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
}

/* Saturated panels pinned h2 leading to 1.08 for the grotesque. That
   is 0.044em inside Playfair's ink box, so on a wrapped Spanish
   headline a 'y' would graze the accent below it. Same 0,1,1, later
   in the file, so it simply wins. */
.ed-panel--verde h2,
.ed-panel--bosque h2,
.ed-panel--tinta h2 {
  line-height: var(--voz-pregon-leading);
}

/* The centred section head painted a 1px white halo under its title.
   Two things wrong with it now: it is a fixed white, so in dark mode
   it ghosts bone type against near-black, and a 1px light shadow
   under a didone thickens exactly the hairlines the face is for.
   0,1,1, later in the file. The hero h1 keeps its halo, because the
   hero h1 is still the grotesque and still sits over the aurora. */
.section-head h2 {
  text-shadow: none;
}

/* The highlighter sweep paints an inline background, and an inline
   background covers the FONT's content area, not the line box: for
   Playfair that is ascent 1.082 plus descent 0.251, so 1.333em tall
   whatever the leading says. The existing marcador rule opens
   headings to 1.16, which was measured for Archivo's 1.088em box and
   would lap two Playfair sweeps by 0.17em. 1.34 separates them by a
   hairline. `:not(h1)` keeps every h1 on the existing 1.16, which is
   where all 27 of today's sweeps actually live; this is insurance for
   the pregon headings and for anyone who puts a sweep in one later.
   Specificity 0,2,1 against the original 0,2,0. */
:is(h2, .title, .display-m, .pregon):not(h1):has(.marcador) {
  line-height: 1.34;
}

/* ═══════════════════════════════════════════════════════════════════
   6.2  EL CARTEL : the guard.

   Everything below is a restatement, not a change. It exists so that
   a shared class can never drag the brand anchor onto the serif.

   The h1 pair is the only rule here that has a visible effect, and it
   is deliberate. `h1.title` (three pages) and `h1.display-m` (two)
   were never inside the Archivo selector list, so they have been
   rendering in the rounded sans while `h1.display-l` on 32 other
   pages rendered in Archivo Black. Excluding them from the pregon
   without a face would have handed them the sans again. They are the
   page's brand anchor, so they are handed the cartel, which is what
   every other h1 on the site already was.
   ═══════════════════════════════════════════════════════════════ */

h1.title {
  font-family: var(--font-black);
  font-weight: 400;             /* Archivo Black ships one cut */
  font-size: var(--fs-title);
  letter-spacing: -0.01em;
  line-height: 1.06;
}
h1.display-m {
  font-family: var(--font-black);
  font-weight: 400;
  font-size: var(--fs-display-m);
  letter-spacing: -0.01em;
  line-height: 1.06;
}

/* The band titles. `.ed-bar h2` upstream is 0,1,1 and already clears
   bare h2, but the class is written as a free-standing hook on 19
   pages, so give it its own 0,1,0 anchor rather than trusting that
   nobody ever lifts one out of its bar. */
.ed-bar-title {
  font-family: var(--font-black);
  font-weight: 400;
}

/* ═══════════════════════════════════════════════════════════════════
   6.3  LA CARTA : every small label becomes the engraved smallcap.

   All of these are restatements of face and tracking only. No rule
   here touches colour, size, padding or layout, so a label that is
   already contrast-checked stays contrast-checked.
   ═══════════════════════════════════════════════════════════════ */

/* The kicker pill. text-transform stays `none`: the face is already
   drawing small capitals, and uppercasing on top of it throws away
   the cap/small-cap contrast that is the whole point. */
.eyebrow {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: var(--voz-carta-track-sc);
  text-transform: none;
  font-optical-sizing: auto;
  font-synthesis-weight: none;
  font-feature-settings: "kern" 1;
}

/* Carta numerals and the ruled PASO foot. Already Cinzel upstream;
   restated so the whole label voice reads from the same two tokens
   instead of from four hand-tuned tracking values. The foot is set in
   full capitals upstream, so it keeps its own wider track. */
.carta-head .n,
.carta-num {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: var(--voz-carta-track);
  font-synthesis-weight: none;
  font-feature-settings: "kern" 1;
}
.carta-foot span,
.carta-foot .carta-label {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: 0.16em;      /* the foot runs wider than the token by design */
  text-transform: uppercase;
  font-synthesis-weight: none;
  font-feature-settings: "kern" 1;
}

/* ---------- Table column heads ---------- */

/* Three comparison tables, three column-head idioms. All of them are
   the same job: the label above a column. `.compare-table` heads are
   proper nouns in sentence case, so they read as cap-plus-smallcap;
   the first cell and the two all-caps head rows take the caps track. */
.compare-table thead th,
.fee-ladder thead th,
.fx-compare-head-row th,
.fx-compare-head {
  font-family: var(--font-carta);
  font-weight: 700;
  font-synthesis-weight: none;
  font-feature-settings: "kern" 1;
}
.compare-table thead th {
  letter-spacing: var(--voz-carta-track-sc);
}
.compare-table thead th:first-child,
.fee-ladder thead th,
.fx-compare-head-row th,
.fx-compare-head {
  letter-spacing: var(--voz-carta-track);
}

/* The stacked-card fallbacks print the column name from `data-label`
   in a ::before, which is the same column head read on a phone. It
   has to carry the same voice or the table changes identity at the
   breakpoint. Both live inside a max-width media block upstream; a
   plain rule here is unconditional, which is correct, because the
   pseudo-element only renders inside that block anyway. */
.compare-table--cards td[data-label]:not(:first-child)::before,
.fx-compare-row td[data-label]:not(.fx-compare-name)::before {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: var(--voz-carta-track);
  font-synthesis-weight: none;
  font-feature-settings: "kern" 1;
}

/* ---------- The remaining small uppercase labels ----------
   Every selector in this block already carries
   `text-transform: uppercase` upstream. That is the qualification for
   membership: if it was already shouting, it is a carta label. */
.footer-col :is(h3, h4),
.fx-label,
.team-card .team-role,
.fee-calc-cap,
.region-switch-label,
.fee-ladder .fee-ladder-tag,
.roadmap-status,
.inv-rung-key {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: var(--voz-carta-track);
  font-synthesis-weight: none;
  font-feature-settings: "kern" 1;
}

/* ═══════════════════════════════════════════════════════════════════
   6.4  UTILITIES : the two opt-in hooks for page agents.
   ═══════════════════════════════════════════════════════════════ */

/* `.pregon` puts anything into the editorial serif without giving it
   a size, so it composes with the existing display and body steps.
   The design-system reference sets this at 900; 800 is the section
   voice, and 900 is one inline `font-weight` away when a page wants
   the heavier cut. */
.pregon {
  font-family: var(--font-pregon);
  font-weight: var(--voz-pregon-weight);
  letter-spacing: var(--voz-pregon-track);
  line-height: var(--voz-pregon-leading);
  font-optical-sizing: auto;
  font-synthesis-weight: none;
  font-synthesis-style: none;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
}

/* `.voz-carta` is the smallcaps register: mixed-case copy in, cap
   plus small caps out, no text-transform involved. */
.voz-carta {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: var(--voz-carta-track-sc);
  text-transform: none;
  font-optical-sizing: auto;
  font-synthesis-weight: none;
  font-feature-settings: "kern" 1;
}

/* The caps register, for a label that really is a label. Declared
   after `.voz-carta` so the pair composes in either class order. */
.voz-carta--caps {
  text-transform: uppercase;
  letter-spacing: var(--voz-carta-track);
}

/* ═══════════════════════════════════════════════════════════════════
   6.5  ACCESSIBILITY : where the voice hands itself back.

   Written last on purpose. `@media` adds no specificity, so an
   accessibility override only holds if nothing of equal specificity
   is declared after it. These are the last words in the file.
   ═══════════════════════════════════════════════════════════════ */

/* A request for more contrast is a request for a plainer letterform.
   Below about 12px Cinzel's engraved strokes are thin enough that the
   face itself costs perceived contrast even when the colour pair
   passes on paper. These are the sub-12px labels; they go back to the
   body grotesque, keep their tracking, and lose nothing but flavour.
   The kicker pill, chip labels and carta feet are all 13px or larger
   and stay in voice. */
@media (prefers-contrast: more) {
  .fx-compare-head-row th,
  .fee-calc-cap,
  .region-switch-label,
  .roadmap-status,
  .inv-rung-key,
  .fee-ladder .fee-ladder-tag,
  .compare-table--cards td[data-label]:not(:first-child)::before,
  .fx-compare-row td[data-label]:not(.fx-compare-name)::before {
    font-family: var(--font-body);
    font-weight: 700;
  }
}

/* Motion is not the only thing a reduced-data or failed-font session
   changes. Nothing here needs a `prefers-reduced-motion` branch, and
   nothing here needs a `forced-colors` branch either: forced colours
   replace the palette, never the face, and this section declares no
   palette. Stated so the next person does not go looking for the
   block that is missing. */
/* ═══════════════════════════════════════════════════════════════════════
   FERIA LAYER : SECTION RHYTHM AND PAGE ARCHITECTURE (LA BANDA)

   What the site is missing structurally. Every page today is one
   continuous canvas with rounded panels floating on it, so a 1400px
   scroll reads as a single undifferentiated column. The reference
   alternates full width horizontal BANDS, papel, vitela, papel, each
   opened by a 2px ink rule, so the same scroll reads as a stack of
   chapters. That is what this section adds, and it is the difference
   between a retone and a redesign.

   Everything below is ADDITIVE and opt in. `.banda`, `.banda-interior`,
   `.banda-cabecera`, `.banda-rotulo`, `.banda-titulo`, `.banda-lede` and
   `.regla` exist nowhere else in this file and nowhere in the 40+ pages,
   so a page that does not write one of those class names renders byte for
   byte as it did before this block landed. The only selectors here that
   touch an existing class (`.carta`, `.eyebrow`, `.caption` and friends)
   are all descendant scoped under `.banda--tinta`, which cannot match
   until a page opts in.

   ── THE BLEED MODEL, which is the part that is easy to get wrong ──

   A band has to run edge to edge of the CONTENT area. Two things make
   the naive spelling wrong here:

     1. The rail. Above 1280px `body.has-rail main` carries
        `margin-left: calc(128px + var(--panel-gap) * 2)` for the fixed
        128px rail. The classic full bleed, `width: 100vw` plus
        `margin-inline: calc(50% - 50vw)`, measures from the VIEWPORT, so
        the band would slide 160px underneath the rail cards.
     2. The scrollbar. `100vw` includes the classic scrollbar gutter, so
        the same spelling overflows by 15px on every desktop browser that
        reserves one. This file already carries `overflow-x: clip` on html
        and body, so that overflow would not scroll, it would silently
        CROP the right edge of every band and shift its centred content
        left by half a scrollbar.

   So the band never looks at the viewport at all. It derives its bleed
   from the layout chain it is standing in: `--banda-bleed` is the inline
   PADDING of its own parent, and the band cancels exactly that padding
   with a negative inline margin, then pays it back as its own inline
   padding. The result is a band whose border box is its parent's border
   box and whose content box is its parent's content box.

     • inside `.ed-wrap`      bleed and gutter are `--panel-gap`
     • inside `.container`    bleed and gutter are 24px
     • directly inside `main` bleed is 0, because `main` is ALREADY the
       content area: the rail margin is on `main` itself and `main` is a
       block box, so its width is the viewport minus the rail minus the
       scrollbar, computed by layout instead of by us.

   Two consequences worth stating out loud. First, this cannot overflow:
   there is no `vw` unit anywhere in the band, so there is nothing to
   overshoot. Second, it cannot reach under the rail: the rail inset lives
   on an ancestor, so it is already subtracted from every width the band
   inherits, at every breakpoint, with no media query of our own.

   Placement guidance for the page agents:
     • `<section class="banda">` as a DIRECT CHILD of `<main>` is a true
       viewport wide band (minus rail, minus scrollbar).
     • as a direct child of `.ed-wrap` it fills the wrap's border box,
       which is the page's own 1720px measure. Below a ~1750px viewport
       those are the same pixel, because the wrap fills main; above it the
       band stops with the rest of the page instead of running past it.
     • only ever a DIRECT child of `.ed-wrap` / `.container`. Nested any
       deeper the contextual rules do not match, `--banda-bleed` stays 0,
       and the band quietly behaves as a plain full width block. That is
       the safe failure, not a broken one.

   ── CONTAINMENT ──

   `.banda` deliberately takes NO `content-visibility`, no `contain`, no
   `overflow`, no `transform` and no `filter`. It must not become a
   stacking context or a scroll container, because a band is an outer
   shell and anything sticky inside it (a sticky sub nav, a sticky pricing
   column) has to keep sticking to the viewport. The below the fold
   deferral list further up this file already covers the INNER shells
   (`.ed-grid--thirds`, `section.ed-panel--tinta`, `.faq-list`), and that
   file comment already notes that nested shells are fine, so a band that
   wraps them keeps every skip it had.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* Vertical rhythm. One scale, used by every band, so the chapters
     breathe identically no matter which surface they carry. The three
     rungs are the same curve at three amplitudes, not three unrelated
     clamps: short is roughly 0.6x, long roughly 1.4x. */
  --banda-espacio: clamp(64px, 7.2vw, 120px);
  --banda-espacio-corto: clamp(40px, 4.6vw, 72px);
  --banda-espacio-largo: clamp(88px, 9.6vw, 168px);

  /* The page measure. Same 1720px `.ed-wrap` caps at, restated as a
     token so a band placed OUTSIDE the wrap lines its content up with a
     band placed inside one. */
  --banda-max: 1720px;

  /* The gap between a section header and the block it heads, and the
     breathing room around a standalone rule. One value for both, so a
     `.regla` and a `.banda-cabecera` sit on the same rhythm. */
  --banda-hueco: clamp(28px, 3.4vw, 48px);
}

/* ═══════════════════════════════════════════════════════════════════
   .banda : the chapter band.
   ═══════════════════════════════════════════════════════════════ */

.banda {
  /* Reset per element, so a band nested inside a band does not inherit
     its parent's bleed and tear out of the layout. The contextual rules
     below are all (0,2,0) and beat this (0,1,0) declaration regardless
     of source order. */
  --banda-bleed: 0px;
  --banda-gutter: var(--panel-gap);
  --banda-costura: 0px;
  --banda-y: var(--banda-espacio);

  /* The band's foreground contract. Every piece of the header block and
     every inner rule reads these instead of reaching for the tinta ramp
     directly, which is what lets the ink band invert wholesale with one
     variant rule instead of a dozen overrides. */
  --banda-ink: var(--tinta-900);
  --banda-ink-quiet: var(--tinta-500);
  --regla-tinta: var(--tinta-900);

  /* The bleed. Negative margin out to the parent's border box, then the
     same distance paid back as padding, so inner content lands on the
     exact grid column the sections above and below the band use. */
  margin-inline: calc(-1 * var(--banda-bleed));
  padding-inline: var(--banda-gutter);
  padding-block: var(--banda-y);

  /* The chapter rule. Ink, 2px, top edge only, so consecutive bands
     read as a stack of chapters and never as a boxed card. Stated as
     the literal 2px rather than `--panel-border` because a band is not
     a panel: it must never pick up a panel radius or a fourth side. */
  border-top: 2px solid var(--tinta-900);

  background: var(--hueso);
  color: var(--banda-ink);
}

/* Surfaces. Four rungs of the paper ladder, each one already a
   light/dark pair upstream, so no variant needs a `data-theme` rule.
   Ink type on the three light bands is 16.08 / 14.40 / 17.18:1 in light
   and bone type is 16.44 / 14.07 / 12.9:1 in dark. */
.banda--papel  { background: var(--hueso); }
.banda--vitela { background: var(--vitela); }
.banda--carta  { background: var(--papel-carta); }

/* The inverting band. Matches `.ed-panel--tinta` exactly, fill
   `--tinta-900` and type `--blanco`, which means it is a near black
   chapter on bone paper in light and a bone chapter on ink stock in
   dark. That wholesale inversion is the signature of this system, not a
   bug: 17.4:1 in light, 12.9:1 in dark.

   Its own top rule stays `--tinta-900`, so it merges into the fill and
   the chapter break is carried by the colour change itself, which is
   louder than any rule could be. Rules drawn INSIDE it flip to
   `--blanco` via `--regla-tinta`, or they would be invisible. */
.banda--tinta {
  background: var(--tinta-900);
  --banda-ink: var(--blanco);
  /* Plain value first for engines without color-mix, exactly the
     two-line idiom the token block uses. The mix softens the lede by
     22% toward the fill and still measures 9.8:1 light / 7.5:1 dark. */
  --banda-ink-quiet: var(--blanco);
  --banda-ink-quiet: color-mix(in srgb, var(--blanco) 78%, var(--tinta-900));
  --regla-tinta: var(--blanco);
}

/* Rhythm modifiers. The padding is the only thing that moves, so a
   compact band and a generous band still share one vertical grammar. */
.banda--compacta { --banda-y: var(--banda-espacio-corto); }
.banda--amplia   { --banda-y: var(--banda-espacio-largo); }

/* The first band on a page usually opens under the nav or under a hero
   and has nothing above it to be separated FROM. Width, not colour: a
   transparent 2px top border would show a 2px stripe of raw canvas
   through the band, which is worse than no rule at all. The 2px the
   band loses is invisible at the top of a page. */
.banda--sin-regla { border-top-width: 0; }

/* Consecutive bands of the SAME surface are one continuous sheet, so a
   rule between them would invent a chapter break that is not there. The
   colour goes to the band's own fill rather than to `transparent`,
   which keeps the 2px box in the layout: no reflow, no 2px canvas
   stripe, and the band above and the band below stay pixel aligned with
   any variant sequence. */
.banda--papel  + .banda--papel  { border-top-color: var(--hueso); }
.banda--vitela + .banda--vitela { border-top-color: var(--vitela); }
.banda--carta  + .banda--carta  { border-top-color: var(--papel-carta); }
.banda--tinta  + .banda--tinta  { border-top-color: var(--tinta-900); }

/* ---------- Where the band is allowed to stand ----------
   Each of these hands the band the inline padding of its own parent, in
   the parent's own units. `.ed-wrap` is `padding: var(--panel-gap)
   var(--panel-gap) 0`, `.container` and `.container-inner` are
   `padding: 0 24px`. Direct child only, on purpose: see the header. */
.ed-wrap > .banda {
  --banda-bleed: var(--panel-gap);
  --banda-gutter: var(--panel-gap);
  /* `.ed-wrap` is a flex column with `gap: var(--panel-gap)`. Chapters
     have to TOUCH, so the seam between two bands has to give that gap
     back. See `.banda + .banda` below. */
  --banda-costura: var(--panel-gap);
}
.container > .banda,
.container-inner > .banda {
  --banda-bleed: 24px;
  --banda-gutter: 24px;
}

/* The seam. A negative top margin on the second band cancels the flex
   gap the parent inserted, so the two bands meet on the 2px rule with no
   strip of page canvas showing between them. Outside a gapped parent
   `--banda-costura` is 0px and this computes to `margin-top: 0`, so the
   rule is inert everywhere else. Margins are laid out normally on flex
   items, so this is exact rather than approximate. */
.banda + .banda {
  margin-top: calc(-1 * var(--banda-costura, 0px));
}

/* ═══════════════════════════════════════════════════════════════════
   .banda-interior : the measure inside a band.

   Only needed when the band bleeds past the page measure, which is the
   case for a band placed directly in `main`. Inside `.ed-wrap` the wrap
   has already capped the band at 1720px and this computes to exactly
   the width it already had, so the same markup can move between the two
   placements without changing.

   The cap subtracts the band's own gutter because `box-sizing` is
   border-box everywhere: `.ed-wrap`'s 1720px INCLUDES its padding, so
   the content column is 1720px minus two gutters, and this matches it.
   ═══════════════════════════════════════════════════════════════ */

.banda-interior {
  width: 100%;
  max-width: calc(var(--banda-max) - var(--banda-gutter, var(--panel-gap)) * 2);
  margin-inline: auto;
}

/* ═══════════════════════════════════════════════════════════════════
   .banda-cabecera : the chapter head.

   A Cinzel rotulo, a Playfair pregon, an optional lede. Centred, and
   held to the site's own measure: the lede is capped at `--reading-max`
   like every other run of prose on the site, and the block around it is
   allowed 200px more so a display line does not break into four.

   It works outside a band too. Every colour reads a `--banda-*` custom
   property with a tinta-ramp fallback, so dropped into an `.ed-panel`
   or a bare `.container` it simply renders in ink.
   ═══════════════════════════════════════════════════════════════ */

.banda-cabecera {
  max-width: calc(var(--reading-max) + 200px);
  margin-inline: auto;
  margin-bottom: var(--banda-hueco);
  /* The band itself pads by the page gutter, which is 10px on a phone.
     That is correct for a grid of panels, which carry their own inner
     padding, and far too tight for centred prose, so the header block
     buys its own breathing room without moving the grid. */
  padding-inline: clamp(8px, 2vw, 24px);
  text-align: center;
}
/* A header with nothing under it is a band of its own; do not leave a
   trailing gap inside the band's bottom padding. */
.banda-cabecera:last-child { margin-bottom: 0; }

.banda-rotulo {
  display: inline-block;
  font-family: var(--font-carta);
  font-weight: 700;          /* Cinzel ships here as a single 700 cut */
  /* Floor matches `--fs-eyebrow`: the site never runs a label under
     12px, and Cinzel at 0.2em tracking needs every pixel it has. */
  font-size: clamp(12px, 0.9vw, 13px);
  line-height: 1;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  /* Tracking is applied AFTER the last glyph too, so without this the
     block reads optically off centre by half an em. */
  padding-inline-start: 0.2em;
  margin-bottom: clamp(12px, 1.4vw, 18px);
  color: var(--banda-ink, var(--tinta-900));
}

/* El pregon. The bare `h2` fallback is wrapped in `:where()` so it lands
   at (0,0,1): it beats the display block's `h1, h2` rule on source order
   alone, and any explicit class on the heading still wins over it. */
.banda-titulo,
:where(.banda-cabecera) h2 {
  margin: 0;
  font-family: var(--font-pregon);
  font-weight: 800;
  font-size: clamp(30px, 3.6vw, 54px);
  line-height: var(--voz-pregon-leading);
  letter-spacing: -0.005em;
  text-wrap: balance;
  color: var(--banda-ink, var(--tinta-900));
}

/* The lede, plus any prose the header carries. The (0,2,0) descendant
   selector is what beats the `.body-l` / `.caption` utilities, which
   carry the tinta ramp and would otherwise land at 2.9:1 on the ink
   band. */
.banda-lede,
.banda-cabecera > :is(p, .body-l, .body-s, .caption) {
  max-width: var(--reading-max);
  margin-inline: auto;
  margin-top: clamp(14px, 1.6vw, 20px);
  margin-bottom: 0;
  font-size: clamp(16px, 1.15vw, 19px);
  font-weight: 500;
  line-height: 1.6;
  text-wrap: pretty;
  color: var(--banda-ink-quiet, var(--tinta-500));
}

/* A lede that sits directly in the band rather than inside a
   `.banda-cabecera` gets no cap from the rule above, so it runs the full
   band width — 100ch on a desktop, well past the 65-75ch the project's
   own accessibility floor sets for prose. The centring is already in the
   markup; only the measure was missing. */
.banda-interior > :is(p.kicker, p.body-l, p.lead) {
  max-width: var(--reading-max);
  margin-inline: auto;
}

/* Left aligned variant, for a chapter that opens beside a figure rather
   than over one. */
.banda-cabecera--izquierda {
  margin-inline: 0;
  padding-inline: 0;
  text-align: left;
}
.banda-cabecera--izquierda .banda-lede,
.banda-cabecera--izquierda > :is(p, .body-l, .body-s, .caption) {
  margin-inline: 0;
}

/* ═══════════════════════════════════════════════════════════════════
   .regla : the standalone 2px ink rule.

   The same weight as the band's own top edge, so a rule between two
   blocks INSIDE a chapter is visibly the same instrument as the rule
   between two chapters. Works as `<hr class="regla">` or as an empty
   `<div>`: the `border: 0` first clears the UA's inset hr border, and
   `height: 0` collapses the box so only the top border draws.

   `--regla-tinta` is inherited from the band, which is what makes a
   rule inside `.banda--tinta` flip to `--blanco` instead of drawing ink
   on ink. Outside any band the property is unset and the var() fallback
   supplies `--tinta-900`.
   ═══════════════════════════════════════════════════════════════ */

.regla {
  display: block;
  width: 100%;
  height: 0;
  margin-block: var(--banda-hueco);
  margin-inline: 0;
  border: 0;
  border-top: 2px solid var(--regla-tinta, var(--tinta-900));
}
/* The short centred rule, for under a chapter head or between two
   stanzas of the same block. */
.regla--corta {
  width: clamp(56px, 6vw, 88px);
  margin-inline: auto;
}
.banda-cabecera--izquierda .regla--corta { margin-inline: 0; }

/* ═══════════════════════════════════════════════════════════════════
   The ink band's foreground guards.

   Same defect the reconciliation block above closes for the saturated
   panels: the text utilities carry the tinta ramp, which FLIPS, while
   `.banda--tinta` sets its own foreground, so an unguarded `.caption`
   inside the ink band lands around 2.9:1.

   Scoped to the two structural levels a band actually has, band > child
   and band > interior > child, using the child combinator rather than a
   descendant one on purpose: a `.carta` dropped on the ink band is a
   raised PAPER card and has to keep its own ink-on-paper colours. This
   is the same posture, and the same limitation, as the existing
   `.ed-panel--verde > p` guards.
   ═══════════════════════════════════════════════════════════════ */

.banda--tinta :is(h2, h3, h4, p, li, .title, .headline, .body-l, .body-s, .caption):not(.carta *):not(.ed-panel *) {
  color: var(--banda-ink);
}
.banda--tinta .eyebrow:not(.carta *):not(.ed-panel *),
.banda--tinta .banda-cabecera > .eyebrow {
  color: var(--blanco);
  border-color: var(--blanco);
}

/* `--card-lift` is `3px 3px 0 var(--tinta-900)`, which is the ink band's
   own fill, so a carta standing on this band loses the one elevation the
   system has. Hand it the band's foreground instead. The hover state is
   restated at the same specificity or the guard would freeze the lift
   the interactive carta deepens on hover. */
.banda--tinta .carta { box-shadow: 3px 3px 0 var(--blanco); }
@media (hover: hover) and (pointer: fine) {
  .banda--tinta a.carta:hover,
  .banda--tinta button.carta:hover { box-shadow: 4px 4px 0 var(--blanco); }
}

/* ═══════════════════════════════════════════════════════════════════
   Accessibility passes.
   ═══════════════════════════════════════════════════════════════ */

/* At `prefers-contrast: more` the paper ladder deliberately collapses to
   two rungs, so three of the four surfaces stop being distinguishable
   and the 2px rules become the only thing carrying the chapter
   structure. Give every seam its rule back, including the same-variant
   seams suppressed above. Written after those rules and at the same
   (0,2,0), so it wins on source order without an !important. */
@media (prefers-contrast: more) {
  .banda--papel  + .banda--papel,
  .banda--vitela + .banda--vitela,
  .banda--carta  + .banda--carta,
  .banda--tinta  + .banda--tinta {
    border-top-color: var(--tinta-900);
  }
}
/* ═══════════════════════════════════════════════════════════════════════
   FERIA LAYER, SECTION 5: LA FAMILIA DE LA CARTA.

   The base `.carta` in section 4 covers exactly one content shape: the
   three step cells on the two homepages. This section turns that one
   card into the card LANGUAGE of the site, so the loteria frame is what
   a statistic, a list, an image, a link, a wide row and the calculator
   all look like.

   Nothing here redefines the base. `.carta`, `.carta-head`, `.carta-num`,
   `.carta-bubble`, `.carta-title`, `.carta-foot` and the four colourways
   are consumed as they are. The only base rule restated is `padding`,
   and it is restated to the byte so that three new custom properties can
   name the three edges the bleeding variants have to cancel.

   Cascade facts this section is written against:

     1. It is appended after section 4 and after the reconciliation
        block, so an EQUAL specificity rule wins by source order. There
        is no `!important` below.
     2. Source order is not enough on its own. `a.carta:hover` upstream
        is (0,2,1), so a plain `.carta--enlace:hover` at (0,2,0) would
        LOSE to it. Every rule below that overrides a base `.carta`
        property is written as the compound `.carta.carta--x`, which is
        (0,2,0) or better and is order proof as well.
     3. `.carta p` is (0,1,1) and `.carta h3` is (0,1,1). A part class on
        its own is (0,1,0) and would lose, so every new part is written
        `.carta .carta-x`.

   Colour contract, restated because it is what makes the variants safe:
   an accent used as a FRAME, a NUMERAL, an ICON STROKE or a LABEL takes
   the INK family, never a `-500`/`-700` fill. Measured on the carta's own
   stock (`--papel-carta`, which is the only ground a carta ever paints,
   even inside a saturated `.ed-panel`):

       rosa-ink        6.19:1 light  /  5.12:1 dark
       azul-ink        6.59:1 light  /  6.69:1 dark
       ambar-ink       4.93:1 light  /  8.25:1 dark
       esmeralda-ink   5.39:1 light  /  7.16:1 dark

   All four clear 4.5:1 for small type in both themes, which is why the
   accent may carry a 12px Cinzel label here. `--tinta-700` on the same
   stock is 11.06:1 light / 10.10:1 dark and is the fallback used wherever
   a label can land somewhere other than carta stock.

   New custom properties, all component scoped, none removed or renamed:
   `--cartas-min`, `--carta-pad-x`, `--carta-pad-top`, `--carta-pad-bottom`,
   `--carta-ring`, `--carta-media-ratio`, `--carta-aside-w`.
   ═══════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════
   5.1  .cartas : the grid, extended.

   The base grid was a hard `repeat(3, 1fr)` with two breakpoints. That
   is correct for a three step row and wrong for everything else: four
   statistics orphan one card onto a second line, two cards leave a
   third of the row empty.

   The default becomes auto-fit, calibrated so a THREE card row is
   byte-identical to what ships today at every width. With
   `--cartas-min: 240px` and the ~16px gap, any container wider than
   about 750px resolves to three or more tracks; auto-fit collapses the
   empty ones and the three survivors each take 1fr, which is exactly
   `repeat(3, 1fr)`. Below 900px the two explicit steps below take over
   and restate the shipped behaviour verbatim. The only pages using
   `.cartas` today carry exactly three cartas, so nothing moves.

   Both breakpoint rules are (0,1,0) and are written AFTER the base
   rule, which is the only reason they still win inside their ranges.
   Do not reorder them.
   ═══════════════════════════════════════════════════════════════ */

.cartas {
  /* Overridable per instance from an inline `style` attribute when a
     row wants chunkier or tighter cards without a new class. */
  --cartas-min: 240px;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--cartas-min), 100%), 1fr));
}
@media (max-width: 900px) {
  .cartas { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .cartas { grid-template-columns: minmax(0, 1fr); }
}

/* Pure auto-fit, no breakpoints. `min(var(--cartas-min), 100%)` is what
   makes this safe unaided: once the container is narrower than the min
   track the track becomes 100% and the row is one up on its own. Use
   this for a set whose length the page does not control. */
.cartas.cartas--auto {
  grid-template-columns: repeat(auto-fit, minmax(min(var(--cartas-min), 100%), 1fr));
}

/* Fixed counts, for a row whose rhythm is meaning rather than fit: a
   two card comparison, the three steps, a four figure stat band. All
   are (0,2,0) so they clear the two breakpoint rules above, which means
   each one has to restate its own stacking. */
.cartas.cartas--2up { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cartas.cartas--3up { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cartas.cartas--4up { grid-template-columns: repeat(4, minmax(0, 1fr)); }

@media (max-width: 1100px) {
  .cartas.cartas--4up { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .cartas.cartas--3up { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  .cartas.cartas--2up { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 600px) {
  .cartas.cartas--2up,
  .cartas.cartas--3up,
  .cartas.cartas--4up { grid-template-columns: minmax(0, 1fr); }
}

/* A wide carta is a row, not a cell. Dropped into any `.cartas` it
   takes the whole line rather than sitting in one track. */
.cartas > .carta--ancha {
  /* auto-fit collapses only EMPTY tracks, so 1 / -1 cannot span an
     auto-fit grid reliably. Give the row an explicit single track. */
  grid-column: 1 / -1;
}
.cartas:has(> .carta--ancha) { grid-template-columns: minmax(0, 1fr); }
/* Exception: an explicit n-up deck keeps its tracks and lets the wide
   carta span them, so one card can lead a row without flattening the
   whole deck into a stack. Without this, `:has()` above would win and
   every sibling would go full width too. */
.cartas.cartas--2up:has(> .carta--ancha) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 600px) {
  .cartas.cartas--2up:has(> .carta--ancha) { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, the home feature deck is one centred column: the two wide
     cards leave their desktop media-left/copy-right axis and join it. */
  .editorial-home :is(#features, #funciones) .carta--ancha :is(h3, p) { text-align: center; margin-inline: auto; }
  .editorial-home :is(#features, #funciones) .carta--ancha > :is(.carta-bubble, .carta-aside, .carta-pedro-figure, canvas) { margin-inline: auto; }
}


/* ═══════════════════════════════════════════════════════════════════
   5.2  Base additions shared by the whole family.

   The padding restatement is the load bearing one. Two variants have to
   cancel the carta's own padding on one or more edges (the media bleeds
   to the frame, the caption bar sits on the bottom edge) and a third has
   to move that padding from the card onto its parts. Cancelling a
   `clamp()` you cannot name means copying it into half a dozen negative
   margins and hoping it never changes. Naming the three edges once means
   it cannot drift.

   The values are byte-identical to the upstream declaration, so this
   restatement is a no-op on every existing carta.
   ═══════════════════════════════════════════════════════════════ */

.carta {
  --carta-pad-x: clamp(18px, 2.4vw, 24px);
  --carta-pad-top: clamp(18px, 2vw, 22px);
  --carta-pad-bottom: 18px;
  /* The colour of the focus ring's outer band. Ink by default, which is
     16.08:1 on bone paper and 16.59:1 on ink stock, and 17.18:1 / 12.02:1
     against the paper band that separates it from the card. A page may
     point this at an accent, but it never needs to. */
  --carta-ring: var(--tinta-900);
  /* Declared here rather than left to a `var()` fallback on purpose:
     `scripts/lint-tokens.mjs` fails CI on a `var(--x)` whose name is
     never declared anywhere in this file, fallback or not. Both are
     meant to be overridden per instance from an inline `style`. */
  --carta-media-ratio: 16 / 10;
  --carta-aside-w: 34%;
  padding: var(--carta-pad-top) var(--carta-pad-x) var(--carta-pad-bottom);
}

/* A bubble that follows whatever colourway the carta around it resolved,
   instead of naming a hue a second time in the markup. Falls back to the
   neutral pair when it is used outside a carta. */
.icon-bubble--carta {
  background: var(--carta-tint, var(--hueso-100));
  color: var(--carta-ink, var(--tinta-900));
}

/* The copy column. Neutral on purpose: it is a block that fills the
   card in every variant, and each variant decides how it is laid out. */
.carta .carta-cuerpo {
  width: 100%;
  min-width: 0;
}

/* The side column of a wide carta: holds a bubble, a numeral or media.
   Centred so a lone bubble sits in the middle of its column. */
.carta .carta-aside {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
}

/* The read-on affordance. Cinzel smallcaps plus a glyph, pinned to the
   bottom edge the same way `.carta-foot` is, so a row of link cartas
   lines its affordances up even when the copy runs to different
   lengths. Accent coloured: the worst case is ambar at 4.93:1 on carta
   stock, which clears the 4.5:1 floor for 12px type. */
.carta .carta-flecha {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 14px;
  font-family: var(--font-carta);
  font-weight: 700;
  font-size: 12px;
  line-height: 1;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--carta-ink);
}
/* A carta with BOTH a flecha and a foot: only one of them can own the
   auto margin, or the flecha eats the free space and the foot rules
   stop lining up across the row. The foot wins, it is the older part. */
.carta .carta-flecha:not(:last-child) { margin-top: 0; }
.carta .carta-flecha > svg { flex: none; }


/* ═══════════════════════════════════════════════════════════════════
   5.3  .carta--dato : the statistic card.

   Replaces the `.ed-chips` treatment, which was a saturated pill with a
   grotesk figure and a body-size label. Here the figure is the whole
   card: Archivo Black at display size in the accent INK, over a small
   Cinzel caption.

   Two deliberate colour choices:

     * the figure keeps the accent. It is display sized, so it only owes
       3:1, and it measures 4.93:1 at worst anyway.
     * the LABEL does not. It is 12px, and the file already has a
       precedent (the ambar and esmeralda foot guard in the
       reconciliation block) for handing small Cinzel back to the ink
       ramp rather than auditing every ground it can land on. Applying
       that rule to all four colourways rather than two keeps a stat
       band reading as one row instead of two accents and two greys.

   Archivo Black, not Cinzel, for the figure: section 2 of this layer
   states that `--font-black` remains the structural display face and
   owns the big `.ed-chip-num` figures. A stat card is that same figure.
   ═══════════════════════════════════════════════════════════════ */

.carta.carta--dato {
  /* Centres the figure in the leftover space when a taller sibling in
     the row stretches this card. With no foot there is no auto margin
     to do it, so it is stated. */
  justify-content: center;
}

.carta .carta-dato {
  display: block;
  font-family: var(--font-black);
  font-weight: 400;
  font-size: clamp(36px, 4.4vw, 56px);
  line-height: 0.94;
  letter-spacing: -0.02em;
  /* Figures across a row of stat cards have to sit on the same width or
     the row reads as ragged. `lining-nums` stops an old-style face from
     dropping the 3 and the 9 below the baseline. */
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--carta-ink);
  /* "24/7" and "$1,500" are single tokens and must never break. */
  white-space: nowrap;
  margin: 0 0 10px;
}

.carta .carta-dato-label {
  display: block;
  max-width: 24ch;
  font-family: var(--font-carta);
  font-weight: 700;
  font-size: var(--fs-body-s);
  line-height: 1.35;
  letter-spacing: 0.09em;
  color: var(--tinta-700);
  margin: 0;
}
/* Degradation: label present, no foot underneath it. */
.carta .carta-dato-label:last-child { margin-bottom: 0; }
/* Degradation: figure with no label at all. */
.carta .carta-dato:last-child { margin-bottom: 0; }

/* A dato card usually has no bubble, so the head rule would otherwise
   sit 18px above a figure that is already visually heavy. */
.carta.carta--dato > .carta-head { margin-bottom: 14px; }


/* ═══════════════════════════════════════════════════════════════════
   5.4  .carta--lista : the ruled list card.

   Same 1px hairline rhythm as `.lista-icono`, inside the frame. The
   base carta centres its content, which is right for a step cell and
   wrong for a list, so this variant hands alignment back to the start.

   The hairline is mixed from `currentColor` rather than pinned, exactly
   as `.lista-icono` does upstream, so the rhythm survives the card being
   dropped anywhere. Engines with no `color-mix` keep the `--hairline`
   line above, which is correct on both canvases.
   ═══════════════════════════════════════════════════════════════ */

.carta.carta--lista {
  align-items: stretch;
  text-align: start;
}
.carta.carta--lista p { max-width: none; }

.carta .carta-lista {
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}
.carta .carta-lista > li {
  display: flex;
  align-items: flex-start;   /* the bubble stays with the first line */
  gap: 12px;
  padding: 13px 0;
  border-bottom: 1px solid var(--hairline);
  border-bottom-color: color-mix(in srgb, currentColor 14%, transparent);
}
/* The list opens flush under the head rule and closes on air, the way
   the compare tables do. */
.carta .carta-lista > li:first-child { padding-top: 0; }
.carta .carta-lista > li:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}
/* With a foot below it the last row keeps its breathing room, because
   the foot draws its own 1.5px rule and the two would collide. */
.carta .carta-lista:not(:last-child) > li:last-child { padding-bottom: 13px; }
.carta .carta-lista:last-child { margin-bottom: 0; }

.carta .carta-lista-texto {
  min-width: 0;              /* lets long strings wrap, not overflow */
  font-size: var(--fs-body-s);
  line-height: 1.45;
}
.carta .carta-lista-texto > :first-child { font-weight: 700; }
.carta .carta-lista-texto > :first-child:not(:last-child) {
  display: block;
  margin-bottom: 2px;
}
/* The secondary line steps down in SIZE only, never in colour. A dimmed
   grey here would be a second contrast problem on every ground a carta
   can be dropped on. */
.carta .carta-lista-texto > :last-child:not(:first-child) {
  display: block;
  font-size: 13px;
  font-weight: 500;
  margin-bottom: 0;
}

/* `.lista-icono` may be used instead of `.carta-lista`; it is the same
   pattern and it should not lose its edge trims inside a frame. */
.carta.carta--lista .lista-icono { width: 100%; }
.carta.carta--lista .lista-icono > li:first-child { padding-top: 0; }

@media (max-width: 480px) {
  .carta .carta-lista > li { gap: 10px; padding: 11px 0; }
}


/* ═══════════════════════════════════════════════════════════════════
   5.5  .carta--media : the image and video frame.

   The media bleeds to the frame instead of the card losing its padding.
   That is one negative margin rather than six re-paddings, and it is
   what makes the variant degrade: any child the page does not wrap in
   `.carta-media` keeps the base padding automatically, so a media carta
   with a head, a title and no foot needs no extra rules at all.

   `overflow: hidden` is what clips the bled media into the 20px corner.
   It is safe here because the elevation is a `box-shadow`, which is
   painted outside the border box and is not affected by overflow.

   The ruled foot becomes the caption bar: it keeps its 1.5px rule, gains
   the colourway tint as a ground, and bleeds to the bottom corners.
   `--tinta-700` on the four pastels measures 9.3:1 to 11.2:1 in both
   themes, so the bar is legible in every colourway.
   ═══════════════════════════════════════════════════════════════ */

.carta.carta--media {
  align-items: stretch;
  text-align: start;
}
.carta.carta--media p { max-width: none; }

/* The caption bar. */
.carta.carta--media > .carta-foot {
  width: calc(100% + 2 * var(--carta-pad-x));
  margin-inline: calc(-1 * var(--carta-pad-x));
  margin-bottom: calc(-1 * var(--carta-pad-bottom));
  padding: 12px var(--carta-pad-x);
  background: var(--carta-tint);
  text-align: start;
}
/* Caption COPY, as opposed to the Cinzel plate label. Beats the
   upstream `.carta-foot span` at (0,1,1) from (0,2,0). */
.carta .carta-caption {
  display: block;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--fs-body-s);
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  color: var(--tinta-700);
}


/* ═══════════════════════════════════════════════════════════════════
   5.6  .carta--enlace : the card that is a link.

   Written as `.carta.carta--enlace` throughout because the upstream
   `a.carta:hover` is (0,2,1) and a bare `.carta--enlace:hover` at
   (0,2,0) would lose to it regardless of source order.

   Hover is lift plus frame emphasis. The emphasis is an INSET ring in
   the accent rather than a wider border, because growing a 2px border
   to 4px reflows the card's whole interior on hover. An inset ring is
   painted, not laid out, so nothing moves.

   The focus ring is two-tone on purpose, and that is the only reason it
   is safe. A single ink ring is 16.08:1 on bone paper and 16.59:1 on ink
   stock, but only 2.56:1 against `--verde-700` if the card is sitting on
   a saturated `.ed-panel`. The 3px band of carta stock between the card
   and the ink ring measures 17.18:1 light / 12.02:1 dark against the
   ring itself, which satisfies the indicator contrast requirement no
   matter what is behind the card. It cannot be defeated by a ground.

   The ring is listed BEFORE the lift in the shadow list, so it paints
   over the 3px lift sliver: an ink ring drawn beside an ink lift reads
   as one thick smear at the bottom right corner.
   ═══════════════════════════════════════════════════════════════ */

.carta.carta--enlace {
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    transform var(--dur-fast) var(--ease-standard),
    box-shadow var(--dur-fast) var(--ease-standard);
}

/* Not trapping the icon, both senses of it.

   Pointer: an inline SVG inside an anchor becomes the event target for
   the click and, in WebKit, a draggable object, so a drag that starts on
   the glyph never reaches the link. `pointer-events: none` hands every
   pixel of the card back to the anchor.

   Layout: `.carta--enlace` deliberately does NOT set `overflow: hidden`,
   so the flecha glyph can slide on hover without being clipped. The one
   combination that does clip is `--enlace` plus `--media`, and there the
   glyph belongs in the padded copy area, not over the image. */
.carta.carta--enlace svg,
.carta.carta--enlace img {
  pointer-events: none;
  -webkit-user-drag: none;
}
/* A control genuinely inside a link card (a secondary link, a chip) has
   to keep its own hit area. */
.carta.carta--enlace :is(a, button, [role="button"]) {
  pointer-events: auto;
  position: relative;
  z-index: 1;
}

/* The overlay pattern: the card is a plain element, one link inside it
   covers the whole frame. Preferred when the card holds more than one
   piece of interactive text, because it keeps the accessible name short
   instead of announcing the entire card. `.carta` is already
   `position: relative`, so the overlay has something to anchor to. */
.carta.carta--enlace .carta-cubre { position: static; }
.carta.carta--enlace .carta-cubre::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 18px;
}

@media (hover: hover) and (pointer: fine) {
  .carta.carta--enlace:hover {
    transform: translate(-1px, -1px);
    box-shadow:
      inset 0 0 0 2px var(--carta-ink),
      5px 5px 0 var(--tinta-900);
  }
  .carta.carta--enlace:hover .carta-flecha > svg {
    transform: translateX(3px);
  }
  .carta .carta-flecha > svg {
    transition: transform var(--dur-fast) var(--ease-standard);
  }
}

/* Both spellings of focus: the card IS the anchor, or the card CONTAINS
   the anchor. `:has()` takes the specificity of its argument, so both
   selectors land at (0,3,0) and clear `a.carta:hover`. The argument is
   narrowed to real controls so a `--interactiva` carta full of form
   fields can never ring itself. */
.carta.carta--enlace:focus-visible,
.carta.carta--enlace:has(:is(a, button):focus-visible) {
  /* Transparent rather than none. Forced colours repaints a transparent
     outline in a system colour and drops box-shadow entirely, so this is
     the only part of the ring that survives there. */
  outline: 2px solid transparent;
  outline-offset: 4px;
  box-shadow:
    0 0 0 3px var(--papel-carta),
    0 0 0 6px var(--carta-ring),
    var(--card-lift);
}
/* The inner anchor has already handed its ring to the card, so it must
   not draw a second one inside the frame. */
.carta.carta--enlace .carta-cubre:focus-visible { outline: none; }


/* ═══════════════════════════════════════════════════════════════════
   5.7  .carta--ancha : the full width horizontal carta.

   Flex row with wrapping rather than a two column grid, for one reason:
   the head and the foot are optional, and in a grid every optional part
   has to be given an explicit row. With `flex-wrap` plus a 100% basis
   they simply take their own line wherever they appear, and `order`
   handles the mirrored layout without any of that bookkeeping.

   Stacking is the same mechanism: at 720px the two columns are given a
   100% basis, which forces the wrap. There is no `flex-direction:
   column`, because that would reinterpret every `flex-basis: 100%` in
   this block as a HEIGHT.
   ═══════════════════════════════════════════════════════════════ */

.carta.carta--ancha {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  text-align: start;
  gap: clamp(16px, 2vw, 28px);
}
.carta.carta--ancha p { max-width: 60ch; }

.carta.carta--ancha > .carta-head {
  order: -1;
  flex: 1 0 100%;
  margin-bottom: 0;      /* the row gap already provides it */
}
.carta.carta--ancha > .carta-foot {
  order: 9;
  flex: 1 0 100%;
  margin-top: 0;
}
.carta.carta--ancha > .carta-aside {
  flex: 0 0 var(--carta-aside-w, 34%);
  max-width: 100%;
  align-self: stretch;
}
/* `min()` on the basis is what lets the copy column collapse instead of
   forcing an overflow when the card is narrow. */
.carta.carta--ancha > .carta-cuerpo {
  flex: 1 1 min(100%, 24ch);
}
/* A bare bubble as the aside, with no wrapper. */
.carta.carta--ancha > .carta-bubble {
  margin-bottom: 0;
}

/* Media on the right instead of the left. `order` on the aside is
   enough: the head is already pinned to -1 and the foot to 9. */
.carta.carta--ancha-inversa > .carta-aside { order: 1; }

/* Wide plus media is the one combination where bleeding by negative
   margin is the wrong tool: the aside has to be flush on THREE edges,
   and a card that kept its own padding while the copy column added its
   own would inset the text twice on the far side. So this pair moves
   the padding off the card and onto the parts, which also means the
   aside needs no negative margin at all and the mirrored and stacked
   layouts have nothing to un-do. */
.carta.carta--ancha.carta--media {
  gap: 0;
  padding: 0;
}
.carta.carta--ancha.carta--media > .carta-aside {
  align-self: stretch;
  margin: 0;
}
.carta.carta--ancha.carta--media > .carta-cuerpo {
  padding: var(--carta-pad-top) var(--carta-pad-x) var(--carta-pad-bottom);
}
.carta.carta--ancha.carta--media > .carta-head {
  padding: var(--carta-pad-top) var(--carta-pad-x) 10px;
}
/* The caption-bar rule three sections up bleeds the foot with negative
   margins, which is right for a stacked media carta and wrong here,
   where the card has no padding left to cancel. (0,4,0) beats it. */
.carta.carta--ancha.carta--media > .carta-foot {
  width: 100%;
  margin-inline: 0;
  margin-bottom: 0;
  padding: 10px var(--carta-pad-x) var(--carta-pad-bottom);
}

@media (max-width: 720px) {
  .carta.carta--ancha {
    align-items: flex-start;
    gap: clamp(14px, 3vw, 20px);
  }
  .carta.carta--ancha > .carta-aside,
  .carta.carta--ancha > .carta-cuerpo {
    flex: 1 0 100%;
    max-width: 100%;
  }
  /* Source order decides on a phone, so the image leads whichever side
     it was authored on. A mirrored layout that keeps its order stacks
     copy above image, which reads as a caption for the wrong thing. */
  .carta.carta--ancha-inversa > .carta-aside { order: 0; }
}


/* ═══════════════════════════════════════════════════════════════════
   5.8  .carta--interactiva : the frame around the calculator.

   The reference draws its simulator as a carta titled CARTA INTERACTIVA
   with a numeral, and the quoter is the one place on this site where a
   whole working instrument sits inside a card. This variant is the
   WRAPPER only. `scripts/fx-calculator.js` injects the `.fx-card`
   subtree at runtime, so no rule below assumes a class, an id or an
   element order that the script does not already emit, and no markup
   changes anywhere.

   Two things do have to reach inside, and both are chrome rather than
   structure:

     1. `.fx-card` carries its own 24px radius, 1px hairline border,
        28px padding and a SOFT two-layer drop shadow. Left alone, a
        blurred rounded card would be floating inside a hard-edged
        loteria frame, which is the exact thing rule 4 of the contract
        forbids. Its chrome is switched off; its `container: fx-card /
        inline-size` is untouched, so every container query in the
        calculator keeps working.
     2. `.fx-input-row:focus-within` is the ONLY focus indicator the
        number field has, because `.fx-input-row input` sets
        `outline: none` at (0,1,1) and beats the global focus ring. It
        was a legacy verde with a fixed rgba halo that does not flip.
        It becomes the carta's own accent, which is 4.62:1 at worst
        against the field ground in both themes.
   ═══════════════════════════════════════════════════════════════ */

.carta.carta--interactiva {
  align-items: stretch;
  text-align: start;
}
.carta.carta--interactiva p { max-width: 62ch; }
.carta.carta--interactiva > * { max-width: 100%; }

/* The mount point the script writes into. Given the top gap here so the
   variant works whether the page wraps it or hands the id straight to
   a bare div. */
.carta.carta--interactiva > .carta-cuerpo,
.carta.carta--interactiva > [id$="calculator"] {
  width: 100%;
  margin-top: 4px;
}
/* A centring flex parent sizes the carta to its content, and .fx-card's inline-size containment makes that 0. Take the row, capped. */
:is(.smx-calc, .ed-calc-panel) > .carta.carta--interactiva { width: 100%; max-width: 600px; }

.carta--interactiva .fx-card,
.carta--interactiva .fx-compare-standalone {
  width: 100%;
  max-width: none;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.carta--interactiva .fx-input-row:focus-within,
.carta--interactiva .fx-field select:focus {
  border-color: var(--carta-ink);
  box-shadow: 0 0 0 3px var(--carta-tint);
}

/* Before the script mounts (and with scripting off) the carta holds
   only the "Loading..." line and the noscript fallback. The saturated
   panel's fixed-ink `p` and `a` rules reached them through the themed
   carta, landing ink on dark paper (1.27:1 and 1.08:1). Back on the
   themed ramp, the same carve-out `.fx-card p` gets. */
.ed-panel--verde-vivo .carta--interactiva .fx-compare-foot,
.ed-panel--menta .carta--interactiva .fx-compare-foot { color: var(--tinta-500); }
.ed-panel--verde-vivo .carta--interactiva .fx-fallback a,
.ed-panel--menta .carta--interactiva .fx-fallback a { color: var(--tinta-900); }

/* With scripting off nothing ever replaces the "Loading..." line, so
   it goes; the calculator's noscript fallback carries the card, and
   the comparison card, which has no fallback, goes with its line. */
@media (scripting: none) {
  :is(#fx-calculator, #fx-compare) > .fx-compare-foot { display: none; }
  .carta--interactiva:has(> #fx-compare) { display: none; }
}


/* ═══════════════════════════════════════════════════════════════════
   5.9  Accessibility passes for the family.
   ═══════════════════════════════════════════════════════════════ */

/* The lift and the glyph slide are gestures, not information. The
   colour and ring changes stay, because those ARE the affordance. */
@media (prefers-reduced-motion: reduce) {
  .carta.carta--enlace {
    transition: none;
  }
  .carta.carta--enlace:hover {
    transform: none;
  }
  .carta .carta-flecha > svg,
  .carta.carta--enlace:hover .carta-flecha > svg {
    transition: none;
    transform: none;
  }
}

/* Someone asking for more contrast is asking us to remove the tinted
   caption bar and the pastel bubble ground, both of which are texture
   sitting under type. The frame, the rules and the ink stay. */
@media (prefers-contrast: more) {
  .carta.carta--media > .carta-foot {
    background: transparent;
    border-top-width: 2px;
  }
  .carta .carta-caption,
  .carta .carta-dato-label {
    color: var(--tinta-900);
  }
  .carta .carta-lista > li {
    border-bottom-color: var(--hairline-strong);
  }
}

/* Forced colours strips author colour, backgrounds and box-shadow. Every
   rule in this section that draws with one of those has to hand itself
   back to a system keyword here, and this block is written after the two
   upstream `forced-colors` blocks, which carry no specificity of their
   own, so it is the last word. */
@media (forced-colors: active) {
  .carta .carta-media,
  .carta.carta--media > .carta-foot {
    background: Canvas;
    border-color: CanvasText;
  }
  .carta .carta-dato,
  .carta .carta-dato-label,
  .carta .carta-caption,
  .carta .carta-flecha {
    color: CanvasText;
  }
  .carta .carta-lista > li {
    border-bottom-color: CanvasText;
  }
  /* The two-tone ring is a box-shadow and does not survive here, so the
     transparent outline declared above is what carries focus. */
  .carta.carta--enlace:focus-visible,
  .carta.carta--enlace:has(:is(a, button):focus-visible) {
    outline: 3px solid Highlight;
    outline-offset: 3px;
    box-shadow: none;
  }
  .carta.carta--enlace:hover {
    box-shadow: none;
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}
/* ═══════════════════════════════════════════════════════════════════
   THE LIVING FOOTER.

   The design system's grid-paper footer is not a background image with
   five pinned squares: it is a real generated grid that breathes. A
   rolling queue keeps four cells at full strength while a fifth
   dissolves, so colour never accumulates and the paper feels alive.

   Progressive enhancement, in the site's existing idiom: the CSS grid
   image below stays as the no-JS floor, and scripts/main.js upgrades
   the paper by building real cells and adding `is-live`, which is what
   turns the painted image off. Nothing here changes a page whose JS
   never runs.
   ═══════════════════════════════════════════════════════════════ */

:root {
  /* Per-cell tint for the living footer. JS writes it on each cell; the
     declaration here is the documented default and satisfies
     lint-tokens.mjs, which requires every referenced name to exist. */
  --footer-cell-tint: transparent;
}

.ed-footer-paper.is-live { background-image: none; }

.ed-footer-grid {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: grid;
  /* Only the container draws a top rule. Every cell draws its own left
     and bottom edge, so the seams meet exactly once and the grid reads
     as one continuous stroke rather than doubled lines. */
  border-top: 1px solid var(--tinta-300);
  pointer-events: auto;
}
.ed-footer-grid > div { position: relative; }
/* Colour rides an ::after so a fading cell never fades the ink rule
   underneath it. */
.ed-footer-grid > div::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--footer-cell-tint);
  opacity: 0;
  transition: opacity 900ms cubic-bezier(0.45, 0, 0.55, 1);
}
.ed-footer-grid > div.lit::after {
  opacity: 1;
  transition: opacity 350ms cubic-bezier(0, 0, 0.2, 1);
}
/* The wordmark and the columns sit above the weave. */
.ed-footer-paper > .ed-footer-wordmark,
.ed-footer-paper > .footer { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  .ed-footer-grid > div::after { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   THE NAV AND THE BUTTONS KEEP MAIN'S PIGMENTS (owner, 2026-09-22)

   "The nav bar is perfect, it shouldn't have been changed" and "keep
   everything about the button". (The side rail is the new editorial
   deck in main's round shapes; see editorial-system.css.) Their rules are main.css's own; what
   still differed was the colour: this layer re-tones the whole palette
   (ink #1C1917, esmeralda #059669, amber, rose...) and the editorial
   body switches --card-lift off to flatten cards, which also took the
   pill's hover lift. So the capsule, its menu sheet and every .btn
   re-declare main's values for exactly the tokens that differ; the
   page around them keeps the new palette. The list was generated by
   diffing the computed tokens of both sites; if main's palette moves,
   regenerate it rather than editing values by hand.
   ═══════════════════════════════════════════════════════════════════ */
.nav,
.nav-sheet,
.btn {
  --accent-verde: #006847;
  --accent-verde: light-dark(#006847, #6FCF97);
  --amarillo-500: #FFC400;
  --azul-700: #0A45A6;
  --blanco: #FFFFFF;
  --blanco: light-dark(#FFFFFF, #2E2921);
  --brand-dot: #006847;
  --brand-dot: light-dark(#006847, #00CC64);
  --card-lift: 3px 3px 0 #0B0B0B;
  --card-lift: 3px 3px 0 light-dark(#0B0B0B, #918B82);
  --crema: #F2EEE6;
  --crema: light-dark(#F2EEE6, #211C14);
  --glass-edge-bottom: rgba(255, 255, 255, 0.14);
  --glass-edge-luminous: rgba(255, 255, 255, 0.85);
  --glass-edge-stroke: rgba(255, 255, 255, 0.42);
  --glass-edge-stroke-dark: rgba(255, 255, 255, 0.1);
  --glass-edge-stroke-warm: rgba(250, 247, 242, 0.5);
  --glass-edge-top: rgba(255, 255, 255, 0.62);
  --glass-edge-top-dark: rgba(255, 255, 255, 0.18);
  --glass-surface-chrome: rgba(250, 247, 242, 0.74);
  --glass-surface-chrome: light-dark(rgba(250, 247, 242, 0.74), rgba(21, 18, 14, 0.8));
  --glass-surface-clear: rgba(255, 255, 255, 0.12);
  --glass-surface-dark: rgba(11, 11, 11, 0.42);
  --glass-surface-dark-regular: rgba(11, 11, 11, 0.58);
  --glass-surface-light: rgba(250, 247, 242, 0.62);
  --glass-surface-light: light-dark(rgba(250, 247, 242, 0.62), rgba(28, 24, 20, 0.66));
  --glass-surface-luminous: rgba(250, 254, 251, 0.66);
  --glass-surface-luminous: light-dark(rgba(250, 254, 251, 0.66), rgba(32, 28, 23, 0.7));
  --glass-surface-luminous-strong: rgba(250, 254, 251, 0.82);
  --glass-surface-luminous-strong: light-dark(rgba(250, 254, 251, 0.82), rgba(32, 28, 23, 0.86));
  --glass-surface-regular: rgba(250, 247, 242, 0.36);
  --glass-surface-regular: light-dark(rgba(250, 247, 242, 0.36), rgba(28, 24, 20, 0.44));
  --glass-tint-verde: rgba(0, 166, 81, 0.18);
  --glass-tint-verde-strong: rgba(0, 104, 71, 0.32);
  --hairline: rgba(11, 11, 11, 0.09);
  --hairline: light-dark(rgba(11, 11, 11, 0.09), rgba(246, 242, 232, 0.14));
  --hairline-strong: rgba(11, 11, 11, 0.16);
  --hairline-strong: light-dark(rgba(11, 11, 11, 0.16), rgba(246, 242, 232, 0.24));
  --hueso: #FAF7F2;
  --hueso: light-dark(#FAF7F2, #14100A);
  --hueso-100: #F6F3EB;
  --hueso-100: light-dark(#F6F3EB, #201C16);
  --hueso-200: #F2EEE0;
  --hueso-200: light-dark(#F2EEE0, #2E2921);
  --ink-fixed: #0B0B0B;
  --note-ink: #55555C;
  --note-ink: light-dark(#55555C, #B3ADA3);
  --panel-border: 2px solid #0B0B0B;
  --panel-border: 2px solid light-dark(#0B0B0B, #918B82);
  --panel-border-width: 2px;
  --panel-gap: clamp(10px, 1.2vw, 16px);
  --panel-pad: clamp(24px, 3.4vw, 56px);
  --panel-radius: 28px;
  --panel-radius-sm: 18px;
  --rosa-500: #E4007C;
  --rosa-600: #C4006B;
  --tinta-300: #C7C7CC;
  --tinta-300: light-dark(#C7C7CC, #413C35);
  --tinta-400: #62626A;
  --tinta-400: light-dark(#62626A, #A49E94);
  --tinta-500: #55555C;
  --tinta-500: light-dark(#55555C, #B3ADA3);
  --tinta-700: #2A2A2E;
  --tinta-700: light-dark(#2A2A2E, #DCD7CD);
  --tinta-900: #0B0B0B;
  --tinta-900: light-dark(#0B0B0B, #F7F1E6);
  /* --verde-500 is deliberately NOT pinned (owner, 2026-09-22: "the same
     colour as the pricing green"): every primary button takes the page's
     green, #059669, the same as the rail's Pricing card. */
}
/* Their text inherited main's page ink; the page ink here is the new
   palette's, so the containers restate main's. */
.nav,
.nav-sheet { color: var(--tinta-900); }
/* Main's high-contrast ramp, restated at the same scope so the pin
   above cannot undo it. */
@media (prefers-contrast: more) {
  .nav,
  .nav-sheet,
  .btn {
    --tinta-700: #000000;
    --tinta-700: light-dark(#000000, #FFFFFF);
    --tinta-500: #1A1A1F;
    --tinta-500: light-dark(#1A1A1F, #EDEAE1);
    --tinta-400: #1A1A1F;
    --tinta-400: light-dark(#1A1A1F, #EDEAE1);
    --hairline: rgba(0, 0, 0, 0.5);
    --hairline: light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.55));
    --hairline-strong: #000000;
    --hairline-strong: light-dark(#000000, #FFFFFF);
    --hueso: #FFFFFF;
    --hueso: light-dark(#FFFFFF, #000000);
    --blanco: #FFFFFF;
    --blanco: light-dark(#FFFFFF, #0D0D0D);
  }
}

/* ═══════════════════════════════════════════════════════════════════
   PHONE PILLS THAT WRAP.

   `.btn` is a flex row, so a label that needs two lines on a phone
   became an anonymous flex item the full width of the pill and its
   icon a separate item beside it: the arrow floated at the far edge,
   centred between the two lines, reading as a second control. Below
   640px a pill with an icon lays out as text instead, so the icon rides
   the last word and the lines balance. Single-line pills keep their
   height; the rail CTA (desktop only) is untouched, and the nav and
   sheet pills live outside <main>. Every one of these sits in a flex or
   grid parent (blockified) or a block parent where it was inline-flex,
   so the box keeps its width either way.
   ═══════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  /* Every pill in a stack centres its label, icon or not, so a survey
     pill reads like the waitlist and back pills beside it. */
  main .btn:not(.ed-rail-cta) {
    text-wrap: balance;
    text-align: center;
    justify-content: center;
  }
  main .btn:not(.ed-rail-cta):has(> svg) {
    display: inline-block;
    text-align: center;
  }
  main .btn:not(.ed-rail-cta):has(> svg) > svg {
    display: inline-block;
    vertical-align: -0.08em;
    /* The flex gap is gone; the label's own space plus this matches
       it, on whichever side the icon sits. */
    margin-inline: 0.2em;
  }
}
/* The narrowest phones (320-359). The hero pair stacks full width, so
   a primary label one word longer than its sibling ("Aidez à façonner
   Pesito", "Ajude a moldar o Pesito") broke onto a second line with
   the brand name alone, and the two pills sat at different heights
   (85 vs 58px). A slightly tighter pill keeps both labels on one line;
   it still clears the 44px target. Labels that are long by any measure
   (the Nahuatl one) still wrap, balanced, as above. */
@media (max-width: 359px) {
  main .hero-ctas .btn:not(.ed-rail-cta) {
    padding-inline: 16px;
    font-size: 16px;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   PRINT.

   Terms, privacy and the on-the-record answers get saved as PDF. Print
   always resolves the light tokens (the dark blocks above are screen
   only), drops the site chrome and the decorative footer, and keeps
   cards and answers whole across page breaks.
   ═══════════════════════════════════════════════════════════════ */
@media print {
  html,
  html[data-theme] { color-scheme: light; }
  .ed-rail,
  nav.nav,
  .nav-sheet,
  .skip-link,
  .theme-toggle,
  .ed-footer-grid,
  .ed-footer-wordmark,
  .hero-store-row,
  .editorial-hero-support,
  video,
  .showcase-stage { display: none !important; }
  .ed-footer-paper { background: none; }
  main { padding-top: 0; }
  h2, h3 { break-after: avoid; }
  .carta, .ed-panel, details, .rec-table { break-inside: avoid; }
  main :is(p, li, dd, td) a[href^="http"]:not(.btn)::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    word-break: break-all;
  }
}
