@import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400&display=swap");

:root {
  --blue: #1e88e5;
  --orange: #ff8f00;
  --text: #e6eef6;
  --bg: #0b1220;
  --card: rgba(255, 255, 255, 0.08);
  --glass: rgba(255, 255, 255, 0.10);
  --border: rgba(255, 255, 255, 0.18);
  /* UX-6: raised from 0.30 (2.66:1, failed WCAG AA) to 0.55 (5.28:1, passes
     AA) — used by the .slogan tagline and the "https://" input-leading hint,
     both user-facing text. Keeps the muted look while staying readable. */
  --white-30: rgba(255, 255, 255, 0.55);
  --white-10: rgba(255, 255, 255, 0.10);
  --blue-16: rgba(30, 136, 229, 0.16);
  --blue-10: rgba(30, 136, 229, 0.10);
  --blue-06: rgba(30, 136, 229, 0.06);
  --orange-20: rgba(255, 143, 0, 0.20);
  --orange-12: rgba(255, 143, 0, 0.12);
  --orange-08: rgba(255, 143, 0, 0.08);
  /* ── RADIUS SCALE (2026-08-07) ────────────────────────────────────────
     Reported as "rounding of elements seems random", and it was: the shell
     used 6, 8, 9, 10, 12, 13, 14 and 16px side by side. 12/13/14 are visually
     indistinguishable, so the variation read as sloppiness rather than
     hierarchy. Four steps, each with a job — anything else is a mistake:
       sm  small controls (paste, toolbar buttons, selects)
       md  inputs, primary buttons, rows
       lg  cards and panels
       pill genuine pills only
     Circles (50%) and the 2px progress hairline are deliberately outside it. */
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 18px;
  --r-pill: 999px;
  --proxy-nav-height: 0px;
  --proxy-nav-z: 2147483647;
  --proxy-frame-z: 2147483000;
}

/* Global Reset */
* { box-sizing: border-box; }

html {
  min-height: 100%;
  scrollbar-gutter: stable;
  /* Dark theme: tells the UA to render native form controls (selects,
     scrollbars, autofill) in dark mode so they match the page chrome. */
  color-scheme: dark;
  /* Avoid the default translucent gray tap-flash on iOS / Android Chrome
     in favor of an intentional accent. Per-element :active state still
     provides the press feedback. */
  -webkit-tap-highlight-color: rgba(30, 136, 229, 0.25);
}

/* Single global reduced-motion reset so every animated/transitioned
   element honors the user's preference (covers pulseWarn, goBtnSpin,
   long card transitions, etc. without needing per-rule guards). */
@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;
  }
}

body {
  font-family: "Roboto", Arial, Helvetica, sans-serif;
  margin: 0;
  --ad-overlay-scroll-top: 0px;
  background:
    radial-gradient(1200px 600px at 10% -10%, var(--orange-08), transparent 60%),
    radial-gradient(900px 500px at 100% 20%, var(--blue-06), transparent 60%),
    radial-gradient(2000px 800px at 50% 120%, rgba(17, 25, 40, 0.85), rgba(11, 18, 32, 0.95)),
    var(--bg);
  color: var(--text);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
  padding:
    max(16px, env(safe-area-inset-top))
    max(16px, env(safe-area-inset-right))
    max(20px, env(safe-area-inset-bottom))
    max(16px, env(safe-area-inset-left));
}

html.ad-overlay-open,
body.ad-overlay-open {
  scrollbar-gutter: stable;
}

body.ad-overlay-open {
  position: fixed !important;
  top: var(--ad-overlay-scroll-top);
  left: 0 !important;
  width: 100% !important;
  overflow: hidden !important;
}

/* Animated background elements: optimized for GPU */
.fx { position: fixed; inset: 0; pointer-events: none; z-index: -1; will-change: transform, opacity; backface-visibility: hidden; }

/* Grid layer moved to ::before and translated via transform instead of background-position */
.fx-grid { overflow: hidden; }
.fx-grid::before {
  content: "";
  position: absolute; inset: -20% -20% -20% -20%;
  background-image:
    linear-gradient(var(--white-10) 1px, transparent 1px),
    linear-gradient(90deg, var(--white-10) 1px, transparent 1px);
  background-size: 40px 40px, 40px 40px;
  opacity: 0.4;
  transform: translate3d(0,0,0);
  animation: gridDrift 26s linear infinite;
  will-change: transform;
}

.fx-noise {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='3' stitchTiles='stitch'/></filter><rect width='120' height='120' filter='url(%23n)' opacity='0.03'/></svg>");
  mix-blend-mode: soft-light;
}

/* Constrain orbs to smaller layers and remove heavy blur filter */
.fx-orb { position: fixed; width: 60vmin; height: 60vmin; border-radius: 50%; opacity: 0.5; will-change: transform; }
.orb-blue {
  left: -10vmin; top: 5vmin;
  background: radial-gradient(closest-side, var(--blue-16) 0%, rgba(0,0,0,0) 70%);
  box-shadow: 0 0 120px rgba(30,136,229,0.35), 0 0 200px rgba(30,136,229,0.25);
  animation: orbDrift1 30s ease-in-out infinite;
}
.orb-orange {
  right: -8vmin; top: -6vmin;
  background: radial-gradient(closest-side, var(--orange-20) 0%, rgba(0,0,0,0) 70%);
  box-shadow: 0 0 140px rgba(255,143,0,0.30), 0 0 220px rgba(255,143,0,0.22);
  animation: orbDrift2 36s ease-in-out infinite;
}

@keyframes gridDrift { 0% { transform: translate3d(0,0,0); } 100% { transform: translate3d(40px, 40px, 0); } }
@keyframes orbDrift1 {
  0%, 100% { transform: translate3d(0,0,0); }
  25% { transform: translate3d(15px, -8px, 0); }
  50% { transform: translate3d(-8px, 15px, 0); }
  75% { transform: translate3d(12px, 10px, 0); }
}
@keyframes orbDrift2 {
  0%, 100% { transform: translate3d(0,0,0); }
  25% { transform: translate3d(-12px, 12px, 0); }
  50% { transform: translate3d(12px, -10px, 0); }
  75% { transform: translate3d(-16px, 6px, 0); }
}

/* Respect users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  .fx-grid::before, .orb-blue, .orb-orange { animation: none !important; transform: none !important; }
}

.fa-magnifying-glass { color: white; }

.flex-center { display: flex; justify-content: center; }

.header-center { align-items: center; flex-direction: column; margin-top: 10%; }

.left-margin { margin: 0px 16px; }

img,
svg {
  max-width: 100%;
  height: auto;
}

#uv-frame {
  display: none;
  border: none;
  background-color: #111;
  /* The shell sets `html { color-scheme: dark }` (above) to theme its OWN native
     form controls/scrollbars. Per spec, `color-scheme` propagates into nested
     browsing contexts, so without this the proxied iframe inherited `dark` and
     reported `prefers-color-scheme: dark` to the proxied page REGARDLESS of the
     user's real OS/browser preference — flipping every `color-scheme: auto` site
     (xvideos, GitHub, MDN, Reddit, …) to its dark theme through the proxy even
     when the user is in light mode. `normal` stops the inheritance so the proxied
     document honours the user's actual preference. (`light dark` breaks
     matchMedia in Firefox; `normal` is correct.) Verified FF: real=light→frame
     light, real=dark→frame dark. */
  color-scheme: normal;
}

#uv-error { color: #ff6666 !important; white-space: pre-wrap; }

/* 🚨 THE BOOT NOTICE IS NOT AN ERROR, AND IT WAS BEING PAINTED AS ONE.
   Pressing Go while the shell is still booting writes "Starting up — one moment,
   then press Go." into #uv-error, and when boot completes the SAME element is
   rewritten to "Ready — press Go to open your page." Both are informational, and
   both were rendering in #ff6666 error red directly under the search card — which
   is exactly the user report "I often, when searching too quickly, get an error
   message at the bottom when pressing Go" (reproduced at 390px, 480px and 1280px).
   The marker `data-boot-notice` already existed (index.js sets it on the Go-during-
   boot path and deletes it before any real error is written), so this needs no new
   plumbing.
   🪤 Scoped to ="1" ON PURPOSE. setShellBootState's FAILED branch re-marks the
   element "failed" because it writes a GENUINE error into the same node; keying the
   neutral colour on ="1" alone is what stops this from de-emphasising a real
   failure. Do not widen it to [data-boot-notice]. */
#uv-error[data-boot-notice="1"] { color: var(--white-30) !important; }

#uv-error-code {
  font-size: 12px;
  color: var(--blue);
  font-family: "Courier New", Courier, monospace;
}

#uv-register-sw { color: white; background: #555555; cursor: pointer; outline: none; border: none; border-radius: 6px; padding: 16px 20px; line-height: 16px; display: none; }
#uv-register-sw:active { background: #333333; }
#uv-register-sw.show { display: block; }

.logo { width: 150px; height: 150px; }

.logo-wrapper .text { font-size: 75px; color: #fff; }
.logo-wrapper h1 { color: white; }

.logo-section {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: min(100%, 720px);
  margin: clamp(12px, 4vh, 40px) auto 0;
  gap: 10px;
}

.logo-svg-wrapper { display: flex; flex-direction: column; align-items: center; }

.bird-logo-shell {
  width: 88px;
  height: 88px;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, rgba(30,136,229,0.5), rgba(255,143,0,0.35));
  border-radius: 50%;
  box-shadow:
    0 10px 40px rgba(30,136,229,0.35),
    inset 0 0 0 1px var(--white-10);
  margin-bottom: 10px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  overflow: hidden;
  flex-shrink: 0;
}

.bird-logo {
  display: block;
  width: 80px;
  height: 80px;
  background: transparent;
}

/* Unify SVG accents */
.bird-logo path { stroke: var(--blue); }
.bird-logo circle { fill: var(--blue); }

/* margin:0 is load-bearing — this element is an <h1>, and the UA default
   (margin: .67em 0) would otherwise push the hero down. */
.logo-title { display: flex; flex-direction: column; align-items: center; margin: 0; }

.vpnfree {
  font-size: 2.6rem; font-weight: 800; letter-spacing: 2px;
  background: linear-gradient(90deg, var(--blue), var(--orange));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

.slogan { font-size: 1.1rem; color: var(--white-30); margin-top: 2px; font-style: italic; }

/* Glass features card */
.features-section {
  background: linear-gradient(180deg, rgba(255,255,255,0.10), rgba(255,255,255,0.06));
  border-radius: 18px;
  border: 1px solid var(--border);
  box-shadow: 0 10px 50px rgba(0,0,0,0.35);
  margin: 32px auto 0 auto;
  padding: 32px 24px 24px 24px;
  width: min(100%, 640px);
  max-width: 640px;
  text-align: center;
  backdrop-filter: blur(10px) saturate(120%);
}

.features-section h2 { color: var(--orange); font-size: 1.6rem; margin-bottom: 0.5em; }
.features-section .subtitle { color: var(--text); opacity: 0.7; font-size: 1.1rem; margin-bottom: 1.2em; }

.features-list { list-style: none; padding: 0; margin: 0 0 1.2em 0; }

.features-list li {
  font-size: 1.08rem; margin: 0.65em 0; color: var(--text); text-align: left; padding-left: 1.2em; position: relative;
}
.features-list li:before { content: "•"; color: var(--orange); position: absolute; left: 0; font-size: 1.2em; top: 0; }
.features-list b { color: var(--orange); }

.catch {
  background: rgba(255,255,255,0.06);
  color: var(--orange);
  border: 1px solid var(--orange-20);
  border-radius: 10px;
  padding: 10px 16px; font-size: 1rem; margin-top: 1em;
}

/* Search input group */
.main-form {
  display: flex;
  justify-content: center;
  width: min(100%, 720px);
  margin: 32px auto 0;
}

/* Landing-page edge-node picker (sits just under the search box). */
.edge-picker {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
  width: min(100%, 720px);
  margin: 14px auto 0;
}
.edge-picker-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: rgba(230, 238, 246, 0.7);
  font-size: 0.92rem;
  font-weight: 500;
}
.edge-picker-icon {
  font-size: 1rem;
  line-height: 1;
}
.edge-picker-select {
  min-height: 40px;
  max-width: 280px;
  padding: 0 30px 0 12px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 14px;
  color: var(--text);
  background: rgba(255, 255, 255, 0.08);
  font: inherit;
  cursor: pointer;
  transition: transform 0.12s ease, background-color 0.2s ease, border-color 0.2s ease;
}
.edge-picker-select:hover:not(:disabled) {
  transform: translateY(-1px);
  background: rgba(255, 255, 255, 0.13);
  border-color: rgba(255, 255, 255, 0.28);
}
.edge-picker-select:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}
.edge-picker-select:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.edge-picker-select option {
  background: #111a2a;
  color: var(--text);
}

.input-group {
  display: flex; align-items: center; gap: 10px;
  background: var(--glass);
  border: 1px solid var(--border);
  border-radius: 48px;
  padding: 8px 10px 8px 14px;
  box-shadow: 0 10px 40px rgba(0,0,0,0.3);
  backdrop-filter: blur(12px) saturate(120%);
  width: 100%;
  min-width: 0;
}
.input-leading { color: var(--white-30); font-weight: 500; }

#uv-address {
  background: transparent; font-family: inherit; padding: 10px 6px; height: 40px; border: none; color: var(--text);
  border-radius: 20px; outline: none; width: auto; min-width: 0; flex: 1 1 240px; margin-top: 0; font-size: 1.06rem;
}
#uv-address::placeholder { color: rgba(230, 238, 246, 0.55); }
/* Native outline removed above; keyboard users get a focus ring on the
   surrounding glass container (see .input-group:focus-within). The input
   itself uses :focus-visible as a fallback for AT that targets the input. */
#uv-address:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}
.input-group:focus-within {
  box-shadow: 0 0 0 2px var(--blue), 0 0 0 4px rgba(30, 136, 229, 0.25);
}

.go-btn {
  appearance: none; border: none; cursor: pointer; outline: none;
  height: 44px; padding: 0 20px; border-radius: 30px; font-weight: 700; color: #0b1220;
  background: linear-gradient(135deg, var(--orange), #ffc046);
  box-shadow: 0 8px 24px rgba(255, 143, 0, 0.35), inset 0 0 0 1px rgba(255,255,255,0.2);
  /* Explicit property list — never `transition: all`. */
  transition: transform 0.12s ease, box-shadow 0.2s ease, filter 0.2s ease;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 88px;
  text-decoration: none;
  user-select: none;
  /* Suppress the iOS Safari 300ms double-tap-zoom delay on tap. */
  touch-action: manipulation;
}
.go-btn:focus-visible {
  /* 🪤 WAS `#0b1220` — the EXACT value of --bg, i.e. an outline painted in the
     page background colour. Keyboard focus on the primary CTA was invisible,
     while every other control on the page gets a 2px --blue ring. Measured
     2026-08-30. Do not "match the button" here: the ring must contrast with the
     PAGE behind it, not with the control it surrounds. */
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}
.go-btn:hover { transform: translateY(-1px); filter: brightness(1.05); box-shadow: 0 12px 32px rgba(255, 143, 0, 0.45); }
.go-btn:active { transform: translateY(0); filter: brightness(0.98); }

.go-btn-spinner {
  display: none;
}

.go-btn-spinner {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid rgba(11, 18, 32, 0.28);
  border-top-color: #0b1220;
  animation: goBtnSpin 0.75s linear infinite;
}

.go-btn.is-loading {
  cursor: wait;
  filter: saturate(0.9);
}

.go-btn.is-loading:hover,
.go-btn.is-loading:active {
  transform: none;
}

.go-btn.is-loading .go-btn-label {
  display: none;
}

.go-btn.is-loading .go-btn-spinner {
  display: inline-flex;
}

.go-btn:disabled {
  opacity: 0.92;
}

.go-btn[aria-disabled="true"] {
  opacity: 0.92;
  pointer-events: none;
}

@keyframes goBtnSpin {
  to { transform: rotate(360deg); }
}

/* Footer */
footer {
  width: min(100%, 720px);
  margin: auto auto 0;
  padding-top: 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 30px;
}
.footer-links { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; margin-bottom: 10px; }
.footer-links a { margin: 0 15px; text-decoration: none; color: var(--text); opacity: 0.75; font-size: 15px; transition: color 0.2s, opacity 0.2s; }
.footer-links a:hover { opacity: 1; text-decoration: underline; }
.footer-links a[aria-current="page"] {
  color: var(--orange);
  opacity: 1;
  text-decoration: underline;
  text-decoration-color: var(--orange);
  text-underline-offset: 4px;
}
footer span { color: rgba(230, 238, 246, 0.5); font-size: 14px; }
.footer-bottom { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; justify-content: center; }

.content-page {
  width: min(100%, 760px);
  margin: 28px auto 0;
}

.page-card {
  background: linear-gradient(180deg, rgba(255,255,255,0.10), rgba(255,255,255,0.06));
  border-radius: 22px;
  border: 1px solid var(--border);
  box-shadow: 0 10px 50px rgba(0,0,0,0.35);
  padding: 32px 24px 28px;
  backdrop-filter: blur(10px) saturate(120%);
}

.page-eyebrow {
  display: inline-flex;
  align-items: center;
  padding: 6px 10px;
  border-radius: 999px;
  border: 1px solid var(--orange-20);
  background: rgba(255,255,255,0.06);
  color: var(--orange);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page-top-link {
  margin-bottom: 18px;
}

.page-card h1 {
  margin: 16px 0 10px;
  font-size: clamp(2rem, 5vw, 2.8rem);
  line-height: 1.05;
}

.page-lead {
  margin: 0 0 24px;
  color: rgba(230, 238, 246, 0.78);
  font-size: 1.05rem;
  line-height: 1.6;
}

.page-section + .page-section {
  margin-top: 20px;
}

.page-section h2 {
  margin: 0 0 8px;
  color: var(--orange);
  font-size: 1.08rem;
}

.page-section p {
  margin: 0;
  color: rgba(230, 238, 246, 0.86);
  line-height: 1.7;
}

.page-section p + p {
  margin-top: 12px;
}

.page-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}

.page-link-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 38px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: rgba(255,255,255,0.06);
  color: var(--text);
  text-decoration: none;
  transition: transform 0.12s ease, background 0.2s ease, border-color 0.2s ease;
}

.page-link-chip:hover {
  transform: translateY(-1px);
  background: rgba(255,255,255,0.1);
  border-color: rgba(255,255,255,0.28);
}

.page-link-chip.is-primary {
  color: #0b1220;
  background: linear-gradient(135deg, var(--orange), #ffc046);
  border-color: transparent;
  box-shadow: 0 8px 24px rgba(255, 143, 0, 0.28);
}

.page-link-chip.is-primary:hover {
  background: linear-gradient(135deg, #ff9800, #ffc85c);
}

.page-link-chip.is-back {
  gap: 10px;
  padding: 0 12px;
}

.page-link-chip-icon {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
}

.contact-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 18px;
  border-radius: 18px;
  border: 1px solid var(--border);
  background: rgba(255,255,255,0.05);
}

.contact-label {
  color: rgba(230, 238, 246, 0.62);
  font-size: 0.82rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.contact-email {
  color: var(--orange);
  font-size: clamp(1.3rem, 4vw, 1.9rem);
  font-weight: 800;
  text-decoration: none;
  word-break: break-word;
}

.contact-note {
  margin: 0;
  color: rgba(230, 238, 246, 0.78);
  line-height: 1.6;
}

/* Language selector */
.lang-selector { display: inline-flex; align-items: center; }
.lang-selector select {
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: var(--r-sm);
  color: rgba(230, 238, 246, 0.7);
  font-size: 13px;
  padding: 4px 8px;
  cursor: pointer;
  /* Explicit property list — never `transition: all`. */
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
  outline: none;
  touch-action: manipulation;
}
.lang-selector select:hover {
  background: rgba(255, 255, 255, 0.15);
  border-color: rgba(255, 255, 255, 0.3);
  color: rgba(230, 238, 246, 0.9);
}
/* :focus-visible (not :focus) — avoids highlighting on click, only on
   keyboard navigation. Matches the global focus-ring style. */
.lang-selector select:focus-visible {
  background: rgba(255, 255, 255, 0.15);
  border-color: var(--blue);
  color: rgba(230, 238, 246, 0.95);
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}
.lang-selector select option {
  background: #1a2332;
  color: #e6eef6;
}

/* Credit block (kept as-is aside from color) */
.credit { border-radius: 10px; padding: 10px; display: block; border: #fff 1px solid; color: #fff; display: flex; flex-wrap: wrap; margin-bottom: 15px; }
.credit label { margin-left: auto; margin-right: 15px; }
.credit a, .credit label { color: white; text-decoration: underline; text-align: right; }
.credit pre { display: none; width: 100%; }
.credit label::after { content: "show license"; cursor: pointer; }
.credit input:checked + label::after { content: "hide license"; }
.credit input:checked ~ pre { display: block; }

iframe#uv-frame { display: none; }

/* Proxy navigation overlay */
.vpn-browser-nav {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* Backstop: even if a child mis-sizes, the navbar itself cannot push the
     viewport wider than the visible window. Prevents the entire page from
     gaining a horizontal scrollbar on narrow viewports when one toolbar
     child briefly grows past its allotted column (long brand text, long
     URL paste mid-shrink, etc.).
     iOS Safari quirk: when the proxied iframe contains a wider-than-viewport
     desktop site (xnxx, etc.), Safari's 100vw can balloon out — it reflects
     the document's effective layout viewport, not the visible window. We
     pin to a JS-measured --vpn-window-width custom property as the
     authoritative cap, falling back to 100svw (small viewport width)
     which is specced to be the visible window even when content overflows. */
  max-width: var(--vpn-window-width, 100vw);
  max-width: var(--vpn-window-width, 100svw);
  overflow: hidden;
  z-index: var(--proxy-nav-z);
  color: var(--text);
  padding:
    calc(8px + env(safe-area-inset-top))
    max(10px, env(safe-area-inset-right))
    8px
    max(10px, env(safe-area-inset-left));
  background:
    linear-gradient(135deg, rgba(8, 14, 25, 0.96), rgba(16, 25, 40, 0.92)),
    radial-gradient(700px 140px at 12% 0%, rgba(255, 143, 0, 0.18), transparent 70%),
    radial-gradient(700px 160px at 90% 10%, rgba(30, 136, 229, 0.20), transparent 70%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.32);
  backdrop-filter: blur(16px) saturate(125%);
  -webkit-backdrop-filter: blur(16px) saturate(125%);
}

.vpn-browser-nav[hidden] {
  display: none !important;
}

body.browsing .vpn-browser-nav:not([hidden]) {
  display: flex;
  flex-direction: column;
  /* Tight gap between toolbar and ad row — fixpack84. The previous
     8px stacked with the ad container's own card chrome (border +
     padding) producing ~30px of empty space above the ad creative.
     With the card chrome removed and a 2px gap, the ad sits visually
     adjacent to the toolbar. */
  gap: 2px;
}

.vpn-browser-toolbar {
  display: grid;
  /* 6 columns matching the 6 toolbar children in order:
     1. brand (anchor, min 150px, grows with text)
     2. close (button, intrinsic)
     3. history (back + forward + refresh wrapper, intrinsic)
     4. address form (flex 1fr, the elastic column)
     5. mirror selector (intrinsic, min 118px)
     6. bug-report button (intrinsic)
     The previous 7-column declaration had an unused trailing track
     left over from before the refresh button (which is part of the
     history wrapper, not its own grid cell). */
  grid-template-columns:
    minmax(150px, auto)         /* brand */
    auto                        /* close */
    auto                        /* history */
    minmax(220px, 1fr)          /* address */
    minmax(118px, auto)         /* mirror */
    auto;                       /* bug */
  gap: 8px;
  align-items: center;
  width: 100%;
  /* Hard cap to the JS-measured window width so a wide proxied page
     (e.g. xnxx, gaming portals) can't push the toolbar past the visible
     viewport on iOS Safari. min-width:0 lets the grid actually shrink
     below content-min when columns are sized `auto`. */
  max-width: var(--vpn-window-width, 100%);
  min-width: 0;
}

/* All grid children get min-width:0 by default so the cascade can't force
   any one of them past its column allocation. Without this, items with
   `auto` columns can resist shrinking and push the row width. */
.vpn-browser-toolbar > * {
  min-width: 0;
}

.vpn-browser-brand {
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  white-space: nowrap;
}

.vpn-browser-brand-mark {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: 14px;
  background: linear-gradient(135deg, rgba(30, 136, 229, 0.34), rgba(255, 143, 0, 0.24));
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}

.vpn-browser-brand-bird {
  display: block;
}

.vpn-browser-brand-text {
  font-weight: 800;
  font-size: 1rem;
  letter-spacing: 0.02em;
  background: linear-gradient(90deg, var(--blue), var(--orange));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.vpn-nav-history {
  display: inline-flex;
  gap: 6px;
}

.vpn-nav-button,
.vpn-nav-go,
.vpn-mirror-select {
  min-height: 38px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 14px;
  color: var(--text);
  background: rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.03);
  font: inherit;
}

.vpn-nav-button,
.vpn-nav-go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 12px;
  cursor: pointer;
  transition: transform 0.12s ease, background-color 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
  /* Suppress the iOS Safari 300ms double-tap-zoom delay on tap. */
  touch-action: manipulation;
}

.vpn-nav-button:hover:not(:disabled),
.vpn-nav-go:hover:not(:disabled),
.vpn-mirror-select:hover:not(:disabled) {
  transform: translateY(-1px);
  background: rgba(255, 255, 255, 0.13);
  border-color: rgba(255, 255, 255, 0.28);
}

/* Keyboard-only focus rings for all browser-nav controls. Matches the
   accent color used on the rest of the page so it reads as part of the
   same UI. */
.vpn-nav-button:focus-visible,
.vpn-nav-go:focus-visible,
.vpn-mirror-select:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-color: var(--blue);
}

.vpn-nav-button:disabled,
.vpn-nav-go:disabled,
.vpn-mirror-select:disabled {
  cursor: not-allowed;
  opacity: 0.42;
}

/* When the address input has focus, ring the surrounding form so the
   "active address bar" is obvious without ringing the input + the Go
   button separately. */
.vpn-nav-form:focus-within {
  box-shadow: 0 0 0 2px var(--blue);
  border-radius: 14px;
}
.vpn-nav-address:focus-visible {
  outline: none; /* container's focus-within ring is the visible state */
}

.vpn-nav-close {
  width: 38px;
  padding: 0;
  color: #0b1220;
  background: linear-gradient(135deg, var(--orange), #ffc046);
  border-color: transparent;
  font-size: 1.45rem;
  font-weight: 800;
}

/* Refresh button: matches the back/forward sizing. The .is-refreshing
   class is toggled by JS while a refresh is in flight so the user gets
   visual feedback that the click registered (the iframe blank window
   would otherwise look like nothing happened). */
#vpn-nav-refresh {
  width: 38px;
  padding: 0;
  font-size: 1.15rem;
  line-height: 1;
}
.vpn-nav-refresh-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Force GPU-friendly transform animation. */
  transform-origin: center;
  transform-box: fill-box;
}
#vpn-nav-refresh.is-refreshing .vpn-nav-refresh-icon {
  animation: vpnNavRefreshSpin 0.8s linear infinite;
}
@keyframes vpnNavRefreshSpin {
  to { transform: rotate(360deg); }
}
/* The global prefers-reduced-motion rule (search :root scope) already
   targets `*, *::before, *::after` and would mute this animation. The
   explicit per-rule guard below is belt-and-suspenders: it makes the
   intent obvious to a reader and protects against future global-rule
   refactors. A subtle opacity pulse keeps SOME visual feedback so the
   user can tell the click registered without nausea-triggering motion. */
@media (prefers-reduced-motion: reduce) {
  #vpn-nav-refresh.is-refreshing .vpn-nav-refresh-icon {
    animation: none;
    opacity: 0.5;
  }
}

.vpn-nav-form {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px;
  border-radius: 18px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.08);
}

.vpn-nav-address {
  min-width: 0;
  flex: 1 1 auto;
  height: 34px;
  border: 0;
  outline: 0;
  color: var(--text);
  background: transparent;
  font: inherit;
  padding: 0 8px;
  /* Native outline removed; container shows a focus-within ring (see
     .vpn-nav-form:focus-within at the form rule). Fallback ring for AT
     that focuses the input directly: */
}

.vpn-nav-address::placeholder {
  color: rgba(230, 238, 246, 0.55);
}

.vpn-nav-go {
  min-height: 34px;
  color: #0b1220;
  border-color: transparent;
  background: linear-gradient(135deg, var(--orange), #ffc046);
  font-weight: 800;
}

.vpn-mirror-field {
  min-width: 0;
}

.vpn-mirror-select {
  width: 100%;
  max-width: 180px;
  padding: 0 28px 0 10px;
  cursor: pointer;
  outline: 0;
}

.vpn-mirror-select option {
  background: #111a2a;
  color: var(--text);
}

.vpn-bug-report {
  min-width: 76px;
  border-color: rgba(255, 143, 0, 0.26);
  background: rgba(255, 143, 0, 0.12);
}

/* The bug glyph is the small-device affordance only — on wide layouts the
   text label carries the button, so keep the icon out of the flow there.
   Each narrow breakpoint that hides .vpn-bug-report-text re-shows this. */
.vpn-bug-report-icon {
  display: none;
}

.vpn-bug-report-icon path,
.vpn-bug-report-icon rect {
  vector-effect: non-scaling-stroke;
}

.vpn-browser-ad {
  /* CONTAINER: full-width centered row (AD label + the 300×100 slot). Kept as a
     full-width flex row (fp164→165): it centers the slot cleanly and gives any
     %-based child width a definite basis. (fp164 briefly used a 728px slot whose
     `min(728px,100%)` collapsed to 0 under the old inline-flex shrink-wrap; the
     nav ad is back to a hard-300×100 slot, but full-width-centered is the
     robust container either way.) */
  display: flex;
  width: 100%;
  justify-content: center;
  align-items: center;
  gap: 10px;
  overflow: hidden;
  padding: 0 6px;
}

.vpn-browser-ad-label {
  color: rgba(230, 238, 246, 0.54);
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  flex-shrink: 0;
}

.vpn-browser-ad-slot {
  /* Holds the DEDICATED navbar ad iframe (ExoClick iframe.php delivery): the
     mobile 300×100 zone 5953056, centered so wide screens get whitespace either
     side. A tablet 728×90 zone (5953060) used to be swapped in above 728px; it
     was removed 2026-08-04 because it earns 21× less per impression than this
     one (eCPM €0.0009 vs €0.0195 over 30d) — NOT because it "never fills", a
     first read that was retracted before shipping. See the note on
     .vpn-browser-ad-mobile below.

     The slot GROWS to fill the toolbar row (`flex: 1`) instead of shrink-wrapping
     its content. That's deliberate: it makes the slot's inline size equal to the
     real available width, which `container-type: inline-size` then exposes to the
     child as `cqw`. (It also used to drive an `@container` toggle that picked the
     728×90 zone only when a full 728 fit; that toggle is gone — see the note on
     .vpn-browser-ad-mobile below — but the sizing rationale still holds, and
     `100cqw` still depends on it.) (fixpack188: fixes the
     "ad takes 1/3 of the space" bug — the old viewport `≤480px` breakpoint put
     every 481–~900px viewport, i.e. most phones in portrait + small tablets, on
     the 728 zone, where `max-width:100%` shrank the 728 iframe far below its
     native width. ExoClick then filled only ~300px of it and its own white
     iframe background showed in the rest. Sizing the switch to the real slot
     width — not a locale-fragile px breakpoint — means the 728 is never capped.) */
  height: 100px;
  max-width: 100%;
  flex: 1 1 auto;
  min-width: 0;
  container-type: inline-size;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.vpn-browser-ad-iframe {
  /* ExoClick's iframe.php delivery. Cross-origin — we only size the box, never
     its content. Rounded + a hairline frame so the creative's own white bg reads
     as a deliberate ad card on the dark toolbar. ⚠️ That framing is ALSO what made
     the 5953060 no-fill read as a deliberate white card instead of an obvious
     bug — the rounded 728×90 users reported was this border-radius around an
     empty ExoClick frame. Per-variant size below. */
  display: block;
  max-width: 100%;
  border: 0;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: rgba(255, 255, 255, 0.04);
}

/* The mobile 300×100 (zone 5953056) is the ONLY nav variant, shown at every slot
   width. `100cqw` caps it on very narrow phones (was `calc(100vw - 60px)`) so it
   can't overflow — that is why the slot KEEPS `container-type: inline-size` even
   though the variant query is gone. Scoped under .vpn-browser-ad-slot so the
   per-variant size rules outrank the base .vpn-browser-ad-iframe without
   !important.

   SUPERSEDED 2026-08-04 (the fp188 design, kept for the reasoning): fp188 drove
   the variant switch off the SLOT's real width rather than a viewport breakpoint,
   so the 728×90 was only shown when a full 728 genuinely fit and could never be
   capped/white-gapped. That reasoning was sound and is why the container-query
   approach stays — but it solved the wrong half of the problem. fp188 stopped the
   728 being CAPPED; it could not make the zone's creatives WORTH anything.

   🪤 So the `@container (min-width: 728px)` block that swapped in the 728×90 is
   REMOVED: over 30d zone 5953060 took 3,990 impressions for €0.0037 (eCPM
   €0.0009) while 5953056 took 5,046 for €0.0985 (eCPM €0.0195) — a 21× worse
   rate for the same slot — and desktop/tablet users saw a blank white frame.
   ⚠️ Do NOT restate this as "5953060 returns an empty document / never fills":
   that was a first read, RETRACTED before shipping (with a €1 soft floor the zone
   served a real creative and billed the impression; the empty probes were almost
   certainly frequency-capped repeats from one datacenter IP). The white-box
   MECHANISM is therefore unproven — leading hypothesis is creatives narrower than
   728 letting ExoClick's own white iframe background show through, framed by the
   border-radius above. The 21× rate gap is the floor-independent reason this went.
   728×90 has now failed twice in this slot — fp165, then again as fp172's
   purpose-built dedicated zone. Do NOT re-add a wide variant without live
   per-zone revenue proving it fills first. See the NAV_AD_ZONES comment in
   index.js and docs/TELEMETRY_FINDINGS.md NAV-AD-728-1. */
.vpn-browser-ad-slot .vpn-browser-ad-mobile {
  width: min(300px, 100cqw);
  height: 100px;
  display: block;
}

.vpn-report-panel {
  display: grid;
  grid-template-columns: minmax(180px, 260px) minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  padding: 10px 12px 12px;
  border-radius: 18px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(4, 9, 18, 0.55);
  /* Drawer/sheet pattern: keep scroll chains inside the panel so a
     bottom-bounce on the report textarea doesn't pull the underlying
     proxied page along with it. */
  overscroll-behavior: contain;
}

.vpn-report-panel[hidden] {
  display: none !important;
}

.vpn-report-panel-copy {
  display: flex;
  flex-direction: column;
  gap: 6px;
  color: rgba(230, 238, 246, 0.82);
  font-size: 0.92rem;
}

/* Safety-critical: the message is posted publicly. Make the notice stand out. */
.vpn-report-public-notice {
  color: #ffcf7a;
  font-size: 0.85rem;
  line-height: 1.35;
  border-left: 3px solid rgba(255, 176, 60, 0.7);
  padding: 4px 0 4px 10px;
}

.vpn-report-form {
  display: grid;
  gap: 10px;
}

.vpn-report-message {
  width: 100%;
  min-height: 92px;
  resize: vertical;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  padding: 12px 14px;
  font: inherit;
  color: var(--text);
  background: rgba(255, 255, 255, 0.08);
  outline: 0;
}
.vpn-report-message:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-color: var(--blue);
}

.vpn-report-message::placeholder {
  color: rgba(230, 238, 246, 0.52);
}

.vpn-report-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.vpn-report-cancel {
  min-width: 88px;
}

.vpn-report-submit {
  min-width: 128px;
}

.vpn-report-status {
  margin: 0;
  grid-column: 1 / -1;
  color: rgba(230, 238, 246, 0.72);
  font-size: 0.88rem;
}

.vpn-report-status.is-error {
  color: #ff9b9b;
}

.vpn-report-status.is-success {
  color: #93ecbc;
}

#uv-frame.is-priming,
#uv-frame.is-visible {
  display: block;
  position: fixed;
  left: 0;
  width: 100vw;
  border: none;
  background-color: #111;
}

#uv-frame.is-priming {
  top: 0;
  height: 100dvh;
  z-index: var(--proxy-frame-z);
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}

body.browsing #uv-frame.is-visible {
  top: var(--proxy-nav-height);
  height: calc(100dvh - var(--proxy-nav-height));
  z-index: var(--proxy-frame-z);
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}

/* Loader overlay: shown during back/forward navigation (when the iframe
   would otherwise be a blank black void for 2-5 seconds during YouTube's
   reload). The shell toggles body.is-frame-loading around the transition. */
#vpn-frame-loader {
  display: none;
  position: fixed;
  top: var(--proxy-nav-height);
  left: 0;
  right: 0;
  bottom: 0;
  /* One above the iframe so the spinner is visible even while the iframe
     is in is-priming state (which is z-index var(--proxy-frame-z)). */
  z-index: calc(var(--proxy-frame-z) + 1);
  background: radial-gradient(900px 600px at 50% 40%, rgba(30, 136, 229, 0.10), transparent 70%), #0c1220;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  /* Fade-in to avoid a hard flash when nav starts. */
  opacity: 0;
  transition: opacity 0.18s ease-out;
}

body.is-frame-loading #vpn-frame-loader {
  display: flex;
  opacity: 1;
}

/* UX-1: stack spinner + message + slow-hint + cancel vertically, and give the
   loader pointer events so the Cancel button is clickable. */
#vpn-frame-loader {
  flex-direction: column;
  gap: 18px;
  padding: 24px;
  text-align: center;
}
body.is-frame-loading #vpn-frame-loader { pointer-events: auto; }

/* UX-1: on the FIRST navigation the proxy nav bar is still hidden, so the
   loader must cover the full viewport (not start below --proxy-nav-height). */
body.is-frame-loading:not(.browsing) #vpn-frame-loader { top: 0; }

.vpn-frame-loader-msg {
  margin: 0;
  font-size: 1.05rem;
  color: #e8eef8;
  font-weight: 600;
  max-width: 32ch;
  word-break: break-word;
}

/* The "taking longer" hint + Cancel only appear once #vpn-frame-loader gets
   the .is-slow class (after ~10s). Hidden by default. */
.vpn-frame-loader-slow,
.vpn-frame-loader-cancel { display: none; }
#vpn-frame-loader.is-slow .vpn-frame-loader-slow {
  display: block;
  margin: 0;
  font-size: 0.9rem;
  color: rgba(232, 238, 248, 0.72);
}
#vpn-frame-loader.is-slow .vpn-frame-loader-cancel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 8px 22px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(255, 255, 255, 0.06);
  color: #e8eef8;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
#vpn-frame-loader.is-slow .vpn-frame-loader-cancel:hover { background: rgba(255, 255, 255, 0.12); }

.vpn-frame-loader-spinner {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 4px solid rgba(255, 255, 255, 0.14);
  border-top-color: var(--orange);
  border-right-color: var(--blue);
  animation: vpn-frame-loader-spin 0.9s linear infinite;
  box-shadow: 0 0 32px rgba(30, 136, 229, 0.25);
}

@keyframes vpn-frame-loader-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .vpn-frame-loader-spinner { animation: none; }
}

@media (max-width: 820px) {
  .vpn-browser-toolbar {
    /* 6 columns: brand | close | history | address(1fr) | mirror | bug */
    grid-template-columns: minmax(132px, auto) auto auto 1fr auto auto;
  }

  .vpn-nav-form {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .vpn-mirror-field {
    min-width: 116px;
  }

  .vpn-mirror-select {
    max-width: none;
  }

  .vpn-report-panel {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vpn-browser-toolbar {
    grid-template-columns: minmax(0, 1fr) auto auto auto auto; /* 5 cols */
    grid-template-areas:
      "brand history close mirror bug"   /* 5 areas = valid */
      "address address address address address";
    align-items: center;
    gap: 5px;
  }

  .vpn-browser-brand {
    grid-area: brand;
    overflow: hidden;
    min-width: 0;
  }

  /* From the 640px breakpoint down, the brand text MUST shrink/ellipsize
     so a long word can't push the toolbar past viewport width. The 400px
     block already does this; we extend it earlier to cover the awkward
     520–640px range where 5 columns + brand text are tight. */
  .vpn-browser-brand-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
  }

  .vpn-nav-close {
    grid-area: close;
    width: 38px;
    padding: 0;
  }

  .vpn-nav-history {
    grid-area: history;
    display: inline-flex;
    gap: 4px;
    min-width: 0;
  }

  .vpn-nav-history .vpn-nav-button {
    flex: 0 0 36px;
    min-width: 36px;
    min-height: 36px;
    padding: 0;
  }

  .vpn-nav-form {
    grid-area: address;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
  }

  .vpn-mirror-field {
    grid-area: mirror;
    position: relative;
    min-width: 40px;
    width: 40px;
    justify-self: end;
  }

  /* emoji overlay floats above the select */
  .vpn-mirror-field::before {
    content: '🌐';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05em;
    pointer-events: none; /* clicks fall through to select below */
    z-index: 2;
  }

  .vpn-mirror-select {
    -webkit-appearance: none;
    appearance: none;
    width: 40px;
    min-width: 40px;
    max-width: 40px;
    padding: 0;
    color: transparent; /* hide "🌐 Auto" text — emoji shown via ::before */
    position: relative;
    z-index: 1; /* below ::before emoji overlay */
    cursor: pointer;
  }

  .vpn-bug-report {
    grid-area: bug;
    min-width: 36px;
    width: 36px;
    min-height: 36px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .vpn-bug-report-text {
    display: none;
  }

  /* Text hidden at this width → the icon IS the button (was: empty square). */
  .vpn-bug-report-icon {
    display: block;
  }
}

@media (max-width: 520px) {
  .vpn-browser-nav {
    padding:
      calc(6px + env(safe-area-inset-top))
      max(8px, env(safe-area-inset-right))
      6px
      max(8px, env(safe-area-inset-left));
    gap: 6px;
  }

  .vpn-browser-toolbar {
    gap: 4px;
  }

  .vpn-browser-brand-text {
    font-size: 0.88rem;
  }

  .vpn-browser-brand-mark {
    width: 34px;
    height: 34px;
    border-radius: 11px;
  }

  .vpn-nav-button,
  .vpn-nav-go,
  .vpn-mirror-select {
    min-height: 36px;
    border-radius: 11px;
  }

  .vpn-nav-button {
    padding: 0 8px;
  }

  .vpn-nav-form {
    padding: 3px;
    gap: 5px;
    border-radius: 14px;
  }

  .vpn-nav-address {
    padding: 0 6px;
    font-size: 0.95rem;
  }

  .vpn-nav-go {
    min-width: 44px;
    padding: 0 12px;
  }

  .vpn-nav-close {
    width: 36px;
    min-height: 36px;
  }

  .vpn-bug-report {
    min-width: 34px;
    width: 34px;
    min-height: 36px;
  }

  .vpn-mirror-field {
    min-width: 36px;
    width: 36px;
  }

  .vpn-mirror-select {
    width: 36px;
    min-width: 36px;
    max-width: 36px;
    padding: 0;
  }

  .vpn-browser-ad {
    padding: 3px 8px;
    gap: 6px;
  }

  .vpn-report-panel {
    padding: 10px;
  }

  .vpn-report-actions {
    flex-direction: column-reverse;
  }

  .vpn-report-cancel,
  .vpn-report-submit {
    width: 100%;
  }
}

@media (max-width: 400px) {
  .vpn-browser-brand {
    gap: 6px;
  }

  .vpn-browser-brand-text {
    font-size: 0.82rem;
    letter-spacing: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 56px;
  }

  .vpn-nav-history {
    gap: 3px;
  }

  .vpn-nav-button,
  .vpn-nav-go,
  .vpn-mirror-select {
    border-radius: 10px;
  }

  /* UX-7: raise tap-target HEIGHT to 40px on small screens (closer to the 44px
     guideline) — height costs no horizontal room, so the 6-button toolbar
     still fits at 320px while becoming far less mis-tap-prone vertically.
     Widths stay compact to avoid overflow. */
  .vpn-nav-history .vpn-nav-button {
    flex: 0 0 34px;
    min-width: 34px;
    min-height: 40px;
  }

  .vpn-mirror-field,
  .vpn-mirror-select {
    width: 34px;
    min-width: 34px;
    max-width: 34px;
    min-height: 40px;
  }

  .vpn-mirror-select {
    padding: 0;
  }

  .vpn-bug-report {
    min-width: 34px;
    width: 34px;
    min-height: 40px;
  }

  .vpn-nav-close {
    width: 34px;
    min-height: 40px;
  }
}

@media (max-width: 360px) {
  .vpn-browser-toolbar {
    gap: 3px;
  }

  .vpn-browser-brand-text {
    font-size: 0.8rem;
    max-width: none; /* buttons are smaller at 360px so brand has more room */
  }

  .vpn-browser-brand-mark {
    width: 30px;
    height: 30px;
    border-radius: 9px;
  }

  .vpn-nav-history .vpn-nav-button {
    flex: 0 0 30px;
    min-width: 30px;
    min-height: 32px;
  }

  .vpn-mirror-field,
  .vpn-mirror-select {
    width: 30px;
    min-width: 30px;
    max-width: 30px;
  }

  .vpn-mirror-select {
    padding: 0;
  }

  .vpn-bug-report {
    min-width: 30px;
    width: 30px;
    min-height: 32px;
  }

  .vpn-nav-close {
    width: 32px;
    min-height: 32px;
  }
}

/* Compatibility warning banner */
.compat-warning {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  display: flex;
  align-items: center;
  gap: 12px;
  width: calc(100% - 32px);
  max-width: 720px;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: linear-gradient(180deg, rgba(255,255,255,0.10), rgba(255,255,255,0.06));
  box-shadow: 0 10px 40px rgba(0,0,0,0.35);
  backdrop-filter: blur(10px) saturate(120%);
}
.compat-warning .icon {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--orange);
  background: rgba(255, 143, 0, 0.12);
  border: 1px solid rgba(255, 143, 0, 0.2);
}
.compat-warning .content {
  flex: 1;
  min-width: 0;
}
.compat-warning .title {
  font-weight: 700;
  color: var(--orange);
  margin: 0;
  font-size: 0.98rem;
}
.compat-warning .desc {
  margin: 2px 0 0 0;
  font-size: 0.92rem;
  color: rgba(230, 238, 246, 0.85);
}
.compat-warning .actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.compat-warning .dismiss-btn {
  appearance: none;
  background: transparent;
  color: rgba(230, 238, 246, 0.8);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px;
  cursor: pointer;
}
.compat-warning .dismiss-btn:hover {
  background: rgba(255,255,255,0.08);
}

@media (max-width: 480px) {
  .compat-warning { top: 12px; padding: 10px 12px; }
  .compat-warning .desc { font-size: 0.88rem; }
  .page-card { padding: 24px 18px 22px; }
  .page-link-chip { width: 100%; }
}

/* Inline compatibility banner under the search bar */
.compat-inline {
  margin: 14px auto 0 auto;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid var(--border);
  background: linear-gradient(180deg, rgba(255,255,255,0.10), rgba(255,255,255,0.06));
  box-shadow: 0 10px 40px rgba(0,0,0,0.35);
  backdrop-filter: blur(10px) saturate(120%);
  display: flex;
  align-items: center;
  gap: 12px;
  width: calc(100% - 32px);
  max-width: 720px;
}
.compat-inline .icon {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--orange);
  background: rgba(255, 143, 0, 0.12);
  border: 1px solid rgba(255, 143, 0, 0.2);
}
.compat-inline .content {
  flex: 1;
  min-width: 0;
}
.compat-inline .title {
  font-weight: 700;
  color: var(--orange);
  margin: 0;
  font-size: 0.96rem;
  opacity: 0.95;
}
.compat-inline .iconset {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}
.compat-inline .badge {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: rgba(255,255,255,0.06);
}
.compat-inline .brand-circle {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 800;
  font-size: 12px;
  color: #0b1220;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.28);
}
/* Brand approximations */
.brand-chrome { background: conic-gradient(#DB4437 0 25%, #F4B400 0 50%, #0F9D58 0 75%, #4285F4 0); }
.brand-firefox { background: linear-gradient(135deg, #FF7139, #FFB000); }
.brand-vivaldi { background: linear-gradient(135deg, #EF2D5E, #FF6A88); }

.compat-inline .device { font-size: 14px; opacity: 0.9; }

@media (max-width: 480px) {
  .compat-inline { padding: 10px 12px; }
  .compat-inline .title { font-size: 0.9rem; }
}

/* Accessibility helper */
.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,1px,1px);
  white-space: nowrap; border: 0;
}

/* Warning variant for inline banner */
.compat-inline.is-warning {
  position: relative;
  border-color: rgba(255, 143, 0, 0.45);
  background:
    linear-gradient(180deg, rgba(255, 165, 0, 0.14), rgba(255, 143, 0, 0.10));
  box-shadow:
    0 12px 40px rgba(255, 143, 0, 0.18),
    inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.compat-inline.is-warning::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 183, 77, 0.12) 0,
    rgba(255, 183, 77, 0.12) 10px,
    rgba(255, 255, 255, 0.04) 10px,
    rgba(255, 255, 255, 0.04) 20px
  );
  pointer-events: none;
  mix-blend-mode: overlay;
}
.compat-inline.is-warning .icon {
  color: #2b1a00;
  background: rgba(255, 193, 7, 0.9);
  border-color: rgba(255, 183, 77, 0.9);
  width: 36px; height: 36px;
  animation: pulseWarn 1.8s ease-in-out infinite;
  box-shadow: 0 0 0 4px rgba(255, 193, 7, 0.12);
}
@keyframes pulseWarn {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 4px rgba(255,193,7,0.12); }
  50% { transform: scale(1.06); box-shadow: 0 0 0 6px rgba(255,193,7,0.22); }
}

/* F-8: brief, non-intrusive flash when the address field is submitted empty.
   Reuses the pulseWarn animation; the class is added/removed transiently by
   navigateToUrl(). Honours reduced-motion. */
.vpn-input-needs-value {
  animation: pulseWarn 0.4s ease-in-out 1;
}
@media (prefers-reduced-motion: reduce) {
  .vpn-input-needs-value { animation: none; outline: 2px solid rgba(255,193,7,0.6); }
}

/* Current environment badge */
.compat-inline .current {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
}
.compat-inline .current .badge {
  position: relative;
  border-color: rgba(255, 143, 0, 0.45);
  background: rgba(255, 143, 0, 0.14);
}
.compat-inline .current .badge::after {
  content: "!";
  position: absolute;
  top: -8px; right: -8px;
  width: 18px; height: 18px;
  display: grid; place-items: center;
  font-weight: 800; font-size: 12px;
  color: #2b1a00;
  background: #FFC107;
  border: 1px solid rgba(0,0,0,0.12);
  border-radius: 50%;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}

/* Supported set subtler styling */
.compat-inline .supported-label {
  display: flex; align-items: center; gap: 8px; margin-top: 8px;
  opacity: 0.9; font-size: 0.92rem;
}
.compat-inline .supported-set {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px;
}
.compat-inline .supported-set .badge { opacity: 0.85; }

/* Unknown brand fallback */
.brand-unknown { background: linear-gradient(135deg, #9aa4af, #c7d0da); color: #0b1220; }

/* SVG icon sizing inside badges */
.compat-inline .brand-circle img {
  width: 14px;
  height: 14px;
  display: block;
}
.compat-inline .device-icon {
  width: 18px;
  height: 18px;
  display: block;
}

/* WebKit warning specific styles */
.compat-inline .webkit-desc {
  margin: 6px 0 12px 0;
  font-size: 0.92rem;
  color: rgba(230, 238, 246, 0.8);
  line-height: 1.4;
}

/* Download links container */
.download-links {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 12px;
}

/* Modern browser download link cards */
.browser-download-link {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border-radius: 14px;
  text-decoration: none;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid var(--border);
  /* Explicit property list — never `transition: all`. Covers the
     hover-lift, color shift, border highlight, and box-shadow ramp. */
  transition:
    transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  overflow: hidden;
  cursor: pointer;
  touch-action: manipulation;
}
.browser-download-link:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

/* Shimmer effect - keeping this because they love it! */
.browser-download-link::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.25), transparent);
  transition: left 0.5s ease;
  z-index: 1;
}

.browser-download-link:hover::before {
  left: 100%;
}

/* Animated gradient background layer */
.browser-download-link::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  opacity: 0;
  transition: opacity 0.4s ease;
  z-index: 0;
}

.browser-download-link.chrome::after {
  background:
    radial-gradient(circle at 20% 50%, rgba(66, 133, 244, 0.15) 0%, transparent 50%),
    radial-gradient(circle at 80% 50%, rgba(52, 168, 83, 0.12) 0%, transparent 50%);
}

.browser-download-link.firefox::after {
  background:
    radial-gradient(circle at 20% 50%, rgba(255, 149, 0, 0.15) 0%, transparent 50%),
    radial-gradient(circle at 80% 50%, rgba(255, 94, 77, 0.12) 0%, transparent 50%);
}

.browser-download-link:hover::after {
  opacity: 1;
}

/* Chrome themed container */
.browser-download-link.chrome {
  background: linear-gradient(135deg, rgba(66, 133, 244, 0.12), rgba(52, 168, 83, 0.08));
  border: 1px solid rgba(66, 133, 244, 0.25);
  box-shadow:
    0 4px 16px rgba(66, 133, 244, 0.1),
    inset 0 0 0 1px rgba(66, 133, 244, 0.08);
}

.browser-download-link.chrome:hover {
  transform: translateX(4px);
  background: linear-gradient(135deg, rgba(66, 133, 244, 0.2), rgba(52, 168, 83, 0.12));
  border-color: rgba(66, 133, 244, 0.5);
  box-shadow:
    0 8px 32px rgba(66, 133, 244, 0.3),
    0 0 40px rgba(66, 133, 244, 0.15),
    inset 0 0 0 1px rgba(66, 133, 244, 0.2);
}

/* Firefox themed container */
.browser-download-link.firefox {
  background: linear-gradient(135deg, rgba(255, 149, 0, 0.12), rgba(255, 94, 77, 0.08));
  border: 1px solid rgba(255, 149, 0, 0.25);
  box-shadow:
    0 4px 16px rgba(255, 149, 0, 0.1),
    inset 0 0 0 1px rgba(255, 149, 0, 0.08);
}

.browser-download-link.firefox:hover {
  transform: translateX(4px);
  background: linear-gradient(135deg, rgba(255, 149, 0, 0.2), rgba(255, 94, 77, 0.12));
  border-color: rgba(255, 149, 0, 0.5);
  box-shadow:
    0 8px 32px rgba(255, 149, 0, 0.3),
    0 0 40px rgba(255, 149, 0, 0.15),
    inset 0 0 0 1px rgba(255, 149, 0, 0.2);
}

.browser-download-link:active {
  transform: translateX(2px) scale(0.98);
}

/* Browser icon */
.browser-download-link .browser-icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.15);
  border: 1px solid rgba(255, 255, 255, 0.2);
  transition:
    transform 0.3s ease,
    background-color 0.3s ease,
    border-color 0.3s ease;
  position: relative;
  z-index: 2;
}

/* Chrome icon container */
.browser-download-link.chrome .browser-icon {
  background: rgba(66, 133, 244, 0.2);
  border: 1px solid rgba(66, 133, 244, 0.3);
  box-shadow: 0 0 12px rgba(66, 133, 244, 0.15);
}

.browser-download-link.chrome:hover .browser-icon {
  transform: scale(1.1) rotate(2deg);
  background: rgba(66, 133, 244, 0.3);
  border-color: rgba(66, 133, 244, 0.5);
  box-shadow:
    0 4px 20px rgba(66, 133, 244, 0.35),
    0 0 16px rgba(66, 133, 244, 0.25);
}

/* Firefox icon container */
.browser-download-link.firefox .browser-icon {
  background: rgba(255, 149, 0, 0.2);
  border: 1px solid rgba(255, 149, 0, 0.3);
  box-shadow: 0 0 12px rgba(255, 149, 0, 0.15);
}

.browser-download-link.firefox:hover .browser-icon {
  transform: scale(1.1) rotate(-2deg);
  background: rgba(255, 149, 0, 0.3);
  border-color: rgba(255, 149, 0, 0.5);
  box-shadow:
    0 4px 20px rgba(255, 149, 0, 0.35),
    0 0 16px rgba(255, 149, 0, 0.25);
}

.browser-download-link .browser-icon img {
  width: 28px;
  height: 28px;
  display: block;
}

/* Browser info */
.browser-download-link .browser-info {
  flex: 1;
  min-width: 0;
  position: relative;
  z-index: 2;
}

.browser-download-link .browser-name {
  font-weight: 600;
  font-size: 1rem;
  color: var(--text);
  margin-bottom: 2px;
  transition: color 0.3s ease, text-shadow 0.3s ease;
}

.browser-download-link .browser-tagline {
  font-size: 0.85rem;
  color: rgba(230, 238, 246, 0.65);
  transition: color 0.3s ease;
}

/* Chrome text glow on hover */
.browser-download-link.chrome:hover .browser-name {
  color: white;
  text-shadow: 0 0 12px rgba(66, 133, 244, 0.5);
}

.browser-download-link.chrome:hover .browser-tagline {
  color: rgba(230, 238, 246, 0.9);
}

/* Firefox text glow on hover */
.browser-download-link.firefox:hover .browser-name {
  color: white;
  text-shadow: 0 0 12px rgba(255, 149, 0, 0.5);
}

.browser-download-link.firefox:hover .browser-tagline {
  color: rgba(230, 238, 246, 0.9);
}

/* Arrow icon */
.browser-download-link .arrow {
  width: 20px;
  height: 20px;
  color: rgba(230, 238, 246, 0.5);
  flex-shrink: 0;
  transition: color 0.3s ease, transform 0.3s ease;
  position: relative;
  z-index: 2;
}

.browser-download-link:hover .arrow {
  transform: translateX(4px);
}

/* Chrome arrow glow */
.browser-download-link.chrome .arrow {
  color: rgba(66, 133, 244, 0.6);
}

.browser-download-link.chrome:hover .arrow {
  color: #4285F4;
  filter: drop-shadow(0 0 8px rgba(66, 133, 244, 0.6));
}

/* Firefox arrow glow */
.browser-download-link.firefox .arrow {
  color: rgba(255, 149, 0, 0.6);
}

.browser-download-link.firefox:hover .arrow {
  color: #FF9500;
  filter: drop-shadow(0 0 8px rgba(255, 149, 0, 0.6));
}

/* Responsive adjustments */
@media (max-width: 480px) {
  .browser-download-link {
    padding: 12px 14px;
    gap: 12px;
  }

  .browser-download-link .browser-icon {
    width: 40px;
    height: 40px;
  }

  .browser-download-link .browser-icon svg {
    width: 24px;
    height: 24px;
  }

  .browser-download-link .browser-name {
    font-size: 0.95rem;
  }

  .browser-download-link .browser-tagline {
    font-size: 0.82rem;
  }
}

/* Ad zones */

/* Bare `<ins>` ad slots default to `display: inline` with 0×0 box until the
   ad-provider script injects an iframe. Safari/Mac gates AdProvider serving
   on a non-zero slot rect and skips serving entirely if the slot is empty
   at scan time. Force block layout with a reservation matching each zone's
   PRIMARY format so the provider sees a slot it can fill:
     - e33 (MFPI, zone 5848626): 300×250 primary; provider also serves 300×100
       and 300×500 into the same slot — 300×250 is the safest middle ground
     - e35 (DFPI, zone 5848628): 300×600 skyscraper primary
   100px (the prior fixpack73 value) was too small for Safari's format
   matcher and ALSO left a visible gap when the slot didn't fill.

   `:empty` collapses the reservation back to zero when the provider never
   injected (e.g. blocked by uBO or the provider script failed to load) —
   no wasted whitespace on the page.
 */
/* Top-document ExoClick `<ins>` zones (fp161, reverted from the sandboxed
   ad-frame.html). Each `<ins>` needs explicit dimensions so the provider scans
   the slot. The `:empty` rule below is the KEY no-flicker collapse: an `<ins>`
   with no injected child collapses to zero height NATIVELY (no JS, no
   visible-then-hide flash — the fp159 mistake). Once the provider injects a
   creative the `<ins>` is no longer :empty and expands to its box. */
ins.eas6a97888e33 {
  display: block;
  width: 300px;
  height: 250px;
  margin: 1rem auto;
  text-align: center;
}
ins.eas6a97888e35 {
  display: block;
  width: 300px;
  height: 600px;
  margin: 1rem auto;
  text-align: center;
}
ins.eas6a97888e10 {
  display: block;
  width: 300px;
  height: 100px;
  margin: 0 auto;
}
ins.eas6a97888e38 {
  display: block;
  width: 728px;
  height: 90px;
  margin: 0 auto;
}
ins.eas6a97888e33:empty,
ins.eas6a97888e35:empty,
ins.eas6a97888e10:empty,
ins.eas6a97888e38:empty {
  /* No ad injected — collapse the reservation so the page carries no permanent
     gap (and no white blob). Visible only on no-fill / ad-blocked loads. */
  height: 0;
  margin: 0;
}

/* The in-nav ad (serveProxyNavAd) still renders in a sandboxed ad-frame.html
   iframe, so keep its frame sizing. */
.ad-frame {
  display: block;
  border: 0;
  margin: 1rem auto;
  max-width: 100%;
}
.ad-frame-300x250 { width: 300px; height: 250px; }
.ad-frame-300x600 { width: 300px; height: 600px; }
.ad-frame-300x100 { width: 300px; height: 100px; margin: 0 auto; }
.ad-frame-728x90  { width: 728px; height: 90px; margin: 0 auto; }

.ad-mobile-banner {
  display: none;
  text-align: center;
  margin: 1rem auto;
  width: 100%;
  overflow: hidden;
}

.ad-desktop-footer {
  display: block;
  text-align: center;
  /* Trim the margin — prior 1rem stacked with `.features-section` bottom
     margin yielding a too-tall gap below the ad. 0.25rem auto keeps the
     centering and a hairline separation from the footer below. */
  margin: 0.25rem auto;
  width: 100%;
  overflow: hidden;
}

/* Ad-revenue plan 2026-07-13 (D2): switch mobile↔footer at 767/768px, not 480.
   The footer <ins> is a fixed 728px box in a 100%-width, overflow:hidden
   container; below ~760px viewport (728 + 32px body padding) the 728 creative
   is clipped left-aligned — the exact bug fp188 fixed for the nav slot, never
   ported here. So 481–759px phones (a big slice of the 58%-mobile audience)
   saw a clipped footer AND lost the 300×100 they should get. Show the mobile
   300×100 up to 767px; show the 728 footer only from 768px (768−32=736 ≥ 728,
   fits uncapped). A media query suffices — .ad-desktop-footer is a direct body
   child so viewport≈container. */
@media (max-width: 767px) {
  .ad-mobile-banner {
    display: block;
  }
  .ad-desktop-footer {
    display: none;
  }
}

/* ===========================================
   BROWSING STATE - Hide home, keep proxy nav + iframe
   =========================================== */

body.browsing {
  overflow: hidden;
  padding: 0;
  background: #111;
}

/* 🚨 THIS RULE HID THE ONLY TWO SURFACES THAT EXPLAIN A FAILURE (fixed
   2026-08-21, TELEMETRY_FINDINGS.md BUGREPORT-9D-1 finding 9).
   Both failure toasts are appended to document.body, so they are direct
   children here and this rule killed them — in browse mode, which is the ONLY
   state either one is ever shown in. `#vpn-nav-error-toast` could therefore
   NEVER be seen at all: its show-condition (`wasBrowsing`) IS its
   hide-condition. Measured on production: display:none, 0x0, pointer-events:
   none, while the DOM carried its text and its Retry button.
   Consequence: after a failed navigation the user got no explanation, an EMPTY
   report box (the prefill only runs from the invisible toast's button), and a
   pulsing 34px unlabelled icon as the sole affordance — so they typed their
   intended DESTINATION into the report box. 45% of real-user reports are
   polluted that way, and 4 of 5 such sessions never reached the host they named.

   🪤 DO NOT "fix" this with a new override rule. This selector's three
   :not(#id) negations give it specificity (3,1,1); a
   `body.browsing > #vpn-nav-error-toast { display:flex !important }` override is
   (1,1,0) and was MEASURED to still lose. The rule must be EDITED.
   🪤 DO NOT re-parent the toasts into #vpn-browser-nav either: the nav has
   `backdrop-filter`, which makes it a containing block for position:fixed
   descendants, plus overflow:hidden. Measured: the re-parented toast landed at
   y=183 against a 163px nav and was clipped.

   Any future overlay that must survive browse mode gets `.vpn-browsing-overlay`
   and is covered by the single negation below — so nobody has to rediscover
   this. */
body.browsing > *:not(#vpn-browser-nav):not(#uv-frame):not(#vpn-frame-loader):not(script):not(.vpn-browsing-overlay) {
  display: none !important;
  visibility: hidden !important;
  pointer-events: none !important;
}

/* Target ad injections specifically while preserving the nav ad slot. */
/* 🔑 `:not(.vpn-browsing-overlay)` on every branch (added 2026-08-24 for the
   In-Page Push slot). The comment above says it outright: an overlay that must
   survive browse mode gets that class, and the rule must be EDITED rather than
   fought with an override. Editing here is also what keeps ExoClick's OWN inline
   positioning intact — an un-nuking `position:…!important` in a stylesheet beats a
   renderer's inline style, so it would move the notification somewhere we chose
   blind. Excluding the node instead leaves the renderer in charge of its own box. */
body.browsing [class^="eas6a97888e"]:not(.vpn-browsing-overlay),
body.browsing [class*="eas6a97888e"]:not(.vpn-browsing-overlay),
body.browsing ins[data-zoneid]:not(.vpn-browsing-overlay),
body.browsing [id*="exo"]:not(.vpn-browsing-overlay),
body.browsing [id*="adprovider"]:not(.vpn-browsing-overlay),
body.browsing [class*="exo"]:not(.vpn-browsing-overlay),
body.browsing [class*="adprovider"]:not(.vpn-browsing-overlay) {
  display: none !important;
  visibility: hidden !important;
  opacity: 0 !important;
  pointer-events: none !important;
  position: absolute !important;
  left: -9999px !important;
  width: 0 !important;
  height: 0 !important;
}

/* PHASE 2 — the In-Page Push slot must survive browse mode.
   The rule above hides `ins[data-zoneid]` and anything matching the exo/adprovider
   markers ANYWHERE under body.browsing, which is why the 2026-07-13 plan's
   browse-mode interstitial never fired. This carve-out is the exemption for the push
   slot and for the notification ExoClick paints itself (index.js tags those nodes
   with .vpn-browsing-overlay, since their ids are unpredictable).
   🪤 Do NOT "simplify" this by widening the sibling carve-out to all of body: that
   would re-expose every ad node the nuke exists to hide during a browse.
   The slot itself is a 0×0 anchor — the renderer positions its own notification, so
   this must NOT impose width/height/position on the injected node. */
.vpn-push-slot {
  position: fixed;
  inset: auto auto 0 0;
  width: 0;
  height: 0;
  z-index: 2147483000;
}

/* DESCENDANTS of a surviving node. The top-level notification is exempted by the
   `:not(.vpn-browsing-overlay)` edit above and keeps its own inline geometry; its
   children can still match `[class*="exo"]`, so they are un-hidden here.
   Deliberately restores VISIBILITY ONLY — no position, no width, no height. Imposing
   geometry on markup we do not own is the fp166..fp172 trap, and there is no way to
   verify the result from a datacenter IP (no creative is ever served to one). */
body.browsing .vpn-browsing-overlay [class^="eas6a97888e"],
body.browsing .vpn-browsing-overlay [class*="eas6a97888e"],
body.browsing .vpn-browsing-overlay ins[data-zoneid],
body.browsing .vpn-browsing-overlay [id*="exo"],
body.browsing .vpn-browsing-overlay [class*="exo"],
body.browsing .vpn-browsing-overlay [class*="adprovider"] {
  display: revert !important;
  visibility: visible !important;
  opacity: 1 !important;
  pointer-events: auto !important;
  position: revert !important;
  left: revert !important;
  width: revert !important;
  height: revert !important;
}

body.browsing #vpn-browser-ad [class^="eas6a97888e"],
body.browsing #vpn-browser-ad [class*="eas6a97888e"],
body.browsing #vpn-browser-ad ins[data-zoneid],
body.browsing #vpn-browser-ad [id*="exo"],
body.browsing #vpn-browser-ad [id*="adprovider"],
body.browsing #vpn-browser-ad [class*="exo"],
body.browsing #vpn-browser-ad [class*="adprovider"] {
  display: inline-block !important;
  visibility: visible !important;
  opacity: 1 !important;
  pointer-events: auto !important;
  position: static !important;
  left: auto !important;
  width: auto !important;
  height: auto !important;
}

.desc,
.compat-inline,
.compat-warning {
  width: min(100%, 720px);
}

.desc {
  margin: 12px auto 0;
}

@media (max-width: 768px) {
  .logo-section {
    margin-top: 8px;
  }

  .vpnfree {
    font-size: clamp(2rem, 9vw, 2.6rem);
    letter-spacing: 1px;
  }

  .slogan {
    font-size: 1rem;
    text-align: center;
  }

  .features-section {
    margin-top: 24px;
    padding: 24px 18px 20px;
    border-radius: 16px;
  }

  .features-section h2 {
    font-size: 1.35rem;
  }

  .features-section .subtitle,
  .features-list li,
  .catch {
    font-size: 0.98rem;
  }

  .footer-links {
    gap: 8px 14px;
  }

  .footer-links a {
    margin: 0;
  }
}

@media (max-width: 640px) {
  .main-form {
    margin-top: 24px;
  }

  .input-group {
    flex-wrap: wrap;
    align-items: stretch;
    gap: 8px;
    border-radius: 24px;
    padding: 10px;
  }

  .input-leading {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0 4px 0 8px;
  }

  #uv-address {
    flex: 1 1 140px;
    padding: 12px 2px;
    font-size: 1rem;
  }

  .go-btn {
    width: 100%;
    justify-content: center;
  }

  .compat-inline,
  .compat-warning {
    padding: 12px;
    align-items: flex-start;
  }

  .compat-inline .icon,
  .compat-warning .icon {
    flex-shrink: 0;
  }

  .features-list li,
  .catch {
    text-align: left;
  }

  .footer-bottom {
    flex-direction: column;
    gap: 8px;
  }
}

@media (max-width: 480px) {
  body {
    padding:
      max(12px, env(safe-area-inset-top))
      max(12px, env(safe-area-inset-right))
      max(16px, env(safe-area-inset-bottom))
      max(12px, env(safe-area-inset-left));
  }

  .bird-logo {
    width: 68px;
    height: 68px;
  }

  .bird-logo-shell {
    width: 76px;
    height: 76px;
  }

  .vpnfree {
    font-size: 1.9rem;
  }

  .features-section {
    padding: 20px 16px 18px;
  }

  .features-section h2 {
    font-size: 1.2rem;
  }

  .browser-download-link {
    gap: 10px;
  }

  .browser-download-link:hover,
  .browser-download-link:active {
    transform: none;
  }
}

/* ============================================================
 * R2 — Proactive nudge: Bug-button pulse + dismissible toast
 * Honors prefers-reduced-motion. Toast bottom-centered, slides in
 * from below with a brief opacity fade; auto-dismisses after 8s
 * (controlled by JS — CSS just transitions the visible state).
 * ============================================================ */
@keyframes vpn-bug-report-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(255, 143, 0, 0.55); }
  50%  { box-shadow: 0 0 0 6px rgba(255, 143, 0, 0); }
  100% { box-shadow: 0 0 0 0 rgba(255, 143, 0, 0); }
}
.vpn-bug-report.vpn-bug-report-pulse {
  animation: vpn-bug-report-pulse 1.4s ease-out 0s 5;
}
@media (prefers-reduced-motion: reduce) {
  .vpn-bug-report.vpn-bug-report-pulse {
    animation: none;
    box-shadow: 0 0 0 2px rgba(255, 143, 0, 0.5);
  }
}

.vpn-nudge-toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translate(-50%, calc(100% + 24px));
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: rgba(20, 20, 24, 0.96);
  color: #f5f5f7;
  border: 1px solid rgba(255, 143, 0, 0.45);
  border-radius: 6px;
  font: 13px/1.3 system-ui, -apple-system, "Segoe UI", sans-serif;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.22s ease-out, opacity 0.22s ease-out;
  z-index: 2147483646;
  max-width: min(560px, calc(100vw - 32px));
}
.vpn-nudge-toast.vpn-nudge-toast-visible {
  transform: translate(-50%, 0);
  opacity: 1;
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .vpn-nudge-toast,
  .vpn-nudge-toast.vpn-nudge-toast-visible {
    transition: opacity 0.15s linear;
    transform: translate(-50%, 0);
  }
}
.vpn-nudge-toast-msg {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* On a phone the restored toast otherwise reads "That page didn't load. Tell us
   w…" — the ellipsis eats the actionable half of the sentence. Mechanical fix
   only; the copy itself is tester-owned. */
@media (max-width: 480px) {
  .vpn-nudge-toast-msg {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
}
.vpn-nudge-toast-action {
  background: rgba(255, 143, 0, 0.18);
  color: #fff;
  border: 1px solid rgba(255, 143, 0, 0.6);
  padding: 4px 10px;
  border-radius: 4px;
  font: inherit;
  cursor: pointer;
}
.vpn-nudge-toast-action:hover { background: rgba(255, 143, 0, 0.28); }
.vpn-nudge-toast-dismiss {
  background: transparent;
  color: rgba(255, 255, 255, 0.7);
  border: none;
  padding: 2px 6px;
  font: 14px/1 system-ui;
  cursor: pointer;
}
.vpn-nudge-toast-dismiss:hover { color: #fff; }

/* UX-3: in-session navigation-error toast. Same fixed/high-z pattern as the
   nudge toast so it sits ABOVE the full-viewport iframe (z 2147483000) — the
   landing-page #uv-error region is occluded while browsing. Error accent. */
.vpn-nav-error-toast {
  position: fixed;
  left: 50%;
  /* Sit clearly BELOW the proxy nav bar (which has the max z-index) and above
     the iframe, so it's never occluded by either. */
  top: calc(var(--proxy-nav-height, 0px) + 20px);
  transform: translate(-50%, -16px);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: rgba(20, 20, 24, 0.97);
  color: #f5f5f7;
  border: 1px solid rgba(255, 99, 99, 0.55);
  border-radius: var(--r-sm);
  font: 13px/1.35 system-ui, -apple-system, "Segoe UI", sans-serif;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.2s ease-out, opacity 0.2s ease-out;
  z-index: 2147483646;
  max-width: min(560px, calc(100vw - 32px));
}
.vpn-nav-error-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .vpn-nav-error-toast { transition: none; }
}
.vpn-nav-error-toast-msg { flex: 1 1 auto; }
.vpn-nav-error-toast-action {
  flex: 0 0 auto;
  background: rgba(255, 99, 99, 0.18);
  color: #ffd9d9;
  border: 1px solid rgba(255, 99, 99, 0.5);
  border-radius: 6px;
  padding: 5px 12px;
  min-height: 32px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.vpn-nav-error-toast-action:hover { background: rgba(255, 99, 99, 0.3); }
.vpn-nav-error-toast-close {
  flex: 0 0 auto;
  background: transparent;
  color: rgba(255, 255, 255, 0.7);
  border: none;
  padding: 2px 6px;
  font: 14px/1 system-ui;
  cursor: pointer;
}
.vpn-nav-error-toast-close:hover { color: #fff; }

/* ═══════════════════════════════════════════════════════════════════════
   NEUTRAL SHELL — new-design branch only.
   Appended as a scoped override block rather than edited through the 2,592
   lines above: this branch is a staging variant, and a last-wins block keeps
   the diff readable and trivially revertible. Same house palette throughout —
   only the pattern changes.
   ═══════════════════════════════════════════════════════════════════════ */

/* the hero is a row now, so the page is not top-heavy */
.nd-brandrow { width: 100%; max-width: 560px; margin: 0 auto; }
.nd-brand {
  display: flex; align-items: center; gap: 8px; margin: 0;
  font-size: 17px; font-weight: 700; letter-spacing: -.02em; line-height: 1;
}
.nd-brand .vpnfree {
  /* COLOUR RESTORED (2026-08-12, owner ask: bring back the old colourful logo).
     ⚠️ The flat-white wordmark this replaces was itself a fix, and the reason it
     existed still holds: production's SOFT blue→orange SWEEP passes through a
     desaturated grey-brown midpoint, which is what made it look dirty at any
     size below ~24px — and this shell renders the wordmark at 17–19px on every
     phone. So the colour comes back as a HARD TWO-TONE STOP AT THE DOT rather
     than a sweep: `VPN` is solid blue, `.free` is solid orange, and there is no
     muddy middle left to degrade. It holds at 19px and at 40px.
     🪤 Do NOT "restore the real gradient" here — that reintroduces exactly the
     defect the flat version was papering over.
     Contrast measured against the shell background (#0d1421): blue 5.1:1,
     orange far higher, so BOTH halves clear AA for normal text at every
     shipped size — this is not relying on the large-text 3:1 allowance. */
  background: linear-gradient(90deg, var(--blue) 0 46%, var(--orange) 54% 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  font-size: 17px; font-weight: 700;
}

/* ── THE MARK IS TILED (2026-08-12) ──────────────────────────────────────
   The wing used to float bare on the page background, where a thin white glyph
   with a 2px stroke has nothing to sit against and reads as a smear once the
   hero scales it up. Production solves that with an 88px translucent CIRCLE.
   This shell is built out of rounded RECTANGLES — the field, the cards and the
   chips are all --r-sm/--r-md — so the same lockup is set in a rounded SQUARE
   instead, and the radius tracks the tile size (~28% at every breakpoint) so it
   reads as one shape rather than three different ones.
   🪤 The gradient is FULL-STRENGTH on purpose, NOT production's rgba(.5/.35)
   wash: at low opacity over this navy background the orange half turns brown —
   the same "murky card" defect fixed in fd5a2c2. Verified against all four
   candidates on the live page before choosing this one.
   🪤 The mid stop keeps the tile mostly BLUE with orange only in the corner.
   A 50/50 tile competes with the orange Go button, which is this design's
   primary action and must stay the loudest thing on the page. */
.nd-mark-tile {
  flex: none; display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 10px;
  background: linear-gradient(135deg, var(--blue) 0%, #2b7fd4 45%, var(--orange) 100%);
  box-shadow: 0 12px 34px rgba(30, 136, 229, .30), inset 0 0 0 1px rgba(255, 255, 255, .22);
}
/* percentage, not px: ONE rule scales the glyph at every breakpoint, so a tile
   resize can never leave the wing mis-scaled inside it (the class of bug that
   produced the fp188 white gap — a fixed child in a fluid box).
   72% and not 62% (2026-08-12, owner: the wing read small): the wing's own
   artwork carries whitespace inside its 64×64 viewBox — the painted glyph is
   ~48 units wide and only ~32 tall — so a percentage of the tile understates how
   big the mark LOOKS by roughly a quarter. 72% keeps a visible margin inside the
   corner radius while giving the glyph presence; the tile is unchanged, so
   nothing around it reflows. */
.nd-mark-tile .nd-mark { width: 72%; height: 72%; display: block; }
.nd-mark { color: var(--blue); flex: none; }

/* ── the field: ONE border. The old .input-group.glass nested a bordered
      pill inside a bordered pill, which is what made it read as two controls. */
/* the base rule is `display:flex` with no direction, i.e. a ROW — the label,
   field, Go and hint were being laid out side by side. Declaring direction is
   the whole fix; an append-only override only wins on properties it states. */
.main-form {
  display: flex; flex-direction: column; align-items: stretch;
  width: 100%; max-width: 560px; margin: 24px auto 0;
}
.nd-label, .nd-hint { display: block; width: 100%; }
.nd-label { font-size: 13px; color: var(--white-30); margin: 0 0 9px; text-align: left; }
.input-group.nd-field {
  display: flex; align-items: center; gap: 8px;
  background: rgba(255,255,255,.05);
  /* 🔑 NOT `var(--border)` (rgba(255,255,255,.18)), which composites to 1.71:1
     against --bg — under the WCAG 3:1 floor for a component boundary. The field
     read as "no box at all" while Go carries a 10.85:1 orange gradient, so a
     stranger's eye lands on the button and 64% of Go taps arrive with the field
     never touched. .34 alpha composites to ~3.1:1. Scoped to this field on
     purpose: --border is shared with many components and is not being restyled. */
  border: 1.5px solid rgba(255,255,255,.34); border-radius: var(--r-md);
  padding: 13px 13px; box-shadow: none;
}
.input-group.nd-field:focus-within {
  border-color: var(--blue); box-shadow: 0 0 0 3px rgba(30,136,229,.16);
}
.input-group.nd-field #uv-address {
  flex: 1; min-width: 0; border: 0; background: transparent; padding: 0;
  /* 44px is the tap-target floor; the input's own box measured 40. This grows
     the field by 4px STATICALLY (from first paint), which is not the load-time
     CTA jump the arm-B work fixed — that came from the ad slot filling. */
  min-height: 44px;
  font-size: 16px; /* 16px min: anything smaller triggers iOS zoom-on-focus */
  color: var(--text); border-radius: 0; box-shadow: none;
}
/* 🪤 SCOPED TO `:not(:focus-visible)` ON PURPOSE. As `:focus` it also matched
   whenever `:focus-visible` did, and at higher specificity — so it silently killed
   the `#uv-address:focus-visible` outline at index.css:391, whose own comment says
   it exists "as a fallback for AT that targets the input". The field's
   `:focus-within` ring is the intended visual, but a keyboard user on assistive
   tech had no ring at all. Now the ring is suppressed only for mouse/touch focus,
   which is what was wanted. */
.input-group.nd-field #uv-address:focus:not(:focus-visible) { outline: none; box-shadow: none; }
/* 🪤 THIS USED TO BE A BORDERED PILL, 59x32, AT THE RIGHT-HAND END OF THE FIELD —
   which is the canonical submit position, and is EXACTLY where arm A puts its Go
   button at >=700px. So this design put a SECONDARY action in the primary
   action's slot, on a field whose primary action had been moved to a separate
   block below. Go's share of phone taps is 30.8% here against arm A's 44.0%.
   ✅ De-buttoned: no border, no filled background, accent text. It reads as a
   secondary inline affordance rather than "the small button at the end of the
   search box", so it stops borrowing the submit slot's meaning.
   ✅ min-height 32px -> 44px: it was the second-smallest target on the page and
   below the 44px minimum, 8px from the input's own text.
   ⛔ DO NOT DELETE IT. Visitors who press it go on to press Go at 15.66% against
   3.17% for those who do not — it is positively selected, i.e. the people who
   paste are the people who convert. It is also Android-only (rendered only when
   the Clipboard API exists), so it is on the surface ~83% of this audience uses.
   🪤 Tab order is deliberately NOT changed. It is input -> paste -> Go, which the
   audit flagged, but the alternatives are worse: moving it after Go in the DOM
   breaks its visual position inside the field (flex `order` does not affect tab
   order), and `tabindex="-1"` would make it keyboard-unreachable. Enter submits
   the form from the input regardless, so the keyboard path to Go is unaffected. */
.nd-paste {
  flex: none; font: inherit; font-size: 11px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--blue);
  background: none; border: 0;
  border-radius: var(--r-sm); padding: 7px 10px; cursor: pointer;
  min-height: 44px;
  touch-action: manipulation;
}
.nd-paste:hover { background: rgba(255,255,255,.10); }
.nd-paste:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* ── C7 hygiene: the 300ms double-tap-zoom delay ──────────────────────────
   `touch-action: manipulation` was set on Go and the language select only, so a
   tap on the ADDRESS FIELD or the server select paid the browser's ~300ms
   double-tap-zoom wait. Both arms have this; fixed here because this design puts
   both controls in the first viewport on every phone. */
.input-group.nd-field #uv-address,
.edge-picker.nd-srv .edge-picker-select { touch-action: manipulation; }

/* ── Go: full width, and visibly inert until the field validates ─────── */
.go-btn.nd-go {
  display: flex; align-items: center; justify-content: center;
  width: 100%; margin-top: 10px; padding: 15px;
  border-radius: var(--r-md); font-size: 16px; font-weight: 700;
  background: linear-gradient(180deg, #ffa726, #ffca4a); color: #241300;
  min-height: 48px;
}
/* Idle state — the field is empty, so there is nothing to submit YET.
   🪤 NO `pointer-events: none` here, and the press must NOT be blocked.
   An empty-field Go press is a real, answerable action: index.js focuses the
   input, pulses it (`vpn-input-needs-value`) and counts `landingEngage.goEmpty`
   — our single best landing signal. Blocking the pointer made that press a
   totally silent no-op AND zeroed the counter (measured 2026-08-07: goEmpty
   0 → 0 on new-design vs 0 → 1 on production for the same click).
   `pointer-events: none` still applies via `.go-btn[aria-disabled="true"]`,
   which now means ONLY "a navigation is in flight" — fp219's double-tap guard,
   whose whole purpose is to stop a second press discarding the first load. */
/* 🎯 ABTEST-8 (2026-08-23): this state is now a VISUAL NO-OP, deliberately.
   It used to paint the CTA inert — `rgba(255,255,255,.10)` background,
   `--white-30` text, `box-shadow: none` — and that halved the number of visitors
   who touched Go at all: apex-scoped, arm B 10.67% vs arm A's 22.16%, a 2.23x
   gap that had sat there since the 50% rung began.

   The rule was ALWAYS purely cosmetic: no `pointer-events`, no `disabled`, so the
   button was fully clickable the entire time it looked dead. It did buy something
   real — it suppressed futile empty-field clicks, which is why arm B's
   click -> attempt was 72.1% against arm A's 55.6% — but the trade was heavily
   net-negative: 22.16% x 72.1% = 16.0% projected pressed-Go against arm A's actual
   12.33%.

   ⚠️ THAT 16.0% IS ARITHMETIC ON TWO MEASURED RATES, NOT AN OUTCOME. Expect arm B's
   click -> attempt to fall back toward arm A's 55.6% now that empty-field clicks are
   no longer discouraged; the hypothesis is that TOTAL pressed-Go still rises. If
   pressed-Go does not improve, the affordance was not the cause and the remaining
   suspect is the Go REPOSITION (it also moved out of the input group to a full-width
   button below the field with `.nd-hint` between them) — the two shipped together
   and this change is what separates them.

   🪤 Neutralised rather than deleted, and the class is still toggled by
   `syncGoState()`: it stays a usable state hook, and leaving the selector here with
   this comment is what stops the dimming being "restored" as a tidy-up.
   🔑 "Needs input" is still communicated, and better: pressing Go with an empty field
   focuses the input and pulses it (`vpn-input-needs-value`) and increments
   `landingEngage.goEmpty`. A dead-looking button was never carrying that message —
   the pulse is, and it fires only when the user actually asks for it. */
.go-btn.nd-go.is-idle {
  /* Inherit the ready state exactly. A *subtly* dimmed CTA would reintroduce the
     same confound in miniature and leave us unable to tell whether the residual
     dimming still suppresses, so this is a clean on/off test of the affordance. */
  background: linear-gradient(180deg, #ffa726, #ffca4a);
  color: #241300;
}
.nd-hint {
  font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--white-30); margin: 10px 0 0; text-align: center;
}

/* ── server row: chrome around the native select ─────────────────────── */
.edge-picker.nd-srv {
  display: flex; align-items: center; gap: 9px;
  width: 100%; max-width: 560px; margin: 14px auto 0;
  background: rgba(255,255,255,.05); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: 4px 13px; position: relative;
}
.nd-srv-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--blue); flex: none; }
.edge-picker-select.nd-srv-select {
  flex: 1; min-width: 0; appearance: none; -webkit-appearance: none;
  background: transparent; border: 0; box-shadow: none;
  color: var(--text); font-size: 14px; padding: 11px 0; cursor: pointer;
}
.edge-picker-select.nd-srv-select:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.nd-srv-change {
  flex: none; font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--blue); pointer-events: none;
}

/* ── footer: the links are the point, so they get room ───────────────── */
footer .footer-links { gap: 8px 16px; flex-wrap: wrap; }
footer .footer-links a { font-size: 13px; }

/* ── the footer links were never a real tap target ────────────────────────
   📊 Measured 2026-08-30 at 390px across all 13 locales: every footer link was
   30px TALL, and the Community link ran from 26px wide (zh) through 34 (fa),
   43 (ar), 48 (tr) up to 74 (pl) — all under the 44x44 floor, with 16px to the
   next link. So the hit area shrank in exactly the languages whose words are
   short, which is why the report was "with SOME languages I cannot click it".
   The link itself was never broken: href, hit test and click all verified good
   in 13/13 locales, on desktop and phone, with ad scripts blocked AND live.
   This is the geometry, not the markup.
   🪤 HEIGHT is the dimension that was violated in EVERY language (30px); width
   only binds on the short-word locales, which are also the narrowest rows — so
   min-width costs the least room exactly where it is applied, and the row does
   not start wrapping. Wrapping matters: it is the one arrangement that could put
   Community directly under Home, whose a[href="/"] click-hijack returns the user
   to the landing page.
   ⚠️ Growing the footer is not free — the body is a flex column and this is the
   family of change that moved arm B's CTA 45-51px mid-load. Go's position was
   traced at 375/390/768/1280 before and after; it does not move. */
.footer-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  padding: 0 4px;
}

/* ── reserved ad space stays, so the layout is honest about it ─────────
   🚨 THIS RULE USED TO SAY `min-height: 0`, WHICH IS THE OPPOSITE OF ITS OWN
   COMMENT, AND IT COST ~5.8pp OF THE PRIMARY METRIC ON PHONES.
   Measured in a real browser on this design: the ad slot goes h=0 → h=100 at
   t≈600ms when the creative lands, and because the body flex column has TWO
   `margin-top: auto` (footer at index.css:439 and `.nd-brandrow` below), the
   free space is split 50/50 — so a 100px late height change moves the WHOLE CTA
   STACK UP BY 50px:
       t=429-536ms  .nd-brandrow margin-top 101.3px  Go at y=273.3-321.3
       t=619ms      .nd-brandrow margin-top  51.3px  Go at y=222.8-270.8
   Arm A over the same load moves Go 248 → 249px. One pixel, because it has only
   the footer's auto and is therefore top-anchored.
   🔑 A thumb tap is ballistic — committed before the page settles. A tap aimed at
   the pre-shift Go rect lands on the inert hint, a gap, or the SERVER DROPDOWN;
   a tap aimed at the pre-shift server pill lands in bare background. That is
   exactly the measured signature: dead taps clustering at page_y p50=426 and Go's
   share of phone taps falling 44.0% → 30.8%, while visitors who DO land on Go
   convert BETTER here than on arm A (43.67% vs 31.77%). The design was never the
   problem; the moving target was. Desktop is unaffected (p=0.87) because a mouse
   user sees the shift and re-targets.
   ✅ Reserving the real creative height removes the late change at the source.
   🪤 Do NOT "fix" this by deleting `.nd-brandrow { margin-top: auto }` instead —
   that reverts to the hard top-alignment the composition block below calls the
   bug, and it discards the optical centring that was a deliberate owner-facing
   decision. Reserve the box; keep both autos.
   ⚖️ Cost, accepted at the time: when a creative does not fill, the slot holds
   blank space instead of collapsing. Heights are the real creative sizes —
   300×100 mobile (shown ≤767px) and 728×90 footer (shown ≥768px).

   ❌ SUPERSEDED 2026-08-31 — THE RESERVATION IS BACK TO ZERO. It was never the
   load-bearing part, and a later change of my own removed what was left of its
   job. Two other mechanisms already hold the invariant: below 1024px the BOUNDED
   `.nd-brandrow` top margin (the other half of the same fp247 fix), and at every
   width the document now being TALLER THAN THE VIEWPORT since `.features-section`
   was restored in fp248 — an overflowing column has no free space, so both
   `margin-top: auto` resolve to 0. Proven by mutation in
   `e2e/tests/cta-stability.spec.ts`: unbinding that margin reproduces the full
   32-162px shift WITH this reservation still in place, so the reservation was
   not what was stopping it. Measured on production with the ad providers
   `page.route`-aborted, so every row is the no-fill case:

       360x640 · 375x667 · 375x740 · 390x844 · 412x915 · 430x932 (phones)
       768x1024 (tablet) · 1280x800 · 1440x900 (desktop)
           overflows=true   goMovesIfCollapsed = 0px   blank box 100-250px
       1280x1400 (taller than any real client)
           overflows=false  goMovesIfCollapsed = -22px

   Nine of ten viewports: the reservation prevents ZERO movement and costs a
   100-250px hole. The tenth is negative — collapsing moves Go DOWN. So it buys
   nothing anywhere and, at a ~14% no-fill rate (2,206 mobile impressions vs
   ~2,570 phone sessions on 2026-08-31), roughly one load in seven rendered a
   large empty box under the CTA. The owner reported it as the ad being broken.

   🪤 DO NOT re-raise these to 100/90 on the strength of the note above without
   RE-MEASURING `goMovesIfCollapsed`. The reservation is correct exactly when the
   page FITS the viewport; it is dead weight when the page scrolls. Whichever way
   that flips, the number to look at is the shift, never the min-height.
   🔑 The invariant — Go must not move when the ad fills — is now guarded by
   `e2e/tests/cta-stability.spec.ts`, which asserts the SHIFT rather than this
   value, so a future change that makes the page fit again fails there and tells
   the next person to reserve the box once more. */
.ad-mobile-banner  { min-height: 0; }
.ad-desktop-footer { min-height: 0; }

@media (min-width: 900px) {
  .nd-brandrow, .main-form, .edge-picker.nd-srv { max-width: 560px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   1 · RESPONSIVE — the first pass capped everything at 560px and then set
   the SAME 560px in the ≥900 media query, so a 1440px desktop rendered the
   phone layout centred in a void. The fix is compositional: the brand row
   and footer span the PAGE, only the field keeps a readable measure.
   ═══════════════════════════════════════════════════════════════════════ */
@media (min-width: 700px) {
  .nd-brandrow { max-width: none; width: 100%; }
  .main-form   { max-width: 600px; }
  .edge-picker.nd-srv { max-width: 600px; }
  .nd-label    { font-size: 14px; }
  .input-group.nd-field { padding: 15px 15px; }
  .input-group.nd-field #uv-address { font-size: 17px; }
  .go-btn.nd-go { padding: 16px; font-size: 17px; }
  footer { width: 100%; max-width: none; }
}
@media (min-width: 1100px) {
  /* desktop is not a tall phone: the brand sits at the page edge, the field
     block is optically centred, and the type steps up with the viewport */
  .nd-brand { font-size: 19px; }
  /* the mark size for ≥1100 lives in the DESKTOP HERO block near the end of
     this file, which is later in source and therefore wins. Nothing is set
     here on purpose — a second declaration would just be dead weight that
     looks authoritative. */
  .main-form, .edge-picker.nd-srv { max-width: 640px; }
  .input-group.nd-field #uv-address { font-size: 18px; }
  .nd-label { font-size: 15px; }
  footer .footer-links a { font-size: 14px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   2 · BROWSER TOOLBAR — redesigned.
   The old one put a 32px brand lockup, close, back, forward, refresh, an
   address form, a Go button, a server <select> and an alert-orange Bug
   button in ONE undifferentiated row. Nine controls, no hierarchy, and the
   loudest colour on the bar belonged to the least-used one.

   New shape: three groups with clear priority —
     left   nav controls (back / forward / refresh), grouped and quiet
     centre the address, which is what the bar is FOR, so it takes the space
     right  server, bug, close — utilities, with close last and separated
   ═══════════════════════════════════════════════════════════════════════ */
.vpn-browser-toolbar {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; min-height: 52px; flex-wrap: nowrap;
}
.vpn-browser-toolbar > * { flex: none; }

/* the brand shrinks to a mark: a toolbar does not need a wordmark, and the
   32px lockup was eating the space the address needed */
.vpn-browser-brand { gap: 0; padding: 0 2px; }
.vpn-browser-brand-text { display: none; }
/* The toolbar mark takes the SAME tile as the hero (2026-08-12). It already had
   a rounded-square wrapper, but painted with production's washed
   rgba(.34/.24) gradient — so after the hero was retiled this was the one place
   left showing the brand in its old form, and the toolbar is exactly where a
   user sees it side by side with the landing page they came from.
   Sized to the 52px bar rather than inheriting the base 38px: the bar is the
   one place the mark competes for space with the address field. */
.vpn-browser-brand-mark {
  width: 32px; height: 32px; border-radius: 9px;
  background: linear-gradient(135deg, var(--blue) 0%, #2b7fd4 45%, var(--orange) 100%);
  border: 0;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22);
}
.vpn-browser-brand-bird { width: 20px; height: 20px; }

.vpn-nav-history { display: flex; align-items: center; gap: 2px; }
.vpn-nav-button {
  width: 36px; height: 36px; min-width: 36px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid transparent; border-radius: var(--r-sm);
  color: var(--text); font-size: 16px; line-height: 1;
}
.vpn-nav-button:hover:not(:disabled) { background: rgba(255,255,255,.10); border-color: var(--border); }
.vpn-nav-button:disabled { opacity: .32; }

/* the address is the point of the bar */
.vpn-nav-form { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 6px; }
.vpn-nav-address {
  flex: 1 1 auto; min-width: 0; height: 38px;
  background: rgba(255,255,255,.06); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 0 12px; font-size: 14px; color: var(--text);
}
.vpn-nav-address:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(30,136,229,.16); outline: none; }
.vpn-nav-go {
  height: 38px; padding: 0 16px; border-radius: var(--r-sm); font-size: 13.5px; font-weight: 700;
  background: var(--blue); color: #061426; border: 0;
}

/* utilities, visually quieter than the address */
.vpn-mirror-field { display: flex; align-items: center; }
.vpn-mirror-select {
  height: 36px; max-width: 150px; border-radius: var(--r-sm); font-size: 12.5px;
  background: rgba(255,255,255,.06); border: 1px solid var(--border); color: var(--text);
  padding: 0 8px;
}
/* the Bug button was alert-orange — the loudest thing on the bar for the
   least-used control. Quiet by default, orange only on hover. */
.vpn-nav-button.vpn-bug-report {
  width: auto; min-width: 36px; padding: 0 10px; gap: 6px;
  background: transparent; color: var(--white-30); border-color: transparent;
}
.vpn-nav-button.vpn-bug-report:hover { color: var(--orange); border-color: var(--orange-20); background: var(--orange-08); }
.vpn-bug-report-text { font-size: 12.5px; }

/* close is destructive-ish and goes last, separated by a hairline */
.vpn-nav-close {
  order: 99; margin-left: 2px; padding-left: 10px;
  border-left: 1px solid var(--border); border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: 20px; color: var(--white-30);
}
.vpn-nav-close:hover { color: var(--text); }

@media (max-width: 720px) {
  /* phone: forward is the least-used control and the address needs the room */
  #vpn-nav-forward { display: none; }
  .vpn-bug-report-text { display: none; }
  .vpn-mirror-select { max-width: 104px; font-size: 11.5px; }
  .vpn-browser-toolbar { gap: 5px; padding: 6px 8px; min-height: 48px; }
  .vpn-nav-button { width: 34px; height: 34px; min-width: 34px; }
  .vpn-nav-address, .vpn-nav-go { height: 36px; }
  .vpn-nav-go { padding: 0 12px; }
}

/* ═══════════════════════════════════════════════════════════════════════
   3 · LOADER — named steps, elapsed time, and an exit from frame one.
   ═══════════════════════════════════════════════════════════════════════ */
.nd-load-card {
  width: min(360px, calc(100vw - 40px));
  background: #1a202d; border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 20px 20px 18px; text-align: left;
}
#vpn-frame-loader .vpn-frame-loader-msg {
  font-size: 16px; font-weight: 700; letter-spacing: -.02em; margin: 0; color: var(--text);
}
.nd-load-sub {
  font-size: 11px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--white-30); margin: 4px 0 0; font-variant-numeric: tabular-nums;
}
.nd-steps { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 9px; }
.nd-step { display: grid; grid-template-columns: 16px 1fr; gap: 10px; align-items: center;
  font-size: 13px; color: var(--white-30); }
.nd-step em {
  width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid rgba(255,255,255,.22);
  display: grid; place-items: center; font-style: normal; font-size: 9px; line-height: 1;
}
.nd-step.is-done { color: #c3d6e9; }
.nd-step.is-done em { background: var(--blue); border-color: var(--blue); color: #061426; }
.nd-step.is-done em::before { content: "✓"; font-weight: 700; }
.nd-step.is-active { color: #fff; }
.nd-step.is-active em { border-color: var(--orange); border-right-color: transparent; animation: ndspin .8s linear infinite; }
@keyframes ndspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .nd-step.is-active em { animation: none; border-right-color: var(--orange); }
}
.nd-load-bar { height: 3px; border-radius: 2px; background: rgba(255,255,255,.10); margin-top: 16px; overflow: hidden; }
.nd-load-bar i { display: block; height: 100%; width: 8%; border-radius: 2px;
  background: linear-gradient(90deg, var(--blue), var(--orange)); transition: width .5s ease; }
.nd-load-actions { display: flex; gap: 8px; margin-top: 16px; }
#vpn-frame-loader .vpn-frame-loader-cancel,
#vpn-frame-loader .nd-load-switch {
  /* both present from frame one — the old build revealed Cancel only at 3s */
  display: inline-flex; align-items: center; justify-content: center;
  flex: 1; min-height: 40px; border-radius: var(--r-sm); font-size: 13px; font-weight: 650;
  border: 1px solid var(--border); background: rgba(255,255,255,.06); color: var(--text);
}
#vpn-frame-loader .nd-load-switch { background: var(--blue); color: #061426; border-color: transparent; }
/* the slow hint is now an addition, not the first feedback */
#vpn-frame-loader .vpn-frame-loader-slow { display: none; font-size: 12px; color: var(--white-30); margin: 12px 0 0; }
#vpn-frame-loader.is-slow .vpn-frame-loader-slow { display: block; }

/* ═══════════════════════════════════════════════════════════════════════
   SERVER ROW — hit-area fix.

   Measured on the live env: the row is 640px wide but the <select> was only
   280px, sitting in the middle with ~159px of dead space on the left and
   ~201px on the right. elementFromPoint at the "change" label returned the
   ROW, not the select — so the word "change", styled blue like a link, did
   nothing at all. That is the "auto change works weirdly" report.

   Two base rules caused it, and neither was overridden because the first
   pass never declared them:
     .edge-picker        { justify-content: center }   -> free space to the sides
     .edge-picker-select { max-width: 280px }          -> flex:1 could not grow it

   Fix: the select fills the row, so the whole row is the hit area. "change"
   becomes decoration layered over the select's own right padding — it keeps
   pointer-events:none, but now the select is underneath it rather than a gap.
   ═══════════════════════════════════════════════════════════════════════ */
.edge-picker.nd-srv {
  position: relative;
  justify-content: flex-start;
  flex-wrap: nowrap;
  transition: border-color .15s ease, background .15s ease;
}
.edge-picker-select.nd-srv-select {
  max-width: none; width: 100%; flex: 1 1 auto;
  padding-right: 78px;           /* room for the decorative "change" */
}
.nd-srv-change {
  position: absolute; right: 13px; top: 50%; transform: translateY(-50%);
  pointer-events: none;          /* the select underneath takes the click */
}
/* the row LOOKS interactive, so it must respond. There was no hover state at
   all before — confirmed by probe: background, border, colour and transform
   were all unchanged on hover. */
.edge-picker.nd-srv:hover { border-color: rgba(255,255,255,.30); background: rgba(255,255,255,.08); }
.edge-picker.nd-srv:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(30,136,229,.16); }
/* the base select's own hover lifts it 1px — inside a row that reads as one
   control, that made the label jump while the row stayed put */
.edge-picker-select.nd-srv-select:hover:not(:disabled) { transform: none; background: transparent; border-color: transparent; }

.nd-srv-dot { pointer-events: none; }

/* ═══════════════════════════════════════════════════════════════════════
   STATIC PAGES — privacy / contact / terms / dmca, brought onto the neutral
   shell. They kept the old hero (80px blurred mark, gradient wordmark,
   slogan) while the landing moved on, so the site read as two designs.
   Only the CHROME changes here — header, page card, footer. Body copy is
   the tester's and is not touched.
   ═══════════════════════════════════════════════════════════════════════ */
/* 🪤 ENUMERATED, never a bare `body[data-page]`. The first version of this block
   was unscoped, which hid `.logo-section` on EVERY static page while only
   contact had a replacement header — privacy rendered with no header at all.
   Listing the pages explicitly means a page added later keeps its own chrome
   until someone deliberately opts it in, rather than silently inheriting a
   layout that may not fit its content.

   privacy joined 2026-08-07. It gets the SAME treatment as contact, not a
   variant: the complaint being fixed is that the site read as two designs, so a
   privacy-specific flourish would just make it three. Rejected on that basis: a
   card-per-section layout (5 cards for 211 words) and a table of contents
   (longer than some of the sections it would index). Also rejected — a "LEGAL"
   kicker and a "last updated" line: no such i18n key exists, so both would mean
   INVENTING copy, and a fabricated date on a legal page is a factual claim
   rather than styling. Body copy is the tester's and is not touched. */
body[data-page="contact"] .logo-section,
body[data-page="privacy"] .logo-section { display: none; }
body[data-page="contact"] .content-page,
body[data-page="privacy"] .content-page { width: 100%; padding: 0 16px; }
/* The card was `rgba(255,255,255,.05)` — a 5% white wash over a near-black page
   with the grid texture showing straight through it. It did not read as a
   surface, it read as a smudge, which is what "the container colour is terrible"
   was pointing at. Lifting the white further only greys it out more.
   Going DARKER instead is what makes it a panel: a translucent near-black sits
   the card *under* the page's texture rather than fighting it, the border does
   the edge definition, and a soft shadow separates it from the background.
   Side effect worth having — body copy gains contrast rather than losing it,
   since the ground is now darker than the page instead of lighter. */
body[data-page="contact"] .page-card,
body[data-page="privacy"] .page-card {
  width: min(100%, 760px); margin: 18px auto 0;
  background: rgba(8, 13, 24, 0.72);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: var(--r-lg); padding: clamp(20px, 4vw, 34px);
  box-shadow: 0 18px 48px rgba(0,0,0,0.34);
}
body[data-page="contact"] .page-card h1,
body[data-page="privacy"] .page-card h1 {
  font-size: clamp(24px, 4vw, 32px); font-weight: 800; letter-spacing: -.03em;
  margin: 0 0 6px; color: var(--text);
}
body[data-page="contact"] .page-card h2,
body[data-page="privacy"] .page-card h2 {
  font-size: 17px; font-weight: 700; letter-spacing: -.015em;
  margin: 26px 0 8px; color: var(--text);
}
body[data-page="contact"] .page-card p,
body[data-page="contact"] .page-card li,
body[data-page="privacy"] .page-card p,
body[data-page="privacy"] .page-card li { font-size: 15px; line-height: 1.65; color: #c3d6e9; max-width: 68ch; }
/* 🪤 `:not(.page-link-chip)` is LOAD-BEARING. Without it this rule — meant for
   links inside body copy — also hit the back CHIP, which is a button, not a
   link. Its own `.page-link-chip.is-primary { color: #0b1220 }` is specificity
   (0,2,0) and lost to this selector's (0,2,2), so blue text landed on the
   orange gradient at a MEASURED 1.61:1 — a third of the 4.5 minimum, and the
   thing that got reported as "blue on orange is terrible". It was, and it was
   caused here, not inherited from production. */
body[data-page="contact"] .page-card a:not(.page-link-chip),
body[data-page="privacy"] .page-card a:not(.page-link-chip) { color: var(--blue); }

/* The back chip is DEMOTED to secondary on static pages. Orange is this
   design's primary-action colour — it is the Go button — so spending it on
   "Home" made a back link the loudest thing on a legal page and competed with
   the heading. Secondary is both the correct hierarchy and inherently safe on
   contrast: --text on the neutral chip is 13.9:1 against the card. */
body[data-page="contact"] .page-link-chip.is-primary,
body[data-page="privacy"] .page-link-chip.is-primary {
  color: var(--text);
  background: rgba(255,255,255,0.07);
  border-color: var(--border);
  box-shadow: none;
}
body[data-page="contact"] .page-link-chip.is-primary:hover,
body[data-page="privacy"] .page-link-chip.is-primary:hover {
  background: rgba(255,255,255,0.13);
  border-color: rgba(255,255,255,0.30);
}
/* 🪤 Undo the LANDING's `.nd-brandrow { margin-top: auto }` here. That rule is
   deliberate and must stay (it splits the free space so the landing column sits
   optically centred while the footer stays pinned — see the COMPOSITION block
   below), but it is unscoped, so the static pages inherited it too.

   The effect only shows on a page SHORT enough to have slack: contact's header
   sat 93px lower than privacy's — measured `margin-top: 92.7px` on contact vs
   `0px` on privacy, from the same rule, because privacy's copy overflows the
   viewport and leaves nothing to distribute. Harmless while the two pages had
   different headers; now that they share one component it reads as the header
   jumping when you navigate between them, which is the exact inconsistency this
   restyle exists to remove. A document should start at the top.

   Scoped as an override rather than by narrowing the landing rule, so the
   landing composition — which was measured and verified separately — cannot
   regress. The footer KEEPS its own auto margin and stays pinned to the bottom.

   ⚠️ UPDATE 2026-08-12: `0` was too literal — with the mark now tiled the header
   sat 16px from the top edge and read as jammed against it. It is a fixed,
   VIEWPORT-derived offset instead of the old `auto`: the whole point of killing
   `auto` was that it resolved to a different value per page (92.7px vs 0px)
   depending on how much slack the copy left, so any replacement must depend only
   on the viewport, never on content length — that is what keeps privacy and
   contact byte-identical here and keeps the parity guard honest. */
body[data-page="contact"] .nd-brandrow,
body[data-page="privacy"] .nd-brandrow { margin-top: clamp(28px, 5vh, 56px); }

/* ═══════════════════════════════════════════════════════════════════════
   DESKTOP HERO — the top of the page read as blank: a 20px brand row in the
   corner, then a small grey label, then the field. Nothing with presence.

   The space BELOW the field is deliberately left alone — that is where the ad
   renders (it is empty on this branch only because the provider tags are not
   shipped here). So this fills the TOP rather than centring the stack, which
   would have pushed the field down into the ad's space.

   No new copy: the mark, the wordmark and the "free yourself" line all already
   exist, the last one localised in all six shipped locales as header.slogan.
   ═══════════════════════════════════════════════════════════════════════ */
.nd-hero-slogan { display: none; }

/* 🚨 THIS HERO BREAKPOINT WAS 700px, AND IT OWNED THE TABLET COLLAPSE.
   Arm B lost 64% relatively at 600-1023px (2.04% vs 5.62% press-Go, Fisher
   p=0.013) — and 700px sits INSIDE that band, which is where the regression
   switches on. Measured depth of the Go button into the first fold:
       viewport      arm A     arm B
       600x800       34.9%     31.4%   (below the breakpoint — B is FINE)
       700x1000      23.3%     43.5%   ← breakpoint fires
       768x1024      22.8%     44.7%
       800x1280      20.8%     45.6%
   Swapping the compact 34px brand row for a centred 72px tile + 34px wordmark +
   slogan takes the block from 34px to 136px, so at 768x1024 there is 404px of
   the 1024px fold left EMPTY (148px above the logo, the 136px logo, then two
   voids of 62px and 58px) and Go lands at y=457 against arm A's y=233.
   ✅ Moving the switch to 1024px keeps the compact left-aligned brand row — the
   one that already works on phones — across the whole tablet band, and puts Go
   back at roughly a quarter of the way down.
   🔑 It also fixes a second mismatch this created: at 700-767px the design used
   the DESKTOP hero while still showing the 300x100 MOBILE ad (the base rules
   switch ad formats at 767/768px), so a small tablet got a big centred logo above
   an orphaned phone-sized banner.
   🪤 The separate `@media (min-width: 700px)` block further up is deliberately
   LEFT ALONE — it only sets max-widths and type sizes, which are harmless in this
   band. This is specifically the hero composition. */
@media (min-width: 1024px) {
  .nd-brandrow {
    flex-direction: column; align-items: center; gap: 10px;
    margin: clamp(24px, 6vh, 64px) auto clamp(22px, 4vh, 40px);
    text-align: center;
  }
  .nd-brand { flex-direction: column; gap: 12px; font-size: 34px; letter-spacing: -.035em; }
  .nd-brand .vpnfree { font-size: 34px; font-weight: 700; }
  /* the tile, not the glyph — .nd-mark is sized as 62% of this (see the
     .nd-mark-tile block). Radius stays ~28% of the side. */
  .nd-mark-tile { width: 72px; height: 72px; border-radius: 20px; }
  .nd-hero-slogan {
    display: block; font-size: 15px; font-style: italic;
    color: var(--white-30); letter-spacing: 0;
  }
  .nd-label { text-align: center; }
  .nd-hint  { text-align: center; }
}
@media (min-width: 1100px) {
  .nd-brand, .nd-brand .vpnfree { font-size: 40px; }
  .nd-mark-tile { width: 80px; height: 80px; border-radius: 22px; }
  .nd-hero-slogan { font-size: 16px; }
}


/* ── Footer language select — suppress the native control chrome ─────────────
   Inherited from production, where `.lang-selector select` is styled (bg,
   border, radius) but never sets `appearance`, so the UA still paints its own
   dropdown button — a light native widget sitting on a dark footer. Production
   has the same, so this is not a regression; it is fixed here because language
   switching is one of THIS design's two headline controls, and it sat directly
   under a server picker that IS fully styled (`.nd-srv-select`, appearance:none).
   Two controls of the same kind must not disagree about whether they are themed.

   🪤 `appearance: none` alone removes the caret entirely — a select with no
   affordance reads as a text label and stops looking clickable. The caret has to
   be redrawn: an inline SVG background, with right padding reserving its space.
   Kept as a data: URI because a strict CSP blocks external assets, and inlined
   rather than a ::after because a <select> cannot render pseudo-elements. */
.lang-selector select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 26px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23e6eef6' stroke-opacity='.6' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
}

/* ═══════════════════════════════════════════════════════════════════════
   COMPOSITION — centre the column, give the ad its own air (2026-08-07)
   ═══════════════════════════════════════════════════════════════════════
   Measured before this change: 527px (desktop) / 523px (tablet) of dead space,
   collapsed into ONE hole between the ad and the footer, with the whole page
   exactly 100vh tall.

   ROOT CAUSE: `footer` already carries `margin-top: auto` (index.css:439) and
   was the ONLY auto margin in the body flex column — so it absorbed 100% of the
   free space and the content stayed hard top-aligned with the remainder dumped
   at the bottom. Adding a second auto at the TOP splits that space evenly: the
   column sits optically centred and the footer stays pinned to the bottom.
   No new markup, no new copy.

   🪤 Do NOT "fix" this instead by removing the footer's own auto margin — that
   unpins the footer, which then floats mid-screen on tall viewports with dark
   space below it. Two autos is the fix; one auto anywhere is the bug. */
.nd-brandrow { margin-top: clamp(24px, 5vh, 56px); }

/* 🚨 THE TOP `auto` IS NOW DESKTOP-ONLY, AND THAT IS THE POINT.
   The two-auto centring above is a deliberate composition decision and it is kept
   at >=1024px, where arm B already WINS (13.19% vs arm A's 12.85% press-Go,
   p=0.60 — no difference to protect against, but nothing to gain either).
   Below 1024px it was costing the primary metric twice over:
   (1) it made the CTA a MOVING TARGET. Two autos split the column's free space
       50/50, so the ad slot filling (h=0 -> 100) moved the whole CTA stack up by
       HALF of it. Traced from document start on the deployed page:
           @768x1024  Go y=502 at t=353ms  ->  y=457 at t=419ms   = 45px jump
       A thumb tap is committed before the page settles, so taps aimed at the
       pre-shift button landed on the hint, a gap, or the server dropdown. Arm A
       moves 1px because it has only the footer's auto. Post-fix: 1px.
   (2) it CENTRED the CTA instead of raising it. Centring means the button sits
       near the middle of a tall fold whatever the content above it, which is why
       moving the hero breakpoint alone only took the tablet fold-depth 44.6% ->
       39.4%. Bounding the top margin takes it to 22.7%, against arm A's 22.8% —
       parity in the exact band that regressed 64%.
   Measured fold-depth of Go with this rule in place (A vs B):
       375x740    A 33.6%   B 28.2%   (B now higher than A)
       768x1024   A 22.8%   B 22.7%   (parity)
       1280x800   unchanged from today's arm B — the auto is restored below
   🪤 The footer's own `margin-top: auto` is UNTOUCHED, per the trap-note above:
   removing that one unpins the footer. This bounds the TOP margin only, and only
   where the measurement says it hurts. */
@media (min-width: 1024px) {
  .nd-brandrow {
    margin-top: auto;
    /* 🚨 FLOOR, added 2026-08-31 — WITHOUT THIS THE LOGO SLAMS TO THE TOP.
       `margin-top: auto` distributes FREE space, so it yields exactly ZERO the
       moment the column has none. Restoring the features-section in fixpack248
       made the page taller than a normal desktop viewport and the hero jumped to
       16px from the top edge. Measured on the live page, same URL, only the
       viewport height differing:
         1280x800  -> docH 1142 > vh 800, computed margin-top 0px,    logo at 16px
         1280x1400 -> content fits,       computed margin-top 62.6px, logo at 79px
       Padding is unconditional, so it survives the collapse, and the `auto` above
       still centres the hero when there IS room — which is the composition the
       arm-B CTA work deliberately kept at >=1024px.
       🪤 It must be PADDING, not a bigger margin: any margin here is still subject
       to the same free-space collapse. `.nd-brandrow` has no background or border,
       so padding and margin are visually identical on this element.
       🪤 Do NOT "fix" a future recurrence by deleting the `auto` — that discards
       the desktop centring and re-opens the composition question the CTA work
       already settled with data. */
    padding-top: clamp(24px, 6vh, 64px);
  }
}

/* The ad is the last block before the footer and, by owner decision, is meant
   to be the dominant element. It was working against that: sitting hard under
   the server row, it read as part of the CTA cluster and competed with the
   primary action rather than owning its own space. Giving it real air separates
   the two — the action group reads as finished, and the ad becomes a deliberate
   block of its own. It gains prominence precisely BY not crowding the CTA.
   `margin-top` only, so the base rules' `auto` horizontal centring survives. */
.ad-mobile-banner,
.ad-desktop-footer {
  /* 🪤 THE `6vh` TERM WAS A VIEWPORT-SCALED DEAD ZONE, so the taller the phone
     the bigger the untappable hole. Measured: 44.4px at 375x740 and 89px at
     768x1024 of pure page background between the server row and the ad —
     `elementFromPoint` sampled at 9 x-positions every 4px across that band
     returns `body` or an empty container, i.e. 0 interactive hits, and it sits
     squarely in the thumb zone of a page that (unlike arm A) does not scroll at
     common phone heights, so a downward swipe there is delivered as a TAP.
     A fixed 28px keeps the separation the note below is about — the ad still
     reads as its own block and does not crowd the CTA — without the hole
     growing with the screen. Recovers ~32px on a phone, ~52px on a tablet.
     🪤 I deliberately did NOT also collapse the empty `#uv-error` placeholder in
     `.desc` (another 16px). It is reserving space on purpose: hiding it would
     make an error APPEARING shift the layout, which is precisely the failure
     class being fixed above. Dead space that prevents a shift is worth keeping. */
  margin-top: 28px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   RIGHT-TO-LEFT (Arabic, Persian)
   Arabic + Persian are 9.0% of visitors (1,641 in the 30d read) and were being
   served correct words inside a left-to-right frame: label, server row and the
   language caret all hugged the wrong edge.

   🔒 SCOPED ENTIRELY TO [dir="rtl"], which i18n.js sets on <html> for RTL_LANGS
   and explicitly clears to "ltr" otherwise. So this block is INERT for the other
   11 locales — it cannot regress the 91% of traffic already converting, which is
   why it is a scoped override and NOT a rewrite of the base rules into logical
   properties. A logical-property refactor would touch every locale's rendering
   to fix two.

   🪤 ONLY PHYSICAL PROPERTIES NEED RESTATING. Flexbox rows, `text-align: center`
   and the input group all mirror by themselves — re-flipping them here would
   double-flip them back. Everything below is a hardcoded left/right that CSS
   will not mirror on its own.
   ═══════════════════════════════════════════════════════════════════════════ */
[dir="rtl"] .nd-label { text-align: right; }
[dir="rtl"] .nd-step  { text-align: right; }

/* The server row's "change" is decoration positioned OVER the select, and the
   select reserves its space with padding. Move one without the other and the
   option text runs underneath the word. */
[dir="rtl"] .edge-picker-select.nd-srv-select { padding-right: 12px; padding-left: 78px; }
[dir="rtl"] .nd-srv-change { right: auto; left: 13px; }

/* The caret is a background IMAGE; background-position never mirrors. */
[dir="rtl"] .lang-selector select {
  padding-right: 8px; padding-left: 26px;
  background-position: left 9px center;
}

/* Browse-mode chrome is ours too, and RTL users see it for the whole session.
   Close keeps `order: 99`, so flex already moves it to the far edge — what does
   not move is its hairline separator and the rounding that pairs with it. */
[dir="rtl"] .vpn-nav-close {
  margin-left: 0; margin-right: 2px;
  padding-left: 0; padding-right: 10px;
  border-left: 0; border-right: 1px solid var(--border);
  border-radius: var(--r-sm) 0 0 var(--r-sm);
}
[dir="rtl"] .vpn-report-public-notice {
  border-left: 0; border-right: 3px solid rgba(255, 176, 60, 0.7);
  padding: 4px 10px 4px 0;
}

/* ── RETURN PATH: install nudge + recent hosts (2026-08-31, RETURN-1) ──────
   Both live BELOW the CTA and both are `hidden` until JS opts in, so neither
   can move Go during load — the arm-B defect. Neither reserves space.
   Tap targets are 44px: the landing page's own audit (fp248 input 40→44) and
   the footer tap-target work both landed on that floor. */
/* 🪤 MANDATORY, and this file has been bitten by it twice already
   (.vpn-browser-nav[hidden], .vpn-report-panel[hidden]): the `hidden` attribute
   is only a UA-stylesheet `display: none`, so ANY author `display` rule below
   beats it and the element paints while the DOM says it is hidden. Caught here
   by the "not made on arrival" spec — without this the install offer rendered on
   first paint, i.e. exactly the behaviour the design forbids. */
.pwa-nudge[hidden],
.recent-sites[hidden] {
  display: none !important;
}
.pwa-nudge {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  margin: 14px 0 0;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);          /* a row → md, per the radius scale */
  background: var(--blue-10);
}
.pwa-nudge-text {
  flex: 1 1 220px;
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.35;
  color: var(--text);
}
.pwa-nudge-actions { display: flex; align-items: center; gap: 8px; }
.pwa-nudge-btn,
.pwa-nudge-skip {
  min-height: 44px;
  padding: 0 16px;
  border-radius: var(--r-sm);          /* small control → sm */
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}
.pwa-nudge-btn {
  border: 1px solid transparent;
  background: var(--blue);
  color: #fff;
  font-weight: 600;
}
/* 🪤 NOT `outline: 2px solid var(--bg)`. That was the go-btn bug fp248 fixed:
   an outline the exact colour of the background is invisible keyboard focus. */
.pwa-nudge-btn:focus-visible,
.pwa-nudge-skip:focus-visible,
.recent-site:focus-visible,
.recent-sites-clear:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.pwa-nudge-skip {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--white-30);
}

.recent-sites { margin: 14px 0 0; }
.recent-sites-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
}
.recent-sites-label {
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--white-30);
}
.recent-sites-clear {
  min-height: 44px;
  padding: 0 10px;
  border: 0;
  background: transparent;
  color: var(--white-30);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
  text-decoration: underline;
}
.recent-sites-list { display: flex; flex-wrap: wrap; gap: 8px; }
.recent-site {
  min-height: 44px;
  max-width: 100%;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);        /* a genuine pill */
  background: var(--card);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
