/* ===========================================================================
   Pesito marketing: home-page-only rules
   ---------------------------------------------------------------------------
   Linked ONLY by the five home pages (/, /en/, /pt/, /fr/, /nah/), directly
   after styles/main.css. Every rule here was moved verbatim, in its original
   order, out of main.css (2026-09-25 performance split) so the other pages no
   longer download or parse it on their render-blocking path. Each rule names
   structure only the home pages render: the hero store row and badges, the
   hero balance and contact rows, the security badges, the showcase media
   panels and hero video, the fee calculator, region switch and fee ladder,
   the hero kicker, the chip labels and step numbers, and the carta media.

   Because this sheet loads after main.css, a rule here beats a rule in
   main.css of equal specificity. The split was checked so that no rule left
   in main.css ever used to win over a moved rule by coming later. Custom
   property blocks, @font-face and @keyframes stay in main.css. Put new rules
   here only if no other page can render what they select; anything another
   page can use belongs in main.css.
   =========================================================================== */

.hero-store-row { display: flex; gap: 12px; margin-top: 24px; flex-wrap: wrap; }
.store-badge strong {
  font-size: 15px;
  font-weight: 700;
  color: var(--tinta-900);
  letter-spacing: -0.005em;
}
/* Balance pulse — the `$12,450` text gently brightens every ~4.2s.
   Keyed off a child SVG `text` element rather than the whole device
   so the ambient reflection animations don't interfere. */
.hero-device [data-balance] {
  animation: saldo-pulse 4200ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
  transform-origin: 32px 210px;
}
/* Contact avatar heartbeat — four staggered pulses. Each avatar
   pulses scale 1 → 1.06 → 1 with a 900ms tick; offsets of 0s, 0.7s,
   1.4s, 2.1s so the row feels like a rolling heartbeat rather than
   a synchronized blink. */
.hero-device [data-contact] {
  animation: contact-heartbeat 3600ms cubic-bezier(0.4, 0, 0.2, 1) infinite;
  transform-origin: center;
  transform-box: fill-box;
}
.hero-device [data-contact="0"] { animation-delay: 0s; }
.hero-device [data-contact="1"] { animation-delay: 0.7s; }
.hero-device [data-contact="2"] { animation-delay: 1.4s; }
.hero-device [data-contact="3"] { animation-delay: 2.1s; }
@media (max-width: 768px) {
  .hero-store-row { margin-top: 20px; gap: 10px; }
  .store-badge { min-width: 0; flex: 1 1 0; padding: 10px 14px; }
}
.security-badge {
  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-md);
  padding: 20px 22px;
  display: flex;
  gap: 14px;
  align-items: flex-start;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.82),
    inset 0 -1px 0 rgba(11, 11, 11, 0.03),
    var(--glass-shadow-sm);
  isolation: isolate;
  transition: transform var(--dur-fast) var(--ease-emphasized),
              box-shadow var(--dur-fast) var(--ease-standard),
              background var(--dur-fast) var(--ease-standard);
}
.security-badge::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.3) 0%,
    rgba(255, 255, 255, 0.03) 40%,
    transparent 72%
  );
  z-index: 0;
}
.security-badge > * { position: relative; z-index: 1; }
.security-badge:hover {
  transform: translateY(-2px);
  background: var(--glass-surface-luminous-strong);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    inset 0 -1px 0 rgba(0, 0, 0, 0.04),
    var(--glass-shadow-md),
    var(--halo-verde-sm);
}
/* `.ed-grid--split` stretches both panels to the taller one. When that
   taller one is a media panel holding a portrait phone clip, the text
   panel beside it inherits ~600px of height with its copy pinned to the
   top, and the bottom half reads as a broken empty card. Centring the
   column spreads the slack above and below so the height reads as
   deliberate padding instead. */
.ed-panel--center-y {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.ed-panel--media > img, .ed-panel--media > video, .ed-panel--media picture img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* A screenshot of the app is content, not a backdrop — it has to be
   shown whole. The `cover` above fills the panel by trimming, which on
   a 470×1024 phone capture lops the avatar off the top and the
   swipe-to-pay control off the bottom. This modifier opts a panel out:
   the capture keeps its own ratio and centres, and the panel takes the
   leftover space instead of the image being cut to fit it.

   Both compound selectors are needed. `.ed-media--contain img` alone
   loses to `.ed-panel--media picture img` on specificity, which is
   exactly the bug this replaces. */
.ed-panel--media.ed-media--contain {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(16px, 2vw, 32px);
}
.ed-panel--media.ed-media--contain picture {
  display: flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
}
.ed-panel--media.ed-media--contain picture img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 680px;
  object-fit: contain;
  border-radius: 22px;
}
/* The corridor clip is a phone rendered on a transparent canvas, and its
   alpha is flattened at encode time (see `.showcase-stage`). Baked pixels
   can only match ONE colour, so this panel is pinned to the black they
   were flattened onto rather than following `--crema`, which is cream on
   the light theme and near-black on the dark one. Leaving the panel to
   flip is what put a slab of backdrop around the hardware and made the
   phone read as double-framed: the clip starts with the phone smaller
   than its own frame and pushes in, so that backdrop is visible, not a
   hairline. Black also gives the green confirm sheet the most contrast
   to read against. Keep this hex, `.showcase-stage`'s background, and the
   encode's `color=c=` argument in step. The caption pill supplies its own
   `--blanco` background, so it stays legible on the black in both
   themes. */
.ed-panel--media.ed-media--contain { background: #0B0B0B; }
/* This clip is a supporting illustration beside the pricing fine print,
   not a hero, and at the showcase width its portrait aspect drove the
   row past 600px and left the text panel half empty. Trimming it here
   shortens the row; the showcases keep the larger default. */
.ed-panel--media.ed-media--contain .showcase-stage { max-width: 260px; }
.ed-chip-num {
  font-family: var(--font-black);
  font-size: clamp(24px, 2.4vw, 34px);
  line-height: 1;
  white-space: nowrap;
}
.ed-chip-label {
  font-size: var(--fs-body-s);
  font-weight: 600;
  line-height: 1.3;
  max-width: 22ch;
}
@media (min-width: 960px) {
  .ed-grid--hero { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); align-items: stretch; }
}
/* Hero video: keep the loop composed, never a zoomed crop. The media
   panel matches the copy panel height on desktop but the video itself
   caps at a cinematic ratio. */
.ed-panel--media > video.hero-video {
  aspect-ratio: 16 / 9;
  height: auto;
  min-height: 100%;
}
@supports (animation-timeline: view()) {
  @media screen and (prefers-reduced-motion: no-preference) {
    /* Stat chips: the row container carries [data-reveal]; hand the
       motion to the chips themselves so the row can stagger. One list,
       four items — legitimate sibling stagger. */
    .ed-chips[data-reveal] { animation: none; }
    .ed-chips > .ed-chip {
      animation: ed-reveal-rise var(--dur-entrance) var(--ease-entrance) both;
      animation-timeline: view();
      animation-range: entry 5% entry 45%;
    }
    .ed-chips > .ed-chip:nth-child(2) { animation-range: entry 12% entry 52%; }
    .ed-chips > .ed-chip:nth-child(3) { animation-range: entry 19% entry 59%; }
    .ed-chips > .ed-chip:nth-child(4) { animation-range: entry 26% entry 66%; }
  }
}
/* ═══════════════════════════════════════════════════════════════════
   FEE CALCULATOR — the $0-to-send proof, on the homepage.

   The pricing claim is only convincing if you can push it around,
   so the calculator is the pricing section rather than an ornament
   beside it. Three parts:

     1. `.fee-calc-stage`  — the amount control (range + number,
                             two views of one value).
     2. `.fee-calc-out`    — fee / rate / recipient readout, wrapped
                             in a polite live region.
     3. `.fee-ladder`      — the cap made visible: five brackets
                             where the fee climbs, then flatlines.

   No-JS: the stage collapses to the number input (which still
   submits nothing and does nothing) and `.fee-ladder` is server-
   rendered static markup, so the cap story survives with scripting
   off, on a crawler, and in print.
   ═══════════════════════════════════════════════════════════════ */
/* Deliberately single-column: the panel it lives in is already a
   column of a page-level grid, so a media query here would size
   against the viewport rather than the panel and fold at the wrong
   moment. Stage over readout also matches how the number is read —
   set the amount, then look down for the consequence. */
.fee-calc {
  display: grid;
  gap: clamp(18px, 2vw, 26px);
  /* Clear air above the control: the compliance mouseprint sits
     directly overhead and without this the "how much?" prompt reads
     as the last line of the disclaimer. */
  margin-top: clamp(22px, 2.6vw, 34px);
}
.fee-calc-stage { min-width: 0; }
.fee-calc-legend {
  display: block;
  font-size: var(--fs-body);
  font-weight: 700;
  margin-bottom: 6px;
}
/* The amount, set in the poster grotesk so it reads as the headline
   of the panel rather than a form value. */
.fee-calc-amount {
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-family: var(--font-black);
  font-size: clamp(40px, 5.6vw, 76px);
  line-height: 1;
  letter-spacing: -0.02em;
}
.fee-calc-amount input {
  font: inherit;
  color: inherit;
  /* Room for a grouped "50,000" (the MXN maximum): five digits plus
     the separator measure ~5.5ch in the poster face. */
  width: calc(6ch + 4px);
  min-width: 0;
  padding: 0 2px;
  background: transparent;
  border: none;
  border-bottom: 3px solid currentColor;
  border-radius: 0;
  -moz-appearance: textfield;
  appearance: textfield;
}
.fee-calc-amount input::-webkit-outer-spin-button,
.fee-calc-amount input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.fee-calc-amount input:focus-visible { outline: 3px solid var(--ink-fixed); outline-offset: 4px; }
.fee-calc-currency { font-size: 0.42em; letter-spacing: 0.04em; }
/* Range track. Styled per-engine because there is still no single
   cross-browser slider anatomy; both ends land on the same 8px ink
   track with an amarillo fill and a 28px ink thumb (well past the
   24px AAA target-size floor, and the whole row is 44px tall). */
.fee-calc-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 44px;
  margin: 14px 0 6px;
  background: transparent;
  cursor: pointer;
}
.fee-calc-range:focus-visible { outline: 3px solid var(--ink-fixed); outline-offset: 4px; border-radius: var(--radius-pill); }
.fee-calc-range::-webkit-slider-runnable-track {
  height: 8px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--ink-fixed);
  background:
    linear-gradient(90deg, var(--amarillo-500) var(--fee-fill, 0%), transparent var(--fee-fill, 0%));
}
.fee-calc-range::-moz-range-track {
  height: 8px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--ink-fixed);
  background: transparent;
}
.fee-calc-range::-moz-range-progress {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--amarillo-500);
}
.fee-calc-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 28px;
  height: 28px;
  margin-top: -12px;
  border-radius: var(--radius-pill);
  background: var(--ink-fixed);
  border: 3px solid var(--paper-fixed);
  box-shadow: 0 0 0 2px var(--ink-fixed);
  transition: box-shadow var(--dur-instant) var(--ease-standard),
              transform var(--dur-instant) var(--ease-standard);
}
.fee-calc-range::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  background: var(--ink-fixed);
  border: 3px solid var(--paper-fixed);
  box-shadow: 0 0 0 2px var(--ink-fixed);
  transition: box-shadow var(--dur-instant) var(--ease-standard),
              transform var(--dur-instant) var(--ease-standard);
}
/* Under the finger the thumb swells and its ink ring thickens, like
   every other control on the panel acknowledging touch-down. Fixed ink
   and paper, so it reads the same on the verde-vivo fill in both
   themes. Reduced motion keeps the ring and drops the growth. */
.fee-calc-range:active::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px var(--ink-fixed);
  transform: scale(1.12);
}
.fee-calc-range:active::-moz-range-thumb {
  box-shadow: 0 0 0 4px var(--ink-fixed);
  transform: scale(1.12);
}
@media (prefers-reduced-motion: reduce) {
  .fee-calc-range:active::-webkit-slider-thumb { transform: none; }
  .fee-calc-range:active::-moz-range-thumb { transform: none; }
}
.fee-calc-scale {
  display: flex;
  justify-content: space-between;
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--note-ink);
}
/* Readout rows. `tnum` is already on for the body font, so the
   numbers don't jitter as the slider moves. */
.fee-calc-out {
  display: grid;
  gap: 2px;
  border: 2px solid var(--ink-fixed);
  border-radius: var(--panel-radius-sm);
  overflow: hidden;
  background: var(--ink-fixed);
}
.fee-calc-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  background: var(--verde-500);
  font-size: var(--fs-body-s);
  font-weight: 600;
}
.fee-calc-row dt { min-width: 0; }
.fee-calc-row dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  white-space: nowrap;
}
.fee-calc-row--fee { background: var(--amarillo-500); }
.fee-calc-row--total { background: var(--paper-fixed); padding: 18px; }
.fee-calc-row--total dt { font-size: var(--fs-body); font-weight: 700; }
.fee-calc-row--total dd {
  font-family: var(--font-black);
  font-size: clamp(24px, 2.6vw, 36px);
  line-height: 1;
}
.fee-calc-note { font-size: var(--fs-caption); line-height: 1.55; margin-top: 12px; }
/* ---------- Region switcher ----------
   Sending region decides the currency and the fee cap. English is
   locked to USD in scripts/region.js, so this control only ever
   appears on the Spanish pages, where it exists for the Los Angeles
   Spanish speaker who sends dollars rather than pesos. The guess is
   scripts/region.js's; this lets the visitor correct it, and the
   correction wins over the sensor for good. */
/* `display: inline-flex` below would otherwise defeat the `hidden`
   attribute the script sets on a locked (English) page. */
.region-switch[hidden] { display: none; }
.region-switch {
  display: inline-flex;
  align-items: stretch;
  gap: 0;
  margin-bottom: 18px;
  border: 2px solid var(--ink-fixed);
  border-radius: var(--radius-pill);
  overflow: hidden;
  background: var(--paper-fixed);
}
.region-switch button {
  /* 9px padding on 14px text gave a 39.7px-tall control, just under
     the 44pt floor. min-height lifts the target; the padding still
     sets the horizontal shape. */
  min-height: 44px;
  padding: 9px 16px;
  font-size: var(--fs-body-s);
  font-weight: 700;
  color: var(--ink-fixed);
  background: transparent;
  border: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-standard);
}
.region-switch button:hover { background: rgba(11, 11, 11, 0.08); }
.region-switch button[aria-pressed="true"] {
  background: var(--ink-fixed);
  color: var(--paper-fixed);
}
.region-switch button:focus-visible {
  outline: 3px solid var(--ink-fixed);
  outline-offset: -3px;
}
@media (prefers-reduced-motion: reduce) {
  .region-switch button { transition: none; }
}
/* ---------- Hero kicker: the dream, one line above the price ----------
   The worldview goes first and the price hook stays exact. Sized as a
   heading so it reads as a statement, coloured as the brand so it is not
   mistaken for the eyebrow status label above it. */
.hero-kicker {
  margin: 18px 0 0;
  font-family: var(--font-black);
  font-weight: 400;
  font-size: clamp(18px, 1.6vw, 22px);
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--accent-verde);
}
.hero-kicker a {
  color: inherit;
  /* Already carries its affordance as a 2px border that inverts on
     hover — a deliberate design, and not colour-alone, so it opts out
     of the prose-underline rule (see the opt-out list there). */
  text-decoration: none;
  border-bottom: 2px solid currentColor;
  padding-bottom: 1px;
}
.hero-kicker a:hover,
.hero-kicker a:focus-visible { border-bottom-color: transparent; }
/* ---------- Region-only copy: retired ----------
   Sentences used to be authored twice behind [data-region-only] and
   one copy hidden here. That shipped both fee schemes in the DOM at
   once, and every reader without CSS (AI comparison agents, reader
   mode, text crawlers) saw a peso answer ending on a dollar cap. Prose
   is now authored once with its figures marked up ([data-region-cap],
   [data-region-amount], [data-region-fee] …) and recomputed in
   scripts/main.js, so there is exactly one sentence to read. */
/* ---------- Fee ladder: the cap, drawn ---------- */
.fee-ladder {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  border: 2px solid var(--ink-fixed);
  border-radius: var(--panel-radius-sm);
  overflow: hidden;
  background: var(--paper-fixed);
  color: var(--ink-fixed);
  /* Fixed-fill surface: paper and amarillo row fills do NOT invert
     with the theme, which is why the table pins its own ink. The note
     token has to follow, or dark mode paints the themed gray ramp
     (#B3ADA3) onto a permanently-white row — 1.39:1 on the capped
     amarillo rows. */
  --note-ink: var(--ink-fixed);
  font-variant-numeric: tabular-nums;
}
.fee-ladder caption {
  caption-side: top;
  text-align: left;
  font-size: var(--fs-body-s);
  font-weight: 700;
  padding-bottom: 10px;
}
.fee-ladder th, .fee-ladder td {
  padding: 11px 16px;
  text-align: left;
  font-size: var(--fs-body-s);
  border-bottom: 2px solid var(--ink-fixed);
}
.fee-ladder tbody tr:last-child th,
.fee-ladder tbody tr:last-child td { border-bottom: none; }
.fee-ladder tbody th { font-weight: 700; }
.fee-ladder td:last-child { text-align: right; font-weight: 700; }
/* Capped rows get the amarillo wash + an explicit "capped" word in
   the cell, never color alone. */
.fee-ladder tr[data-capped] td { background: var(--amarillo-500); }
.fee-ladder .fee-ladder-tag {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--note-ink);
}
/* The row the slider is currently sitting in. */
.fee-ladder tr[aria-current="true"] th,
.fee-ladder tr[aria-current="true"] td {
  box-shadow: inset 4px 0 0 var(--rosa-500);
}
/* ═══════════════════════════════════════════════════════════════════
   ASSET + CHROME POLISH — 2026-08 review pass.

   Four things in this file predate the Lotería editorial redesign and
   still wore the old Liquid Glass skin: the store badges, the security
   badges, the stat chips and the app-showcase device art. On cream
   they merely looked like a different era; on ink stock they broke
   outright, because every one of them hard-coded a white translucent
   surface and then set ink-colored type on top of it.

   The fix in all four cases is the same: stop approximating a surface
   with `rgba(255,255,255,…)` + `backdrop-filter`, and use the flat
   fill + ink outline the rest of the system uses.
   ═══════════════════════════════════════════════════════════════ */
/* ---------- Store badges ----------
   Was: 55%-white glass with `--tinta-900` type. In dark mode the
   translucent white sat over near-black as a muddy grey while the
   type flipped to bone — grey-on-grey, ~1.6:1, the worst contrast on
   the page. Now a flat editorial pill that carries the real Apple and
   Google Play glyphs instead of relying on the store's name in text. */
.store-badge {
  flex-direction: row;
  align-items: center;
  gap: 10px;
  padding: 9px 18px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--frame-ink);
  background: var(--blanco);
  color: var(--tinta-900);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
}
/* The legacy iridescent sheen rode on these pseudos. It was tuned for
   a glass surface and smears on a flat one. */
.store-badge::before,
.store-badge::after { content: none; }
.store-badge:hover {
  background: var(--blanco);
  box-shadow: var(--card-lift);
}
.store-badge-glyph {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: var(--tinta-900);
}
.store-badge-copy {
  display: flex;
  flex-direction: column;
  line-height: 1.15;
  min-width: 0;
}
.store-badge span {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  /* The badge is a translucent glass surface (55% white + backdrop
     blur) with a decorative white inner-shadow wash on ::after, so
     whatever sits behind it bleeds through and the effective
     luminance under this label moves. A flat mid-gray is the classic
     "gray label on a material" failure — measured 3.67:1 here during
     the entry shimmer. `--tinta-700` holds 14.3:1 against the badge's
     own white and keeps a wide margin through the material. */
  color: var(--tinta-700);
}
.store-badge strong {
  font-size: 15px;
  font-weight: 700;
  color: var(--tinta-900);
  letter-spacing: -0.005em;
  white-space: nowrap;
}
/* Inside the loud verde-vivo panel the pill keeps a bright fill in
   both themes, so its contents pin to fixed ink. */
.ed-panel--verde-vivo .store-badge {
  background: var(--paper-fixed);
  border-color: var(--ink-fixed);
}
.ed-panel--verde-vivo .store-badge strong,
.ed-panel--verde-vivo .store-badge .store-badge-glyph { color: var(--ink-fixed); }
/* On the verde-vivo panel the badge keeps its fixed white fill, so its
   label must keep fixed dark ink rather than the themed ramp (which
   would go bone in dark mode, on a permanently white badge). */
.ed-panel--verde-vivo .store-badge span { color: #2A2A2E; }
/* ---------- Security badges ----------
   Was: luminous glass card + a glossy green gradient chip behind each
   icon, with a green drop-glow. That chip is the single most
   "generated" -looking element on the site and it reads as a different
   product from the trust grid three sections below, which uses a plain
   stroked glyph. Same card, same glyph treatment, both places now. */
.security-badge {
  background: var(--blanco);
  border: 2px solid var(--frame-ink);
  border-radius: var(--panel-radius-sm);
  padding: 18px 20px;
  gap: 12px;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
}
.security-badge::before { display: none; }
.security-badge:hover {
  background: var(--blanco);
  transform: none;
  box-shadow: var(--card-lift);
}
.ed-chip-num {
  font-size: clamp(28px, 2.8vw, 40px);
  /* A numeric value must never break; a worded one must be allowed to. */
  white-space: normal;
  text-wrap: balance;
}
.ed-chip-label {
  max-width: none;
  font-size: var(--fs-body-s);
  line-height: 1.35;
}
/* ---------- Product-preview clips (homepage showcases) ----------
   These clips arrive as ProRes 4444 with a real alpha channel, but
   Safari ignores alpha in WebM and only macOS can encode HEVC-with
   -alpha, so shipping transparency would hand every Safari visitor a
   black box. Instead the alpha is flattened onto `--tinta-900`'s
   light value at encode time and the corners are clipped here: on the
   light theme the stage is the same colour as the panel and vanishes,
   and on the dark theme it reads as the phone's own body, the way
   `.device-frame` does for the static mockups above.

   The phone is rendered INTO the clip, so no CSS bezel wraps it. The
   radius is expressed per-axis in percentages so one value tracks the
   phone's own corner (~16% of its width) at any rendered size. */
.showcase-stage {
  display: block;
  position: relative;
  box-sizing: border-box;
  width: 100%;
  max-width: 300px;
  aspect-ratio: 608 / 1266;
  margin: 0 auto;
  /* The stage is a phone-shaped window and nothing else: no body, no
     outline, no backdrop. The clip is flattened onto near-black, so the
     window has to be filled by the phone at every frame; the clip's own
     camera push (the phone grows from 89% of the frame to 99%, drifting
     from slightly low to centred) is cancelled by the counter-zoom on
     `.showcase-video`, driven per frame from scripts/main.js. The radius
     is a touch larger than the render's corner so the mask trims the
     phone's corner rather than exposing the backdrop beside it. */
  border-radius: 17% / 8.2%;
  overflow: hidden;
  background: transparent;
}
/* The still beneath the clip (added by scripts/main.js): the poster,
   then the clip's last frame. Same box and framing as the video, so the
   phone is painted by a plain image whenever the player paints nothing. */
.showcase-still {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform-origin: 50% 50%;
  transform: translateY(var(--push-y, -6.2%)) scale(var(--push-s, 1.135));
}
.showcase-video {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform-origin: 50% 50%;
  /* Rest values frame the first frame (and the poster) exactly; main.js
     eases both towards 1.02 / 0.6% as the clip plays. */
  transform: translateY(var(--push-y, -6.2%)) scale(var(--push-s, 1.135));
  will-change: transform;
}
/* Without scripting the browser forces native controls onto every
   <video>, and the showcase clips no longer carry a parse-time poster
   (the still in the markup is the poster, lazily loaded). Keep the
   still and hide the empty player, so a no-JS visit sees the phone
   instead of a grey control bar. The clips are illustrative; the
   section's copy says what they show. */
@media (scripting: none) {
  .showcase-video { visibility: hidden; }
}
/* Was #55555C, a frozen light --tinta-500. The store badge inside
   verde-vivo is pinned to --paper-fixed (see ~5599), so its secondary
   line is fixed grey on fixed white: 6.45:1 at 30% paper, identically
   in both themes. */
.ed-panel--verde-vivo .store-badge span {
  color: color-mix(in srgb, var(--ink-fixed) 70%, var(--paper-fixed));
}
/* ---------- 2.7 Stat chips: the last word on a duplicated set ------ */
/* HEADS UP, this is not a no-op and the thing you are about to edit
   probably is. .ed-chips, .ed-chip, .ed-chip-num and .ed-chip-label
   are each declared TWICE upstream: ~4069 and ~5646, ~4074 and ~5651,
   ~4091 and ~5657, ~4097 and ~5663. The second copy restructured the
   chip from a baseline row into a stacked grid cell and won silently
   on source order, so editing the FIRST copy changes nothing and
   looks like a broken build. This block is the third and final word.
   Change chip behaviour here, and leave both upstream pairs alone:
   deleting either one is a bigger diff than it looks.
   Only the label voice needs stating. Fill, frame, radius and
   geometry all retone through tokens and are deliberately not
   repeated. */
.ed-chip-label {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: 0.045em;
}
/* The 01 / 02 / 03 numerals on the homepage step cells. This needs
   (0,2,0): both homepages declare .ed-step-num in a page-level
   <style> that the parser reaches AFTER main.css, so a bare
   .ed-step-num here would tie and lose on source order. Every
   instance sits inside an .ed-panel, and the ES homepage wraps it in
   .ed-cell as well, so both hooks are covered. */
.ed-panel .ed-step-num,
.ed-cell .ed-step-num {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: 0.06em;
}
/* Stat chip labels. These carry sentence-length copy ("de lo que
   envias (precio planeado)"), so they take the SMALLCAPS track, not
   the caps track: 0.09em on a 60-character small-cap line stops
   being a label and starts being a puzzle. */
.ed-chip-label {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: var(--voz-carta-track-sc);
  font-synthesis-weight: none;
  font-feature-settings: "kern" 1;
}
/* Step numerals. Needs 0,2,0: both homepages declare .ed-step-num in
   a page-level <style> that the parser reaches AFTER main.css, so a
   bare hook would tie and lose on source order. The bare rule is kept
   anyway as the base for any page that adds one without a wrapper. */
.ed-step-num,
.ed-panel .ed-step-num,
.ed-cell .ed-step-num,
.ed-grid .ed-step-num {
  font-family: var(--font-carta);
  font-weight: 700;
  letter-spacing: var(--voz-carta-track);
  font-synthesis-weight: none;
  font-feature-settings: "kern" 1;
}
/* Keyed off the PART, not the variant class, and a DESCENDANT rather
   than a child: a page that drops a `.carta-media` into a plain carta
   and forgets `--media`, or nests one inside a `.carta-aside`, still
   gets its image clipped into the 20px corner instead of a square photo
   poking through a round frame. */
.carta:has(.carta-media) { overflow: hidden; }
.carta .carta-media {
  display: block;
  width: calc(100% + 2 * var(--carta-pad-x));
  margin-inline: calc(-1 * var(--carta-pad-x));
  /* The ground behind a transparent PNG, a slow image and a poster-less
     video. A hole in the card is worse than a warm rectangle. */
  background: var(--hueso-100);
  overflow: hidden;
}
/* No head above it: the media takes the top edge too. */
.carta .carta-media:first-child { margin-top: calc(-1 * var(--carta-pad-top)); }
/* No foot below it: the media takes the bottom edge. */
.carta .carta-media:last-child { margin-bottom: calc(-1 * var(--carta-pad-bottom)); }
/* Copy after media needs the gap the vertical padding used to give it.
   The foot and the flecha are excluded because both are pinned to the
   bottom edge by an auto margin, and a stated margin would unpin them. */
.carta .carta-media + :not(.carta-foot):not(.carta-flecha) { margin-top: 16px; }
.carta .carta-media > img,
.carta .carta-media > video,
.carta .carta-media > iframe,
.carta .carta-media > picture > img,
.carta .carta-media > svg {
  display: block;
  width: 100%;
  height: auto;
  /* Overridable per instance. A ratio rather than a height is what
     stops the row from shifting while the image decodes. */
  aspect-ratio: var(--carta-media-ratio, 16 / 10);
  object-fit: cover;
  border: 0;
  /* Beats the global `img, svg { max-width: 100% }`, which would
     otherwise clamp the bled media back to the padded content width. */
  max-width: none;
}
/* Inside a stretched aside the media has a definite height to fill, so
   the ratio steps aside and `cover` does the cropping. */
.carta.carta--ancha .carta-aside > .carta-media,
.carta.carta--ancha .carta-aside > .carta-media > img,
.carta.carta--ancha .carta-aside > .carta-media > video {
  width: 100%;
  height: 100%;
  margin: 0;
  aspect-ratio: auto;
  min-height: 180px;
  object-fit: cover;
}
@media (max-width: 720px) {

  .carta.carta--ancha .carta-aside > .carta-media,
  .carta.carta--ancha .carta-aside > .carta-media > img,
  .carta.carta--ancha .carta-aside > .carta-media > video {
    height: auto;
    aspect-ratio: var(--carta-media-ratio, 16 / 10);
  }
}
