/* ==========================================================================
   FLIGHTPROOF SIGNATURE DESIGN — "INSTRUMENT"
   ==========================================================================
   Direction of record: docs/adr/0001-homepage-design-direction.md
   (owner decision 2026-10-03, "do not re-litigate").

   Premise: the enforcement data IS the hero. No decorative hero section.
   Styling comes from the e-ticket EMAIL, not the old marketing pages —
   navy #1a1a2e, amber #FFE66D, paper #ece8df, barcode spine, dashed
   perforation, mono figures for the PNR, green CONFIRMED status.

   The barcode is the page's structural device, and the stub that carries the
   CTA is perforated off the main column exactly like the real e-ticket.

   Consumed by: locale homepages, country guides, news, partners, tools.
   Look-dev reference: public/directions/instrument.html
   ========================================================================== */

/* ── the site is LIGHT, and has to say so ──────────────────────────────────
   Nothing in this tree declares a colour scheme, so a visitor whose OS is in
   dark mode got the browser's dark UA paint on every native widget while the
   page stayed paper-white: the plan <select> and its popup, text-input carets,
   scrollbars, the date picker, autofill. Measured 2026-10-28: htmlScheme
   "normal" with prefers-color-scheme: dark true, and `color-scheme: light` had
   already been hand-applied to ONE control (.fp-ex-in[type="date"], line ~1744)
   — the symptom was known, the fix was local. Declaring it at the root fixes the
   class of bug; the dark-mode hook below keeps the manual scheme able to ask
   for dark widgets if it is ever wired to a toggle (today nothing adds
   body.dark-mode, so its rules are inert). */
:root { color-scheme: light; }
body.dark-mode { color-scheme: dark; }

.inst, .inst * { box-sizing: border-box; }

.inst, .inst-map {
  /* Palette — do not add colours without an ADR. Every value here is taken
     from the shipped e-ticket email so the site matches what customers get. */
  --paper: #ece8df;
  --card: #fff;
  --edge: #e2ddcf;
  --dash: #d7d2c2;
  --navy: #1a1a2e;
  --amber: #FFE66D;
  --dim: #67624f;
  --grey: #6a6555;
  --ok: #22c55e;
  --okbg: #f0fdf4;
  --okedge: #86efac;
  --hi: #c0392b;
  --mid: #d98c00;
  --lo: #2e8b57;

  /* Typography. The prototype ran on a single-family system stack; production
     needs real families with web-safe fallbacks or non-Latin locales lose
     their intended face entirely. */
  --sans: "Outfit", "Segoe UI", -apple-system, system-ui, "Noto Sans",
          "Noto Sans Devanagari", "Noto Sans Bengali", "Noto Sans Thai",
          "Noto Sans Arabic", "Noto Sans CJK SC", sans-serif;
  --mono: "JetBrains Mono", "Courier New", ui-monospace, Menlo, monospace;

  font: 15px/1.55 var(--sans);
  background: var(--paper);
  color: var(--navy);
  -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
}

/* The paper surround used to be declared on `.inst` (the body), which meant the
   two stacked once the UA's default 8px body margin was added: 26 + 26 + 8 =
   84px of empty gutter above the ticket on a 1440px screen. That gutter is what
   made the guides read as unfinished — the document floated in the middle of the
   viewport with nothing above it.

   Moved to <html> and scoped to the country-guide layout ONLY. A bare `html`
   rule would also hit the 41 locale homepages, which load this stylesheet with
   a plain <body> and were never given a paper surround; adding 26px to them is a
   regression, not a fix.

   The guide pages are not uniform: 53 masters use `body.inst` + `.doc`, while
   belgium and thailand-digital-nomad predate the instrument shell and use the
   older `.aph` layout with no `.doc` wrapper at all. `.crumbs` was added to all
   55 by fix-country-seo-head.cjs, so it is the reliable marker for "this is a
   guide". */
html:has(body .crumbs) { padding: clamp(10px, 2.5vw, 26px); }
body.inst, body:has(.crumbs) { margin: 0; padding: 0; }

.inst .doc {
  max-width: 1180px;
  margin: 0 auto;
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 12px;
  /* clip, NOT hidden. `overflow: hidden` makes .doc a scroll container, which
     silently disables `position: sticky` for everything inside it — the buy
     panel below included. `clip` still rounds the corner (and still clips)
     without claiming the scroll, so the rail can stick. */
  overflow: clip;
  /* Column flex purely so the barcode spine can be ordered above the nav
     without touching the markup of 55 hand-edited masters. The barcode is the
     top edge of the ticket — it is the first thing the eye should land on —
     so it must precede the navigation, not sit under it. */
  display: flex;
  flex-direction: column;
}
.inst .doc > .barcode { order: -1; flex: 0 0 auto; }

/* --- nav -----------------------------------------------------------------
   Navigation order is part of the ADR decision, not incidental:
   Get the ticket (action, leads) -> Radar (bold, the lead link) -> Tools ->
   Guides -> Checklist -> Partners. */
.inst .nav {
  display: flex; flex-wrap: wrap; gap: 10px 16px;
  align-items: center; padding: 14px 18px;
}
.inst .nav .brand {
  display: flex; align-items: center; gap: 9px;
  font-weight: 800; font-size: 15px; letter-spacing: -.01em;
  color: var(--navy); text-decoration: none;
}
.inst .nav .mark {
  width: 22px; height: 22px; flex: 0 0 auto;
  background: var(--navy); border-radius: 5px;
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}
.inst .nav .kicker {
  font-size: 9.5px; letter-spacing: 2px; font-weight: 700;
  color: var(--grey); margin-left: 4px;
}
.inst .nav ul {
  display: flex; gap: 16px; margin: 0; padding: 0;
  list-style: none; font-size: 13.5px; flex-wrap: wrap;
}
.inst .nav ul a {
  /* inline-block + explicit line box: as a bare inline element the vertical
     padding bled into the next flex row and left only ~2px of unobscured tap
     area (Lighthouse target-size). --i-mono is only defined inside .fp-ex, so
     the font shorthand silently fell back to 13.5px/20.9px — the fallback
     chain keeps the intended mono face everywhere else. */
  display: inline-block;
  font: 600 10.5px/1 var(--i-mono, var(--mono, monospace));
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--grey); text-decoration: none;
  /* 10/8px padding so the tap target clears 24×24 (Lighthouse target-size);
     neighbours keep ≥32px separation via the 16px ul gap. */
  padding: 10px 8px 8px; border-bottom: 2px solid transparent;
  white-space: nowrap;
}
.inst .nav ul a:hover { opacity: 1; border-bottom-color: var(--i-amber); }
.inst .nav ul a[data-lead] { opacity: 1; font-weight: 700; color: var(--navy); }
.inst .nav li.nav-buy { margin-inline-start: auto; }
/* The ticket stub: the one loud thing in the bar. Amber board-pass card with a
   hard offset shadow (print, not glow), price inline in the same mono voice. */
.inst .nav .nav-cta, .inst-strip nav a.inst-strip-cta {
  display: inline-flex; align-items: baseline; gap: 7px;
  font: 700 11.5px/1.15 var(--i-mono);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--i-navy); text-decoration: none;
  background: var(--i-amber);
  border: 2px solid var(--i-navy); border-radius: 4px;
  padding: 8px 13px;
  box-shadow: 3px 3px 0 var(--i-navy);
  transition: transform .12s, box-shadow .12s, background .15s, color .15s;
  animation: navStubPulse 1.2s ease-in-out 2 .8s both;
}
.inst .nav .nav-cta:hover, .inst-strip nav a.inst-strip-cta:hover {
  transform: translate(-1px, -1px);
  box-shadow: 4px 4px 0 var(--i-navy);
}
.inst .nav .nav-cta:active, .inst-strip nav a.inst-strip-cta:active {
  transform: translate(1px, 1px);
  box-shadow: 2px 2px 0 var(--i-navy);
}
@keyframes navStubPulse {
  0%, 100% { transform: none; }
  50% { transform: translateY(-2px); box-shadow: 3px 4px 0 var(--i-navy); }
}
@media (prefers-reduced-motion: reduce) {
  .inst .nav .nav-cta, .inst-strip nav a.inst-strip-cta { animation: none; transition: none; }
}
.inst .nav .nav-cta .nav-cta-price { font-weight: 800; opacity: .8; }
@media (max-width: 600px) {
  .inst .nav { padding: 10px 14px; }
  .inst .nav .kicker { display: none; }
  /* Two wrapped rows: links on row one, buy stub right-aligned on its own
     line. The old thumb-scroll row pinned the CTA with position:sticky, which
     physically covered the last links at scroll 0 — Lighthouse target-size
     "partially obscured", and real taps landed on the CTA instead of the link.
     Wrapped rows keep every link tappable and the CTA just as reachable. */
  .inst .nav ul {
    flex-wrap: wrap; row-gap: 2px; width: 100%;
  }
  .inst .nav li.nav-buy { position: static; margin-inline-start: auto; }
  .inst .nav .nav-cta { background: var(--i-amber); }
}

/* --- barcode spine: the page's structural device ------------------------- */
/* Same spine as the homepage's .inst-barcode below — one device, one style.
   The two rules must stay visually identical: height, gradient, borders. */
.inst .barcode {
  position: relative;
  height: 34px;
  background: repeating-linear-gradient(90deg,
    var(--navy) 0 2px, transparent 2px 5px,
    var(--navy) 5px 8px, transparent 8px 12px,
    var(--navy) 12px 13px, transparent 13px 17px,
    var(--navy) 17px 19px, transparent 19px 23px);
  border-top: 1px solid #eee; border-bottom: 1px solid #eee;
}

/* --- breadcrumbs ---------------------------------------------------------
   The country masters carry a BreadcrumbList in their JSON-LD; this is the
   visible half of that same trail, so what Google reads and what a person
   reads are the same three levels. Deliberately quiet -- it is navigation, not
   content, so it sits at 12.5px in the muted ink with no panel, no border and
   no background of its own. */
.inst .crumbs,
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 10px clamp(16px, 3vw, 30px);
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--dim);
  background: #faf8f4;
  border-bottom: 1px solid var(--edge);
}
.inst .crumbs a,
.crumbs a {
  color: var(--dim); text-decoration: none;
  /* 16.8px tall without it — under 2.5.8's 24px floor. Inline padding grows the
     hit box without moving the breadcrumb line. */
  display: inline-block; padding-block: 4px;
}
.inst .crumbs a:hover,
.crumbs a:hover { color: var(--navy); text-decoration: underline; }
.inst .crumbs a:focus-visible,
.crumbs a:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; border-radius: 3px; }
/* the separators are real glyphs the markup hides from screen readers, so a
   linear-navigation user hears "Home, Country guides, South Korea" and not
   four slashes */
.inst .crumbs span[aria-hidden],
.crumbs span[aria-hidden] { color: var(--grey); }
/* the current page is not a link, so it must not look like one */
.inst .crumbs [aria-current],
.crumbs [aria-current] { color: var(--navy); font-weight: 600; }
/* Below 420px three levels plus an ellipsised country name is tight; let the
   trail wrap rather than push the layout sideways. */
@media (max-width: 420px) {
  .inst .crumbs,
  .crumbs { font-size: 12px; padding: 9px clamp(14px, 3vw, 30px); gap: 5px; }
}

/* --- back to guides ----------------------------------------------------
   Always-visible way back to the guide index. Sticky to the top of the
   viewport while the guide scrolls (.doc uses overflow: clip, so sticky holds).
   Large tap target for thumbs on phones. */
.inst .guide-back {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center;
  padding: 0 clamp(16px, 3vw, 30px);
  background: var(--navy); border-bottom: 1px solid var(--edge);
}
.inst .guide-back a {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 48px; padding: 12px 4px;
  font: 700 14px/1.2 var(--sans); color: #fff; text-decoration: none;
}
.inst .guide-back a:hover { text-decoration: underline; }
.inst .guide-back a:focus-visible { outline: 3px solid var(--i-amber, #ffe66d); outline-offset: 2px; border-radius: 4px; }
.inst .guide-back .arr { font-size: 18px; line-height: 1; }
@media (prefers-reduced-motion: no-preference) {
  .inst .guide-back a .arr { transition: transform .2s ease; }
  .inst .guide-back a:hover .arr { transform: translateX(-3px); }
}

/* --- hero band ----------------------------------------------------------- */
.inst .band {
  background: rgba(255, 230, 109, .9); color: var(--navy);
  padding: clamp(20px, 3vw, 32px) clamp(16px, 3vw, 28px);
  border-bottom: 2px dashed var(--navy);
}
.inst .band h1 {
  font-size: clamp(25px, 4.2vw, 40px); font-weight: 900;
  letter-spacing: -.03em; line-height: 1.04; max-width: 30ch;
  color: var(--navy);
}
.inst .band .kick {
  font-size: 12px; color: var(--navy); letter-spacing: .04em;
  margin-bottom: 9px; font-weight: 800;
}
.inst .band p { color: rgba(26, 26, 46, .74); max-width: 62ch; margin-top: 9px; }
.inst .readout {
  display: flex; flex-wrap: wrap; gap: 7px 10px; margin-top: 15px;
  font: 700 10.5px/1.35 var(--sans); letter-spacing: 0;
  text-transform: none; color: rgba(26, 26, 46, .68);
}
.inst .readout b { color: var(--navy); font-weight: 800; }
.inst .readout .rscell {
  display: inline-flex; align-items: baseline; gap: 6px;
  min-height: 34px; padding: 7px 10px;
  border: 1px solid rgba(26, 26, 46, .18); border-radius: 7px;
  background: rgba(255, 255, 255, .34);
}
.inst .readout .rsv { color: var(--navy); font-weight: 800; }

/* --- inst-band: the locked hero band, unscoped ----------------------------
   Moved here from public/index.html's inline <style>, where it lived only on
   the English homepage. The 41 locale homepages link this stylesheet but had
   no access to those selectors, which is the concrete reason /de/ and /ja/
   still rendered the legacy rotor hero instead of the band.

   NOT scoped to `.inst`: the locale homepages carry a plain <body> and are not
   being converted to the ticket shell in this pass. `.inst-band` is a unique
   class name, so an unscoped selector is safe and is what lets the band render
   on body layouts the ticket shell does not wrap.

   PALETTE NAMESPACE — read before "simplifying" these back to --navy/--amber.
   The Instrument colours are declared here as literal --i-* values rather than
   reusing this file's --navy/--amber tokens, because those two names are NOT
   safe on every page that loads this stylesheet. The locale homepages still
   carry the legacy palette and redefine them in their own inline CSS:
       de/index.html:359  --yellow:#FFE66D;--navy:#1a1a2e;   <- dark GREEN
   So a band styled with var(--navy) rendered #1a1a2e instead of the locked
   #1a1a2e, and var(--amber)/var(--mono)/var(--sans) were undefined there
   entirely -- `color: var(--amber)` became invalid at computed-value time and
   silently fell back to inherited white. Declaring the palette on the element
   makes the locked design immune to whatever a page's legacy vars happen to be,
   and it is why these values are repeated rather than shared. */
.inst-band,
.inst-barcode,
.inst-buy-first {
  --i-navy: #1a1a2e;
  --i-amber: #FFE66D;
  --i-mono: "JetBrains Mono", ui-monospace, "IBM Plex Mono", Menlo, Consolas, monospace;
  --i-sans: "Outfit", "Segoe UI", system-ui, sans-serif;
}

/* --- the barcode spine --------------------------------------------------- */
/* Was styled only by public/index.html's inline block, so on the 41 locale
   homepages it rendered as a zero-height empty div: present in the DOM,
   invisible on the page. The spine is the top edge of the ticket, so it is the
   first thing on the page and precedes the navigation. */
.inst-barcode {
  position: relative;
  height: 34px;
  background: repeating-linear-gradient(90deg,
    var(--i-navy) 0 2px, transparent 2px 5px,
    var(--i-navy) 5px 8px, transparent 8px 12px,
    var(--i-navy) 12px 13px, transparent 13px 17px,
    var(--i-navy) 17px 19px, transparent 19px 23px);
  border-top: 1px solid #eee; border-bottom: 1px solid #eee;
}

/* carrier plate: the wordmark rides ON the spine, the way a boarding pass
   prints the airline name in a white knockout over the barcode. The plate
   breaks the bars locally; the wordmark is real text (crawlable, translated
   by nothing — it is the brand). */
.inst-barcode-brand {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 24px;
  padding: 0 12px 0 8px;
  background: #fff;
  border: 1.5px solid var(--i-navy);
  border-radius: 5px;
  box-shadow: 0 1.5px 0 var(--i-navy);
  font-family: var(--i-sans);
  font-size: 12.5px;
  font-weight: 900;
  letter-spacing: .04em;
  color: var(--i-navy);
  white-space: nowrap;
  line-height: 1;
}
.inst-barcode-brand .fpb-mark {
  width: 15px;
  height: 15px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--i-amber);
  border: 1.5px solid var(--i-navy);
  border-radius: 4px;
  font-size: 9px;
}
.inst-barcode-brand b {
  font-weight: 900;
}
.inst-barcode-brand .fpb-dim {
  font-weight: 500;
  color: #6b6b7b;
}
@media (max-width: 560px) {
  .inst-barcode-brand {
    height: 21px;
    font-size: 11px;
    padding: 0 9px 0 6px;
    gap: 5px;
  }
  .inst-barcode-brand .fpb-mark {
    width: 13px;
    height: 13px;
    font-size: 8px;
  }
}
@media (prefers-reduced-motion: reduce) {
  /* no motion on the plate; declared for completeness of the token set */
}
.inst-band {
  background: var(--i-navy); color: #fff;
  padding: clamp(24px, 4vw, 40px) clamp(16px, 3vw, 30px);
}
.inst-band .kick {
  font-size: 12px; font-weight: 800; letter-spacing: .06em;
  color: var(--i-amber); margin: 0 0 12px;
}
.inst-band h1 {
  font-family: var(--i-sans);
  font-size: clamp(26px, 5vw, 44px); font-weight: 900;
  letter-spacing: -.03em; line-height: 1.04;
  margin: 0; max-width: 21ch; color: #fff;
}
.inst-band p.dek {
  color: rgba(255, 255, 255, .72); max-width: 60ch;
  margin: 12px 0 0; font-size: 15px; line-height: 1.55;
}
.inst-readout {
  display: flex; flex-wrap: wrap; gap: 6px 22px; margin: 20px 0 0;
  font: 11px/1.4 var(--i-mono); letter-spacing: .09em;
  text-transform: uppercase; color: rgba(255, 255, 255, .62);
}
.inst-readout b { color: var(--i-amber); font-weight: 700; }

/* --- buy-first hero (conversion) -----------------------------------------
   The buy action sits directly under the H1. The explainer dek and the stats
   strip drop to a secondary tier so the ticket is the only loud thing in the
   band. 56px+ tall, full width on phones: a real touch target. */
.inst-buy-first {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  margin: 14px 0 0; min-height: 48px; padding: 11px 21px;
  background: var(--i-navy); color: var(--i-amber);
  border: 2px solid var(--i-navy); border-radius: 12px;
  font: 900 17px/1.1 var(--i-sans); letter-spacing: .01em; text-decoration: none;
  box-shadow: 4px 4px 0 rgba(26, 26, 46, .2);
  transition: transform .15s ease, box-shadow .15s ease;
}
.inst-buy-first b { font-weight: 900; }
.inst-buy-first:hover { transform: translate(-1px, -1px); box-shadow: 6px 6px 0 rgba(26, 26, 46, .16); }
.inst-buy-first:focus-visible { outline: 3px solid var(--i-navy); outline-offset: 3px; }
.inst-band p.dek {
  color: rgba(255, 255, 255, .6); font-size: 14px; line-height: 1.5; margin-top: 16px;
}
.inst-band .inst-readout { font-size: 10px; opacity: .7; margin-top: 14px; }

/* Country-guide hero (body.inst .band): the buy button is the first action under
   the H1; the stats strip and the empty spacer paragraph drop to secondary. */
.inst .band > p:empty { display: none; }
.inst .band .readout { opacity: .7; font-size: 12px; margin-top: 12px; }
@media (max-width: 560px) { .inst .band .readout { display: none; } }
@media (max-width: 560px) {
  .inst-buy-first { display: flex; width: 100%; font-size: 18px; }
  .inst-readout { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .inst-buy-first { transition: none; }
  .inst-buy-first:hover { transform: none; }
}

/* No border-radius, deliberately. The locked design has the band flush; adding
   a radius would be inventing styling, and a `body:not(.inst)` guard for it
   would also have matched the English homepage (body.fp-inst) and changed the
   very design being locked in. */

/* --- reviewed badge: editorial freshness, stated on the page -------------- */
.inst .reviewed {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 12px; padding: 5px 11px; border-radius: 50px;
  background: rgba(255, 255, 255, .1); border: 1px solid rgba(255, 255, 255, .22);
  color: var(--amber); font-size: 10.5px; font-weight: 800;
  letter-spacing: 1px; text-transform: uppercase;
}

/* --- main | perforation | stub -------------------------------------------
   TWO columns, not three: only two children (.main, .stub); the perforation
   is drawn by .stub::before. A third column left the stub in the gap track,
   squeezing it to 45px and stacking the button one word per line. */
/* The stub column is the guide's buy panel: it holds the express routing
   block, the tear line, the stamp and the PNR receipt. 292px made the route
   fields 106px wide, so every airport name landed as "Singa…" — the source
   comment on the popup in fp-express.js still apologises for it.
   minmax(340,440) gives the pair ~190px each on the full 1000px document,
   and costs the article nothing: .inst .prose caps at 68ch = 549.8px while
   the main column was 618px, so 68px of slack absorbs the whole increase.
   Below 960px the panel goes full-width instead (see the split media query). */
.inst .split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(340px, 440px); align-items: start; }
/* 2026-10-15 owner pass ("country guides too narrow"): the article column,
   not the stub, was the pinch point — .prose capped at 68ch (~550px) out of a
   618px column. Widen the cap so prose uses the wider 1180px doc, and keep the
   stub from exceeding its design measure on very wide viewports. */
.inst .prose { max-width: 88ch; }
.inst .main {
  padding: clamp(18px, 3vw, 26px) clamp(16px, 3vw, 28px) clamp(22px, 3vw, 30px);
  min-width: 0;
}
.inst .stub {
  border-left: 1px dashed var(--dash);
  padding: clamp(18px, 3vw, 26px) clamp(16px, 2.4vw, 22px);
  background: #faf8f4; position: relative; min-width: 0;
}
/* The panel hugs its content instead of stretching to the article's height.
   Measured before this: .stub was 440 x 9,219px — the article's own height,
   because grid rows stretch by default — so "the ticket mockup" the owner was
   looking at was mostly a 9,219px empty beige plate running the whole guide.
   align-items:start fixes that.

   NOT sticky any more (owner 2026-10-12: the right-rail mockup was "not
   responsive and distracting"). A panel that chased the reader down a 12,000px
   guide competed with the fixed buy bar and with the .passcta block for the
   same click. The rail is now a static card the reader scrolls past, and
   #fp-buybar is the single persistent CTA on every viewport. */
.inst .stub::before {
  content: ""; position: absolute; left: -9px; top: 0; bottom: 0; width: 9px;
  background: repeating-linear-gradient(180deg, var(--dash) 0 5px, transparent 5px 11px);
  opacity: .7;
}
@media (max-width: 960px) {
  /* Stack at 960 rather than 840: with a 440px panel the main column would
     drop under its 68ch measure (549.8px) below ~1020px of viewport, and a
     full-width search card beats a squeezed article. */
  .inst .split { grid-template-columns: minmax(0, 1fr); }
  .inst .stub { border-left: 0; border-top: 1px dashed var(--dash); }
  .inst .stub::before { display: none; }
}

.inst h2 {
  font-size: 11px; letter-spacing: 2px; font-weight: 800;
  color: var(--grey); margin: 26px 0 12px;
}
.inst h2:first-child { margin-top: 0; }
.inst h3 { font-size: clamp(16px, 2vw, 19px); font-weight: 800; margin: 20px 0 8px; }
.inst .dek { color: var(--dim); font-size: 14px; margin: -6px 0 12px; max-width: 60ch; }

/* --- prose: country guide bodies ----------------------------------------
   The prototype had no long-form body. Guide prose is long, so it needs a
   measure and generous leading, or it reads as a wall at 1000px. */
.inst .prose { max-width: 88ch; }
.inst .prose p { margin: 0 0 14px; line-height: 1.68; }
.inst .prose h2 {
  font-size: clamp(17px, 2.2vw, 20px); letter-spacing: .2px;
  color: var(--navy); margin: 30px 0 10px; font-weight: 900;
}
.inst .prose h3 { margin: 24px 0 8px; }
.inst .prose ul, .inst .prose ol { margin: 0 0 14px; padding-left: 22px; line-height: 1.68; }
.inst .prose li { margin-bottom: 6px; }
.inst .prose a { color: var(--navy); }
.inst .prose strong { font-weight: 800; }
.inst .prose code {
  font: 12.5px var(--mono); background: #f4f1ea;
  padding: 2px 5px; border-radius: 4px;
}
.inst .prose table { width: 100%; border-collapse: collapse; margin: 0 0 16px; font-size: 13.5px; }
.inst .prose th, .inst .prose td {
  text-align: left; padding: 9px 11px; border: 1px solid var(--edge);
}
.inst .prose th { background: #f6f4ef; font-weight: 800; }

/* --- the facts: entry requirements + FAQ (owner 2026-10-12) ---------------
   "the important facts like where they check requirements faq should be more
   clearly visible than the digital nomad info".

   Measured cause, and it was a styling hole, not a copy problem: nothing in any
   stylesheet had ever matched .content-card or .faq-section, so the two blocks
   that answer the visitor's actual question inherited the eyebrow rule from
   `.inst h2` — 11px, grey, 2px letter-spacing. Meanwhile the "Know Before You
   Go" infographics block carries its own inline 1.4em pink-underlined heading.
   The planning material was literally twice the size of the requirement.

   Order also changed in scripts/guide-facts-first.cjs: the requirements card
   and the FAQ now sit directly under the Key Rule / where-they-check block, and
   the explainer + Pass mockup + video drop below them.

   Fixed here by weight, not by new colour: the facts get a paper card (the
   site's own "notice" ground), an amber spine and a real heading — hooked on
   data-facts="1" rather than a second class, so the card markers other scripts
   match on keep working; everything
   after the "Know Before You Go" divider keeps the eyebrow treatment, which is
   what makes the two zones tell themselves apart at a glance. */
.inst .content-card[data-facts="1"] {
  background: #faf8f4; border: 1px solid var(--edge); border-left: 4px solid var(--amber);
  border-radius: 10px; padding: clamp(16px, 2.4vw, 22px) clamp(16px, 2.4vw, 22px);
  margin: 0 0 18px;
}
.inst .content-card[data-facts="1"] > h2:first-child {
  font-size: clamp(17px, 2.3vw, 22px); font-weight: 800; line-height: 1.25;
  letter-spacing: 0; color: var(--navy); margin: 0 0 12px;
  padding-bottom: 10px; border-bottom: 1px dashed var(--dash);
}
.inst .content-card[data-facts="1"] h3 { margin: 16px 0 6px; }
.inst .content-card[data-facts="1"] > p:last-child { margin-bottom: 0; }

/* The FAQ, same ground and same weight as the requirements card: it answers
   what the visitor typed into Google, and it is what the FAQPage schema on the
   page is describing — it should not read as small print. */
.inst .faq-section {
  background: #faf8f4; border: 1px solid var(--edge); border-left: 4px solid var(--amber);
  border-radius: 10px; padding: clamp(16px, 2.4vw, 22px) clamp(16px, 2.4vw, 22px);
  margin: 0 0 18px;
}
.inst .faq-section > h2:first-child {
  font-size: clamp(16px, 2.1vw, 20px); font-weight: 800; line-height: 1.28;
  letter-spacing: 0; color: var(--navy); margin: 0 0 6px;
  padding-bottom: 10px; border-bottom: 1px dashed var(--dash);
}
.inst .faq-section .faq-item {
  padding: 14px 0 0; margin: 14px 0 0; border-top: 1px dashed var(--dash);
}
.inst .faq-section .faq-item:first-of-type { border-top: 0; padding-top: 4px; margin-top: 0; }
.inst .faq-section .faq-item h3 {
  font-size: clamp(15px, 1.9vw, 17px); font-weight: 800; line-height: 1.35;
  color: var(--navy); margin: 0 0 5px;
}
.inst .faq-section .faq-item p { color: var(--dim); font-size: 14px; line-height: 1.62; margin: 0; }
.inst .faq-section .faq-item a { color: var(--navy); }

/* --- the extras zone: "Know Before You Go" and everything after it --------
   The infographics block is the zone's own divider (guide-facts-first.cjs
   moves it in front of the first planning card), so it gets a torn edge to
   read as a boundary rather than as another headline. Its heading style is
   baked inline per locale and is deliberately left alone. */
.inst .infographics-section {
  border-top: 2px dashed var(--dash); padding-top: 20px;
}
/* Everything AFTER that divider is extra: weather, best time, getting around,
   destinations, money, digital nomad, trend sports. Those cards inherit
   `.inst .prose h2` (17-20px navy), which made the planning material exactly as
   loud as the entry requirements. The facts card and the FAQ keep the full size
   and the card ground; the extras drop a step and lose the ink.
   Expressed with a sibling selector on purpose: the cards are direct children of
   .prose alongside the divider, so no marker has to be baked into 2,279 pages'
   worth of card markup for the zone to be styleable. */
.inst .infographics-section ~ .content-card > h2 {
  font-size: clamp(15px, 1.9vw, 17px); color: var(--dim); letter-spacing: .2px;
}
.inst .infographics-section ~ .content-card > h2 + p { color: var(--dim); }

/* --- alert block (the "Key Rule" the guides already carry) --------------- */
.inst .alert {
  border-left: 4px solid var(--hi); background: #fdecec;
  border-radius: 0 8px 8px 0; padding: 13px 16px; margin: 0 0 18px;
}
.inst .alert h2 {
  margin: 0 0 5px; font-size: 11px; letter-spacing: 1.5px;
  color: #a3161b; font-weight: 800;
}
.inst .alert p { margin: 0; font-size: 13.5px; line-height: 1.6; color: #7d1a1d; }

/* --- enforcement grid: the hero data, rendered from real datasets ---------
   Three real tiers, straight from countryAirports. `no` is the only other
   state the data supports: "not on the documented list". No invented tiers. */
.inst .heat {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(52px, 7vw, 78px), 1fr));
  gap: 1px; background: var(--edge);
  border: 1px solid var(--edge); border-radius: 8px; overflow: hidden;
}
/* Card-white filler: with a short final row a border-coloured background
   shows the empty tail as a grey hole in the data. */
.inst .heat::after { content: ""; background: var(--card); grid-column: 1/-1; grid-row: -2/-1; }
.inst .cell { background: var(--card); padding: 7px 6px; text-decoration: none; color: inherit; min-width: 0; }
.inst .cell:hover { background: #f6f4ef; }
/* display:block on BOTH, or the code and name run together as "ABJAbidjan". */
.inst .cell .cc { display: block; font: 12px/1.2 var(--mono); font-weight: 700; letter-spacing: .5px; }
.inst .cell .nm { display: block; font-size: 10px; line-height: 1.3; color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inst .cell .none { display: block; font-size: 9px; color: #a8b0ba; font-style: italic; }
.inst .cell .tick { display: block; height: 3px; margin-top: 4px; }
.inst .cell.high { border-top: 2px solid var(--hi); }   .inst .cell.high .tick { background: var(--hi); }
.inst .cell.medium { border-top: 2px solid var(--mid); } .inst .cell.medium .tick { background: var(--mid); }
.inst .cell.low { border-top: 2px solid var(--lo); }    .inst .cell.low .tick { background: var(--lo); }
.inst .cell.no { border-top: 2px solid #dfe3e8; }       .inst .cell.no .nm { color: #8b95a1; }

.inst .legend { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 10px; font-size: 11.5px; color: var(--dim); }
.inst .legend .cap { display: block; width: 100%; margin-bottom: 6px; font-size: 11px; color: var(--dim); line-height: 1.5; }
.inst .legend i { display: inline-flex; align-items: center; gap: 5px; font-style: normal;
  background-color: var(--grey); }
/* The dot is painted BY THE PSEUDO-ELEMENT, and the pseudo-element was painted
   one hard-coded grey. Every guide authors its tier colour on the <i> itself
   (style="background:#c0392b"), which the ::before then covered up: all three
   dots on all ~2,000 guide pages came out identical grey -- including the red
   "High" one. inherit passes the author's colour down to the swatch; the
   fallback declared on the <i> above still covers a dot authored without one. */
.inst .legend i::before { content: ""; width: 9px; height: 9px; border-radius: 1px;
  background: inherit; }

/* --- rows: FROM / TO / FLIGHT / CLASS ------------------------------------ */
.inst .rows {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 0;
  border: 1px solid var(--edge); border-radius: 8px; overflow: hidden; margin-top: 14px;
}
.inst .rows div {
  background: var(--card); padding: 10px 12px;
  border-right: 1px solid var(--edge); border-bottom: 1px solid var(--edge);
}
.inst .rows .k {
  font-size: 9px; letter-spacing: 1.5px; font-weight: 700;
  color: var(--grey); text-transform: uppercase; margin-bottom: 3px;
}
.inst .rows .v { font-size: 15px; font-weight: 800; }

/* --- the stub: booking form, amber accent, green status ------------------
   Stacked, not side-by-side: at 292px the stub gave each field 108px, which
   clipped "Bangkok Suvarnabhumi" (226px). Departure airport is the field that
   matters, so it gets a full row. */
.inst .two { display: grid; grid-template-columns: 1fr; gap: 7px; position: relative; }
.inst label.f { display: block; flex: 1 1 0; min-width: 0; position: relative; }
.inst label.f > span { display: block; font-size: 9px; letter-spacing: 1.5px; font-weight: 700;
  color: var(--grey); text-transform: uppercase; margin-bottom: 4px; }
.inst label.f input {
  width: 100%; font: inherit; font-size: 15px; font-weight: 700;
  padding: 9px 10px; background: #fff; color: var(--navy);
  border: 1px solid var(--edge); border-radius: 6px; min-width: 0;
}
.inst label.f input:focus-visible { outline: 2px solid var(--navy); outline-offset: 1px; }
.inst .arrow { color: var(--grey); font-size: 15px; line-height: 1; text-align: center; }
.inst button.buy {
  width: 100%; font: inherit; font-size: 15px; font-weight: 800;
  padding: 11px; background: var(--navy); color: var(--amber);
  border: 0; border-radius: 12px; cursor: pointer; margin-top: 10px;
}
.inst button.buy:hover { background: #000; color: #fff; }
.inst button.buy:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.inst button.buy[disabled] { background: #9aa3ae; color: #e6e8eb; cursor: not-allowed; }
.inst button.buy[disabled]:hover { background: #9aa3ae; color: #e6e8eb; }
.inst .status {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--ok); color: #fff; font-size: 10px; font-weight: 800;
  letter-spacing: 1px; text-transform: uppercase;
  padding: 5px 12px; border-radius: 50px; margin-top: 12px;
}
.inst .fine { font-size: 11.5px; color: var(--dim); line-height: 1.5; margin-top: 10px; }
.inst .pnr {
  margin-top: 14px; background: var(--okbg); border: 2px solid var(--okedge);
  border-radius: 8px; padding: 10px 12px;
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
}
.inst .pnr .k { font-size: 9px; letter-spacing: 1.5px; font-weight: 700; color: #666; text-transform: uppercase; }
.inst .pnr .c { font: 20px/1 var(--mono); font-weight: 700; letter-spacing: 3px; }

/* --- the guide stub IS a boarding pass (2026-10-07) -----------------------
   The express stub on the country guides read as a generic form in a beige
   column — the owner called it "plastic": boxed inputs, a bright-green
   status pill (the old-brand green the homepage already killed) and a green
   PNR box. It now prints as the ticket it sells, in the same language as the
   homepage's demo e-ticket (.inst-tk-*): white route card with printed
   blanks, a perforation with the swap as its notch, a tear line above the
   buy action, an ink "Confirmed" stamp instead of the green pill, and a
   PNR row that carries a real barcode strip.

   Scoped to .inst .stub — the guide stubs only. The homepage stub is
   .inst-stub (a different class token) and keeps its own composition next
   to the demo ticket. No markup changes: same ids and classes, so
   fp-express.js, app-core and the i18n wiring are untouched.

   No prefill either: the route fields start EMPTY (fp-express.js no longer
   copies fp_last_route in), so the traveller types their own airports from
   the first tap. ---------------------------------------------------------------- */
.inst .stub > h2:first-child { color: var(--navy); }

/* Owner 2026-10-18, "no confusing empty ticket": on an inline guide stub the
   animated e-ticket face (fp-express.js skips it now) left two receipt lines
   behind — "Confirmed" and an EMPTY "Booking ref (PNR) —" with nothing in it.
   They read as a broken ticket. Hidden on any guide stub where the face is
   off; the sheet pages keep their rules in fp-express.js's own CSS.
/* the route card: white paper on the stub, printed head to tear */   off; the sheet pages keep their rules in fp-express.js's own CSS. */
.inst .stub.has-etk-face > .status,
.inst .stub.has-etk-face > .pnr { display: none; }
/* .inst .stub .pnr (0,2,1 + elements) out-leagues the child-selector above at
   (0,2,1) + elements on the SAME property, because it comes later in the
   cascade — so the empty receipt line was still flex. */
.inst .stub.has-etk-face .status,
.inst .stub.has-etk-face .pnr { display: none; }

.inst .stub .fp-ex {
  background: var(--card, #fff);
  border: 1.5px solid var(--edge, #e2ddcf);
  border-radius: 10px;
  padding: 13px 14px 15px;
  box-shadow: 3px 3px 0 rgba(26, 26, 46, .08);
}
/* printed blanks, not boxes: a boarding pass prints the route on the paper */
.inst .stub .fp-ex-in {
  background: transparent;
  border: 0; border-bottom: 2px dashed var(--dash, #d7d2c2);
  border-radius: 0;
  min-height: 0; padding: 6px 4px;
}
.inst .stub .fp-ex-grid .fp-ex-in:not(.fp-ex-in--human) {
  font-size: 21px; font-weight: 800;
  text-align: center; padding: 7px 4px 6px;
}
.inst .stub .fp-ex-row2 .fp-ex-in { padding: 7px 2px 6px; }
.inst .stub .fp-ex-in--human {
  font: 600 15px/1.2 Outfit, sans-serif;
  letter-spacing: .01em; text-transform: none;
}
.inst .stub .fp-ex-in::placeholder { color: #cdc7ba; }
.inst .stub .fp-ex-in:focus-visible {
  outline: 2px solid var(--navy); outline-offset: 2px;
  border-bottom: 2px solid var(--navy);
  background: rgba(255, 230, 109, .16);
}
.inst .stub .fp-ex-in[aria-invalid="true"] { border-bottom-color: var(--hi); }
/* the perforation column: a dashed tear between the halves, the swap as its notch */
.inst .stub .fp-ex-gap { position: relative; }
.inst .stub .fp-ex-gap::before {
  content: ""; position: absolute; left: 50%; top: -4px; bottom: -4px;
  border-left: 2px dashed var(--dash, #d7d2c2);
}
/* 44, matching the base rule further down: this override cut the notch to 34x34
   and made the express card's only icon button a sub-44 target (measured
   2026-10-29). The perforation column is laid out for the base size. */
.inst .stub .fp-ex-swap {
  align-self: center; justify-self: center;
  width: 44px; min-height: 44px;
  background: #faf8f4;
  border: 1.5px solid var(--edge, #e2ddcf);
}
.inst .stub .fp-ex-swap:hover { background: var(--navy); color: var(--amber); border-color: var(--navy); }
/* the tear: one perforation across the stub, right above the buy action */
.inst .stub button.buy {
  position: relative;
  margin-top: 20px; padding: 13px 11px;
  border-radius: 12px;
  letter-spacing: .02em;
}
/* under the label: what closes the deal for a nervous first-time buyer —
   the payment rail and the coverage, in one mono line. Content is English
   on all locales (same accepted precedent as booking.consent_text). */
.inst .stub button.buy::after {
  content: "STRIPE-SECURED  ·  EVERY AIRLINE COVERED";
  display: block; margin-top: 3px;
  font: 700 9px/1.5 var(--mono, "JetBrains Mono"), ui-monospace, monospace;
  letter-spacing: .18em; text-transform: uppercase;
  color: rgba(255, 230, 109, .75);
}
.inst .stub button.buy::before {
  content: ""; position: absolute;
  left: calc(-1 * clamp(16px, 2.4vw, 22px));
  right: calc(-1 * clamp(16px, 2.4vw, 22px));
  top: -10px;
  border-top: 2px dashed var(--dash, #d7d2c2);
}
/* "Confirmed" is a stamp, not a status LED: ink navy with the amber wash,
   tilted like the homepage demo ticket's stamp. Kills the old-brand green. */
.inst .stub .status {
  display: inline-block;
  background: rgba(255, 230, 109, .5);
  border: 2px solid var(--navy);
  color: var(--navy);
  border-radius: 6px;
  padding: 4px 12px;
  font-size: 11px;
  transform: rotate(-4deg);
  margin-top: 14px;
}
/* the receipt: PNR row + a printed barcode strip, hairline above */
.inst .stub .pnr {
  background: transparent;
  border: 0; border-top: 1px solid var(--edge, #e2ddcf);
  border-radius: 0;
  padding: 12px 2px 0; margin-top: 14px;
  flex-wrap: wrap;
}
.inst .stub .pnr .c { color: var(--navy); }
.inst .stub .pnr::after {
  content: ""; flex-basis: 100%;
  height: 24px; margin-top: 8px;
  border-radius: 2px; opacity: .9;
  background: repeating-linear-gradient(90deg,
    var(--navy, #1a1a2e) 0 2px, transparent 2px 4px,
    var(--navy, #1a1a2e) 4px 7px, transparent 7px 9px,
    var(--navy, #1a1a2e) 9px 10px, transparent 10px 13px);
}
@media (max-width: 960px) {
  /* the stub stacks under the guide; keep the tear inside its padding */
  .inst .stub button.buy::before { left: 0; right: 0; }
}

/* --- booking-modal overlay: restored hide/show ----------------------------
   Only the homepage carries the overlay's display rules (inline <style>) and
   the legacy masters carry them in styles.css — the generated guides load
   neither, so #bookingModal rendered INLINE at the bottom of every guide
   page (found live 2026-10-07: an unstyled Express-Ticket form sitting above
   the footer on /countries/colombia). These are the homepage's own rules,
   moved into the shared stylesheet so every page that opens a .modal-overlay
   behaves like a modal. All openers (app-core and the legacy masters' own
   scripts) toggle the .open class, so the kill-switch on the closed state is
   safe everywhere. ---------------------------------------------------------------- */
.modal-overlay {
  display: none; position: fixed; inset: 0;
  background: rgba(20, 20, 42, .62);
  z-index: 9999; align-items: center; justify-content: center;
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  overflow: auto; padding: 18px; box-sizing: border-box;
}
.modal-overlay.open { display: flex; }
.modal-overlay:not(.open) { display: none !important; }
body.dark-mode .modal-overlay { background: rgba(0, 0, 10, .75); }
/* the card on the shared pages: guides load no styles.css either, so
   .modal-content gets its base card here; pages with their own modal
   styling override it with ID selectors or inline styles. */
.modal-overlay .modal-content {
  background: #faf8f4; color: #1a1a2e;
  border: 2px solid #1a1a2e; border-radius: 14px;
  box-shadow: 6px 6px 0 rgba(26, 26, 46, .35);
  max-width: 560px; width: 100%; margin: auto;
  max-height: calc(100vh - 36px); overflow: auto;
  padding: 16px 20px 22px; box-sizing: border-box;
}

/* --- booking popup: the palette it was written against, and the state rules --
   The popup is legacy markup (inline styles) written against the styles.css
   palette. Since the Instrument rework these pages load instrument.css and
   nothing else, and none of those names live here — so inside #bookingModal
   every var(--pink)/var(--pink-light)/var(--mint)/var(--yellow)/var(--cream)/
   var(--text)/var(--text-muted) resolved to NOTHING, which fails silently:

     - the primary CTA is `linear-gradient(135deg,var(--pink),var(--pink-light))`.
       Both stops undefined = invalid at computed-value time, so `background`
       falls back to its initial value and the button paints its white label on
       a TRANSPARENT ground. An invisible Buy Now (found live 2026-10-09).
     - the "$7 only" chip's `background:var(--yellow)` dropped, leaving navy on
       cream; the airline notice's success border and the PNR chip lost theirs.
     - `#b-consent:checked ~ .b-consent-custom{background:var(--pink)}` dropped,
       so the consent box could never fill — and the tick it reveals is white.
       Click "I confirm" and nothing happens: it reads as not clickable.

   WHERE THE PALETTE LIVES (2026-10-09, second pass). It is injected PER PAGE
   by scripts/booking-popup-palette.cjs, and only into the pages that define no
   palette of their own. Declaring it here instead was the first attempt and it
   was wrong: belgium/thailand-digital-nomad and their 84 locale copies DO
   declare one (they render a dark card, where --mint is the paper ink that
   reads on navy), and a declaration on #bookingModal outranks their inherited
   :root values — measured, the airline notice fell from paper-on-navy to
   #1e6b4a-on-navy, 2.6:1, under the 3:1 floor for large text. So the palette
   goes where it cannot outrank a page that already has one, and every rule in
   this block that needs a value uses the page's first, a literal fallback
   second: var(--name, <literal>). */

/* The consent box is filled by an inline `background:#fff` in the markup, which
   outranks any plain stylesheet rule — so the checked state has to be
   !important or it never paints. (Instruments the same class of bug as the
   builder pins: a value that has to track something else, declared twice.) */
#b-consent:checked ~ #b-consent-custom,
#b-consent:checked ~ .b-consent-custom {
  background: var(--pink, #1a1a2e) !important;
  border-color: var(--pink, #1a1a2e) !important;
}
#b-consent:checked ~ #b-consent-custom svg path,
#b-consent:checked ~ .b-consent-custom svg path { stroke: #fff !important; }
#b-consent-wrapper.checked {
  border-color: var(--mint, #1e6b4a) !important;
  background: rgba(46, 139, 87, .07) !important;
}
/* The input is the opacity:0 hit-area, so it cannot show a focus ring; the
   stand-in box carries it instead. */
#b-consent:focus-visible ~ #b-consent-custom,
#b-consent:focus-visible ~ .b-consent-custom {
  outline: 3px solid var(--amber, #FFE66D); outline-offset: 2px;
}

/* Phone layout. Two 1fr columns inside a 331px card truncate every value the
   traveller is asked to check — the plan renders as "Econoi", the email
   placeholder as "your@email.cor". The belgium and thailand-digital-nomad
   masters have carried this stacking rule all along; the 2,279 generated pages
   never got it. */
@media (max-width: 560px) {
  .inline-booking-grid { grid-template-columns: 1fr !important; }
  #bookingModal .modal-content { padding: 14px 14px 18px; }
}

/* Keep the CTA reachable while the card scrolls: at 685px of viewport the buy
   button sat below the fold behind the airline notice. Sticky un-pins at its
   natural position, so the "Secure payment" line under it still shows. */
#bookingForm > button[onclick="submitBooking()"] {
  position: sticky; bottom: 0; z-index: 3;
}

/* The heading's gradient needs the same treatment as the palette — it is
   light-card-only (amber on cream measures 1.9:1, so it is re-aimed along the
   CTA's own axis), and on a dark card the page's own amber tail is correct.
   It therefore ships in the same per-page block as the palette, injected by
   scripts/booking-popup-palette.cjs, and not here. */

/* --- visa ≠ entry: the note under the Key Rule alert ----------------------
   Owner (2026-10-07): "globally explain visa and entry immigration is
   something different — first you need to be able to enter, and for this
   you need a flightproof ticket, the cheapest onward proof worldwide —
   pass immigration and enjoy the visa". Guides mixed visa policy and
   border admission; this note separates the two right under the alert and
   points at the product. Plain copy (English everywhere — the accepted
   fallback precedent), one file swept into every guide's alert position. */
.inst .visanote {
  margin: 0 0 18px; padding: 12px 14px;
  background: var(--card, #fff);
  border: 1.5px solid var(--edge, #e2ddcf);
  border-inline-start: 4px solid var(--amber, #FFE66D);
  border-radius: 8px;
  font-size: 13.5px; line-height: 1.6; color: var(--text, #1a1a2e);
}
.inst .visanote p { margin: 0; }
.inst .visanote b { color: var(--navy, #1a1a2e); }
.inst .visanote::after {
  content: "ONE OF THE CHEAPEST ONWARD PROOFS WORLDWIDE";
  display: inline-block; margin-top: 8px; margin-left: -2px;
  font: 700 8.5px/1 var(--mono, "JetBrains Mono"), ui-monospace, monospace;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--dim, #67624f);
  background: rgba(255, 230, 109, .35);
  padding: 4px 8px; border-radius: 3px;
}

/* --- stub heading ---------------------------------------------------------
   "Your ticket" (#inst-stub-h, homepage + every guide stub) used to pulse
   forever like an airport departures board (2026-10-07). The owner's verdict on
   the guide rail was "distracting", and an element that never stops moving is
   the loudest thing on a 12,000px page. The heading is now plain ink: the one
   animated buy affordance is the fixed bar, which animates on entry and stops.
   (See the FIXED BUY BAR block above.) The colour is pinned rather than
   inherited because the stub background is #faf8f4 and the flag's endpoint was
   chosen for 4.5:1 against exactly that ground. */
#inst-stub-h { color: var(--i-navy, #1a1a2e); }

/* --- combobox ------------------------------------------------------------
   Keyboard-accessible airport + country search over the real datasets. */
.inst .combo { position: relative; }
/* Must out-specify `label.f input` (0,1,2): `.combo input` is (0,1,1) and loses,
   which is what left the badge sitting on top of the value. */
.inst label.f.combo input { padding-right: 12px; }
/* The code chip lives in the LABEL, not the field: in the field it both
   overlapped the value and stole 48px of it. */
.inst .combo .code {
  display: inline-block; margin-left: 6px; font: 10px/1 var(--mono); font-weight: 700;
  color: #fff; background: var(--navy); padding: 3px 5px; border-radius: 3px;
  vertical-align: 1px; letter-spacing: 1px;
}
.inst .combo .code[hidden] { display: none; }
.inst .pop {
  position: absolute; z-index: 40; left: 0; right: 0; top: calc(100% + 4px);
  margin: 0; padding: 0; list-style: none;
  background: #fff; border: 1px solid var(--edge); border-radius: 8px;
  box-shadow: 0 12px 28px rgba(16, 24, 40, .18);
  max-height: 262px; overflow: auto;
}
.inst .pop[hidden] { display: none; }
.inst .pop li {
  display: flex; align-items: center; gap: 9px; padding: 9px 10px;
  cursor: pointer; border-bottom: 1px solid var(--edge); min-width: 0;
}
.inst .pop li:last-child { border-bottom: 0; }
.inst .pop li:hover, .inst .pop li[aria-selected="true"] { background: #f6f4ef; }
.inst .pop .i { font: 12px/1 var(--mono); font-weight: 700; background: var(--navy);
  color: #fff; padding: 5px 6px; border-radius: 4px; letter-spacing: 1px; flex: none; }
.inst .pop .n { font-size: 13px; font-weight: 700; color: var(--navy);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inst .pop .c { font-size: 11.5px; color: var(--dim); flex: none; }
.inst .pop .hi {
  margin-left: auto; flex: none; font: 9px/1 var(--mono); font-weight: 700;
  letter-spacing: .5px; text-transform: uppercase;
  padding: 4px 6px; border-radius: 3px; background: #fdecec; color: #a3161b;
}
.inst .pop .hi.medium { background: #fdf3e0; color: #8a5a00; }
.inst .pop .hi.low { background: #eaf5ee; color: #1c6b3f; }
.inst .pop .no { background: #eef1f5; color: #5a6472; }
.inst .pop .none { padding: 12px 10px; font-size: 12.5px; color: var(--dim); cursor: default; }

/* --- verdict strip: appears once a departure airport is resolved ---------- */
.inst .verdict {
  display: none; margin-top: 12px; border-radius: 8px; padding: 10px 12px;
  border-left: 4px solid var(--grey); background: #f6f4ef;
}
.inst .verdict.on { display: block; }
.inst .verdict .vh { display: flex; align-items: center; gap: 7px; font-size: 10px;
  font-weight: 800; letter-spacing: 1.2px; text-transform: uppercase; margin-bottom: 4px; }
.inst .verdict .vt { display: block; font-size: 13px; line-height: 1.5; color: var(--navy); font-weight: 600; }
.inst .verdict .vt b { font-weight: 800; }
.inst .verdict .vt a { color: var(--navy); }
.inst .verdict.high { border-left-color: #c0392b; background: #fdecec; }
.inst .verdict.high .vh { color: #a3161b; }
.inst .verdict.on.med { border-left-color: #d98c00; background: #fdf3e0; }
.inst .verdict.on.med .vh { color: #8a5a00; }
.inst .verdict.clear { border-left-color: var(--ok); background: var(--okbg); }
.inst .verdict.clear .vh { color: #0f6b3f; }
.inst .verdict .note { display: block; margin-top: 6px; font-size: 10.5px; color: var(--dim); line-height: 1.45; }
.inst .verdict a { font-weight: 700; white-space: nowrap; }

/* --- same-airport guard --------------------------------------------------
   A $7 onward ticket from BKK to BKK is not an onward ticket, and it is the
   exact thing the product exists to prevent. Block, not flex: as a flex row
   the button's margin-left:auto collapsed the text into a ~90px column. */
.inst .routeerr {
  display: block; margin: 12px 0 0; padding: 9px 11px;
  border-radius: 6px; background: #fdecec; border-left: 4px solid #c0392b;
  font-size: 12px; line-height: 1.5; color: #7d1a1d;
}
.inst .routeerr[hidden] { display: none; }
.inst .routeerr b { font-weight: 800; }
.inst .routeerr button {
  display: inline-block; margin-top: 8px; padding: 6px 10px; font-size: 11px;
  font-weight: 700; background: transparent; color: #7d1a1d;
  border: 1px solid #d98b8e; border-radius: 12px; cursor: pointer; white-space: nowrap;
}
.inst .routeerr button:hover { background: #f8d7d8; }
.inst label.f input[aria-invalid="true"] { border-color: #c0392b; box-shadow: 0 0 0 2px #f8d7d8; }

/* --- news strip: this country's own news only ---------------------------- */
.inst .news { margin-top: 14px; border: 1px solid var(--edge); border-radius: 8px; overflow: hidden; }
.inst .news a {
  display: flex; gap: 12px; align-items: baseline; padding: 11px 13px;
  border-bottom: 1px solid var(--edge); text-decoration: none; color: inherit;
}
.inst .news a:last-child { border-bottom: 0; }
.inst .news a:hover { background: #f6f4ef; }
.inst .news b { font-weight: 700; font-size: 13.5px; }
.inst .news .src { margin-left: auto; color: var(--dim); font-size: 11.5px;
  white-space: nowrap; flex: none; font-family: var(--mono); }
/* The source name is a long monospace string. `nowrap` + `flex:none` made it
   wider than a phone viewport, and the parent's overflow-x:hidden then cut it
   off mid-word with no way to read it. Drop nowrap at the narrow breakpoint so
   the label can break; the desktop layout is untouched. */
@media (max-width: 640px) {
  .inst .news a { flex-wrap: wrap; }
  .inst .news .src { white-space: normal; margin-left: 0; flex: 1 1 100%;
    overflow-wrap: anywhere; }
}
.inst .news .age { color: var(--grey); font-size: 11px; font: 11px/1.4 var(--mono); flex: none; }
.inst .news .empty { padding: 14px; color: var(--dim); font-size: 12.5px; }
/* The empty state carries a link to the hub. It sits inside .news, so the row
   rule above (.news a: flex, full padding, border) would dress a one-word
   inline link as a news item — undo every bit of it. */
.inst .news .empty a.more {
  display: inline; padding: 0; border: 0; gap: 0; margin: 0;
  color: var(--navy); font-weight: 700; text-decoration: underline;
}
.inst .news .empty a.more:hover { background: none; }

/* --- partners band ------------------------------------------------------- */
.inst .partners { margin-top: 26px; border: 1px solid var(--edge); border-radius: 10px; overflow: hidden; }
.inst .partners .ptitle {
  padding: 12px 16px; background: var(--navy); color: var(--amber);
  font-size: 11px; letter-spacing: 2px; font-weight: 800; text-transform: uppercase;
}
.inst .partners .ptitle span { color: rgba(255, 255, 255, .6); letter-spacing: 0;
  text-transform: none; font-weight: 600; margin-left: 10px; }
.inst .partners ul { list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.inst .partners li { border-right: 1px solid var(--edge); border-bottom: 1px solid var(--edge); }
.inst .partners a {
  display: block; padding: 13px 16px; text-decoration: none; color: inherit; height: 100%;
}
.inst .partners a:hover { background: #f6f4ef; }
.inst .partners .pn { font-weight: 800; font-size: 14px; }
.inst .partners .pd { color: var(--dim); font-size: 12px; margin-top: 3px; line-height: 1.5; }
.inst .partners .pcta {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 12px;
  font-size: 13px; font-weight: 800; color: var(--navy);
}
.inst .partners .pcta::after { content: "\2192"; }

/* --- tools list ---------------------------------------------------------- */
.inst .tools a {
  display: flex; gap: 12px; align-items: baseline; padding: 10px 0;
  border-bottom: 1px solid var(--edge); text-decoration: none; color: inherit; flex-wrap: wrap;
}
.inst .tools a:hover { background: #f6f4ef; }
.inst .tools a b { font-weight: 700; }
.inst .tools a span { margin-left: auto; color: var(--dim); font-size: 13.5px; text-align: right; }

/* --- footer -------------------------------------------------------------- */
.inst .foot {
  padding: 16px clamp(16px, 3vw, 28px); font-size: 12.5px; color: var(--dim);
  display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
}
.inst .foot a { color: var(--dim); }
.inst .foot a:hover { color: var(--navy); }
.inst .foot .spacer { margin-left: auto; }

/* --- the footer is the ticket's back page, not a generic link row --------
   Owner: "the footer looks too generic". A bare dim text row under a
   boarding-pass page breaks the document fiction. Now the back of the
   ticket: a navy band (the only other full-strength surface on the guide,
   matching the hero), a perforated top edge, the coverage promise in mono
   over the strip, the legal links as board-listing chips with amber hover,
   and the payment/issuance line at the bottom. Amber (#FFE66D) is the one
   contrast accent used — same rules as the stub: print, not glow.
   Markup untouched: the same five anchors + spacer + brand restyle in place. */
.inst .foot {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px 18px;
  margin: 0;
  padding: 18px clamp(16px, 3vw, 28px) 20px;
  background: var(--navy, #1a1a2e);
  color: rgba(236, 232, 223, .78);
  font-size: 12.5px;
  position: relative;
}
/* the perforated tear where the ticket separates from the page */
.inst .foot::before {
  content: ""; position: absolute; left: 0; right: 0; top: -1px;
  height: 0; border-top: 2px dashed var(--dash, #d7d2c2);
}
/* the coverage promise: what a buyer needs to read before they trust it */
.inst .foot::after {
  content: "EVERY AIRLINE  ·  EVERY AIRPORT COVERED  —  STRIPE-SECURED CHECKOUT  ·  ISSUED IN UNDER A MINUTE";
  flex-basis: 100%;
  order: 3;
  font: 600 9.5px/1.6 var(--mono, "JetBrains Mono"), ui-monospace, monospace;
  letter-spacing: .14em; text-transform: uppercase;
  color: rgba(255, 230, 109, .85);
  margin-top: 10px; padding-top: 12px;
  border-top: 1px solid rgba(236, 232, 223, .18);
}
.inst .foot a {
  color: rgba(236, 232, 223, .78); text-decoration: none;
  font: 600 10.5px/1 var(--mono, "JetBrains Mono"), ui-monospace, monospace;
  letter-spacing: .12em; text-transform: uppercase;
  padding: 9px 2px 8px;
  border-bottom: 2px solid transparent;
}
.inst .foot a:hover { color: #fff; border-bottom-color: var(--amber, #FFE66D); }
.inst .foot a:focus-visible { outline: 2px solid var(--amber, #FFE66D); outline-offset: 2px; }
/* the brand, re-set as the ticket plate: amber field, navy text, hard edge */
.inst .foot a[href="/"] {
  margin-left: auto;
  background: var(--amber, #FFE66D);
  color: var(--navy, #1a1a2e);
  padding: 8px 12px;
  border-radius: 4px;
  border-bottom: 0;
  font-weight: 800;
  box-shadow: 2px 2px 0 rgba(0, 0, 0, .35);
}
.inst .foot a[href="/"]:hover { color: var(--navy, #1a1a2e); background: #ffef8a; }
.inst .foot .spacer { display: none; }
body.dark-mode .inst .foot { background: #101024; }
body.dark-mode .inst .foot a { color: rgba(236, 232, 223, .6); }
@media (max-width: 620px) {
  .inst .foot { gap: 0 14px; padding: 14px 18px 16px; }
  .inst .foot a { padding: 12px 2px 11px; } /* thumb-height tap targets */
  .inst .foot a[href="/"] { margin-left: 0; flex-basis: 100%; order: 2; text-align: center; margin-top: 8px; }
  .inst .foot::after { letter-spacing: .1em; }
}

/* --- the onward-ticket trigger in the footer -----------------------------
   Why the footer: on /our-story the whole page is one counter story, and the
   reader still unsure at the bottom ("would this work for MY route?") is the
   one about to leave. So the answer sits where they are — a blinking ledger
   stub that opens the explainer in place instead of sending them elsewhere.
   What blinks is the 6px dot (::before), never the label: a flashing word is
   unreadable, and a 2.4s pulse on a small mark is not a strobe.
   It is a <button>, not a link: nothing here navigates, so the trigger needs no
   history entry and no href. Keyboard: opens on Enter/Space (native button),
   closes on Escape, focus returns to the trigger (see js/fp-conn-footer.js). */
.inst .foot .foot-conn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  font: inherit;
  font-weight: 800;
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  /* the same "live" indigo the guide uses for its open-ticket rows */
  color: #cfd0ff;
  background: rgba(110, 106, 255, .1);
  border: 1px solid rgba(140, 136, 255, .32);
  border-radius: 999px;
  padding: 5px 11px 5px 9px;
  cursor: pointer;
  transition: color .15s, background .15s, border-color .15s;
}
.inst .foot .foot-conn::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #8b87ff;
  box-shadow: 0 0 0 3px rgba(139, 135, 255, .16);
  animation: inst-conn-blink 2.4s steps(1, end) infinite;
}
.inst .foot .foot-conn:hover,
.inst .foot .foot-conn:focus-visible {
  color: #0d0d14;
  background: #c7c4ff;
  border-color: #c7c4ff;
}
.inst .foot .foot-conn:hover::before,
.inst .foot .foot-conn:focus-visible::before {
  background: #0d0d14;
  box-shadow: 0 0 0 3px rgba(13, 13, 20, .18);
}

/* The trigger's own row on a phone. It must come AFTER the base rule above:
   the base sets the `flex` shorthand (basis:auto), and at equal specificity the
   later block wins — placing this inside the sheet's earlier 620px query left
   the pill at its content width, 229px in a 339px footer. */
@media (max-width: 620px) {
  .inst .foot .foot-conn {
    flex: 1 1 100%; order: 1; justify-content: center;
    margin-bottom: 6px; min-height: 40px; padding: 10px 12px;
  }
}

/* the blink: 2.4s, ~19% of the cycle dark, so it reads as a heartbeat and not
   as a strobe. Reduced motion freezes it (the block below sets duration to
   .001ms once), which leaves a steady dot — still visible, never flashing. */
@keyframes inst-conn-blink {
  0%, 74%, 100% { opacity: 1; }
  78%, 90% { opacity: .28; }
}

/* --- the connection explainer modal --------------------------------------
   Scoped to .inst-conn and mounted by the footer script only when asked for,
   so no page pays for it unless a reader opens it. Native <dialog> would be
   shorter, but Safari <15.4 has no ::backdrop and this bundle still serves
   older iOS; a fixed overlay with an explicit focus trap is the boring, safe
   version. Body scroll is locked while open (html.conn-open). */
.inst-conn[hidden] { display: none; }
.inst-conn {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: grid;
  place-items: center;
  padding: 18px;
  background: rgba(9, 9, 18, .72);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  overscroll-behavior: contain;
}
html.conn-open, html.conn-open body { overflow: hidden; }
.inst-conn-card {
  position: relative;
  width: 100%;
  max-width: 520px;
  max-height: calc(100vh - 36px);
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  background: #fbf9f4;
  color: #1a1a2e;
  border-radius: 14px;
  box-shadow: 0 24px 60px -18px rgba(0, 0, 0, .6), 0 0 0 1px rgba(26, 26, 46, .1);
  padding: 24px 24px 22px;
  font-family: var(--sans);
}
.inst-conn-eyebrow {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: #625e78; /* 4.9:1 on the card's paper, not 3.6:1 */
  margin: 0 0 8px;
}
.inst-conn-card h2 {
  /* explicit: brand.css settles h2 globally at a warm grey (#6a6555), which
     measured 5.5:1 on this paper — readable, but the heading of a modal is the
     one line that must not look like body copy */
  color: #1a1a2e;
  font-size: 21px;
  line-height: 1.24;
  letter-spacing: -.01em;
  margin: 0 26px 8px 0;
}
.inst-conn-lede { font-size: 14px; line-height: 1.55; color: #4a4860; margin: 0 0 16px; }
.inst-conn-steps { list-style: none; margin: 0 0 16px; padding: 0; display: grid; gap: 10px; }
.inst-conn-steps li {
  display: grid;
  grid-template-columns: 22px 1fr;
  gap: 10px;
  align-items: start;
  font-size: 13.5px;
  line-height: 1.5;
  color: #2c2a3d;
}
.inst-conn-steps b {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #1a1a2e;
  color: #ece8df;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
}
.inst-conn-note {
  display: block;
  font-size: 12px;
  line-height: 1.5;
  color: #6b6880;
  border-top: 1px dashed rgba(26, 26, 46, .18);
  padding-top: 12px;
  margin: 0 0 16px;
}
.inst-conn-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.inst-conn-actions a,
.inst-conn-actions button {
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  border-radius: 8px;
  padding: 10px 14px;
  cursor: pointer;
  text-decoration: none;
  min-height: 40px; /* thumb-sized */
}
.inst-conn-cta { background: #1a1a2e; color: #ffe66d; border: 1px solid #1a1a2e; }
.inst-conn-cta:hover { background: #2a2a44; }
.inst-conn-more { background: #fff; color: #1a1a2e; border: 1px solid rgba(26, 26, 46, .22); }
.inst-conn-more:hover { border-color: #1a1a2e; }
.inst-conn-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  font-size: 18px;
  line-height: 1;
  color: #5b5873;
  background: transparent;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
}
.inst-conn-close:hover { background: rgba(26, 26, 46, .08); color: #1a1a2e; }
.inst-conn-close:focus-visible,
.inst-conn-actions a:focus-visible,
.inst-conn-actions button:focus-visible {
  outline: 2px solid #1a1a2e;
  outline-offset: 2px;
}
@media (max-width: 420px) {
  .inst-conn-card { padding: 20px 18px 18px; }
  .inst-conn-card h2 { font-size: 19px; }
  .inst-conn-actions a, .inst-conn-actions button { flex: 1 1 100%; text-align: center; }
}

/* --- focus + reduced motion ---------------------------------------------
   One visible focus ring everywhere. The stub's buy button is the single most
   important control on the page, so its focus state must never be removed. */
.inst :focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; border-radius: 4px; }

@media (prefers-reduced-motion: reduce) {
  .inst *, .inst *::before, .inst *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  /* the blink lives outside .inst's subtree when the modal is open, and the
     dot itself is inside it — pin both to a steady "live" state */
  .inst .foot .foot-conn::before { animation: none !important; opacity: 1; }
  .inst-conn { animation: none !important; transition: none !important; }
}

@media print {
  .inst { background: #fff; padding: 0; }
  .inst .doc { border: 0; }
  .inst .nav, .inst .foot, .inst button.buy { display: none; }
}

/* ==========================================================================
   INSTRUMENT MAP — "select your destination"
   ==========================================================================
   Two levels on one grid. Level 1 is the world silhouette with the seven
   regions as clickable landmasses; level 2 zooms the same grid to one region
   and resolves its country pins.

   Design intent: this is an instrument readout, not an illustration. The land
   is a dotted plate in navy, the countries are dots whose COLOUR IS DATA (the
   enforcement tier), and everything else on the page stays quiet. The map is
   the one place boldness is spent.

   The dot grid is 60x24 cells. At that resolution a "country" is a 5px dot,
   so the dots carry the meaning and the labels sit in the list beneath —
   putting text on the map itself would be unreadable at this scale.
   ========================================================================== */

.inst-map { margin: 0; }

/* Visually hidden but announced. Needed here because each country link ends
   with its enforcement tier, which is real information for a screen reader
   and must NOT be printed inside the card. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.inst-map-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 14px;
}
.inst-map-title {
  font-size: clamp(21px, 3vw, 30px); font-weight: 800; letter-spacing: -.02em;
  color: var(--navy); margin: 0;
}
.inst-map-sub { margin: 4px 0 0; color: var(--dim); font-size: 13.5px; }

/* --- the plate ---------------------------------------------------------- */
.inst-map-stage {
  background: var(--navy);
  border-radius: 14px;
  padding: clamp(12px, 2vw, 22px);
  overflow: hidden;
  /* the zoom animates a transform; the dashed edge matches the e-ticket
     perforation language already on country guides */
  outline: 1px dashed rgba(255, 230, 109, .3);
  outline-offset: -7px;
}
.inst-map-svg {
  display: block; width: 100%; height: auto;
  touch-action: manipulation;
}
.inst-map-zoom {
  transform-box: view-box;
  transform-origin: 0 0;
}

/* --- level 1: the seven regions as landmasses --------------------------- */
.inst-map-land {
  fill: #4a4a80;
  stroke: transparent;
  stroke-width: 1;
  cursor: pointer;
  transition: fill .18s ease, opacity .22s ease;
}
.inst-map-land:hover,
.inst-map-land:focus-visible {
  fill: #b0b0e0;
  stroke: var(--amber);
  stroke-width: 1.5;
  outline: none;
}
.inst-map-land:focus-visible { stroke-width: 2; }
.inst-map-land.is-dim { opacity: .22; cursor: default; }
.inst-map-land.is-dim:hover { opacity: .22; fill: #4a4a80; stroke: transparent; }

/* --- level 2: country pins --------------------------------------------- */
.inst-map-pin { cursor: pointer; }
.inst-map-pin .inst-map-dot {
  fill: var(--grey);
  /* the tier colour is the ONLY place colour encodes meaning here */
  transition: r .15s ease;
}
.inst-map-pin--high .inst-map-dot { fill: #e85d4a; }
.inst-map-pin--medium .inst-map-dot { fill: var(--amber); }
.inst-map-pin--none .inst-map-dot { fill: #5a5a80; }

.inst-map-pin .inst-map-iata {
  font-size: 9px; fill: var(--paper); pointer-events: none;
  dominant-baseline: middle;
}
.inst-map-pin .inst-map-dot {
  stroke: rgba(10, 10, 30, .55); stroke-width: 1.2;
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, .45));
}
.inst-map-pin:hover .inst-map-dot,
.inst-map-pin:focus-visible .inst-map-dot {
  r: 7;
  filter: drop-shadow(0 0 4px rgba(255, 230, 109, .55));
}
.inst-map-pin:focus-visible { outline: none; }
.inst-map-pin:focus-visible .inst-map-dot {
  stroke: var(--amber); stroke-width: 2;
}

/* --- legend -------------------------------------------------------------
   Three states, not two. "Not documented" has to be visible: a country with
   no enforcement data is not a safe country, and the country guides already
   hedge that copy. Showing only high/medium would imply the rest are fine. */
.inst-map-legend {
  display: flex; flex-wrap: wrap; gap: 8px 20px;
  margin: 12px 0 0; padding-top: 12px;
  border-top: 1px solid var(--edge);
}
.inst-map-key {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; color: var(--dim);
}
.inst-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  display: inline-block;
}
/* --med is accepted alongside --medium on purpose. The legend shipped
   class="inst-dot--med" once, and a class with no rule does not fall back to
   the tier colour -- it falls back to nothing, so the "Medium enforcement" dot
   rendered as an empty 9px hole. Any cached copy of instrument-map.js still
   asking for --med has to keep painting.

   Medium is --mid, not --amber. The legend row sits on the paper backdrop, and
   amber (#FFE66D) against it measures 1.02:1 -- a painted dot nobody can see,
   which is exactly how the yellow went missing. --mid is the tier colour the
   heat cells and the country-guide legends already use for medium, so the map
   key and the guide key now name the same colour. */
.inst-dot--high { background: #e85d4a; }
.inst-dot--medium, .inst-dot--med { background: var(--mid); }
.inst-dot--none { background: #9a9ab8; }

/* --- level 2 chrome: back control + country list ------------------------ */
.inst-map-crumb {
  display: flex; align-items: center; gap: 12px;
  margin: 0 0 12px;
}
.inst-map-back {
  font: 600 13px var(--sans);
  color: var(--navy); background: none;
  border: 1px solid var(--edge); border-radius: 999px;
  padding: 7px 14px; cursor: pointer;
  min-height: 36px;
}
.inst-map-back:hover { background: var(--navy); color: var(--paper); border-color: var(--navy); }
.inst-map-crumb-name { font-weight: 800; color: var(--navy); font-size: 15px; }

.inst-map-panel { margin-top: 14px; }

.inst-map-list {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}
.inst-map-link {
  display: flex; align-items: center; gap: 9px;
  padding: 10px 12px; min-height: 44px;   /* touch target */
  background: var(--card); color: var(--navy);
  border: 1px solid var(--edge); border-radius: 10px;
  text-decoration: none; font: 600 13.5px/1.3 var(--sans);
}
.inst-map-link:hover { border-color: var(--navy); }
.inst-map-link-flag { font-size: 16px; flex: 0 0 auto; }
.inst-map-link-name { flex: 1; min-width: 0; }

/* --- states ------------------------------------------------------------- */
.inst-map--failed { display: none; }

@media (max-width: 640px) {
  .inst-map-list { grid-template-columns: 1fr; }
  .inst-map-stage { padding: 10px; }
}

@media (prefers-reduced-motion: reduce) {
  .inst-map-land, .inst-map-pin .inst-map-dot { transition: none; }
}

/* --- region captions on the plate ---------------------------------------
   Seven landmasses with no names are guesswork. These are part of the map,
   not decoration: they are how you know which continent you are about to
   click. Hidden on zoom via the JS. */
.inst-map-region-label {
  font: 800 7.5px var(--sans);
  fill: #ececf8;
  letter-spacing: .06em;
  pointer-events: none;
  text-transform: none;
  paint-order: stroke;
  stroke: rgba(14, 14, 36, .7);
  stroke-width: 2.4px;
  stroke-linejoin: round;
}
@media (max-width: 520px) {
  .inst-map-region-label { font-size: 8px; }
}

/* --- booking modal header ------------------------------------------------
   The three badge chips under the "Express Ticket" heading are inline-flex
   with their own padding, so their combined width exceeds a phone viewport.
   In a non-wrapping flex row that pushed the whole document sideways by a few
   pixels. The markup is inline-styled per page, so the wrap is applied by
   attribute selector here rather than by editing 53 masters. */
.modal-header div[style*="display:flex"][style*="gap:14px"] {
  flex-wrap: wrap;
}
.modal-header div[style*="display:flex"][style*="gap:14px"] > div {
  min-width: 0;
}
.modal-header div[style*="display:flex"][style*="gap:8px"] {
  flex-wrap: wrap;
}

/* ==========================================================================
   TICKET STRIP — the navigation rail of the email-ticket design
   ==========================================================================
   Replaces the legacy `.aph` header (green/teal, gradient logo tile, gradient
   "Get the ticket" pill). That header is the single most visible old-design
   element left on the page: it carried its own palette, its own sticky
   behaviour, and its own copy of the primary CTA, so the page advertised two
   brands at once.

   The strip takes its structure from directions/boarding-pass.html, the
   email-ticket specimen: brand at the left, a document label, then the links
   running inline to the right. One sticky row: the strip pins to the top on
   scroll so the buy stub survives the long homepage, and on narrow screens
   the links become a thumb-scrollable row with the stub pinned at the edge —
   no burger toggle. Palette is declared locally for the same reason as
   .inst-band — the locale pages redefine --navy/--amber. */
.inst-strip {
  --i-navy: #1a1a2e;
  --i-amber: #FFE66D;
  --i-paper: #f7f4ee;
  --i-rule: #d6d0c4;
  --i-muted: #6a6f7a;
  /* Declared here, not inherited: the nav links' `font` shorthand reads this.
     Undefined, the shorthand is invalid and the links fell back to the 16px body
     size, which wrapped the nav into the ticket button on ~830px screens. */
  --i-mono: "JetBrains Mono", ui-monospace, "IBM Plex Mono", Menlo, Consolas, monospace;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 16px;
  padding: 14px clamp(16px, 3vw, 30px);
  /* Sticky: the buy stub is the one thing that must survive a scroll down the
     homepage. Sits directly under <body>, outside .inst .doc, so no
     overflow:hidden ancestor can cancel it. */
  position: sticky; top: 0; z-index: 600;
  background: rgba(247, 244, 238, .94);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--i-rule);
  box-shadow: 0 2px 0 rgba(26, 26, 46, .04);
}
.inst-strip .inst-brand {
  font-family: var(--sans);
  font-weight: 800; font-size: 17px; letter-spacing: -.02em;
  color: var(--i-navy); text-decoration: none;
}
.inst-strip .inst-brand b { font-weight: 800; }
.inst-strip .inst-doc-label {
  font: 600 11px/1 var(--mono);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--i-muted);
}
.inst-strip nav {
  /* LOGICAL, not physical. `margin-left: auto` pushed the nav toward the RIGHT
     in every RTL locale, because the margin never mirrors. Measured on /ar/ at
     1440px: strip 0–1423, brand 1306–1393, nav 723–1011 — the nav floated in
     the middle-right instead of hugging the left edge, so the reading order ran
     right-to-left but the layout ran left-to-right. `margin-inline-start`
     mirrors automatically: right margin in RTL, left margin in LTR, which is
     what both directions actually want. */
  margin-inline-start: auto;
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 6px 16px;
  /* RESET, not decoration. Two bare `nav` selectors reach this element:
     an inline block in the page head and `nav` in styles.css?v=38, both of
     which paint a green gradient and set `padding:18px 48px`. Those paint a
     large green slab behind the links and made them unreadable. Specificity
     alone did not save us: this rule sets no padding or background, so the
     legacy values were simply inherited rather than overridden. Both must be
     zeroed explicitly. */
  padding: 0; background: none;
}
.inst-strip nav a {
  font: 600 10.5px/1 var(--i-mono);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--i-muted); text-decoration: none;
  /* 8/7, not 6/5: at 10.5px/1 the link drew a 23.5px box, half a pixel under
     WCAG 2.5.8's 24px minimum target (measured 2026-10-29 at 390/768/1280). */
  padding: 8px 1px 7px; border-bottom: 2px solid transparent;
  white-space: nowrap;
}
.inst-strip nav a:not(.inst-strip-cta):hover { color: var(--i-navy); border-bottom-color: var(--i-amber); }
.inst-strip nav a:focus-visible { outline: 2px solid var(--i-navy); outline-offset: 2px; border-radius: 2px; }

@media (max-width: 620px) {
  /* The links become one thumb-scrollable row instead of wrapping into a
     second line of tiny text; the buy stub pins at the scroll edge and stays
     reachable for the whole page. Logical property keeps the reset mirroring
     in RTL as well. */
  /* Two clean rows, nothing overlaid. The old single scrolling row pinned the
     ticket button over the last links (Partners was hidden under it at 390px).
     Links wrap on row one; the buy button takes its own full-width row below. */
  .inst-strip nav {
    margin-inline-start: 0; width: 100%;
    flex-wrap: wrap; overflow: visible;
    gap: 8px 18px;
  }
  .inst-strip nav a.inst-strip-cta {
    flex: 1 1 100%; box-sizing: border-box;
    display: flex; justify-content: center; align-items: center; gap: 8px;
    padding: 12px 14px; background: var(--i-amber); color: var(--i-navy);
    border-radius: 8px; font-size: 12px;
  }
}

/* --- inst-status: recolour the stub's status pill -------------------------
   It rendered in the legacy brand green (#FFE66D family), the single loudest
   old-brand colour left inside the ticket stub — a bright green pill sitting
   directly under a navy "Issue the ticket" button. Recoloured to the locked
   palette: navy text on an amber-tinted ground, which reads as part of the
   ticket rather than as a separate green brand. */
.inst-status {
  display: inline-block; margin-top: 14px;
  padding: 6px 12px; border-radius: 50px;
  background: rgba(255, 230, 109, .22);
  border: 1px solid rgba(255, 230, 109, .5);
  color: #8a6d00; font: 700 10.5px/1 var(--mono);
  letter-spacing: .1em; text-transform: uppercase;
}

/* ==========================================================================
   THE EXPRESS FORM — the routing block you fill in to get the ticket
   ==========================================================================
   Lives in .inst-stub, above the ticket receipt, because the two are one
   product: you type a pair, the stub below prints that pair, and the fare
   beside the amber button is the fare for THAT pair. One CTA for the whole
   stub — the form submits, the receipt's "Issue the ticket" is the same
   destination (the booking modal), so the two never compete.

   The form is drawn as the routing block printed on a real e-ticket: a
   3x3 grid whose columns are FROM | notch | TO and whose rows are label,
   value, resolved name. Not two stacked inputs and not a rounded search card.
   The middle column is the perforation the two halves are joined at, and the
   swap control sits in it as the notch you would tear along.

   Palette declared locally for the same reason as .inst-band and
   .inst-strip: --navy/--amber are redefined by legacy pages that load this
   stylesheet (de/index.html sets --navy:#1a1a2e), so the locked design cannot
   read them. Everything else inherits --i-edge/--i-dash/--i-dim from
   .inst-doc, and the literals below are only the four the component needs to
   stand alone.

   No entry animation. The one motion is the notch rotating on swap, which
   answers the press; it is disabled under prefers-reduced-motion. */
.fp-ex {
  --i-navy: #1a1a2e; --i-amber: #FFE66D; --i-card: #fff;
  --i-edge: #e2ddcf; --i-dash: #d7d2c2; --i-dim: #67624f; --i-hi: #c0392b;
  --i-mono: "JetBrains Mono", ui-monospace, "IBM Plex Mono", Menlo, Consolas, monospace;
  position: relative;
  margin: 0 0 16px;
}
/* Three columns (FROM | notch | TO) x three rows (label, value, name).
   Auto-flow, so the DOM order below IS the visual order: label, notch
   spacer, label / input, notch, input / name, spacer, name. */
.fp-ex-grid {
  display: grid;
  grid-template-columns: 1fr 44px 1fr;
  grid-template-rows: auto auto auto;
  align-items: center;
}
.fp-ex-lab {
  font: 600 10px/1 var(--i-mono);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--i-dim); margin: 0 0 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fp-ex-in {
  width: 100%; min-height: 46px; margin: 0; padding: 8px 9px;
  appearance: none; -webkit-appearance: none;
  border: 1px solid var(--i-edge); border-radius: 4px;
  background: var(--i-card); color: var(--i-navy);
  font: 700 16px/1.1 var(--i-mono);
  letter-spacing: .08em; text-transform: uppercase;
}
/* The two halves are one divided block, so the seam is where the perforation
   is: the left field gives up its inline-end border and the notch fills it. */
.fp-ex-grid > .fp-ex-in:nth-of-type(1) { border-inline-end: 0; border-end-start-radius: 0; border-start-end-radius: 0; }
.fp-ex-grid > .fp-ex-in:nth-of-type(2) { border-inline-start: 0; border-end-end-radius: 0; border-start-start-radius: 0; }
.fp-ex-in::placeholder { color: #c6c0b3; font-weight: 600; letter-spacing: .08em; }
.fp-ex-in:focus-visible { outline: 2px solid var(--i-navy); outline-offset: 1px; border-color: var(--i-navy); }
.fp-ex-in[aria-invalid="true"] { border-color: var(--i-hi); box-shadow: inset 2px 0 0 var(--i-hi); }
.fp-ex-name {
  display: block; margin-top: 6px; min-height: 14px;
  font-size: 10.5px; line-height: 1.3; color: var(--i-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fp-ex-gap { display: block; }
/* ---- the fare hint (row 4 of the route grid) ----------------------------
   Owner (2026-10-09): "we hint that the airport will cost a little". The
   buy CTA already repaints to the real fare the moment both codes parse
   (fp-express.js paintBuyPrice → fpAirportPrice: named airports $7–$13,
   unlisted pairs hashed $7–$14), so a traveller who types "FRA" watches the
   price leave the $7 floor with no explanation. This says it before they
   type, in the ticket's own mono. The grid places it: `grid-column: 1/-1`
   makes it the fourth row, directly under the route, above Date. EN-only,
   exactly like the STRIPE-SECURED line under the buy label — same accepted
   precedent (keys like booking.consent_text never reached the dictionaries).
   NB the $7–$13 / $7–$14 ranges come from AIRPORT_PRICES and fpPairPrice
   in app-core-en.js; keep this text in step if those tables move. */
.inst .stub .fp-ex-grid::after {
  content: "Route fare: $7 city-level, up to $14 with airports";
  grid-column: 1 / -1;
  margin-top: 9px;
  font: 700 9.5px/1.5 var(--i-mono, "JetBrains Mono"), ui-monospace, monospace;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--i-dim); text-align: left;
}
/* ---- the essential fields (row 2, injected by fp-express.js) -------------
   Date keeps the mono/uppercase treatment — it is ticket data. Name and
   email are the traveller's own writing, so they take the UI sans at
   normal case; the mono codes stay reserved for what a machine reads.
   The aside column is narrow, so Date+Name share a line and Email spans. */
.fp-ex-row2 {
  display: grid;
  /* Even halves. The old minmax(0,1fr) minmax(0,1.5fr) made Date and Name
     unequal for no reason the form can explain; Date | Name now align on
     their own column edges, and Email keeps the full-width row. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px 12px;
  margin-top: 12px;
}
.fp-ex-f2wrap { min-width: 0; }
.fp-ex-row2 .fp-ex-f2wrap:nth-child(3) { grid-column: 1 / -1; }
.fp-ex-row2 .fp-ex-in { margin-top: 0; }
/* ---- phone: the route stacks, the notch turns horizontal ----------------
   Two 160px half-fields are unreadable at 390px, so the pair becomes
   From above To and the swap moves out of its own column onto its own row.
   `order` does the work because the DOM order is the desktop grid's order
   (label, gap, label / from, swap, to / name, gap, name); the gap spans are
   the decorative perforation and are hidden here rather than reordered. */
@media (max-width: 560px) {
  .fp-ex-grid { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; }
  /* Address the halves by what identifies them, not by position: the three
     rows of children are label/gap/label, from/swap/to, name/gap/name, and
     the two name spans share their tag with the decorative gaps, so
     :nth-of-type lands on a gap (measured: the From name printed above its
     own field). The `for` and `id` hooks are what the markup and
     fp-express.js already agree on — all 2,323 pages carrying a
     .fp-ex-grid carry both. */
  .fp-ex-grid > .fp-ex-lab[for="inline-from"] { order: 1; margin-top: 10px; }
  .fp-ex-grid > .fp-ex-in:nth-of-type(1) { order: 2; }
  .fp-ex-grid > #fp-ex-name-from { order: 3; }
  .fp-ex-grid > .fp-ex-swap { order: 4; justify-self: center; margin: 4px 0; }
  .fp-ex-grid > .fp-ex-lab[for="inline-to"] { order: 5; }
  .fp-ex-grid > .fp-ex-in:nth-of-type(2) { order: 6; }
  .fp-ex-grid > #fp-ex-name-to { order: 7; }
  .fp-ex-grid::after { order: 8; }
  .fp-ex-gap { display: none; }
  /* the seam only makes sense when the halves are side by side; restore the
     full outline the two halves gave up to it (the guide stub overrides
     this back to its printed dashed blank right below, at higher
     specificity). */
  .fp-ex-grid > .fp-ex-in:nth-of-type(1),
  .fp-ex-grid > .fp-ex-in:nth-of-type(2) {
    border: 1px solid var(--i-edge); border-radius: 4px;
  }
}
/* the guide stub keeps its printed blanks when the route stacks: a boarding
   pass underlines the route, it does not box it. Three classes deep so it
   wins over the base stacking rule above (equal specificity, later file
   position would otherwise decide it). */
@media (max-width: 560px) {
  .inst .stub .fp-ex-grid .fp-ex-in:not(.fp-ex-in--human) {
    border: 0; border-bottom: 2px dashed var(--dash, #d7d2c2); border-radius: 0;
  }
}
.fp-ex-in--human {
  font: 600 15px/1.2 Outfit, sans-serif;
  letter-spacing: .01em;
  text-transform: none;
}
.fp-ex-in[type="date"] { color-scheme: light; }
body.dark-mode .fp-ex-in[type="date"] { color-scheme: dark; }
.fp-ex-consent {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 10px; cursor: pointer;
  font: 500 11.5px/1.45 Outfit, sans-serif;
  color: var(--i-dim);
}
.fp-ex-consent input {
  width: 16px; height: 16px; margin: 1px 0 0; flex-shrink: 0;
  accent-color: var(--i-navy); cursor: pointer;
}
.fp-ex-consent a { color: var(--i-navy); font-weight: 700; }
.fp-ex-consent:focus-within { color: var(--i-navy); }
@media (max-width: 420px) {
  .fp-ex-row2 { grid-template-columns: minmax(0, 1fr); }
  .fp-ex-row2 .fp-ex-f2wrap:nth-child(3) { grid-column: auto; }
}
/* ---- the notch: swap ---------------------------------------------------- */
.fp-ex-swap {
  align-self: stretch; justify-self: center;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; min-height: 46px; margin: 0; padding: 0;
  appearance: none; cursor: pointer;
  border: 1px dashed var(--i-dash); border-radius: 50%;
  background: #faf8f4; color: var(--i-navy);
  transition: transform .28s cubic-bezier(.4, 1.4, .5, 1), background .15s, color .15s;
}
.fp-ex-swap svg { width: 16px; height: 16px; display: block; }
.fp-ex-swap:hover { background: var(--i-navy); color: var(--i-amber); border-color: var(--i-navy); }
.fp-ex-swap:focus-visible { outline: 2px solid var(--i-navy); outline-offset: 2px; }
.fp-ex-swap.is-swapped { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .fp-ex-swap { transition: background .15s, color .15s; }
  .fp-ex-swap.is-swapped { transform: none; }
}
/* ---- the list ----------------------------------------------------------- */
/* Anchored to the field by fp-express.js (the stub is the offset parent and
   the list has to clear the perforation on the right, so logical properties
   are not enough here). */
.fp-ex-pop {
  display: none; position: absolute; z-index: 40;
  margin-top: 4px; padding: 0;
  min-width: 196px;
  border: 1px solid var(--i-navy); border-radius: 4px;
  background: var(--i-card);
  box-shadow: 3px 3px 0 rgba(26, 26, 46, .16);
}
.fp-ex-pop.is-open { display: block; }
.fp-ex-list {
  list-style: none; margin: 0; padding: 0;
  max-height: 232px; overflow-y: auto; overscroll-behavior: contain;
}
.fp-ex-opt {
  display: grid; grid-template-columns: 3.6em minmax(0, 1fr); align-items: baseline; gap: 8px;
  min-height: 44px; padding: 9px 10px; cursor: pointer;
  border-bottom: 1px solid #f0ece3;
}
.fp-ex-opt:last-child { border-bottom: 0; }
.fp-ex-opt b { font: 700 13px/1.2 var(--i-mono); letter-spacing: .06em; color: var(--i-navy); }
.fp-ex-opt span {
  font-size: 12.5px; line-height: 1.3; color: var(--i-dim);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Keyboard highlight and hover are two different states on purpose: the
   amber wash + navy rail is the arrow-key cursor (aria-selected), a plain
   paper tint is a pointing-device hover — so the listbox cursor stays
   findable after a mouse wiggle. The code chip on the active row turns
   amber-on-navy (ticket language) instead of the legacy alarm red. */
.fp-ex-opt.is-active { background: rgba(255,230,109,.24); box-shadow: inset 3px 0 0 var(--i-navy); }
.fp-ex-opt.is-active b { color: var(--i-navy); background: var(--i-amber); border-radius: 3px; padding: 1px 4px; }
.fp-ex-opt:hover { background: #faf8f4; }
/* ---- submit ------------------------------------------------------------- */
/* The form has no button of its own: the stub's existing "Issue the ticket"
   is the only buy action, and a second one in the same 296px column would be
   two prices to compare. Enter submits from either field; the notch submits
   too, because swapping and then pressing Enter is the whole gesture. */
.fp-ex-hint { margin: 10px 0 0; font-size: 11px; line-height: 1.45; color: var(--i-dim); }
.fp-ex-hint:empty { display: none; }
/* Visually hidden live region for the search/validation announcements. The
   stub already prints a status line (#fp-truth), so this must not be seen. */
.fp-ex-sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ==========================================================================
   STUB SHELL — shared so the 41 locale homepages can host the express form.
   ==========================================================================
   On the EN homepage these rules live in an inline <style id="inst-frame">
   block that also carries legacy CSS; the locale homepages do not load that
   block, so the stub rendered unstyled there and the form could not ship.
   Duplicating the layout rules here (scoped, palette-namespaced) lets a
   locale page wrap its existing #fp-pair in .inst-split/.inst-stub and get
   the same perforated two-column ticket. EN keeps its inline copy — the
   specificity is identical, the declarations agree, and un-duplicating EN is
   a refactor with regression risk and no user-visible gain.

   One token this shell asks for had no definition to fall back on: --i-grey,
   the colour of the arrow between the two airport codes. The instrument
   palette names it on .inst-doc, a wrapper the locale homepages and /widget/
   do not ship — so there the declaration was invalid at computed-value time
   and the glyph rendered from a literal (on /widget/, which resolves the same
   name through inheritance, it came out navy). It is declared below, on
   .inst-stub itself, with the palette's own #67624f — the dim ink EN renders. */

/* the two-column ticket: main column | dashed perforation | stub */
.inst-split{display:grid;grid-template-columns:minmax(0,1fr) 296px;align-items:start}
.inst-main{padding:clamp(18px,3vw,28px);min-width:0}
.inst-stub{--i-grey:#67624f;
  border-inline-start:1px dashed var(--i-dash,#d7d2c2);
  background:#faf8f4;position:relative;
  padding:clamp(18px,3vw,26px) clamp(14px,2.2vw,20px);align-self:stretch}
.inst-stub::before{content:"";position:absolute;inset-inline-start:-9px;top:0;bottom:0;width:9px;
  background:repeating-linear-gradient(180deg,var(--i-dash,#d7d2c2) 0 5px,transparent 5px 11px);opacity:.7}
@media(max-width:880px){
  .inst-split{grid-template-columns:1fr}
  .inst-stub{border-inline-start:0;border-top:1px dashed var(--i-dash,#d7d2c2)}
  .inst-stub::before{display:none}
}

/* receipt skin: the locale #fp-pair ships with a legacy dark block style;
   inside the stub it must read as ink on the same paper as EN's stub */
.inst-stub #fp-pair{background:transparent!important;color:var(--i-navy,#1a1a2e)!important;
  border:0!important;box-shadow:none!important;padding:0!important;margin:0!important}
.inst-stub .fp-pair-fact{color:var(--i-dim,#67624f);margin:0 0 14px;font-size:11px;letter-spacing:.06em}
.inst-stub .fp-codes{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:8px;margin:0}
.inst-stub .fp-code{display:grid;gap:2px;text-align:center;min-width:0}
/* line-height 1 on a 30px mono clipped 5px off the digits (measured at every
   width): the glyphs are taller than the em box at this size, and 1.15 still
   left 2px, so the box gets the full 1.3. Kept HERE, not in the generated
   widget page, because build-widget-page.cjs re-inlines this rule and quietly
   reverted the fix the first time it ran. */
.inst-stub .fp-code b{font:800 30px/1.3 var(--i-mono,"JetBrains Mono",ui-monospace,monospace);letter-spacing:1px;color:var(--i-navy,#1a1a2e);display:block}
.inst-stub .fp-code i{font-style:normal;font-size:10.5px;line-height:1.3;color:var(--i-dim,#67624f);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.inst-stub .fp-arrow{color:var(--i-grey,#67624f)}
.inst-stub .fp-truth{font-size:12.5px;line-height:1.5;color:var(--i-dim,#67624f);margin:14px 0 0;
  padding:9px 10px;background:#f6f4ef;border-inline-start:3px solid var(--i-dash,#d7d2c2);border-radius:0 4px 4px 0}
.inst-stub .fp-issue{display:grid;gap:8px;margin:14px 0 0;padding:12px;border:1px solid var(--i-edge,#e2ddcf);
  border-radius:6px;background:var(--i-card,#fff)}
.inst-stub .fp-fare{font:800 26px/1 var(--i-mono,"JetBrains Mono",ui-monospace,monospace);color:var(--i-navy,#1a1a2e)}
.inst-stub .fp-fare-note{font-size:11px;line-height:1.45;color:var(--i-dim,#67624f)}
.inst-stub .fp-issue-btn{width:100%;font:800 15px/1.2 inherit;padding:12px;border:0;border-radius:12px;
  background:var(--i-navy,#1a1a2e);color:var(--i-amber,#FFE66D);cursor:pointer}
.inst-stub .fp-issue-btn:hover{background:#000;color:#fff}
.inst-stub .fp-issue-btn:focus-visible{outline:2px solid var(--i-navy,#1a1a2e);outline-offset:2px}

/* ==========================================================================
   FIXED BUY BAR — the guide's ONE persistent CTA (guide-facts-first.cjs v2)
   ==========================================================================
   Why it exists: the buy panel is the guide's product, and on a phone it landed
   at 11,701px of a 12,702px page — 92% of the way down. Before this, the only
   `position: fixed` elements on a guide were the modals.

   What the owner asked for, and what changed (2026-10-12): the sticky
   right-rail ticket read as "not responsive and distracting", but a FIXED,
   ANIMATED buy CTA was still wanted. So the split is deliberate — the rail
   panel is now static (never sticky, never blinking; see .inst .stub and
   #inst-stub-h below) and this bar is the only thing that follows the reader.

   Skin rules behind the numbers:
   - ≤720px: full-width bar in the thumb zone, safe-area padded, dismiss on the
     right where a thumb reaches it. Body gets 68px of padding so the footer
     never hides under it.
   - ≥721px: the same components as a floating capsule pinned bottom-right
     (inline-end, so it flips under RTL), max 560px wide, so the bar stops
     being a band across the whole screen on a desktop. No body padding needed:
     it floats over the margin instead of over content.
   - Motion: transform-only entry (.26s), plus a two-beat amber pulse on the
     button when the bar first appears — the animation the owner asked for, and
     it STOPS. Eternal blinking is what made the old mockup distracting.
   - Price + "⚡ Delivered < 1 min" (banner.chip_fast, already translated in all
     42 dictionaries) is the whole pitch: what it costs, how fast it arrives.
     The price span is the shared data-instrument="price" hook, so the existing
     price code repaints it in the visitor's own currency.
   - Disappears in print; 44px targets; visible focus rings. */
#fp-buybar {
  position: fixed; inset-inline: 0; inset-block-end: 0; z-index: 900;
  display: flex; align-items: center; gap: clamp(8px, 2vw, 14px);
  padding: 10px clamp(12px, 3vw, 20px);
  padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  background: #1a1a2e; color: #fff;
  border-top: 3px solid #FFE66D;
  box-shadow: 0 -2px 14px rgba(26, 26, 46, .28);
  transform: translateY(105%);
  transition: transform .26s cubic-bezier(.22, .68, .3, 1);
}
#fp-buybar[hidden] { display: none; }
#fp-buybar.on { transform: translateY(0); }
#fp-buybar .bb-tag { flex: none; font-size: 17px; line-height: 1; }
#fp-buybar .bb-price {
  flex: none;
  font: 900 17px/1 "JetBrains Mono", ui-monospace, Consolas, monospace;
  color: #FFE66D; font-variant-numeric: tabular-nums;
}
#fp-buybar .bb-sub {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: 700 11.5px/1.3 inherit; letter-spacing: .01em;
  color: rgba(255, 255, 255, .74);
}
#fp-buybar .bb-btn {
  appearance: none; cursor: pointer; margin-inline-start: auto;
  min-height: 44px; padding: 0 20px;
  background: #FFE66D; color: #1a1a2e;
  border: 1.5px solid #1a1a2e; border-radius: 12px;
  font: 900 15px/1 inherit; white-space: nowrap;
  box-shadow: 2px 2px 0 rgba(0, 0, 0, .35);
  transition: transform .12s ease, box-shadow .12s ease;
}
#fp-buybar .bb-btn:hover { transform: translate(-1px, -1px); box-shadow: 3px 3px 0 rgba(0, 0, 0, .35); }
#fp-buybar .bb-btn:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 rgba(0, 0, 0, .35); }
#fp-buybar .bb-btn:focus-visible,
#fp-buybar .bb-x:focus-visible { outline: 2px solid #FFE66D; outline-offset: 2px; }
#fp-buybar .bb-x {
  appearance: none; cursor: pointer; flex: none;
  min-width: 44px; min-height: 44px;
  background: none; color: rgba(255, 255, 255, .72);
  border: 1.5px solid rgba(255, 255, 255, .28); border-radius: 8px;
  font-size: 20px; line-height: 1;
}
#fp-buybar .bb-x:hover { color: #fff; border-color: rgba(255, 255, 255, .6); }
/* Keep the footer from sitting under the bar while it is on screen. */
body.fp-buybar-on { padding-bottom: 68px; }
/* The entry pulse: exactly two beats (2 × 1.5s), then the bar is still. */
@keyframes fp-bar-nudge {
  0%, 100% { box-shadow: 2px 2px 0 rgba(0, 0, 0, .35); }
  45%      { box-shadow: 0 0 0 7px rgba(255, 230, 109, .30); }
}
/* .nudge is set by the controller on the FIRST appearance only, so a reader who
   scrolls past an in-flow buy panel and back does not get a fresh pulse every
   time — the animation is a pointer, not a tic. */
@media (prefers-reduced-motion: no-preference) {
  #fp-buybar.nudge .bb-btn { animation: fp-bar-nudge 1.5s ease-out 2; }
}
/* Below 430px the row cannot hold price + proof line + button + dismiss without
   ellipsing the proof line (measured at 390px: it rendered as "Delive…", which
   reads as a bug, not a claim). The hero already states "delivered to your inbox
   in under 60 seconds", so on a phone the bar keeps only what the thumb needs. */
@media (max-width: 429px) {
  #fp-buybar .bb-sub { display: none; }
  #fp-buybar { gap: 10px; }
}
@media (min-width: 721px) {
  #fp-buybar {
    inset-inline: auto; inset-inline-end: 18px; inset-block-end: 16px;
    max-width: min(560px, calc(100vw - 36px));
    border: 2px solid #FFE66D; border-radius: 999px;
    padding: 8px 12px 8px 16px;
    box-shadow: 0 10px 30px rgba(26, 26, 46, .34);
    transform: translateY(160%);
  }
  body.fp-buybar-on { padding-bottom: 0; }
}
@media (prefers-reduced-motion: reduce) {
  #fp-buybar { transition: none; }
  #fp-buybar .bb-btn { transition: none; animation: none; }
}
@media print {
  #fp-buybar { display: none !important; }
  body.fp-buybar-on { padding-bottom: 0; }
}

/* ---- "Protected by Stripe" badge -----------------------------------------
   Injected by the #fp-pair controller (app-core) after the buy button; kept
   in the shared sheet so all 42 stubs get it from one declaration block.
   Instrument look: paper pill, edge border, mono, amber shield tile. The only
   animation is a slow amber pulse on the shield — assurance, not a disco —
   and it is switched off under prefers-reduced-motion. */
.fp-stripe-badge{display:flex;align-items:center;justify-content:center;gap:7px;
  margin:2px 0 0;padding:7px 10px;border:1px dashed var(--i-dash,#d7d2c2);border-radius:6px;
  background:#faf8f4;font:600 11px/1 var(--i-mono,"JetBrains Mono",ui-monospace,monospace);
  letter-spacing:.03em;color:var(--i-dim,#67624f)}
.fp-stripe-badge b{color:var(--i-navy,#1a1a2e);font-weight:800}
.fp-stripe-shield{display:inline-flex;align-items:center;justify-content:center;
  width:18px;height:18px;flex:none;border-radius:5px;
  background:var(--i-amber,#FFE66D);border:1.5px solid var(--i-navy,#1a1a2e);color:var(--i-navy,#1a1a2e)}
.fp-stripe-shield svg{width:10px;height:10px;display:block}
@media(prefers-reduced-motion:no-preference){
  .fp-stripe-shield{animation:fpStripePulse 2.6s ease-in-out infinite}
}
@keyframes fpStripePulse{0%,100%{box-shadow:0 0 0 0 rgba(255,230,109,.0)}50%{box-shadow:0 0 0 4px rgba(255,230,109,.45)}}
@media(prefers-reduced-motion:reduce){.fp-stripe-shield{animation:none}}

/* ---- fp-fly — "Flying to …" arrival banner on country guides -------------
   Stamped at the head of each guide's content window (band / sr-only h1 /
   first alert) by scripts/add-guide-banner.cjs; the copy (and the transit
   name inside the badge) come from guide-banner-copy.json and
   Intl.DisplayNames. 2026-10-20 redesign per owner testing: bigger, calmer,
   INSIDE the content window. Two-lane card: the ✈ plane glyph lives in its
   own left lane and pulses (stays visible through the whole cycle), while
   the COUNTRY NAME fades in a slower two-beacon cycle — much softer than
   the old hard steps() blink, so country name and FlightProof line stay
   readable at every point of the animation. prefers-reduced-motion kills
   both animations outright (badge freezes at full opacity). Palette: guide
   pages may define --navy/--yellow (styles.css); instrument pages don't,
   hence the --i-* fallbacks; the red stays banner-local and loud by design. */
.fp-fly{display:flex;align-items:center;gap:0 16px;
  margin:0 0 20px;padding:16px 20px;border:2px solid rgba(225,29,72,.30);
  border-left:6px solid #E11D48;border-radius:14px;background:linear-gradient(180deg,#fff5f1,#fff9f7);
  box-sizing:border-box}
.fp-fly-lane{flex:none;width:52px;height:52px;display:flex;align-items:center;justify-content:center;
  border-radius:50%;background:rgba(225,29,72,.10);font-size:30px;color:#E11D48}
@media(prefers-reduced-motion:no-preference){
  .fp-fly-plane{animation:fpFlyPlane 2.4s ease-in-out infinite}
}
@keyframes fpFlyPlane{0%,100%{opacity:.35;transform:translateX(-3px)}50%{opacity:1;transform:translateX(3px)}}
.fp-fly-main{flex:1 1 260px;min-width:0}
.fp-fly-blink{display:block;color:#E11D48;
  font:900 22px/1.15 var(--i-sans,Outfit,'DM Sans',sans-serif);letter-spacing:.005em}
@media(prefers-reduced-motion:no-preference){
  .fp-fly-blink{animation:fpFlyBlink 2.4s ease-in-out infinite}
}
@keyframes fpFlyBlink{0%,42%,100%{opacity:1}55%,87%{opacity:.34}}
@media(prefers-reduced-motion:reduce){.fp-fly-blink{animation:none;opacity:1}.fp-fly-plane{animation:none}}
.fp-fly-sub{display:block;margin-top:3px;color:var(--i-navy,#1a1a2e);
  font:600 15.5px/1.4 var(--i-sans,'DM Sans',sans-serif)}
.fp-fly-cta{flex:none;display:inline-flex;align-items:center;min-height:48px;
  padding:10px 22px;border-radius:12px;background:var(--i-navy,#1a1a2e);
  border:2px solid var(--i-amber,var(--yellow,#FFE66D));color:#fff;text-decoration:none;
  font:800 15px/1.1 var(--i-sans,Outfit,sans-serif);letter-spacing:.01em;
  transition:transform .12s,box-shadow .12s}
.fp-fly-cta:hover,.fp-fly-cta:focus-visible{transform:translate(-2px,-2px);outline:none;
  box-shadow:3px 3px 0 rgba(225,29,72,.45)}
@media(max-width:560px){
  .fp-fly{flex-wrap:wrap;gap:10px 14px;padding:14px 16px}
  .fp-fly-lane{width:40px;height:40px;font-size:22px}
  .fp-fly-blink{font-size:19px}
  .fp-fly-sub{font-size:14px}
  .fp-fly-cta{width:100%;justify-content:center}
}

/* the "issued in under a minute" status pill (base rule lives inline on EN;
   locale pages need the whole thing here) */
.inst-status{display:inline-flex;align-items:center;gap:6px;margin:14px 0 0;
  padding:7px 12px;border-radius:50px;font-size:11px;font-weight:800;letter-spacing:.04em;
  background:var(--i-navy,#1a1a2e);color:var(--i-amber,#FFE66D)}
.inst-status::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--i-amber,#FFE66D)}

/* fine print under the pill */
.inst-fine{font-size:11.5px;line-height:1.5;color:var(--i-dim,#67624f);margin:10px 0 0}

/* ==========================================================================
   PICK-ON-MAP — "Pick on map" fills the express form's TO field
   ==========================================================================
   The map is the destination picker the brief asked for: a country pin on the
   Instrument map resolves to that country's main departure airport
   (map-data.json iso → app-core COUNTRY_MAIN_AIRPORT) and writes it into
   #inline-to. The map component emits a custom DOM event so it stays decoupled
   from the form: instrument-map.js dispatches `fp:mappick` on document with
   {slug, iso, airport, name}; fp-express.js listens when a #fp-express form
   exists. No coupling in either direction beyond the event contract. */
/* The .fp-ex-pick button and its four rules were removed 2026-10-25 (owner: a
   "Choose a destination" control under the BUY button reads as a competing
   action). The event contract it used to start still exists — see the
   body.fp-picking block below — but nothing in the form enters that mode now. */
/* While picking, the map IS the picker and has to say so: the plate reads as
   clickable (crosshair), country stubs take an amber dashed ring, and the amber
   code plate pulses — the one element on a stub that is already the e-ticket
   accent. These rules used to name .inst-map-pin, a layer the region rework
   deleted when pins became stubs, so pick mode had no visible state anywhere and
   the button read as dead. The land paths carry the cursor too: at world level
   the first step of a pick is still "click your continent". */
body.fp-picking .inst-map-land,
body.fp-picking .inst-map-box{cursor:crosshair}
body.fp-picking .inst-map-box{border-style:dashed;border-color:var(--amber,#FFE66D);
  box-shadow:0 0 0 3px rgba(255,230,109,.30)}
body.fp-picking .inst-map-box-code{animation:fpPickPulse 1.1s ease-in-out infinite}
body.fp-picking .inst-map-stage{outline-color:rgba(255,230,109,.85)}
@keyframes fpPickPulse{0%,100%{opacity:1}50%{opacity:.4}}
@media(prefers-reduced-motion:reduce){body.fp-picking .inst-map-box-code{animation:none}}

/* ==========================================================================
   PARTNER STRIP — one quiet line, not a marketing band
   ==========================================================================
   The owner removed the old 1,563-byte partner slide from the conversion path
   and wants the programme reachable but minimal: one Instrument-styled strip
   after the ticket, mono kick label, one sentence, one link to /partners/.
   Copy keys exist in 13 locale dictionaries; the other 28 fall back to inline
   English (acceptable: the strip is one line, and check-i18n-keys --strict
   only fails keys missing from a dictionary, which these are not for the 13
   that carry them — the rest resolve at runtime). */
.fp-pslide{margin:26px 0 0;padding:14px clamp(14px,2vw,20px);
  border:1px solid var(--i-edge,#e2ddcf);border-radius:6px;background:#faf8f4;
  display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px}
.fp-pslide .kick{margin:0;font:800 10.5px/1 var(--i-mono,"JetBrains Mono",ui-monospace,monospace);
  letter-spacing:.14em;color:var(--i-navy,#1a1a2e)}
.fp-pslide p{margin:0;font-size:12.5px;line-height:1.45;color:var(--i-dim,#67624f);flex:1 1 240px;min-width:0}
.fp-pslide a{white-space:nowrap;font-weight:800;font-size:12.5px;color:var(--i-navy,#1a1a2e);
  text-decoration:underline;text-underline-offset:3px}
.fp-pslide a:hover,.fp-pslide a:focus-visible{color:#000}

/* ==========================================================================
   DEMO E-TICKET — the old homepage boarding-pass animation, reborn in the
   Instrument palette and sized to sit under the express stub.
   ==========================================================================
   What the customer actually receives IS the proof, so the sample ticket is
   the section: white card on paper, navy frame, amber E-TICKET badge, mono
   figures, green CONFIRMED stamp, perforated stub with punched notches. The
   card flips on a slow CSS cycle to the check-in promise, then back. No JS
   drives the flip; the only script mirrors whatever the visitor types into
   the express form onto the sample codes, so the demo feels live.
   Reduced motion: no flip, no plane, no sheen — the front stays put and the
   check-in line appears as a static caption instead. */
.inst-tk-sec{max-width:726px;margin:30px auto 0;padding:0 clamp(14px,3vw,20px)}
/* A heavy 2px navy border on a white card inside a white page read as a dark
   slab out of nowhere — the heaviest line on the whole ticket. The spine,
   the perforation and the barcode are the structural ink on an e-ticket; the
   card itself is paper. 1.5px at the edge color, weight carried by the stub
   perforation and the barcode instead. */
.inst-tk-front{display:flex;align-items:stretch;background:#fff;border:1.5px solid var(--i-edge,#e2ddcf);
  border-radius:10px;overflow:hidden;position:relative;text-align:start;
  box-shadow:3px 3px 0 rgba(26,26,46,.08)}
.inst-tk-cap{margin:0 0 12px;font:800 10.5px/1.4 var(--i-mono,"JetBrains Mono",ui-monospace,monospace);
  letter-spacing:.14em;color:var(--i-dim,#67624f);text-align:center}
.inst-tk-scene{perspective:1400px;transform:rotate(-.6deg)}
.inst-tk-flip{position:relative;transform-style:preserve-3d;animation:instTkFlipOnce 6s ease-in-out 1 both}
.inst-tk-face{backface-visibility:hidden;-webkit-backface-visibility:hidden}
/* the light sweep — one quiet pass, the only non-functional motion on the card */
.inst-tk-front::after{content:"";position:absolute;top:0;bottom:0;width:42px;left:-70px;
  background:linear-gradient(105deg,transparent,rgba(26,26,46,.07),transparent);
  transform:skewX(-18deg);animation:instTkSheen 5.5s ease-in-out infinite;pointer-events:none}
.inst-tk-main{flex:1;padding:14px 18px 12px;min-width:0}
.inst-tk-head{display:flex;align-items:center;gap:8px;font-size:10.5px;font-weight:900;
  letter-spacing:1.6px;color:var(--i-navy,#1a1a2e);margin-bottom:9px}
.inst-tk-logo{width:19px;height:19px;border-radius:5px;background:var(--i-amber,#FFE66D);
  border:1.5px solid var(--i-navy,#1a1a2e);display:inline-flex;align-items:center;justify-content:center;
  font-size:10px;color:var(--i-navy,#1a1a2e);flex:none}
.inst-tk-bp{margin-inline-start:auto;background:var(--i-amber,#FFE66D);border:1.5px solid var(--i-navy,#1a1a2e);
  border-radius:5px;padding:2px 8px;font:800 8.5px/1 var(--i-mono,"JetBrains Mono",ui-monospace,monospace);letter-spacing:1px}
.inst-tk-route{display:flex;align-items:center;gap:12px}
.inst-tk-ap{display:block;min-width:0}
.inst-tk-ap b{display:block;font:800 clamp(24px,4.2vw,32px)/1 var(--i-mono,"JetBrains Mono",ui-monospace,monospace);
  letter-spacing:1px;color:var(--i-navy,#1a1a2e)}
.inst-tk-ap i{display:block;font-style:normal;font-size:10.5px;font-weight:700;color:var(--i-dim,#67624f);margin-top:2px;white-space:nowrap;max-width:150px;overflow:hidden;text-overflow:ellipsis}
.inst-tk-ap-r{text-align:end}
.inst-tk-line{flex:1;position:relative;height:2px;min-width:70px;
  background:repeating-linear-gradient(90deg,var(--i-navy,#1a1a2e) 0 7px,transparent 7px 13px)}
.inst-tk-line::before,.inst-tk-line::after{content:"";position:absolute;top:50%;width:6px;height:6px;
  border:2px solid var(--i-navy,#1a1a2e);border-radius:50%;background:#fff;transform:translateY(-50%)}
.inst-tk-line::before{inset-inline-start:-4px}.inst-tk-line::after{inset-inline-end:-4px}
.inst-tk-plane{position:absolute;top:50%;inset-inline-start:0;font-size:17px;color:var(--i-navy,#1a1a2e);
  transform:translate(-8px,-55%);animation:instTkFly 3.2s ease-in-out infinite}
.inst-tk-meta{display:flex;align-items:center;gap:16px;margin-top:11px;font-size:8.5px;font-weight:800;
  letter-spacing:1px;color:var(--i-dim,#67624f);flex-wrap:wrap}
.inst-tk-meta b{display:block;font:800 13px/1.2 var(--i-mono,"JetBrains Mono",ui-monospace,monospace);
  color:var(--i-navy,#1a1a2e);letter-spacing:.6px;margin-top:2px}
.inst-tk-pnr{color:var(--i-dim,#67624f)!important;letter-spacing:2px!important}
/* perforated stub with punched notches */
.inst-tk-side{width:106px;flex:none;border-inline-start:2px dashed var(--i-navy,#1a1a2e);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;
  padding:10px 8px;position:relative}
.inst-tk-side::before,.inst-tk-side::after{content:"";position:absolute;inset-inline-start:-9px;width:14px;height:14px;
  border-radius:50%;background:var(--i-paper,#ece8df);border:2px solid var(--i-navy,#1a1a2e)}
.inst-tk-side::before{top:-9px}.inst-tk-side::after{bottom:-9px}
.inst-tk-stamp{font-size:11.5px;font-weight:900;letter-spacing:1.5px;color:var(--i-navy,#1a1a2e);
  border:2px solid var(--i-navy,#1a1a2e);border-radius:8px;padding:3px 9px;transform:rotate(-8deg);
  background:rgba(26,26,46,.07);animation:instTkStamp 3.6s ease-in-out infinite}
/* The base declaration used to read var(--i-ok,#33335c): --i-ok is #22c55e
   (terminal green) on the homepage scope, which fought the navy/amber lock, and
   the #33335c literal was a violet leftover nothing else on the ticket uses.
   "CONFIRMED" is a brand moment, not a status LED: ink navy with an amber wash,
   same as every other accent on the ticket. So the base rule carries the ink
   token itself — every shipped stamp sits inside one of the three scopes below
   anyway, and no context left can reach the legacy green. */
.inst-stub .inst-tk-stamp,.inst-doc .inst-tk-stamp,.inst-tk-sec .inst-tk-stamp{color:#1a1a2e;border-color:#1a1a2e;background:rgba(255,230,109,.5)}
.inst-tk-bar{width:72px;height:24px;border-radius:2px;background:repeating-linear-gradient(90deg,
  var(--i-navy,#1a1a2e) 0 2px,transparent 2px 4px,var(--i-navy,#1a1a2e) 4px 7px,
  transparent 7px 9px,var(--i-navy,#1a1a2e) 9px 10px,transparent 10px 13px)}
/* flip back: the check-in promise */
.inst-tk-back{position:absolute;inset:0;transform:rotateY(180deg);background:#fff;
  border:2px solid var(--i-navy,#1a1a2e);border-radius:12px;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:14px;text-align:center;padding:22px 26px}
.inst-tk-back .inst-tk-stamp{animation:none;transform:rotate(-4deg)}
.inst-tk-backtxt{margin:0;font-size:14px;line-height:1.55;color:var(--i-dim,#67624f);max-width:420px}
.inst-tk-backtxt strong{color:var(--i-navy,#1a1a2e)}
/* static caption shown only when motion is off — the back face is unreachable then */
.inst-tk-note{display:none;margin:10px 0 0;font-size:12.5px;line-height:1.5;color:var(--i-dim,#67624f);text-align:center}
.inst-tk-note strong{color:var(--i-navy,#1a1a2e)}
/* live-fill flash on the airport codes */
.inst-tk-ap b.is-hit{animation:instTkHit .7s}
@media(prefers-reduced-motion:reduce){.inst-tk-flip{animation:none;transform:none}}
/* The 10s loop spent 48% of its cycle on the BACK face — the marketing
   message — so a visitor landing mid-cycle saw a text card where the ticket
   should be. One flip at load, then a long hold on the front. */
@keyframes instTkFlipOnce{0%{transform:rotateY(180deg)}30%,100%{transform:rotateY(360deg)}}
@keyframes instTkSheen{0%,55%{left:-70px}90%,100%{left:110%}}
@keyframes instTkFly{0%,100%{inset-inline-start:8%}50%{inset-inline-start:88%}}
@keyframes instTkStamp{0%,100%{transform:rotate(-8deg) scale(1)}50%{transform:rotate(-8deg) scale(1.06)}}
@keyframes instTkHit{0%{background:var(--i-amber,#FFE66D)}100%{background:transparent}}
@media(max-width:560px){
  .inst-tk-front{flex-direction:column}
  .inst-tk-side{width:100%;border-inline-start:0;border-top:2px dashed var(--i-navy,#1a1a2e);
    flex-direction:row;gap:16px;padding:9px}
  .inst-tk-side::before{top:-9px;inset-inline-start:-9px}
  .inst-tk-side::after{display:none}
  .inst-tk-line{min-width:50px}
}
@media(prefers-reduced-motion:reduce){
  .inst-tk-flip{animation:none}
  .inst-tk-front::after,.inst-tk-plane,.inst-tk-stamp,.inst-tk-ap b.is-hit{animation:none}
  .inst-tk-back{display:none}
  .inst-tk-note{display:block}
}
[dir="rtl"] .inst-tk-plane{transform:translate(8px,-55%) scaleX(-1)}
/* ── Partner band → Instrument ─────────────────────────────────────────────
   The homepage partner slide (.fp-pslide) is marked up on all 42 homepages
   with per-page inline CSS still carrying the old green/orange gradient.
   This file loads after every page's own <style>, so these equal-specificity
   rules win: navy field, amber accent, mono chip — same language as the
   /partners/ earn section. Country-guide variants that already use navy/amber
   converge to the identical look. */
.fp-pslide{background:#1a1a2e;border-radius:14px;border-top:4px solid #FFE66D;box-shadow:0 18px 44px -18px rgba(0,0,0,.55)}
.fp-pslide::before,.fp-pslide::after{display:none}
.fp-pslide-kick{font-family:ui-monospace,Consolas,monospace;font-size:11px;letter-spacing:2px;color:#FFE66D;border:1.5px solid rgba(255,230,109,.6);border-radius:6px;background:transparent}
.fp-pslide-sub{color:rgba(255,255,255,.75)}
.fp-pslide-chips span{color:#fff;background:transparent;border:1.5px solid rgba(255,255,255,.35)}
.fp-pslide-btn{background:#FFE66D;color:#1a1a2e;border-radius:10px;box-shadow:0 10px 24px -12px rgba(0,0,0,.55)}
.fp-pslide-how{color:#FFE66D}
.fp-pslide-micro{color:rgba(255,255,255,.55)}
.fp-pslide-ui{border:2px solid #FFE66D;border-radius:12px;box-shadow:0 18px 44px -18px rgba(0,0,0,.55)}
.fp-pslide-bar b{background:#ece8df;color:#1a1a2e}
.fp-pslide-bar i,.fp-pslide-bar i:nth-child(1),.fp-pslide-bar i:nth-child(2){background:rgba(26,26,46,.35)}
.fp-pslide-bar i:nth-child(3){background:#FFE66D}
.fp-pslide-bars i{background:rgba(26,26,46,.15)}
.fp-pslide-bars i:last-child{background:#FFE66D;border:1.5px solid #1a1a2e}
/* compact band variant (EN + locale homepages): generic .kick/p/a children.
   html-prefix wins over the locale pages' own late light-band blocks, so every
   homepage renders the identical navy Instrument band. */
html .fp-pslide{background:#1a1a2e;border:0;border-top:4px solid #FFE66D;border-radius:14px;box-shadow:0 18px 44px -18px rgba(0,0,0,.55)}
html .fp-pslide .kick{color:#FFE66D;border:1.5px solid rgba(255,230,109,.6);border-radius:6px;background:transparent;font-family:ui-monospace,Consolas,monospace;letter-spacing:2px}
html .fp-pslide p{color:rgba(255,255,255,.85);font-weight:700}
html .fp-pslide a{display:inline-block;background:#FFE66D;color:#1a1a2e;border-radius:10px;padding:12px 26px;font-family:'Outfit',sans-serif;font-weight:900;text-decoration:none;transition:transform .12s}
html .fp-pslide a:hover{transform:translate(-2px,-2px)}
@media(prefers-reduced-motion:reduce){html .fp-pslide a{transition:none;transform:none}}

/* --- level 2 swap: continent -> country boxes ---------------------------
   Clicking a continent used to just zoom the same silhouette, which read as
   "nothing happened". Now the region view REPLACES the world plate with one
   ticket-stub per guide: white stub, navy border, amber mono code chip — the
   e-ticket language of the demo ticket and the sale receipt. 'All regions'
   swaps the world silhouette back in. */
.inst-map-countries {
  display: none;
}
.inst-map--region .inst-map-countries {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
/* hide the world silhouette while the region's boxes are showing; the plate
   itself stays, so the navy field and dashed perforation are continuous */
.inst-map--region .inst-map-svg { display: none; }
.inst-map--region.inst-map .inst-map-stage { padding: clamp(12px, 2vw, 18px); }

.inst-map-box {
  display: flex; align-items: center; gap: 10px;
  min-height: 46px;                       /* touch target */
  padding: 9px 12px;
  background: var(--card, #fff);
  color: var(--navy, #1a1a2e);
  border: 1.5px solid var(--navy, #1a1a2e);
  border-radius: 8px;
  text-decoration: none;
  font: 600 13.5px/1.3 var(--sans, 'DM Sans', sans-serif);
  transition: transform .12s ease, box-shadow .12s ease;
}
.inst-map-box:hover,
.inst-map-box:focus-visible {
  transform: translate(-2px, -2px);
  box-shadow: 3px 3px 0 var(--amber, #FFE66D);
  outline: none;
}
.inst-map-box-code {
  font-family: var(--mono, ui-monospace, Consolas, monospace);
  font-size: 11px; font-weight: 800; letter-spacing: 1px;
  background: var(--amber, #FFE66D);
  color: var(--navy, #1a1a2e);
  border-radius: 4px;
  padding: 3px 6px;
  display: inline-flex; align-items: center; gap: 6px;
  flex: 0 0 auto;
}
/* The code plate is amber. Tier dots on it are 7px and sit inside a colour of
   their own, so they get a hairline ring to stay delimited at that size. The
   medium dot used to be the plate's own amber and vanished completely (a
   medium country showed no tier mark at all); it now reads --mid like every
   other medium mark on the site. */
.inst-map-box-code .inst-dot { width: 7px; height: 7px;
  box-shadow: 0 0 0 1px rgba(26, 26, 46, .45); }
.inst-map-box-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 640px) {
  .inst-map--region .inst-map-countries { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  .inst-map-box { min-height: 44px; padding: 8px 10px; }
  .inst-map-box-name { white-space: normal; }
}
@media (max-width: 380px) {
  .inst-map--region .inst-map-countries { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .inst-map-box { transition: none; }
  .inst-map-box:hover { transform: none; }
}
/* guides' .barcode spine gets the same brand plate as .inst-barcode — the
   wordmark knockout rides on every spine, not just the homepage's */
.inst .barcode .barcode-brand{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:inline-flex;align-items:center;gap:7px;height:24px;padding:0 12px 0 8px;background:#fff;border:1.5px solid var(--navy,#1a1a2e);border-radius:5px;box-shadow:0 1.5px 0 var(--navy,#1a1a2e);font-family:"Outfit","Segoe UI",system-ui,sans-serif;font-size:12.5px;font-weight:900;letter-spacing:-.01em;color:var(--navy,#1a1a2e);white-space:nowrap}
.inst .barcode .barcode-brand .bb-mark{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:4px;background:var(--amber,#FFE66D);border:1px solid var(--navy,#1a1a2e);font-size:9px;flex:none}
.inst .barcode .barcode-brand .bb-dim{color:#8a8577}
@media(max-width:560px){.inst .barcode .barcode-brand{height:21px;font-size:11px;padding:0 9px 0 6px}.inst .barcode .barcode-brand .bb-mark{width:14px;height:14px;font-size:8px}}

/* --- the language picker + install button --------------------------------
   The white chip carries inline color:var(--navy), which on legacy pages is
   still the OLD GREEN navy (#0C2119 family) — it read as a green button
   floating on the navy footer/band. Paint it Instrument here, unscoped, the
   same way the footer paint reaches every page that loads this sheet. */
#fp-lang-btn{background:#ece8df!important;border-color:rgba(255,230,109,.5)!important;color:#1a1a2e!important}
#fp-lang-btn *{color:#1a1a2e!important}
#fp-lang-dropdown div[style]{color:#1a1a2e!important}
#pwa-install-btn{background:#FFE66D!important;color:#1a1a2e!important;border-color:#1a1a2e!important}

/* ==========================================================================
   DOC PAGES — our-story, faq, terms, privacy, refund, checklist, payments,
   us-* briefs. The Instrument reading page: same e-ticket language as the
   guides (paper, navy ink, amber, mono eyebrows, dashed perforation, hard
   offset shadows) applied to long-form content. Dark mode is intentionally
   not shipped here — the guides do not have it either.
   ========================================================================== */
.inst .doc-head { max-width: 1020px; margin: 0 auto; padding: 42px 20px 26px; }
.inst .doc-kicker {
  display: inline-flex; align-items: center; gap: 8px;
  font: 700 10px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase;
  color: var(--dim); background: var(--card); border: 1px solid var(--edge); padding: 6px 12px;
}
.inst .doc-kicker:before { content: ""; width: 8px; height: 8px; background: var(--amber); border: 1px solid var(--navy); }
.inst .doc-head h1 { font: 900 clamp(28px, 5vw, 46px)/1.12 var(--sans); color: var(--navy); margin: 16px 0 10px; }
.inst .doc-head .dek { font-size: 17px; color: var(--dim); max-width: 62ch; margin: 0; }

/* departures-board strip */
.inst .doc-strip { overflow: hidden; background: var(--navy); padding: 9px 0; white-space: nowrap; }
.inst .doc-strip-track {
  display: inline-block; padding-inline-start: 100%; animation: docScroll 30s linear infinite;
  font: 700 12px/1 var(--mono); letter-spacing: .08em; color: var(--paper);
}
.inst .doc-strip .chip { border: 1px solid rgba(236,232,223,.35); border-radius: 2px; padding: 2px 8px; margin: 0 4px; }
.inst .doc-strip .dot { color: var(--amber); margin: 0 6px; }
.inst .doc-strip .tail { color: var(--amber); margin-inline-start: 8px; }
@keyframes docScroll { to { transform: translateX(-100%); } }
[dir="rtl"] .inst .doc-strip-track { animation-name: docScrollRtl; padding-inline-start: 0; padding-inline-end: 100%; }
@keyframes docScrollRtl { to { transform: translateX(100%); } }

.inst .doc-body { max-width: 1020px; margin: 0 auto; padding: 36px 20px 64px; }
.inst .doc-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; margin: 0 0 40px; }
.inst .doc-stat { background: var(--card); border: 1.5px solid var(--navy); padding: 18px 14px; text-align: center; box-shadow: 4px 4px 0 var(--dash); }
.inst .doc-stat .v { font: 800 26px/1.1 var(--sans); color: var(--navy); font-variant-numeric: tabular-nums; }
.inst .doc-stat .l { font: 700 10px/1.4 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--dim); margin-top: 6px; }

/* timeline as dashed perforation */
.inst .doc-line { position: relative; padding-inline-start: 30px; }
.inst .doc-line:before { content: ""; position: absolute; top: 8px; bottom: 8px; inset-inline-start: 8px; width: 0; border-inline-start: 2px dashed var(--dash); }
.inst .doc-chapter { position: relative; margin-bottom: 40px; }
.inst .doc-chapter:before {
  content: ""; position: absolute; inset-inline-start: -28px; top: 6px; width: 12px; height: 12px;
  background: var(--amber); border: 2px solid var(--navy); box-shadow: 2px 2px 0 rgba(26,26,46,.2);
}
.inst .doc-eyebrow {
  display: inline-block; font: 700 10px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--dim); background: var(--card); border: 1px solid var(--edge); padding: 5px 10px; margin-bottom: 10px;
}
.inst .doc-chapter h2 { font: 800 24px/1.2 var(--sans); color: var(--navy); margin: 0 0 12px; }
.inst .doc-chapter p { font-size: 15.5px; line-height: 1.75; color: var(--dim); margin: 0 0 13px; }
.inst .doc-chapter p strong { color: var(--navy); }

.inst .doc-quote {
  position: relative; background: var(--amber); border: 1.5px solid var(--navy);
  box-shadow: 4px 4px 0 rgba(26,26,46,.16); padding: 20px 22px; margin: 26px 0;
  font-size: 16.5px; line-height: 1.6; color: var(--navy);
}
.inst .doc-values { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 14px; margin: 16px 0 0; }
.inst .doc-card { background: var(--card); border: 1.5px solid var(--navy); padding: 20px; box-shadow: 4px 4px 0 var(--dash); }
.inst .doc-card h3 { font: 800 16px/1.25 var(--sans); color: var(--navy); margin: 0 0 8px; }
.inst .doc-card p { font-size: 13.5px; line-height: 1.65; color: var(--dim); margin: 0; }

.inst .doc-cta {
  background: var(--amber); border: 2px solid var(--navy); box-shadow: 6px 6px 0 rgba(26,26,46,.2);
  padding: 34px 26px; text-align: center; margin: 40px 0 0;
}
/* Scoped through .doc-body deliberately. These two selectors and the doc-body
   typography pair further down (`.inst .doc-body h3` / `.inst .doc-body p`)
   all sit at (0,2,1), and the doc-body ones come later in the sheet — so they
   silently won: the CTA heading rendered at 17px instead of 22px, and
   `margin: 0 auto 18px` was replaced by `margin: 0 0 12px`, leaving the subtext
   flush left, ~195px off the card's centre, on all 333 doc pages. The extra
   class restores the CTA's own type scale and centring. */
.inst .doc-body .doc-cta h3 { font: 800 22px/1.25 var(--sans); color: var(--navy); margin: 0 0 8px; }
.inst .doc-body .doc-cta p { font-size: 14.5px; color: var(--navy); opacity: .85; max-width: 52ch; margin: 0 auto 18px; }
.inst .doc-btn {
  display: inline-block; background: var(--navy); color: var(--amber);
  font: 800 15px var(--sans); padding: 13px 30px; border: 2px solid var(--navy);
  text-decoration: none; box-shadow: 4px 4px 0 rgba(26,26,46,.35);
}
.inst .doc-btn:hover { transform: translate(-1px,-1px); box-shadow: 5px 5px 0 rgba(26,26,46,.35); }
.inst .doc-btn:active { transform: translate(1px,1px); box-shadow: 2px 2px 0 rgba(26,26,46,.35); }
/* The doc CTA row ends with TWO buttons: the primary `.doc-btn` link, plus the
   "🤖 Travel AI" button fp-ai-chat.js appends into the same `.doc-cta`. Both
   are inline-block with a 4px offset shadow and no margin, so they butted
   together at 0px — one navy slab side by side on desktop, and a stacked slab
   with no seam once they wrap on mobile; the first button's shadow hid under
   the second and copied text ran the two labels together. The margin must live
   here rather than on `.fpchat-openbtn`: that button's `margin-top:10px` sits
   in the chat stylesheet, which fp-ai-chat.js only injects inside build() —
   i.e. after the visitor opens the chat — so at load the injected button had
   no margin at all. Symmetric margins give a 14px gap on both axes, keep the
   pair centred while they share a line, and keep each button centred once they
   wrap. */
.inst .doc-cta .doc-btn { margin: 7px; }
/* The SECOND button in that row is the "🤖 Travel AI" one fp-ai-chat.js appends,
   and it carries the same `doc-btn` class, so the pair had identical weight —
   and at ≤720px brand.css gives both `width: 100%`, which measured on
   /travel-checklist in a 602px window as two 496×50 navy slabs stacked under a
   paragraph about the ticket. The assistant is the secondary action, so it
   drops to an outlined button: same box, same hit area, a fraction of the
   weight, and the row reads "buy it, or ask about it" in one glance. */
.inst .doc-cta .fpchat-openbtn {
  background: #fff; color: var(--navy); border-color: var(--navy);
  box-shadow: 3px 3px 0 rgba(26, 26, 46, .18);
}
.inst .doc-cta .fpchat-openbtn:hover { background: var(--amber); color: var(--navy); }
.inst .doc-note { font: 700 10px/1.5 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--dim); margin-top: 14px; }

/* --- story illustrations, step row, honesty panel ------------------------
   /our-story is the one doc page that tells a story, so it gets three things
   the reading pages do not have: a plate for a drawn figure (same paper card,
   navy edge and hard offset shadow as .doc-card, so a drawing sits in the same
   grammar as the text), a numbered three-step row, and a bordered panel for
   the "what this is / isn't" paragraph that a buyer has to be able to find
   without reading the whole page.

   The figures are INLINE SVG on purpose: no extra request, no bitmap to blur at
   2x, and the drawing inherits the page's own palette literals (the e-ticket
   navy/amber/paper). They are decorative-illustrative, so each carries
   role="img" + aria-label and the same sentence in a <figcaption>. */
.inst .doc-illus {
  margin: 18px 0 22px; padding: 16px 16px 12px;
  background: var(--card); border: 1.5px solid var(--navy);
  box-shadow: 4px 4px 0 var(--dash);
}
.inst .doc-illus svg { display: block; width: 100%; height: auto; }
.inst .doc-illus figcaption {
  margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--edge);
  font: 700 9.5px/1.55 var(--mono); letter-spacing: .12em;
  text-transform: uppercase; color: var(--dim);
}

.inst .doc-steps {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px; margin: 20px 0 4px; padding: 0; list-style: none;
}
.inst .doc-step {
  position: relative; background: var(--card); border: 1.5px solid var(--navy);
  padding: 18px 16px 16px; box-shadow: 4px 4px 0 var(--dash);
}
.inst .doc-step .n {
  display: block; margin-bottom: 8px;
  font: 800 10px/1 var(--mono); letter-spacing: .18em; color: var(--dim);
}
.inst .doc-step h3 { font: 800 15.5px/1.25 var(--sans); color: var(--navy); margin: 0 0 7px; }
.inst .doc-step p { font-size: 13.5px; line-height: 1.6; color: var(--dim); margin: 0; }

/* the honesty paragraph: white field, amber rule, no shadow — it must read as a
   statement of record, not as another promo card */
.inst .doc-panel {
  background: var(--card); border: 1.5px solid var(--navy);
  border-inline-start: 6px solid var(--amber);
  padding: 16px 18px; margin: 26px 0 0;
}
.inst .doc-panel h3 { font: 800 12px/1.3 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--navy); margin: 0 0 8px; }
.inst .doc-panel p { font-size: 14px; line-height: 1.7; color: var(--dim); margin: 0; }

@media (max-width: 640px) {
  .inst .doc-head { padding: 30px 16px 20px; }
  .inst .doc-body { padding: 28px 16px 48px; }
  .inst .doc-line { padding-inline-start: 22px; }
  .inst .doc-chapter:before { inset-inline-start: -22px; }
}
@media (prefers-reduced-motion: reduce) { .inst .doc-strip-track { animation: none; } }

/* --- doc body typography (faq/terms/privacy/refund) ----------------------- */
.inst .doc-body h2 { font: 800 22px/1.25 var(--sans); color: var(--navy); margin: 26px 0 10px; }
.inst .doc-body h3 { font: 800 17px/1.3 var(--sans); color: var(--navy); margin: 20px 0 8px; }
.inst .doc-body p { font-size: 15px; line-height: 1.7; color: var(--dim); margin: 0 0 12px; }
.inst .doc-body p strong { color: var(--navy); }
.inst .doc-body ul, .inst .doc-body ol { margin: 0 0 12px; padding-inline-start: 22px; line-height: 1.7; color: var(--dim); }
/* `:not(.doc-btn)` is load-bearing: the doc CTA sits inside .doc-body, and this
   selector (0,2,1) outranks `.inst .doc-btn` (0,2,0) while also coming later in
   the sheet — so it painted the CTA's label navy on the button's own navy
   background, i.e. an invisible primary CTA on all 333 doc pages. Body links
   are unchanged; the button keeps the amber-on-navy from its own rule. */
.inst .doc-body a:not(.doc-btn) { color: var(--navy); font-weight: 600; }

/* --- native details/summary FAQ accordions ------------------------------- */
.inst details.doc-faq {
  background: var(--card); border: 1.5px solid var(--navy); box-shadow: 3px 3px 0 var(--dash);
  margin: 0 0 12px; break-inside: avoid;
}
.inst details.doc-faq summary {
  cursor: pointer; font: 800 15.5px/1.4 var(--sans); color: var(--navy);
  padding: 14px 44px 14px 16px; list-style: none; position: relative;
}
.inst details.doc-faq summary::-webkit-details-marker { display: none; }
.inst details.doc-faq summary:after {
  content: "+"; position: absolute; inset-inline-end: 16px; top: 11px;
  font: 700 18px/1 var(--mono); color: var(--dim);
}
.inst details.doc-faq[open] summary:after { content: "\2013"; }
.inst details.doc-faq[open] summary { border-bottom: 1px dashed var(--edge); }
.inst .doc-answer { padding: 14px 16px; }
.inst .doc-answer p { font-size: 14.5px; line-height: 1.7; color: var(--dim); margin: 0 0 10px; }
.inst .doc-answer p:last-child { margin-bottom: 0; }
.inst .doc-answer strong { color: var(--navy); }
/* --- FAQ presentation, all 29 /faq pages ----------------------------------
   The accordions already had cards, but the page still read as one long beige
   list: every question had the same weight, a section heading looked exactly
   like a body heading, and nothing responded to the pointer. Scoped through
   `.doc-body:has(details.doc-faq)` so terms/privacy/refund — which share this
   typography — keep the plain doc look. A browser without :has() simply keeps
   the previous appearance; nothing here is load-bearing. */
.inst .doc-body:has(details.doc-faq) > h2 {
  position: relative; margin: 34px 0 16px; padding-top: 15px;
  font-size: 23px; letter-spacing: -.01em;
}
.inst .doc-body:has(details.doc-faq) > h2:before {
  content: ""; position: absolute; top: 0; inset-inline-start: 0;
  width: 46px; height: 4px; background: var(--amber);
}
.inst .doc-body:has(details.doc-faq) details.doc-faq {
  border-width: 2px; box-shadow: 4px 4px 0 var(--dash); margin-bottom: 10px;
  transition: box-shadow .16s ease, transform .16s ease;
}
.inst .doc-body:has(details.doc-faq) details.doc-faq:hover {
  transform: translate(-1px, -1px); box-shadow: 6px 6px 0 var(--dash);
}
.inst .doc-body:has(details.doc-faq) details.doc-faq[open] {
  box-shadow: 6px 6px 0 var(--dash);
}
.inst .doc-body:has(details.doc-faq) details.doc-faq[open] summary {
  background: rgba(255, 230, 109, .26); border-bottom-color: rgba(26, 26, 46, .3);
}
.inst .doc-body:has(details.doc-faq) details.doc-faq summary {
  padding: 15px 54px 15px 16px; transition: background .16s ease;
}
.inst .doc-body:has(details.doc-faq) details.doc-faq summary:hover {
  background: rgba(255, 230, 109, .18);
}
/* The +/− becomes a key, not a glyph: it is the one affordance that says the
   card opens, and at 18px of unbacked monospace it read as stray punctuation. */
.inst .doc-body:has(details.doc-faq) details.doc-faq summary:after {
  display: grid; place-items: center; width: 26px; height: 26px;
  inset-inline-end: 14px; top: 50%; transform: translateY(-50%);
  background: var(--navy); color: var(--amber); border-radius: 7px;
  font: 800 16px/1 var(--mono); box-shadow: 2px 2px 0 var(--dash);
}
.inst .doc-body:has(details.doc-faq) details.doc-faq[open] summary:after {
  background: var(--amber); color: var(--navy); box-shadow: 2px 2px 0 rgba(26, 26, 46, .25);
}
.inst .doc-body:has(details.doc-faq) .doc-answer { padding: 16px 18px 18px; }
.inst .doc-body:has(details.doc-faq) .doc-answer p {
  font-size: 15px; line-height: 1.72; margin: 0 0 12px;
}
.inst .doc-body:has(details.doc-faq) .doc-answer p:first-child {
  font-size: 15.5px; color: var(--navy);
}
.inst .doc-body:has(details.doc-faq) .doc-answer a { text-decoration: underline; text-underline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .inst .doc-body:has(details.doc-faq) details.doc-faq,
  .inst .doc-body:has(details.doc-faq) details.doc-faq:hover { transition: none; transform: none; }
}

/* --- checklist groups ----------------------------------------------------- */
.inst .doc-check {
  background: var(--card); border: 1.5px solid var(--navy); box-shadow: 4px 4px 0 var(--dash);
  padding: 20px 22px; margin: 0 0 16px; break-inside: avoid;
}
.inst .doc-check h2 { margin: 0 0 10px; }
.inst .doc-check ul { list-style: none; margin: 0; padding: 0; }
.inst .doc-check li {
  font-size: 14.5px; line-height: 1.6; color: var(--dim);
  padding: 7px 0 7px 28px; position: relative; border-bottom: 1px dashed var(--edge);
}
.inst .doc-check li:last-child { border-bottom: 0; }
.inst .doc-check li::before {
  content: ""; position: absolute; inset-inline-start: 2px; top: 11px;
  width: 13px; height: 13px; border: 2px solid var(--navy); background: var(--paper);
}
.inst .doc-printbar { text-align: center; margin: 0 0 18px; }
.inst .doc-printbar button {
  display: none; font: 800 13px var(--sans); padding: 11px 24px;
  background: var(--card); color: var(--navy); border: 1.5px solid var(--navy);
  cursor: pointer; box-shadow: 3px 3px 0 var(--dash);
}
.inst .doc-printbar button:hover { transform: translate(-1px,-1px); }
@media screen and (min-width: 641px) { .inst .doc-printbar button { display: inline-block; } }

/* --- print: the ticket metaphor ends at paper ----------------------------- */
@media print {
  .inst .nav, .inst .foot, .inst .doc-strip, .inst .barcode, .inst .doc-cta,
  .inst .doc-printbar { display: none !important; }
  .inst .doc-stat, .inst .doc-quote, .inst .doc-card, .inst .doc-check { box-shadow: none; }
  .inst .doc-body { max-width: 100%; padding: 0 8px; }
}

/* --- builder doc components (checklist / payments / usvisa / usborder) ---- */
.inst .doc-panel {
  background: var(--card); border: 1.5px solid var(--navy); box-shadow: 4px 4px 0 var(--dash);
  padding: 20px 22px; margin: 0 0 18px; break-inside: avoid;
}
.inst .doc-panel h2 { margin: 0 0 10px; }
.inst .doc-panel > p:first-of-type { font-size: 15px; }
.inst .doc-chips { display: flex; flex-wrap: wrap; gap: 7px; margin: 6px 0 4px; }
.inst .doc-chips .chip {
  display: inline-block; background: var(--paper); border: 1px solid var(--navy);
  padding: 4px 12px; font: 700 12px/1.5 var(--mono); color: var(--navy);
}
.inst .doc-tip {
  font-size: 14px; font-weight: 700; color: var(--navy); background: var(--paper);
  border: 1px dashed var(--navy); padding: 10px 14px; margin: 12px 0 0;
}
.inst .doc-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin: 0 0 26px; }
.inst .doc-step {
  background: var(--card); border: 1.5px solid var(--navy); box-shadow: 4px 4px 0 var(--dash);
  padding: 18px; font-size: 14px; line-height: 1.6; color: var(--dim); break-inside: avoid;
}
.inst .doc-stepnum {
  display: inline-block; min-width: 26px; height: 26px; line-height: 26px; text-align: center;
  background: var(--navy); color: var(--amber); font: 800 13px/26px var(--mono); margin-inline-end: 6px;
}
.inst .doc-honest {
  background: var(--amber); border: 1.5px solid var(--navy); box-shadow: 4px 4px 0 rgba(26,26,46,.16);
  padding: 20px 22px; margin: 0 0 26px;
}
.inst .doc-honest h3 { font: 800 17px/1.3 var(--sans); color: var(--navy); margin: 0 0 6px; }
.inst .doc-honest p { font-size: 14.5px; line-height: 1.65; color: var(--navy); opacity: .85; margin: 0; }
.inst .doc-rule { background: var(--paper); border: 1px solid var(--edge); padding: 14px 16px; margin: 12px 0 0; }
.inst .doc-rule h4 { font: 800 15px/1.3 var(--sans); color: var(--navy); margin: 0 0 4px; }
.inst .doc-rule p { font-size: 14px; line-height: 1.6; color: var(--dim); margin: 0; }
.inst .doc-table { width: 100%; border-collapse: collapse; margin: 6px 0 0; }
.inst .doc-table td { padding: 9px 0; border-bottom: 1px dashed var(--edge); font-size: 14px; line-height: 1.6; color: var(--dim); vertical-align: top; }
.inst .doc-table tr:last-child td { border-bottom: 0; }
.inst .doc-table td:first-child { width: 34%; font-weight: 700; color: var(--navy); white-space: nowrap; padding-inline-end: 10px; }
@media (max-width: 640px) {
  .inst .doc-table, .inst .doc-table tbody, .inst .doc-table tr, .inst .doc-table td { display: block; }
  .inst .doc-table tr { border-bottom: 0; border-top: 1px dashed var(--edge); padding: 8px 0; }
  .inst .doc-table tr:first-child { border-top: 0; }
  .inst .doc-table td:first-child { width: auto; padding: 0 0 2px; }
}
@media print {
  .inst .doc-panel, .inst .doc-step, .inst .doc-honest { box-shadow: none; }
}

/* --- news hub -------------------------------------------------------------- */
.inst .news-card {
  display: block; background: var(--card); border: 1.5px solid var(--navy);
  box-shadow: 4px 4px 0 var(--dash); padding: 18px 22px; margin: 0 0 14px;
  text-decoration: none; break-inside: avoid;
}
.inst .news-card:hover { transform: translate(-1px,-1px); box-shadow: 5px 5px 0 rgba(26,26,46,.2); }
.inst .news-card h2 { font: 800 19px/1.35 var(--sans); color: var(--navy); margin: 8px 0 6px; }
.inst .news-card time { font: 500 12.5px/1.4 var(--mono); color: var(--dim); }
.inst .news-badge {
  display: inline-block; font: 700 10px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
  background: var(--paper); border: 1px solid var(--edge); color: var(--dim); padding: 4px 10px;
}
.inst .news-badge:empty { display: none; }
.inst .doc-head a { color: var(--navy); }
@media print { .inst .news-card { box-shadow: none; } }

/* ==========================================================================
   guide-rework (2026-10-08): review alert bar, 3-stat strip, FlightProof Pass
   CTA, and the guides' fluid layer. Lives at the end of the file so the
   earlier locked rules can be read without these overrides in mind.
   ========================================================================== */

/* --- reviewed span -> amber alert bar ------------------------------------- */
/* The old badge was a 10.5px uppercase pill — a freshness signal nobody read.
   Inside the dark band it is now a full amber bar with navy text: the loudest
   trustworthy thing on the page, directly under the h1. The scope sentence
   (data-i18n="guide.reviewed_scope") states WHAT was re-verified. */
.inst .reviewed {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px;
  margin-top: 16px; padding: 10px 14px; border-radius: 6px;
  background: var(--amber); border: 1px solid rgba(255, 230, 109, .55);
  color: var(--navy);
  font: 700 12.5px/1.5 var(--sans); letter-spacing: 0; text-transform: none;
  box-shadow: 3px 3px 0 rgba(0, 0, 0, .28);
}
.inst .reviewed::before { content: "\2713\00a0"; font-weight: 900; }
.inst .reviewed .rev-scope { font-weight: 600; font-size: 12px; opacity: .82; }

/* --- readout -> 3-cell data strip ----------------------------------------- */
/* Was a mono uppercase prose row that wrapped unpredictably at 390px. Now a
   bordered 3-cell strip: the airport-ask split, the flat price, the delivery
   promise — values in amber, labels in plain sentence case. */
.inst .readout {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px; margin-top: 14px;
  background: rgba(26, 26, 46, .16); border: 1px solid rgba(26, 26, 46, .16);
  border-radius: 7px; overflow: hidden;
  font: inherit; letter-spacing: 0; text-transform: none;
}
.inst .readout .rscell {
  background: rgba(255, 255, 255, .4); padding: 9px 11px;
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
.inst .readout .rsv {
  font: 800 18px/1 var(--sans); color: var(--navy);
  font-variant-numeric: tabular-nums;
}
.inst .readout .rsl {
  font: 600 10.5px/1.4 var(--sans); letter-spacing: 0;
  color: rgba(26, 26, 46, .68); text-transform: none;
}

/* The direct answer under a guide headline: one line that tells a one-way
   traveller what to do, without turning the page into another sales banner. */
.inst .band .guide-direct,
.guide-direct {
  max-width: 68ch;
  margin: 12px 0 0;
  padding: 9px 12px;
  border-left: 3px solid var(--navy);
  background: rgba(255, 255, 255, .42);
  color: var(--navy);
  font: 800 13.5px/1.45 var(--sans);
}

/* --- FlightProof Pass: the boarding-pass CTA (doc end) --------------------- */
/* COMPACTED 2026-10-09 (owner: the block "looks overloaded"). The doc end used
   to carry a decorative barcode strip, a green CONFIRMED stamp that thudded in
   on scroll-in (with its own inline IntersectionObserver) and a sub-line
   repeating "show it at check-in and immigration" — on a page that already had
   the body .fp-pass widget, the express ticket in the stub and a hero CTA, all
   saying the same thing. Markup and injector: scripts/guide-rework.cjs. */
.inst .passcta {
  position: relative; overflow: hidden; margin: 28px 0 0;
  display: grid; grid-template-columns: 1fr 220px;
  background: var(--card); border: 1px solid var(--edge); border-radius: 6px;
  box-shadow: 4px 4px 0 rgba(26, 26, 46, .16);
}
.inst .pass-route { padding: 18px 20px; min-width: 0; }
.inst .pass-city {
  font: 800 9.5px/1 var(--mono); letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--dim);
}
.inst .pass-code {
  font: 900 clamp(22px, 4vw, 34px)/1.1 var(--mono); color: var(--navy);
  letter-spacing: .02em; margin: 4px 0 12px; overflow-wrap: anywhere;
}
.inst .pass-arrow { color: var(--dim); font-size: 15px; margin: 0 4px; }
.inst .pass-onward { color: var(--dim); }
.inst .pass-stub {
  padding: 18px; border-left: 2px dashed var(--dash);
  display: flex; flex-direction: column; gap: 6px;
  align-items: flex-start; justify-content: center;
}
.inst .pass-price { font: 900 24px/1 var(--mono); color: var(--navy); }
.inst .pass-price small { font-size: 12px; font-weight: 700; color: var(--dim); }
.inst .pass-btn {
  appearance: none; cursor: pointer; text-align: center;
  background: var(--amber); color: var(--navy); border: 1px solid var(--navy);
  border-radius: 12px; padding: 11px 18px;
  font: 800 13.5px/1 var(--sans); letter-spacing: .01em;
  box-shadow: 2px 2px 0 var(--navy); transition: transform .12s ease, box-shadow .12s ease;
}
.inst .pass-btn:hover { transform: translate(-1px, -1px); box-shadow: 3px 3px 0 var(--navy); }
.inst .pass-btn:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 var(--navy); }
@media (prefers-reduced-motion: reduce) {
  .inst .pass-btn { transition: none; }
}

/* --- FlightProof Pass: the card skin, and the buy CTA that moves ---------- */
/* 2026-10-22 (owner queue: "an ANIMATED FlightProof Pass buy CTA — the
   show-to-immigration angle; respect reduced-motion"). Two gaps, one feature:

   1. THE CARD HAD NO STYLESHEET AT ALL. The .fp-pass widget — the boarding
      pass with the ONWARD stub, the barcode and the "show it at check-in /
      border" line — was styled ONLY by the inline <style> block of the 86
      non-standard pages. The other 1,978 guide pages carried the markup and
      no rules for it: measured on production, the barcode strip was 0px tall,
      the card had no border and no paper ground, and the show-to-immigration
      line sat on the page as bare text. The rules below port that skin into
      the sheet every guide page already loads.
      What is ported is the card's RESTING state, not its entrance: opacity
      stays 1 and there is no transform, so a visitor without JS — or a
      crawler that does not run it — still sees the card, the barcode and the
      show-to-immigration line. The entrance (and the stamp thud) is driven by
      the .in class, which public/js/instrument.js adds when the card arrives.
   2. THE BUY CTA HAD NO MOTION. .passcta now rises into place the first
      time it scrolls into view (two beats, then it stops) and its button
      pulses with it. The start state is a TRANSFORM, never opacity: the buy
      button is the money path, so a reveal that failed to fire would leave a
      gap where the CTA should be. It hangs on the data-fprw reveal hook —
      the same marker the 129 non-standard pages' own observer watches — so
      that observer and instrument.js (on the other 2,236 pages) drive one
      rule instead of two mechanisms. Without JS the CTA sits where it
      belongs, 18px lower and still clickable.
      The show-to-immigration sub-line is back too (stripped 2026-10-09 as
      "overload"): it is baked per locale by scripts/guide-rework.cjs from
      public/i18n/<loc>.json (guide.pass_sub, translated in all 42
      dictionaries), with data-i18n as the runtime backstop.

   Reduced motion: every animation below sits inside a
   prefers-reduced-motion: no-preference block, so the reduced-motion visitor
   gets the same page with nothing moving. */

/* The card: paper ground, navy ink, hard shadow — the boarding-pass look the
   86 reference pages have had since 2026-10-09. */
.inst .fp-pass {
  --pp-navy: var(--navy);
  --pp-amber: var(--amber);
  --pp-paper: #ece8df;
  --pp-dim: #67624f;
  position: relative; max-width: 620px; margin: 26px auto;
  font-family: var(--sans);
  background: var(--pp-paper);
  border: 2px solid var(--pp-navy); border-radius: 16px;
  box-shadow: 5px 5px 0 var(--pp-navy);
  overflow: hidden;
}
.inst .fp-pass-top {
  display: flex; justify-content: space-between; align-items: center;
  background: var(--pp-navy); color: var(--pp-amber);
  padding: 10px 16px; font-size: 12px; font-weight: 900; letter-spacing: 2px;
}
.inst .fp-pass-top .fp-pass-val {
  background: var(--pp-amber); color: var(--pp-navy);
  padding: 2px 10px; border-radius: 10px; font-size: 10px; letter-spacing: 1px;
  animation: fpPassVal 2.4s ease-in-out infinite;
}
@keyframes fpPassVal {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 230, 109, .5); }
  50% { box-shadow: 0 0 0 6px rgba(255, 230, 109, 0); }
}
.inst .fp-pass-main { display: flex; align-items: center; gap: 18px; padding: 18px 20px; }
.inst .fp-pass-route {
  font-size: clamp(20px, 4vw, 30px); font-weight: 900;
  color: var(--pp-navy); letter-spacing: 1px;
}
.inst .fp-pass-plane { font-size: 22px; display: inline-block; animation: fpPassFly 3.2s ease-in-out infinite; }
@keyframes fpPassFly {
  0%, 100% { transform: translateX(0); }
  50% { transform: translateX(7px); }
}
.inst .fp-pass-meta { font-size: 11px; font-weight: 700; color: var(--pp-dim); line-height: 1.8; }
.inst .fp-pass-stub {
  border-left: 2px dashed var(--pp-navy); padding: 14px 16px;
  text-align: center; min-width: 118px; position: relative;
}
.inst .fp-pass-barcode {
  height: 34px; border-radius: 3px; margin-bottom: 6px;
  background: repeating-linear-gradient(90deg, var(--pp-navy) 0 2px, transparent 2px 4px, var(--pp-navy) 4px 7px, transparent 7px 9px);
}
.inst .fp-pass-stub small { font-size: 9px; font-weight: 800; color: var(--pp-dim); letter-spacing: 1px; }
.inst .fp-pass-stamp {
  position: absolute; top: 50%; right: 8%;
  transform: translateY(-50%) rotate(-14deg) scale(3);
  font-size: 13px; font-weight: 900; color: var(--pp-navy);
  border: 3px solid var(--pp-navy); border-radius: 8px;
  padding: 4px 10px; letter-spacing: 2px;
  opacity: 0; pointer-events: none;
}
.inst .fp-pass.stamped .fp-pass-stamp { animation: fpPassStamp .45s cubic-bezier(.2, 2.2, .4, 1) forwards; }
@keyframes fpPassStamp {
  0% { opacity: 0; transform: translateY(-50%) rotate(-14deg) scale(3); }
  60% { opacity: 1; transform: translateY(-50%) rotate(-14deg) scale(.94); }
  100% { opacity: .92; transform: translateY(-50%) rotate(-14deg) scale(1); }
}
.inst .fp-pass-cta {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 12px; padding: 14px 20px;
  background: rgba(255, 255, 255, .72); border-top: 2px dashed var(--pp-navy);
}
.inst .fp-pass-btn {
  display: inline-flex; align-items: center; gap: 8px;
  background: linear-gradient(135deg, #1a1a2e, #FFE66D); color: #fff;
  border: 2px solid var(--pp-navy); border-radius: 12px;
  padding: 12px 22px; font-size: 15px; font-weight: 900;
  text-decoration: none; box-shadow: 3px 3px 0 var(--pp-navy);
  transition: transform .12s ease, box-shadow .12s ease;
}
.inst .fp-pass-btn:hover { transform: translate(-2px, -2px); box-shadow: 5px 5px 0 var(--pp-navy); }
.inst .fp-pass-note { font-size: 11px; color: var(--pp-dim); font-weight: 700; }
/* The card's own button takes two attention beats when the card arrives, then
   stops — the treatment styles.css used to give it (fpPassCtaPulse), which
   no guide page could reach because styles.css is not loaded here. */
.inst .fp-pass.in .fp-pass-btn { animation: fpPassCtaPulse 1.5s ease-in-out .55s 2; }
@keyframes fpPassCtaPulse {
  0% { transform: scale(1); box-shadow: 3px 3px 0 var(--pp-navy); }
  35% { transform: scale(1.055); box-shadow: 0 0 0 9px rgba(26, 26, 46, .16), 3px 3px 0 var(--pp-navy); }
  100% { transform: scale(1); box-shadow: 3px 3px 0 var(--pp-navy); }
}
@media (max-width: 560px) {
  .inst .fp-pass-main { flex-direction: column; text-align: center; gap: 8px; }
  .inst .fp-pass-stub { border-left: 0; border-top: 2px dashed var(--pp-navy); width: 100%; }
}

/* The buy CTA: the show-to-immigration line, and the reveal. */
.inst .pass-sub {
  margin-top: 10px; padding-left: 10px;
  border-left: 3px solid var(--amber);
  font: 700 12px/1.5 var(--sans); color: var(--dim);
  max-width: 46ch;
}
@media (prefers-reduced-motion: no-preference) {
  /* The start state is a TRANSFORM, never an opacity: the buy button is the
     money path, so a reveal that failed to fire must leave the CTA in place,
     not a hole. data-fprw is the guide-rework reveal hook, so the page's own
     observer (the 129 non-standard pages) and instrument.js (the rest) both
     land the same .in class and one rule animates both. */
  .inst .passcta[data-fprw] { transform: translateY(18px); }
  .inst .passcta[data-fprw].in { animation: fpPassCtaIn .68s cubic-bezier(.22, .9, .28, 1) both; }
  .inst .passcta.in .pass-btn { animation: fpPassBtn 1.5s ease-in-out .45s 2; }
}
@keyframes fpPassCtaIn {
  0% { transform: translateY(18px); }
  55% { transform: translateY(-5px); }
  78% { transform: translateY(2px); }
  100% { transform: translateY(0); }
}
@keyframes fpPassBtn {
  0%, 100% { transform: scale(1); box-shadow: 2px 2px 0 var(--navy); }
  50% { transform: scale(1.04); box-shadow: 2px 2px 0 var(--navy), 0 0 0 8px rgba(255, 230, 109, .3); }
}
@media (prefers-reduced-motion: reduce) {
  .inst .fp-pass, .inst .fp-pass-plane, .inst .fp-pass-top .fp-pass-val,
  .inst .fp-pass.stamped .fp-pass-stamp, .inst .fp-pass.in .fp-pass-btn {
    animation: none !important; transition: none !important;
  }
  /* No motion means no thud: the stamp is simply on the card, unrotated by
     the animation that would have thrown it there. */
  .inst .fp-pass.stamped .fp-pass-stamp {
    opacity: .92; transform: translateY(-50%) rotate(-14deg) scale(1);
  }
}

/* --- fluid layer: the gaps the 840/600px rules leave ---------------------- */
/* Tables and wide pre blocks: scroll instead of stretching the page. */
.inst .doc-body table, .inst .main table { display: block; max-width: 100%; overflow-x: auto; }
/* Touch targets: every tap-able thing at least 44px tall on a phone. */
@media (max-width: 600px) {
  .inst .doc-btn, .inst .pass-btn, .inst .readout .rscell { min-height: 44px; }
  .inst .passcta { grid-template-columns: 1fr; }
  .inst .pass-stub { border-left: 0; border-top: 2px dashed var(--dash); }
  .inst .readout .rsv { font-size: 17px; }
  .inst .doc-body, .inst .main > p, .inst .main li { font-size: 15.5px; }
}
/* (Removed 2026-10-09) A tablet rule used to make the stacked stub a 2-column
   grid and give the express card `grid-column: auto` while every other child
   spanned — so the form alone sat in a half column, 316px wide at 768px,
   narrower than it is on a phone. Nothing downstream needed the grid: with
   all children spanning, it rendered exactly like the block layout. */
/* ── Country-guide video block (scripts/guide-video.cjs, 2026-10-09) ────────
   The facade IS the design constraint: no iframe, no YouTube script and no
   YouTube cookie until the visitor clicks. 16:9 is reserved by the img's own
   width/height attributes, so swapping in the player cannot shift the page.
   Fits the Instrument palette (paper/card/navy/amber, mono for machine text). */
.inst .guidevideo {
  max-width: 720px;
  margin: 22px auto 6px;
  padding: 16px 18px 14px;
  background: var(--card);
  border: 2px solid var(--navy);
  border-radius: 14px;
  box-shadow: 4px 4px 0 var(--navy);
}
.inst .guidevideo h2 {
  margin: 0 0 12px;
  font-size: clamp(17px, 2.4vw, 21px);
  font-weight: 900;
  color: var(--navy);
  letter-spacing: .2px;
}
.inst .gv-frame {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 10px;
  border: 2px solid var(--navy);
  background: var(--paper);
  cursor: pointer;
}
.inst .gv-frame img { display: block; width: 100%; height: 100%; object-fit: cover; }
.inst .gv-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.inst .gv-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.inst .gv-play::before {
  content: "";
  width: 62px; height: 62px;
  border-radius: 50%;
  background: var(--amber);
  border: 3px solid var(--navy);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .35);
  transition: transform .12s, background .12s;
}
.inst .gv-play::after {
  content: "";
  position: absolute;
  margin-inline-start: 6px;
  border-style: solid;
  border-width: 13px 0 13px 21px;
  border-color: transparent transparent transparent var(--navy);
}
.inst .gv-frame:hover .gv-play::before,
.inst .gv-frame:focus-visible .gv-play::before { transform: scale(1.06); background: #fff; }
.inst .gv-frame:focus-visible { outline: 3px solid var(--amber); outline-offset: 3px; }
.inst .gv-dur {
  position: absolute;
  inset-inline-end: 10px;
  bottom: 10px;
  padding: 3px 8px;
  border-radius: 6px;
  background: rgba(26, 26, 46, .86);
  color: #fff;
  font: 700 12px/1.4 var(--mono);
  letter-spacing: .04em;
}
/* Painted from data-dur rather than a text node: axe counts visible text inside
   the labelled button and fails label-content-name-mismatch otherwise. */
.inst .gv-dur::after { content: attr(data-dur); }
.inst .gv-meta { margin: 10px 0 0; font-size: 12.5px; color: var(--dim); }
.inst .gv-src {
  font: 700 11px/1 var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--hi);
}
.inst .gv-meta a {
  color: var(--navy);
  font-weight: 700;
  text-decoration: underline;
  text-decoration-style: dotted;
}
.inst .gv-note { margin: 6px 0 0; font-size: 11.5px; color: var(--grey); }
@media (prefers-reduced-motion: reduce) {
  .inst .gv-play::before { transition: none; }
}
/* ==========================================================================
   E-TICKET FACE (.fp-etk) — the express form, printed as a real airline
   e-ticket.
   ==========================================================================
   Owner (2026-10-10): "where you write your ticket i need a high end animated
   thing that makes the ticket look real airline e ticket code".

   So the stub's form no longer starts with five bare fields. Above them sits
   the ticket itself: the carrier plate, the route with its perforated flight
   line, the spec strip (DATE / PASSENGER / CLASS), the machine-readable
   E-TICKET PNR block and the barcode under it. The fields below are the pen;
   this is the ticket being written on. Nothing generates this markup — the
   fp-express.js enhancer injects the face into form#fp-express at runtime, so
   every one of the 2,323 pages that carries the form gets it, including the
   country guides, where that form is moved into the flight-search modal: the
   face travels with it because it is a child of the form.

   IT IS aria-hidden. Every value on the face (route, date, passenger) already
   lives in a labelled input one line below it; announcing them twice is noise,
   and the PNR is masked until it is real. The face is a picture of the form.

   AMBIENT MOTION (owner chose "always-on ambient motion"): a sheen crosses the
   carrier plate, a scan line sweeps the barcode, the aircraft glides along the
   perforated route line, and the masked PNR breathes while the ticket is
   unpaid. When the booking issues a real PNR the breathing stops and a stamp
   lands once — motion that answers an event, not decoration. Everything here is
   disabled under prefers-reduced-motion, and this block carries its own guard
   because the modal form is appended to <body> and sits outside .inst (whose
   sitewide reduced-motion rule would not reach it).

   Tokens are declared locally for the same reason: --i-navy/--i-mono are only
   declared inside .fp-ex, and --dash/--navy on .inst.
   ========================================================================== */
.fp-etk {
  --etk-ink: #1a1a2e;
  --etk-amber: #FFE66D;
  --etk-paper: #fff;
  --etk-edge: #e2ddcf;
  --etk-dash: #d7d2c2;
  --etk-dim: #67624f;
  --etk-mono: "JetBrains Mono", ui-monospace, "IBM Plex Mono", Menlo, Consolas, monospace;
  position: relative;
  display: block;
  margin: 0 0 14px;
  background: var(--etk-paper);
  border: 1px solid var(--etk-edge);
  border-radius: 8px;
  overflow: hidden;
  color: var(--etk-ink);
  /* the printed card, lifted off the page the way the modal card is */
  box-shadow: 3px 3px 0 rgba(26, 26, 46, .1);
  font: 400 12px/1.4 Outfit, sans-serif;
}

/* --- the carrier plate: brand + the E-TICKET chip ------------------------ */
.fp-etk-top {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 11px;
  background: var(--etk-ink);
  overflow: hidden;
}
.fp-etk-brand {
  font: 800 11px/1 Outfit, sans-serif;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: #fff;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.fp-etk-brand i { font-style: normal; color: var(--etk-amber); font-size: 12px; }
/* the chip is the ticket's grade: E-TICKET until paid, ISSUED once it is */
.fp-etk-chip {
  font: 800 9px/1 var(--etk-mono);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--etk-ink);
  background: var(--etk-amber);
  border-radius: 3px;
  padding: 4px 7px;
  white-space: nowrap;
}
/* the sheen that crosses the plate */
.fp-etk-top::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(105deg,
    transparent 0 38%, rgba(255, 255, 255, .22) 48%, transparent 58% 100%);
  transform: translateX(-100%);
  animation: fpEtkSheen 7s cubic-bezier(.4, 0, .5, 1) infinite;
  pointer-events: none;
}
@keyframes fpEtkSheen {
  0%, 62% { transform: translateX(-100%); }
  88%, 100% { transform: translateX(100%); }
}

/* --- the route: two codes over a perforated flight line ------------------ */
.fp-etk-route {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  padding: 12px 11px 10px;
  border-bottom: 1px dashed var(--etk-dash);
}
.fp-etk-ap { display: block; min-width: 0; text-align: left; }
.fp-etk-ap--to { text-align: right; }
.fp-etk-ap b {
  display: block;
  font: 800 26px/1 var(--etk-mono);
  letter-spacing: .04em;
  color: var(--etk-ink);
}
.fp-etk-ap.is-blank b { color: #cdc7ba; }
.fp-etk-ap i {
  display: block;
  margin-top: 3px;
  font: 500 10px/1.3 Outfit, sans-serif;
  font-style: normal;
  color: var(--etk-dim);
  max-width: 92px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* the dashed line the aircraft travels along */
.fp-etk-line {
  position: relative;
  display: block;
  height: 20px;
  border-top: 2px dashed var(--etk-dash);
  align-self: center;
  margin-top: 2px;
  overflow: hidden;
}
.fp-etk-line b {
  position: absolute; top: -11px; left: 0;
  font-size: 12px; line-height: 1; color: var(--etk-ink);
  animation: fpEtkFly 6s cubic-bezier(.45, .05, .55, .95) infinite;
}
@keyframes fpEtkFly {
  0%   { transform: translateX(-6px); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { transform: translateX(calc(100% + 6px)); opacity: 0; }
}

/* --- the spec strip ------------------------------------------------------ */
.fp-etk-spec {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  padding: 10px 11px;
  border-bottom: 1px dashed var(--etk-dash);
}
.fp-etk-f { display: block; min-width: 0; }
.fp-etk-f u {
  display: block;
  text-decoration: none;
  font: 600 8.5px/1 var(--etk-mono);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--etk-dim);
}
.fp-etk-f b {
  display: block;
  margin-top: 5px;
  font: 700 11px/1.2 var(--etk-mono);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--etk-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fp-etk-f--name b { font-family: Outfit, sans-serif; letter-spacing: .01em; }
.fp-etk-f b.is-blank { color: #cdc7ba; }

/* --- the machine-readable block: the e-ticket code ----------------------- */
.fp-etk-code {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 4px;
  padding: 10px 11px 8px;
}
.fp-etk-code-k {
  font: 600 8.5px/1 var(--etk-mono);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--etk-dim);
}
/* the code itself: the one loud thing on the card, because it is the thing a
   traveller reads off to check in */
.fp-etk-code-v {
  display: block;
  font: 800 21px/1 var(--etk-mono);
  letter-spacing: .18em;
  color: var(--etk-ink);
  background: rgba(255, 230, 109, .38);
  border: 1px dashed rgba(26, 26, 46, .28);
  border-radius: 4px;
  padding: 6px 8px;
  text-align: center;
  min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fp-etk-code-s {
  font: 700 8.5px/1 var(--etk-mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--etk-dim);
  text-align: right;
}
/* unpaid: the masked code breathes, so the card reads as awaiting payment */
.fp-etk:not(.is-issued) .fp-etk-code-v { animation: fpEtkWait 2.6s ease-in-out infinite; }
@keyframes fpEtkWait {
  0%, 100% { opacity: 1; }
  50% { opacity: .55; }
}
/* issued: the breathing stops and a stamp lands once. The real PNR is set by
   fp-express.js the moment the booking writes #pnr-code. */
.fp-etk.is-issued .fp-etk-code-v {
  background: var(--etk-ink);
  color: var(--etk-paper);
  border-style: solid;
  border-color: var(--etk-ink);
  animation: fpEtkStamp .5s cubic-bezier(.2, 1.5, .4, 1) 1;
}
@keyframes fpEtkStamp {
  0% { transform: scale(1.18); opacity: .2; }
  60% { transform: scale(.98); }
  100% { transform: scale(1); opacity: 1; }
}
.fp-etk.is-issued .fp-etk-chip { background: #2fbf6b; color: #fff; }

/* --- the barcode + its scan line ----------------------------------------- */
.fp-etk-bar {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 34px;
  padding: 0 11px 9px;
  overflow: hidden;
}
.fp-etk-bar i {
  display: block;
  width: 2px;
  background: var(--etk-ink);
  flex: none;
}
/* the amber scan sweeping the stripes */
.fp-etk-bar::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--etk-amber), transparent);
  box-shadow: 0 0 6px rgba(255, 230, 109, .9);
  animation: fpEtkScan 2.8s linear infinite;
  pointer-events: none;
}
@keyframes fpEtkScan {
  0% { transform: translateY(1px); }
  100% { transform: translateY(33px); }
}

/* --- narrow columns ------------------------------------------------------
   The homepage stub is 296px; a locale homepage or a guide card can be
   narrower still. The spec strip drops to two columns before the 26px route
   codes start crowding their airport names. */
@media (max-width: 360px) {
  .fp-etk-ap b { font-size: 22px; }
  .fp-etk-spec { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fp-etk-code-v { font-size: 18px; letter-spacing: .14em; }
}

/* --- motion off ---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .fp-etk-top::after,
  .fp-etk-line b,
  .fp-etk-bar::after,
  .fp-etk:not(.is-issued) .fp-etk-code-v,
  .fp-etk.is-issued .fp-etk-code-v {
    animation: none !important;
  }
  /* the aircraft still has to be somewhere on the line when it cannot move */
  .fp-etk-line b { left: 50%; transform: translateX(-50%); opacity: 1; }
}

/* --- dark mode ----------------------------------------------------------- */
body.dark-mode .fp-etk {
  --etk-paper: #16162a;
  --etk-ink: #ece8df;
  --etk-edge: rgba(255, 230, 109, .28);
  --etk-dash: rgba(236, 232, 223, .28);
  --etk-dim: rgba(236, 232, 223, .6);
  box-shadow: 3px 3px 0 rgba(255, 230, 109, .18);
}
body.dark-mode .fp-etk-top { background: #0f0f22; }
body.dark-mode .fp-etk-brand { color: #fff; }
body.dark-mode .fp-etk-code-v { background: var(--etk-amber); color: #1a1a2e; }
body.dark-mode .fp-etk.is-issued .fp-etk-code-v {
  background: var(--etk-amber); color: #1a1a2e; border-color: var(--etk-amber);
}
body.dark-mode .fp-etk-bar i { background: #ece8df; }
body.dark-mode .fp-etk-ap.is-blank b { color: rgba(236, 232, 223, .3); }
body.dark-mode .fp-etk-f b.is-blank { color: rgba(236, 232, 223, .3); }
/* ==========================================================================
   FLIGHTPROOF ISSUANCE STRIP — the express form's header, animated the way a
   ticket is issued. (2026-10-11)

   The express form (and the booking modal that hosts it) opens on a title row
   and three chips: they say what the thing costs, but not what the buyer gets
   or what we ask for to get it. This strip is that header's second half — the
   same face the card and the search sheet already print (.fp-etk above),
   compacted, flying the route the visitor has picked, typing in the code block
   that will hold the reference, and stating the two facts a buyer hesitates
   over: the checkout is Stripe-protected, and the only things we ask for are a
   name and an email.

   `--mini` is a MODIFIER, not a second component: it compacts the type scale
   and drops the spec grid and the barcode, so the strip cannot drift from the
   ticket face it is a smaller copy of. It is mounted at runtime by the "fp
   issuance strip" block in app-core-en.js, immediately below two headers:
     · #bookingModal .modal-header   — the booking modal
     · .fp-ticket-head               — the inline expanded form

   Motion is keyed to .modal-overlay.open (so it replays on every open of the
   modal) or to `--loop`, which is what the inline mount carries and which
   loops instead. The reduced-motion block at the end stills all of it and
   leaves every end state on screen — the aircraft parked on the line, the code
   block fully typed, the tags shown — because the strip has to read the same
   when it cannot move.
   ========================================================================== */
.fp-etk--mini {
  margin: 10px 0 0;
  font: 400 11px/1.4 Outfit, sans-serif;
}
.fp-etk--mini .fp-etk-top { padding: 6px 9px; }
.fp-etk--mini .fp-etk-brand { font-size: 10px; letter-spacing: .16em; }
.fp-etk--mini .fp-etk-chip { padding: 3px 6px; font-size: 8.5px; letter-spacing: .12em; max-width: 62%; overflow: hidden; text-overflow: ellipsis; }
.fp-etk--mini .fp-etk-route { padding: 9px 9px 8px; gap: 7px; }
.fp-etk--mini .fp-etk-ap b { font-size: 16px; letter-spacing: .06em; }
.fp-etk--mini .fp-etk-ap i { display: none; }   /* the city name needs the width of the card, not the strip */
.fp-etk--mini .fp-etk-line { height: 16px; margin-top: 1px; }
.fp-etk--mini .fp-etk-code { padding: 8px 9px 7px; gap: 3px; }
/* the label and the promise share one line, so the strip is a row shorter: the
   modal header is already the tallest thing on a phone screen */
.fp-etk--mini .fp-etk-code-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.fp-etk--mini .fp-etk-code-head .fp-etk-code-s { text-align: right; }
.fp-etk--mini .fp-etk-code-v { padding: 5px 7px; font-size: 15px; letter-spacing: .16em; }
.fp-etk--mini .fp-etk-bar { display: none; }

/* --- the route the visitor has picked ------------------------------------
   The face's own line, but restarted on open. Its keyframe animates translateX
   as a percentage of the *aircraft* (12px), which parks it ~18px along; here
   the travel is a percentage of the thin space the plane sits in, and is
   expressed on the flow-relative edge, so the aircraft leaves the departure
   code and reaches the arrival code on a right-to-left page too. */
.fp-etk--mini .fp-etk-line b { left: auto; inset-inline-start: 0; }
@keyframes fpEtkFlyMini {
  0%   { inset-inline-start: 0; opacity: 0; }
  14%  { opacity: 1; }
  86%  { opacity: 1; }
  /* 14px, not 6: the line clips, and the glyph is wider than the last 6px of
     it, so a tighter stop parks half an aircraft at the arrival code */
  100% { inset-inline-start: calc(100% - 14px); opacity: 1; }
}
@keyframes fpEtkFlyMiniLoop {
  0%   { inset-inline-start: 0; opacity: 0; }
  14%  { opacity: 1; }
  86%  { opacity: 1; }
  100% { inset-inline-start: calc(100% - 14px); opacity: 0; }
}
.modal-overlay.open .fp-etk--mini .fp-etk-line b {
  animation: fpEtkFlyMini 2.2s cubic-bezier(.45, .05, .55, .95) .15s 1 forwards;
}
.fp-etk--mini.fp-etk--loop .fp-etk-line b {
  animation: fpEtkFlyMiniLoop 4.5s cubic-bezier(.45, .05, .55, .95) infinite;
}

/* --- the sheen across the carrier plate ---------------------------------- */
@keyframes fpEtkSheenMini {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}
.modal-overlay.open .fp-etk--mini .fp-etk-top::after {
  animation: fpEtkSheenMini 1.6s cubic-bezier(.4, 0, .5, 1) .15s 1;
}
.fp-etk--mini.fp-etk--loop .fp-etk-top::after {
  animation: fpEtkSheen 7s cubic-bezier(.4, 0, .5, 1) infinite;
}

/* --- the code block types itself in -------------------------------------
   Nine monospace cells revealed from the inline start, so it reads as the
   reference being written. The mask then breathes exactly as the face's does,
   because that is the same "payment is what mints this" statement; a real
   reference (is-issued) still stops it and lands the stamp. */
@keyframes fpEtkTypeMini {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
.modal-overlay.open .fp-etk--mini .fp-etk-code-v {
  animation: fpEtkTypeMini .9s steps(9, end) .45s 1 backwards,
             fpEtkWait 2.6s ease-in-out 1.5s infinite;
}
.fp-etk--mini.fp-etk--loop .fp-etk-code-v {
  animation: fpEtkTypeMini .9s steps(9, end) .35s 1 backwards,
             fpEtkWait 2.6s ease-in-out 1.4s infinite;
}
.fp-etk--mini.is-issued .fp-etk-code-v {
  animation: fpEtkStamp .5s cubic-bezier(.2, 1.5, .4, 1) 1;
}

/* --- what the buyer is told while the code is being written -------------- */
.fp-etk-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  padding: 8px 9px 9px;
  border-top: 1px dashed var(--etk-dash);
}
.fp-etk-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border: 1px solid var(--etk-edge);
  border-radius: 999px;
  font: 700 9.5px/1 Outfit, sans-serif;
  letter-spacing: .02em;
  color: var(--etk-dim);
  white-space: nowrap;
}
/* the two claims the header has to make: protection, and how little it takes */
.fp-etk-tag--stripe {
  border-color: rgba(47, 191, 107, .45);      /* the green the issued chip uses */
  background: rgba(47, 191, 107, .1);
  color: #1f7a4d;
  font-weight: 800;
}
.fp-etk-tag--min { border-style: dashed; }
@keyframes fpEtkTagIn {
  0%   { opacity: 0; transform: translateY(2px) scale(.94); }
  100% { opacity: 1; transform: none; }
}
.modal-overlay.open .fp-etk--mini .fp-etk-tag,
.fp-etk--mini.fp-etk--loop .fp-etk-tag {
  animation: fpEtkTagIn .34s cubic-bezier(.2, .9, .3, 1.4) 1 backwards;
}
.modal-overlay.open .fp-etk--mini .fp-etk-tag:nth-child(1) { animation-delay: .95s; }
.modal-overlay.open .fp-etk--mini .fp-etk-tag:nth-child(2) { animation-delay: 1.15s; }
.fp-etk--mini.fp-etk--loop .fp-etk-tag:nth-child(1) { animation-delay: .2s; }
.fp-etk--mini.fp-etk--loop .fp-etk-tag:nth-child(2) { animation-delay: .4s; }

/* --- narrow columns ------------------------------------------------------
   The modal header is already the tallest thing on a phone screen, so the
   strip gives its height back first: tighter paddings, one step down the type
   scale, and the tags left-aligned so they wrap as a block instead of
   trailing. */
@media (max-width: 420px) {
  .fp-etk--mini { padding: 0; margin-top: 8px; }
  .fp-etk--mini .fp-etk-top { padding: 5px 8px; }
  .fp-etk--mini .fp-etk-route { padding: 8px 8px 7px; }
  .fp-etk--mini .fp-etk-code { padding: 7px 8px 6px; }
  .fp-etk--mini .fp-etk-ap b { font-size: 15px; }
  .fp-etk--mini .fp-etk-code-v { font-size: 13.5px; letter-spacing: .14em; }
  .fp-etk-tags { padding: 7px 8px 8px; }
  .fp-etk-tag { font-size: 9px; padding: 3px 7px; }
}

/* --- motion off ---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .modal-overlay.open .fp-etk--mini .fp-etk-line b,
  .fp-etk--mini.fp-etk--loop .fp-etk-line b,
  .modal-overlay.open .fp-etk--mini .fp-etk-code-v,
  .fp-etk--mini.fp-etk--loop .fp-etk-code-v,
  .modal-overlay.open .fp-etk--mini .fp-etk-tag,
  .fp-etk--mini.fp-etk--loop .fp-etk-tag,
  .modal-overlay.open .fp-etk--mini .fp-etk-top::after,
  .fp-etk--mini.fp-etk--loop .fp-etk-top::after {
    animation: none !important;
  }
  /* the aircraft still has to be somewhere on the line when it cannot move */
  .fp-etk--mini .fp-etk-line b { inset-inline-start: calc(50% - 6px); opacity: 1; }
  /* and the code block cannot stay clipped waiting for a reveal that never runs */
  .fp-etk--mini .fp-etk-code-v { clip-path: none !important; }
}

/* --- dark mode ----------------------------------------------------------- */
body.dark-mode .fp-etk-tag {
  border-color: rgba(236, 232, 223, .24);
  background: rgba(255, 255, 255, .06);
  color: #ece8df;
}
body.dark-mode .fp-etk-tag--stripe {
  border-color: rgba(125, 219, 164, .5);
  background: rgba(125, 219, 164, .12);
  color: #7ddba4;
}

/* ==========================================================================
   THE POINT OF SALE — #bookingModal as the FlightProof eTicket counter.
   ==========================================================================

   What this replaced, measured on a 390x844 phone before it (2026-10-11):

     · the card painted `linear-gradient(135deg, #191936, …)` — a dark ground —
       while its own `color` was `#1a1a2e`. Navy ink on a navy plate: the header,
       the field labels and the airline notice were all unreadable.
     · the h2 read "Flight Search", and three separate header chips ALSO read
       "Flight Search" (`tbar.label`, `cta.express_title` and a second brand slot
       resolve to the same string), next to a hard-coded "$7 only" on a page
       charging EUR. Three duplicate chips and a price that contradicted the CTA.
     · the buy button was already `position: sticky` and already on screen
       (bottom 806 of 844) — the pinning was right, the surface around it was not.
     · crypto existed only as two `display: none` buttons (`selectPayCrypto` and
       "What is crypto?"), so the rail was unreachable by a sighted buyer.

   The strip itself (`.fp-etk--mini`, mounted by the issuance-strip block in
   app-core-en.js below the header) is KEPT: it is the ticket face at the point
   of sale and it was already doing that job. It only needed the ground under it
   to be readable.

   NAMING. Everything here is `--pos-*` on purpose. The legacy popup vocabulary
   (--pink/--pink-light/--mint/--yellow/--cream) is injected per page by
   scripts/booking-popup-palette.cjs, and redeclaring those names from this file
   would outrank a page that legitimately declares its own (see the palette note
   above). A separate namespace cannot collide with either.                  */

#bookingModal {
  --pos-bg: #1a1a2e;
  --pos-plate: #22223c;
  --pos-ink: #ece8df;
  --pos-dim: rgba(236, 232, 223, .64);
  /* Body copy inside the counter is a separate step from --pos-dim. `.64` is
     right for a 10px uppercase chip label, which is what --pos-dim was picked
     for; on the airline notice's 12.5px/500 sentence it composites to
     rgb(163,160,161) over the plate — 6.0:1, technically AA but visibly washed
     out in the dark counter the owner flagged ("contrast is not good"). .88
     composites to rgb(208,204,199) on the same ground: 10.0:1, and the bold
     lead-in still outranks it at --pos-ink. */
  --pos-body: rgba(236, 232, 223, .88);
  --pos-edge: rgba(236, 232, 223, .16);
  --pos-dash: rgba(236, 232, 223, .26);
  --pos-amber: #FFE66D;
  --pos-mid: #d98c00;
  --pos-mono: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace;
}

/* The plate. `!important` because the gradient it replaces arrives as an inline
   style on the modal box on several page types, and an inline style outranks
   any selector — the same trap the consent box documents further up. */
#bookingModal .modal-content {
  background: var(--pos-bg) !important;
  background-image: none !important;
  color: var(--pos-ink) !important;
  border: 1px solid var(--pos-edge) !important;
  border-radius: 14px !important;
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .7) !important;
  padding: 0 !important;            /* the sections own their own rhythm */
  overflow: hidden auto;
}
/* the header, the face and the form are one column of bands, not a padded card */
#bookingModal .modal-header,
#bookingModal .modal-body { padding: 0; }
#bookingModal .modal-body { margin: 0; }

/* --- the counter header -------------------------------------------------- */
#bookingModal .fp-pos-head {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 18px 14px;
  border-bottom: 1px dashed var(--pos-dash);
  background: linear-gradient(180deg, rgba(255, 230, 109, .07), transparent);
}
#bookingModal .fp-pos-mark {
  width: 40px; height: 40px; flex: 0 0 auto;
  display: grid; place-items: center;
  border: 1px solid var(--pos-edge); border-radius: 8px;
  background: rgba(255, 230, 109, .1);
  font-size: 19px; line-height: 1;
}
#bookingModal .fp-pos-title { min-width: 0; }
#bookingModal .fp-pos-title h2 {
  margin: 0; font: 800 21px/1.15 Outfit, 'DM Sans', sans-serif;
  letter-spacing: -.2px;
  color: var(--pos-ink) !important;
  background: none !important; -webkit-text-fill-color: currentColor !important;
}
#bookingModal .fp-pos-kicker {
  display: block; margin-top: 3px;
  font: 700 10px/1.3 var(--pos-mono); letter-spacing: .18em; text-transform: uppercase;
  color: var(--pos-mid);
}

/* --- the price sticker: the header's click magnet ------------------------
   Owner order (2026-10-12): the yellow "$7" box in the express-form header has
   to blink so it pulls the click. It is a ticket stub in the same voice as the
   nav sticker (amber ground, 2px plate ink, hard offset shadow — print, not
   glow), and it BREATHES rather than flips: the loop never goes dark, so it
   reads as alive instead of broken. Hover freezes it into a crisp stub — the
   motion stops exactly when the buyer engages, which is also why the animation
   is switched off on hover rather than fighting it for the transform. The
   number itself is written by fpPosPricePaint() from the localized price the
   rest of the page uses: the chip it replaces hard-coded "$7 only" and printed
   dollars on pages that charge euros. */
#bookingModal .fp-pos-price {
  margin-left: auto;                 /* the stub sits at the far end of the band */
  flex: 0 0 auto;
  display: inline-flex; align-items: baseline; gap: 5px;
  font: 800 15px/1 Outfit, 'DM Sans', sans-serif;
  font-variant-numeric: tabular-nums;
  color: #1a1a2e; text-decoration: none;
  background: #FFE66D;
  border: 2px solid #1a1a2e; border-radius: 5px;
  padding: 9px 12px 8px;
  box-shadow: 3px 3px 0 #1a1a2e;
  animation: fpPosPriceBreathe 2.4s ease-in-out infinite;
  transition: transform .12s, box-shadow .12s;
}
#bookingModal .fp-pos-price:hover { animation: none; transform: translate(-1px, -1px); box-shadow: 4px 4px 0 #1a1a2e; }
#bookingModal .fp-pos-price:active { animation: none; transform: translate(1px, 1px); box-shadow: 2px 2px 0 #1a1a2e; }
#bookingModal .fp-pos-price:focus-visible { outline: 2px solid #FFE66D; outline-offset: 3px; }
/* before a route is chosen the stub carries a label, not a fare — smaller, and
   it does not breathe because there is no number to grab yet */
#bookingModal .fp-pos-price.fp-pos-pending {
  font-size: 12px;
  animation: none;
}
/* one ornament, no flicker: a soft amber ring swells and fades, and the stub
   leans forward at the same time */
@keyframes fpPosPriceBreathe {
  0%, 100% { transform: scale(1); box-shadow: 3px 3px 0 #1a1a2e, 0 0 0 0 rgba(255, 230, 109, 0); }
  50% { transform: scale(1.05); box-shadow: 3px 3px 0 #1a1a2e, 0 0 0 7px rgba(255, 230, 109, .22); }
}
@media (prefers-reduced-motion: reduce) {
  #bookingModal .fp-pos-price { animation: none; transition: none; }
}

/* --- the form fields, restyled for the night plate -----------------------
   The legacy fields carry their inline styles (white grounds, navy borders)
   written for the cream card. JS strips those inline styles when it builds the
   counter, so these rules are the only ones in play — no !important war. */
#bookingModal .fp-pos-fields { padding: 16px 18px 4px; display: flex; flex-direction: column; gap: 12px; }
#bookingModal .fp-pos-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
#bookingModal label {
  display: block; margin-bottom: 5px;
  font: 800 10px/1.2 var(--pos-mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--pos-dim);
}
#bookingModal input[type=text],
#bookingModal input[type=email],
#bookingModal input[type=date],
#bookingModal select {
  width: 100%; box-sizing: border-box;
  min-height: 46px;                       /* a finger, not a mouse */
  padding: 11px 13px;
  background: var(--pos-plate);
  color: var(--pos-ink);
  border: 1px solid var(--pos-edge); border-radius: 9px;
  font: 600 15px/1.2 Outfit, 'DM Sans', sans-serif;
  outline: none;
}
#bookingModal input::placeholder { color: rgba(236, 232, 223, .38); }
#bookingModal input:focus, #bookingModal select:focus {
  border-color: var(--pos-amber); box-shadow: 0 0 0 3px rgba(255, 230, 109, .18);
}
#bookingModal select { appearance: auto; }
#bookingModal .fp-pos-hint { margin-top: 4px; font-size: 10.5px; color: var(--pos-dim) !important; }
#bookingModal .fp-pos-req { color: var(--pos-amber); font-weight: 800; }
#bookingModal .fp-pos-fields a, #bookingModal .fp-pos-notice a { color: var(--pos-amber); }
#bookingModal .fp-pos-notice strong, #bookingModal .fp-pos-notice b { color: var(--pos-ink); font-weight: 800; }

/* --- the fare box, reskinned for the plate --------------------------------
   initFareBox() injects #b-farebox at the counter's own header and styles it
   from its own <style> block in cream/pink. Those inks were written for the
   cream card: the 3:00 lock chip measured 1.59:1 on the night ground. An id in
   the selector outranks that block, so these rules win without !important. */
#bookingModal .fp-fh { background: rgba(255, 255, 255, .04); border-color: var(--pos-dash); }
#bookingModal .fp-fh .fp-fh-status { color: var(--pos-dim); }
#bookingModal .fp-fh .fp-fh-state.fp-fh-expired { color: var(--pos-amber); }
#bookingModal .fp-fh .fp-fh-price b { color: var(--pos-amber); }
#bookingModal .fp-fh .fp-fh-note { color: var(--pos-dim); }
#bookingModal .fp-fh .fp-fh-chip { color: #1a1a2e; background: var(--pos-amber); border-color: var(--pos-amber); }
#bookingModal .fp-fh .fp-fh-chip.fp-fh-urgent { color: #1a1a2e; background: var(--pos-mid); border-color: var(--pos-mid); }
#bookingModal .fp-fh .fp-fh-prog { background: rgba(255, 255, 255, .12); }
#bookingModal .fp-fh .fp-fh-prog i { background: linear-gradient(90deg, var(--pos-amber), var(--pos-mid)); }
#bookingModal .fp-fh .fp-fh-cta { background: var(--pos-amber); color: #1a1a2e; font-weight: 800; }

/* the consent box, on the night plate */
#bookingModal #b-consent-wrapper {
  align-items: flex-start; gap: 10px;
  padding: 12px 13px !important;
  background: rgba(255, 255, 255, .04) !important;
  border: 1px solid var(--pos-edge) !important; border-radius: 9px !important;
}
#bookingModal #b-consent-wrapper label { font: 600 12.5px/1.5 Outfit, sans-serif; color: var(--pos-dim); letter-spacing: 0; text-transform: none; }
#bookingModal #b-consent-wrapper label a { color: var(--pos-amber); }
#bookingModal #b-consent { accent-color: var(--pos-amber); }

/* the airline notice reads as a fact on the plate, not a green card */
#bookingModal .fp-pos-notice {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 12px 13px;
  border: 1px solid var(--pos-edge); border-left: 2px solid var(--pos-amber);
  border-radius: 9px; background: rgba(255, 255, 255, .03);
  color: var(--pos-body); font: 500 12.5px/1.55 Outfit, sans-serif;
}
#bookingModal .fp-pos-notice b, #bookingModal .fp-pos-notice strong { color: var(--pos-ink); font-weight: 800; }

/* --- the legacy inks, neutralised in CSS so the first paint is readable ----
   Everything above is ink the counter chooses. What follows is the OTHER ink:
   the cream card's, still written into the markup the counter wraps — 27 inline
   colour declarations inside #bookingForm (input values and their labels in
   var(--navy), hints and small print in var(--text-muted), the required
   markers and the terms links in var(--pink), the airline notice in var(--text)
   and its opening line in var(--mint)) plus two the JS builds itself (the
   partner-fee line under the CTA, the "Business Class" i-button).

   Inside #bookingModal those names resolve to near-black: the palette script
   points --text/--navy/--pink at #1a1a2e and --mint at #1e6b4a. On this #1a1a2e
   plate that is 1.0–2.6:1 — invisible. The runtime strip removes those inline
   declarations, but it runs LAST, and the plate is a stylesheet rule: between
   the CSS arriving and the counter building — and for every client that never
   runs the bundle at all (JS blocked or errored, a crawler, a screenshot
   service) — the ground was dark and the text was not.

   So the override has to live here, and it has to use `!important`: an inline
   declaration outranks any selector (the same trap the plate and the consent box
   document above). It cannot touch the counter's own work — no component the
   counter builds carries an inline colour (measured: 0), so matching on the
   inline value can only ever hit this legacy set. */
#bookingModal [style*="var(--text)"]:not(a),
#bookingModal [style*="var(--text-muted)"]:not(a),
#bookingModal [style*="var(--navy)"]:not(a),
#bookingModal [style*="var(--pink)"]:not(a),
#bookingModal [style*="var(--mint)"]:not(a) { color: var(--pos-ink) !important; }

/* Links are the one legacy ink that has a meaning to keep: the counter paints
   its links amber, and pre-JS the inline navy was beating that rule. */
#bookingModal .fp-pos-fields a,
#bookingModal #b-consent-wrapper label a,
#bookingModal .fp-pos-notice a { color: var(--pos-amber) !important; }

/* The "Business Class" i-button is built by JS with an inline red glyph and a red
   tint, and the strip deletes BOTH. A <button> inherits neither colour nor
   background from its ancestors, so what was left was Chrome's own control face
   and Chrome's own black glyph inside the night plate — legible but foreign. It
   keeps its intended shape here instead, and the ink has to come with the ground:
   light ink on the untouched UA face measured 1.07:1, which is worse than what
   it replaced. */
#bookingModal .fp-bc-info {
  color: var(--pos-ink) !important;
  background: rgba(200, 64, 46, .16) !important;
  border: 1.5px solid rgba(200, 64, 46, .7) !important;
}

/* The field CONTROLS are the one part that needs nothing here, and they are
   worth naming because it is not obvious: their inline white ground and navy
   value are already beaten by styles.css's `#bookingModal input[...]` block,
   which is `!important` throughout (#fff ink on rgba(0,0,0,.25) — measured
   12.6:1 on the harness). So an override here would not add legibility, it would
   only re-pick their ground and border against a rule another file owns. */

/* --- the pinned counter: price + CTA + the small print, one bar ----------
   Sticky rather than fixed so it un-pins at its natural place inside the card
   (a fixed bar would sit over the card's own scroll and escape its radius). */
#bookingModal .fp-pos-foot {
  position: sticky; bottom: 0; z-index: 4;
  margin-top: 12px;
  padding: 12px 18px 14px;
  border-top: 1px dashed var(--pos-dash);
  background: linear-gradient(180deg, rgba(26, 26, 46, .86), var(--pos-bg) 42%);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
#bookingModal .fp-pos-foot-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
#bookingModal .fp-pos-total-k { font: 800 10px/1 var(--pos-mono); letter-spacing: .16em; text-transform: uppercase; color: var(--pos-dim); }
#bookingModal .fp-pos-total-v { font: 800 20px/1 Outfit, sans-serif; color: var(--pos-amber); font-variant-numeric: tabular-nums; }
#bookingModal .fp-pos-total-note { font-size: 11px; color: var(--pos-dim); }

/* the one button that has to be findable without scrolling. Explicit ground,
   never a gradient that can fail to resolve: an invalid background-image leaves
   `background` at its initial value and paints white text on nothing. */
#bookingModal #fp-pos-cta {
  /* The label sits at the inline start and the amount at the inline end: the
     two slots are separated by the button's width, not by a punctuation mark a
     dictionary may or may not carry (14 of the 42 end booking.buy_now with no
     separator at all, which printed "🎫 Kup Teraz11,96 zł"). */
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 4px 10px;
  width: 100%; min-height: 52px; margin-top: 10px;
  padding: 0 16px; box-sizing: border-box;
  background: var(--pos-amber); color: #1a1a2e;
  border: 1px solid #1a1a2e; border-radius: 12px;
  font: 900 16px/1 Outfit, 'DM Sans', sans-serif;
  cursor: pointer; box-shadow: 0 2px 0 rgba(0, 0, 0, .45);
  transition: transform .12s ease, box-shadow .12s ease;
}
#bookingModal #fp-pos-cta:hover { transform: translateY(-1px); box-shadow: 0 4px 0 rgba(0, 0, 0, .45); }
#bookingModal #fp-pos-cta:active { transform: translateY(0); box-shadow: 0 1px 0 rgba(0, 0, 0, .45); }
#bookingModal #fp-pos-cta:focus-visible { outline: 3px solid var(--pos-ink); outline-offset: 2px; }
#bookingModal #fp-pos-cta[disabled] { opacity: .6; cursor: progress; }
/* The price is the second half of the label: same button ink, but set apart so
   "€11,96" reads as an amount and not as the last word of the sentence. */
#bookingModal #fp-pos-cta #booking-price {
  font-variant-numeric: tabular-nums; letter-spacing: .01em; white-space: nowrap;
  font-size: 17px;
}

/* --- the trust row ------------------------------------------------------- */
#bookingModal .fp-pos-trust {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 12px 0 0; padding: 0 18px;
}
/* Direct children only: each chip now holds an icon span and a text span
   (the text is what the dictionary pass writes into, so it cannot share a
   node with the glyph), and a descendant selector would chip the inner two
   as well — four chips became twelve. */
#bookingModal .fp-pos-trust > span {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 9px;
  border: 1px solid var(--pos-edge); border-radius: 999px;
  font: 700 10.5px/1 Outfit, sans-serif; letter-spacing: .01em;
  color: var(--pos-dim); background: rgba(255, 255, 255, .04);
}
#bookingModal .fp-pos-trust > span b { color: var(--pos-ink); font-weight: 800; }
#bookingModal .fp-pos-trust i { font-style: normal; font-size: 11px; line-height: 1; }

/* The tickets-sold chip is the site's animated counter (app-core wraps
   span[data-i18n="trust.tickets_sold"] in b.fp-tnum and re-runs it after every
   dictionary apply). Its own gradient is navy on navy — 1.16:1 against this
   plate — so the count gets the amber treatment the dark-mode rule gives it
   elsewhere, and the chip keeps a legible number. */
#bookingModal .fp-pos-trust .fp-tnum {
  background-image: linear-gradient(100deg, #ffe66d, #e0b25c);
  -webkit-background-clip: text; background-clip: text;
}

/* --- crypto, demoted to what it is: a second way to pay ------------------ */
#bookingModal .fp-pos-crypto {
  display: flex; align-items: center; gap: 7px;
  margin: 12px 18px 0; padding-top: 12px;
  border-top: 1px dashed var(--pos-dash);
}
#bookingModal .fp-pos-crypto a, #bookingModal .fp-pos-crypto button {
  appearance: none; background: none; border: 0; padding: 0;
  font: 700 12.5px/1.4 Outfit, sans-serif; color: var(--pos-amber);
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
}
#bookingModal .fp-pos-crypto small { font-size: 11.5px; color: var(--pos-dim); }

/* --- the ticket face on this plate --------------------------------------
   The face is PAPER (--etk-paper: #fff, navy ink) and it is the point of the
   band: a real stub pasted into the night counter. It therefore keeps every
   one of its own inks — repainting them in the counter's cream was measured at
   1.17:1 for the "Name + Email" tag and 1.50:1 for the Stripe tag on that white
   ground, i.e. two invisible strings. Only the seating is adjusted here. */
#bookingModal .fp-etk--mini {
  margin: 0; border-radius: 0;
  border-bottom: 1px dashed var(--pos-dash);
}

/* --- the dead review step is gone, not merely hidden --------------------- */
#bookingModal #booking-confirmation { display: none !important; }

/* --- what the buyer is told under the button ---------------------------- */
#bookingModal .fp-pos-secure {
  margin-top: 9px; text-align: center;
  font: 600 11px/1.4 Outfit, sans-serif; color: var(--pos-dim);
}

/* --- narrow columns ------------------------------------------------------
   One column for the field pairs (two 1fr columns inside a 354px card clip
   every value being checked), a taller CTA, and the trust row scrolling
   horizontally rather than stacking four chips into the fold. */
@media (max-width: 560px) {
  #bookingModal .fp-pos-grid { grid-template-columns: 1fr; }
  #bookingModal .fp-pos-head { padding: 14px 14px 12px; gap: 10px; }
  /* the plate is 354px wide here: the stub keeps the price legible without
     pushing the brand off the band */
  #bookingModal .fp-pos-price { font-size: 14px; padding: 8px 10px 7px; }
  #bookingModal .fp-pos-fields { padding: 14px 14px 2px; }
  #bookingModal .fp-pos-foot { padding: 11px 14px 13px; }
  /* Two rows rather than one scrolling row: the row is a trust statement, and
     measured at 390px it was 662px wide inside a 352px column, so a third of it
     (the tickets-sold count among them) sat invisible behind a scroll with no
     affordance. Wrapping costs ~30px of height above a pinned bar that cannot
     move, and shows all four facts. */
  #bookingModal .fp-pos-trust { padding: 0 14px; gap: 5px; }
  #bookingModal .fp-pos-trust > span { flex: 0 0 auto; padding: 4px 8px; font-size: 10px; }
  #bookingModal .fp-pos-trust i { font-size: 10px; }
  #bookingModal .fp-pos-crypto { margin: 10px 14px 0; }
  #bookingModal #fp-pos-cta { min-height: 54px; font-size: 16.5px; }
}

/* --- motion off ---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  #bookingModal #fp-pos-cta { transition: none; }
  #bookingModal #fp-pos-cta:hover, #bookingModal #fp-pos-cta:active { transform: none; }
}

/* --- dark mode ------------------------------------------------------------
   The page-level dark mode already repaints the overlay; the counter is the
   same night plate either way, so it only needs its hairlines to stay visible. */
body.dark-mode #bookingModal .modal-content { box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .9) !important; }

/* --- dest-first hero picker (2026-10-18) ----------------------------------
   The band's destination-first journey: pick a country → the tier board
   (flag, tier chip, rule) appears → buy. Lives in instrument.css, not the EN
   inline block, so the 41 locale homepages get the picker without another
   propagate-a-stylesheet incident: they all link this file already.
   Copy resolves from dictionary keys every locale already carries
   (map.pick_title / banner.cta / guides.chip_* / guides.open); per-country
   name and rule fall back to English exactly like the high-risk list.
   The band scope (--i-navy/--i-amber/--i-sans/--i-mono) is re-declared on
   .inst-band in this stylesheet; the tier literals stand on their own so the
   picker needs nothing from the EN-only .inst-doc palette. */
.inst-dest { margin: 6px 0 0; max-width: 640px; }
.inst-dest-ask {
  display: block; margin: 18px 0 8px;
  font: 800 12px/1.2 var(--i-sans, Outfit, sans-serif);
  letter-spacing: .08em; text-transform: uppercase; color: var(--i-amber, #FFE66D);
}
.inst-dest-row { display: flex; gap: 10px; align-items: stretch; flex-wrap: wrap; }
.inst-dest-selwrap { position: relative; flex: 1 1 300px; min-width: 0; display: flex; align-items: center; }
.inst-dest-icn { position: absolute; left: 12px; font-size: 16px; pointer-events: none; }
.inst-dest-sel {
  width: 100%; appearance: none; min-height: 52px;
  padding: 10px 38px 10px 40px; border-radius: 10px;
  border: 1.5px solid rgba(255, 230, 109, .45); background: #14142a; color: #fff;
  font: 700 16px/1.2 var(--i-sans, Outfit, sans-serif); cursor: pointer;
}
.inst-dest-sel:focus-visible { outline: 2px solid var(--i-amber, #FFE66D); outline-offset: 2px; }
.inst-dest-sel option { background: #14142a; color: #fff; }
.inst-dest-chev { position: absolute; right: 12px; pointer-events: none; color: var(--i-amber, #FFE66D); font-size: 13px; }
.inst-dest-buy {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 52px; padding: 12px 22px; border: 2px solid var(--i-amber, #FFE66D);
  border-radius: 12px; background: var(--i-amber, #FFE66D); color: var(--i-navy, #1a1a2e);
  font: 900 17px/1.1 var(--i-sans, Outfit, sans-serif); cursor: pointer;
  box-shadow: 4px 4px 0 rgba(255, 230, 109, .28);
  transition: transform .15s ease, box-shadow .15s ease;
}
.inst-dest-buy:hover { transform: translate(-1px, -1px); box-shadow: 6px 6px 0 rgba(255, 230, 109, .35); }
.inst-dest-buy:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }
.inst-dest-warn {
  display: flex; gap: 12px; align-items: flex-start;
  margin-top: 12px; padding: 13px 15px; border-radius: 10px;
  background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .18);
}
.inst-dest-warn.is-high { border-left: 4px solid #c0392b; }
.inst-dest-warn.is-medium { border-left: 4px solid #d98c00; }
.inst-dest-warn.is-low { border-left: 4px solid #2e8b57; }
.inst-dest-flag { font-size: 26px; line-height: 1; flex: 0 0 auto; }
.inst-dest-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.inst-dest-main b { font: 900 17px/1.25 var(--i-sans, Outfit, sans-serif); color: #fff; }
.inst-dest-tier { font: 800 11px/1.3 var(--i-mono, monospace); letter-spacing: .08em; text-transform: uppercase; }
.inst-dest-warn.is-high .inst-dest-tier { color: #ffb3a7; }
.inst-dest-warn.is-medium .inst-dest-tier { color: #f5c877; }
.inst-dest-warn.is-low .inst-dest-tier { color: #9fd9ba; }
.inst-dest-rule { font-size: 13px; line-height: 1.5; color: rgba(255, 255, 255, .78); }
.inst-dest-guide {
  align-self: center; flex: 0 0 auto;
  font: 800 13px/1.2 var(--i-sans, Outfit, sans-serif); color: var(--i-amber, #FFE66D);
  text-decoration: underline; text-underline-offset: 3px;
}
.inst-dest-guide:hover { color: #fff; }
@media (max-width: 560px) {
  .inst-dest-buy { flex: 1 1 100%; }
  .inst-dest-guide { align-self: flex-end; }
}
@media (prefers-reduced-motion: reduce) { .inst-dest-buy { transition: none; } }
/* RTL: body[dir="rtl"] flips are handled by logical properties being absent
   here — the flex row and grid order survive as-is; only the absolute chevron
   and icon swap sides. */
body[dir="rtl"] .inst-dest-icn { left: auto; right: 12px; }
body[dir="rtl"] .inst-dest-sel { padding: 10px 40px 10px 38px; }
body[dir="rtl"] .inst-dest-chev { right: auto; left: 12px; }

/* ══ touch targets (measured 2026-10-29) ══════════════════════════════════
 * A render pass at 390/768/1280 found three controls under WCAG 2.5.8's 24px
 * minimum and two primary controls under 44px. Fixed here rather than in the
 * pages because these rules are shared: the rescue bar is baked into all 43
 * homepages (no generator owns that CSS, so an override beats 43 edits), the
 * guide footers are inline-styled in 1,539 pages, and the strip is here.
 *
 * Deliberately NOT touched: inline links inside a sentence (2.5.5 exempts
 * them), and the text links in nav/footer columns that already clear 24px on
 * both axes.
 */

/* The strip's buy stub drew 41.8px tall — the loudest control on the page, just
   under the 44px thumb size. min-height, not padding: its text must not move. */
.inst-strip nav a.inst-strip-cta {
  min-height: 44px;
  align-items: center;
  box-sizing: border-box;
}

/* The rescue bar's dismiss was 16.3x23 (a 15px glyph with 4px of padding) in all
   43 homepages. .fp-rescue .fp-rescue-x is more specific than the page's own
   .fp-rescue-x, so this lands without !important and without touching them. */
.fp-rescue .fp-rescue-x {
  min-width: 32px;
  min-height: 32px;
  padding: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  top: 2px;
  inset-inline-end: 4px;
}

/* Guide footers: links are inline-styled at 13px, which lands at 20.1px tall.
   Inline padding grows the hit box; the flex row's 8px gap still separates
   them, and nothing in the inline style sets display or padding. */
.inst .prose footer a {
  padding-block: 3px;
}

/* Second sweep (320–1920 × 20 page families, 2026-10-29) found four more rows
   of adjacent targets under the 24px floor. Adjacent is the part that matters:
   2.5.8's spacing exception only rescues an isolated control, and none of these
   is isolated. */

/* Doc pages' brand link drew 23.3px tall in the nav row — 0.7px under, and the
   whole row is targets, so it gets the padding rather than an exception. */
.inst .doc .nav .brand {
  padding-block: 3px;
}

/* The four non-standard country masters (belgium, korea, schengen-area,
   thailand-digital-nomad) inline-style their own footer, so they are not
   inside .prose and the rule above never reached them: 17px tall. 
   padding-block:5px puts the 13px text at 27px. */
.inst footer.fp-gf-foot a {
  display: inline-block;
  padding-block: 5px;
}

/* Widget triggers the guides inject themselves — the crypto explainer button
   and the eSIM CTA link — are text-only boxes 16px tall. One shared rule beats
   editing them into 1,539 pages. */
.inst .fp-what-crypto,
.inst .fp-what-crypto-mini,
.inst .esim-cta-row a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

/* ===========================================================================
   GUIDE END — one close, one price (owner 2026-11-01)
   "the country guides need a repolish in formating leaning to the end .. when
   you scroll down it gets confusing with too many $7 etc etc ... make it more
   nice globally"

   Measured cause (Thailand master, preview server 8897, own JS run). The last
   stretch of a guide was five blocks stacked with no hierarchy:
     .prose > footer   navy slab carrying the © line (397px tall at 390px)
     Latest / Partners bare 17px h2 + grids, sitting AFTER that footer
     .stub             ticket panel: form + "Get the ticket — $7.00"
     .passcta          "FlightProof Pass … $7 flat" + a SECOND "Get the ticket"
     .foot             a second navy band, directly under it
   and #fp-buybar adds a third price while scrolling — measured at 390px:
   $7.00 at 14034, $7 flat at 14374, another "Get the ticket" at 14404.

   The markup is shared by 2,352 guide pages through two generators, so the
   order is left alone and the TAIL is taught to read as a sequence instead:
   article sign-off → "more" → exactly one purchase close → ticket-back footer.
   Inside the close there is one primary action and one price voice; the Pass
   becomes the receipt tail of the ticket rather than an offer competing with
   it. Nothing is hidden — every link, price and button stays on the page. */

/* --- 1. the article sign-off is a card, not a page-ending slab ------------
   The four non-standard masters aside, this footer is inline-styled in the
   markup (background/color/padding/border-top/margin-top), which is why the
   properties those attributes set need !important. It sits INSIDE .prose with
   Latest and Partners still below it, so a full-bleed navy slab with a © line
   was reading as the end of the page and making everything after it look like
   stray leftovers. Contained to the prose measure it reads as the end of the
   ARTICLE, which is what it is. */
.inst .prose > footer {
  margin: 32px 0 0 !important;
  padding: 22px 24px !important;
  border: 0 !important;
  border-top: 3px solid var(--amber, #FFE66D) !important;
  border-radius: 8px;
  box-shadow: 4px 4px 0 rgba(26, 26, 46, .16);
  background: var(--navy, #1a1a2e) !important;
  color: #fff !important;
}
.inst .prose > footer > div { gap: 12px 20px !important; }
.inst .prose > footer p { max-width: none !important; font-size: 12px !important; }
.inst .prose > footer > div:last-child {
  padding-top: 14px !important; margin-top: 14px !important;
}

/* --- 2. "Latest" / "Partners" become deliberate sections, not bare h2s ----
   :has() keeps this to the two headings that actually own a grid below them,
   so no other h2 in the guide changes size. */
.inst .main > h2:has(+ .news),
.inst .main > h2:has(+ .partners) {
  display: flex; align-items: center; gap: 12px;
  margin: 30px 0 10px;
  font: 800 9.5px/1 var(--mono, "JetBrains Mono"), ui-monospace, monospace;
  letter-spacing: 1.6px; text-transform: uppercase; color: var(--dim);
}
.inst .main > h2:has(+ .news)::after,
.inst .main > h2:has(+ .partners)::after {
  content: ""; flex: 1 1 auto;
  border-top: 1px dashed var(--dash, #d7d2c2);
}
.inst .main > h2:has(+ .partners) { margin-top: 22px; }

/* --- 3. one purchase close, one price voice ------------------------------- */
/* The Pass headline was clamp(22px, 4vw, 34px) — bigger than any h2 in the
   guide and the loudest thing in the last screen, on a band whose job is to
   close the document. It is a receipt line now, not a second headline. */
.inst .pass-code { font-size: clamp(17px, 2.4vw, 23px); margin: 3px 0 9px; }
/* 24px display figure → mono caption. The ticket's own $7 above stays the
   loud statement; two 24px+ prices in one screen is the repetition the owner
   was seeing. */
.inst .passcta .pass-price {
  font: 800 12.5px/1.5 var(--mono, "JetBrains Mono"), ui-monospace, monospace;
  letter-spacing: .12em; text-transform: uppercase; color: var(--dim);
}
.inst .passcta .pass-price small { font-size: 10px; letter-spacing: .1em; }
/* Amber is the primary-CTA colour (.buy, #fp-buybar .bb-btn). A second amber
   button in the same screen made the Pass read as a rival offer instead of the
   same purchase seen once more. */
.inst .passcta .pass-btn {
  background: transparent; color: var(--navy);
  border: 1.5px solid var(--navy); box-shadow: none;
}
.inst .passcta .pass-btn:hover,
.inst .passcta .pass-btn:active {
  background: var(--navy); color: var(--amber, #FFE66D);
  transform: none; box-shadow: none;
}

@media (min-width: 961px) {
  .inst .passcta {
    margin: clamp(20px, 2.4vw, 26px) 0 0;
    box-shadow: 3px 3px 0 rgba(26, 26, 46, .12);
  }
}

/* --- 4. stacked: the ticket and the Pass are ONE card ---------------------
   Below 960 the rail drops under the article, so the phone tail ran
   Partners → ticket panel (form + $7 buy) → Pass band ($7 flat + a second
   buy) as two separate cards 47px apart: the exact "too many $7" moment.
   Joined with one border and one shadow they read as a ticket and its stub,
   which is also what the copy says. */
@media (max-width: 960px) {
  .inst .stub {
    margin-top: clamp(22px, 4vw, 30px);
    border: 1px solid var(--edge, #e2ddcf);
    border-bottom: 0;
    border-radius: 10px 10px 0 0;
    box-shadow: 4px 4px 0 rgba(26, 26, 46, .10);
  }
  .inst .passcta {
    margin: 0;
    border-top: 2px dashed var(--dash, #d7d2c2);
    border-radius: 0 0 10px 10px;
    box-shadow: 4px 4px 0 rgba(26, 26, 46, .10);
  }
  .inst .pass-stub { border-top: 0; }
}
