/* ============================================================================
   anna-i18n — Cyrillic type cover + language switcher (LIVE — promoted from
   04-WORKSHOP/i18n-lab/src/ in s93, 2026-08-14)
   ========================================================================== */

/* --- Cyrillic cover for the two Adobe display faces -------------------------
   Verified 2026-08-01 against the actual glyph tables in kit abu7raw:
   altesse-std-24pt and ratiomodern carry Latin and full Spanish (ñ á ¿ ¡) and
   NOT ONE Cyrillic glyph. Left alone, every section title and hero title on the
   Ukrainian and Russian pages would silently drop to Georgia — a bookish serif
   where the design calls for a copperplate script.

   Rather than restate ~20 selectors, we add a second @font-face under the SAME
   family name, scoped by unicode-range. The browser then picks the substitute
   for Cyrillic codepoints only, so a Latin word inside a Russian sentence still
   renders in the real Adobe face and every existing rule keeps working
   untouched. Nothing here is language-gated — it is glyph-gated, which is
   stricter and cannot be defeated by the switcher.

   Pairing: altesse (formal copperplate) -> Great Vibes, ratiomodern (modern
   serif) -> Prata. Both OFL, both self-hosted, ~45 KB total, and fetched only
   when a Cyrillic character is actually painted.
   size-adjust brings the substitute's x-height onto the original's, so a
   translated title occupies the same optical size as its English twin. */

@font-face {
  font-family: 'altesse-std-24pt';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  size-adjust: 112%;
  src: url('fonts/great-vibes-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'altesse-std-24pt';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  size-adjust: 112%;
  src: url('fonts/great-vibes-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

@font-face {
  font-family: 'ratiomodern';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/prata-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'ratiomodern';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/prata-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* --- vertical swash cushion for the close's Cyrillic cover (LAWS ⑧, s99) ----
   The shipped cushion on `.cta-final__title .proof-word` is horizontal only
   (0 .3em) — sized for altesse, whose ink never leaves the box vertically.
   Great Vibes does: at 96px with size-adjust 112%, «З» carries 84.3px of ink
   above the baseline against 81px of box above it, and 43px below against 15px
   under it — the top flourish loses 3.3px and the descender 28px to the same
   two renderers LAWS ⑧ names (background-clip:text ≥1200, the blur-filter
   raster below). Same disease, third axis — but the cushion cures ONLY the
   background-clip:text renderer here: iOS bounds a layer raster at the
   CONTENT box and ignores padding (s100); the layer itself is dropped by
   the settled transform:none/filter:none rule in styles.css. Padding grows
   the paint box over the ink, the equal negative margin hands the
   space back (the spans are inline-block, so the margin box is what the line
   box measures — layout is unchanged at rest). Scoped to uk/ru so the CLOSED
   English homepage and Spanish stay byte-identical. */
html[data-lang='uk'] .cta-final__title .proof-word,
html[data-lang='ru'] .cta-final__title .proof-word {
  padding: 0.1em 0.3em 0.35em;
  margin: -0.1em -0.3em -0.35em;
}

/* --- section-title descender clip (s108 morning, Oleg's screenshots:
   destination «Що кажуть наречені» + «Ціни» tails amputated at rest) ----
   Same disease as the close, one renderer only: the section titles carry NO
   background-clip gradient, so the sole clipper is the settled reveal's LIVE
   `filter: blur(0)` — iOS rasterizes the revealed title at its CONTENT box
   and the Great Vibes cover's descenders («Щ», «Ц») paint outside it (s100's
   mechanism, LAWS ②). The cushion recipe is USELESS here on purpose: iOS
   bounds the raster at the content box and ignores padding (s99's own note),
   so the cure is the settled-state hand-back, s100's exact pattern. uk/ru
   scoped — the shared `.reveal.revealed` blur(0) stays untouched site-wide
   (a styles.css box comment ~10527 depends on that stacking context).
   Entrance unchanged: the blur still interpolates to `none`. During the
   1800ms run the descender is still bounded — transient, same as the close
   ships today; at rest the ink is whole. */
html[data-lang='uk'] .section-title.revealed,
html[data-lang='ru'] .section-title.revealed {
  filter: none;
  transform: none;
}

/* The taller padding box would silently re-map the ≥1200 candlelight gradient:
   its 120%/160% geometry resolves against the background positioning area, so
   growing the box drops the glow ~42px below where the English close holds it.
   Re-state the same gradient with the vertical terms in em, measured from the
   original geometry (center 1.2em below content top + the 0.1em pad = 1.3em;
   vertical radius 160% × 1em = 1.6em) — identical light on the glyphs, paint
   that now reaches the flourishes. Guards mirror the styles.css rule. */
@media (min-width: 1200px) {
  @supports ((-webkit-background-clip: text) or (background-clip: text)) {
    html[data-lang='uk'] .cta-final__title .proof-word,
    html[data-lang='ru'] .cta-final__title .proof-word {
      background-image: radial-gradient(90% 1.6em at 50% 1.3em,
        #F7EFDF 0%,
        #E3CD9C 55%,
        var(--gold) 100%);
    }
  }
}

/* Cyrillic has no true small-caps and no ligature culture; letter-spaced
   uppercase Cyrillic reads as shouting rather than as an editorial eyebrow.
   Ease the tracking back on the label classes for those two languages only. */
html[data-lang='uk'] .label,
html[data-lang='ru'] .label,
html[data-lang='uk'] .proof__label,
html[data-lang='ru'] .proof__label {
  letter-spacing: 0.08em;
}

/* Long-word insurance. Russian and Ukrainian produce compounds far longer than
   any English equivalent ("індивідуальний", "профессиональный"), and a rigid
   grid cell will let one punch through its border rather than wrap. */
html[data-lang='uk'] .inc-card__title,
html[data-lang='ru'] .inc-card__title,
html[data-lang='uk'] .pricing-tiers__name,
html[data-lang='ru'] .pricing-tiers__name,
html[data-lang='uk'] .nav__mobile-link,
html[data-lang='ru'] .nav__mobile-link {
  hyphens: auto;
  overflow-wrap: break-word;
}

/* --- desktop hero: the one place the translation genuinely breaks layout -----
   .hero-v4__headline is white-space:nowrap by design — the English line is
   authored to fit on ONE line at ≥1024, and the phone break is handled by
   <br class="hero-br">, which is display:none up here. Ukrainian and Russian
   run roughly 25% longer, so the headline could not wrap, could not shrink
   (its flex parent inherits min-width:auto), and pushed the proof badge and
   the booking CTA 60-90px past the right edge of the viewport, where the
   hero's own clip swallowed them. Measured 2026-08-02: side column right edge
   1327 vs a 1262 viewport in Ukrainian, 1323 in Russian, 1238 (inside) in
   English and Spanish.

   Two lines is the honest answer — shrinking the type instead would make the
   Slavic pages read visibly smaller than the English one. text-wrap:balance
   keeps the two lines even. English and Spanish never see any of this. */
html[data-lang='uk'] .hero-v3d__main,
html[data-lang='ru'] .hero-v3d__main { min-width: 0; }

html[data-lang='uk'] .hero-v3d__side,
html[data-lang='ru'] .hero-v3d__side { flex: 0 0 auto; }

html[data-lang='uk'] .hero-v4__headline,
html[data-lang='ru'] .hero-v4__headline {
  white-space: normal;
  text-wrap: balance;
}

/* --- LINE BALANCING FOR THE TRANSLATED LANGUAGES --------------------------
   The site already solved this for English: LAWS ㉘ / the site-wide
   `text-wrap: balance` list in styles.css, born because "the text is broken
   into lines wrong" is the note Oleg has given more than any other. That list
   is deliberately scoped — short display copy is in, body prose is out — and
   every `<p>` class below was left OUT of it because in English it never
   needed it.

   Translation reopens the wound. The same card note that sat on two even lines
   in English lands on three in Russian with one word alone on the last, which
   is the exact defect the law exists to prevent. Measured across 9 pages × 3
   languages × 3 widths: 156 single-word orphans, none of them present in the
   English baseline.

   So: extend the same mechanism to the same kind of copy, scoped to the three
   translated languages so the English page is byte-for-byte what it was. This
   is the law being applied, not bypassed — a browser-level guarantee that also
   covers strings nobody has translated yet. */
html[data-lang='es'] .swipe-card__text,   html[data-lang='uk'] .swipe-card__text,   html[data-lang='ru'] .swipe-card__text,
html[data-lang='es'] .inc-card__body,     html[data-lang='uk'] .inc-card__body,     html[data-lang='ru'] .inc-card__body,
html[data-lang='es'] .pricing-tiers__note,html[data-lang='uk'] .pricing-tiers__note,html[data-lang='ru'] .pricing-tiers__note,
html[data-lang='es'] .session-flow__desc, html[data-lang='uk'] .session-flow__desc, html[data-lang='ru'] .session-flow__desc,
html[data-lang='es'] .included-list__item,html[data-lang='uk'] .included-list__item,html[data-lang='ru'] .included-list__item,
html[data-lang='es'] .hero__title-line,   html[data-lang='uk'] .hero__title-line,   html[data-lang='ru'] .hero__title-line,
html[data-lang='es'] .spectrum__caption,  html[data-lang='uk'] .spectrum__caption,  html[data-lang='ru'] .spectrum__caption,
html[data-lang='es'] .fmt3__note,         html[data-lang='uk'] .fmt3__note,         html[data-lang='ru'] .fmt3__note,
html[data-lang='es'] .timeline__desc,     html[data-lang='uk'] .timeline__desc,     html[data-lang='ru'] .timeline__desc,
html[data-lang='es'] .dif__s,             html[data-lang='uk'] .dif__s,             html[data-lang='ru'] .dif__s,
html[data-lang='es'] .hero-v4__subline,   html[data-lang='uk'] .hero-v4__subline,   html[data-lang='ru'] .hero-v4__subline,
html[data-lang='es'] .hero-v4__headline,  html[data-lang='uk'] .hero-v4__headline,  html[data-lang='ru'] .hero-v4__headline,
html[data-lang='es'] .proof__quote,       html[data-lang='uk'] .proof__quote,       html[data-lang='ru'] .proof__quote,
html[data-lang='es'] .looks-grid__desc,   html[data-lang='uk'] .looks-grid__desc,   html[data-lang='ru'] .looks-grid__desc,
html[data-lang='es'] .plusone__sub,       html[data-lang='uk'] .plusone__sub,       html[data-lang='ru'] .plusone__sub,
html[data-lang='es'] .whoc__t,            html[data-lang='uk'] .whoc__t,            html[data-lang='ru'] .whoc__t,
html[data-lang='es'] .timing-grid__note,  html[data-lang='uk'] .timing-grid__note,  html[data-lang='ru'] .timing-grid__note,
html[data-lang='es'] .timing-grid__label, html[data-lang='uk'] .timing-grid__label, html[data-lang='ru'] .timing-grid__label,
html[data-lang='es'] .tlD-pull,           html[data-lang='uk'] .tlD-pull,           html[data-lang='ru'] .tlD-pull,
html[data-lang='es'] .footer__text,       html[data-lang='uk'] .footer__text,       html[data-lang='ru'] .footer__text,
html[data-lang='es'] .nf__text,           html[data-lang='uk'] .nf__text,           html[data-lang='ru'] .nf__text,
html[data-lang='es'] .testimonial-card__quote,
html[data-lang='uk'] .testimonial-card__quote,
html[data-lang='ru'] .testimonial-card__quote {
  text-wrap: balance;
}

/* Body prose is a different problem and gets a different tool. LAWS ㉘ rejected
   `pretty` as a REPLACEMENT for balance — it will not rescue a four-word stub.
   But a single word alone on the last line is precisely what `pretty` does fix,
   and balance is not available here anyway: the browser stops balancing past
   ~6 lines, and Anna's story paragraphs run longer than that. The two are not
   in competition; they cover different copy. English is untouched either way. */
/* ⚠️ `p.reveal`, NOT `.reveal`. `.reveal` is the scroll-animation class and it
   sits on almost every element on this site, including the <h2>s — targeting it
   bare overrode the site's own `text-wrap: balance` on every section title with
   `pretty`, and left them wrapping [681,164] where balance had them even. Found
   by measuring, not by reading. Prose only. */
html[data-lang='es'] p.reveal, html[data-lang='uk'] p.reveal, html[data-lang='ru'] p.reveal,
html[data-lang='es'] .whoc__ans, html[data-lang='uk'] .whoc__ans, html[data-lang='ru'] .whoc__ans {
  text-wrap: pretty;
}

/* The authored phone break fights the balancer, and this is the single change
   that fixed the headline Oleg called out. `<br class="hero-br">` is a locked
   two-line reading authored for the ENGLISH sentence; drop a longer sentence
   into it and the break lands mid-thought, forcing a fourth line and leaving a
   stub. Measured at 402px, before → after:
     RU headline  [241,141,205,152] 4 lines  →  [241,321,187] 3 lines
     RU subline   [311,77,314]  (one word alone)  →  [213,243,246]
     UK headline  [227,155,156,205] 4 lines  →  [227,269,258] 3 lines
     ES subline   [275,329,52]  (52px orphan)     →  [218,211,226]
   Every translated language drops the authored break and lets balance pick the
   split. English keeps its break untouched — it fits, and it is Oleg's. */
html[data-lang='es'] .hero-br,
html[data-lang='uk'] .hero-br,
html[data-lang='ru'] .hero-br { display: none; }

/* --- boot veil --------------------------------------------------------------
   Only ever applied when a non-English language is restored from storage, so
   English visitors never pay a frame for it. Removed by setLang(). */
html.i18n-booting body { opacity: 0; }
html.i18n-booting.i18n-ready body { opacity: 1; transition: opacity 160ms ease; }

/* --- switcher --------------------------------------------------------------- */
.i18n-switch {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-family: var(--font-sans);
}

.i18n-switch__btn {
  appearance: none;
  background: none;
  border: 0;
  padding: 6px 7px;
  cursor: pointer;
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  line-height: 1;
  color: var(--text-light);
  border-radius: 4px;
  /* s103 §11 host requirement — the .haptic-tick overlay is inset:0 */
  position: relative;
  /* s103 — transform rides the BASE list so the press can interpolate;
     a list declared only in the pressed rule would snap (LAWS ③) */
  transition: color 200ms var(--ease-soft), background-color 200ms var(--ease-soft), transform 340ms var(--ease-soft);
}
.i18n-switch__btn:hover { color: var(--gold); }
/* s103 — the §11 press pair: fast-in 90ms, release on the base 340ms (the
   asymmetry IS the feel); :active stays for Space/Enter; the pressed rule
   overrides ONLY transition-duration, never the property list (rule 41/42). */
.i18n-switch__btn:active,
.i18n-switch__btn.is-pressed {
  transition-duration: 90ms;
  transform: scale(0.94);
}
.i18n-switch__btn.is-active {
  color: var(--navy);
  background: rgba(196, 162, 101, 0.16);
}

/* ⛔ SUPERSEDED s112 (2026-08-16) — every `--nav` rule from here to the
   `.i18n-switch--mobile` block below has NO CONSUMER: buildSwitchers() now
   builds `.i18n-lang` for the desktop header and `i18n-switch--nav` is never
   emitted. Kept through the port so the revert is one JS line, not an
   archaeology exercise; the code-cleanup pass rules on removing them.
   The `.i18n-switch` / `.i18n-switch__btn` BASE rules above are still live —
   the mobile sheet and the footer use them. */

/* Over the dark hero the nav sits on video, so the resting colour has to come
   from the nav's own light state, not from --text-light. */
.nav:not(.scrolled) .i18n-switch--nav .i18n-switch__btn { color: rgba(255, 255, 255, 0.66); }
.nav:not(.scrolled) .i18n-switch--nav .i18n-switch__btn:hover { color: #fff; }
.nav:not(.scrolled) .i18n-switch--nav .i18n-switch__btn.is-active {
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
}

/* Dark-ground scrolled bar (.nav--over-dark, verdict in main.js) — the chips
   mirror the hero treatment above, one class heavier so it wins over the
   scrolled default without touching source order. */
.nav.scrolled.nav--over-dark .i18n-switch--nav .i18n-switch__btn { color: rgba(255, 255, 255, 0.66); }
.nav.scrolled.nav--over-dark .i18n-switch--nav .i18n-switch__btn:hover { color: #fff; }
.nav.scrolled.nav--over-dark .i18n-switch--nav .i18n-switch__btn.is-active {
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
}

.i18n-switch--nav { margin-right: 12px; flex: 0 0 auto; }
@media (max-width: 1023px) { .i18n-switch--nav { display: none; } }

.i18n-switch--mobile {
  gap: 4px;
  margin: 0 0 22px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--border-color);
  width: 100%;
}
.i18n-switch--mobile .i18n-switch__btn { font-size: 13px; padding: 8px 12px; }

.i18n-switch--footer {
  display: flex;
  justify-content: center;
  width: 100%;
  margin-top: 28px;
  opacity: 0.85;
}

/* The lab's "DRAFT — translation review" ribbon (`.i18n-draft-flag`) was
   deliberately NOT promoted: it exists to mark the review copy, and its markup
   in i18n.js/buildSwitchers() was dropped in the same edit. Both halves live on
   in `04-WORKSHOP/i18n-lab/src/` — restore the pair, never one alone. (s93) */

/* ============================================================================
   DESKTOP LANGUAGE DROPDOWN — `.i18n-lang` (s112, 2026-08-16)
   Replaces the four `.i18n-switch--nav` chips at ≥1024 ONLY. The mobile sheet
   and the footer keep the chip row untouched: this control exists to trade a
   click for the languages' OWN NAMES, and both of those places already have
   room to show four items at once.

   Lab + measurements: `04-WORKSHOP/block-labs/lang-switcher-lab.html` ·
   brief `04-WORKSHOP/briefs/lang-switcher-brief.md`.

   ⚠️ THE PANEL IS PORTALLED TO <body> (D071 / LAWS ②). `.nav` is fixed AND
   carries its own backdrop-filter, which makes it a BACKDROP ROOT: nested
   inside it this panel's blur paints literally zero. i18n.js appends the menu
   to <body> and feeds the trigger's coordinates in through --lang-x/--lang-y.
   If that JS is ever removed, a fixed menu with no coordinates lands in the
   middle of the screen — that is the symptom, not a mystery.

   ⚠️ NAMES ARE IN THE SANS FACE, and that is a constraint, not a preference:
   both display faces (altesse-std-24pt / ratio-modern) have ZERO Cyrillic
   coverage, so «Українська» in either one falls back mid-word.

   ⚠️ NO CODE COLUMN. It was designed with one and MEASURED OUT: over the navy
   band the frost samples navy through the 0.58 cream tint and composites to
   rgb(151,160,183) — gold #C4A265 on that reads 1.1:1, i.e. invisible (the
   same failure class D076 cured on the nav links at 1.02:1). The native name
   on the identical ground measured 6.8:1. The name identifies the language on
   its own, so the column was deleted rather than recoloured.
   ============================================================================ */

/* --- the header's right-hand group (s112b) --------------------------------
   Created by i18n.js's buildSwitchers(), NOT by the page markup — the revert
   is one edit instead of nine. It exists because `.nav__inner` is
   `justify-content: space-between`: a fourth child claims a whole share of the
   free space, and the switcher was dragging the nav links 145px left of where
   they were designed to sit (measured 1440: centre 703 → 558). Grouped, the
   bar is back to three children and the links return.
   ⚠️ The gap is a FLEX gap on purpose. The CTA's width is set by the language
   ("Book on WhatsApp" 175px · «Записатися у WhatsApp» 213px), so any fixed
   distance from the bar's edge collides in the longer languages — measured
   29px (EN) → −9px (UA) on the absolute-positioned variant that lost. → D079 */
.nav__actions {
  display: flex;
  align-items: center;
  gap: 14px;
  /* Items in a shrinking flex row give up space PROPORTIONALLY, so a tight bar
     would squeeze the WebGL pill and the switcher together rather than letting
     the roomier siblings pay first. There is slack at every shipped width
     today (worst case 1024 in UA: 686px of content in 1024), so this changes
     nothing now — it is the guard that keeps a longer CTA label or a longer
     wordmark from silently deforming the button later. */
  flex: 0 0 auto;
}
/* ⚠️ The base `.i18n-lang { margin-right: 12px }` and its own ≤1023 hide below
   are NOT dead and must not be "simplified" away: buildSwitchers() has a
   fallback branch for a page with no `.nav__cta--desktop`, which appends the
   switcher straight into `.nav__inner` with no group around it. That branch is
   live today on `pages/_hero-v4.html` (the deliberate noindex stub). */
.nav__actions .i18n-lang { margin-right: 0; }
/* Below 1024 both of its children are display:none (`.i18n-lang` here,
   `.nav__cta.nav__cta--desktop` in styles.css), so the group is an empty
   zero-width flex child sitting in the phone bar's space-between. Hide it
   outright — an inert box is still a box, and this one sits between the
   wordmark and the hamburger. */
@media (max-width: 1023px) { .nav__actions { display: none; } }

.i18n-lang {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin-right: 12px;
  flex: 0 0 auto;
  font-family: var(--font-sans);
}
@media (max-width: 1023px) { .i18n-lang { display: none; } }

.i18n-lang__trigger {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  appearance: none;
  background: none;
  border: 0;
  padding: 6px 7px;
  cursor: pointer;
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  line-height: 1;
  color: var(--text-light);
  border-radius: 4px;
  /* §11 host requirement — the .haptic-tick overlay is inset:0 */
  position: relative;
  /* transform rides the BASE list so the press can interpolate; a list
     declared only in the pressed rule would snap (LAWS ③) */
  transition: color 200ms var(--ease-soft), background-color 200ms var(--ease-soft),
    transform 340ms var(--ease-soft);
}
.i18n-lang__trigger:hover { color: var(--gold); }
/* §11 press pair — fast-in 90ms, release on the base 340ms; the pressed rule
   overrides ONLY transition-duration, never the property list. */
.i18n-lang__trigger:active,
.i18n-lang__trigger.is-pressed { transition-duration: 90ms; transform: scale(0.94); }
.i18n-lang.is-open .i18n-lang__trigger {
  color: var(--navy);
  background: rgba(196, 162, 101, 0.16);
}

/* The three nav grounds, mirroring what the chips already solve: naked over
   the hero video, frosted over cream, and the D076 dark-tinted bar. */
.nav:not(.scrolled) .i18n-lang__trigger { color: rgba(255, 255, 255, 0.66); }
.nav:not(.scrolled) .i18n-lang__trigger:hover { color: #fff; }
.nav:not(.scrolled) .i18n-lang.is-open .i18n-lang__trigger {
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
}
.nav.scrolled.nav--over-dark .i18n-lang__trigger { color: rgba(255, 255, 255, 0.66); }
.nav.scrolled.nav--over-dark .i18n-lang__trigger:hover { color: #fff; }
.nav.scrolled.nav--over-dark .i18n-lang.is-open .i18n-lang__trigger {
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
}

/* Same 10px chevron the .nav__trigger uses, so opening the language panel is
   the header's existing gesture rather than a second one. */
.i18n-lang__chevron {
  flex: none;
  margin-top: 1px;
  transition: transform 300ms var(--ease-fluid);
}
.i18n-lang.is-open .i18n-lang__chevron { transform: rotate(180deg); }

/* FIXED, not absolute — see the portal note above. */
.i18n-lang__menu {
  position: fixed;
  top: var(--lang-y, var(--nav-height, 64px));
  left: var(--lang-x, 50%);
  translate: -50%;
  padding-top: 12px;
  pointer-events: none;
  /* above .nav's z-index:1000, matching .nav__menu */
  z-index: 1001;
}
.i18n-lang__menu.is-open { pointer-events: auto; }
/* A portalled menu is a child of <body>, so `.i18n-lang{display:none}` below
   1024 cannot reach it. Hide it explicitly — down there the mobile sheet's
   chip row owns language. */
@media (max-width: 1023px) { body > .i18n-lang__menu { display: none; } }

/* Deep frost — VALUE-COPIED from .nav__menu-panel (D071), never selector-
   shared: retuning the navigation dropdown must not silently move this. Tint
   never above ~0.65 or the blur stops being visible at all. */
.i18n-lang__panel {
  min-width: 188px;
  padding: 8px;
  background: rgba(245, 240, 235, 0.58);
  -webkit-backdrop-filter: blur(32px) saturate(2);
  backdrop-filter: blur(32px) saturate(2);
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  box-shadow: 0 18px 40px rgba(17, 24, 39, 0.12),
    inset 0 1px 0 rgba(255, 255, 255, 0.5);
  opacity: 0;
  transform: translateY(8px) scale(0.94);
  transform-origin: top center;
  visibility: hidden;
  /* closed list = the EXIT (150ms); visibility delays past the fade so rows
     are not tabbable while invisible */
  transition: opacity 150ms ease, transform 150ms ease, visibility 0s linear 150ms;
}
.i18n-lang__menu.is-open .i18n-lang__panel {
  opacity: 1;
  transform: none;
  visibility: visible;
  /* open list = the ENTER (350ms spring) */
  transition: opacity 350ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 350ms cubic-bezier(0.22, 1, 0.36, 1), visibility 0s;
}

.i18n-lang__row {
  display: flex;
  align-items: center;
  width: 100%;
  appearance: none;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  padding: 9px 12px 9px 20px;
  border-radius: 8px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  color: var(--navy);
  /* §11 host requirement (haptic overlay) + the active hairline's anchor */
  position: relative;
  transition: background 200ms ease, transform 340ms var(--ease-soft);
}
.i18n-lang__row:active,
.i18n-lang__row.is-pressed { transition-duration: 90ms; transform: scale(0.97); }
.i18n-lang__row:hover,
.i18n-lang__row:focus-visible { background: rgba(196, 162, 101, 0.12); }
/* The active mark is a gold hairline, not a checkmark: this header has no icon
   language, and a tick is a form control's gesture. */
.i18n-lang__row.is-active::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 2px;
  height: 14px;
  border-radius: 1px;
  background: var(--gold);
}
