/* webapp/public/assets/css/app.css
   Layered alongside Tailwind (CDN in dev, compiled in production).
   Keep custom rules minimal — prefer utility classes inside templates. */

:root {
    color-scheme: light;
}

/* Inter only, house rule. Inline code still has to read as a literal string,
   so it is set apart by a tinted chip and tabular figures rather than by a
   monospace family. */
/* Scoped to body so it outranks Tailwind's preflight, which sets a monospace
   stack on bare `code` and is injected as a <style> after this file's <link>,
   winning on source order at equal specificity. */
body code, body kbd, body samp, body pre {
    font-family: inherit;
    font-size: 0.92em;
    font-variant-numeric: tabular-nums;
    background: var(--ink-100);
    border-radius: 4px;
    padding: 0.1em 0.35em;
    color: var(--ink-700);
}
/* Inside the dark code block the chip would fight the syntax colours. */
.code-block code { background: none; padding: 0; color: inherit; }
/* A key cap reads as a key, not as a quoted string, so it keeps a border. */
body kbd {
    border: 1px solid var(--ink-200);
    box-shadow: 0 1px 0 var(--ink-200);
    font-weight: 600;
}

/* Auto-dismiss flashes after a beat. JS handles the actual removal —
   this gives a soft exit transition. */
#flashes > div {
    transition: opacity 200ms ease, transform 200ms ease;
}
#flashes > div.is-dismissed {
    opacity: 0;
    transform: translateY(-4px);
}

/* docs/api.md rendered by webapp/app/core/Markdown.php. Tailwind CDN
   doesn't ship the typography plugin, so we style the markdown output
   directly. Scoped under .docs-prose so it doesn't leak. */
.docs-prose h1 { font-size: 1.875rem; font-weight: 600; margin: 1.5rem 0 0.75rem; color: rgb(15 23 42); }
.docs-prose h2 { font-size: 1.5rem;   font-weight: 600; margin: 1.5rem 0 0.5rem;  color: rgb(15 23 42); border-bottom: 1px solid rgb(226 232 240); padding-bottom: 0.35rem; }
.docs-prose h3 { font-size: 1.15rem;  font-weight: 600; margin: 1.25rem 0 0.5rem; color: rgb(30 41 59); }
.docs-prose p  { margin: 0.5rem 0; line-height: 1.65; color: rgb(51 65 85); }
.docs-prose hr { border: 0; border-top: 1px solid rgb(226 232 240); margin: 1.5rem 0; }
.docs-prose code {
    background: rgb(241 245 249);
    padding: 0.1rem 0.35rem;
    border-radius: 0.25rem;
    font-size: 0.92em;
    color: rgb(15 23 42);
}
.docs-prose pre {
    background: rgb(15 23 42);
    color: rgb(241 245 249);
    padding: 1rem;
    border-radius: 0.5rem;
    overflow-x: auto;
    margin: 0.75rem 0;
    font-size: 0.875rem;
    line-height: 1.55;
}
.docs-prose pre code { background: transparent; color: inherit; padding: 0; }
.docs-prose table {
    width: 100%;
    border-collapse: collapse;
    margin: 1rem 0;
    font-size: 0.9rem;
}
.docs-prose th, .docs-prose td {
    border: 1px solid rgb(226 232 240);
    padding: 0.5rem 0.75rem;
    text-align: left;
}
.docs-prose th { background: rgb(248 250 252); font-weight: 600; }
.docs-prose strong { color: rgb(15 23 42); }
.docs-prose h2, .docs-prose h3 { scroll-margin-top: 84px; }

/* ─── API REFERENCE PAGE (/developers/docs) ──────────────────────────── */
#screen-docs { max-width: none; }
.docs-layout { display: grid; grid-template-columns: minmax(0, 1fr) 220px; gap: 40px; align-items: start; }
.docs-toc { position: sticky; top: 84px; align-self: start; }
.docs-toc-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-400); margin-bottom: 12px; }
.docs-toc nav { display: flex; flex-direction: column; border-left: 1px solid var(--ink-200); max-height: calc(100vh - 140px); overflow-y: auto; }
.docs-toc a { font-size: 12.5px; color: var(--ink-500); padding: 5px 0 5px 14px; margin-left: -1px; border-left: 2px solid transparent; line-height: 17px; transition: color .15s, border-color .15s; }
.docs-toc a:hover { color: var(--ink-900); }
.docs-toc a.lvl-3 { padding-left: 26px; font-size: 12px; color: var(--ink-400); }
.docs-toc a.active { color: var(--brand-600); border-left-color: var(--brand-600); font-weight: 600; }
@media (max-width: 1024px) {
  /* minmax(0,1fr) lets the column shrink below content width so long <pre>
     code blocks scroll internally instead of blowing out / getting clipped. */
  .docs-layout { grid-template-columns: minmax(0, 1fr); }
  .docs-toc { display: none; }
}
@media (max-width: 600px) {
  .docs-prose.vc-card { padding: 18px !important; }
  .docs-prose h1 { font-size: 1.4rem; }
  .docs-prose h2 { font-size: 1.2rem; }
  .docs-prose table { display: block; overflow-x: auto; white-space: nowrap; }
}
/* Long inline code / paths shouldn't force the column wider. */
.docs-prose :not(pre) > code { overflow-wrap: anywhere; word-break: break-word; }

/* ════════════════════════════════════════════════════════════════════
   VisaCheki design system — ported verbatim from the approved UI mockup
   (visacheki_ui.html). Component classes used by the app shell and the
   converted views. Tailwind remains loaded for not-yet-converted pages.
   ════════════════════════════════════════════════════════════════════ */
:root {
  /* Premium Corporate Blue/Slate Palette */
  --brand-50: #EEFBF6;
  --brand-100: #D4F4E6;
  --brand-400: #34D399;
  --brand-500: #1D9E75;
  --brand-600: #00966D;
  --brand-700: #007855;
  --brand-950: #002B20;

  --accent-50: #EEFBF6;
  --accent-100: #D4F4E6;
  --accent-500: #00BA88;
  --accent-600: #00966D;
  --accent-700: #007855;
  --accent-800: #045D45;

  --ink-950: #0F172A;
  --ink-900: #1E293B;
  --ink-700: #334155;
  --ink-600: #475569;
  --ink-500: #64748B;
  --ink-400: #94A3B8;
  --ink-300: #CBD5E1;
  --ink-200: #E2E8F0;
  --ink-100: #F1F5F9;
  --ink-50: #F8FAFC;
  --bg: #FAF9F7;          /* warm paper: neutral without a nameable hue */
  --surface: #ffffff;
  --surface-2: #FAF9F7;

  --amber-50: #FFFBEB;
  --amber-100: #FEF3C7;
  --amber-600: #D97706;
  --amber-700: #B45309;
  --amber-800: #92400E;
  --amber-900: #78350F;

  --rose-50: #FFF1F2;
  --rose-100: #FFE4E6;
  --rose-600: #E11D48;
  --rose-700: #BE123C;

  /* Sharper Enterprise Radii. Every surface that reads as a card is 6px, so
     lg and xl collapse onto md rather than each keeping a step of their own.
     sm stays 4px: it dresses pills, tags and badges, which are chrome, not
     cards. Deliberate non-card shapes (the bottom nav, the trip dock, bottom
     sheets, avatars) carry their own literal radii and are untouched. */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 6px;
  --radius-xl: 6px;
  --radius-btn: 6px;   /* shared radius for all pressable CTA buttons */
}
/* Inter, self-hosted. This is the same latin subset Google Fonts was serving,
   but as one variable file covering the whole 100-900 axis, so the six weights
   the app uses cost one request instead of six faces from a third-party host.
   U+2192, the arrow in "NG -> GB", is absent here and was absent from Google's
   subset too, so it falls back to a system glyph exactly as it always has. */
@font-face {
  font-family: 'Inter';
  src: url('/assets/fonts/inter-var.woff2?v=3100e775') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Windows ships no font that draws flags. Segoe UI Emoji renders a regional
   indicator pair as the two letters it is built from, so every flag on the site
   came out as "NG" or "GB" in Edge, Chrome and Firefox alike. That is a
   Microsoft decision rather than a browser bug, and the only fix is to bring the
   glyphs ourselves.
   unicode-range pins this to the regional indicator block, so it is the only
   thing this font is ever asked to draw and no other text on the page changes.
   It has to come first in the stack: put it last and Windows never reaches it,
   because Segoe UI Emoji does claim these codepoints, it just draws letters.
   Flag artwork is Twemoji, CC-BY 4.0, packaged by country-flag-emoji-polyfill. */
@font-face {
  font-family: 'Country Flags';
  src: url('/assets/fonts/TwemojiCountryFlags-subset.woff2?v=b3e87b9c') format('woff2');
  font-display: swap;
  unicode-range: U+1F1E6-1F1FF;
}

html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; overscroll-behavior-y: none; }

/* Font smoothing, scoped to the screens it actually helps.
   `antialiased` turns OFF the platform's subpixel rendering. On a 2x screen
   there is no subpixel detail to lose and the lighter stroke is the better
   looking one, so it stays there. At 1x, and at the 1.25x / 1.5x / 1.75x the
   fractional display scales produce, it was throwing away the horizontal
   resolution ClearType and its equivalents provide, which is exactly the range
   where text has the fewest pixels to work with. Those now get what the
   platform renders natively. */
@media (min-resolution: 192dpi), (-webkit-min-device-pixel-ratio: 2) {
  body.vc-app {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
}
body.vc-app {
  font-family: 'Country Flags', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  background: var(--bg);
  color: var(--ink-950);
  letter-spacing: -0.01em;
  overflow-x: hidden;
  /* App-feel: no grey tap flash, no rubber-band overscroll, no double-tap-zoom delay */
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior-y: none;
  touch-action: manipulation;
}
/* UI chrome shouldn't be text-selectable like a document */
.topbar, .bottomnav, .vc-app button, .nav-btn, .desktop-nav-btn, .header-cta, .header-btn, .action-label {
  -webkit-user-select: none; user-select: none;
}
.vc-app button { font-family: inherit; cursor: pointer; transition: all 0.2s; }
.vc-app button:active { transform: scale(0.98); }
.vc-app input, .vc-app select { font-family: inherit; }
.vc-app ::selection { background: rgba(0,186,136,0.15); }
/* Only strip underlines — do NOT force color:inherit, or it overrides
   Tailwind text-* utilities (e.g. text-white on dark link-buttons). */
.vc-app a { text-decoration: none; }

/* ─── GLOBAL CONTROL LAYER ────────────────────────────────────────────
   Restyles native form controls and the most common Tailwind container
   patterns to the mockup design on EVERY page, overriding Tailwind CDN
   utilities via higher-specificity .vc-app selectors. This is what makes
   forms/inputs/buttons consistent app-wide without rewriting every view. */
.vc-app input:not([type=radio]):not([type=checkbox]):not([type=range]):not([type=file]):not([type=submit]):not([type=button]):not([type=hidden]),
.vc-app select,
.vc-app textarea {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--ink-300);
  border-radius: var(--radius-btn);
  padding: 12px 15px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-950);
  line-height: 20px;
  outline: none;
  box-shadow: none;
  transition: border-color 0.2s, box-shadow 0.2s;
  font-feature-settings: "tnum";
}
.vc-app textarea { min-height: 96px; resize: vertical; }
.vc-app select {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394A3B8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 38px;
}
.vc-app input:focus, .vc-app select:focus, .vc-app textarea:focus {
  border-color: var(--accent-500);
  box-shadow: 0 0 0 3px var(--accent-100);
}
.vc-app input::placeholder, .vc-app textarea::placeholder { color: var(--ink-400); font-weight: 400; }
.vc-app input[type=radio], .vc-app input[type=checkbox] {
  width: 16px; height: 16px; accent-color: var(--brand-600); cursor: pointer; flex-shrink: 0;
}
.vc-app input[readonly] { background: var(--ink-50); color: var(--ink-700); }

/* Reusable component primitives for converted views */
.vc-card { background:var(--surface); border:1px solid var(--ink-200); border-radius:var(--radius-xl); padding:24px; box-shadow:0 1px 2px rgba(15,23,42,0.03); }
.vc-card-sm { background:var(--surface); border:1px solid var(--ink-200); border-radius:var(--radius-lg); padding:16px; }
.field { display:block; margin-bottom:16px; }
.field:last-child { margin-bottom:0; }
.field > .label-strong { margin-bottom:8px; }
.hint { font-size:12px; color:var(--ink-500); margin-top:6px; display:block; }
.sub-grid { display:grid; grid-template-columns:96px 96px 1fr auto; gap:12px; align-items:end; }
@media (max-width:560px){
  .sub-grid { grid-template-columns:1fr 1fr; }
  .sub-grid > div:nth-of-type(3) { grid-column:1 / -1; }  /* email field */
  .sub-grid > button { grid-column:1 / -1; width:100%; }
}
.btn-brand {
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  background:var(--brand-600); color:var(--btn-ink, #fff); border:none;
  border-radius:var(--radius-btn); padding:12px 18px;
  font-size:14px; font-weight:600; cursor:pointer;
  box-shadow:0 2px 4px rgba(16,42,67,0.15); transition:background .2s;
}
.btn-brand:hover { background:var(--brand-500); }

/* The one action a requirements page exists to lead to.
   Accent rather than brand because three identical btn-brand buttons shared
   that page, so nothing read as primary. Green already means affirmed here,
   in the verified and "Your trip" pills, so it carries the right sense for
   the confident next step. */
.btn-accent {
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  background:var(--accent-600); color:#fff; border:none;
  border-radius:var(--radius-btn); padding:12px 18px;
  font-size:14px; font-weight:600; cursor:pointer; text-decoration:none;
  box-shadow:0 2px 4px rgba(0,90,65,0.18); transition:background .2s;
}
.btn-accent:hover { background:var(--accent-700); }

/* Full width on its own row, with the data caveat beneath rather than beside.
   Side by side, the button ended up the width of its text and read as one more
   control in a row; on its own row it reads as the next step. */
.ans-f { flex-direction: column; align-items: stretch; gap: 10px; }
.ans-f .btn-accent { width: 100%; }
@media (min-width: 700px) {
  .ans-f { flex-direction: row; align-items: center; gap: 14px; }
  .ans-f .btn-accent { width: auto; min-width: 260px; }
}
.btn-ghost {
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  background:var(--surface); color:var(--ink-700); border:1px solid var(--ink-300);
  border-radius:var(--radius-btn); padding:11px 16px; font-size:13px; font-weight:600; cursor:pointer;
  transition:all .15s;
 line-height: 20px;}
.btn-ghost:hover { background:var(--ink-50); border-color:var(--ink-400); }
@keyframes vcspin { to { transform: rotate(360deg); } }
.vc-spinner { display:inline-block; width:18px; height:18px; border-radius:50%; border:2px solid var(--ink-200); border-top-color:var(--brand-600); animation:vcspin .7s linear infinite; vertical-align:middle; margin-right:8px; }
/* App-friendly assessment preloader (overlay modal) */
.vc-loader-overlay { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(15, 23, 42, .55); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); animation: vc-loader-fade .2s ease; }
.vc-loader-overlay.hidden { display: none; }
.vc-loader-card { width: 100%; max-width: 340px; background: var(--surface); border-radius: var(--radius-lg); padding: 32px 28px; text-align: center; box-shadow: 0 24px 64px rgba(2, 6, 23, .35); animation: vc-loader-rise .28s cubic-bezier(.4, 0, .2, 1); }
.vc-loader-orb { position: relative; width: 74px; height: 74px; margin: 0 auto 20px; }
.vc-loader-orb-ring { position: absolute; inset: 0; border-radius: 50%; border: 3px solid transparent; border-top-color: var(--brand-500); animation: vcspin 1s linear infinite; }
.vc-loader-orb-ring.two { inset: 9px; border-top-color: var(--accent-500); animation-duration: 1.6s; animation-direction: reverse; }
.vc-loader-orb-core { position: absolute; inset: 19px; border-radius: 50%; background: var(--brand-50); display: flex; align-items: center; justify-content: center; color: var(--brand-600); font-size: 24px; }
.vc-loader-title { font-size: 16px; font-weight: 700; color: var(--ink-950); margin-bottom: 6px; }
.vc-loader-msgs { position: relative; height: 18px; margin-bottom: 20px; }
.vc-loader-msgs span { position: absolute; inset: 0; font-size: 13px; color: var(--ink-500); opacity: 0; animation: vc-loader-msg 9s infinite;  line-height: 20px;}
.vc-loader-msgs span:nth-child(1) { animation-delay: 0s; }
.vc-loader-msgs span:nth-child(2) { animation-delay: 3s; }
.vc-loader-msgs span:nth-child(3) { animation-delay: 6s; }
.vc-loader-track { height: 4px; border-radius: 4px; background: var(--ink-100); overflow: hidden; }
.vc-loader-track > i { display: block; height: 100%; width: 40%; border-radius: 4px; background: var(--brand-500); animation: vc-loader-bar 1.4s ease-in-out infinite; }
@keyframes vc-loader-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes vc-loader-rise { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes vc-loader-msg { 0% { opacity: 0; transform: translateY(5px); } 4%, 30% { opacity: 1; transform: none; } 34%, 100% { opacity: 0; transform: translateY(-5px); } }
@keyframes vc-loader-bar { 0% { transform: translateX(-120%); } 100% { transform: translateX(320%); } }

.page-head { margin-bottom:24px; }
.page-head h1 { font-size:26px; font-weight:700; letter-spacing:-0.02em; color:var(--ink-950); margin-bottom:8px; line-height:30px; }
.page-head p { font-size:14px; color:var(--ink-500); line-height:22px; max-width:640px; }
.crumbs { font-size:13px; color:var(--ink-400); margin-bottom:16px; display:flex; align-items:center; gap:8px; flex-wrap:wrap;  line-height: 20px;}
.crumbs a { color:var(--ink-500); }
.crumbs a:hover { color:var(--ink-900); }
.pill { display:inline-flex; align-items:center; gap:5px; font-size:11px; font-weight:600; padding:3px 9px; border-radius:var(--radius-sm); letter-spacing:0.02em;  line-height: 16px;}
.pill.green { background:var(--accent-50); color:var(--accent-700); border:1px solid var(--accent-100); }
.pill.amber { background:var(--amber-50); color:var(--amber-800); border:1px solid var(--amber-100); }
.pill.rose  { background:var(--rose-50);  color:var(--rose-700);  border:1px solid var(--rose-100); }
.pill.slate { background:var(--ink-100);  color:var(--ink-600);   border:1px solid var(--ink-200); }

/* ── Arrows that stand inside a run of text ────────────────────────────────
   U+2192 is not in the Inter subset and was not in Google's either, so every
   arrow on the site fell back to whatever the operating system draws. On the
   route heading that meant a long thin glyph at a different weight from the
   two country names beside it, and a different shape on each platform.

   These are the Lucide arrow instead, sized in em so one class works from a
   13px breadcrumb to a 24px page title. */
.txt-arrow   { font-size: 0.85em; }
/* Between two endpoints the arrow is punctuation, not content: the two country
   names are the thing being read, so it steps back to the hairline grey the
   admin route tables already use. */
.route-arrow { font-size: 0.8em; color: var(--ink-300); }
/* A card whose whole job is to hold a table.
   Written as a class because it was six inline styles that had drifted into
   three spellings, half of them `overflow:hidden`. Hidden is right for the
   card's rounded corners and wrong for the table inside it: on a phone every
   column past the card edge was simply unreachable, with nothing to scroll.
   The table keeps its own width and the card scrolls sideways instead. */
.table-card { padding: 0; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-card > table { border: 0; border-radius: 0; }
/* Below this the columns stop being readable, so scrolling is the right answer
   rather than squeezing them further. */
.table-card > .vc-table, .table-card > .an-table { min-width: 560px; }

.vc-table { width:100%; border-collapse:collapse; font-size:13px; background:var(--surface); border:1px solid var(--ink-200); border-radius:var(--radius-lg); overflow:hidden; }
.vc-table th { text-align:left; font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:0.06em; color:var(--ink-500); background:var(--ink-50); padding:10px 14px; border-bottom:1px solid var(--ink-200); }
.vc-table td { padding:12px 14px; border-bottom:1px solid var(--ink-100); color:var(--ink-700); }
.vc-table tr:last-child td { border-bottom:none; }
.dl-grid { display:grid; grid-template-columns:1fr 1fr; gap:20px 32px; }
.dl-grid .dl-key { font-size:11px; font-weight:600; color:var(--ink-500); text-transform:uppercase; letter-spacing:0.06em; }
.dl-grid .dl-val { font-size:15px; font-weight:600; color:var(--ink-950); margin-top:3px; }
@media (max-width:600px){ .dl-grid{ grid-template-columns:1fr; gap:14px; } }

/* AT A GLANCE — icon-row spec list (used by requirement-card) */
.glance-grid { display:grid; grid-template-columns:1fr 1fr; gap:0 28px; }
.glance-row { display:flex; align-items:center; gap:11px; padding:11px 0; border-bottom:1px solid var(--ink-100); }
.glance-ico { width:30px; height:30px; flex-shrink:0; border-radius:var(--radius-md); background:var(--brand-50); border:1px solid var(--brand-100); color:var(--brand-600); display:flex; align-items:center; justify-content:center; font-size:15px; }
.glance-label { flex:1; min-width:0; font-size:12.5px; color:var(--ink-500); line-height: 19px;}
.glance-value { font-size:14px; font-weight:600; color:var(--ink-950); white-space:nowrap; }
.glance-row .pill { flex-shrink:0; }
@media (max-width:760px){ .glance-grid { grid-template-columns:1fr; } }
/* Global mobile collapses — override inline grid-template styles (hence
   !important) so 2-col forms and stat grids never crowd on small screens. */
@media (max-width:600px){
  .port-form-grid { grid-template-columns:1fr !important; }
  .pt-stats       { grid-template-columns:repeat(2,1fr) !important; }
  .docs-list      { grid-template-columns:1fr !important; }
}
@media (max-width:1023px){ .elig-results, #screen-results-inner, .req-grid{ grid-template-columns:1fr !important; } }

/* ─── TAILWIND COMPAT BRIDGE ──────────────────────────────────────────
   Keeps any not-yet-hand-converted view on-brand: remap the old emerald
   accent to the VisaCheki green, and give the common rounded-xl/white
   card pattern the mockup's softer border + shadow. */
.vc-app .text-emerald-600, .vc-app .text-emerald-700, .vc-app .text-emerald-800,
.vc-app .text-emerald-900 { color: var(--brand-600) !important; }
.vc-app .bg-emerald-600, .vc-app .bg-emerald-700 { background-color: var(--brand-600) !important; }
.vc-app .bg-emerald-500 { background-color: var(--accent-500) !important; }
.vc-app .bg-emerald-50  { background-color: var(--brand-50)  !important; }
.vc-app .border-emerald-200, .vc-app .border-emerald-300 { border-color: var(--brand-100) !important; }
.vc-app .hover\:bg-emerald-700:hover { background-color: var(--brand-700) !important; }
.vc-app .ring-emerald-500, .vc-app .focus\:ring-emerald-500:focus { --tw-ring-color: var(--accent-500) !important; }

/* CONFIDENCE RING — radial gauge on the requirements rail */
.conf-ring { position: relative; width: 130px; height: 130px; margin: 0 auto; }
.conf-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.conf-ring-text {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 30px; font-weight: 700; color: var(--ink-950); letter-spacing: -0.02em;
  font-feature-settings: "tnum";
}
.conf-ring-text small { font-size: 13px; font-weight: 600; color: var(--ink-400); margin-left: 1px; }

/* TOOL TILES — the requirements-page "what next" grid */
.tool-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.tool-tile {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 16px; background: var(--surface); border: 1px solid var(--ink-200);
  border-radius: var(--radius-lg); transition: transform .2s, box-shadow .2s, border-color .2s;
}
.tool-tile:hover { transform: translateY(-2px); box-shadow: 0 10px 20px -8px rgba(15,23,42,0.12); border-color: var(--ink-300); }
.tool-tile-ico {
  width: 38px; height: 38px; flex-shrink: 0; border-radius: var(--radius-md);
  background: var(--brand-50); border: 1px solid var(--brand-100); color: var(--brand-600);
  display: flex; align-items: center; justify-content: center; font-size: 19px;
 line-height: 28px;}
.tool-tile-body { min-width: 0; }
.tool-tile-title { font-size: 13px; font-weight: 600; color: var(--ink-950); display: flex; align-items: center; gap: 6px;  line-height: 20px;}
.tool-tile-title .ti-arrow-narrow-right { color: var(--ink-300); font-size: 15px; }
.tool-tile:hover .tool-tile-title .ti-arrow-narrow-right { color: var(--brand-600); }
.tool-tile-desc { font-size: 12px; color: var(--ink-500); line-height: 17px; margin-top: 3px; }
@media (max-width:880px){ .tool-grid { grid-template-columns: repeat(2,1fr); } }
@media (max-width:560px){ .tool-grid { grid-template-columns: 1fr; } }

/* ─── AUTH LAYOUT (login / register) ─────────────────────────────────── */
.vc-auth { background: var(--bg); }
.auth-wrap { min-height: 100vh; display: grid; grid-template-columns: 1.05fr 1fr; }

.auth-aside {
  background: var(--brand-950);
  color: #fff; padding: 48px 44px;
  display: flex; flex-direction: column;
  position: relative; overflow: hidden;
}
.auth-aside::after {
  content: ""; position: absolute; right: -120px; top: -120px;
  width: 360px; height: 360px; border-radius: 50%;
  background: radial-gradient(circle at center, rgba(0,186,136,0.28), transparent 70%);
}
.auth-logo { display: flex; align-items: center; gap: 11px; position: relative; z-index: 1; }
.auth-logo .mk {
  width: 36px; height: 36px; border-radius: var(--radius-md);
  background: var(--brand-600); color: #fff; font-size: 19px;
  display: flex; align-items: center; justify-content: center;
 line-height: 28px;}
.auth-logo .nm { font-size: 19px; font-weight: 700; color: #fff;  line-height: 28px;}
.auth-logo .nm em { color: var(--brand-400); font-style: normal; }

.auth-aside-body { margin: auto 0; position: relative; z-index: 1; max-width: 420px; }
.auth-aside-body h1 { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; line-height: 39px; margin-bottom: 14px; }
.auth-aside-body p { font-size: 15px; color: #BFE6D8; line-height: 1.6; margin-bottom: 26px; }
.auth-points { list-style: none; display: flex; flex-direction: column; gap: 12px; }
.auth-points li { display: flex; align-items: center; gap: 10px; font-size: 14px; color: #E6F4EE; }
.auth-points li i { color: var(--brand-400); font-size: 18px; flex-shrink: 0; }
.auth-aside-foot { position: relative; z-index: 1; font-size: 12px; color: rgba(255,255,255,0.45); }

.auth-main { display: flex; align-items: center; justify-content: center; padding: 40px 24px; }
.auth-card-wrap { width: 100%; max-width: 384px; }
.auth-logo-mobile { display: none; align-items: center; gap: 10px; justify-content: center; margin-bottom: 28px; }
.auth-logo-mobile .mk {
  width: 34px; height: 34px; border-radius: var(--radius-md);
  background: var(--brand-600); color: #fff; font-size: 18px;
  display: flex; align-items: center; justify-content: center;
}
.auth-logo-mobile .nm { font-size: 18px; font-weight: 700; color: var(--ink-950); }

.auth-head { margin-bottom: 22px; }
.auth-head h1 { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; color: var(--ink-950); }
.auth-head p { font-size: 14px; color: var(--ink-500); margin-top: 6px; }
.auth-form { padding: 24px; }
.auth-alt { margin-top: 18px; text-align: center; font-size: 13px; color: var(--ink-500);  line-height: 20px;}
.auth-alt a { color: var(--brand-600); font-weight: 600; }
.pw-wrap { position: relative; }
.pw-wrap input { padding-right: 42px !important; }
.pw-toggle {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 30px; height: 30px; border: none; background: none; cursor: pointer;
  color: var(--ink-400); display: flex; align-items: center; justify-content: center; font-size: 17px;
 line-height: 26px;}
.pw-toggle:hover { color: var(--ink-700); }

@media (max-width: 860px) {
  .auth-wrap { grid-template-columns: 1fr; }
  .auth-aside { display: none; }
  .auth-logo-mobile { display: flex; }
  .auth-main { padding: 32px 20px; align-items: flex-start; }
  .auth-card-wrap { margin: 0 auto; }
  .auth-head { text-align: center; }
}

/* ─── SHARED COMPONENTS ─────────────────────────────── */
.eyebrow-pill {
  display: inline-block;
  padding: 4px 10px;
  border-radius: var(--radius-sm);
  background: var(--brand-50);
  border: 1px solid var(--brand-100);
  color: var(--brand-600);
  font-size: 11px; font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 14px;
 line-height: 16px;}
.section-head {
  font-size: 12px; font-weight: 600;
  color: var(--ink-500);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: 16px;
}
.label-up { font-size: 11px; font-weight: 600; color: var(--ink-500); text-transform: uppercase; letter-spacing: 0.08em; display: block; margin-bottom: 8px;  line-height: 16px;}
.label-strong { font-size: 12px; font-weight: 600; color: var(--ink-700); display: block; margin-bottom: 10px; }
.stack { display: flex; flex-direction: column; gap: 14px; }
.stack-lg { display: flex; flex-direction: column; gap: 20px; }
.stack-sm { display: flex; flex-direction: column; gap: 8px; }
.row { display: flex; gap: 10px; }

/* ELEVATED CARDS */
.action-card, .score-card-premium, .route-tile, .dev-card {
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease, border-color 0.3s ease;
  box-shadow: 0 4px 6px -1px rgba(15, 23, 42, 0.05), 0 2px 4px -2px rgba(15, 23, 42, 0.03);
}
.action-card:hover, .score-card-premium:hover, .route-tile:hover, .dev-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 12px 24px -8px rgba(15, 23, 42, 0.12), 0 4px 6px -2px rgba(15, 23, 42, 0.04);
  border-color: var(--ink-300);
}

/* ACTION CARD */
.action-card {
  background: var(--surface);
  border-radius: var(--radius-xl);
  padding: 24px;
  color: var(--ink-950);
  box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -2px rgba(0,0,0,0.05);
  border: 1px solid var(--ink-200);
  position: relative;
}
.action-stack { position: relative; display: flex; flex-direction: column; gap: 16px; }
.action-label {
  font-size: 12px; font-weight: 600;
  color: var(--ink-700);
  display: block;
  margin-bottom: 6px;
}
.action-select-wrap { position: relative; }
.action-select {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--ink-300);
  border-radius: var(--radius-btn);
  padding: 13px 42px 13px 15px;
  font-size: 14px; font-weight: 500;
  color: var(--ink-950);
  appearance: none; outline: none;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.action-select:focus { border-color: var(--accent-500); box-shadow: 0 0 0 3px var(--accent-100); }
.action-select-wrap i {
  position: absolute; right: 14px; top: 50%;
  transform: translateY(-50%);
  color: var(--ink-400); pointer-events: none; font-size: 16px;
}
.action-btn {
  width: 100%;
  background: var(--brand-600); color: #fff; border: none;
  border-radius: var(--radius-btn); padding: 14px;
  font-size: 14px; font-weight: 600;
  box-shadow: 0 2px 4px rgba(16, 42, 67, 0.15);
  margin-top: 15px;
  display: flex; align-items: center; justify-content: center; gap: 8px;
}
.action-btn:hover { background: var(--brand-500); }

/* ROUTES GRID */
.routes-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.route-tile {
  /* 6px, matching the score card and the phone tile, which already gets there
     through --radius-btn. --radius-lg is 8px and left this one tile rounder
     than everything it sits beside on a wide screen. */
  padding: 16px; border-radius: 6px;
  border: 1px solid var(--ink-200);
  background: var(--surface);
  cursor: pointer; transition: all 0.2s; position: relative;
  display: block;
}
.route-tile:hover { box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); border-color: var(--ink-300); }
.route-tile:active { background: var(--ink-50); }
.route-tile-flag { font-size: 24px; display: block; margin-bottom: 10px; }
.route-tile-name { font-size: 14px; font-weight: 600; color: var(--ink-950); }
.route-tile-code { font-size: 11px; font-weight: 500; color: var(--ink-500); margin-top: 4px;  line-height: 16px;}
.route-tile-badge {
  position: absolute; top: 14px; right: 14px;
  font-size: 10px; font-weight: 600; padding: 2px 6px;
  border-radius: var(--radius-sm); letter-spacing: 0.02em;
}
.route-tile-badge.req { background: var(--rose-50); color: var(--rose-700); border: 1px solid var(--rose-100); }
.route-tile-badge.free { background: var(--accent-50); color: var(--accent-700); border: 1px solid var(--accent-100); }
.route-tile-badge.evisa { background: var(--amber-50); color: var(--amber-800); border: 1px solid var(--amber-100); }

/* PROGRESS */
.progress-segments { display: flex; gap: 6px; margin-bottom: 24px; }
.progress-seg { flex: 1; height: 4px; border-radius: 2px; background: var(--ink-200); }
.progress-seg.done { background: var(--accent-500); }

/* CHECK HEADING */
.check-heading { margin-bottom: 24px; }
.check-heading h2 { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 6px; }
.check-heading p { font-size: 14px; color: var(--ink-500); line-height: 1.5; }

/* CHOICE BUTTONS */
.choice-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.choice-btn {
  background: var(--surface); border: 1px solid var(--ink-300);
  border-radius: var(--radius-btn); padding: 14px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  cursor: pointer; color: var(--ink-700); transition: all 0.2s;
}
.choice-btn:hover { border-color: var(--ink-400); }
.choice-btn.selected { border-color: var(--accent-500); background: var(--accent-50); color: var(--accent-700); box-shadow: inset 0 0 0 1px var(--accent-500); }
.choice-btn i { font-size: 20px; }
.choice-btn span { font-size: 13px; font-weight: 600;  line-height: 20px;}
/* Radio-backed choice button: hide the native input, light up on :checked. */
.choice-btn input[type=radio] { position: absolute; opacity: 0; width: 0; height: 0; }
.vc-app .choice-btn:has(input:checked) {
  border-color: var(--accent-500); background: var(--accent-50);
  color: var(--accent-700); box-shadow: inset 0 0 0 1px var(--accent-500);
}

/* BIG INPUT */
.big-input-wrap { position: relative; }
.big-input {
  width: 100%; background: var(--surface); border: 1px solid var(--ink-300);
  border-radius: var(--radius-lg); padding: 14px 16px;
  font-size: 16px; font-weight: 600;
  color: var(--ink-950); font-feature-settings: "tnum";
  transition: all 0.2s;
}
.big-input:focus { border-color: var(--accent-500); box-shadow: 0 0 0 3px var(--accent-100); outline: none; }
.big-input-prefix {
  position: absolute; left: 16px; top: 50%;
  transform: translateY(-50%);
  color: var(--ink-400); font-size: 16px; font-weight: 600;
}
.vc-app input.big-input.with-prefix { padding-left: 34px !important; }

/* INSIGHT */
.insight {
  padding: 14px; border-radius: var(--radius-lg);
  background: var(--brand-50); border: 1px solid var(--brand-100);
  display: flex; gap: 12px;
}
.insight-icon {
  width: 24px; height: 24px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  color: var(--brand-600); font-size: 16px;
}
.insight-text { font-size: 13px; line-height: 20px; color: var(--brand-700); }
.insight-text strong { color: var(--brand-950); font-weight: 600; }

/* BUTTONS */
.btn-dark {
  width: 100%; background: var(--ink-950); color: #fff; border: none;
  border-radius: var(--radius-btn); padding: 14px; font-size: 14px; font-weight: 600;
  box-shadow: 0 4px 6px -1px rgba(15,23,42,0.1);
  display: flex; align-items: center; justify-content: center; gap: 8px;
}
.btn-dark:hover { background: var(--ink-900); }
.btn-text {
  width: 100%; background: none; border: none;
  color: var(--ink-500); font-size: 13px; font-weight: 500;
  padding: 12px; margin-top: 4px;
}

/* SCORE RING */
.score-card-premium {
  background: var(--surface); border: 1px solid var(--ink-200);
  border-radius: var(--radius-xl); padding: 32px 24px; text-align: center;
  margin-bottom: 24px;
}
.score-ring { position: relative; width: 140px; height: 140px; margin: 0 auto 16px; }
.score-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.score-ring-track { color: var(--ink-100); }
.score-ring-text {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 32px; font-weight: 700;
  letter-spacing: -0.02em; color: var(--ink-950);
  font-feature-settings: "tnum";
}
.score-ring-text small { font-size: 16px; font-weight: 600; color: var(--ink-400); margin-left: 2px; }
.verdict-title { font-size: 20px; font-weight: 700; color: var(--ink-950); margin-bottom: 4px; }
.verdict-sub { font-size: 13px; color: var(--ink-500); }

/* WARN */
.warn {
  display: flex; gap: 12px; padding: 14px; border-radius: var(--radius-lg);
  background: var(--amber-50); border: 1px solid var(--amber-100);
}
.warn i { color: var(--amber-600); font-size: 18px; flex-shrink: 0; margin-top: 2px; }
.warn-title { font-size: 13px; font-weight: 600; color: var(--amber-900); margin-bottom: 4px; }
.warn-text { font-size: 13px; color: var(--amber-800); line-height: 20px; }

/* RISK ROWS */
.risk-row {
  background: var(--surface); border: 1px solid var(--ink-200);
  border-radius: var(--radius-lg); padding: 12px 16px;
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 10px;
}
.risk-info { display: flex; align-items: center; gap: 12px; }
.risk-icon { width: 36px; height: 36px; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
.risk-icon.med { background: var(--amber-50); color: var(--amber-700); border: 1px solid var(--amber-100); }
.risk-icon.ok { background: var(--accent-50); color: var(--accent-700); border: 1px solid var(--accent-100); }
.risk-icon.high { background: var(--rose-50); color: var(--rose-700); border: 1px solid var(--rose-100); }
.risk-title { font-size: 13px; font-weight: 600; color: var(--ink-950);  line-height: 20px;}
.risk-sub { font-size: 12px; color: var(--ink-500); margin-top: 2px; }
.risk-tag.med { background: var(--amber-100); color: var(--amber-800); }
.risk-tag.ok { background: var(--accent-100); color: var(--accent-800); }
.risk-tag.high { background: var(--rose-100); color: var(--rose-700); }

/* RECS */
.recs-block { padding: 20px; border-radius: var(--radius-lg); background: var(--surface); border: 1px solid var(--ink-200); }
.recs-block h4 { font-size: 14px; font-weight: 600; color: var(--ink-950); margin-bottom: 14px; }
.rec-line { display: flex; align-items: flex-start; gap: 10px; font-size: 13px; line-height: 20px; color: var(--ink-700); margin-bottom: 12px; }
.rec-line:last-child { margin-bottom: 0; }
.rec-line i { flex-shrink: 0; color: var(--accent-500); font-size: 16px; margin-top: 2px; }
.recs-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 28px; }
@media (max-width: 700px) { .recs-grid { grid-template-columns: 1fr; } }

/* DEV PORTAL */
.dev-card { background: var(--surface); border: 1px solid var(--ink-200); border-radius: var(--radius-lg); padding: 20px; margin-bottom: 16px; }
.key-row { display: flex; align-items: center; gap: 8px; }
.key-val {
  flex: 1; font-variant-numeric: tabular-nums; font-size: 13px;
  color: var(--ink-950); background: var(--ink-50);
  border: 1px solid var(--ink-200); border-radius: var(--radius-md);
  padding: 10px 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.icon-btn {
  width: 40px; height: 40px; border: 1px solid var(--ink-300);
  border-radius: var(--radius-md); background: var(--surface); color: var(--ink-600);
  display: flex; align-items: center; justify-content: center; font-size: 16px;
}
.icon-btn:hover { background: var(--ink-50); border-color: var(--ink-400); }
.usage-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--ink-200); }
.usage-cell { text-align: center; padding: 0 6px; }
.usage-cell + .usage-cell { border-left: 1px solid var(--ink-200); }
.usage-num { font-size: 20px; font-weight: 700; color: var(--ink-950); }
.usage-num.accent { color: var(--accent-600); }
.usage-label { font-size: 11px; font-weight: 500; color: var(--ink-500); margin-top: 4px; }

.ep-list { background: var(--surface); border: 1px solid var(--ink-200); border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 24px; }
.ep-row { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--ink-200); transition: background 0.15s; }
.ep-row:hover { background: var(--ink-50); }
.ep-row:last-child { border-bottom: none; }
.mth { font-variant-numeric: tabular-nums; font-size: 11px; font-weight: 600; padding: 2px 6px; border-radius: var(--radius-sm); min-width: 46px; text-align: center;  line-height: 16px;}
.mget { background: var(--brand-100); color: var(--brand-700); }
.mpost { background: var(--accent-100); color: var(--accent-700); }
.ep-path { font-variant-numeric: tabular-nums; font-size: 13px; color: var(--ink-950); flex: 1;  line-height: 20px;}
.ep-row i.arr { color: var(--ink-400); font-size: 14px; }

.code-block {
  background: var(--ink-950); border-radius: var(--radius-lg); padding: 16px;
  font-variant-numeric: tabular-nums; font-size: 13px;
  line-height: 21px; overflow-x: auto; color: #E2E8F0;
}

/* CHANGELOG */
.cl-card { background: var(--surface); border: 1px solid var(--ink-200); border-radius: var(--radius-lg); padding: 16px; margin-bottom: 12px; box-shadow: 0 1px 2px rgba(0,0,0,0.02); }
.cl-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.cl-route { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 600; color: var(--ink-950); }
.cl-route i { color: var(--ink-400); font-size: 14px; }
.cl-flag { font-size: 18px; }
.cl-date { font-size: 12px; color: var(--ink-500); }
.cl-change { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Same metrics as .pill.slate, so the grey tag and the green verified pill on
   the same changelog card are the same height. It was 2px 8px against the
   pill's 3px 9px, which made one 22px and the other 24px on one row. */
.cl-tag { font-variant-numeric: tabular-nums; font-size: 11px; font-weight: 600; background: var(--ink-100); border: 1px solid var(--ink-200); color: var(--ink-600); padding: 3px 9px; border-radius: var(--radius-sm); letter-spacing: 0.02em; line-height: 16px;}
.cl-old { font-size: 13px; color: var(--ink-500); text-decoration: line-through; min-width: 0; overflow-wrap: anywhere; word-break: break-word;  line-height: 20px;}
.cl-new-up { font-size: 13px; font-weight: 600; color: var(--amber-700); min-width: 0; overflow-wrap: anywhere; word-break: break-word;  line-height: 20px;}
.cl-new-down { font-size: 13px; font-weight: 600; color: var(--accent-600); min-width: 0; overflow-wrap: anywhere; word-break: break-word;  line-height: 20px;}

/* ─── APP SHELL — MOBILE LAYOUT (default) ─────────────────────────── */
/* Operator-only banner, shown by partials/maintenance-banner.php. */
.maint-banner {
  background: var(--amber-100); color: var(--amber-900);
  padding: 9px 20px; display: flex; align-items: center; justify-content: center;
  gap: 10px; flex-wrap: wrap; font-size: 13px; font-weight: 600; line-height: 20px;
}
.maint-banner i { font-size: 16px; }
.maint-banner form { display: inline; }
.maint-banner button {
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  text-decoration: underline; cursor: pointer;
}

.topbar {
  position: sticky; top: 0; z-index: 50;
  background: rgba(255,255,255,0.95);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  /* An inset shadow rather than a border, and a whole-pixel line height.
     Both exist for the same reason. The bar is 60px border-box, so a 1px border
     left a 59px content box: centring a 19.5px line in an odd box put every
     label in this bar on y=19.75, and the bar is backdrop-filtered, so its
     contents are composited and a fractional offset there is resampled rather
     than snapped. An inset shadow draws the same hairline without taking a
     pixel from the content box, and 20px lines centre in 60px exactly. */
  box-shadow: inset 0 -1px 0 var(--ink-200);
  line-height: 20px;
  /* Clear the status bar / notch when running full-screen (viewport-fit=cover) */
  padding: calc(12px + env(safe-area-inset-top)) max(20px, env(safe-area-inset-right)) 12px max(20px, env(safe-area-inset-left));
  display: flex; align-items: center; justify-content: space-between;
}
.logo { display: flex; align-items: center; gap: 10px; }
.logo-mark {
  width: 32px; height: 32px; background: var(--brand-600);
  border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.logo-mark i { color: #fff; font-size: 16px; }
/* Carved-tile logo mark (negative-space check) — the SVG carries its own
   gradient tile, so any wrapper background must drop away. */
/* The carved/gradient SVG carries its own tile, so the wrapper must be
   transparent. Scope to each wrapper's parent so this out-specifies the
   later `.vc-footer-brand .mk { background }` etc. — otherwise the wrapper's
   green peeks through the tile's edge inset and reads as a border. */
.logo-mark:has(.logo-tile),
.vc-footer-brand .mk:has(.logo-tile),
.auth-logo .mk:has(.logo-tile),
.auth-logo-mobile .mk:has(.logo-tile) { background: transparent; overflow: visible; }
.logo-mark .logo-tile { width: 32px; height: 32px; display: block; }
.mk .logo-tile { width: 100%; height: 100%; display: block; }
.logo-mark img { width: 100%; height: 100%; object-fit: cover; }
.logo-text { font-size: 16px; font-weight: 700; color: var(--ink-950); }
.header-actions { display: flex; gap: 10px; }
.header-btn {
  width: 36px; height: 36px; border-radius: var(--radius-md);
  background: transparent; border: none;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-700); font-size: 18px; position: relative;
}
.header-btn:hover { background: var(--ink-50); }
/* Unread policy-change count on the bell / Changelog nav. */
.header-badge {
  position: absolute; top: -5px; right: -5px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 9px; background: var(--rose-600); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid #fff;
}
.nav-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 4px; margin-left: 2px;
  border-radius: 8px; background: var(--rose-600); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 1; vertical-align: middle;
}

.desktop-nav { display: none; }
.header-cta { display: none; }

.app-shell { display: block; }
.sidebar { display: none; }

.content-area {
  max-width: 480px;
  margin: 0 auto;
  min-height: 100vh;
  background: var(--bg);
  position: relative;
  padding-bottom: 80px;
  display: flex;
  flex-direction: column;
}
/* Body grows to fill, pushing the footer to the absolute bottom on short pages. */
.ca-body { flex: 1 0 auto; min-width: 0; }

.vc-footer { margin: 56px 4px 4px; padding: 0; }
.vc-footer-top {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 18px 24px;
}
.vc-footer-brand { display: flex; align-items: center; gap: 11px; }
.vc-footer-brand .mk {
  width: 32px; height: 32px; flex-shrink: 0; border-radius: var(--radius-md);
  background: var(--brand-600); color: #fff; font-size: 17px;
  display: flex; align-items: center; justify-content: center;
 line-height: 26px;}
.vc-footer-brand .txt { display: flex; flex-direction: column; gap: 1px; line-height: 1.25; }
.vc-footer-brand .nm { font-size: 15px; font-weight: 700; color: var(--ink-900);  line-height: 19px;}
.vc-footer-brand .tag { font-size: 12px; color: var(--ink-400); }
.vc-footer-links { display: flex; gap: 22px; flex-wrap: wrap; }
.vc-footer-links a { font-size: 13px; color: var(--ink-500); font-weight: 500; transition: color .15s;  line-height: 20px;}
.vc-footer-links a:hover { color: var(--brand-600); }
.vc-footer-meta {
  margin-top: 20px; padding-top: 18px;
  border-top: 1px solid var(--ink-100);
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 8px; color: var(--ink-400); font-size: 12px;
}
.vc-footer-meta a { color: var(--ink-500); }
.vc-footer-meta a:hover { color: var(--brand-600); }
@media (max-width: 560px){
  .vc-footer-meta { flex-direction: column; align-items: flex-start; gap: 6px; }
}
/* Mobile (bottom-nav present): drop the redundant brand + links from the
   app-shell footer, keep just the legal disclaimer centred, and clear the nav. */
@media (max-width: 1023px){
  .content-area .vc-footer { margin-top: 28px; }
  .content-area .vc-footer-top { display: none; }
  .content-area .vc-footer-meta {
    margin-top: 0; padding-top: 0; border-top: none;
    flex-direction: column; align-items: center; justify-content: center;
    text-align: center; gap: 4px; line-height: 1.5;
  }
}

/* STAGGERED ENTRANCE ANIMATIONS */
.screen {
  display: block;
  padding: 24px 20px;
}
.screen > * {
  /* backwards, not forwards, and this is the whole point of the rule.
     forwards holds the last keyframe for the life of the page, and the last
     keyframe carries transform: translateY(0). An identity transform is still a
     transform: it promotes the element to its own compositing layer, where the
     text is rasterised separately from the page and resampled whenever the
     layer does not land on whole device pixels. That is what made body copy on
     the requirements page look faintly soft, on every block, permanently.
     backwards applies the opening keyframe during the delay, which is the only
     reason forwards was reached for, and then lets the element return to its
     ordinary painted state once the animation ends. No lingering transform, no
     layer, text rasterised with the page. */
  animation: slideUpFade 0.5s cubic-bezier(0.16, 1, 0.3, 1) backwards;
}
.screen > *:nth-child(1) { animation-delay: 0.05s; }
.screen > *:nth-child(2) { animation-delay: 0.10s; }
.screen > *:nth-child(3) { animation-delay: 0.15s; }
.screen > *:nth-child(4) { animation-delay: 0.20s; }
.screen > *:nth-child(5) { animation-delay: 0.25s; }

@keyframes slideUpFade {
  0% { opacity: 0; transform: translateY(12px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* No entrance animation on a wrapper that holds a full-screen overlay.
   slideUpFade fills forwards, and a forwards fill never stops applying, so the
   wrapper keeps both a translateY(0) and a filled opacity animation for the
   life of the page. Each one breaks the overlay in its own way:
     the transform, identity though it is, makes the wrapper the containing
     block for position:fixed, which shrank the loader from the viewport to
     818x556 at (321,133), the wrapper's own box;
     the filled opacity animation makes the wrapper a stacking context even at
     opacity 1, which buried the loader under the topbar and sidebar no matter
     how high its z-index went. 9999 lost to 50.
   Fading instead of sliding only fixes the first. Both go, or neither does. */
.screen > *:has(.vc-loader-overlay) { animation: none; opacity: 1; }

.hero { padding: 2px 0 18px; }
.hero h1 { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; line-height: 34px; margin-bottom: 12px; }
.hero p { font-size: 15px; color: var(--ink-600); line-height: 22px; max-width: 380px; }

/* BOTTOM NAV */
.bottomnav {
  position: fixed; left: 14px; right: 14px;
  bottom: calc(env(safe-area-inset-bottom) + 12px);
  background: var(--surface);
  border: 1px solid var(--ink-200);
  border-radius: 22px;
  padding: 9px 10px;
  display: flex; align-items: center; justify-content: space-around;
  box-shadow: 0 18px 40px -10px rgba(15,23,42,0.45), 0 6px 16px -6px rgba(15,23,42,0.28);
  z-index: 40;
  transition: transform .32s cubic-bezier(.4,0,.2,1), opacity .32s ease;
}
.bottomnav.nav-hidden {
  transform: translateY(200%);
  opacity: 0;
  pointer-events: none;
}
.nav-btn {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: none; border: none; color: var(--ink-500); padding: 8px;
  /* The thumb-target rule forces these to 44px while the icon and padding come
     to 36, and a column flex box leaves the spare 8px at the bottom. That sat
     every icon 8px above centre: 18px from the top of the bar, 26 from the
     bottom. Home looked right only because .nav-home centres itself. */
  justify-content: center;
}
.nav-btn.active { color: var(--brand-600); }
.nav-btn i { font-size: 20px; }
.nav-btn span { font-size: 10.5px; font-weight: 500; white-space: nowrap;  line-height: 16px;}

/* The operator's "More" sheet.
   The bottom bar holds five, and the sidebar that used to be described as
   holding the overflow is display:none on a phone, so anything not marked
   primary could not be reached there at all. The fifth slot opens the rest. */
/* The details element is a flex item of the bar and shrinks to its content, so
   the summary has to be told to match the width of the real nav buttons beside
   it. Left alone it collapsed to 16px, which is an icon, not a thumb target. */
.nav-more { position: relative; display: flex; }
.nav-more > summary { list-style: none; cursor: pointer; min-width: 56px; }
.nav-more > summary::-webkit-details-marker { display: none; }
.nav-more > summary::marker { content: ""; }
.nav-more[open] > summary { color: var(--brand-600); }

/* The scrim belongs to the page, not to the bar.
   Put inside the bar it resolved to the wrong box entirely: .bottomnav is
   backdrop-filtered, which makes it a containing block for position:fixed, so
   inset:0 measured against the bar and tinted the bar while the page behind it
   stayed bright. It lives on the body now, where fixed means the viewport.
   body::after is already the nav's gradient fade, hence ::before. */
body.vc-app:has(.nav-more[open])::before {
  content: "";
  position: fixed; inset: 0;
  background: rgba(2, 6, 23, 0.34);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  z-index: 39;
  animation: nav-scrim-in .18s ease;
}
@keyframes nav-scrim-in { from { opacity: 0; } }

/* That fade exists to lift the bar off the page. With the sheet open the scrim
   does that job, and leaving both draws a pale band across the dimmed page. */
body.vc-app:has(.nav-more[open])::after { opacity: 0; }

.nav-more-sheet {
  /* Measured from the summary, which sits inside the bar's padding, so the
     offset has to clear that padding as well as the gap. At 14px the sheet
     overlapped the bar it was supposed to sit above. */
  position: absolute; bottom: calc(100% + 22px); right: -6px;
  min-width: 190px; padding: 6px;
  background: var(--surface);
  border: 1px solid var(--ink-200); border-radius: var(--radius-card);
  box-shadow: 0 18px 40px -10px rgba(15,23,42,0.45), 0 6px 16px -6px rgba(15,23,42,0.28);
  display: flex; flex-direction: column; gap: 2px;
  animation: nav-more-rise .18s cubic-bezier(.4, 0, .2, 1);
}
@keyframes nav-more-rise { from { opacity: 0; transform: translateY(6px); } }

.nav-more-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: var(--radius-btn);
  font-size: 13.5px; font-weight: 500; color: var(--ink-700); line-height: 20px;
}
.nav-more-row i { font-size: 17px; color: var(--ink-500); }
.nav-more-row.active { background: var(--brand-50); color: var(--brand-600); }
.nav-more-row.active i { color: var(--brand-600); }
.nav-more-row:active { background: var(--ink-50); }

@media (prefers-reduced-motion: reduce) { .nav-more-sheet { animation: none; } }
/* Icons-only by default; the active tab expands into a labelled pill */
.bottomnav .nav-btn { border-radius: 999px; transition: background .2s, padding .2s; }
.bottomnav .nav-btn span { display: none; }
.bottomnav .nav-btn.active {
  flex-direction: row; gap: 7px;
  background: var(--brand-50); color: var(--brand-600);
  padding: 8px 14px;
}
.bottomnav .nav-btn.active span { display: inline; font-size: 12px; font-weight: 600; }
/* Center action — elevated "New check" button (icon circle + label) */
.nav-fab {
  flex: none; margin-top: -22px; align-self: flex-start;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  text-decoration: none;
}
.nav-fab-ic {
  width: 54px; height: 54px; border-radius: 50%;
  background: var(--brand-600); color: #fff;
  display: flex; align-items: center; justify-content: center;
  border: 3px solid var(--surface);
  box-shadow: 0 8px 18px -4px rgba(0,150,109,.55);
  transition: transform .15s ease, box-shadow .15s ease;
}
.nav-fab:active .nav-fab-ic { transform: scale(.92); }
.nav-fab-ic i { font-size: 26px; }
/* The plus is the one Material Symbols glyph we kept (Rounded, filled). Inlined
   as SVG rather than loading the icon font for a single character. */
.nav-fab-plus { width: 27px; height: 27px; display: block; }
.nav-fab-lbl { font-size: 10.5px; font-weight: 700; color: var(--brand-600); white-space: nowrap; line-height: 16px;}

#screen-home { display: flex; flex-direction: column; }
/* The hero's opening line: what the product is, said before the number that
   proves it. Plain type on the page ground rather than a tinted band, so it
   reads as the page starting to speak instead of as a panel above the page.
   Sized to lead the hero without competing with the score, which stays the
   largest thing on the screen because it is the evidence. */
.oh-intro {
  margin: 0; font-size: 25px; line-height: 32px; font-weight: 800;
  letter-spacing: -0.03em; color: var(--ink-950); text-wrap: balance; max-width: 22ch;
}
.oh-intro em { font-style: normal; color: var(--brand-600); }
.oh-intro-sub {
  margin: 7px 0 0; font-size: 14px; line-height: 21px; color: var(--ink-600); max-width: 48ch;
}
/* The route label starts a new thought, so it gets a clear break from the
   claim above it rather than the 10px that separates the score's own lines. */
.oh-intro-sub + .oh-flipwrap { margin-top: 26px; }
.home-left { order: 1; display: flex; flex-direction: column; gap: 24px; width: 100%; }
.home-right { order: 0; display: none; }
/* Trending is a flex item here, not a grid child, so it needs an explicit
   order. Without one it defaults to 0 and jumps above the hero, which has
   order 1. */
.home-full { order: 2; width: 100%; margin-top: 24px; }
.home-left .hero { order: -1; }
.mobile-only { display: block; }
.results-left, .results-right, .dev-left, .dev-right, .cl-grid { display: flex; flex-direction: column; gap: 16px; }
/* The two-column screens only get their 32px grid gap on desktop. Stacked on
   mobile they are plain blocks that butt together, so the right column's first
   heading sat flush against the bottom of the left column's last element:
   Popular endpoints was touching the code block with zero space between them. */
#screen-developers, #screen-results { display: flex; flex-direction: column; gap: 28px; }
.desktop-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 6px 0 8px; }

/* ─── DESKTOP LAYOUT (≥ 1024px) ─────────────────────────── */
@media (min-width: 1024px) {
  .topbar {
    padding: 0 32px;
    height: 60px;
    position: fixed; top: 0; left: 0; right: 0;
  }
  .logo-text { font-size: 18px; }
  .header-btn { display: none; }

  .desktop-nav { display: flex; align-items: center; gap: 4px; }
  .desktop-nav-btn {
    padding: 8px 12px;
    border-radius: var(--radius-md);
    background: none; border: none;
    font-size: 13px; font-weight: 500;
    color: var(--ink-600);
    display: flex; align-items: center; gap: 6px;
    transition: all 0.2s;
  }
  .desktop-nav-btn:hover { background: var(--ink-100); color: var(--ink-950); }
  .desktop-nav-btn.active { background: var(--brand-50); color: var(--brand-700); font-weight: 600; }
  .desktop-nav-btn i { font-size: 16px; }

  .header-cta {
    padding: 8px 16px;
    background: var(--brand-600); color: var(--btn-ink, #fff); border: none;
    border-radius: var(--radius-btn); font-size: 13px; font-weight: 600;
    display: flex; align-items: center; gap: 6px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  }
  .header-cta:hover { background: var(--brand-500); }

  .bottomnav { display: none; }
  .app-shell { display: flex; margin-top: 60px; min-height: calc(100vh - 60px); }

  .sidebar {
    display: flex; flex-direction: column; width: 240px; flex-shrink: 0;
    background: var(--surface); border-right: 1px solid var(--ink-200); padding: 24px 16px;
    position: sticky; top: 60px; height: calc(100vh - 60px); overflow-y: auto; gap: 4px;
  }
  .sidebar-label {
    font-size: 11px; font-weight: 600; color: var(--ink-500);
    text-transform: uppercase; letter-spacing: 0.08em; padding: 0 12px; margin: 16px 0 8px;
  }
  .sidebar-label:first-child { margin-top: 0; }

  /* ── Route row ──────────────────────────────────────────────────────────
     The route used to be legible only in the address bar. As a row it can be
     seen and changed from anywhere, and it gives the "This route" group below
     it something to refer to. */
  .sb-route {
    display: flex; align-items: center; gap: 8px; justify-content: space-between;
    padding: 9px 10px; margin-bottom: 12px;
    background: var(--ink-50); border: 1px solid var(--ink-200);
    border-radius: var(--radius-md); text-decoration: none;
  }
  .sb-route:hover { border-color: var(--ink-300); }
  .sb-route-pair {
    display: flex; align-items: center; gap: 5px; min-width: 0;
    font-size: 12.5px; font-weight: 700; color: var(--ink-950);
    letter-spacing: 0.01em;
    /* 12.5 x 1.5 is 18.75, and this element sits above every other item in
       the sidebar, so its quarter pixel was inherited by all of them: 30 text
       elements on the requirements page started on a fraction because of this
       one line. */
    line-height: 19px;
  }
  .sb-route-pair i { color: var(--ink-400); font-size: 12px; }
  .sb-route-fl { font-size: 14px; line-height: 1; }
  .sb-route-ch { font-size: 10.5px; font-weight: 600; color: var(--brand-600); flex: none; line-height: 16px;}

  /* ── All tools ──────────────────────────────────────────────────────────
     Eight specialist tools behind one row. Open when you are on one of them,
     so the sidebar never hides where you currently are. */
  .sb-more { display: block; }
  .sb-more > summary {
    list-style: none; cursor: pointer;
    display: flex; align-items: center; gap: 10px;
  }
  .sb-more > summary::-webkit-details-marker { display: none; }
  .sb-more > summary::marker { content: ""; }
  .sb-more > summary:hover { background: var(--ink-50); color: var(--ink-950); }
  .sb-more > summary:focus-visible { outline: 2px solid var(--brand-600); outline-offset: -2px; }
  .sb-more .sb-caret {
    margin-left: 0; font-size: 12px; color: var(--ink-400);
    transition: transform 0.18s ease;
  }
  .sb-more[open] > summary .sb-caret { transform: rotate(90deg); }
  /* The eight open as a panel beside the sidebar, not inline. Expanding them
     in place put the menu 193px over the fold again, which is the whole thing
     this redesign exists to stop. */
  .sb-more { position: relative; }
  /* The sidebar is position:sticky, which makes it a stacking context, so the
     panel's z-index cannot lift it past .screen. Raising the sidebar itself is
     what puts the panel over the page. Stays under the topbar at 45. */
  .sidebar { z-index: 20; }
  /* The sidebar also scrolls, and overflow:auto clips a panel that sits outside
     it. Released only while the panel is open, so the scrollability the tall
     cases rely on is otherwise untouched. */
  .sidebar:has(.sb-more[open]) { overflow: visible; }
  .sb-more-panel {
    position: absolute; left: calc(100% + 6px); top: -6px; z-index: 70;
    width: 216px; padding: 6px;
    background: var(--surface); border: 1px solid var(--ink-200);
    border-radius: var(--radius-card);
    box-shadow: 0 4px 8px -4px rgba(18,16,14,.10), 0 18px 40px -14px rgba(18,16,14,.26);
    max-height: 70vh; overflow-y: auto;
  }
  .sb-more > summary .sidebar-count { margin-left: auto; }
  .sb-more-panel .sidebar-label { margin: 10px 0 4px; }
  .sb-more-panel .sidebar-label:first-child { margin-top: 2px; }
  @media (prefers-reduced-motion: reduce) { .sb-more .sb-caret { transition: none; } }

  /* Pushes the account row and upgrade card to the foot, so the 323px of dead
     space that used to sit above them is simply the gap it always was. */
  .sb-spacer { flex: 1 1 auto; min-height: 12px; }
  /* The spacer already separates the account block, so the divider's 24px of
     margin above the upgrade card was buying nothing. */
  .sb-spacer ~ .sidebar-divider { margin: 10px 0; }
  .sidebar-btn {
    display: flex; align-items: center; gap: 10px; padding: 10px 12px;
    border-radius: var(--radius-md); background: none; border: none;
    font-size: 13px; font-weight: 500; color: var(--ink-700);
    text-align: left; width: 100%; transition: all 0.15s;
  }
  .sidebar-btn:hover { background: var(--ink-50); color: var(--ink-950); }
  .sidebar-btn.active { background: var(--brand-50); color: var(--brand-700); font-weight: 600; }
  .sidebar-btn i { font-size: 16px; flex-shrink: 0; }
  .sidebar-divider { height: 1px; background: var(--ink-200); margin: 12px 0; }
  .sidebar-footer {
    margin-top: auto; padding: 16px; background: var(--ink-50);
    border-radius: var(--radius-lg); border: 1px solid var(--ink-200);
  }
  .sidebar-footer-title { font-size: 13px; font-weight: 600; color: var(--ink-950); margin-bottom: 4px; }
  .sidebar-footer-sub { font-size: 12px; color: var(--ink-500); line-height: 17px; margin-bottom: 12px; }
  .sidebar-footer-btn {
    display: block; text-align: center;
    width: 100%; background: var(--surface); color: var(--ink-950); border: 1px solid var(--ink-300);
    border-radius: var(--radius-md); padding: 8px; font-size: 12px; font-weight: 600;
    box-shadow: 0 1px 2px rgba(0,0,0,0.05);
  }
  .sidebar-footer-btn:hover { background: var(--ink-50); border-color: var(--ink-400); }

  .content-area {
    flex: 1; max-width: none; padding: 40px; display: flex; flex-direction: column;
  }
  .screen { padding: 0; max-width: 960px; width: 100%; }

  /* Two tracks for the hero row, then trending runs the whole width beneath
     it. Capping the page at 960 left a wide empty gutter to the right of a
     column that was itself only half full, which read as a broken layout
     rather than a deliberate one. */
  #screen-home { max-width: none; }
  #screen-home {
    display: grid; grid-template-columns: minmax(0, 1fr) 340px;
    gap: 40px 48px; align-items: start;
  }
  #screen-home .home-right { grid-column: 2; grid-row: 1; display: flex; flex-direction: column; gap: 24px; }
  #screen-home .home-left  { grid-column: 1; grid-row: 1; display: flex; flex-direction: column; gap: 32px; }
  #screen-home .home-full  { grid-column: 1 / -1; grid-row: 2; z-index: 1; }
  /* Between 1024 and 1359 the 340px rail leaves the hero column too narrow for
     the picker to show its longest values, so the route names ellipsised. The
     rail is secondary content, so it drops below the hero and gives the column
     its full width back rather than being hidden. */
  @media (max-width: 1359px) {
    #screen-home { grid-template-columns: minmax(0, 1fr); }
  
    #screen-home .home-right { grid-column: 1; grid-row: 3; }
    #screen-home .home-left  { grid-column: 1; grid-row: 1; }
    #screen-home .home-full  { grid-column: 1; grid-row: 2; }
  }
  /* The hero column animates in, and that transform makes it a stacking
     context, so the dropdown's own z-index cannot lift it past a later
     sibling. Ordering the two grid items is what actually keeps the open
     menu above the trending cards. Stays under the topbar at 45. */
  #screen-home .home-left  { z-index: 2; }
  .mobile-only { display: none; }

  .hero { padding: 0 0 8px; }
  .hero h1 { font-size: 36px; line-height: 40px; margin-bottom: 16px; }

  /* Now that trending spans the page, fix the column count to the number of
     cards rather than 3, so six routes form one clean row instead of a row of
     three plus an orphaned row of three. auto-fill would leave a ragged gap
     at widths that do not divide evenly. */
  .routes-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 16px; }

  .desktop-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px; }
  .stat-card { background: var(--surface); border: 1px solid var(--ink-200); border-radius: var(--radius-lg); padding: 16px; }
  .stat-card-icon {
    width: 32px; height: 32px; background: var(--brand-50); border: 1px solid var(--brand-100);
    border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center;
    margin-bottom: 12px; color: var(--brand-600); font-size: 16px;
  }
  .stat-card-val { font-size: 24px; font-weight: 700; color: var(--ink-950); margin-bottom: 4px; }
  .stat-card-label { font-size: 12px; color: var(--ink-500); font-weight: 500; }

  #screen-check, #screen-eligibility {
    max-width: 1024px; background: var(--surface); border-radius: var(--radius-xl);
    padding: 40px; border: 1px solid var(--ink-200); box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);
  }
  #screen-eligibility { max-width: 900px; }

  #screen-results { display: grid; grid-template-columns: 320px 1fr; gap: 32px; align-items: start; }
  #screen-developers { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; align-items: start; }
  /* Grid/flex children must be allowed to shrink so wide inner content
     (code blocks, tables, long monospace paths) scrolls instead of
     blowing out the column. */
  .home-left, .home-right, .results-left, .results-right,
  .dev-left, .dev-right { min-width: 0; }
  .dev-right .docs-prose { overflow-x: auto; }
  /* Full width, like home and requirements. The 800px cap was set when this
     page was a single column of change cards; with the two-column grid below
     it left most of a desktop screen empty. */
  #screen-changelog { max-width: none; }

  /* The operator's screens run the full width of the window.
     The 960px cap suits a reading column, which is what the public site mostly
     is. Admin is the opposite: wide tables of keys, subscribers, routes and
     usage, where the cap wasted half a desktop and pushed columns into
     wrapping while empty gutter sat beside them.
     Forms keep their own narrower caps, because a text input stretched across
     a 2560px monitor is harder to use, not easier. */
  .vc-admin .screen { max-width: none; }

}

/* Collapsible disclosure (e.g. report outcome form) */
.disclosure summary { list-style: none; cursor: pointer; }
.disclosure summary::-webkit-details-marker { display: none; }
.disclosure summary::marker { content: ""; }
.disclosure-caret { transition: transform .2s ease; }
.disclosure[open] .disclosure-caret { transform: rotate(180deg); }

/* ══════════════════════════════════════════════════════════════════════
   APP-FEEL LAYER — mobile native-app polish (press, glass nav, page
   transitions, skeletons). Kept at the end so it layers over base rules.
   ══════════════════════════════════════════════════════════════════════ */

/* 1 ─ Tactile press: cards & controls dip + dim the instant you touch them.
   Scoped to touch devices so desktop hover behaviour is untouched. */
@media (hover: none) and (pointer: coarse) {
  .action-card, .route-tile, .vc-card, .vc-card-sm, .dev-card, .choice-btn,
  .nav-btn, .header-cta, .sidebar-btn, .tool-card, .glance-item, .ep-row,
  [data-fix-next], .btn-brand, .btn-dark {
    transition: transform .12s ease, opacity .12s ease, box-shadow .12s ease;
    -webkit-tap-highlight-color: transparent;
  }
  .action-card:active, .route-tile:active, .vc-card:active, .vc-card-sm:active,
  .dev-card:active, .choice-btn:active, .tool-card:active, .glance-item:active,
  .header-cta:active, .sidebar-btn:active, [data-fix-next]:active,
  .btn-brand:active, .btn-dark:active {
    transform: scale(.975);
    opacity: .93;
  }
}

/* 2 ─ Frosted-glass bottom tab bar: translucent + blur of the content behind. */
.bottomnav {
  background: rgba(255, 255, 255, 0.72);
  -webkit-backdrop-filter: saturate(1.6) blur(18px);
  backdrop-filter: saturate(1.6) blur(18px);
  border-color: rgba(226, 232, 240, 0.9);
}

/* 3 ─ Cross-screen page transitions (View Transitions API, cross-document).
   Supporting browsers slide/fade between pages; others navigate normally. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: vc-page-out .16s ease both; }
  ::view-transition-new(root) { animation: vc-page-in .28s cubic-bezier(.32, .72, 0, 1) both; }
}
@keyframes vc-page-in  { from { opacity: 0; transform: translateX(18px); } to { opacity: 1; transform: none; } }
@keyframes vc-page-out { from { opacity: 1; } to { opacity: 0; transform: translateX(-8px); } }

/* 4 ─ Skeleton shimmer: content-shaped loading placeholders (JS swaps these
   in for the plain spinners on the AI-powered pages). */
.skel { position: relative; overflow: hidden; background: var(--ink-100); border-radius: var(--radius-md); }
.skel::after {
  content: ''; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.65), transparent);
  animation: vc-shimmer 1.3s infinite;
}
.skel-line { height: 12px; border-radius: 7px; }
.skel-block { min-height: 68px; border-radius: var(--radius-lg); }
.skel-card { display: flex; flex-direction: column; gap: 12px; padding: 18px; border: 1px solid var(--ink-200); border-radius: var(--radius-xl); background: var(--surface); }
@keyframes vc-shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .skel::after { animation: none; } }

/* 5 ─ Bottom-sheet pickers (progressive enhancement of the home selects).
   The native <select> stays in the DOM as the form value; a matching field
   button opens a slide-up sheet. Falls back to the native picker if JS is off. */
.as-sheet-field { text-align: left; display: flex; align-items: center; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vc-sheet-scrim { position: fixed; inset: 0; z-index: 80; background: rgba(6, 20, 16, 0.42);
  opacity: 0; transition: opacity .28s ease; display: flex; align-items: flex-end; justify-content: center; }
.vc-sheet-scrim.open { opacity: 1; }
.vc-sheet-scrim[hidden] { display: none; }
.vc-sheet { width: 100%; max-width: 520px; background: var(--surface); border-radius: 22px 22px 0 0;
  padding: 8px 0 calc(14px + env(safe-area-inset-bottom)); transform: translateY(102%);
  transition: transform .34s cubic-bezier(.32, .72, 0, 1); max-height: 82vh;
  display: flex; flex-direction: column; box-shadow: 0 -12px 44px rgba(6, 30, 22, 0.24); }
.vc-sheet-scrim.open .vc-sheet { transform: translateY(0); }
.vc-sheet-grab { width: 38px; height: 5px; border-radius: 3px; background: var(--ink-300); margin: 6px auto 10px; }
.vc-sheet-title { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-500); padding: 0 20px 10px; }
.vc-sheet-search { margin: 0 16px 10px; }
/* The global control-layer rule (.vc-app input:not(...)×7) forces width:100%
   at very high specificity, so 100% + the 16px side margins overflowed the
   sheet. !important is the clean way to override just the width; the flex
   column then stretches the field to fill minus its margins. */
.vc-app input.vc-sheet-search { width: auto !important; }
.vc-sheet-list { overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 0 8px 4px; }
.vc-sheet-opt { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 13px 14px; border: none; background: none; border-radius: var(--radius-btn); font-size: 15px;
  font-weight: 500; color: var(--ink-900); cursor: pointer; }
.vc-sheet-opt:active { background: var(--ink-100); }
.vc-sheet-opt.sel { background: var(--brand-50); color: var(--brand-700); font-weight: 600; }
.vc-sheet-opt .chk { margin-left: auto; color: var(--brand-600); opacity: 0; font-size: 17px; }
.vc-sheet-opt.sel .chk { opacity: 1; }

/* 6 ─ Condensing app bar: elevates + tightens as content scrolls under it. */
.topbar { transition: box-shadow .25s ease, background .25s ease; }
.topbar.scrolled { background: rgba(255, 255, 255, 0.98); box-shadow: 0 8px 24px -12px rgba(15, 23, 42, 0.22); }
.topbar .logo-mark { transition: transform .25s ease; }
.topbar.scrolled .logo-mark { transform: scale(.92); }

/* 7 ─ iOS-style large page titles. JS tags the page's first .check-heading as
   .is-large-title, mirrors its <h2> into the app bar, and crossfades the bar
   title in as the big title scrolls up under it. Mobile only. */
@media (max-width: 1023px) {
  .check-heading.is-large-title { margin-bottom: 20px; }
  .check-heading.is-large-title h2 { font-size: 30px; line-height: 34px; }
}
.topbar-title {
  position: absolute; top: env(safe-area-inset-top, 0px); left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none; transition: opacity .25s ease;
}
.topbar-title > span {
  max-width: 74%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: 15px; font-weight: 700; letter-spacing: -0.01em; color: var(--ink-950);
  transform: translateY(7px); transition: transform .25s cubic-bezier(.32, .72, 0, 1);
}
.topbar.show-title .topbar-title { opacity: 1; }
.topbar.show-title .topbar-title > span { transform: translateY(0); }
/* The compact title and the wordmark were both drawn in the middle of the bar,
   the title centred over the whole width and the logo underneath it, so any
   title long enough to reach the left third ran straight through "VisaCheki".
   They are alternates, not neighbours: the logo hands the bar over. */
.topbar .logo { transition: opacity .25s ease; }
.topbar.show-title .logo { opacity: 0; pointer-events: none; }
@media (min-width: 1024px) { .topbar-title { display: none; } }

/* 8 ─ Trip-type floating dock — selected option lifts into a gradient circle. */
.vc-trip-dock { display: flex; justify-content: space-around; align-items: flex-end; gap: 4px;
  background: var(--ink-50); border: 1px solid var(--ink-200); border-radius: 20px; padding: 8px 6px 10px; }
.vc-trip-dock-item { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 6px;
  background: none; border: none; cursor: pointer; color: var(--ink-400); padding: 0;
  transition: transform .32s cubic-bezier(.34,1.3,.5,1), color .2s; }
.vc-trip-dock-item .ic { width: 42px; height: 42px; border-radius: 14px; background: var(--surface);
  border: 1px solid var(--ink-200); display: grid; place-items: center; box-shadow: 0 1px 2px rgba(15,23,42,0.06);
  transition: width .32s cubic-bezier(.34,1.3,.5,1), height .32s cubic-bezier(.34,1.3,.5,1), border-radius .3s, background .3s; }
/* undo the .action-select-wrap caret positioning on these icons */
.vc-trip-dock-item .ic i { position: static; top: auto; right: auto; transform: none; pointer-events: auto; font-size: 19px; }
.vc-trip-dock-item .tl { font-size: 11px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.vc-trip-dock-item.on { transform: translateY(-14px); color: var(--brand-700); }
.vc-trip-dock-item.on .ic { width: 54px; height: 54px; border-radius: 18px; border: none;
  background: linear-gradient(135deg, var(--brand-600), var(--brand-400)); box-shadow: 0 12px 22px rgba(0,150,109,0.4);
  outline: 4px solid var(--surface); }
.vc-trip-dock-item.on .ic i { font-size: 25px; color: #fff; }
.vc-trip-dock-item.on .tl { font-size: 12.5px; font-weight: 700; line-height: 19px;}

/* 9 ─ Depth/elevation system + thumb-friendly tap targets.
   The --elev-* tokens themselves live once, in the Dossier block further down.
   They used to be declared here too, in the cool slate palette, and again in
   the middle of the file; all three were plain :root, so the last simply won and
   the earlier two were read as authoritative by anyone editing near them. */
.vc-card, .route-tile, .dev-card { box-shadow: var(--elev-1); }
.score-card-premium, .action-card { box-shadow: var(--elev-2); }
@media (max-width: 1023px) {
  /* Comfortable thumb targets — min 44px on the things people tap. */
  .vc-app button, .nav-btn, .choice-btn, .header-cta, .header-btn, .sidebar-btn,
  .ep-row, .vc-sheet-opt, .as-sheet-field, .action-select, .vc-trip-card {
    min-height: 44px;
  }
}

/* 10 ─ Result reaction confetti canvas (fixed, non-interactive overlay). */
.vc-confetti { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 90; }

/* 11 ─ App-grade dark mode — follows the OS (prefers-color-scheme). The palette
   is token-driven, so most surfaces (var(--surface)) and text (var(--ink-*))
   flip automatically. This block also darkens the hardcoded translucent bars,
   the Tailwind slate/white utilities used in views, and the API-docs prose. */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --bg: #0b1219;
    --surface: #141e29;
    --surface-2: #0f1822;
    /* ink scale flips: 950 = lightest (text), 50 = darkest (surface) */
    --ink-950: #F1F5F9; --ink-900: #E6ECF3; --ink-700: #C7D1DD; --ink-600: #A6B3C1;
    --ink-500: #8794A4; --ink-400: #6C7B8C; --ink-300: #3B4753; --ink-200: #2A3743;
    --ink-100: #1C2732; --ink-50: #15202B;
    --brand-50: rgba(0,150,109,0.18); --brand-100: rgba(0,150,109,0.26);
    --brand-400: #34D399; --brand-500: #2FBE8E; --brand-600: #15B487; --brand-700: #38CB9F;
    --accent-50: rgba(0,186,136,0.18); --accent-100: rgba(0,186,136,0.26);
    --accent-500: #22C99B; --accent-600: #16B489; --accent-700: #40D4AB; --accent-800: #6FE0C0;
    --amber-50: rgba(217,119,6,0.18); --amber-100: rgba(217,119,6,0.26);
    --amber-600: #EFA23A; --amber-700: #ECAA4B; --amber-800: #F1BB6C; --amber-900: #F3C784;
    --rose-50: rgba(225,29,72,0.18); --rose-100: rgba(225,29,72,0.26);
    --rose-600: #FB7185; --rose-700: #F06178;
    /* --elev-* are set once, in the Dossier dark block. --amber-800/900 and
       --rose-50/100 stay here because that block does not restate them. */
  }
  /* Translucent app bars (hardcoded whites can't be tokenised) */
  .topbar { background: rgba(17,25,34,0.86); border-bottom-color: var(--ink-200); }
  .topbar.scrolled { background: rgba(17,25,34,0.94); }
  .bottomnav { background: rgba(20,30,41,0.72); border-color: rgba(42,55,67,0.9); }
  .topbar-title > span { color: var(--ink-950); }
  .vc-app ::selection { background: rgba(0,186,136,0.30); }
  /* Tailwind CDN utilities used in views (fixed colours won't flip via tokens) */
  .vc-app .bg-white { background-color: var(--surface) !important; }
  .vc-app .bg-slate-50 { background-color: var(--surface-2) !important; }
  .vc-app .bg-slate-100 { background-color: var(--ink-100) !important; }
  .vc-app .text-slate-900 { color: var(--ink-950) !important; }
  .vc-app .text-slate-800 { color: var(--ink-900) !important; }
  .vc-app .text-slate-700 { color: var(--ink-700) !important; }
  .vc-app .text-slate-600 { color: var(--ink-600) !important; }
  .vc-app .text-slate-500 { color: var(--ink-500) !important; }
  .vc-app .text-slate-400 { color: var(--ink-400) !important; }
  .vc-app .border-slate-200 { border-color: var(--ink-200) !important; }
  .vc-app .border-slate-300 { border-color: var(--ink-300) !important; }
  /* API-docs prose (uses hardcoded rgb()) */
  .vc-app .docs-prose h1, .vc-app .docs-prose h2, .vc-app .docs-prose h3,
  .vc-app .docs-prose strong, .vc-app .docs-prose th { color: var(--ink-950); }
  .vc-app .docs-prose p, .vc-app .docs-prose td { color: var(--ink-600); }
  .vc-app .docs-prose :not(pre) > code { background: var(--ink-100); color: var(--ink-900); }
  .vc-app .docs-prose th { background: var(--ink-100); }
  .vc-app .docs-prose th, .vc-app .docs-prose td { border-color: var(--ink-200); }
}

/* 12 ─ Check-form popover (mobile) — the home check card floats ABOVE the
   bottom nav, anchored to the centre button with a pointer. The scrim sits
   BELOW the nav (z 35 < nav 40) so the menu stays visible; the card sits above
   it (z 55). Scrim + card are reparented to <body> so nothing traps them. */
.vc-checkpop-scrim { position: fixed; inset: 0; z-index: 35; background: rgba(15,23,42,0.42);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  opacity: 0; pointer-events: none; transition: opacity .3s ease; }
.vc-checkpop-sheet { position: fixed; left: 50%; bottom: calc(env(safe-area-inset-bottom) + 100px);
  width: calc(100% - 28px); max-width: 372px; z-index: 55; background: var(--surface);
  border-radius: 26px; box-shadow: 0 24px 60px rgba(6,30,22,0.30), 0 4px 14px rgba(6,30,22,0.10);
  padding: 25px; overflow: visible;
  transform: translateX(-50%) translateY(18px) scale(.97); transform-origin: bottom center;
  opacity: 0; pointer-events: none;
  transition: transform .38s cubic-bezier(.16,1,.3,1), opacity .25s ease; }
/* downward notch pointing at the centre button (flat 32x12 triangle) */
.vc-checkpop-sheet::after { content: ""; position: absolute; left: 50%; bottom: -11px; margin-left: -16px;
  width: 0; height: 0; border-left: 16px solid transparent; border-right: 16px solid transparent;
  border-top: 12px solid var(--surface); filter: drop-shadow(0 4px 3px rgba(6,30,22,0.09)); }
body.checkpop-open .vc-checkpop-scrim { opacity: 1; pointer-events: auto; }
body.checkpop-open .vc-checkpop-sheet { transform: translateX(-50%) translateY(0) scale(1); opacity: 1; pointer-events: auto; }
.vc-checkpop-head { display: flex; align-items: center; justify-content: space-between; margin: 0 0 14px; }
.vc-checkpop-title { font-size: 19px; font-weight: 800; letter-spacing: -0.02em; color: var(--ink-950); }
.ckt-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.ckt-head-title { display: inline-flex; align-items: center; gap: 9px; font-size: 16.5px; font-weight: 800; letter-spacing: -0.015em; color: var(--ink-950); line-height: 25px;}
.ckt-head-ic { width: 28px; height: 28px; flex: none; border-radius: 9px; background: var(--brand-50); border: 1px solid var(--brand-100);
  color: var(--brand-600); display: grid; place-items: center; font-size: 15px; }
.vc-checkpop-x { box-sizing: border-box; padding: 0; width: 43px; border-radius: 50%; border: none;
  background: var(--ink-100); color: var(--ink-500); display: grid; place-items: center; cursor: pointer; flex: none; transition: background .15s, color .15s; }
.vc-checkpop-x:hover { background: var(--ink-200); color: var(--ink-700); }
.vc-checkpop-x svg { width: 15px; height: 15px; }
.vc-checkpop-x:active { transform: scale(.9); }
/* Form inside the popover — drop card chrome, compact spacing (no scroll). */
.vc-checkpop-sheet .action-card { border: none; box-shadow: none; padding: 0; background: transparent; }
.vc-checkpop-sheet .action-stack { gap: 12px; }
.vc-checkpop-sheet .action-label { font-size: 12.5px; font-weight: 600; color: var(--ink-600); margin-bottom: 6px; margin-left: 2px; display: block; line-height: 19px;}
.vc-checkpop-sheet .as-sheet-field { border-radius: 16px; padding: 12px 40px 12px 14px; font-size: 14px; font-weight: 500;
  border-color: var(--ink-200); box-shadow: 0 1px 2px rgba(15,23,42,0.05); }
.vc-checkpop-sheet .action-btn { border-radius: var(--radius-btn); padding: 16px; font-size: 15px; font-weight: 700;
  box-shadow: 0 4px 12px rgba(0,150,109,0.20); }
.vc-checkpop-sheet .vc-trip-dock { margin-top: 16px; }

/* Mobile: give the home stat cards real card chrome (desktop styles them
   inside its own min-width media query, leaving mobile unstyled). */
@media (max-width: 1023px) {
  .stat-card { background: var(--surface); border: 1px solid var(--ink-200); border-radius: var(--radius-lg); padding: 13px 11px; }
  .stat-card-icon { width: 30px; height: 30px; background: var(--brand-50); border: 1px solid var(--brand-100);
    border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; margin-bottom: 9px;
    color: var(--brand-600); font-size: 15px; }
  .stat-card-val { font-size: 19px; font-weight: 700; color: var(--ink-950); margin-bottom: 2px; letter-spacing: -0.02em; }
  .stat-card-label { font-size: 11px; color: var(--ink-500); font-weight: 500; line-height: 14px; }
}

/* ─ 11 ─ Eligibility result: Counsel x Coach ────────────────────────────────
   The result screen leads with a sentence, keeps the score at conversational
   volume, and turns every flagged concern into a checkable task carrying the
   points it is worth. Rendered by triage.js on both the live questionnaire
   and /report/:token, so the two surfaces stay identical. */
/* Capped at a readable measure but flush left, so it lines up with the
   breadcrumb and page heading rather than floating in the middle of the card. */
.tri {
  max-width: 560px; margin-right: auto;
  display: flex; flex-direction: column;
}
/* Type spec lifted verbatim from the approved mockup. Sized against a 296px
   content width there, so it has more room on a real phone, not less. */
.tri-lead {
  font-size: 31px; line-height: 35px; letter-spacing: -0.034em;
  font-weight: 600; color: var(--ink-950); margin: 0 0 14px; text-wrap: balance;
}
.tri-lead em { font-style: normal; color: var(--brand-600); }
.tri-say {
  font-size: 14.5px; line-height: 23px; color: var(--ink-600);
  margin: 0; max-width: 46ch;
}

/* Gauge: the closable gap. Score present, never shouting. */
.tri-gauge { margin: 26px 0 4px; }
.tri-gnum { display: block; margin-bottom: 14px; }
.tri-gnum b {
  font-size: 44px; font-weight: 800; letter-spacing: -0.045em; line-height: 1;
  color: var(--ink-950); font-variant-numeric: tabular-nums;
  display: inline-block; margin-right: 10px;
}
.tri-vd { font-size: 14px; font-weight: 600; }
.tri-proj { display: block; margin-top: 5px; font-size: 13px; color: var(--ink-500); }
.tri-proj u { text-decoration: none; color: var(--brand-600); font-weight: 600; font-variant-numeric: tabular-nums; }
/* One track, and the projection drawn over it as A's tint inside B's outline
   with the number it is reaching for above the end.

   The ramp rides the FILL, so the whole red to amber to green run shows across
   the drawn line rather than only the stretch of a 0-100 ramp the score has
   got to. Asked for directly, and the trade is worth stating: every value now
   ends on green, so a 34 gets the same green tip as a 92 and the tip colour
   stops carrying meaning. The distance line above the bar is what says how the
   score is doing.

   Six pixels, not the home page's four: this is the page's subject rather than
   a supporting figure beside a headline. */
.tri-gbar {
  position: relative; height: 6px; margin-bottom: 26px;
  background: var(--ink-200);
}
.tri-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  border-radius: 99px;
  background: linear-gradient(90deg, var(--rose-500-mark), var(--amber-500-mark) 55%, var(--brand-600));
  transition: width .6s cubic-bezier(.32,.72,0,1);
}
/* The tint alone marks the projection now. It sits in the track rather than
   standing proud of it, and takes --brand-100 rather than --brand-50: with no
   outline defining the region the tint has to do that job on its own, and
   brand-50 is close enough to the track's value to read as nothing. */
.tri-ghost {
  position: absolute; top: 0; bottom: 0;
  background: var(--brand-100);
  transition: left .6s cubic-bezier(.32,.72,0,1), width .6s cubic-bezier(.32,.72,0,1);
}
/* The number that outline is reaching for, above the end of it. */
.tri-peak {
  position: absolute; top: -19px; transform: translateX(-50%);
  font-size: 11px; font-weight: 600; color: var(--brand-600);
  font-variant-numeric: tabular-nums;
  transition: left .6s cubic-bezier(.32,.72,0,1);
}
.tri-tgt {
  position: absolute; top: -5px; width: 2px; height: 16px;
  background: var(--ink-300); border-radius: 1px;
}
.tri-tgtl {
  position: absolute; top: 15px; transform: translateX(-50%);
  font-size: 9.5px; color: var(--ink-400); white-space: nowrap;
  letter-spacing: 0.04em; line-height: 14px;
}
/* The poles of the scale, on the same line as the threshold's caption so the
   three read as one row of axis labels rather than three separate notes. */
.tri-end {
  position: absolute; top: 15px;
  font-size: 9.5px; color: var(--ink-400); white-space: nowrap;
  letter-spacing: 0.04em; line-height: 14px;
}
.tri-end-lo { left: 0; }
.tri-end-hi { right: 0; }

/* Plan: concerns as things you can act on, as one ledger block. The left rail
   is coloured from the flag's real severity (data-sev, set in triage.js), so
   priority reads as form before anyone parses the 'worth N' number. */
.tri-plan {
  margin-top: 30px; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid rgba(15,23,42,0.07);
  border-radius: var(--radius-card); overflow: hidden;
}
.tri-plan-h {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--ink-400);
  padding: 13px 15px 11px; margin: 0;
  background: var(--ink-50); border-bottom: 1px solid rgba(15,23,42,0.07); line-height: 16px;
}
.tri-plan-h span { color: var(--brand-600); letter-spacing: 0.06em; }
.tri-task {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 15px 15px 15px 12px;
  border: none; border-bottom: 1px solid var(--ink-100);
  border-left: 3px solid var(--ink-300);
  background: none; text-align: left; width: 100%; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background .15s ease, border-left-color .2s ease;
}
.tri-task:last-child { border-bottom: none; }
.tri-task:active { background: var(--ink-50); }
.tri-task[data-sev="high"]   { border-left-color: var(--rose-500-mark); }
.tri-task[data-sev="medium"] { border-left-color: var(--amber-500-mark); }
.tri-task[data-sev="low"]    { border-left-color: var(--ink-300); }
/* Once addressed the rail stops shouting: the concern is handled. */
.tri-task.done[data-sev]     { border-left-color: var(--brand-500); }
.tri-box {
  width: 21px; height: 21px; border-radius: 6px; flex: none; margin-top: 2px;
  border: 1.5px solid var(--ink-300); background: transparent;
  display: grid; place-items: center; font-size: 11px; color: transparent;
  transition: background .2s, border-color .2s, color .2s;
}
.tri-task.done .tri-box { background: var(--brand-600); border-color: var(--brand-600); color: #fff; }
.tri-tx { flex: 1; min-width: 0; }
.tri-tx b {
  display: block; font-size: 14.5px; font-weight: 600;
  color: var(--ink-950); margin-bottom: 3px; letter-spacing: -0.005em; line-height: 22px;
}
.tri-tx span { display: block; font-size: 12.5px; color: var(--ink-500); line-height: 18px; }
.tri-task.done .tri-tx b { color: var(--ink-400); text-decoration: line-through; }
.tri-task.done .tri-tx span { color: var(--ink-400); }
.tri-wt {
  flex: none; padding-top: 3px; font-size: 11.5px; color: var(--ink-400);
  white-space: nowrap; font-variant-numeric: tabular-nums; line-height: 17px;
}
.tri-task.done .tri-wt { color: var(--brand-600); font-weight: 600; }

/* Projection honesty + follow-ups. */
.tri-note {
  margin-top: 16px; display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 11px 13px; border-radius: var(--radius-card);
  background: var(--ink-50); border: 1px solid var(--ink-200);
  font-size: 11.5px; color: var(--ink-500); line-height: 17px;
}
.tri-note button {
  background: none; border: none; flex: none;
  font-size: 12px; font-weight: 600; color: var(--brand-600); cursor: pointer;
}
.tri-recs { margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--ink-100); }
.tri-recs h4 {
  margin: 0 0 12px; font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-400); line-height: 16px;
}
.tri-recs h4 { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.tri-progress {
  font-size: 11px; font-weight: 650; letter-spacing: 0; text-transform: none;
  color: var(--ink-500); font-variant-numeric: tabular-nums; flex: none;
}

/* A checkbox rather than a tick. Five green check-circles read as five things
   already achieved, which is the opposite of a list of things still to do. */
.tri-rec {
  display: flex; gap: 10px; padding: 9px 0; font-size: 13.5px;
  color: var(--ink-700); line-height: 20px; cursor: pointer;
}
.tri-rec input { position: absolute; opacity: 0; width: 0; height: 0; }
.tri-box {
  flex: none; width: 18px; height: 18px; margin-top: 1px;
  border: 1.5px solid var(--ink-300); border-radius: var(--radius-sm);
  background: var(--surface); display: flex; align-items: center; justify-content: center;
  color: transparent; font-size: 12px; transition: background .15s, border-color .15s;
}
.tri-rec input:checked + .tri-box {
  background: var(--brand-600); border-color: var(--brand-600); color: #fff;
}
/* The box is the visible control, so the focus ring belongs on it, not on the
   input that is off-screen for styling. */
.tri-rec input:focus-visible + .tri-box {
  outline: 2px solid var(--brand-500); outline-offset: 2px;
}
.tri-rec.is-done > span:last-child { color: var(--ink-400); text-decoration: line-through; }
@media (hover: hover) {
  .tri-rec:hover .tri-box { border-color: var(--ink-400); }
  .tri-rec:hover input:checked + .tri-box { border-color: var(--brand-600); }
}
@media (prefers-reduced-motion: reduce) { .tri-box { transition: none; } }
.tri-foot {
  margin-top: 26px; padding-top: 16px; border-top: 1px solid var(--ink-100);
  display: flex; align-items: center; gap: 7px; font-size: 11px; color: var(--ink-400);
}
@media (max-width: 380px) {
  .tri-lead { font-size: 27px; }
}

/* ─ 10 ─ Premium surface pass ───────────────────────────────────────────────
   Surfaces sit *above* the ground on a layered shadow (a 1px contact shadow
   for crispness plus a wide ambient one for lift) instead of being fenced in
   by a visible hairline. Radius opens up so cards read as native panels.
   Placed after the .stat-card media blocks above so it wins the cascade. */
/* --radius-card, --edge-card and --elev-card were declared here as well, in the
   cool slate palette. The Dossier block below restates all three, so these were
   dead: 16px of card radius that never applied, and the shadow the comment above
   describes is the one defined there, not here. */
.stat-card, .route-tile, .cl-card, .vc-card, .dev-card {
  border: 1px solid var(--edge-card);
  border-radius: var(--radius-card);
  box-shadow: var(--elev-card);
}

/* Stat cards are controls now, not decoration: each opens the thing it counts.
   Reset the <button>/<a> chrome so they still render as cards. */
.stat-card {
  position: relative; display: block; width: 100%;
  text-align: left; font: inherit; color: inherit;
  background: var(--surface); cursor: pointer; text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: transform .15s ease, background .15s ease, box-shadow .15s ease;
}
.stat-card:hover { border-color: var(--ink-300); }
.stat-card:active { transform: scale(0.97); background: var(--ink-50); }
.stat-card-ch {
  position: absolute; top: 11px; right: 9px;
  display: flex; font-size: 12px; line-height: 1; color: var(--ink-300);
}
.stat-card-val { font-variant-numeric: tabular-nums; }

/* Trending is ranked from real lookup volume, so the heading says so. The view
   only renders this variant when the rows are genuine data, never the fallback. */
.section-head-live {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 16px;
}
.section-head-live .section-head {
  margin-bottom: 0; display: flex; align-items: center; gap: 8px;
}
.section-head-meta {
  font-size: 10.5px; font-weight: 500; color: var(--ink-400);
  white-space: nowrap; font-variant-numeric: tabular-nums;
 line-height: 16px;}
.live-dot {
  position: relative; flex: none;
  width: 7px; height: 7px; border-radius: 50%; background: var(--brand-500);
}
.live-dot::after {
  content: ""; position: absolute; inset: -4px;
  border-radius: 50%; border: 1.5px solid var(--brand-500); opacity: 0.55;
  animation: vc-live-pulse 2.1s ease-out infinite;
}
@keyframes vc-live-pulse {
  0%   { transform: scale(0.55); opacity: 0.7; }
  70%  { transform: scale(1.25); opacity: 0; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .live-dot::after { animation: none; opacity: 0.35; }
}

/* ─ Production polish & accessibility ───────────────────────────────────────
   Additive, low-risk global consistency: a visible keyboard focus ring on every
   interactive element (keyboard only, never on mouse click), consistent disabled
   affordance, smooth in-page scrolling, and a full reduced-motion guard. */
.vc-app a:focus-visible,
.vc-app button:focus-visible,
.vc-app [role="button"]:focus-visible,
.vc-app input:focus-visible,
.vc-app select:focus-visible,
.vc-app textarea:focus-visible,
.vc-app .action-btn:focus-visible,
.vc-app .action-select:focus-visible,
.vc-app .as-sheet-field:focus-visible,
.vc-app .nav-item:focus-visible,
.vc-app .nav-fab:focus-visible {
  outline: 2px solid var(--brand-500);
  outline-offset: 2px;
}
/* Choice cards wrap a hidden radio; ring the card on keyboard focus. Kept in its
   own rule so an unsupported :has() can't invalidate the main focus ring above. */
.vc-app .choice-btn:has(input:focus-visible) {
  outline: 2px solid var(--brand-500);
  outline-offset: 2px;
}
/* Mouse/touch users keep the softer control styles; no hard outline on click. */
.vc-app :focus:not(:focus-visible) { outline: none; }

/* Consistent disabled affordance across custom controls. */
.vc-app button:disabled,
.vc-app [aria-disabled="true"],
.vc-app .action-btn:disabled { cursor: not-allowed; }

/* Smooth in-page scrolling and readable long-word wrapping in content. */
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }

/* Respect users who ask for reduced motion: neutralise all animation/transition. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* Trending routes on mobile: three cards, not six borderless rows.
   Desktop keeps its full row of six tiles.

   Six rows ran past the fold and the ones below it were scrolled past rather
   than read, so the list stops at three. They are cards now rather than rules
   between rows: a bordered surface reads as something to tap, which is what
   these are, and it matches the route cards used elsewhere.

   One row. The route code sits in front of the visa badge rather than under
   the destination, which is where it belongs anyway: the code and the badge
   are both facts about the route, while the destination name is the thing you
   are scanning for. Stacked, each tile spent a second line on six characters. */
@media (max-width: 1023px) {
  .routes-grid { display: flex; flex-direction: column; gap: 8px; }
  .routes-grid .route-tile:nth-child(n+4) { display: none; }

  .routes-grid .route-tile {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto auto;
    grid-template-areas: "flag name code badge chev";
    align-items: center; column-gap: 11px;
    padding: 9px 13px;
    border: 1px solid var(--edge-card); border-bottom: 1px solid var(--edge-card);
    border-radius: var(--radius-btn); background: var(--surface); box-shadow: var(--elev-1);
  }
  .routes-grid .route-tile:active { background: var(--ink-50); }
  .routes-grid .route-tile-flag { grid-area: flag; font-size: 20px; margin-bottom: 0; }
  .routes-grid .route-tile-name {
    grid-area: name; font-size: 13.5px; font-weight: 600;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 20px;
}
  /* Never wraps: it is five or six characters and an arrow, and letting it
     wrap would put the second line back that this layout removed. */
  .routes-grid .route-tile-code {
    grid-area: code; margin-top: 0; color: var(--ink-500); font-size: 11.5px; font-weight: 500;
    white-space: nowrap; font-variant-numeric: tabular-nums; line-height: 17px;
}
  .routes-grid .route-tile-badge { grid-area: badge; position: static; }
  .routes-grid .route-tile::after {
    grid-area: chev; content: "\203A"; color: var(--ink-300); font-size: 19px; line-height: 1;
  }
}

/* Five things in one row need about 360px. Below that the destination name is
   what gets crushed: measured at 320px it fell to 64px, which truncates
   Liechtenstein to "Liecht". The code is the item that goes, because the flag
   and the name already say the destination and the origin is the passport the
   visitor picked themselves. */
@media (max-width: 359px) {
  .routes-grid .route-tile {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-areas: "flag name badge chev";
  }
  .routes-grid .route-tile-code { display: none; }
}

/* Redesigned check ticket inside the popover (route panel + purpose field) ─── */
.vc-checkpop-sheet .ckt { padding: 0; }
.ckt-panel { display: flex; align-items: center; gap: 6px; background: var(--ink-50); border: 1px solid var(--ink-200);
  border-radius: var(--radius-card); padding: 16px 10px; }
/* Follows the panel down: a 12px region inside a 6px panel reads as a mistake,
   its corners being rounder than the box that contains them. */
.ckt-ep { flex: 1; position: relative; text-align: center; padding: 4px 2px; border-radius: var(--radius-btn); min-width: 0; }
.ckt-ep:active { background: var(--ink-100); }
.ckt-ep .fl { display: block; font-size: 30px; line-height: 1; }
.ckt-ep .cd { display: block; font-size: 16px; font-weight: 800; letter-spacing: .01em; color: var(--ink-950); margin-top: 6px; }
.ckt-ep .nm { display: block; font-size: 11px; color: var(--ink-500); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
.ckt-ep .as-sheet-field, .ckt-ep select { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; padding: 0; margin: 0;
  border: none; min-height: 0; box-shadow: none; -webkit-appearance: none; appearance: none; background: none; cursor: pointer; }
.ckt-mid { flex: none; display: flex; flex-direction: column; align-items: center; gap: 6px; }
.ckt-mid-lbl { font-size: 9px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-400); }
.ckt-mid-ic { width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--ink-200); background: var(--surface);
  color: var(--ink-400); display: grid; place-items: center; font-size: 13px; }

.ckt-div { border-top: 1px dashed var(--ink-200); margin: 18px 0 16px; }

.ckt-purpose { margin-bottom: 16px; }
.ckt-purpose-lbl { display: block; font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-400); margin-bottom: 8px; }

/* ══════════════════════════════════════════════════════════════════════════
   DOSSIER  ·  design system
   ──────────────────────────────────────────────────────────────────────────
   Documentary treatment: warm paper ground, a warm-biased ink scale, sheets
   defined by a faint hairline plus soft depth, and a coloured left rail as
   the single device for state. Tighter geometry than Terrace throughout.

   Appended last so it wins the cascade, and kept as one contiguous block so
   the direction is reversible by deleting from this banner to end of file.
   NOTE: because this :root sits after the prefers-color-scheme block earlier
   in the file, the dark overrides MUST be repeated here or dark mode silently
   inherits these light values.
   ═════════════════════════════════════════════════════════════════════════ */
:root {
  /* Warm paper, and an ink scale with a brown bias so it sits on that paper */
  --bg: #FAF9F7;
  --surface: #FFFFFF;
  --surface-2: #F5F3F0;
  --ink-950: #12100E; --ink-900: #23201C; --ink-700: #3F3931; --ink-600: #5C554D;
  --ink-500: #7A726A; --ink-400: #9C948B; --ink-300: #C6BFB7; --ink-200: #E4DFD8;
  --ink-100: #EFEBE6; --ink-50: #F5F3F0;

  --brand-700: #00553D; --brand-600: #00966D; --brand-500: #1D9E75;
  --brand-100: #D4F4E6; --brand-50: #EEFBF6;
  --accent-700: #00553D; --accent-600: #00966D; --accent-500: #1D9E75;
  --accent-100: #D4F4E6; --accent-50: #EEFBF6;
  --amber-600: #B0700A; --amber-700: #8F5A08;
  --rose-600: #C1264A; --rose-700: #A01D3C;

  /* Marks, not words. The 600s above were darkened for text, which at 4.06:1
     still does not reach the 4.5 body text needs, so the darkening bought no
     compliance and only turned amber brown: #B0700A reads as mustard beside
     the rose on the same list. A graphic mark needs 3.0:1, which both of these
     clear (3.19 and 4.70), so bars and severity rails use them and text keeps
     the darker pair. These are also the literals the home page's score ramp
     was already written with, so all three now agree on one amber. */
  --rose-500-mark: #E11D48; --amber-500-mark: #D97706;

  /* Tighter than Terrace. Buttons come down from 17px. */
  /* shadcn's derived scale, flattened at the card level: one 6px radius for
     every surface. Controls were already there; surfaces come down to join
     them, which is what the score card and the trending tiles each did on
     their own. */
  --radius-sm: 4px; --radius-md: 6px; --radius-lg: 6px; --radius-xl: 6px;
  --radius-btn: 6px; --radius-card: 6px;

  --edge-card: rgba(18,16,14,0.07);
  --elev-card: 0 1px 2px rgba(18,16,14,.04), 0 10px 24px -14px rgba(18,16,14,.22);
  --elev-1: 0 1px 2px rgba(18,16,14,.05);
  --elev-2: 0 2px 8px rgba(18,16,14,.07), 0 1px 2px rgba(18,16,14,.05);
  --elev-3: 0 12px 32px -8px rgba(18,16,14,.20);

  /* Direction B's glass, used by the phone score card. */
  --b-surface: rgba(255,255,255,0.86);
  --b-edge:    rgba(255,255,255,0.90);
  --b-elev:    0 1px 1px rgba(18,16,14,.03), 0 12px 30px -14px rgba(18,16,14,.26);
}

/* Dark mode, warmed to match. Repeated here on purpose: see note above. */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #12100E; --surface: #1C1917; --surface-2: #171412;
    --ink-950: #F5F2EE; --ink-900: #E8E3DC; --ink-700: #CFC7BD; --ink-600: #ADA398;
    --ink-500: #8E8478; --ink-400: #6F675C; --ink-300: #4A443C; --ink-200: #332E29;
    --ink-100: #25211D; --ink-50: #1E1B18;
    --brand-50: rgba(0,150,109,0.18); --brand-100: rgba(0,150,109,0.28);
    --brand-500: #2FBE8E; --brand-600: #17B489; --brand-700: #39CBA0;
    --accent-50: rgba(0,186,136,0.18); --accent-100: rgba(0,186,136,0.28);
    --accent-500: #22C99B; --accent-600: #16B489; --accent-700: #40D4AB;
    --amber-600: #E0A33C; --amber-700: #ECAA4B;
    --rose-600: #F2557A; --rose-700: #F06178;
    --rose-500-mark: #F2557A; --amber-500-mark: #E0A33C;
    --edge-card: rgba(255,255,255,0.08);
    --elev-card: 0 1px 2px rgba(0,0,0,.45), 0 12px 28px -14px rgba(0,0,0,.65);
    --elev-1: 0 1px 2px rgba(0,0,0,.4);
    --elev-2: 0 2px 10px rgba(0,0,0,.5);
    --elev-3: 0 14px 36px -8px rgba(0,0,0,.6);

    --b-surface: rgba(38,34,30,0.78);
    --b-edge:    rgba(255,255,255,0.09);
    --b-elev:    0 1px 1px rgba(0,0,0,.40), 0 14px 32px -14px rgba(0,0,0,.70);
  }
}

/* Sheets: a faint hairline plus depth, not one or the other */
.vc-card, .stat-card, .route-tile, .cl-card, .dev-card, .action-card, .score-card-premium {
  border: 1px solid var(--edge-card);
  border-radius: var(--radius-card);
  box-shadow: var(--elev-card);
  background: var(--surface);
}
.vc-card-sm, .insight { border: 1px solid var(--edge-card); border-radius: var(--radius-lg); box-shadow: var(--elev-1); }

/* Buttons: tighter corners, restrained shadow */
.btn-brand, .action-btn, .btn-dark, .btn-ghost, .header-cta, .choice-btn {
  border-radius: var(--radius-btn);
}
.btn-brand, .action-btn { font-weight: 650; box-shadow: 0 4px 12px -4px rgba(0,150,109,.35); }
.btn-brand:hover, .action-btn:hover { box-shadow: 0 6px 16px -5px rgba(0,150,109,.45); }

/* Selection tints rather than fills: quieter than Terrace by design */
.choice-btn { border: 1px solid var(--ink-200); box-shadow: none; }
.choice-btn:hover { border-color: var(--ink-300); }
.choice-btn.selected,
.vc-app .choice-btn:has(input:checked) {
  background: var(--brand-50); color: var(--brand-700);
  border-color: var(--brand-500); box-shadow: inset 0 0 0 1px var(--brand-500);
}

.action-select, .as-sheet-field { border-radius: var(--radius-btn); border-color: var(--ink-200); }
.action-select:focus { box-shadow: 0 0 0 3px var(--brand-50); }
.route-tile-badge { border-radius: var(--radius-sm); font-weight: 600; }

.bottomnav { border: 1px solid var(--edge-card); border-radius: 22px; box-shadow: var(--elev-3); }
.nav-fab-ic { box-shadow: 0 8px 18px -4px rgba(0,150,109,.55); }

/* Result screen returns to the ledger, with the rail carrying severity */
.tri-plan {
  background: var(--surface); border: 1px solid var(--edge-card);
  border-radius: var(--radius-card); overflow: hidden; gap: 0;
}
.tri-plan-h {
  background: var(--ink-50); border-bottom: 1px solid var(--edge-card);
  padding: 13px 15px 11px; margin: 0;
}
.tri-task {
  background: none; border: none; border-bottom: 1px solid var(--ink-100);
  border-left: 3px solid var(--ink-300); border-radius: 0;
  padding: 15px 15px 15px 12px; box-shadow: none;
}
.tri-task:last-child { border-bottom: none; }
.tri-task[data-sev="high"]   { border-left-color: var(--rose-500-mark); }
.tri-task[data-sev="medium"] { border-left-color: var(--amber-500-mark); }
.tri-task[data-sev="low"]    { border-left-color: var(--ink-300); }
.tri-task.done[data-sev]     { border-left-color: var(--brand-500); }
.tri-task.done { background: none; }
/* The rail encodes severity again, so the checkbox goes back to neutral */
.tri-task[data-sev] .tri-box { border-color: var(--ink-300); }
.tri-task.done .tri-box { border-color: var(--brand-600); }
.tri-gbar { height: 6px; }
.tri-tgt { top: -5px; height: 16px; }
.tri-note { border: 1px solid var(--ink-200); border-radius: var(--radius-card); background: var(--ink-50); }

/* ─ Focused flow chrome (mobile) ────────────────────────────────────────────
   The questionnaire is a flow, not a page, so on mobile it takes over the
   screen: its own step bar replaces the app header, and the step controls pin
   above the safe area. Desktop keeps the existing inline header and buttons,
   where there is room for both. */
.step-bar { display: none; }
.step-nav {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 28px; padding-top: 22px; border-top: 1px solid var(--ink-200);
}

@media (max-width: 1023px) {
  body.screen-eligibility .topbar,
  body.screen-eligibility .bottomnav { display: none; }

  .step-bar {
    position: sticky; top: 0; z-index: 45;
    display: flex; align-items: center; gap: 12px;
    /* The content column is capped at 480px and centred, so cancelling only
       .screen's padding left the bar floating in the middle of a tablet with
       white either side. It now breaks out to the full viewport, the way the
       pinned action bar at the bottom already did.
       The safe-area inset is handled by padding, not margin, so the bar's own
       background covers the notch area. */
    width: 100vw;
    margin: -24px 0 18px calc(50% - 50vw);
    padding: calc(14px + env(safe-area-inset-top)) 20px 14px;
    background: var(--surface);
    border-bottom: 1px solid var(--ink-200);
  }
  .step-bar-back {
    flex: none; width: 34px; height: 34px; border-radius: var(--radius-md);
    border: none; background: transparent;
    color: var(--ink-700); font-size: 19px;
    display: grid; place-items: center; cursor: pointer;
    transition: opacity .15s ease, background .15s ease;
  }
  .step-bar-back:disabled { opacity: .35; cursor: default; }
  .step-bar-back:not(:disabled):active { background: var(--ink-50); transform: scale(.94); }
  /* Centred on the bar itself. As a flex child it only centred within the
     space the back button and the count left over, which is off by half their
     difference and reads as slightly wrong rather than obviously broken. */
  .step-bar-title {
    position: absolute; left: 50%; transform: translateX(-50%);
    max-width: 56%; text-align: center; font-size: 15px; font-weight: 650;
    letter-spacing: -.015em; color: var(--ink-950);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .step-bar-count {
    flex: none; min-width: 34px; text-align: right; margin-left: auto;
    font-size: 13px; font-weight: 500; color: var(--ink-400);
    font-variant-numeric: tabular-nums;
  }

  /* Pinned action bar. Sits on its own surface so content scrolls under it. */
  /* One segmented bar welded to the bottom edge: a narrow back panel and the
     primary action taking the rest. No gap, no radius, no inset, so nothing
     floats and the colour runs to the physical edge of the screen. Each button
     carries the safe-area inset itself, so its own fill covers the home
     indicator rather than leaving a strip of bar behind it. */
  .step-nav {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
    margin: 0; gap: 0; padding: 0;
    background: var(--surface);
    border-top: 1px solid var(--ink-200);
    box-shadow: 0 -8px 24px -16px rgba(18,16,14,.35);
  }
  /* :not(.hidden) matters: display:flex here outranks .hidden's display:none,
     which would force the step-6 submit button visible alongside Next. */
  .step-nav .btn-ghost,
  .step-nav .btn-dark:not(.hidden) {
    border-radius: 0; border: none; margin: 0;
    min-height: 62px;
    /* Padding reset in full, not just the bottom: the base button class carries
       11px on top, which flex centring cannot see past and which pushed the
       back arrow 11px below centre. Only the safe-area inset remains, so the
       fill still reaches under the home indicator. */
    padding: 0 0 env(safe-area-inset-bottom);
    display: flex; align-items: center; justify-content: center;
  }
  .step-nav .btn-dark.hidden { display: none; }
  .step-nav .btn-ghost {
    flex: 0 0 23%; opacity: 1 !important;
    background: var(--surface); color: var(--ink-700);
    border-right: 1px solid var(--ink-200); font-size: 19px;
  }
  .step-nav .btn-ghost:disabled { opacity: .3 !important; }
  .step-nav .btn-ghost:not(:disabled):active { background: var(--ink-50); }
  .step-nav-word { display: none; }
  .step-nav .btn-dark {
    flex: 1; width: auto !important;
    font-size: 16px; font-weight: 700; letter-spacing: -.01em; text-transform: none;
  }
  /* Clear the pinned bar so the last field is never trapped underneath. */
  body.screen-eligibility .content-area { padding-bottom: 116px; }
  /* The pinned step bar is position:fixed and needs the viewport as its
     containing block. The transform this wrapper used to keep is gone at every
     width now, handled where slideUpFade is defined, so nothing more is needed
     here. */
}
@media (max-width: 1023px) {
  /* The step bar already carries the crumb, the step name and the count, so
     the page-level versions would just repeat it back. */
  body.screen-eligibility .crumbs { display: none; }
  /* The row carries an inline display:flex, so it needs !important to lose. */
  body.screen-eligibility .step-count-row { display: none !important; }
  /* Primary action takes brand colour here: it is the only thing to do next.
     No lift shadow: the button is welded to the screen edge, not floating. */
  .step-nav .btn-dark {
    background: var(--brand-600); color: #fff;
    font-weight: 700; box-shadow: none;
  }
  .step-nav .btn-dark:active { background: var(--brand-700); }
}
@media (max-width: 1023px) {
  /* The flow hides the app header, so it has to offer its own way out.
     A house reads as "leave and go home" more plainly than a close cross. */
  .step-bar { gap: 10px; }
  /* Bare icons, matching the back control. Boxing one and not the other read
     as a mistake, so neither is boxed. */
  .step-bar-home {
    flex: none; width: 34px; height: 34px; border-radius: var(--radius-md);
    border: none; background: transparent;
    color: var(--ink-600); font-size: 19px; text-decoration: none;
    display: grid; place-items: center;
    transition: background .15s ease, color .15s ease;
  }
  .step-bar-home:active { background: var(--ink-50); color: var(--ink-950); transform: scale(.94); }
  .step-bar-count { min-width: 28px; }
}

/* The questionnaire is a flow, not a document, so the footer has no place mid
   form: it competes with the pinned action bar and pushes the last question up.
   eligibility.js drops .screen-eligibility once the assessment mounts, so the
   footer reappears with the result, where the disclaimer actually belongs. */
body.screen-eligibility .vc-footer { display: none; }

/* ─ Desktop shell: grouped sidebar ──────────────────────────────────────────
   The tool set under /check/:o/:d is large enough that tiles on a page stopped
   scaling. Grouped navigation puts it in the shell, and the groups only render
   when the URL actually carries a route (see main.php). */
@media (min-width: 1024px) {
  .sidebar-count {
    margin-left: auto; flex: none;
    font-size: 11px; font-weight: 700; line-height: 1;
    color: var(--brand-700); background: var(--brand-50);
    border: 1px solid var(--brand-100); border-radius: var(--radius-sm); padding: 3px 7px;
    font-variant-numeric: tabular-nums;
  }
  .sidebar-btn.active .sidebar-count { background: var(--surface); }

  /* Account row replaces the upgrade card once someone is signed in. */
  .sidebar-user {
    margin-top: auto; display: flex; align-items: center; gap: 10px;
    padding: 9px 10px; border-radius: var(--radius-md);
    background: var(--ink-50); border: 1px solid var(--ink-200);
    text-decoration: none; transition: background .15s ease, border-color .15s ease;
  }
  .sidebar-user:hover { background: var(--surface); border-color: var(--ink-300); }
  .sidebar-user-av {
    flex: none; width: 30px; height: 30px; border-radius: 9px;
    background: var(--brand-50); border: 1px solid var(--brand-100);
    color: var(--brand-700); display: grid; place-items: center;
    font-size: 11.5px; font-weight: 700; letter-spacing: .02em; line-height: 17px;
}
  .sidebar-user-txt { min-width: 0; flex: 1; }
  .sidebar-user-txt b {
    display: block; font-size: 12.5px; font-weight: 650; color: var(--ink-950);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 19px;
}
  .sidebar-user-txt span {
    display: block; font-size: 11px; color: var(--ink-500);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .sidebar-user i { flex: none; font-size: 14px; color: var(--ink-400); }

  /* Groups are dense enough now that the old spacing wasted a screen height. */
  .sidebar { gap: 2px; padding: 18px 14px; }
  .sidebar-label { margin: 14px 0 6px; }
  .sidebar-btn { padding: 8px 12px; }
}

/* ─ Desktop shell: header, collapsible rail, command palette ────────────────
   The header stops being a second navigation (that duplicated the sidebar) and
   becomes what a dashboard header is for: where you are, what you are looking
   for, and the utilities. */
.side-toggle, .crumbs-bar, .hdr-search, .hdr-tools { display: none; }

@media (min-width: 1024px) {
  .desktop-nav { display: none !important; }
  .header-btn { display: grid; place-items: center; width: 34px; height: 34px; flex: none; }
  .hdr-tools { display: flex; align-items: center; gap: 9px; flex: none; }

  .side-toggle {
    display: grid; place-items: center; flex: none;
    width: 34px; height: 34px; border-radius: var(--radius-md);
    border: none; background: transparent; color: var(--ink-500);
    font-size: 19px; cursor: pointer; margin-right: 2px;
    transition: background .15s ease, color .15s ease;
  }
  .side-toggle:hover { background: var(--ink-100); color: var(--ink-950); }

  .crumbs-bar {
    display: flex; align-items: center; gap: 7px; min-width: 0;
    margin-left: 8px; font-size: 13px; color: var(--ink-500);
  }
  .crumbs-bar a { color: var(--ink-500); text-decoration: none; white-space: nowrap; }
  .crumbs-bar a:hover { color: var(--ink-950); }
  .crumbs-bar b { color: var(--ink-950); font-weight: 600; white-space: nowrap; }
  .crumbs-bar i { font-size: 11px; color: var(--ink-300); }
  /* Views still render their own breadcrumb for mobile; the header owns it here. */
  .content-area .crumbs { display: none; }

  .hdr-search {
    display: flex; align-items: center; gap: 9px;
    margin-left: auto; width: 260px; height: 34px; padding: 0 11px;
    border: 1px solid var(--ink-200); border-radius: var(--radius-md);
    background: var(--bg); color: var(--ink-400); font-size: 13px;
    font-family: inherit; cursor: pointer; text-align: left;
    transition: border-color .15s ease, background .15s ease;
  }
  .hdr-search:hover { border-color: var(--ink-300); background: var(--surface); }
  .hdr-search span { flex: 1; min-width: 0; }
  .hdr-search kbd {
    flex: none; font-family: inherit; font-size: 10.5px; font-weight: 600; color: var(--ink-400);
    background: var(--surface); border: 1px solid var(--ink-200); border-radius: 5px; padding: 2px 6px;
}

  /* Collapsed rail. Width is the only thing that animates, so text just goes. */
  .sidebar { transition: width .18s ease; }
  body.side-collapsed .sidebar { width: 68px; padding: 18px 10px; overflow: visible; }
  body.side-collapsed .sidebar .sb-txt,
  body.side-collapsed .sidebar-label,
  body.side-collapsed .sidebar-count,
  body.side-collapsed .sidebar-footer,
  body.side-collapsed .sidebar-user-txt,
  body.side-collapsed .sidebar-user i { display: none; }
  body.side-collapsed .sidebar-btn { justify-content: center; padding: 10px 0; }
  body.side-collapsed .sidebar-btn i { font-size: 18px; }
  body.side-collapsed .sidebar-divider { margin: 8px 4px; }
  body.side-collapsed .sidebar-user { justify-content: center; padding: 9px 0; }
  /* Label on hover, so an icon rail is still navigable. */
  body.side-collapsed .sidebar-btn { position: relative; }
  body.side-collapsed .sidebar-btn::after {
    content: attr(data-label);
    position: absolute; left: calc(100% + 10px); top: 50%; transform: translateY(-50%);
    background: var(--ink-950); color: var(--surface);
    font-size: 12px; font-weight: 600; white-space: nowrap;
    padding: 5px 9px; border-radius: 7px; opacity: 0; pointer-events: none;
    transition: opacity .12s ease; z-index: 60;
  }
  body.side-collapsed .sidebar-btn:hover::after { opacity: 1; }
}

/* Command palette. Real navigation over the links already in the sidebar,
   not a search box wired to nothing. */
.cmdk-scrim {
  position: fixed; inset: 0; z-index: 90; background: rgba(18,16,14,.42);
  backdrop-filter: blur(2px); display: flex; align-items: flex-start; justify-content: center;
  padding-top: 12vh; opacity: 0; transition: opacity .14s ease;
}
.cmdk-scrim.open { opacity: 1; }
.cmdk {
  width: min(560px, 92vw); background: var(--surface);
  border: 1px solid var(--ink-200); border-radius: var(--radius-xl);
  box-shadow: 0 30px 70px -20px rgba(18,16,14,.5); overflow: hidden;
  transform: translateY(-6px); transition: transform .16s ease;
}
.cmdk-scrim.open .cmdk { transform: none; }
/* The global .vc-app input:not(...)x6 rule computes to (0,7,1) and forces a
   border and 14px radius, which drew a floating box inside the panel. Beating
   it on specificity alone would need noise, so this is a deliberate override. */
.vc-app .cmdk-in {
  border: none !important;
  border-bottom: 1px solid var(--ink-100) !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
.cmdk-in {
  width: 100%; border: none; border-bottom: 1px solid var(--ink-100);
  padding: 16px 18px; font-size: 15px; font-family: inherit; color: var(--ink-950);
  background: transparent; outline: none;
}
.cmdk-list { max-height: 340px; overflow-y: auto; padding: 6px; }
.cmdk-item {
  display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 10px 12px; border: none; background: none; border-radius: var(--radius-md);
  font-size: 13.5px; font-family: inherit; color: var(--ink-700); text-align: left; cursor: pointer; line-height: 20px;
}
.cmdk-item i { font-size: 17px; color: var(--ink-400); flex: none; }
.cmdk-item .grp { margin-left: auto; font-size: 11px; color: var(--ink-400); flex: none; }
.cmdk-item.on { background: var(--brand-50); color: var(--brand-700); }
.cmdk-item.on i { color: var(--brand-600); }
.cmdk-empty { padding: 22px 18px; font-size: 13.5px; color: var(--ink-500); text-align: center; line-height: 20px;}

/* ─ Desktop shell: brand in the rail, bordered utilities ────────────────────
   Matching the reference block precisely: the logo sits at the head of the
   sidebar with the collapse control beside it, the header begins with the
   breadcrumb, and the header utilities are bordered squares. */
.side-brand { display: none; }

@media (min-width: 1024px) {
  .side-brand {
    display: flex; align-items: center; gap: 8px;
    padding: 2px 4px 14px; margin-bottom: 2px;
  }
  .side-brand .logo { display: flex; align-items: center; gap: 9px; min-width: 0; text-decoration: none; }
  .side-brand .logo-text { font-size: 16px; font-weight: 800; letter-spacing: -.025em; white-space: nowrap; }
  .side-toggle { margin-left: auto; margin-right: 0; }

  /* The header no longer carries the mark, so it opens on the breadcrumb. */
  .topbar > .logo { display: none; }
  .crumbs-bar { margin-left: 0; }
  .topbar { padding-left: 26px; }

  /* Collapsed rail keeps the mark and centres the control beneath it. */
  body.side-collapsed .side-brand { flex-direction: column; gap: 10px; padding: 2px 0 14px; }
  body.side-collapsed .side-brand .logo-text { display: none; }
  body.side-collapsed .side-toggle { margin: 0; }

  /* Bare icons, matching the bell on mobile. The header already has its own
     surface and bottom rule, so boxing these just fenced them off from it. */
  .hdr-tools { margin-left: 18px; gap: 2px; }
  /* The primary action is not one of the icons, so it gets its own separation. */
  .hdr-tools .header-cta { margin-left: 12px; }
  .hdr-tools .header-btn {
    width: 36px; height: 36px; border-radius: var(--radius-md);
    border: none; background: transparent;
    color: var(--ink-500); font-size: 18px;
    transition: background .15s ease, color .15s ease;
  }
  .hdr-tools .header-btn:hover { background: var(--ink-100); color: var(--ink-950); }
  .hdr-search { height: 36px; }
  .header-cta { height: 36px; }
}
@media (min-width: 1024px) {
  /* In the reference the rail owns the full height and the header begins where
     the rail ends, rather than a full-width bar sitting above both. */
  .sidebar { top: 0; height: 100vh; }
  .app-shell { margin-top: 0; min-height: 100vh; }
  .topbar { left: 240px; transition: left .18s ease; }
  body.side-collapsed .topbar { left: 68px; }
  .content-area { padding-top: calc(60px + 32px); }
  /* The rail scrolls under nothing, so it needs its own clearance. */
  .sidebar { padding-top: 16px; }
}

/* ─ Requirements: verdict first ─────────────────────────────────────────────
   The page answers one question, so it opens with the answer in a sentence,
   then the four figures as one strip, then the documents as a task. Replaces
   a glance grid that stranded a cell, a ring that ate a third of the rail, and
   a second copy of the same call to action. */
.req-verdict {
  display: flex; align-items: center; gap: 18px;
  padding: 20px 22px; margin-bottom: 16px;
  background: var(--surface); border: 1px solid var(--edge-card);
  border-left: 4px solid var(--ink-300); border-radius: var(--radius-card);
  box-shadow: var(--elev-card);
}
.req-verdict.md { border-left-color: var(--amber-600); }
.req-verdict.ok { border-left-color: var(--brand-600); }
.req-verdict .rv-ic {
  flex: none; width: 46px; height: 46px; border-radius: var(--radius-card);
  display: grid; place-items: center; font-size: 24px;
  background: var(--ink-50); color: var(--ink-500);
}
.req-verdict.md .rv-ic { background: var(--amber-50); color: var(--amber-600); }
.req-verdict.ok .rv-ic { background: var(--brand-50); color: var(--brand-600); }
.req-verdict .rv-tx { flex: 1; min-width: 0; }
.req-verdict .rv-tx b {
  display: block; font-size: 19px; font-weight: 650;
  letter-spacing: -0.022em; color: var(--ink-950); margin-bottom: 3px;
}
.req-verdict .rv-tx span { font-size: 13.5px; color: var(--ink-600); line-height: 20px; }
.req-verdict .rv-cta { flex: none; padding: 13px 20px; font-size: 14px; }

.req-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  background: var(--surface); border: 1px solid var(--edge-card);
  border-radius: var(--radius-card); box-shadow: var(--elev-card);
  overflow: hidden; margin-bottom: 20px;
}
.req-stats .rs { padding: 15px 18px; border-right: 1px solid var(--ink-100); }
.req-stats .rs:last-child { border-right: none; }
.req-stats .k { font-size: 11.5px; color: var(--ink-500); margin-bottom: 6px; line-height: 17px;}
.req-stats .v {
  font-size: 22px; font-weight: 700; letter-spacing: -0.03em; line-height: 24px;
  color: var(--ink-950); font-variant-numeric: tabular-nums;
}
.req-stats .v .of { font-size: 13px; font-weight: 500; color: var(--ink-400); }
.req-stats .n { font-size: 11.5px; color: var(--ink-400); margin-top: 5px; line-height: 17px;}

/* Documents as cards. The data carries no per-document requirement text, so
   these show the name and a type icon and claim nothing further. */
.doc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.doc-card {
  display: flex; align-items: center; gap: 11px;
  padding: 13px 14px; border-radius: var(--radius-lg);
  background: var(--ink-50); border: 1px solid var(--ink-100);
}
.doc-card i { color: var(--brand-600); font-size: 18px; flex: none; }
.doc-card span { font-size: 13.5px; font-weight: 550; color: var(--ink-900); line-height: 18px; }

.req-note {
  border-left: 3px solid var(--ink-300); padding-left: 13px;
  font-size: 13px; color: var(--ink-600); line-height: 20px;
}
.req-note.md { border-left-color: var(--amber-600); }

.req-source {
  display: flex; align-items: center; gap: 11px; text-decoration: none;
  padding: 13px 14px; border-radius: var(--radius-lg);
  background: var(--surface); border: 1px solid var(--edge-card); box-shadow: var(--elev-1);
}
.req-source:hover { border-color: var(--ink-300); }
.rsrc-ic {
  flex: none; width: 34px; height: 34px; border-radius: var(--radius-md);
  background: var(--ink-100); color: var(--ink-600); display: grid; place-items: center; font-size: 16px;
}
.rsrc-tx { flex: 1; min-width: 0; }
.rsrc-tx b { display: block; font-size: 13px; font-weight: 600; color: var(--ink-950);  line-height: 20px;}
.rsrc-tx span {
  display: block; font-size: 11.5px; color: var(--ink-500);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
 line-height: 17px;}
.req-source > i { flex: none; color: var(--brand-600); font-size: 15px; }

@media (max-width: 700px) {
  .req-verdict { flex-wrap: wrap; gap: 14px; padding: 18px; }
  .req-verdict .rv-tx { flex: 1 1 100%; order: 2; }
  .req-verdict .rv-cta { flex: 1 1 100%; order: 3; justify-content: center; }
  .req-verdict .rv-tx b { font-size: 17.5px; line-height: 26px;}
  .req-stats .rs { border-right: none; border-bottom: 1px solid var(--ink-100); }
  .req-stats .rs:last-child { border-bottom: none; }
}

/* ─ Home: odds hero ─────────────────────────────────────────────────────────
   One number, as large as it deserves to be. Which number is decided in the
   controller: an approval rate once a route has 30+ decisive outcomes, real
   lookup demand until then. The layout is identical either way, so the page
   upgrades itself the moment the data earns it. */
.odds-hero { display: flex; flex-direction: column; }
/* The 6px here was measured as 4px of actual daylight, because the score below
   runs a 70px line box under a 104px font and its digits sit high in that box.
   Space between the two has to be judged against the number, not the label. */
.oh-pre { font-size: 12.5px; color: var(--ink-500); margin: 0 0 10px; line-height: 19px; }
.oh-big {
  font-size: 76px; font-weight: 800; letter-spacing: -0.055em; line-height: 70px;
  margin: 0; color: var(--ink-950); font-variant-numeric: tabular-nums;
}
.oh-big span { font-size: 30px; font-weight: 700; color: var(--ink-400); letter-spacing: -0.03em; }
.oh-cap {
  font-size: 16px; line-height: 22px; color: var(--ink-700);
  margin: 10px 0 0; font-weight: 500; max-width: 28ch;
}
/* The gradient lives on the full track and the fill is cut out of it, rather
   than the gradient being squeezed into the fill. Squeezing it meant every
   value ended on green: a 42/100 profile got a bright green bar tip telling
   them they were in the clear. Anchored to the track, the colour at the fill
   point is the colour of that actual position. */
.oh-meter {
  margin: 20px 0 8px; height: 10px; border-radius: 6px;
  background: linear-gradient(90deg, var(--rose-500-mark), var(--amber-500-mark) 55%, var(--brand-600));
  position: relative; overflow: hidden;
}
.oh-meter i {
  position: absolute; left: 0; top: 0; bottom: 0; border-radius: 6px;
  /* Masks off everything past the value, leaving the empty track behind it. */
  background: none;
}
.oh-meter i::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 100%;
  width: 100vw; background: var(--ink-200);
}
.oh-mlbl { display: flex; justify-content: space-between; font-size: 11px; color: var(--ink-400);  line-height: 16px;}

/* ─ Desktop dropdown ────────────────────────────────────────────────────────
   Replaces the native <select> menu, which rendered in the OS font at OS
   metrics and looked nothing like the rest of the page. The <select> is still
   the submitted value; this is the visible layer on top of it. */
.dd-wrap { position: relative; }
.dd-native { position: absolute; opacity: 0; pointer-events: none; width: 1px; height: 1px; }
/* .dd-wrap IS the .action-select-wrap, so this has to be a compound selector.
   Written as a descendant it never matched, and the stylesheet's CSS chevron
   kept drawing underneath the button's own SVG caret: two carets per field. */
.dd-wrap::after,
.dd-wrap > i { display: none !important; }
/* Default is a real form field, matching what .vc-app select used to render,
   because most selects in the app stand on their own in a form. */
.dd-btn {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: var(--surface); border: 1px solid var(--ink-300);
  border-radius: var(--radius-btn); padding: 11px 14px; min-height: 44px;
  cursor: pointer; font-family: inherit; font-size: 14px; font-weight: 500;
  color: var(--ink-950); text-align: left; min-width: 0;
}
.dd-btn:hover { border-color: var(--ink-400); }
.dd-open .dd-btn { border-color: var(--brand-600); }
/* Inside the home bar the segment already is the field, so the button strips
   back to bare text and lets the bar provide the chrome. */
.oh-bar .dd-btn {
  background: none; border: 0; border-radius: 0; padding: 0; min-height: 0;
  font-size: 14.5px; font-weight: 600;
 line-height: 22px;}
.oh-bar .dd-btn:hover { border-color: transparent; }
.dd-flag { font-size: 16px; flex: none; line-height: 1; }
.dd-btn .dd-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dd-caret { margin-left: auto; flex: none; color: var(--ink-400); transition: transform .18s ease; }
.dd-open .dd-caret { transform: rotate(180deg); }
.dd-btn:focus-visible { outline: 2px solid var(--brand-600); outline-offset: 4px; border-radius: 3px; }

.dd-menu {
  position: absolute; z-index: 60; top: calc(100% + 8px); left: 0; min-width: 100%;
  max-width: 320px; background: var(--surface);
  border: 1px solid var(--ink-200); border-radius: var(--radius-card);
  box-shadow: 0 4px 8px -4px rgba(18,16,14,.10), 0 18px 40px -14px rgba(18,16,14,.26);
  overflow: hidden; animation: ddIn .16s cubic-bezier(.22,1,.36,1);
}
.dd-menu.up { top: auto; bottom: calc(100% + 12px); }
@keyframes ddIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .dd-menu { animation: none; } .dd-caret { transition: none; } }

.dd-search { padding: 9px; border-bottom: 1px solid var(--ink-100); }
/* The global .vc-app input rule computes to (0,7,1), so a plain class cannot
   reach this field. Scoped !important is the honest fix, not a specificity war. */
.dd-search input {
  width: 100% !important; border: 1px solid var(--ink-200) !important;
  border-radius: var(--radius-btn) !important; background: var(--ink-50) !important;
  padding: 8px 11px !important; font-size: 13.5px !important; font-family: inherit !important;
  color: var(--ink-950) !important; box-shadow: none !important; min-height: 0 !important; line-height: 20px;
}
.dd-search input:focus { border-color: var(--brand-600) !important; background: var(--surface) !important; }

.dd-list { max-height: 288px; overflow-y: auto; padding: 6px; overscroll-behavior: contain; }
.dd-opt {
  display: flex; align-items: center; gap: 9px; width: 100%;
  background: none; border: 0; cursor: pointer; font-family: inherit;
  padding: 9px 10px; border-radius: var(--radius-btn);
  font-size: 14px; font-weight: 500; color: var(--ink-700); text-align: left;
}
.dd-opt .dd-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dd-opt:hover { background: var(--ink-50); color: var(--ink-950); }
.dd-opt:focus-visible { outline: none; background: var(--ink-50); box-shadow: inset 0 0 0 1.5px var(--brand-600); }
.dd-opt .dd-tick { flex: none; color: var(--brand-600); opacity: 0; }
.dd-opt.sel { color: var(--ink-950); font-weight: 600; }
.dd-opt.sel .dd-tick { opacity: 1; }
.dd-empty { margin: 0; padding: 14px 10px; font-size: 13px; color: var(--ink-400); text-align: center; }

/* Right-rail module: a titled stack, so the rail reads as a section of the
   page rather than one card left floating at the top of an empty column. */
.rail-mod { display: flex; flex-direction: column; gap: 10px; }
.rail-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.rail-head h3 {
  margin: 0; font-size: 11px; font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--ink-500);
  display: flex; align-items: center; gap: 7px;
 line-height: 16px;}
.rail-head a { font-size: 12px; color: var(--ink-500); text-decoration: none; }
.rail-head a:hover { color: var(--brand-600); }
.oh-cells { display: flex; gap: 10px; margin-top: 20px; }
.oh-cell {
  flex: 1; background: var(--surface); border: 1px solid var(--edge-card);
  border-radius: var(--radius-lg); padding: 13px 14px; box-shadow: var(--elev-1);
}
.oh-cell span { display: block; font-size: 11px; color: var(--ink-500); margin-bottom: 5px;  line-height: 16px;}
.oh-cell b { font-size: 19px; font-weight: 700; letter-spacing: -0.025em; font-variant-numeric: tabular-nums;  line-height: 28px;}
.oh-cell b small { font-size: 12px; font-weight: 500; color: var(--ink-400); }
/* Words, not a figure: a route name or a flag label needs the smaller size
   to survive its own length. */
.oh-cell b.oh-cell-txt { font-size: 16px; }
/* The phone card's own shapes. They are the same two figures as .oh-cells and
   the same bar as .oh-mlbl's ends, said shorter, so only one of each pair is
   ever displayed. */
.oh-chip, .oh-line, .oh-ml-note, .oh-ml-band, .oh-facts, .oh-flags { display: none; }
/* Phone-only pieces of direction D. The wide layout says the same things
   its own way: .oh-pre names the route, and the caption there needs no
   subject because the number is directly above it. */
.oh-greet, .oh-route-line, .oh-cap-lead { display: none; }
.oh-route {
  margin-top: 20px; background: var(--surface); border: 1px solid var(--ink-200);
  border-radius: var(--radius-btn); padding: 12px 14px; text-decoration: none;
  display: flex; align-items: center; gap: 9px; box-shadow: var(--elev-1);
}
.oh-route .fl { font-size: 19px; flex: none; }
.oh-route b { font-size: 14.5px; font-weight: 600; color: var(--ink-950); white-space: nowrap; line-height: 22px;}
.oh-route i { color: var(--ink-300); font-size: 14px; flex: none; }
.oh-route .sw { margin-left: auto; font-size: 12.5px; font-weight: 650; color: var(--brand-700); flex: none; line-height: 19px;}
.oh-cta { margin-top: 12px; width: 100%; }
.oh-foot { font-size: 11.5px; color: var(--ink-400); margin: 12px 0 0; line-height: 17px; }

@media (min-width: 1024px) {
  /* Leading tracks the size: 70px under a 104px font is a 34px negative
     leading, which pulled the digits up into the label above. */
  .oh-big { font-size: 104px; line-height: 96px; }
  .oh-big span { font-size: 40px; }
  .oh-cap { font-size: 19px; max-width: 24ch; }
  /* The number and its meter share one measure so the bar reads as belonging
     to the figure above it. Wider than the old 460 now that the hero column
     is no longer fighting a 960px page cap. */
  /* One measure for the number's supporting furniture and the bar beneath it.
     Widened from 560 so the picker can hold its longest values: the segments
     need 190 + 215 + 202 for South Africa, United Kingdom and Visiting family,
     plus 103 for the submit. */
  .oh-meter, .oh-mlbl, .oh-cells, .oh-foot { max-width: 720px; }
  .oh-route, .oh-cta { max-width: 720px; }
}
@media (max-width: 400px) {
  .oh-route b { font-size: 13px; }
}

/* ─ The hero figures on a phone ─────────────────────────────────────────────
   Stacked, this block ran 305px before the check form: a 72px number, a
   two-line caption, the meter, the meter's labels, then two cards each giving
   a whole row to one figure. On a 667px screen that is the entire fold spent
   restating one number, with the primary action below it.

   One card instead. The number sits on the baseline of its own meter, the
   first figure becomes a chip in the header and the second a sentence at the
   foot. Both figures also exist as the desktop cards; whichever shape the
   viewport is not showing is display:none, so only one is ever in the page.

   The bar is the same bar as the wide layout, just shorter: the gradient on
   the track, the fill masking off everything past the value. Only its height
   and margins change here. */
/* ─ The score card ─────────────────────────────────────────────────────────
   These were a phone block. The card is the design at every width now, so
   the presentation lives here and only what is genuinely phone-shaped is
   scoped below: the sentence that replaces the picker, the action row that
   submits it, and hiding the picker's own row. */
  /* Direction B's score card, lifted whole: glass at 22px with a layered
     shadow, rather than the flat 12px card the rest of the app uses. The
     surfaces are tokens so dark mode answers them; a literal white here would
     be a white card on a black page. */
  .oh-stat {
    background: var(--surface); border: 1px solid var(--ink-200);
    border-radius: 6px; box-shadow: var(--elev-1);
    padding: 20px;
    /* So the action row, which is pulled out to the card's edges, clips to the
       card's corners rather than squaring them off. */
    overflow: hidden;
  }
  /* A carries no count pill, so the phone loses it. The figure itself is not
     lost: on a wide screen the same count is one of the two .oh-cells, which
     is where that number has always lived outside the phone layout. */
  .oh-chip { display: none; }

  .oh-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
  /* The route as its two flags, in the slot the count chip used to hold. The
     words are already on the card in .oh-route-line, so this is a second way
     of saying the same thing and is hidden from assistive tech rather than
     read out twice. */
  .oh-flags { display: inline-flex; align-items: center; gap: 6px; flex: none; line-height: 1; }
  .oh-flags .fl { font-size: 17px; }
  .oh-flags .lu { font-size: 12px; color: var(--ink-300); }
  /* min-width:0 so a long route name shrinks rather than shoving the chip off
     the card. */
  .oh-pre { font-size: 12px; line-height: 18px; margin: 0 0 11px; min-width: 0; }
  .oh-pre-tail { display: none; }

  /* Bottom-aligned, so the number sits on the same line as the bar it belongs
     to instead of floating beside it. */
  /* Direction D reorders the card: the score leads and the prose explains it.
     The pieces are nested three deep in the markup, so display:contents
     dissolves the gauge and lets one column order all of them. */
  .oh-stat { display: flex; flex-direction: column; }
  /* Direction D: the score leads and the prose explains it. Both wrappers
     dissolve so one column orders pieces that are nested three deep, and the
     number keeps a row of its own with the verdict pushed to its far end. */
  .oh-head       { order: 1; }
  .oh-figure, .oh-gauge { display: contents; }
  /* The row measures 55px for 40px of digits: the /100 span rides the same
     44px line box and stretches it, and the digits sit near the top of what
     is left. Measured, that put 24px of daylight under the number before the
     bar. The negative margin takes back the slack the box is carrying rather
     than the space the design wants. */
  .oh-score-row  { order: 2; display: flex; align-items: baseline; gap: 10px; margin-bottom: -4px; }
  .oh-meter      { order: 3; }
  .oh-cap        { order: 4; }
  .oh-route-line { order: 5; }
  .oh-line       { order: 6; }
  .oh-open       { order: 7; }
  .oh-cells      { order: 8; }
  .oh-mlbl       { display: none; }

  /* The greeting takes the eyebrow slot; the route moves into the sentence. */
  .oh-greet {
    display: block; margin: 0 0 12px;
    font-size: 10px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
    color: var(--ink-400);
  }
  .oh-pre { display: none; }

  /* D shows the denominator, so the bar's own note is redundant and the ends
     of the scale never appear. The verdict moves up beside the number. */
  .oh-big span {
    display: inline; font-size: 22px; font-weight: 700; letter-spacing: -0.02em;
    margin-left: 2px; color: var(--ink-300); -webkit-text-fill-color: var(--ink-300);
  }
  .oh-mlbl { display: none; }
  /* D gives the verdict the far end of the number's row. It is the one word
     on this card that is a judgement rather than a label. */
  .oh-ml-band {
    display: block; margin-left: auto;
    font-size: 12px; font-weight: 650; color: var(--amber-600);
    text-transform: capitalize;
  }

  .oh-route-line {
    display: block; margin: 7px 0 0; font-size: 15px; line-height: 23px;
    color: var(--ink-700); font-weight: 450;
  }
  /* Each token is a control, so it is a button. Everything a button brings
     with it is stripped back to the running text it sits in, and the only
     thing it keeps is the underline that says it can be changed. */
  .oh-tok {
    display: inline; vertical-align: baseline; line-height: inherit;
    font: inherit; color: var(--ink-950); font-weight: 700;
    background: none; border: 0; margin: 0 -3px; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    /* These are spans with a button role rather than real buttons, because a
       browser blockifies a <button> however you style it: as inline-block its
       vertical padding opens the line box, and the app's own min-height:44px
       for tap targets made every token a 44px tall word. A genuinely inline
       element takes the thumb target as vertical padding, which grows the hit
       area and leaves the line box alone, so the sentence reads as running
       text and each word is still comfortably tappable. */
    padding: 14px 3px;
    /* A real underline rather than a bottom border. A border on an inline
       button sits at the edge of its box and pushes the line box open, which
       was forcing 20px between the sentence's two lines. text-underline-offset
       puts the rule exactly where it should be and costs the line nothing. */
    text-decoration: underline;
    text-decoration-color: var(--brand-100);
    text-decoration-thickness: 2px;
    text-underline-offset: 5px;
    text-decoration-skip-ink: none;
  }
  .oh-tok:hover, .oh-tok:active { text-decoration-color: var(--brand-600); }
  .oh-tok:focus-visible { outline: 2px solid var(--ink-950); outline-offset: 3px; border-radius: 2px; }
  /* The number sits directly beside the caption in A, so the caption needs
     no subject of its own: it reads "58/100 is the typical...". */
  .oh-cap {
    display: block; margin: 0; font-size: 13px; line-height: 19px;
    color: var(--ink-500); font-weight: 400; max-width: none;
  }
  /* A draws the number solid. That retires the background-clip wash and
     everything it needed: the @supports guard against an invisible score, and
     the four-sided padding that stopped the glyphs being cut off by their own
     paint box. A solid fill has neither failure mode. */
  .oh-big {
    font-size: 54px; line-height: 44px; letter-spacing: -0.05em;
    color: var(--brand-700); -webkit-text-fill-color: currentColor;
    background: none; padding: 0; margin: 0;
  }
  .oh-gauge { flex: 1; min-width: 0; padding-bottom: 4px; }

  /* B's bar: 8px and fully rounded, carrying B's own ramp colours, which are
     a brighter rose and amber than the app's tokens.

     The ramp stays ON THE TRACK with the fill masking off everything past the
     value, which is where this app already puts it. B's drawing had the ramp
     inside the fill; done that way every score ends on green, so a 42 gets a
     green tip telling them they are in the clear. Same colours, honest bar. */
  /* The ramp rides the fill here, so the whole red to amber to green run is
     visible across the filled portion, which is what B drew and what was
     asked for twice.

     Worth knowing what that costs: a fill carrying the ramp ends on green at
     every value, so a 42 gets the same green tip as a 92. The wide layout
     still puts the ramp on the track, where the green only appears once the
     score has earned it. */
  .oh-meter {
    margin: 4px 0 14px; height: 4px; border-radius: 0;
    background: var(--ink-200);
  }
  .oh-meter i {
    border-radius: 99px;
    background: linear-gradient(90deg, var(--rose-500-mark), var(--amber-500-mark) 58%, var(--brand-600));
  }
  .oh-meter i::after { display: none; }
  .oh-mlbl { font-size: 10.5px; line-height: 15px; margin-top: 5px; }
  .oh-ml-end { display: none; }
  .oh-ml-note { display: inline; }
  /* B carries the verdict in amber rather than grey: it is the one word on
     this card that is a judgement rather than a label, so it is the one word
     that gets a colour. */

  /* The flag reads mid-sentence, so the API's sentence-case label is
     lowercased here rather than at the source, where it heads its own column
     on the wide layout and on every other screen. */
  .oh-line .oh-flag-label { text-transform: lowercase; }

  /* B's foot: a rule across the card, then the flag with an icon in front. */
  .oh-line {
    display: flex; align-items: center; gap: 9px;
    font-size: 12.5px; line-height: 18px; color: var(--ink-600);
    margin: 15px 0 0; padding-top: 13px; border-top: 1px solid var(--ink-200);
  }
  .oh-line .lu { font-size: 15px; color: var(--ink-400); }

  /* The card's own last row: tinted rather than filled, edge to edge, and
     clipped to the card's corners by .oh-stat's overflow. The negative margin
     is what takes it out to the card's edges without the card needing an inner
     wrapper. Tinted because this runs the check on what the sentence above
     already says, and a filled bar for that is louder than the card it closes. */
  .oh-open:not([hidden]) {
    display: flex; align-items: center; justify-content: space-between; gap: 9px;
    width: calc(100% + 40px); margin: 15px -20px -20px;
    padding: 15px 20px; border-radius: 0;
    background: var(--brand-50); color: var(--brand-700);
    border-top: 1px solid var(--brand-100);
    font-size: 14.5px; font-weight: 650; line-height: 20px;
    box-shadow: none; white-space: nowrap;
  }
  .oh-open:active { background: var(--brand-100); }
  .oh-open .lu { font-size: 17px; }
  .oh-line b { color: var(--ink-950); font-weight: 600; }

  /* The two figures are the chip and the sentence here. */
  .oh-cells { display: none; }

/* Wide screens keep their own picker and their own two stat cells, so the
   phone-shaped pieces of that card stand down here. */
/* ─ The wide layout's own version of the card ───────────────────────────────
   Everything above is the phone card and stays that way. These are the wide
   screen's departures from it, and they are only here: a run of changes meant
   for this layout got applied to both once, which is what this separation
   exists to prevent. */
@media (min-width: 1024px) {
  .oh-route-line, .oh-open { display: none; }
  .oh-bar { display: flex; }
  .oh-form { margin-top: 20px; }

  /* No card here: the figure sits straight on the page. */
  .oh-stat { background: none; border: 0; box-shadow: none; padding: 0; overflow: visible; }

  /* A wrapping row: the number on the left, the line explaining it beside, and
     the bar dropping under both on a basis of its own. 720px is the measure the
     bar and its labels already used, so all three end on the same edge. */
  /* order matters here: as a flex child of the card with no order of its own
     the figure sorted to 0 and jumped above the greeting, which put GOOD
     MORNING underneath the bar. */
  .oh-figure {
    order: 2; display: flex; flex-wrap: wrap;
    /* Centring left the caption's last line 11px above the number's baseline,
       which is close enough to look like a mistake rather than a choice. Last
       baseline puts the two on the same line exactly, and keeps doing so if
       the caption ever reflows to one line or three. */
    align-items: last baseline;
    gap: 0 18px; max-width: 720px;
    /* The number sat 15px under the greeting, tight for something this large. */
    margin-top: 10px;
  }
  .oh-score-row {
    order: 1; flex: none; flex-direction: column; align-items: flex-start; gap: 0; margin: 0;
  }
  /* Given the whole row it ran to a single 561px line, which reads as a
     caption under the bar rather than as a line belonging to the number.
     "is the typical eligibility" measures 135.6px and adding "score" takes it
     to 173.6px, so 150 is the basis that breaks after "eligibility" and
     leaves "score on this route." (118.3px) whole on the second line. */
  .oh-cap { order: 2; flex: 0 1 150px; min-width: 0; max-width: none; }

  /* The band is a phone-card idea: there, it sits at the far end of the
     number's row. Here the number carries the verdict on its own. The rule
     that shows it is unwrapped, so hiding it takes an explicit override. */
  .oh-ml-band { display: none; }
  /* Same for the flag pair: the rule that shows it sits with .oh-head's flex
     row, which is unwrapped. The wide layout states the route in the picker
     rather than in the card, so the flags have nothing to sit beside here. */
  .oh-flags { display: none; }
  .oh-meter { order: 4; flex-basis: 100%; margin: 14px 0 0; }
  .oh-mlbl {
    order: 5; flex-basis: 100%;
    display: flex; justify-content: space-between;
    font-size: 11px; line-height: 16px; margin-top: 5px; color: var(--ink-400);
  }
  .oh-ml-end  { display: inline; }
  .oh-ml-note { display: none; }
  /* The verdict tucks under the number it judges rather than floating 200px
     away at the end of a 950px row. */

  .oh-big { font-size: 70px; line-height: 58px; letter-spacing: -0.055em; }
  /* line-height:normal on a 26px span hangs its inline box 16px below the
     number's 58px strut, and that dead space was reading as a gap between the
     score and the bar. line-height:1 collapses it without moving the glyphs,
     which sit on the shared baseline either way. */
  .oh-big span { font-size: 26px; line-height: 1; }

  /* The flag moves under the caption and the two stat cards go with it. */
  .oh-line {
    display: flex; align-items: center; gap: 8px;
    font-size: 12.5px; line-height: 18px; color: var(--ink-600);
    margin: 14px 0 0; padding: 0; border: 0;
  }
  .oh-line .lu { font-size: 15px; color: var(--ink-400); }
  .oh-cells { display: none; }

  /* The figure row runs to 720px but the caption stops at 150, so around
     400px of it sat empty. The two facts the phone card carries in its cells
     take that room, divided by a hairline rather than boxed: this hero has no
     card on a wide screen and should not grow one for these. */
  .oh-facts {
    /* Ordered, not left at 0: as a flex child with no order of its own it
       sorted ahead of the number and rendered to its left. The figure hit the
       same trap. */
    order: 3;
    display: flex; align-items: stretch; gap: 26px;
    margin-left: auto; align-self: center;
  }
  .oh-facts-rule { width: 2px; background: var(--ink-200); }
  .oh-fact { display: flex; flex-direction: column; gap: 3px; }
  .oh-fact-lbl {
    font-size: 10px; font-weight: 700; letter-spacing: 0.14em;
    text-transform: uppercase; color: var(--ink-400);
  }
  .oh-fact-val {
    font-size: 17px; font-weight: 600; line-height: 22px;
    color: var(--ink-900); font-variant-numeric: tabular-nums;
  }
  .oh-fact-sub { font-size: 11px; line-height: 16px; color: var(--ink-400); }
  /* The flag moved into the pair, so the foot line would say it twice. Only
     where a pair exists: the visa-class branch has no pair and its foot line
     is the only thing carrying that fact. */
  .oh-figure:has(.oh-facts) ~ .oh-line { display: none; }
}
}

/* ─ Outcome prompt ──────────────────────────────────────────────────────────
   Outcomes are what calibrate the score and unlock approval rates, and the
   only existing way to record one was a collapsed disclosure at the bottom of
   the report page. This asks on the dashboard instead, where people return,
   and answers in one tap. Shown only for reports with no outcome yet. */
.outcome-ask {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: -6px 0 14px; padding: 11px 14px;
  background: var(--ink-50); border: 1px solid var(--ink-200);
  border-top: none; border-radius: 0 0 var(--radius-card) var(--radius-card);
}
.oa-q { font-size: 13px; color: var(--ink-600); font-weight: 500; }
.oa-form { display: flex; align-items: center; gap: 8px; margin: 0; flex-wrap: wrap; }
.oa-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: var(--radius-btn); cursor: pointer;
  font-size: 12.5px; font-weight: 650; font-family: inherit; text-decoration: none;
  border: 1px solid var(--ink-200); background: var(--surface); color: var(--ink-700);
  transition: border-color .15s ease, background .15s ease, color .15s ease; line-height: 19px;
}
.oa-btn i { font-size: 15px; }
.oa-btn.ok:hover { border-color: var(--brand-500); background: var(--brand-50); color: var(--brand-700); }
.oa-btn.no:hover { border-color: var(--rose-600); background: var(--rose-50); color: var(--rose-700); }
.oa-btn.more { border-color: transparent; background: none; color: var(--ink-500); font-weight: 550; }
.oa-btn.more:hover { color: var(--ink-950); }
@media (max-width: 560px) {
  .outcome-ask { gap: 9px; }
  .oa-q { flex: 1 1 100%; }
}
/* The hero picker is one object: three segments and a submit in a single bar.
   Real selects underneath, so the mobile bottom-sheet enhancer still applies
   and the form still submits natively. Three segments in a pill do not fit a
   phone, so below 900px the bar becomes a stacked card and keeps being one
   object rather than collapsing into separate fields. */
.oh-form { margin-top: 20px; }
.oh-bar {
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--edge-card);
  border-radius: var(--radius-card); box-shadow: var(--elev-card); overflow: hidden;
}
/* Mobile pairs the two endpoints on one row facing each other, with a
   direction marker between them, then puts purpose on its own row. It reads as
   a route the way a flight app does, and it is 41px shorter than the numbered
   stack it replaces, which the hero number gets back. */
.oh-seg {
  position: relative; min-width: 0;
  padding: 11px 16px; border-bottom: 1px solid var(--ink-100);
}
.oh-pair { display: flex; align-items: stretch; position: relative; }
.oh-pair .oh-seg { flex: 1; min-width: 0; }
.oh-to { text-align: right; }
/* The field is a flex container, so text-align does not move its contents:
   justify-content is what actually aligns them. It also needs to outrank
   .oh-bar .as-sheet-field, which is !important at equal specificity and wins on
   source order, hence the extra .oh-bar here. The right padding goes with it,
   since that space was reserved for a caret this row no longer shows. */
.oh-bar .oh-to .as-sheet-field,
.oh-bar .oh-to .action-select {
  justify-content: flex-end; text-align: right; padding-right: 0 !important;
}
/* The marker overlaps the seam between the two endpoints, so both keep their
   full padding and neither has to make room for it. */
.oh-mid {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center; z-index: 1;
  background: var(--ink-50); color: var(--ink-400);
  border: 3px solid var(--surface); pointer-events: none;
}
.oh-mid i { font-size: 14px; }
/* Endpoints show flag and name only. The chevron stays on purpose, where the
   value is a word with no flag to signal that it is a choice. */
.oh-pair .action-select-wrap::after { display: none; }
.oh-seg-k {
  display: block; font-size: 9.5px; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-400); margin-bottom: 2px;
 line-height: 14px;}
/* Strip the field chrome: inside the bar the segment is the field. */
.oh-bar .action-select-wrap { position: relative; }
.oh-bar .action-select,
.oh-bar .as-sheet-field {
  width: 100%; border: none !important; background: none !important;
  box-shadow: none !important; border-radius: 0 !important;
  padding: 0 18px 0 0 !important; font-size: 14.5px; font-weight: 600;
  color: var(--ink-950); min-height: 0;
 line-height: 20px;}
.oh-bar .action-select-wrap::after {
  content: ""; position: absolute; right: 4px; top: 50%; width: 7px; height: 7px;
  border-right: 1.5px solid var(--ink-400); border-bottom: 1.5px solid var(--ink-400);
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
.oh-bar .action-select-wrap > i { display: none; }
.oh-for { display: none; }
.oh-go {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  border: none; cursor: pointer; font-family: inherit;
  background: var(--brand-600); color: #fff; font-size: 15px; font-weight: 700;
  padding: 15px; margin: 0; width: 100%;
}
.oh-go:hover { background: var(--brand-700); }
.oh-go i { font-size: 18px; }
.oh-go-short { display: none; }

@media (min-width: 900px) {
  /* One 12px rectangle, segments divided by hairlines, submit flush to the
     right edge at full height. The old pill was the only fully round surface
     left after the radius pass, and its circular submit floated inside the
     padding as a second competing shape. */
  .oh-form { max-width: 720px; }
  /* Not equal thirds. Destination holds the longest values in the product
     (United Kingdom, Liechtenstein), passport only ever holds three short
     country names, and purpose sits between them. Equal thirds clipped
     United Kingdom by 2px at every width this column can offer.
     Specificity has to match .oh-pair .oh-seg, which sets flex: 1. */
  .oh-pair .oh-from { flex: 0.92; }
  .oh-pair .oh-to   { flex: 1.16; }
  .oh-purpose       { flex: 0.92; }
  .oh-bar {
    flex-direction: row; align-items: stretch;
    border-radius: var(--radius-card); padding: 0;
    position: relative;   /* anchors the dropdown popover to the whole control */
    /* The mobile base rule sets overflow:hidden so the stacked segments and the
       full-width submit clip to the rounded container. On desktop that same
       clip swallows the dropdown popover, which lives inside a segment, so it
       has to be released here. The submit rounds its own corners instead. */
    overflow: visible;
  }
  .oh-seg {
    /* 13px, not 16. At 16 the flag, name and caret together ran 2 to 3px past
       the segment, so every value ellipsised by a hair: South Africa, United
       Kingdom and even Tourism. Three pixels a side buys the headroom without
       pushing the bar past the width of its column. */
    flex: 1; min-width: 0; padding: 10px 13px;
    border-bottom: none; border-left: 1px solid var(--ink-100);
  }
  .oh-seg:first-child { border-left: none; }
  /* display:contents dissolves the wrapper so the two endpoints become direct
     flex children of the bar again. The desktop row is then exactly what it was
     before the mobile pairing existed, with no extra nesting to compensate for. */
  .oh-pair { display: contents; }
  .oh-mid { display: none; }
  .oh-to { text-align: left; }
  /* Restore the native field's own right padding, which the mobile rule zeroes. */
  .oh-bar .oh-to .as-sheet-field,
  .oh-bar .oh-to .action-select {
    justify-content: flex-start; text-align: left; padding-right: 18px !important;
  }
  /* The custom dropdown button carries no padding of its own and its caret is a
     flex child, so giving it the field's 18px only steals width from the name. */
  .oh-bar .oh-to .dd-btn { justify-content: flex-start; text-align: left; }
  .oh-pair .action-select-wrap::after { display: block; }
  .oh-go {
    flex: none; width: auto; align-self: stretch;
    border-radius: 0 var(--radius-card) var(--radius-card) 0;
    padding: 0 22px; gap: 8px; font-size: 14.5px; box-shadow: none;
    /* The app-wide -0.01em is meant for running text. On a short bold label it
       closes the letters up, which is what made this button look cramped. */
    letter-spacing: 0; line-height: 22px;
}
  /* 16px rendered about 9px of actual glyph, which reads as a speck beside a
     14.5px bold word rather than as an icon. */
  .oh-go i { font-size: 18px; }
  .oh-go-txt   { display: none; }
  .oh-go-short { display: inline; }
}

/* ─ Mobile home fixes ───────────────────────────────────────────────────────
   The bottom nav is fixed, so any page whose content runs to the bottom needs
   clearance or the last control sits underneath it. The home CTA did. */
@media (max-width: 1023px) {
  body.screen-home .content-area { padding-bottom: 132px; }

  /* The tagline goes on a phone. The headline above it already says what the
     product does, and the card below says it again with an actual number, so
     on a screen this narrow it was the third telling. The wide layout keeps
     it: there it sits beside the picker rather than on top of the card. */
  .oh-intro-sub { display: none; }

  /* Unboxing the fields inside the bar removed their padding, which took the
     tap target down to 22px. Height is restored on the row, not the control,
     so the segment stays flush and the whole row remains tappable. */
  .oh-bar .action-select,
  .oh-bar .as-sheet-field { min-height: 44px; }
  .oh-seg { padding-top: 8px; padding-bottom: 4px; }
}

/* ─ The check form on a phone: no card ──────────────────────────────────────
   The card was a frame round three fields that were already legible without
   one, and it put a second bordered surface directly under the score card,
   which is also bordered. Without it the route is the only large thing on the
   page ground and the green button is the only painted surface, so there is
   exactly one thing to press.

   The route reads as a route: 19px names either side of a plain arrow, no
   grey circle in between. That circle was pointer-events:none, so it looked
   like a swap control and did nothing. A swap would be dead on 37 of 40
   destinations anyway, since only three passports are supported.

   The eyebrow labels go with the card. Passport and Destination are obvious
   from the flags and the arrow, and Purpose becomes running text: "for
   Tourism". The selects keep their own aria-labels, so nothing is lost to
   assistive tech.

   Scoped to 899px rather than 1023 because the desktop bar starts at 900:
   between the two the form still collapses behind .oh-open, and opens as the
   row it is on a wide screen. */
@media (max-width: 899px) {
  /* The picker's visible row goes: the three words in the card's sentence are
     its controls now, and showing the same three fields twice was the second
     form. The <select>s stay in the page, hidden, because the bottom sheets are
     built from them and the submit handler reads them. */
  .oh-bar { display: none; }
  .oh-face-back { display: contents; }
  /* With its row hidden the form is a zero-height box, but it kept its 20px
     top margin and pushed the disclaimer that far below the card. Measured, the
     gap under the card was 32px for 12px of intended spacing. */
  .oh-form { margin-top: 0; }
  .oh-seg-k { display: none; }

  /* One line: flag, name, arrow, flag, name, and the caret at the far right.
     Destination takes the slack, so the caret lands on the page edge and
     belongs to the field whose hit area reaches it. */
  .oh-pair { display: flex; align-items: center; gap: 10px; position: static; }
  .oh-pair .oh-seg { padding: 0; border-bottom: 0; }
  .oh-pair .oh-from { flex: 0 1 auto; }
  .oh-pair .oh-to { flex: 1 1 auto; text-align: left; }
  /* The padding is on the field, not the row, so each of the two names is its
     own 44px target rather than one 44px row holding two 26px ones. */
  .oh-bar .oh-pair .action-select,
  .oh-bar .oh-pair .as-sheet-field {
    min-height: 44px; font-size: 19px; font-weight: 700;
    letter-spacing: -0.025em; line-height: 26px;
  }
  .oh-bar .oh-to .action-select,
  .oh-bar .oh-to .as-sheet-field {
    justify-content: flex-start; text-align: left; padding-right: 18px !important;
  }
  .oh-bar .oh-to .dd-btn { justify-content: flex-start; text-align: left; }
  .oh-pair .oh-to .action-select-wrap::after { display: block; }
  /* Passport draws no caret, so the 18px the base reserves for one is dead
     space between the name and the arrow. With it the arrow sat 28px from
     Nigeria and 10px from Austria: mechanically between the two boxes, but
     visually glued to the wrong end, because only one of the boxes ends where
     its word does. Zeroed, both gaps are the row's 10px. */
  .oh-bar .oh-from .action-select,
  .oh-bar .oh-from .as-sheet-field { padding-right: 0 !important; }

  /* Just the glyph. No circle, no ring, nothing that reads as pressable. */
  .oh-mid {
    position: static; transform: none; width: auto; height: auto;
    border: 0; background: none; border-radius: 0;
    color: var(--ink-300); display: flex; place-items: initial;
  }
  .oh-mid i { font-size: 16px; }

  /* A hairline is enough to separate the two rows now that neither is boxed. */
  .oh-purpose {
    display: flex; align-items: center; gap: 7px;
    padding: 0; border-bottom: 0; border-top: 1px solid var(--ink-200);
  }
  .oh-for {
    display: inline; flex: none;
    font-size: 13.5px; line-height: 19px; color: var(--ink-500);
  }
  .oh-purpose .action-select-wrap { flex: 0 1 auto; }
  .oh-bar .oh-purpose .action-select,
  .oh-bar .oh-purpose .as-sheet-field {
    min-height: 45px; font-size: 13.5px; font-weight: 650;
    color: var(--ink-700); line-height: 19px; padding-right: 16px !important;
  }

  /* The only painted surface on the block, so it carries its own corners and
     the same lift .oh-open has. They occupy the same slot one after the other,
     and a button that loses its shadow on tap reads as a glitch. */
  .oh-go {
    margin-top: 14px; border-radius: var(--radius-btn);
    box-shadow: 0 1px 2px rgba(18,16,14,.05), 0 10px 22px -14px rgba(0,150,109,.6);
  }
  .oh-go:active { transform: translateY(1px); }
}

/* ─ Bottom nav scrim ────────────────────────────────────────────────────────
   The nav floats with a 14px inset, so content scrolling past it used to run
   straight into its edge. A soft fade to the page ground sits behind it, at
   z-index 39 against the nav's 40, so the nav stays crisp while whatever is
   under it dissolves.

   Fades to var(--bg) rather than a literal white: on the warm paper ground a
   pure white would leave a visible seam, and in dark mode it would be a bright
   band across the bottom of the screen. The token gets both right for free. */
@media (max-width: 1023px) {
  body.vc-app::after {
    content: "";
    position: fixed; left: 0; right: 0; bottom: 0;
    height: calc(env(safe-area-inset-bottom) + 104px);
    background: linear-gradient(to top,
      var(--bg) 0%,
      color-mix(in srgb, var(--bg) 82%, transparent) 46%,
      color-mix(in srgb, var(--bg) 0%, transparent) 100%);
    pointer-events: none;
    z-index: 39;
  }
  /* The flow hides the nav, so the scrim goes with it. */
  body.screen-eligibility::after { display: none; }
  /* It follows the nav out of view on scroll-down rather than lingering. */
  body.vc-app:has(.bottomnav.nav-hidden)::after { opacity: 0; transition: opacity .32s ease; }
}

/* ─ Bottom nav: Home ────────────────────────────────────────────────────────
   Home took the middle slot the FAB used to hold, so it keeps a shape of its
   own rather than behaving like the four tabs around it: always a circle,
   never a label, so the centre of the bar stays a fixed anchor instead of
   widening into a pill when you happen to be on it. */
.bottomnav .nav-home {
  flex: none; width: 44px; height: 44px; padding: 0;
  border-radius: 50%; justify-content: center;
  background: var(--ink-100); color: var(--ink-600);
  transition: background .2s ease, color .2s ease;
}
.bottomnav .nav-home i { font-size: 21px; }
.bottomnav .nav-home span { display: none !important; }
.bottomnav .nav-home.active {
  background: var(--brand-600); color: #fff;
  box-shadow: 0 4px 12px -3px rgba(0,150,109,.5);
}
.bottomnav .nav-home:active { transform: scale(.94); }

/* ─ Requirements: tabbed sheet ──────────────────────────────────────────────
   The page used to render thirteen separate cards in a 960px column, which is
   what made it read as scattered. One sheet with tabs shows a single section
   at a time, and counts on the labels keep the hidden sections announced. */
.rq {
  background: var(--surface); border: 1px solid var(--edge-card);
  border-radius: var(--radius-card); box-shadow: var(--elev-card); overflow: hidden;
}
.rq-tabs {
  display: flex; gap: 2px; padding: 6px 8px 0;
  /* --ink-50 sat two points off the page ground, so the strip read as part of
     the background. A deeper band separates it, and the selected tab lifts out
     of it in the sheet's own white, which is what makes it read as a tab. */
  background: var(--ink-100); border-bottom: 1px solid var(--ink-200);
  overflow-x: auto; scrollbar-width: none;
}
.rq-tabs::-webkit-scrollbar { display: none; }
.rq-tab {
  flex: none; padding: 12px 16px; border: none; background: none;
  font-family: inherit; font-size: 13.5px; font-weight: 600; color: var(--ink-500);
  border-radius: 8px 8px 0 0; cursor: pointer; white-space: nowrap;
  margin-bottom: -1px;
  transition: color .15s ease, background .15s ease;
 line-height: 20px;}
.rq-tab:hover { color: var(--ink-950); background: rgba(255,255,255,.5); }
.rq-tab.on {
  color: var(--ink-950); background: var(--surface);
  box-shadow: 0 -1px 0 var(--ink-200) inset, 1px 0 0 var(--ink-200), -1px 0 0 var(--ink-200);
}
.rq-tab .c {
  font-size: 11px; font-weight: 700; color: var(--ink-400);
  margin-left: 5px; font-variant-numeric: tabular-nums;
 line-height: 16px;}
.rq-tab.on .c { color: var(--brand-600); }
.rq-panel { padding: 22px; }
.rq-two { display: grid; grid-template-columns: 1fr; gap: 20px; align-items: start; }
.rq-aside { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.rq-note { background: var(--ink-50); border: 1px solid var(--ink-100); border-radius: var(--radius-lg); padding: 16px 17px; }

@media (min-width: 1024px) {
  /* This page carries the most content in the app, so it opts out of the
     shared 960px cap and uses the width the shell already gives it. */
  #screen-requirements { max-width: none; }
  .rq-two { grid-template-columns: 1fr 340px; gap: 24px; }
  .rq-panel { padding: 24px; }
  /* With the full width available the tool tiles stop being a cramped 3-up. */
  #screen-requirements .tool-grid { grid-template-columns: repeat(4, 1fr); }
  #screen-requirements .doc-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ─ Route title ─────────────────────────────────────────────────────────────
   Origin and destination belong on one line: wrapping splits the route in
   half and reads as two separate places. Sized so the longest real pair in
   the data, South Africa to United Kingdom, fits a 372px column with room to
   spare. The ellipsis is insurance only: if a longer country name is ever
   added it truncates rather than pushing the page sideways. */
.route-title {
  font-size: 26px; font-weight: 600; letter-spacing: -0.03em;
  color: var(--ink-950); line-height: 30px;
  white-space: nowrap; min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
}
/* Steps follow the shell, not arbitrary phone widths. Below 1024px the content
   column is capped at 480px regardless of viewport, so a tablet has no more
   room for this line than a phone does and needs the same size. */
@media (max-width: 1023px) {
  /* Heavier on a phone: at 21px and 600 the flags either side out-weighed
     the country names, so the line read as two flags with text between
     them rather than as the heading it is. */
  .route-title { font-size: 21px; font-weight: 700; letter-spacing: -0.032em; }
}
@media (max-width: 400px) {
  .route-title { font-size: 18.5px; font-weight: 700; letter-spacing: -0.034em; line-height: 28px;}
}
/* 320px devices: measured 2px over at 16.5px, so one notch smaller. */
@media (max-width: 340px) {
  .route-title { font-size: 16px; letter-spacing: -0.035em; }
}


/* ─ Requirements answer card ────────────────────────────────────────────────
   Replaces the four-cell summary strip. That strip read fee and stay from the
   first visa type but processing from a route-wide range, so it published a
   combination matching no real visa. Here every figure sits inside the card of
   the visa it belongs to, which makes the old mistake unrepresentable. */
.ans {
  background: var(--surface); border: 1px solid var(--edge-card);
  border-radius: var(--radius-card); overflow: hidden;
  box-shadow: var(--elev-card); margin-bottom: 12px;
}
.ans-h { padding: 15px 18px; border-bottom: 1px solid var(--ink-100); display: flex; align-items: flex-start; gap: 13px; }
.ans-ic {
  width: 38px; height: 38px; border-radius: var(--radius-md); flex: none;
  background: var(--brand-50); color: var(--brand-700); display: grid; place-items: center; font-size: 19px;
 line-height: 28px;}
.ans-tx { flex: 1; min-width: 0; }
.ans-tx b { display: block; font-size: 16px; letter-spacing: -0.02em; color: var(--ink-950); }
.ans-tx span { font-size: 13px; color: var(--ink-600);  line-height: 20px;}
/* The same pill the changelog uses for a verified entry, values copied rather
   than approximated: 11px at 600, accent-50 on an accent-100 hairline, 3px by
   9px. One pill style across the site means one thing for a reader to learn.
   line-height 1 and the flex centring stay, because the base .pill inherits the
   page line height and would otherwise sit its capitals a pixel high. */
/* Deliberately identical to .pill.green, down to the line-height. This was a
   near-copy of it with line-height:1 instead of 16px, which made it 5px shorter
   than the verified pill on the changelog cards even though every colour
   matched, so the two read as different components. flex:none is the only thing
   it adds: these sit in a flex row and must not squash. */
.ans-pill {
  flex: none;
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.02em;
  padding: 3px 9px; border-radius: var(--radius-sm);
  background: var(--accent-50); color: var(--accent-700);
  border: 1px solid var(--accent-100);
  line-height: 16px;
}
.ans-pill.alt { background: var(--ink-100); color: var(--ink-600); border-color: var(--ink-200); }
.ans-b { display: grid; grid-template-columns: repeat(3, 1fr); }
.ans-b .c { padding: 13px 18px; border-left: 1px solid var(--ink-100); min-width: 0; }
.ans-b .c:first-child { border-left: 0; }
.ans-b .k { font-size: 11px; color: var(--ink-500); margin-bottom: 3px;  line-height: 16px;}
.ans-b .v {
  font-size: 19px; font-weight: 750; letter-spacing: -0.03em;
  color: var(--ink-950); font-variant-numeric: tabular-nums;
 line-height: 28px;}
.ans-b .n { font-size: 11px; color: var(--ink-400); margin-top: 2px;  line-height: 16px;}
.ans-note {
  margin: 0; padding: 11px 18px; font-size: 12.5px; color: var(--ink-500);
  border-top: 1px solid var(--ink-100);
 line-height: 19px;}
.ans-f {
  padding: 12px 18px; border-top: 1px solid var(--ink-100);
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
.ans-meta { font-size: 11.5px; color: var(--ink-400);  line-height: 17px;}

/* Other types stay one line each: present, not competing. */
.others {
  border: 1px solid var(--edge-card); border-radius: var(--radius-card);
  background: var(--surface); overflow: hidden; margin-bottom: 18px;
}
.others > summary {
  padding: 11px 16px; font-size: 12.5px; font-weight: 600; color: var(--ink-700);
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px; line-height: 19px;
}
.others > summary::-webkit-details-marker { display: none; }
.others > summary::marker { content: ""; }
.others > summary:hover { background: var(--ink-50); }
.others > summary i { margin-left: auto; font-size: 12px; color: var(--ink-400); transition: transform 0.18s ease; }
.others[open] > summary i { transform: rotate(90deg); }
.orow {
  display: flex; align-items: center; gap: 12px; padding: 11px 16px;
  border-top: 1px solid var(--ink-100); font-size: 13px;
}
.orow .nm { font-weight: 600; color: var(--ink-950); flex: 1; min-width: 0; }
.orow .d { font-size: 11.5px; color: var(--ink-400); line-height: 17px;}
.orow .f { font-variant-numeric: tabular-nums; font-weight: 650; color: var(--ink-700); }
@media (max-width: 640px) {
  .ans-b { grid-template-columns: 1fr 1fr; }
  .ans-b .c:nth-child(3) { border-left: 0; border-top: 1px solid var(--ink-100); grid-column: 1 / -1; }
  .orow { flex-wrap: wrap; }
  .orow .nm { flex: 1 0 100%; }
}
@media (prefers-reduced-motion: reduce) { .others > summary i { transition: none; } }

/* Without the button the verdict is a statement, not a call to action, so it
   loses the card weight and sits as a line above the visa cards. */
.req-verdict.slim { box-shadow: none; padding: 12px 16px; margin-bottom: 14px; }
.req-verdict.slim .rv-tx b { font-size: 14.5px;  line-height: 22px;}
.req-verdict.slim .rv-tx span { font-size: 12.5px;  line-height: 19px;}

/* Fee note and route context follow the cards they describe, now that the
   Visa types tab is gone and its contents live with the answer. */
.ans-feenote { margin: -4px 0 14px; font-size: 11.5px; color: var(--ink-400); line-height: 17px; }
.ans-extra { display: grid; gap: 12px; margin-bottom: 16px; }
@media (min-width: 900px) { .ans-extra { grid-template-columns: 1fr 300px; align-items: start; } }

/* ─ Home skyline ────────────────────────────────────────────────────────────
   Lagos, in line art, anchored to the foot of the hero where a skyline belongs.
   It is a real city and the origin most of this product's traffic starts from,
   which is why it is here rather than a generic one.
   Served as a file, not a data URI: 164kb of path data inlined would sit in
   every stylesheet request, whereas the file is cached once and gzips to 46kb.
   Sub-12-unit paths were dropped from the source, which halved the geometry
   without losing anything visible at this opacity. */
.odds-hero, .hero { position: relative; }
.odds-hero > *, .hero > * { position: relative; z-index: 1; }
/* The rule above lifts every hero child off the skyline, but it also puts them
   all on the same layer, where DOM order alone decides who paints on top. The
   disclaimer follows the picker, so an open dropdown was rendering underneath
   it. The picker owns the only thing here that overflows its own box, so it
   gets the layer above its siblings. */
.odds-hero > .oh-flipwrap { z-index: 3; }
.odds-hero::before, .hero::before {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  left: 0; right: 0; bottom: -18px; height: 118%;
  /* Deliberately no background-image here; see the .deco-ready rule below. */
  background: no-repeat center bottom;
  background-size: 100% auto;
  opacity: 0.085;
  /* Dissolves at both ends instead of stopping mid-building. Without this the
     art reads as a picture that got cropped rather than as atmosphere. */
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 22%, #000 78%, transparent);
  mask-image: linear-gradient(180deg, transparent, #000 22%, #000 78%, transparent);
}
/* The art arrives only once the page has finished loading. At 45kb transferred
   it is the second heaviest asset on the home page and it is pure decoration,
   so it has no business competing with the fonts for the first few hundred
   milliseconds of a slow connection. app.js sets .deco-ready when loading is
   done. Without the class the rule never resolves, so the file is never even
   requested; browsers do not fetch a background-image nothing references.
   The keyframe declares only `from`, which lets the fade land on whichever
   opacity applies here: 0.085, 0.12 on a phone, 0.13 in the dark. */
html.deco-ready .odds-hero::before,
html.deco-ready .hero::before {
  background-image: url("/assets/img/lagos-skyline.svg?v=43ba9d09");
  animation: decoFade .5s ease both;
}
@keyframes decoFade { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  html.deco-ready .odds-hero::before,
  html.deco-ready .hero::before { animation: none; }
}

@media (prefers-color-scheme: dark) {
  /* One file, recoloured. Inverting the stroke is cheaper than shipping a
     second copy and keeps the two themes from drifting apart. */
  .odds-hero::before, .hero::before { filter: invert(1); opacity: 0.13; }
}
/* On a phone the hero is tall and dense, and anchoring the art to the foot of
   it put the skyline behind the picker bar and the cards, where none of it was
   visible. It moves up into the open band under the caption instead, and keeps
   the full width rather than a centre crop: at this size the whole silhouette
   reads as a city, whereas a crop reads as three tower blocks. A little more
   opacity to survive the smaller drawing. */
@media (max-width: 640px) {
  .odds-hero::before, .hero::before {
    background-position: center 24%;
    opacity: 0.12;
  }
}
@media (prefers-reduced-motion: reduce) { .odds-hero::before { transition: none; } }

/* The wide card gave up its white surface, so the skyline now sits directly
   under the caption and the foot line rather than behind an opaque panel.
   Measured against an art-free render, 0.085 moves the page ground by up to 22
   of 255 levels, which is about a sixth of the caption's own contrast and
   enough to soften 13px text. Halving it puts the darkest stroke at 11, where
   the art still reads as a city but no longer competes with anything set on
   it. The phone keeps 0.12: its card is still opaque white, so the art is only
   ever beside the text there, never under it. Dark is scaled by the same
   ratio. */
@media (min-width: 1024px) {
  .odds-hero::before, .hero::before { opacity: 0.045; }
}
@media (min-width: 1024px) and (prefers-color-scheme: dark) {
  .odds-hero::before, .hero::before { opacity: 0.07; }
}

/* Data caveat under the first visa card: shown when a route has not been
   verified in 90 days, or when the source was not explicit enough to score 60.
   Amber rather than red, and inside the card rather than above it, because the
   data is still the best available and still worth reading. A red banner at the
   top would read as "this page is broken" and send people away from an answer
   that is probably correct. */
.data-caveat {
  display: flex; gap: 9px; align-items: flex-start;
  margin: 0 18px 16px; padding: 10px 12px;
  background: var(--amber-50, rgba(245, 158, 11, .09));
  border: 1px solid var(--amber-100, rgba(245, 158, 11, .22));
  border-radius: var(--radius-md);
  font-size: 12.5px; line-height: 19px; color: var(--ink-700);
}
.data-caveat i { flex: none; font-size: 15px; color: var(--amber-700); margin-top: 1px; }
@media (max-width: 640px) { .data-caveat { margin: 0 14px 14px; } }

/* Changelog cards, every desktop width.
   This lived inside a max-width:1359 block, so above 1360 there was no rule at
   all and .cl-grid fell back to plain block flow: one card per row on the
   widest screens, which is where there is most room. Below 1024 the flex
   column further up still applies, which is what a phone wants.
   auto-fill rather than a fixed column count, because a change entry is a short
   old-to-new pair and a card much past 460px is mostly whitespace. */
@media (min-width: 1024px) {
  .cl-grid {
    display: grid; gap: 16px;
    grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
    /* stretch, not start: if a card ever does outgrow the others, the rest of
       its row matches it rather than leaving ragged gaps underneath. */
    align-items: stretch;
  }
}

/* ── Changelog cards and their detail dialog ──────────────────────────────
   Some entries hold a whole visa list or a paragraph of conditions. Printed in
   full they gave one card the height of six and turned an even grid into a
   ragged column, so the card states the shape of the change and the detail
   lives one click away. */
.cl-card-btn {
  width: 100%; text-align: left; font: inherit; cursor: pointer;
  border: 1px solid var(--edge-card);
}
.cl-card-btn:hover { border-color: var(--ink-300); }
.cl-card-btn:active { transform: translateY(1px); }
.cl-card-btn:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; }
/* One row, one line, so every card is the same height whatever it carries.
   Ellipsising the two values was not enough on its own: .cl-change wraps by
   default, so the tag, the old value and the new one each took a line of their
   own and a card carrying a long note came out at 213px against a neighbour's
   129px. The row has to refuse to wrap before the children can be made to fit
   inside it. Both values share what is left after the tag and the arrow, which
   are fixed, and each ellipsises on its own half. */
.cl-card .cl-change { flex-wrap: nowrap; }
.cl-card .cl-tag,
.cl-card .cl-change > i { flex: none; }
.cl-card .cl-old, .cl-card .cl-new-up, .cl-card .cl-new-down {
  flex: 1 1 0; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  overflow-wrap: normal; word-break: normal;
}
.cl-foot {
  margin-top: 10px; font-size: 11px; color: var(--ink-400);
  display: flex; align-items: center; gap: 6px;
 line-height: 16px;}
.cl-more { margin-left: auto; color: var(--brand-600); font-weight: 650; white-space: nowrap; }
.cl-more i { font-size: 11px; }

.cl-modal {
  border: 1px solid var(--edge-card); border-radius: var(--radius-card);
  padding: 0; max-width: 620px; width: calc(100% - 32px);
  background: var(--surface); color: var(--ink-950); box-shadow: var(--elev-3);
}
.cl-modal::backdrop { background: rgba(18, 16, 14, .45); backdrop-filter: blur(2px); }
.cl-modal-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 16px 18px; border-bottom: 1px solid var(--ink-100);
}
.cl-modal-head strong { display: block; font-size: 14.5px;  line-height: 22px;}
.cl-modal-head span { display: block; margin-top: 2px; font-size: 12px; color: var(--ink-500); }
.cl-modal-x {
  margin-left: auto; flex: none; border: 0; background: transparent;
  color: var(--ink-500); font-size: 18px; cursor: pointer; line-height: 1;
  padding: 2px; border-radius: var(--radius-sm);
}
.cl-modal-x:hover { background: var(--ink-50); color: var(--ink-950); }
.cl-modal-body { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 16px 18px; }
.cl-modal-label {
  display: block; font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; color: var(--ink-400); margin-bottom: 5px;
 line-height: 16px;}
.cl-modal-body pre {
  margin: 0; padding: 10px 12px; border-radius: var(--radius-md);
  background: var(--ink-50); border: 1px solid var(--ink-100);
  font-family: inherit; font-size: 12.5px; line-height: 19px; color: var(--ink-700);
  white-space: pre-wrap; overflow-wrap: anywhere; max-height: 44vh; overflow-y: auto;
}
.cl-modal-body > div:last-child pre { background: var(--accent-50); border-color: var(--accent-100); }
/* Before and after only compare when they are side by side, so on a phone they
   stack in that order rather than shrinking into two unreadable columns. */
@media (max-width: 560px) { .cl-modal-body { grid-template-columns: 1fr; } }

/* ── Tab strip overflow affordance ────────────────────────────────────────
   The strip scrolls sideways with its scrollbar hidden, which is right on a
   phone and wrong for discovery: a tab past the edge may as well not exist.
   These sit over the strip's own band, fade the content under them so the cut
   edge reads as "continues" rather than "ends", and are hidden by app.js
   whenever there is nothing that way. */
.rq-tabs-wrap { position: relative; }
.rq-tabs-arrow {
  position: absolute; top: 0; bottom: 1px; width: 40px;
  display: flex; align-items: center; border: 0; cursor: pointer;
  color: var(--ink-600); font-size: 15px; padding: 0; z-index: 2;
 line-height: 22px;}
.rq-tabs-arrow[hidden] { display: none; }
.rq-tabs-arrow:hover { color: var(--ink-950); }
.rq-tabs-arrow:focus-visible { outline: 2px solid var(--brand-500); outline-offset: -2px; }
/* The gradient is the affordance as much as the caret: a hard edge reads as the
   end of the list, a fade reads as more of it. */
.rq-tabs-arrow.left {
  left: 0; justify-content: flex-start; padding-left: 6px;
  background: linear-gradient(90deg, var(--ink-100) 55%, transparent);
}
.rq-tabs-arrow.right {
  right: 0; justify-content: flex-end; padding-right: 6px;
  background: linear-gradient(270deg, var(--ink-100) 55%, transparent);
}
/* Scrolling is animated, so the reader sees the strip move and understands the
   row is longer than the window rather than that the tabs changed. */
.rq-tabs { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { .rq-tabs { scroll-behavior: auto; } }

/* ── Whole-pixel line boxes in the sidebar ────────────────────────────────
   A ratio line height on an odd font size lands on a half pixel: 13px at 1.5
   is 19.5, 10.5 is 15.75, 11px is 16.5. Each fractional box pushes everything
   below it off the pixel grid too, so a column of labels drifts a little
   further off with every row.
   Fixed pixel values on the same sizes, chosen as the nearest even number to
   what the ratio already produced, so the spacing is unchanged to the eye and
   every row starts on a whole pixel. Confined to the sidebar: it is fixed
   chrome that every page shows, which makes it both the most visible place to
   get this right and the safest, since nothing here reflows with content. */
.sb-txt, .sidebar-btn, .sidebar-footer-title { line-height: 20px; }
.sidebar-label, .sb-route-ch { line-height: 16px; }
.sb-route-fl { line-height: 14px; }

/* ── Collapsed route picker on small screens ──────────────────────────────
   The picker is three stacked controls plus a button on a phone. Most arrivals
   read the score first, so it waits behind a single control and opens on tap.
   Desktop is untouched: there the picker is one row and always worth showing. */
.oh-open {
  display: none;
  /* The same 20px the form carries. Standing in for something means standing
     where it stood: without this the button sat flush against the stat cards
     above it, touching their border. */
  margin-top: 20px;
  width: 100%; align-items: center; justify-content: center; gap: 9px;
  /* --radius-btn like every other pressable control, not the card radius.
     It took 12px from the picker bar it replaces, but the bar is a card and
     this is a button: the bar's own submit is square precisely because the
     card around it supplies the corners. */
  padding: 15px 18px; border: 0; border-radius: var(--radius-btn);
  background: var(--brand-600); color: #fff;
  font-family: inherit; font-size: 15px; font-weight: 700; line-height: 20px;
  letter-spacing: 0; cursor: pointer;
  box-shadow: 0 1px 2px rgba(18,16,14,.05), 0 10px 22px -14px rgba(0,150,109,.6);
}
.oh-open i { font-size: 18px; }
.oh-open:active { transform: translateY(1px); }
.oh-open:focus-visible { outline: 2px solid var(--ink-950); outline-offset: 2px; }

/* ── Score and picker as two faces of one block ────────────────────────────
   Tapping the button turns the block over: the score is the front, the form is
   the back. Until app.js has measured both faces and added .is-flip these are
   plain stacked boxes, which is exactly what a page without JS gets, and what
   every screen too wide to collapse the picker gets.

   Both faces have to be taken out of flow for the rotation, so the block has no
   height of its own. app.js measures each face and writes --flip-h, and the
   height animates alongside the turn rather than snapping when it ends. */
@media (max-width: 899px) {
  .oh-flipwrap.is-flip { perspective: 1100px; }
  .oh-flipwrap.is-flip .oh-flip {
    position: relative;
    transform-style: preserve-3d;
    height: var(--flip-h, auto);
    transition: height .42s cubic-bezier(.5, 0, .2, 1),
                transform .42s cubic-bezier(.5, 0, .2, 1);
  }
  .oh-flipwrap.is-flip .oh-face {
    position: absolute; left: 0; right: 0; top: 0;
    -webkit-backface-visibility: hidden; backface-visibility: hidden;
  }
  .oh-flipwrap.is-flip .oh-face-back { transform: rotateX(180deg); }
  .oh-flipwrap.is-flip[data-flipped] .oh-flip { transform: rotateX(180deg); }

  /* The form carries no top margin here: it is a whole face, not something
     following the score. */
  .oh-flipwrap.is-flip .oh-face-back .oh-form { margin-top: 0; }

  /* The form sits straight on the page ground with the skyline behind it, and
     at 19px the country names were competing with the building outlines. This
     washes and blurs what is behind the form without putting a card back: it
     rides a pseudo-element, so nothing about the layout moves and the measured
     face height is untouched, and a radial mask fades the edges so there is no
     rectangle to notice.

     Scoped to .is-flip because that is where the face is already positioned.
     Giving .oh-face-back a position of its own here would override the absolute
     placement the flip depends on. */
  .oh-flipwrap.is-flip .oh-face-back::before {
    content: ""; position: absolute; inset: -12px -14px; z-index: -1;
    border-radius: 18px;
    background: rgba(245, 243, 240, 0.34);
    -webkit-backdrop-filter: blur(9px); backdrop-filter: blur(9px);
    -webkit-mask-image: radial-gradient(118% 112% at 50% 50%, #000 60%, transparent 100%);
    mask-image: radial-gradient(118% 112% at 50% 50%, #000 60%, transparent 100%);
  }
  /* Asked for less transparency: keep the wash, drop the blur. The skyline is
     still covered, which is the point of it. */
  @media (prefers-reduced-transparency: reduce) {
    .oh-flipwrap.is-flip .oh-face-back::before {
      background: var(--surface-2);
      -webkit-backdrop-filter: none; backdrop-filter: none;
    }
  }

  /* A turn is decoration. Without it the two faces still swap, instantly. */
  @media (prefers-reduced-motion: reduce) {
    .oh-flipwrap.is-flip .oh-flip { transition-duration: .001s; }
  }
}
@media (max-width: 1023px) {
  /* Only ever shown by script, so a page without JS keeps the form itself. */
  .oh-open:not([hidden]) { display: flex; }
}

/* ── TEMPORARY: bug-test recruitment (/beta). Remove with the feature. ─────── */
.beta-req { margin-bottom: 20px; padding: 20px; }
.beta-req h3 { margin: 0 0 6px; font-size: 16px; font-weight: 700; color: var(--ink-950); }
.beta-req-lead { margin: 0 0 14px; font-size: 13.5px; line-height: 20px; color: var(--ink-600); }
.beta-req-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.beta-req-list li {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 12px; align-items: baseline;
  padding: 9px 12px; border: 1px solid var(--ink-100); border-radius: var(--radius-md);
  background: var(--ink-50);
}
.beta-req-what { font-size: 13.5px; font-weight: 650; color: var(--ink-950); line-height: 20px;}
.beta-req-ver  { font-size: 13px; font-weight: 700; color: var(--brand-700); white-space: nowrap; }
.beta-req-note { grid-column: 1 / -1; font-size: 11.5px; color: var(--ink-400); line-height: 17px;}
.beta-req-foot { margin: 12px 0 0; font-size: 12.5px; color: var(--ink-500); line-height: 19px;}

/* The live browser check. Three states, and it says which one it is in words
   as well as colour, because colour alone is not an answer. */
.beta-cap {
  margin-top: 14px; display: flex; align-items: flex-start; gap: 9px;
  padding: 11px 13px; border-radius: var(--radius-md);
  font-size: 13px; line-height: 19px;
  border: 1px solid var(--ink-200); background: var(--surface); color: var(--ink-600);
}
.beta-cap[data-state="pass"] { border-color: var(--accent-100); background: var(--accent-50); color: var(--accent-800); }
.beta-cap[data-state="fail"] { border-color: var(--amber-100); background: var(--amber-50); color: var(--amber-800); }
.beta-cap i { font-size: 16px; flex: none; margin-top: 1px; }

/* The form is a column of sections with one gap between them, rather than each
   block carrying its own margin. A stack of fields where the spacing lives on
   the individual blocks is a stack where one block eventually has none: the
   devices question sat flush against the row above it for exactly that reason.
   22px between sections against 14px inside a row, so a new question reads as a
   bigger break than a neighbouring field. */
.beta-form { padding: 20px; display: flex; flex-direction: column; gap: 22px; }
.beta-form h3 { margin: 0; font-size: 16px; font-weight: 700; color: var(--ink-950); }
/* One source of spacing, and only one. The .field class carries its own
   bottom margin for use outside a flex column, and inside one that margin adds
   to the gap: the sections measured 22, 22, 38, 22, 38, so a form that looked
   deliberate was actually alternating between two spacings. Zeroing the
   margins here leaves the gap as the only thing setting the rhythm. */
.beta-form > * { margin-top: 0; margin-bottom: 0; }
/* The closing note belongs to the button above it, not to the column. */
.beta-form > .beta-foot { margin-top: -12px; }
/* Row and column spacing stated separately, and the children's own margins
   zeroed so they cannot add to it. Left alone, .field's 16px bottom margin sat
   on top of the 14px gap and rows came out at 30px against columns at 14px: two
   different spacings in one grid, neither of them chosen. 18 down and 14 across
   is deliberate, stacked fields needing a little more air than side-by-side. */
.beta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 14px; }
.beta-grid > * { margin: 0; }
@media (max-width: 640px) { .beta-grid { grid-template-columns: 1fr; } }
.beta-opt { font-weight: 400; color: var(--ink-400); font-size: 11.5px; line-height: 17px;}
.beta-err { display: block; margin-top: 5px; font-size: 12px; color: var(--rose-700); }

.beta-devices { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 8px; }
.beta-radio-row { display: flex; gap: 8px; flex-wrap: wrap; }
.beta-device {
  display: flex; align-items: center; gap: 9px; cursor: pointer;
  padding: 11px 13px; border: 1px solid var(--ink-200); border-radius: var(--radius-md);
  background: var(--surface); font-size: 13.5px; color: var(--ink-700); line-height: 20px;
}
.beta-device:has(input:checked) {
  border-color: var(--brand-500); background: var(--brand-50); color: var(--brand-700); font-weight: 600;
}
.beta-device input { accent-color: var(--brand-600); width: 16px; height: 16px; flex: none; }
.beta-foot { margin: 12px 0 0; font-size: 12px; line-height: 18px; color: var(--ink-500); text-align: center; }
