/* home.css - page-scoped layout for index.html (Fini Landing v3). Render-
 * blocking: keep comments terse, every byte is on the S1.21 budget. No
 * selector here redefines a base token; scoped under this page's own hooks.
 */

/* R2 spacing tokens (spacing-spec.md): section = padding-block; title =
 * h2->lede; head = head->component; cta = component->.wa-cta; stack =
 * sub-blocks inside a component. */
:root { --sp-section: 88px; --sp-title: 16px; --sp-head: 56px; --sp-cta: 56px; --sp-cta-lead: 16px; --sp-stack: 32px; }
@media (max-width: 1023px) { :root { --sp-section: 72px; --sp-head: 44px; --sp-cta: 44px; --sp-stack: 28px; } }
@media (max-width: 600px) { :root { --sp-section: 56px; --sp-title: 14px; --sp-head: 32px; --sp-cta: 32px; --sp-cta-lead: 14px; --sp-stack: 24px; } }

/* ============ A12 (S1 gate iteration 5, F1): no repaint-and-hide ============
 * fills.css's `.js .page-fade{opacity:1}` assumes `.js` lands before first
 * paint - only true when fx.js is a blocking <script>. Since S1.21 made this
 * page's own scripts `defer`, a slow connection paints the served page THEN
 * adds `.js`, slamming an opaque curtain on top of visible content (the load
 * flash). This page cancels that trigger and uses `.nav-transition` instead
 * (set synchronously by an inline <head> script - before ANY paint, on every
 * load - only when fx.js's click-intercept flagged this as an in-site nav
 * arrival). A first/direct load never gets that class, so the curtain stays
 * fully transparent, start to finish: nothing to flash. Source order (not
 * specificity - both selectors are 2 classes) makes the second rule win
 * whenever both are present. */
@media (prefers-reduced-motion: no-preference) {
  .js .page-fade { opacity: 0; }
  .nav-transition .page-fade { opacity: 1; }
}

/* A12(c): the served rotator word (plain text, visible pre-JS) must render
 * with fx.js's exact eventual styling (editorial.css .rot-rotator__word:
 * italic Instrument Serif, 1.06em, -.01em tracking) so initRotatingText()'s
 * DOM rewrite (fx.js) never swaps the typeface out from under a visitor who
 * already saw it. `:not(.rot-rotator)` is load-bearing, not decoration: fx.js
 * adds that class to this SAME span and nests the sized/styled word inside
 * it - if this rule kept matching afterward too, the child's own 1.06em
 * would compound on top of this element's 1.06em (1.06 x 1.06, a real size
 * jump). The :not() stops this rule the instant that class lands, so the
 * two states hand off at identical computed size instead of stacking. */
.h-display .rot-word:not(.rot-rotator) {
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  font-size: 1.06em;
  letter-spacing: -.01em;
}

/* ============ S0: nav menu visual: WhatsApp/app phone mock [data-mock] ============ */
.nav-app {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  overflow: hidden;
  border-radius: 24px;
  -webkit-mask-image: linear-gradient(#000 78%, transparent);
  mask-image: linear-gradient(#000 78%, transparent);
  background: radial-gradient(80% 70% at 50% 30%, #eef9dc, #f7fcf0 70%);
  height: 100%;
}
.nav-app__phone {
  width: calc(393px * .56);
  height: calc(852px * .56);
  margin-top: 36px;
  position: relative;
  flex: none;
}
.nav-app__phone .wa-device {
  transform: scale(.56);
  transform-origin: 0 0;
}
.nav-app__screen {
  position: absolute;
  inset: 0;
  border-radius: 56px;
  overflow: hidden;
  background: linear-gradient(180deg, #f7fcf0, #f8f9f6);
  padding: 66px 22px 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
  font-family: var(--font-label), sans-serif;
  color: var(--green-ink);
}
/* Status bar out of flow: overlays the top instead of pushing rows down. */
.nav-app__screen .wa-status {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
}
.nav-app__top {
  display: flex;
  align-items: center;
  gap: 8px;
}
.nav-app__top img { width: 22px; height: 22px; object-fit: contain; flex: none; }
.nav-app__top span { font-size: 15px; color: #74746c; }
.nav-app__hello { font-size: 32px; font-weight: 600; letter-spacing: -.02em; margin: 6px 0 0; line-height: 1; }
.nav-app__sub { font-size: 17px; color: #74746c; margin: -6px 0 4px; }
.nav-app__label { font-size: 16px; font-weight: 500; color: #74746c; margin: 6px 0 -4px; }

/* S0: egg last-slide CTA (D20: drop the inline margin-top) */
.wrapped-egg__slide .btn--primary { margin-top: 8px; }

/* S0: menu CTA card margin (render: cta-v3.css:30) */
.nav-cta { margin: 28px 0 8px; }

/* N1: base.css's .twostep-nav__bottom-col has no min-width override, so it
 * floors at content size and wraps "Aviso de Privacidad" at 768-790px;
 * letting it shrink below content width restores the render's one row. */
.twostep-nav__bottom-col.is--visual { min-width: 0; max-width: 100%; }

/* S0/M2: the footer WhatsApp number on its own nowrap line, so it never
 * breaks mid-number and doesn't inflate .fp-top's shrink-to-fit grid width
 * (G14, S1). */
.fp-wa-number { display: block; white-space: nowrap; }
/* N2: the <a> was inline with a block span inside, so :focus-visible only
 * ringed the first fragment; inline-block gives it one generated box. */
a[data-track-location="footer_whatsapp"] { display: inline-block; }

/* G16: zero every twostep-nav open/close transition directly (same
 * selectors/specificity as base.css/fills.css, shared/untouched) - zeroing
 * only the duration vars leaves literal transition-delays still "running"
 * for their full window, which fails a 100ms-after-open check. fx.js drives
 * nav state off data-nav-status/aria/focus(), never a transitionend, so
 * this can't strand it mid-open/close. */
@media (prefers-reduced-motion: reduce) {
  .twostep-nav__bg,
  .twostep-nav__bar,
  [data-nav-status="active"] .twostep-nav__bar,
  .twostep-nav__top-line,
  [data-nav-status="active"] .twostep-nav__top-line,
  .twostep-nav__back,
  .twostep-nav__bottom,
  [data-nav-status="active"] .twostep-nav__bottom,
  .twostep-nav__bottom-row > *,
  [data-nav-status="active"] .twostep-nav__bottom-row > *,
  .twostep-nav__toggle-bar,
  .twostep-nav__cta {
    transition: none;
  }
}

/* ============ S1 Hero: two-state stage + conversation + right rail ============
 * design-notes §3.1. `.stage-flow` wraps #inicio + the bridge. Geometry is
 * CSS custom properties (--ps1/--ps2/--p1-top/--p2-top), viewport-relative
 * calc()/min(), so no JS resize listener is needed.
 * CLS rule (S1.6): top/bottom/margin-top/height of .stage__phone/__copy/
 * __side are set ONCE (never touched by .is--open); all state-2 motion is
 * `transform` only (translateY covers both the phone's shift and the
 * copy/side "bottom 20% -> 9%" move) - diverges from design-notes' literal
 * "bottom transition" wording; the CLS rule supersedes it. */
.stage-flow { position: relative; }

.hero--stage {
  min-height: 100svh;
  padding: 0;
  display: block;
  overflow: hidden;
  text-align: left;
}

.stage-bridge {
  position: relative;
  z-index: 1;
  height: 260px;
  margin-top: -260px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(247, 252, 240, 0) 0%, rgba(247, 252, 240, .55) 40%, rgba(247, 252, 240, .9) 75%, #f7fcf0 100%);
}

.stage { position: relative; z-index: 2; }

@media (min-width: 961px) {
  .stage {
    height: 100svh;
    min-height: 640px;
    --ease-stage: cubic-bezier(.32, .72, 0, 1);
    /* ps1 = min(1, .29*vw/393); ps2 = min(.76, (vh-190)/852, ps1-.1) (D25,
     * tightened 2026-09-24 per Jero: a slightly smaller open phone so it
     * keeps clear air under the nav bar). */
    --ps1: min(1, calc(.29 * 100svw / 393px));
    --ps2: min(.76, calc((100svh - 190px) / 852px), calc(var(--ps1) - .1));
    /* A8(a): at short viewports (~830px or less) the plain formula can put
     * the phone/rail top within 16px of, or under, the fixed nav pill
     * (measured bottom 84px). Clamped to a 100px floor (84+16); never binds
     * at 1440x900 or taller. */
    --p1-top: max(21svh, 100px);
    --p2-top: max(calc(50svh - (852px * var(--ps2)) / 2 + 22px), 116px);
  }

  .stage__phone {
    position: absolute;
    left: 50%;
    top: var(--p1-top);
    width: 393px;
    height: 852px;
    transform: translateX(-50%) scale(var(--ps1));
    transform-origin: 50% 0;
    transition: transform 1200ms var(--ease-stage);
    /* max(72, round(852-(vh-p1top)/ps1+40)) so the newest bubble sits ~40px
     * above the fold. */
    --wa-thread-pad: max(104px, calc(852px - (100svh - var(--p1-top)) / var(--ps1) + 40px));
  }
  .stage.is--open .stage__phone {
    transform: translateX(-50%) translateY(calc(var(--p2-top) - var(--p1-top))) scale(var(--ps2));
    --wa-thread-pad: 104px;
  }
  /* A real iPhone's composer: the bar sits above the home indicator, never
   * on the device's rounded bottom edge (Jero 2026-09-24: the bar looked
   * cropped). The thread pad above (104px) clears the taller composer. */
  .stage__phone .wa-input { padding: 0 6px 30px; }
  .stage__phone .wa-input::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: 9px;
    width: 134px;
    height: 5px;
    margin-left: -67px;
    border-radius: 3px;
    background: #0a0a0a;
  }

  .stage__copy {
    position: absolute;
    left: clamp(24px, 3vw, 48px);
    /* Copy and side share one center line (Jero 2026-09-24: the CTA block
     * sat higher than the heading when both were bottom-anchored). The
     * centering uses the separate `translate` property because home.js and
     * product.js's parallax own the inline `transform` on .stage__copy. */
    top: calc(80% - 92px);
    translate: 0 -50%;
    /* S1.7: 33vw left only 5.1px to the phone at 1440x900 (min 8px); 32vw
     * gives ~19.5px there without touching the <=1280px override below. */
    width: min(32vw, 540px);
    transition: transform 1200ms var(--ease-stage);
  }
  .stage__copy .h-display {
    font-size: clamp(40px, 3.9vw, 70px);
    line-height: .98;
    letter-spacing: -.03em;
    margin: 0;
    white-space: normal;
  }
  .stage__side {
    position: absolute;
    right: clamp(24px, 3vw, 48px);
    top: calc(80% - 92px);
    translate: 0 -50%;
    width: min(28vw, 420px);
    display: flex;
    flex-direction: column;
    gap: 24px;
    transition: transform 1200ms var(--ease-stage);
  }
  /* 20% -> 9%, as a fixed 11svh translateY (CLS rule above). */
  .stage.is--open .stage__copy,
  .stage.is--open .stage__side {
    transform: translateY(11svh);
  }
  .stage__sub {
    font-size: clamp(19px, 1.55vw, 24px);
    line-height: 1.45;
    font-weight: 500;
    color: var(--ink-500);
    /* S8-should: ink-500 over the photo is low-contrast at D. Same
     * --paper-150 rgb as .hero__scrim/.stage-bridge, layered tighter for a
     * stronger halo (a ::before scrim instead inflated sub's scrollWidth
     * past its own box and failed S1.7 - box/text-shadow have no box). */
    text-shadow: 0 0 3px rgba(247, 252, 240, .95), 0 1px 6px rgba(247, 252, 240, .9), 0 1px 18px rgba(247, 252, 240, .8);
    text-wrap: pretty;
    margin: 0;
  }

  .stage__rail--right {
    position: absolute;
    left: calc(50% + 196.5px * var(--ps2) + clamp(32px, 3.5vw, 64px));
    top: calc(var(--p2-top) + 36px);
    width: min(320px, calc(50% - 196.5px * var(--ps2) - clamp(32px, 3.5vw, 64px) - clamp(24px, 3vw, 48px)));
    opacity: 0;
    pointer-events: none;
    transition: opacity 700ms ease-out 500ms;
  }
  .stage.is--open .stage__rail--right { opacity: 1; }
  /* Narrow desktops: the side block (sub + CTA) sits close under the rail,
   * so the rail's extra drop is smaller there (S1.7: >= 8px apart). */
  @media (max-width: 1100px) {
    .stage__rail--right { top: calc(var(--p2-top) + 16px); }
  }

  @media (max-width: 1280px) {
    .stage__copy { width: min(31vw, 420px); }
    /* S1-should-1: 28vw/340px left the CTA's small line wrapping to 3 at
     * 1024/961 ("En línea · te / responde en / segundos"); a bit more room
     * caps it at 2. */
    .stage__side { width: min(31vw, 360px); }
    .stage__sub { font-size: clamp(17px, 1.5vw, 20px); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .stage__phone,
  .stage.is--open .stage__phone,
  .stage__copy,
  .stage.is--open .stage__copy,
  .stage__side,
  .stage.is--open .stage__side,
  .stage__rail--right,
  .stage.is--open .stage__rail--right {
    transition: none;
  }
}

/* Should-fix (S1 gate iteration 5, F1 adjacent): under reduced motion,
 * home.js opens the stage on load (bindOpenTriggers -> openStage('load'))
 * instead of waiting for a scroll/wheel/tap, but that call can't land until
 * home.js runs - on a slow connection (same S1.21 defer timing as F1), first
 * paint shows state 1 (scale ps1) and the visitor sees it visibly snap to
 * state 2 (scale ps2, translateY(11svh)) whenever the script finally
 * executes. These overrides make the BASE (pre-.is--open) selectors already
 * equal state 2's geometry under reduce, so .is--open landing later changes
 * nothing - `.stage.is--open .stage__phone` etc. (home.css above) still win
 * on specificity once added, but resolve to the identical values. Only the
 * rotator word's text still updates at init (A12(c) keeps its typeface
 * constant across that change). */
@media (min-width: 961px) and (prefers-reduced-motion: reduce) {
  .stage__phone {
    transform: translateX(-50%) translateY(calc(var(--p2-top) - var(--p1-top))) scale(var(--ps2));
    --wa-thread-pad: 104px;
  }
  .stage__copy,
  .stage__side {
    transform: translateY(11svh);
  }
}

/* Right-rail card stack: one card visible at a time, absolutely stacked. */
.rail-card {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  opacity: 0;
  transform: translateY(14px);
  filter: blur(4px);
  transition: opacity 900ms var(--ease-out), transform 1100ms var(--ease-out), filter 900ms var(--ease-out);
  pointer-events: none;
}
.rail-card.is--on { opacity: 1; transform: translateY(0); filter: blur(0); }
.rail-card.is--off { opacity: 0; transform: translateY(-10px); filter: blur(4px); }
@media (prefers-reduced-motion: reduce) {
  .rail-card { transition: none; }
}

/* D14: the hero chat must never trap page scroll. Lenis hijacks wheel/touch
 * document-wide unless told to yield to a nested scrollable
 * (data-lenis-prevent, deliberately not set), so wheeling over the phone
 * already scrolls the page by default. overscroll-behavior-y:auto keeps
 * that true for a plain native scroll too (--reduced/--cdnfail). */
#inicio .wa-chat { overscroll-behavior-y: auto; }

/* ============ S1 mobile (<=960): a real phone, not a card (M1) ============
 * Headline -> sub -> phone in plain DOM order (flex column, no absolute
 * positioning), spaced with the R2 tokens: the section's top under the
 * nav, --sp-title to the sub, --sp-head to the phone, the section's bottom
 * under it (the inline CTA is gone at <=1023: the bottom bar is the CTA).
 * The phone is a real iPhone scaled down as a whole (Jero 2026-09-24:
 * "keep the proportions of an iPhone, just make everything a little
 * smaller"): the device lays out at 393x852 with WhatsApp's own sizes and
 * is drawn at --m-s (.8 on phones, .72 under 360, .86 on tablets), the
 * same transform the desktop stage uses. .stage__phone holds the scaled
 * footprint in the flow. Radii: device and screen share one radius (13% of
 * the width); inner surfaces keep WhatsApp's own. Everything sits inside
 * this query, so desktop (>=961) is untouched. */
@media (max-width: 960px) {
  .stage {
    --m-s: .8;
    --m-phone-w: calc(393px * var(--m-s));
    --m-phone-h: calc(852px * var(--m-s));
    --m-phone-r: 51px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    height: auto;
    padding: calc(var(--nav-bar-h, 84px) + var(--sp-section)) 20px var(--sp-section);
  }
  .stage__copy,
  .stage__side,
  .stage__phone,
  .stage__rail--right {
    position: static;
    width: auto;
    transform: none;
  }
  .stage__copy .h-display {
    font-size: clamp(36px, 13vw, 60px);
    line-height: 1.02;
    letter-spacing: -.03em;
    text-align: center;
    margin: 0;
  }
  .stage__side {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
    margin-top: var(--sp-title);
    margin-bottom: var(--sp-head);
    width: 100%;
  }
  .stage__sub {
    font-size: 18px;
    line-height: 1.45;
    font-weight: 500;
    color: var(--ink-500);
    margin: 0;
    max-width: 480px;
    text-wrap: pretty;
  }
  .stage__side .ca--lg { margin: 0 auto; }
  .stage__rail--right { display: none; }

  .stage__phone {
    position: relative;
    flex: none;
    width: var(--m-phone-w);
    height: var(--m-phone-h);
    margin: 0 auto;
    text-align: left;
  }
  .stage__phone .wa-device {
    width: 393px;
    height: 852px;
    transform: scale(var(--m-s));
    transform-origin: 0 0;
    border-radius: var(--m-phone-r);
  }
  .stage__phone .wa-screen { border-radius: var(--m-phone-r); }

  /* Thread: 1:1 WhatsApp scale - 17px bubbles (16px under 360), 13px
   * meta, 44px+ reply buttons. The chat is not a user scroller: the
   * thread is bottom-aligned inside it and older messages leave under the
   * header, so the newest is always in view (also with no JS) and a thumb
   * on the phone always scrolls the page instead of getting trapped. */
  .stage__phone .wa-chat {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    overflow: hidden;
    touch-action: pan-y;
  }
  /* Top-anchored while the conversation is short (home.js swaps at the
   * exact fill point); bottom-anchored once it fills the phone. */
  .stage__phone .wa-chat.is--full { justify-content: flex-end; }
  .stage__phone .wa-thread {
    flex: none;
    gap: 10px;
    padding: 106px 12px 90px;
    min-height: 0;             /* its natural height, so .wa-chat can anchor it */
    justify-content: flex-start;
  }
  .stage__phone .wa-date { font-size: 13px; margin-top: 4px; }
  .stage__phone .wa-msg.is--grouped { margin-top: -4px; } /* 6px between grouped bubbles (Jero 2026-09-25: too squished) */
  .stage__phone .wa-bubble {
    max-width: min(296px, 86%);
    font-size: 17px;
    line-height: 22px;
    padding: 6px 10px 7px;
    border-radius: 12px;
  }
  .stage__phone .wa-bubble.is--options { width: min(296px, 100%); max-width: min(296px, 100%); }
  .stage__phone .wa-time { font-size: 13px; letter-spacing: .02em; margin-top: 9px; }
  .stage__phone .wa-btns { margin: 8px -10px -7px; }
  .stage__phone .wa-btn,
  .stage__phone .wa-btn-spent {
    height: auto;
    min-height: 44px;
    padding: 10px 14px;
    font-size: 17px;
    line-height: 22px;
  }
  .stage__phone .wa-btn svg,
  .stage__phone .wa-btn-spent svg { flex: none; }
  .stage__phone .wa-doc { width: auto; max-width: 100%; }
  .stage__phone .wa-typing { height: 34px; }

  /* Composer above the home indicator, as on the desktop device. */
  .stage__phone .wa-input { padding: 0 6px 28px; }
  .stage__phone .wa-input::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: 8px;
    width: 134px;
    height: 5px;
    margin-left: -67px;
    border-radius: 3px;
    background: #0a0a0a;
  }

  /* The rail effects as an iOS banner (home.js showBanner): slides down
   * over the top of the screen, holds ~3.2s, slides away. Glass surface,
   * iOS notification radius, app-icon radius; text >= 13px. */
  .stage__phone .wa-banner {
    position: absolute;
    z-index: 6;
    left: 8px;
    right: 8px;
    top: 8px;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 14px 12px 11px;
    border-radius: 24px;
    background: rgba(250, 250, 247, .86);
    -webkit-backdrop-filter: blur(24px) saturate(1.6);
    backdrop-filter: blur(24px) saturate(1.6);
    box-shadow: 0 12px 32px -12px rgba(10, 24, 4, .4), 0 0 0 .5px rgba(0, 0, 0, .1);
    color: #0a0a0a;
    opacity: 0;
    transform: translateY(calc(-100% - 12px));
    transition: transform 560ms cubic-bezier(.32, .72, 0, 1), opacity 320ms ease-out;
    pointer-events: none;
  }
  .stage__phone .wa-banner.is--on { opacity: 1; transform: translateY(0); }
  .wa-banner__icon {
    flex: none;
    width: 38px;
    height: 38px;
    border-radius: 9px;
    background: #fff;
    box-shadow: inset 0 0 0 .5px rgba(10, 10, 10, .12);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .wa-banner__icon img { width: 24px; height: 24px; }
  .wa-banner__tx { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
  .wa-banner__head { display: flex; align-items: baseline; gap: 8px; }
  .wa-banner__title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 15px;
    line-height: 20px;
    font-weight: 600;
    letter-spacing: -.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .wa-banner__when { flex: none; font-size: 13px; color: rgba(0, 0, 0, .45); }
  .wa-banner__body {
    font-size: 15px;
    line-height: 20px;
    letter-spacing: -.01em;
    color: rgba(0, 0, 0, .78);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }
  .wa-banner__body b { font-weight: 600; color: #0a0a0a; }

  /* Background: the hero photo fades into the page color before the
   * phone, so nothing reads as a seam behind or beside it (the parallax
   * transform moves the fade with the layer; it never exposes an edge). */
  #inicio .hero__bg {
    -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100svh - 380px), transparent calc(100svh + 120px));
    mask-image: linear-gradient(180deg, #000 0, #000 calc(100svh - 380px), transparent calc(100svh + 120px));
    /* R2: the old layout's photo box (its cover framing). */
    bottom: auto;
    height: calc(100svh - 176px + min(640px, calc(100svh - 150px)));
  }
}

@media (max-width: 960px) and (prefers-reduced-motion: reduce) {
  .stage__phone .wa-banner { transition: none; }
}
/* Jero 2026-09-24: no banners pop over the phone when an option is picked. */
.stage__phone .wa-banner { display: none; }

/* Narrow phones: the same iPhone a little smaller so it keeps a gutter. */
@media (max-width: 359px) {
  .stage { --m-s: .72; }
}

/* Tablet: the same iPhone at .86, the copy centered above it. */
@media (min-width: 601px) and (max-width: 960px) {
  .stage {
    --m-s: .86;
    padding-left: 32px;
    padding-right: 32px;
  }
  .stage__copy .h-display { font-size: clamp(60px, 9.4vw, 84px); }
  .stage__side { gap: 24px; }
  .stage__sub { font-size: 22px; max-width: 560px; }
  #inicio .hero__bg {
    -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100svh - 300px), transparent calc(100svh + 320px));
    mask-image: linear-gradient(180deg, #000 0, #000 calc(100svh - 300px), transparent calc(100svh + 320px));
    height: calc(100svh + 40px + min(720px, calc(100svh - 104px)) - min(280px, calc(.38 * min(720px, calc(100svh - 104px)))));
  }
}

/* R1/A20: hero intro, CSS from first paint (never waits for deferred JS).
 * h1 = fx.js revealHead('hero') values. Individual scale/translate props:
 * `transform` belongs to the stage states and the CTA hover. `backwards`
 * fill and no .js gate: nothing stays pinned, JS never replays it (A12).
 * Head and sub start at opacity .1, never 0: they are the page's LCP element
 * (the h1 on phones and wide screens, the sub around 1024px). Chrome skips
 * an element painted at opacity 0 and records it only at the next main-thread
 * paint, which here comes after every deferred script has run (PageSpeed
 * mobile LCP 9.4s, 2026-10-05). At .1 under the blur it looks the same and
 * counts at first paint. */
@keyframes r1-hero-head { from { opacity: .1; scale: 1.12; filter: blur(18px); translate: 0 14px; } }
@keyframes r1-hero-sub { from { opacity: .1; filter: blur(8px); translate: 0 14px; } }
@keyframes r1-hero-cta { from { opacity: 0; translate: 0 14px; } }
@keyframes r1-hero-phone { from { opacity: 0; translate: 0 60px; } }
@media (prefers-reduced-motion: no-preference) {
  #inicio .stage__copy .h-display { animation: r1-hero-head 1.1s cubic-bezier(.215, .61, .355, 1) backwards; }
  #inicio .stage__sub { animation: r1-hero-sub .8s cubic-bezier(.215, .61, .355, 1) .25s backwards; }
  #inicio .stage__side .ca--lg { animation: r1-hero-cta .6s cubic-bezier(.215, .61, .355, 1) .35s backwards; }
  #inicio .stage__phone { animation: r1-hero-phone 1s cubic-bezier(.215, .61, .355, 1) .5s backwards; }
}
/* fx.js sizes the rotator box with getBoundingClientRect(), so measured
 * mid-intro it includes the scale above and could wrap line 1. The served
 * (base, in-flow) word is the widest: the box never needs more than it. */
#inicio .h-display .rot-rotator { max-width: max-content; }

/* R1/M-A+M-B (<=1023, phones and tablets): the floating pill is the page's
 * one WhatsApp CTA, a persistent bottom bar shown from first paint in pure
 * CSS (Cash App pattern): 16px gutters, 520px max centered, tile left,
 * label left-aligned, arrow right. home.js only hides it (.is--off: menu or
 * egg open, cierre CTA or footer in view) and swaps its text per section.
 * The inline big CTAs it replaces are hidden (hero here, the rest in
 * home-below.css). */
@media (max-width: 1023px) {
  .ca.ca--fl {
    left: 16px;
    right: 16px;
    /* Jero round 4: lower when Safari's toolbar hides. With the toolbar
     * shown the inset is 0 (16px above it); collapsed, it sits 10px inside
     * the 34px home-indicator inset (24px from the edge, ~11px clear of the
     * indicator) instead of 16px above it (50px). */
    bottom: max(16px, calc(env(safe-area-inset-bottom) - 10px));
    width: auto;
    max-width: 520px;
    margin: 0 auto;
    justify-content: flex-start;
    padding: 8px 20px 8px 8px;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: none;
  }
  .ca.ca--fl .ca__tx { flex: 1 1 auto; }
  .ca.ca--fl .ca__ar { display: block; }
  .ca.ca--fl.is--off { opacity: 0; visibility: hidden; pointer-events: none; transform: translateY(130%); }
  #inicio .stage__side .ca--lg { display: none; }
  /* Until the async home-below.css lands, S2's 393px lock-screen device
   * overflows a phone: mobile browsers then zoom the FIRST paint out (~0.94)
   * and snap back. Clip (not a scroll container: sticky still works). */
  main { overflow-x: clip; }
}
@media (max-width: 1023px) and (prefers-reduced-motion: reduce) {
  .ca.ca--fl .ca__ar { transition: none; }
}

/* R2 M-D (<=600): hero copy left-aligned; the intro scales from the left. */
@media (max-width: 600px) {
  .stage__copy,
  .stage__side { align-self: stretch; align-items: flex-start; }
  .stage__copy .h-display,
  .stage__sub { text-align: left; transform-origin: 0 50%; }
}

/* Section regions: each section adds its rules only inside its own region (parallel build, clean merges). */


/* F3: the hero CTA's small line has a short form (the round-2 one-line rule).
 * The hero CTA only shows at >= 1024 (the bottom bar takes over below), and
 * below 1440 the long phrase wrapped to two lines. */
#inicio .ca__sm--m { display: none; }
@media (max-width: 1439px) {
  #inicio .ca__sm--d { display: none; }
  #inicio .ca__sm--m { display: inline; white-space: nowrap; }
}

/* Jero 2026-09-24 (round 4): the floating "Chatea con Fini" is the full pill
 * with a round tile again (wa-kit's own .ca--fl shape; the squared try is
 * reverted). "Iniciar sesión" is the quiet outline pill at every width, so
 * the WhatsApp CTA is the one filled action (home only; other pages keep the
 * shared lime button). */
.twostep-nav__cta {
  border-radius: 999px;
  background: transparent;
  box-shadow: inset 0 0 0 1.5px rgba(20, 40, 8, .2);
  color: var(--green-ink);
}
.twostep-nav__cta:hover { background: rgba(20, 40, 8, .05); color: var(--green-ink); }

/* F5 tablet review: one frame on tablets. The nav bar, the content column
 * and the bottom WhatsApp bar share the 600px column (they were 544 / 600 /
 * 520: three left edges). Touch screens get a 44px "Iniciar sesión". */
@media (min-width: 601px) and (max-width: 1023px) {
  .twostep-nav__bar { max-width: 600px; }
  .ca.ca--fl { max-width: 600px; }
}
@media (pointer: coarse) {
  .twostep-nav__cta { min-height: 44px; }
}

/* Legacy anchors (the Ploy blog nav and older outside links still use
 * /#ecosistema, /#regimenes, /#seguridad): empty spans at the matching
 * sections, offset so the heading lands below the fixed nav. */
#ecosistema, #regimenes, #seguridad { display: block; scroll-margin-top: 112px; }

/* Round 4 (Jero 2026-09-24: the mobile hero reads "busier and noisier than
 * Cash App"): one calm object on a clean ground, borrowing Cash App's hero.
 * - the cloud photo stays behind the headline and is gone before the phone
 *   (it muddied the device's edges);
 * - the WhatsApp doodle wallpaper is softened (the densest texture on the
 *   first screen);
 * - once the thread fills the phone, older bubbles dissolve before the
 *   translucent header instead of reading as sliced under it;
 * - a deeper, softer shadow so the device reads as one crisp object;
 * - more air between the sub and the phone. Desktop is untouched. */
@media (max-width: 960px) {
  #inicio .hero__bg {
    -webkit-mask-image: linear-gradient(180deg, #000 0, #000 28svh, transparent 50svh);
    mask-image: linear-gradient(180deg, #000 0, #000 28svh, transparent 50svh);
  }
  .stage__side { margin-bottom: var(--sp-section); }
  .stage__phone .wa-wallpaper { opacity: .4; }
  .stage__phone .wa-chat.is--full {
    -webkit-mask-image: linear-gradient(180deg, transparent 98px, #000 150px);
    mask-image: linear-gradient(180deg, transparent 98px, #000 150px);
  }
  /* Jero 2026-09-25: the shadow ran past the hero's bottom edge (#inicio
   * clips) and ended on a line at the section change. Raised and tightened:
   * it reaches 56 device px (45 on screen), inside the 56/72px padding. */
  .stage__phone .wa-device {
    box-shadow: 0 28px 56px -28px rgba(20, 40, 8, .36), 0 12px 24px -14px rgba(20, 40, 8, .2);
  }
}

/* SEO pass 2026-09-25: the testimonial quotes are <blockquote> (quotes, not
 * section headings; they were h3s in the outline). Zeroed here, in the
 * render-blocking sheet, so there is no first-paint indent. */
.testimonial-lines__h { margin: 0; }
