@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap');

:root {
  --terminal-fg: #c3cbd3;
  --bg: #0a0d12;
  --panel: #12171f;
  --panel-line: #232b36;
  --signal-cyan: #4ce0d2;
  --pulse-amber: #ffb454;
  --alert-coral: #ff6b5e;
  --text-primary: #edf2f5;
  --text-muted: #8a97a6;
  /* Body copy: dimmer than headings, but still comfortably readable. Kept
     separate from --text-muted, which is for labels and captions where lower
     contrast is acceptable. */
  --text-body: #c3cbd3;
  --radius: 10px;
  --font-display: 'Space Grotesk', sans-serif;
  --font-body: 'Inter', sans-serif;
  --font-mono: 'IBM Plex Mono', monospace;
}

/* Light theme: same accent hues, swapped surface/text so contrast stays right */
[data-theme="light"] {
  --bg: #f4f6f8;
  --panel: #ffffff;
  --panel-line: #e1e5ea;
  --signal-cyan: #0f9c8e;
  --pulse-amber: #c97a12;
  --alert-coral: #d94f42;
  --text-primary: #161b22;
  --text-muted: #5c6673;
  /* Near-black rather than a mid grey: on a white panel this is ~11:1, so
     long prose stays readable rather than washing out. */
  --text-body: #2b333d;
}
[data-theme="light"] body {
  background-image:
    linear-gradient(180deg, rgba(15,156,142,0.06), transparent 40%),
    repeating-linear-gradient(0deg, rgba(0,0,0,0.012) 0px, rgba(0,0,0,0.012) 1px, transparent 1px, transparent 3px);
}
[data-theme="light"] .btn-start,
[data-theme="light"] .btn-solid { color: #ffffff; }
[data-theme="light"] .trace-wrap { background: #eef1f4; }
[data-theme="light"] .consent-card,
[data-theme="light"] .panel-card,
[data-theme="light"] .instrument { box-shadow: 0 8px 24px rgba(20,30,40,0.06); }

* { box-sizing: border-box; }
[hidden] { display: none !important; }

/* Keep centered header content at the same horizontal coordinate on short
   and long pages. Without a reserved gutter, pages that gain a vertical
   scrollbar move the menu by half the scrollbar width. */
html { scrollbar-gutter: stable; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  /* Safety net: in RTL mode any element that still uses a physical
     left/right offset instead of a logical one (a leftover from a future
     edit, a third-party embed, etc.) can no longer drag the whole
     document's scrollable area sideways and strand the visible content
     off-screen. The real offenders should still be fixed individually
     (see the [dir="rtl"] .skip-link rule below for the one that caused
     the reported "page shifts right in Arabic" bug), but this keeps a
     single bad offset from breaking the entire page again. */
  overflow-x: hidden;
}

body {
  background-image:
    linear-gradient(180deg, rgba(76,224,210,0.05), transparent 40%),
    repeating-linear-gradient(0deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 3px);
}

a { color: var(--signal-cyan); text-decoration: none; }

/* Content column widened from a fixed 960px.
   960px was leaving a lot of unused space on a normal desktop display. It is
   now fluid: it grows with the viewport but stops at 1280px, because a
   measurement readout and its metric grid stop being easier to read past
   roughly that point — line lengths get long and the eye has to travel.
   `min()` keeps a sensible margin on mid-size windows without a breakpoint. */
/* Content column.
   1280px on its own, but the side rails are fixed-position elements sitting
   in the margins, so when they are actually serving an ad the column has to
   leave room for them or they overlap the content. `--rail-reserve` is 0 by
   default and set only when an ad network has filled a rail (body.ads-active,
   applied by ads.js), so a site running no ads keeps the full width. */
/* Ad layout reservation.
   `ads-reserved` is set by ads.js as soon as it confirms the operator has an
   ad network configured — NOT when an ad renders. Reserving up front is the
   point: it stops the column jumping narrower the moment an ad arrives, and
   it means the space is visibly there and ready even while AdSense is still
   in its 24-48h approval window.
   `ads-active` is set separately, only when a slot really was filled, and is
   what makes a slot visible. Space reserved, box hidden until filled. */
:root { --rail-reserve: 0px; }
/* Content narrows when ads are configured so the rails have somewhere to go.
   Sized against a 1536px CSS viewport — what a 1920px screen reports at
   Windows' default 125% scaling, which is the most common desktop setup and
   the one this was being tested on. 1040 content + 2x160 rails + gutters
   fits inside 1440px. */
body.ads-reserved { --rail-reserve: 700px; --content-max: 1040px; }
:root { --content-max: 1280px; }

.wrap {
  max-width: min(var(--content-max), calc(100% - 32px));
  margin: 0 auto;
  padding: 0 24px;
  box-sizing: border-box;
}
@media (max-width: 640px) {
  .wrap { max-width: 100%; padding: 0 16px; }
}

/* Header */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24px;
  border-bottom: 1px solid var(--panel-line);
}

.logo {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: -0.01em;
}

/* The wordmark now carries the actual app icon rather than an abstract dot,
   so the browser tab, the installed home-screen icon and the header all show
   the same mark. Sized in em so it tracks the wordmark if the font size
   changes, and given an explicit width/height so it reserves its space before
   the image loads instead of shifting the header. */
/* Inline SVG rather than the PNG app icon. The PNG carries its own dark tile
   baked into the image, so in light mode it showed as a dark square stuck to
   the wordmark. Inline SVG has a transparent background and draws itself from
   CSS variables, so the same mark works in both themes and stays crisp at any
   density. The PNG is still used for the favicon and the installed app icon,
   where a tile is exactly what the platform wants. */
.logo-mark {
  width: 1.5em;
  height: 1.5em;
  flex-shrink: 0;
  display: block;
  overflow: visible;
}
.logo-mark .logo-trace {
  stroke: var(--signal-cyan);
  fill: none;
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.logo-mark .logo-node { fill: var(--pulse-amber); }

.logo a,
a.logo {
  color: inherit;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 10px;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}

.nav-divider {
  width: 1px;
  height: 18px;
  background: var(--panel-line);
  flex-shrink: 0;
}

.nav-controls {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.site-nav a {
  color: var(--text-muted);
  font-size: 0.9rem;
}
.site-nav a:hover { color: var(--text-primary); }

.theme-btn {
  background: none;
  border: 1px solid var(--panel-line);
  color: var(--text-muted);
  border-radius: 100px;
  width: 32px;
  height: 32px;
  font-size: 0.95rem;
  cursor: pointer;
  line-height: 1;
}
.theme-btn:hover { color: var(--text-primary); }

/* Language switcher */
.lang-switch {
  position: relative;
  display: inline-block;
}
.lang-switch button#langBtn {
  background: none;
  border: 1px solid var(--panel-line);
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: 0.82rem;
  border-radius: 100px;
  padding: 6px 14px;
  cursor: pointer;
}
.lang-switch button#langBtn:hover { color: var(--text-primary); }
.lang-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: 8px;
  padding: 6px;
  min-width: 140px;
  z-index: 60;
  box-shadow: 0 12px 32px rgba(0,0,0,0.4);
}
[dir="rtl"] .lang-menu { right: auto; left: 0; }
.lang-menu button {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 0.85rem;
  padding: 8px 10px;
  border-radius: 6px;
  cursor: pointer;
}
[dir="rtl"] .lang-menu button { text-align: right; }
.lang-menu button:hover { background: rgba(255,255,255,0.05); color: var(--text-primary); }
.lang-menu button.active { color: var(--signal-cyan); }
.account-menu-email {
  padding: 8px 10px 10px;
  font-size: 0.78rem;
  color: var(--text-muted);
  border-bottom: 1px solid var(--panel-line);
  margin-bottom: 4px;
  word-break: break-all;
}

/* Hero / instrument panel */
.instrument {
  margin: 48px auto 24px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: var(--radius);
  padding: 40px clamp(20px, 5vw, 56px);
}

.instrument-top {
  display: flex;
  justify-content: flex-end;
  align-items: baseline;
  margin-bottom: 8px;
}

.connection-info {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  margin-bottom: 24px;
}
.connection-row { margin: 3px 0; color: var(--text-muted); }
.connection-label { color: var(--text-muted); }
.connection-sep { color: var(--text-muted); margin-right: 4px; }
.connection-value { color: var(--text-primary); font-weight: 600; }
.location-accuracy-btn {
  margin-left: 7px;
  padding: 2px 7px;
  border: 1px solid var(--panel-line);
  border-radius: 999px;
  background: transparent;
  color: var(--signal-cyan);
  font: inherit;
  font-size: .68rem;
  cursor: pointer;
}
.location-accuracy-btn:hover { border-color: color-mix(in srgb, var(--signal-cyan) 56%, var(--panel-line)); }
.location-accuracy-btn:disabled { opacity: .55; cursor: wait; }

.status-pill {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-muted);
  border: 1px solid var(--panel-line);
  border-radius: 100px;
  padding: 4px 12px;
}
.status-pill.live { color: var(--signal-cyan); border-color: rgba(76,224,210,0.4); }

/* Big readout */
.readout {
  text-align: center;
  margin: 12px 0 8px;
}

.readout-value {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: clamp(3.5rem, 12vw, 6rem);
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--signal-cyan);
  transition: color 0.3s;
}
.readout-value.upload-phase { color: var(--pulse-amber); }
.readout-value.ping-phase { color: var(--text-primary); }

.readout-unit {
  font-family: var(--font-mono);
  color: var(--text-muted);
  font-size: 1rem;
  margin-top: 4px;
}
.readout-unit-sub { font-size: 0.85rem; opacity: 0.8; }

.readout-phase {
  font-family: var(--font-display);
  font-size: 0.85rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: 6px;
}

/* Pulse trace (signature element) */
.trace-wrap {
  margin: 28px 0 32px;
  border: 1px solid var(--panel-line);
  border-radius: 8px;
  background: #0d1218;
  overflow: hidden;
}
#trace {
  display: block;
  width: 100%;
  height: 120px;
}

/* Start button */
.start-row { display: flex; justify-content: center; }

.btn-start {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1rem;
  letter-spacing: 0.02em;
  color: #06110f;
  background: var(--signal-cyan);
  border: none;
  border-radius: 100px;
  padding: 14px 42px;
  cursor: pointer;
  transition: transform 0.15s, box-shadow 0.15s;
  box-shadow: 0 0 0 rgba(76,224,210,0.5);
}
.btn-start:hover { transform: translateY(-1px); box-shadow: 0 6px 24px rgba(76,224,210,0.25); }
.btn-start:disabled { opacity: 0.5; cursor: default; transform: none; box-shadow: none; }

/* Metric cards */
.metrics {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--panel-line);
  border: 1px solid var(--panel-line);
  border-radius: 8px;
  overflow: hidden;
  margin-top: 32px;
}
.metric {
  background: var(--panel);
  padding: 18px 16px;
  text-align: center;
}
.metric-label {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  color: var(--text-muted);
  text-transform: uppercase;
}
.metric-value {
  font-family: var(--font-mono);
  font-size: 1.4rem;
  font-weight: 600;
  margin-top: 6px;
}
.metric-value.download { color: var(--signal-cyan); }
.metric-value.upload { color: var(--pulse-amber); }
.metric-value.ping,
.metric-value.jitter { color: var(--text-primary); }

.metric-sub {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-muted);
  margin-top: 2px;
}

@media (max-width: 640px) {
  .metrics { grid-template-columns: repeat(2, 1fr); }
}

/* Insights & history panel cards */
.panel-card {
  margin-top: 24px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: var(--radius);
  padding: 24px clamp(20px, 5vw, 32px);
}
.panel-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: var(--font-display);
  font-size: 0.95rem;
  margin-bottom: 16px;
}

.score-badge {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  padding: 5px 12px;
  border-radius: 100px;
  border: 1px solid var(--panel-line);
  color: var(--text-muted);
}
.score-badge.score-excellent { color: var(--signal-cyan); border-color: rgba(76,224,210,0.4); }
.score-badge.score-good { color: #8ee6a0; border-color: rgba(142,230,160,0.35); }
.score-badge.score-fair { color: var(--pulse-amber); border-color: rgba(255,180,84,0.4); }
.score-badge.score-poor { color: var(--alert-coral); border-color: rgba(255,107,94,0.4); }

.insights-list { display: flex; flex-direction: column; gap: 14px; }
.insight-row { display: flex; gap: 12px; align-items: flex-start; }
.insight-dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 6px; flex-shrink: 0; }
.insight-good .insight-dot { background: var(--signal-cyan); box-shadow: 0 0 8px var(--signal-cyan); }
.insight-warn .insight-dot { background: var(--pulse-amber); box-shadow: 0 0 8px var(--pulse-amber); }
.insight-bad .insight-dot { background: var(--alert-coral); box-shadow: 0 0 8px var(--alert-coral); }
.insight-title { font-weight: 600; font-size: 0.9rem; }
.insight-detail { color: var(--text-muted); font-size: 0.85rem; margin-top: 2px; line-height: 1.5; }

.history-table { width: 100%; border-collapse: collapse; }
.history-table td {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--text-muted);
  padding: 8px 4px;
  border-bottom: 1px solid var(--panel-line);
}
.history-table tr:last-child td { border-bottom: none; }

/* Consent card: small, corner-anchored, non-blocking */
.consent-card {
  position: fixed;
  right: 20px;
  bottom: 20px;
  max-width: 340px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: 12px;
  padding: 18px 20px;
  box-shadow: 0 16px 40px rgba(0,0,0,0.45);
  z-index: 50;
  transition: opacity 0.3s, transform 0.3s;
}
[dir="rtl"] .consent-card { right: auto; left: 20px; }
.consent-card.consent-dismiss { opacity: 0; transform: translateY(8px); }
.consent-card p {
  margin: 0 0 14px;
  font-size: 0.85rem;
  color: var(--text-muted);
  line-height: 1.5;
}
.consent-card .consent-confirm { margin: 0; color: var(--signal-cyan); font-size: 0.85rem; }
.consent-actions { display: flex; gap: 10px; justify-content: flex-end; }
[dir="rtl"] .consent-actions { justify-content: flex-start; }
.btn-ghost, .btn-solid {
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 600;
  border-radius: 6px;
  padding: 8px 14px;
  cursor: pointer;
  border: 1px solid var(--panel-line);
}
.btn-ghost { background: transparent; color: var(--text-muted); }
.btn-ghost:hover { color: var(--text-primary); }
.btn-solid { background: var(--signal-cyan); color: #06110f; border-color: var(--signal-cyan); }

@media (max-width: 480px) {
  .consent-card { right: 12px; left: 12px; bottom: 12px; max-width: none; }
}

/* Methodology modal: shown once per browser, explains the server-location
   tradeoff honestly rather than a one-sided "we're better" claim. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Above the mobile nav drawer (120) and its scrim (110). At the old value
     of 100 the login dialog opened UNDERNEATH the open drawer, which is
     exactly what it looked like on a phone: the menu stayed in front and the
     dialog was stuck behind it. */
  z-index: 200;
  padding: 20px;
  overflow-y: auto;
}
.modal-overlay[hidden] {
  display: none;
}
.modal-card {
  background: var(--panel);
  border: 1px solid var(--panel-line);
  border-radius: 12px;
  padding: 28px 28px 24px;
  max-width: 480px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.5);
}
.modal-card h2 {
  font-family: var(--font-display);
  font-size: 1.1rem;
  margin: 0 0 14px;
}
.modal-card p {
  color: var(--text-muted);
  font-size: 0.88rem;
  line-height: 1.65;
  margin: 0 0 20px;
}
.modal-card .btn-solid {
  width: 100%;
  padding: 11px;
  font-size: 0.9rem;
}

/* Account modal: close button, tabs, and form fields. Also reused as the
   general "form inside a card" pattern (e.g. the WiFi QR generator page). */
/* The close control used to be a bare muted glyph with no background and no
   border — invisible against the panel until you knew to look for it. It now
   uses the same treatment as the primary button so it reads as a control, and
   it is 36px square so it is a comfortable tap target rather than the ~14px
   the glyph alone occupied. */
.modal-close-x {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--signal-cyan);
  border: none;
  border-radius: 9px;
  color: #06110f;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  line-height: 1;
  transition: filter 0.15s, transform 0.15s;
}
[dir="rtl"] .modal-close-x { right: auto; left: 14px; }
.modal-close-x:hover { filter: brightness(1.1); transform: scale(1.05); }
.modal-close-x:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }

/* Space for the close button so it never sits on top of the tabs. */
.modal-card { position: relative; }
.modal-card .account-tabs { padding-right: 44px; }
[dir="rtl"] .modal-card .account-tabs { padding-right: 0; padding-left: 44px; }

.account-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 20px;
  border-bottom: 1px solid var(--panel-line);
}
.account-tab {
  background: none;
  border: none;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: 0.88rem;
  font-weight: 600;
  padding: 8px 4px 10px;
  margin-right: 20px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
.account-tab.active { color: var(--signal-cyan); border-bottom-color: var(--signal-cyan); }

.account-form { display: flex; flex-direction: column; gap: 6px; }
.account-form[hidden] { display: none; }

.google-signin-wrap { display: flex; justify-content: center; margin: 4px 0 2px; min-height: 40px; }
.google-signin-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  max-width: 280px;
  padding: 10px 16px;
  border: 1px solid var(--panel-line);
  border-radius: 7px;
  background: var(--panel);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.86rem;
  text-decoration: none;
  transition: border-color 0.15s, background 0.15s;
}
.google-signin-link:hover { border-color: color-mix(in srgb, var(--signal-cyan) 55%, var(--panel-line)); background: color-mix(in srgb, var(--panel) 85%, var(--bg)); }
.google-signin-link::before {
  content: '';
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23FFC107' d='M43.6 20.5H42V20H24v8h11.3c-1.6 4.7-6.1 8-11.3 8-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.5 6.1 29.5 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.7-.4-3.5z'/%3E%3Cpath fill='%23FF3D00' d='M6.3 14.7l6.6 4.8C14.6 15.9 18.9 13 24 13c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.5 6.1 29.5 4 24 4c-7.7 0-14.4 4.4-17.7 10.7z'/%3E%3Cpath fill='%234CAF50' d='M24 44c5.4 0 10.3-2.1 14-5.5l-6.5-5.5c-2 1.4-4.6 2.3-7.5 2.3-5.2 0-9.6-3.3-11.3-8l-6.6 5.1C9.5 39.6 16.2 44 24 44z'/%3E%3Cpath fill='%231976D2' d='M43.6 20.5H42V20H24v8h11.3c-.8 2.3-2.2 4.2-4.1 5.6l6.5 5.5C39.8 37.6 44 32 44 24c0-1.3-.1-2.7-.4-3.5z'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
}
.account-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0 14px;
  color: var(--text-muted);
  font-size: 0.72rem;
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.account-divider::before, .account-divider::after { content: ''; flex: 1; height: 1px; background: var(--panel-line); }
.account-form label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-top: 10px;
}
.account-form input,
.account-form select {
  background: #0d1218;
  border: 1px solid var(--panel-line);
  color: var(--text-primary);
  padding: 10px 12px;
  border-radius: 6px;
  font-family: var(--font-body);
  font-size: 0.92rem;
  width: 100%;
}
[data-theme="light"] .account-form input,
[data-theme="light"] .account-form select {
  background: #f4f6f8;
}
.account-form input:focus,
.account-form select:focus { outline: 2px solid var(--signal-cyan); }
.account-hint { font-size: 0.76rem; color: var(--text-muted); margin: 4px 0 0; }
.account-error { color: var(--alert-coral); font-size: 0.82rem; min-height: 1.2em; margin-top: 8px; }

.account-btn {
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: auto !important;
  padding: 0 12px !important;
  font-size: 0.82rem !important;
}

/* Footer */
.site-footer {
  margin-top: 64px;
  padding: 32px 24px 48px;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.8rem;
  border-top: 1px solid var(--panel-line);
}
.site-footer a { color: var(--text-muted); margin: 0 8px; }

/* Ad placeholder panel. Named "promo-panel" rather than "ad-slot" —
   generic ad blockers commonly hide any element whose class name contains
   "ad-", which would otherwise hide this even before any real ad script
   goes in. */
/* THE BUG: these placeholders defaulted to `display: flex`, so the dashed
   "Ad space" box was shown to every visitor and only hidden if ads.js got far
   enough to hide it. It doesn't get that far in the most common case — a
   visitor who hasn't ticked the Advertising consent box makes ads.js return
   early, before any hiding happens — so real users just saw empty "Ad space"
   boxes on the live site.
   They now default to display:none and are only revealed when an ad network
   has actually put something in them (.ad-live, set by ads.js). Failing
   closed is the right default here: an unfilled ad slot should be invisible,
   not a dashed box announcing that an advert was meant to be there. */
.promo-panel {
  margin: 32px auto;
  /* A responsive AdSense display unit needs real height to render into;
     at 100px Google frequently declines to fill and the slot stays blank. */
  min-height: 280px;
  border: 1px dashed var(--panel-line);
  border-radius: 8px;
  display: none;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  font-size: 0.75rem;
  font-family: var(--font-mono);
}
.promo-panel.ad-live { display: block; border: none; }

/* Side ad rails: sit in the page margins outside the centered content column.
   Only shown when the viewport is wide enough that they won't crowd the test
   itself — below that width they're simply not rendered. Same naming note
   as above applies here too. */
.promo-rail {
  position: fixed;
  top: 120px;
  /* Widened from 160px.
     A responsive AdSense unit at 160px has almost no inventory to draw on —
     Google's responsive formats are built around 300px and up, so a 160px
     container reliably comes back blank even when everything else is right.
     300x600 (half page) is a standard, well-stocked size. */
  width: 300px;
  min-height: 600px;
  border: 1px dashed var(--panel-line);
  border-radius: 8px;
  /* Same fail-closed default as .promo-panel above. */
  display: none;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  font-size: 0.75rem;
  font-family: var(--font-mono);
  z-index: 5;
}
.promo-rail.ad-live { display: block; }
.promo-rail.ad-live .adsbygoogle { width: 300px; height: 600px; display: block; }

/* A real advertiser's image only — not the house-ad fallback, which keeps
   the min-height above so it still reads as a proper ad-sized placeholder
   rather than shrinking to a couple of lines of text. The base min-height
   exists for a network ad unit that needs guaranteed space to render
   into, but a real image has its own natural height, and that min-height
   just left empty space below it, framed by the box's own
   border-radius/background — exactly the "frame" that didn't match the
   image. NOTE: this alone isn't enough — later, higher-specificity rules
   (body.ads-reserved .promo-panel.ad-live and its @media siblings, added
   across several earlier releases) re-apply min-height and a stretching
   grid layout on top of this. The rule that actually wins is at the very
   end of this file, after all of them; this one stays as the sensible
   default for anywhere those overrides don't reach. */
.promo-panel.ad-live:has(.custom-ad-link),
.promo-rail.ad-live:has(.custom-ad-link) { min-height: 0; }

/* Direct-sold banner creative. The box resizes to the image, not the other
   way around — no object-fit:cover, no forced min-height on the image
   itself. A container that crops to a fixed shape is right for an ad
   NETWORK's fixed-size unit, but wrong for an advertiser's own creative:
   whatever aspect ratio they upload should render in full, uncropped, at
   the container's width. min-height on the box (.promo-panel/.promo-rail
   above) still keeps the house banner's box from collapsing to nothing
   before any image has loaded. */
.custom-ad-link { display: block; width: 100%; }
.custom-ad-link img, .custom-ad-link picture { display: block; width: 100%; height: auto; border-radius: 7px; }

/* House banner — PulseTest's own fallback when a placement hasn't been
   sold. Deliberately not a static image: a CSS-animated gradient sweep
   reads as "alive" the way a real ad creative does, without needing
   raster assets or separate desktop/mobile files the way real advertiser
   creative does — this one is just responsive by nature. */
.house-ad {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-height: inherit;
  overflow: hidden;
  border-radius: 7px;
  text-decoration: none;
  background: linear-gradient(120deg, color-mix(in srgb, var(--signal-cyan) 14%, var(--panel)) 0%, var(--panel) 45%, color-mix(in srgb, var(--signal-cyan) 14%, var(--panel)) 100%);
  background-size: 220% 220%;
  animation: house-ad-sweep 6s ease-in-out infinite;
  border: 1px solid var(--panel-line);
  transition: border-color 0.15s;
}
.house-ad:hover { border-color: color-mix(in srgb, var(--signal-cyan) 55%, var(--panel-line)); }
.house-ad-glow {
  position: absolute;
  inset: -40%;
  background: radial-gradient(circle, color-mix(in srgb, var(--signal-cyan) 22%, transparent) 0%, transparent 65%);
  animation: house-ad-pulse 3.2s ease-in-out infinite;
  pointer-events: none;
}
.house-ad-text {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  padding: 20px;
  font-family: var(--font-body);
}
.house-ad-text strong { color: var(--signal-cyan); font-size: 1rem; font-weight: 700; }
.house-ad-text span { color: var(--text-muted); font-size: 0.78rem; line-height: 1.4; max-width: 220px; }
.house-ad-bottom .house-ad-text { flex-direction: row; gap: 12px; padding: 16px 24px; }
.house-ad-bottom .house-ad-text span { max-width: none; }
@keyframes house-ad-sweep {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}
@keyframes house-ad-pulse {
  0%, 100% { opacity: 0.5; transform: scale(0.9); }
  50% { opacity: 1; transform: scale(1.05); }
}
@media (prefers-reduced-motion: reduce) {
  .house-ad, .house-ad-glow { animation: none; }
}

/* Reserved but not yet filled: the slot occupies its space silently. No
   border and no text, so a visitor sees clean margin rather than an empty
   dashed box announcing a missing advert. */
body.ads-reserved .promo-rail,
body.ads-reserved .promo-panel {
  display: block;
  /* A barely-there outline: enough that you can confirm the space is
     reserved, faint enough that a visitor reads it as margin rather than as
     a broken advert. Once a real ad fills it (.ad-live) the outline goes. */
  border: 1px dashed var(--panel-line);
  background: none;
  opacity: 0.5;
}
body.ads-reserved .promo-rail.ad-live,
body.ads-reserved .promo-panel.ad-live { border: none; opacity: 1; }
/* Centred in whatever margin is left beside the content column, so the rails
   sit in dead space rather than crowding the test. */
.promo-rail-left  { left:  max(12px, calc((100vw - var(--content-max)) / 4 - 150px)); }
.promo-rail-right { right: max(12px, calc((100vw - var(--content-max)) / 4 - 150px)); }
/* Below this the rails cannot coexist with the content column without
   crowding it: 1280 content + 2x160 rails + gutters needs ~1660px. Beats
   .ad-live deliberately — an ad that overlaps the speed test is worse than no
   ad. Google is fine with a unit not being rendered; it just isn't requested. */
/* Two 300px rails plus a 1040px column needs ~1700px. Between 1400 and 1700
   there is room for one, so the left rail is dropped and the right one kept —
   one served ad beats two that never fill. Below 1400 neither fits, both go,
   and the column reclaims the full width rather than leaving a gap for rails
   that aren't there. */
@media (max-width: 1699px) {
  .promo-rail-left,
  .promo-rail-left.ad-live,
  body.ads-reserved .promo-rail-left { display: none; }
  body.ads-reserved { --rail-reserve: 360px; --content-max: 1000px; }
}
@media (max-width: 1399px) {
  .promo-rail,
  .promo-rail.ad-live,
  body.ads-reserved .promo-rail { display: none; }
  body.ads-reserved { --rail-reserve: 0px; --content-max: 1280px; }
}

/* Static content pages (privacy) */
/* Deliberately NOT widened with .wrap. Legal and glossary text is
   long-form prose, and a 1280px measure is genuinely harder to read than a
   ~75-character line. Widening everything uniformly would have made the
   Privacy Policy worse, not better. */
.prose { padding: 48px 0 80px; max-width: 760px; }
.prose h1 { font-family: var(--font-display); }
.prose h2 { font-family: var(--font-display); font-size: 1.1rem; margin-top: 2em; }
/* Was hardcoded #c3cbd3 — a pale grey for dark backgrounds. On the light
   theme that gave roughly 1.7:1 contrast against a near-white panel, well
   under the 4.5:1 minimum for body text, which made the Glossary, Terms,
   Privacy and VPS pages genuinely hard to read. `--text-body` follows the
   active theme. */
.prose p, .prose li { color: var(--text-body); line-height: 1.7; }

:focus-visible { outline: 2px solid var(--signal-cyan); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* RTL support (Arabic) */
/* RTL support (Arabic) — gap-based flex layout handles spacing automatically
   when the whole row's direction flips, so no manual margin overrides needed
   here anymore (unlike the old margin-left hack this replaced). */
[dir="rtl"] .insight-row { flex-direction: row-reverse; }
[dir="rtl"] .metrics, [dir="rtl"] .instrument-top { direction: rtl; }

/* ═══════════════════════════════════════════════════════════════════════
   v17 — mobile header rebuild, install UI, granular consent, new metrics
   ═══════════════════════════════════════════════════════════════════════ */

/* --- Header layout -------------------------------------------------------
   The old header let one flex row wrap. Wrapping only spills items onto new
   lines in source order — it can't reorder — so on a phone the nav links
   landed ABOVE the logo and the controls were stranded on a third row.
   The header is now an explicit grid: logo, then nav, then a menu button
   that only exists on mobile. */

.site-header {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 16px;
  padding: 16px 24px;
  position: relative;
}

.nav-toggle { display: none; }

.site-nav {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  /* Separates the link group from the controls group. The links have their
     own tighter internal gap below, so the two groups read as distinct
     rather than one long undifferentiated row. */
  gap: 28px;
  flex-wrap: nowrap;
}

/* BUG FIX: this file's original .nav-links rule (much earlier, shared with
   older markup) sets flex-wrap: wrap, and nothing above overrode it for
   the current desktop layout. .site-nav going nowrap only stops ITS direct
   children from wrapping — .nav-links is one of those children, and is
   itself a flex container whose own child links kept wrapping onto a
   second row once a fifth link (My Account) pushed the row past whatever
   width was left after .nav-controls. Nowrap here stops that; the
   980px breakpoint below (raised from 860px for the same reason) is what
   actually gives the row enough room before the drawer takes over. */
.nav-links {
  flex-wrap: nowrap;
  /* Was 20px, which read as cramped once five links sat side by side —
     particularly "VPS Speed Test" and "My Account", whose two-word labels
     ran together visually. Padding on the anchors rather than gap alone also
     enlarges the click target. */
  gap: 4px;
}
.site-nav .nav-links a {
  padding: 8px 12px;
  border-radius: 7px;
  /* Multi-word labels were wrapping onto two lines inside their own link,
     which is what made the row look crowded and uneven. */
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.site-nav .nav-links a:hover { background: rgba(255, 255, 255, 0.05); }
[data-theme="light"] .site-nav .nav-links a:hover { background: rgba(0, 0, 0, 0.05); }

.nav-controls { gap: 12px; }

/* Skip link — keyboard users shouldn't have to tab the whole nav to reach
   the one control that matters on this page. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 8px;
  background: var(--signal-cyan);
  color: #06110f;
  padding: 10px 16px;
  border-radius: 6px;
  font-weight: 600;
  z-index: 200;
}
.skip-link:focus { left: 16px; }

/* THE ARABIC LAYOUT BUG: this rule was missing. "left: -9999px" is a
   physical offset, not a logical one. In an LTR document that just parks
   the link off the left edge, harmless. But once <html dir="rtl"> is set,
   the document's writing direction flips and the browser's horizontal
   scroll anchor moves to the right edge — so an element sitting 9999px to
   the *physical* left is no longer "off to the side", it's off the start
   of a now-enormous scrollable area. That's what stretched the whole
   page and pushed all real content out to the right, forcing you to keep
   scrolling to reach it. It also explains why the menu "stopped working"
   in Arabic: the menu wasn't broken, it was rendered off-screen along
   with everything else, so taps were landing on empty space.
   Mirroring the offset for RTL (and adding the overflow-x:hidden safety
   net above) fixes both symptoms from this one root cause. */
[dir="rtl"] .skip-link { left: auto; right: -9999px; }
[dir="rtl"] .skip-link:focus { right: 16px; left: auto; }

@media (max-width: 1239px) {
  .site-header {
    grid-template-columns: 1fr auto;
    padding: 14px 18px;
    /* backdrop-filter creates a containing block for fixed descendants in
       Chromium/WebKit. Because the drawer lives inside this header, its
       top/bottom offsets were being resolved against the 68px header rather
       than the phone viewport, so the open menu looked completely empty. */
    backdrop-filter: none;
  }

  /* Hamburger: 44px so it meets the minimum comfortable touch target. The
     old controls were ~32px and hard to hit accurately. */
  .nav-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;
    width: 44px;
    height: 44px;
    background: none;
    border: 1px solid var(--panel-line);
    border-radius: 10px;
    cursor: pointer;
    padding: 0;
  }
  .nav-toggle span {
    display: block;
    width: 18px;
    height: 2px;
    background: var(--text-primary);
    border-radius: 2px;
    transition: transform 0.25s, opacity 0.2s;
  }
  .nav-toggle-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav-toggle-open span:nth-child(2) { opacity: 0; }
  .nav-toggle-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
  .nav-toggle-open {
    position: fixed;
    top: 12px;
    right: 18px;
    z-index: 130;
    background: var(--panel);
  }
  [dir="rtl"] .nav-toggle-open {
    right: auto;
    left: 18px;
  }

  /* Drawer slides in from the trailing edge, full height, above everything. */
  .site-nav {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    height: 100vh;
    height: 100dvh;
    max-height: 100dvh;
    width: min(320px, 86vw);
    background: var(--panel);
    border-left: 1px solid var(--panel-line);
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    padding: 84px 20px 32px;
    transform: translateX(105%);
    transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
    z-index: 120;
    overflow-y: auto;
    overscroll-behavior: contain;
    box-shadow: -20px 0 50px rgba(0, 0, 0, 0.45);
  }
  [dir="rtl"] .site-nav {
    right: auto;
    left: 0;
    border-left: none;
    border-right: 1px solid var(--panel-line);
    transform: translateX(-105%);
  }
  .site-nav.nav-drawer-open { transform: translateX(0); }

  .nav-links {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    width: 100%;
  }
  .site-nav .nav-links a {
    display: block;
    padding: 15px 4px;
    font-size: 1rem;
    color: var(--text-primary);
    border-bottom: 1px solid var(--panel-line);
  }
  /* THE BUG: the rule above (2 classes + element) has higher specificity
     than the browser's own "[hidden] { display: none }" default (1
     attribute selector), so it was winning and showing WiFi QR / My
     Account in the drawer even while logged out — the `hidden` attribute
     account.js sets was being visually ignored. This adds the attribute
     into the selector so it out-specifies the rule above instead of
     fighting it, and actually hides the link again. */
  .site-nav .nav-links a[hidden] { display: none; }

  .nav-divider { display: none; }

  .nav-controls {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    width: 100%;
    margin-top: 24px;
  }
  .nav-controls .lang-switch { width: 100%; }
  .nav-controls .lang-switch button#langBtn,
  .nav-controls .theme-btn,
  .nav-controls .account-btn {
    width: 100% !important;
    max-width: none !important;
    height: 46px;
    border-radius: 10px;
    font-size: 0.92rem !important;
    padding: 0 16px !important;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  /* In the drawer the menu has room to open downward in flow rather than
     floating over content it can't fit next to. */
  .nav-controls .lang-menu {
    position: static;
    width: 100%;
    margin-top: 8px;
    box-shadow: none;
  }
  .nav-menu-title {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    padding: 0 4px 8px;
  }
}

.nav-scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 110;
  backdrop-filter: blur(2px);
}
.nav-scrim[hidden] { display: none; }
@media (min-width: 1240px) { .nav-scrim { display: none; } }

body.nav-locked { overflow: hidden; }
/* The drawer is a descendant of the sticky header, which forms its own
   stacking context. Raising only the child z-index cannot overtake the scrim
   outside that context; without this rule the scrim visibly covered the menu
   and intercepted every tap. */
body.nav-locked .site-header { z-index: 125; }

/* --- Service strip: the VPS CLI pitch, between the header and the test --- */
.service-strip {
  max-width: min(var(--content-max), calc(100% - 32px));
  margin: 10px auto 0;
  padding: 0 24px;
}
.service-strip-inner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 16px;
  border-radius: 12px;
  border: 1px solid rgba(76, 224, 210, 0.28);
  background:
    linear-gradient(115deg, rgba(76, 224, 210, 0.10), rgba(255, 180, 84, 0.06) 60%, transparent);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.2s, transform 0.2s;
}
.service-strip-inner:hover {
  border-color: rgba(76, 224, 210, 0.55);
  transform: translateY(-1px);
}
.service-strip-badge {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--signal-cyan);
  border: 1px solid rgba(76, 224, 210, 0.4);
  border-radius: 100px;
  padding: 4px 10px;
}
.service-strip-text { flex: 1; min-width: 0; }
.service-strip-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--text-primary);
}
.service-strip-sub {
  font-size: 0.76rem;
  color: var(--text-muted);
  margin-top: 2px;
}
.service-strip-arrow { color: var(--signal-cyan); font-size: 1.1rem; flex-shrink: 0; }
[dir="rtl"] .service-strip-arrow { transform: scaleX(-1); }

@media (max-width: 640px) {
  .service-strip { padding: 0 18px; }
  .service-strip-inner { flex-wrap: wrap; gap: 10px; padding: 14px 16px; }
  .service-strip-arrow { display: none; }
  .service-strip-title { font-size: 0.9rem; }
}

/* --- Progress bar across the instrument panel --- */
.test-progress {
  height: 3px;
  background: var(--panel-line);
  border-radius: 100px;
  overflow: hidden;
  margin: 0 0 22px;
}
.test-progress-bar {
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--signal-cyan), var(--pulse-amber));
  border-radius: 100px;
  transition: width 0.3s ease;
}

/* --- Phase steps --- */
.phase-steps {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}
.phase-step {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  border: 1px solid var(--panel-line);
  border-radius: 100px;
  padding: 5px 12px;
  transition: color 0.25s, border-color 0.25s;
}
.phase-step.phase-active { color: var(--signal-cyan); border-color: rgba(76, 224, 210, 0.45); }
.phase-step.phase-complete { color: var(--text-primary); border-color: var(--panel-line); }

/* --- Metrics: six cells now (bufferbloat + consistency) --- */
.metrics { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 640px) { .metrics { grid-template-columns: repeat(2, 1fr); } }

.metric-value.bloat-good { color: var(--signal-cyan); }
.metric-value.bloat-warn { color: var(--pulse-amber); }
.metric-value.bloat-bad { color: var(--alert-coral); }

.trend-note { color: var(--text-muted); font-size: 0.8rem; margin: 12px 0 0; }

/* --- Install prompt --- */
.install-banner {
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: min(var(--content-max), calc(100% - 32px));
  margin: 20px auto 0;
  padding: 14px 20px;
  border: 1px solid var(--panel-line);
  border-radius: 12px;
  background: var(--panel);
}
.install-banner[hidden] { display: none; }
.install-banner-icon { width: 38px; height: 38px; border-radius: 9px; flex-shrink: 0; }
.install-banner-text { flex: 1; min-width: 0; }
.install-banner-title { font-weight: 600; font-size: 0.92rem; }
.install-banner-sub { color: var(--text-muted); font-size: 0.8rem; margin-top: 2px; }
.install-banner-actions { display: flex; gap: 8px; flex-shrink: 0; }

@media (max-width: 640px) {
  .install-banner { margin: 16px 18px 0; flex-wrap: wrap; }
  .install-banner-actions { width: 100%; }
  .install-banner-actions .btn-solid { flex: 1; }
}

.install-steps { counter-reset: step; margin: 0; padding: 0; list-style: none; }
.install-steps li {
  position: relative;
  padding: 0 0 14px 34px;
  color: var(--text-muted);
  font-size: 0.88rem;
  line-height: 1.6;
}
.install-steps li::before {
  counter-increment: step;
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(76, 224, 210, 0.14);
  color: var(--signal-cyan);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
[dir="rtl"] .install-steps li { padding: 0 34px 14px 0; }
[dir="rtl"] .install-steps li::before { left: auto; right: 0; }

/* --- Granular consent --- */
.consent-card { max-width: 400px; }
.consent-actions { flex-wrap: wrap; }
.consent-actions .btn-ghost,
.consent-actions .btn-solid { flex: 1; min-width: 96px; }

.consent-option {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 14px 0;
  border-bottom: 1px solid var(--panel-line);
}
.consent-option:last-of-type { border-bottom: none; }
.consent-option input[type="checkbox"] {
  width: 18px;
  height: 18px;
  margin-top: 2px;
  flex-shrink: 0;
  accent-color: var(--signal-cyan);
  cursor: pointer;
}
.consent-option-disabled { opacity: 0.45; }
.consent-option-disabled input { cursor: not-allowed; }
.consent-option-title { font-weight: 600; font-size: 0.88rem; }
.consent-option-desc { color: var(--text-muted); font-size: 0.8rem; line-height: 1.55; margin-top: 3px; }
.consent-option-locked {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--signal-cyan);
  margin-left: 6px;
}

.modal-card-wide { max-width: 560px; max-height: 86vh; overflow-y: auto; }

/* --- Copy-to-clipboard command block (CLI page) --- */
.command-block {
  position: relative;
  background: #0d1218;
  border: 1px solid var(--panel-line);
  border-radius: 10px;
  padding: 16px 56px 16px 18px;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--text-primary);
  overflow-x: auto;
  white-space: pre;
  margin: 12px 0 20px;
  line-height: 1.6;
}
[data-theme="light"] .command-block { background: #eef1f4; }
.command-block .cmd-prompt { color: var(--signal-cyan); user-select: none; }
.copy-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  color: var(--text-muted);
  border-radius: 7px;
  padding: 7px 11px;
  font-family: var(--font-body);
  font-size: 0.74rem;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
[dir="rtl"] .copy-btn { right: auto; left: 10px; }
.copy-btn:hover { color: var(--text-primary); border-color: var(--text-muted); }
.copy-btn.copied { color: var(--signal-cyan); border-color: rgba(76, 224, 210, 0.5); }

.terminal {
  background: #0d1218;
  border: 1px solid var(--panel-line);
  border-radius: 10px;
  overflow: hidden;
  margin: 20px 0 28px;
}
[data-theme="light"] .terminal { background: #10161d; }
.terminal-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--panel-line);
  background: rgba(255, 255, 255, 0.02);
}
.terminal-dot { width: 10px; height: 10px; border-radius: 50%; }
.terminal-title {
  margin-left: 8px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-muted);
}
.terminal-body {
  padding: 16px 18px;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  line-height: 1.75;
  color: var(--terminal-fg);
  overflow-x: auto;
  white-space: pre;
}
.terminal-body .t-cyan { color: var(--signal-cyan); }
.terminal-body .t-amber { color: var(--pulse-amber); }
.terminal-body .t-green { color: #8ee6a0; }
.terminal-body .t-dim { color: var(--text-muted); }

/* --- Data-collection disclosure table (privacy page) --- */
.data-table { width: 100%; border-collapse: collapse; margin: 18px 0 28px; font-size: 0.85rem; }
.data-table th {
  text-align: left;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  padding: 10px 12px 10px 0;
  border-bottom: 1px solid var(--panel-line);
  font-weight: 500;
}
[dir="rtl"] .data-table th { text-align: right; padding: 10px 0 10px 12px; }
.data-table td {
  padding: 11px 12px 11px 0;
  border-bottom: 1px solid var(--panel-line);
  color: var(--text-body);
  vertical-align: top;
  line-height: 1.55;
}
[dir="rtl"] .data-table td { padding: 11px 0 11px 12px; }
.data-table tr:last-child td { border-bottom: none; }
.tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  padding: 3px 8px;
  border-radius: 100px;
  border: 1px solid var(--panel-line);
  color: var(--text-muted);
  white-space: nowrap;
}
.tag-consent { color: var(--signal-cyan); border-color: rgba(76, 224, 210, 0.4); }
.tag-essential { color: var(--text-primary); }
.tag-never { color: var(--alert-coral); border-color: rgba(255, 107, 94, 0.4); }

.callout {
  border-left: 3px solid var(--signal-cyan);
  background: rgba(76, 224, 210, 0.05);
  padding: 16px 20px;
  border-radius: 0 8px 8px 0;
  margin: 22px 0;
}
[dir="rtl"] .callout {
  border-left: none;
  border-right: 3px solid var(--signal-cyan);
  border-radius: 8px 0 0 8px;
}
.callout p { margin: 0; font-size: 0.88rem; }

/* --- Feature grid (account benefits) --- */
.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1px;
  background: var(--panel-line);
  border: 1px solid var(--panel-line);
  border-radius: 10px;
  overflow: hidden;
  margin: 20px 0 28px;
}
.feature-cell { background: var(--panel); padding: 20px; }
.feature-cell h3 {
  font-family: var(--font-display);
  font-size: 0.92rem;
  margin: 0 0 6px;
  color: var(--text-primary);
}
.feature-cell p { color: var(--text-muted); font-size: 0.84rem; line-height: 1.6; margin: 0; }

/* --- Mobile density pass on the instrument itself --- */
@media (max-width: 640px) {
  .instrument { margin: 20px auto 20px; padding: 24px 18px; }
  .connection-info { font-size: 0.76rem; }
  .connection-row { display: flex; justify-content: space-between; gap: 10px; }
  .connection-value { text-align: right; word-break: break-all; }
  [dir="rtl"] .connection-value { text-align: left; }
  .readout-value { font-size: clamp(3rem, 17vw, 4.5rem); }
  .trace-wrap { margin: 20px 0 24px; }
  #trace { height: 96px; }
  .btn-start { width: 100%; padding: 16px 24px; font-size: 1.05rem; }
  .panel-card { padding: 20px 18px; }
  /* Prose ran to within a few pixels of the screen edge on a phone, which
     made the Glossary, Terms and Privacy pages genuinely hard to read —
     the eye has no margin to rest against. The .wrap padding alone was not
     enough once the page had no side panels. */
  .prose { padding: 28px 20px 60px; }
  .prose h1 { font-size: 1.55rem; }
  .prose h2 { font-size: 1.05rem; }
  .prose p, .prose li { font-size: 0.94rem; }
  /* Tables in the privacy policy would otherwise force horizontal scroll on
     the whole page rather than just themselves. */
  .prose .table-wrap { margin-left: -4px; margin-right: -4px; }
  .site-footer { margin-top: 40px; }
  .history-table td { font-size: 0.74rem; padding: 8px 3px; }
}

/* --- Footer: one row of links, wrapping cleanly on narrow screens.
   Separators are drawn with CSS rather than typed "·" characters between
   anchors, so a wrapped line never starts with an orphaned dot. --- */
.footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 0;
}
.footer-links a { padding: 4px 14px; position: relative; }
.footer-links a:not(:last-child)::after {
  content: "";
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 1px;
  height: 11px;
  background: var(--panel-line);
}
[dir="rtl"] .footer-links a:not(:last-child)::after { right: auto; left: 0; }
.footer-note { margin-top: 10px; }

@media (max-width: 560px) {
  .footer-links a { padding: 6px 10px; font-size: 0.85rem; }
  /* Dividers look broken once links wrap onto several short rows. */
  .footer-links a::after { display: none; }
}

/* --- Privacy controls panel (privacy policy page) --- */
.controls-panel {
  border: 1px solid var(--panel-line);
  border-radius: 10px;
  overflow: hidden;
  margin: 18px 0 30px;
}
.control-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 18px 20px;
  background: var(--panel);
}
.control-row + .control-row { border-top: 1px solid var(--panel-line); }
.control-title { font-weight: 600; font-size: 0.92rem; }
.control-desc {
  color: var(--text-muted);
  font-size: 0.83rem;
  line-height: 1.55;
  margin-top: 4px;
  max-width: 52ch;
}
.control-row button { flex-shrink: 0; }

@media (max-width: 600px) {
  .control-row { flex-direction: column; align-items: stretch; gap: 14px; }
  .control-row button { width: 100%; }
}

/* --- Numbered installation steps (VPS page) --- */
.lede {
  font-size: 1.02rem;
  line-height: 1.75;
  color: var(--text-primary);
}

.step {
  display: flex;
  gap: 18px;
  padding: 22px 0;
  border-top: 1px solid var(--panel-line);
}
.step:first-of-type { border-top: none; padding-top: 8px; }

.step-num {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.92rem;
  color: var(--signal-cyan);
  background: rgba(76, 224, 210, 0.10);
  border: 1px solid rgba(76, 224, 210, 0.35);
}
.step-body { flex: 1; min-width: 0; }
.step-body h3 {
  font-family: var(--font-display);
  font-size: 1.05rem;
  margin: 4px 0 8px;
}
.step-body p { margin: 0 0 12px; }
.step-body .command-block { margin: 12px 0; }
.step-note {
  color: var(--text-muted);
  font-size: 0.84rem;
  line-height: 1.6;
  margin: 10px 0 0 !important;
}

/* advertise.html: the "Placements" list and the contact-details block. */
.glossary-list { color: var(--text-muted); font-size: 0.9rem; line-height: 1.7; padding-inline-start: 20px; margin: 10px 0; }
.glossary-list strong { color: var(--text-primary); }
.contact-block { margin: 14px 0; display: flex; flex-direction: column; gap: 10px; }
.contact-row { display: flex; flex-wrap: wrap; gap: 4px 8px; font-size: 0.92rem; }
.contact-label { color: var(--text-muted); font-weight: 600; white-space: nowrap; }
.contact-label::after { content: ':'; }
.contact-value { color: var(--text-primary); word-break: break-word; }
.contact-empty { color: var(--text-muted); font-size: 0.9rem; }

/* Install Options — OS picker on installation.html. Same accent language as
   the rest of the page (signal-cyan active state, panel/panel-line surfaces)
   so it reads as part of this site, not a bolted-on widget. */
.os-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 14px 0 18px;
}
.os-tab {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--panel);
  border: 1px solid var(--panel-line);
  color: var(--text-muted);
  border-radius: 999px;
  padding: 9px 16px 9px 12px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.84rem;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.os-tab-icon { width: 17px; height: 17px; flex-shrink: 0; opacity: 0.75; transition: opacity 0.15s; }
.os-tab:hover { color: var(--text-primary); border-color: var(--text-muted); }
.os-tab:hover .os-tab-icon { opacity: 1; }
.os-tab.active {
  color: var(--signal-cyan);
  border-color: rgba(76, 224, 210, 0.5);
  background: rgba(76, 224, 210, 0.10);
}
.os-tab.active .os-tab-icon { opacity: 1; }
.os-tab:focus-visible { outline: 2px solid var(--signal-cyan); outline-offset: 2px; }

.os-panel {
  border: 1px solid var(--panel-line);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--panel) 88%, var(--bg));
  padding: 18px 20px 4px;
  margin-bottom: 28px;
}
.os-panel .command-block:last-of-type { margin-bottom: 16px; }

@media (max-width: 560px) {
  .os-tab { flex: 1 1 calc(50% - 8px); justify-content: center; padding: 10px 10px; font-size: 0.8rem; }
  .os-panel { padding: 16px 14px 4px; }
  .step { gap: 12px; }
  .step-num { width: 28px; height: 28px; font-size: 0.82rem; }
  .step-body h3 { font-size: 0.98rem; }
  /* Long one-line install commands are the whole point of this page, so they
     scroll horizontally rather than wrapping into something un-copyable. */
  /* On a phone the copy button was absolutely positioned over the command
     text, covering the end of the very line the button exists to copy. Below
     this width the button drops onto its own full-width row underneath the
     command instead of floating on top of it. */
  .command-block {
    font-size: 0.78rem;
    padding: 14px 14px 12px;
    white-space: pre-wrap;
    word-break: break-all;
  }
  .command-block .copy-btn {
    position: static;
    display: block;
    width: 100%;
    margin-top: 12px;
    padding: 10px;
    font-size: 0.8rem;
  }
  [dir="rtl"] .command-block .copy-btn { left: auto; right: auto; }
}

/* --- CAPTCHA --- */
.captcha-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 4px 0 10px;
}
.captcha-image {
  flex: 1;
  min-height: 64px;
  border: 1px solid var(--panel-line);
  border-radius: 8px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  background: #0d1218;
  /* The SVG is authored at 200x64 but the modal is narrower on a phone, so it
     scales to fit rather than overflowing or being cropped. */
  color: var(--text-muted);
  font-size: 0.78rem;
  text-align: center;
  padding: 4px;
}
.captcha-image svg { width: 100%; height: auto; max-width: 200px; display: block; }
.captcha-image.captcha-loading { opacity: 0.4; }

.captcha-refresh {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  border-radius: 8px;
  border: 1px solid var(--panel-line);
  background: none;
  color: var(--text-muted);
  font-size: 1.15rem;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s, transform 0.3s;
}
.captcha-refresh:hover {
  color: var(--signal-cyan);
  border-color: rgba(76, 224, 210, 0.45);
  transform: rotate(180deg);
}

.captcha-input {
  font-family: var(--font-mono);
  letter-spacing: 0.32em;
  text-transform: uppercase;
  text-align: center;
  font-size: 1.05rem;
}

/* --- Legal pages --- */
.legal-meta { color: var(--text-muted); font-size: 0.84rem; margin-top: -6px; }
.callout-warn {
  border-left-color: var(--pulse-amber);
  background: rgba(255, 180, 84, 0.06);
}
[dir="rtl"] .callout-warn { border-right-color: var(--pulse-amber); }
#legalContent h2 { margin-top: 34px; }
#legalContent .table-wrap { overflow-x: auto; }


/* Build version in the footer — so "which version am I looking at?" is
   answerable from any page without opening DevTools or curling an endpoint. */
.footer-version {
  margin-top: 10px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--text-muted);
  opacity: 0.75;
}

/* ═══════════════════════════════════════════════════════════════════════
   Header controls — language, theme, account
   ═══════════════════════════════════════════════════════════════════════
   These were three unrelated shapes sitting in a row: a pill-shaped account
   button, a small circular icon button, and a wider dropdown. Different
   heights, different corner radii, different weights, so the group read as
   leftovers rather than a control cluster.
   They now share one height, one radius and one border treatment, and sit in
   a bordered group that separates them from the navigation links. */
@media (min-width: 1240px) {
  .nav-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-left: 20px;
    margin-left: 4px;
    border-left: 1px solid var(--panel-line);
  }
  [dir="rtl"] .nav-controls {
    padding-left: 0;
    padding-right: 20px;
    border-left: none;
    border-right: 1px solid var(--panel-line);
  }

  /* One shared shape for every control in the group. */
  .nav-controls .theme-btn,
  .nav-controls .account-btn,
  .nav-controls .lang-switch > button {
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 0 14px;
    border: 1px solid var(--panel-line);
    border-radius: 8px;
    background: transparent;
    color: var(--text-muted);
    font-family: var(--font-body);
    font-size: 0.85rem;
    line-height: 1;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
    white-space: nowrap;
  }
  .nav-controls .theme-btn:hover,
  .nav-controls .account-btn:hover,
  .nav-controls .lang-switch > button:hover {
    color: var(--text-primary);
    border-color: var(--text-muted);
    background: rgba(255, 255, 255, 0.04);
  }
  [data-theme="light"] .nav-controls .theme-btn:hover,
  [data-theme="light"] .nav-controls .account-btn:hover,
  [data-theme="light"] .nav-controls .lang-switch > button:hover {
    background: rgba(0, 0, 0, 0.04);
  }

  /* The theme toggle is icon-only, so it gets a square footprint instead of
     a wide button with a lone glyph rattling around inside it. */
  .nav-controls .theme-btn[aria-label] {
    width: 36px;
    padding: 0;
    font-size: 0.95rem;
  }

  /* Signed in: the account button carries the email, so it needs to be the
     emphasised item in the group — but capped, because a long address would
     otherwise push the whole header out of shape. */
  .nav-controls .account-btn.is-signed-in {
    color: var(--signal-cyan);
    border-color: rgba(76, 224, 210, 0.4);
    max-width: 190px;
    overflow: hidden;
    text-overflow: ellipsis;
    display: inline-block;
    line-height: 34px;
  }
}

/* The language dropdown sat flush against the button edge. */
.lang-switch { position: relative; }
.lang-menu {
  min-width: 100%;
  margin-top: 6px;
  border-radius: 8px;
  overflow: hidden;
}
.lang-menu button {
  width: 100%;
  text-align: left;
  padding: 9px 14px;
  font-size: 0.85rem;
}
[dir="rtl"] .lang-menu button { text-align: right; }


/* ═══════════════════════════════════════════════════════════════════════
   Header width is independent of the content column
   ═══════════════════════════════════════════════════════════════════════
   `.site-header` carries `.wrap`, so it inherited `--content-max` — which
   narrows to 1000-1040px once ad space is reserved. Two consequences, both
   visible: the header sat at a different width on pages depending on whether
   the ad config had loaded, and on any one page it visibly JUMPED narrower a
   moment after load, because the reservation is applied after an async fetch.

   Site chrome should not move when an advert is reserved. The header now has
   its own fixed measure on every page, in every ad configuration. */
.site-header.wrap {
  max-width: min(1440px, calc(100% - 32px));
}
@media (max-width: 640px) {
  .site-header.wrap { max-width: 100%; }
}

/* The footer had the same inheritance, and the same jump. */
.site-footer {
  max-width: min(1280px, calc(100% - 32px));
  margin-left: auto;
  margin-right: auto;
}

/* --- WiFi QR: saved networks --- */
.wifi-remember {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 4px 0 16px;
  cursor: pointer;
}
.wifi-remember input { margin-top: 3px; flex-shrink: 0; accent-color: var(--signal-cyan); }
.wifi-remember span { display: block; font-size: 0.88rem; }
.wifi-remember small {
  display: block;
  color: var(--text-muted);
  font-size: 0.78rem;
  line-height: 1.5;
  margin-top: 3px;
}

.saved-wrap {
  margin-top: 26px;
  padding-top: 20px;
  border-top: 1px solid var(--panel-line);
}
.saved-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.btn-xs-link {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 0.72rem;
  text-decoration: underline;
  cursor: pointer;
  font-family: inherit;
}
.btn-xs-link:hover { color: var(--alert-coral); }
.saved-note {
  color: var(--text-muted);
  font-size: 0.79rem;
  line-height: 1.55;
  margin: 8px 0 14px;
}
.saved-net {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--panel-line);
  border-radius: 8px;
  margin-bottom: 8px;
  overflow: hidden;
}
.saved-net-name {
  flex: 1;
  text-align: left;
  background: none;
  border: none;
  color: var(--text-primary);
  padding: 12px 14px;
  font-family: var(--font-mono);
  font-size: 0.87rem;
  cursor: pointer;
  /* An SSID can be long; keep the row a fixed height. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
[dir="rtl"] .saved-net-name { text-align: right; }
.saved-net-name:hover { background: rgba(255, 255, 255, 0.04); color: var(--signal-cyan); }
[data-theme="light"] .saved-net-name:hover { background: rgba(0, 0, 0, 0.04); }
.saved-net-remove {
  background: none;
  border: none;
  border-left: 1px solid var(--panel-line);
  color: var(--text-muted);
  padding: 12px 14px;
  cursor: pointer;
  font-size: 0.85rem;
}
[dir="rtl"] .saved-net-remove { border-left: none; border-right: 1px solid var(--panel-line); }
.saved-net-remove:hover { color: var(--alert-coral); }

/* ═══════════════════════════════════════════════════════════════════════
   WiFi QR page layout
   ═══════════════════════════════════════════════════════════════════════
   The form was capped at 360px inside a full-width panel, leaving most of
   the page empty while the output sat underneath. Two columns on desktop:
   inputs on the left, the generated code and saved networks on the right,
   so the result appears beside the form rather than below the fold. */
.wifi-grid {
  display: grid;
  grid-template-columns: minmax(300px, 380px) 1fr;
  gap: 36px;
  align-items: start;
}
@media (max-width: 780px) {
  .wifi-grid { grid-template-columns: 1fr; gap: 24px; }
}

.wifi-col-out {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  min-height: 240px;
}
@media (max-width: 780px) {
  .wifi-col-out { align-items: stretch; }
}

/* Checkbox rows.
   These inherited .account-form's label styling — uppercase, monospace,
   block display — which is right for a field label and completely wrong for
   a checkbox caption. That inheritance is what stacked the text into a
   narrow vertical column of capitals. Reset explicitly. */
.wifi-form .wifi-check {
  display: flex !important;
  flex-direction: row !important;
  align-items: flex-start;
  gap: 10px;
  margin: 6px 0 4px;
  cursor: pointer;
  /* Undo the field-label treatment */
  text-transform: none;
  letter-spacing: normal;
  font-family: var(--font-body);
  font-size: 0.88rem;
  color: var(--text-primary);
}
.wifi-form .wifi-check input[type="checkbox"] {
  width: 17px;
  height: 17px;
  margin: 1px 0 0;
  flex-shrink: 0;
  accent-color: var(--signal-cyan);
  cursor: pointer;
}
.wifi-form .wifi-check span { display: block; }
.wifi-form .wifi-check small {
  display: block;
  color: var(--text-muted);
  font-size: 0.78rem;
  line-height: 1.5;
  margin-top: 4px;
  text-transform: none;
  letter-spacing: normal;
}
.wifi-check-block { margin-bottom: 16px; }
.wifi-form button[type="submit"] { margin-top: 12px; }

/* The saved list belongs in the output column, full width of it. */
.wifi-col-out .saved-wrap { width: 100%; max-width: 420px; }

/* ═══════════════════════════════════════════════════════════════════════
   v38 visual system: quieter surfaces, clear hierarchy, and the primary
   action visible before the diagnostic trace. Existing component tokens are
   retained so every older page continues to inherit light/dark/RTL support.
   ═══════════════════════════════════════════════════════════════════════ */
.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  min-height: 70px;
  padding-top: 16px;
  padding-bottom: 16px;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(18px);
}
/* A filtered ancestor becomes the containing block for fixed descendants in
   Chromium/WebKit. The mobile drawer lives inside the header, so the desktop
   blur must be disabled again here, after the v38 header rule in cascade
   order; otherwise its 100dvh is measured from the short header and the
   opened drawer appears empty. */
@media (max-width: 1239px) {
  .site-header {
    backdrop-filter: none;
    background: var(--bg);
  }
}
.site-nav a { font-size: .88rem; font-weight: 600; color: var(--text-body); }
.site-nav a:hover { color: var(--signal-cyan); }

.hero-v38 {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(260px, .85fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: center;
  padding: clamp(42px, 5vw, 68px) 0 28px;
}
.hero-eyebrow, .section-kicker {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--signal-cyan);
  font-family: var(--font-mono);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.hero-eyebrow > span:first-child { width: 28px; height: 1px; background: var(--signal-cyan); }
.hero-copy h1 {
  max-width: 720px;
  margin: 16px 0;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 4.5vw, 4.65rem);
  line-height: 1.02;
  letter-spacing: -.045em;
}
.hero-copy > p { max-width: 640px; margin: 0; color: var(--text-body); font-size: clamp(.94rem, 1.2vw, 1.04rem); line-height: 1.7; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; }
.hero-primary { padding: 12px 22px; }
.hero-proof { display: grid; border: 1px solid var(--panel-line); border-radius: 16px; overflow: hidden; background: color-mix(in srgb, var(--panel) 80%, transparent); }
.hero-proof > div { display: grid; gap: 5px; padding: 18px 20px; border-bottom: 1px solid var(--panel-line); }
.hero-proof > div:last-child { border-bottom: 0; }
.hero-proof strong { font-family: var(--font-display); font-size: .92rem; }
.hero-proof span { color: var(--text-muted); font-size: .78rem; }

.instrument { margin-top: 24px; border-radius: 18px; background: linear-gradient(145deg, color-mix(in srgb, var(--panel) 96%, var(--signal-cyan) 4%), var(--panel)); box-shadow: 0 28px 80px rgba(0,0,0,.16); }
.connection-info { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 1px; overflow: hidden; margin: 0 0 18px; border: 1px solid var(--panel-line); border-radius: 10px; background: var(--panel-line); text-align: left; }
.connection-row { display: grid; gap: 5px; margin: 0; padding: 12px 14px; background: var(--panel); overflow: hidden; }
.connection-label { font-size: .64rem; letter-spacing: .08em; text-transform: uppercase; }
.connection-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .78rem; }
.readout { margin: 10px 0 16px; }
.readout-value { font-size: clamp(3.5rem, 9vw, 6.2rem); }
.start-row { margin: 18px 0 22px; }
.btn-start { min-width: 190px; padding: 15px 34px; }
.trace-wrap { margin: 0 0 18px; opacity: .92; }
#trace { height: 82px; }
.phase-steps { margin-bottom: 6px; }
.metrics { grid-template-columns: repeat(6, 1fr); margin-top: 24px; }
.metric { padding: 16px 10px; }
.metric-value { font-size: clamp(1rem, 1.5vw, 1.3rem); }

.account-value { display: grid; grid-template-columns: minmax(260px,.75fr) minmax(0,1.25fr); gap: clamp(28px,6vw,80px); align-items: start; margin: 72px 0 48px; padding: clamp(28px,5vw,56px); border: 1px solid var(--panel-line); border-radius: 18px; background: linear-gradient(145deg, rgba(76,224,210,.07), transparent 55%), var(--panel); }
.account-value h2 { margin: 12px 0; font-family: var(--font-display); font-size: clamp(1.8rem,3vw,3rem); line-height: 1.08; letter-spacing: -.035em; }
.account-value-copy p { margin: 0 0 24px; color: var(--text-body); line-height: 1.7; }
.feature-grid { display: grid; grid-template-columns: repeat(2,1fr); gap: 12px; }
.feature-grid article { min-height: 150px; padding: 20px; border: 1px solid var(--panel-line); border-radius: 12px; background: color-mix(in srgb,var(--panel) 88%,var(--bg)); }
.feature-grid article > span { color: var(--signal-cyan); font-family: var(--font-mono); font-size: .7rem; }
.feature-grid strong { display: block; margin: 18px 0 7px; font-family: var(--font-display); font-size: .95rem; }
.feature-grid p, .panel-copy { margin: 0; color: var(--text-muted); font-size: .82rem; line-height: 1.55; }

.plan-advisor-prompt { display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.plan-advisor-prompt p { max-width: 760px; margin: 7px 0 0; color: var(--text-muted); line-height: 1.55; }
.advisor-badge { padding: 5px 9px; border: 1px solid rgba(76,224,210,.28); border-radius: 100px; color: var(--signal-cyan); font-family: var(--font-mono); font-size: .65rem; text-transform: uppercase; letter-spacing: .08em; }
.plan-verdict { display: flex; gap: 12px; align-items: flex-start; padding: 16px; border: 1px solid var(--panel-line); border-radius: 10px; }
.plan-verdict-dot { width: 10px; height: 10px; margin-top: 4px; flex: 0 0 auto; border-radius: 50%; background: currentColor; box-shadow: 0 0 14px currentColor; }
.plan-verdict strong, .plan-verdict small { display: block; }
.plan-verdict small { margin-top: 5px; color: var(--text-muted); }
.plan-excellent, .plan-good { color: var(--signal-cyan); }
.plan-low { color: var(--pulse-amber); }
.plan-critical { color: var(--alert-coral); }
.plan-metrics-grid { display: grid; grid-template-columns: repeat(2,1fr); gap: 14px; margin: 18px 0; }
.plan-metric { padding: 15px; border: 1px solid var(--panel-line); border-radius: 10px; }
.plan-metric > div:first-child { display: flex; justify-content: space-between; gap: 14px; font-size: .8rem; }
.plan-metric small { color: var(--text-muted); font-family: var(--font-mono); }
.plan-progress { height: 7px; margin: 12px 0 8px; overflow: hidden; border-radius: 99px; background: var(--panel-line); }
.plan-progress span { display: block; height: 100%; border-radius: inherit; background: currentColor; }
.advisor-steps { display: grid; gap: 9px; margin: 18px 0 0; padding-inline-start: 24px; color: var(--text-body); font-size: .88rem; line-height: 1.55; }

.account-dashboard-grid { width: 100%; display: grid !important; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 16px; }
.account-dashboard-grid[hidden] { display: none !important; }
.account-dashboard-grid > .panel-card { margin-top: 0; }
.account-plan-card { grid-column: 1 / -1; }
.plan-form { max-width: 760px; margin-top: 18px; }
.plan-speed-fields { display: grid; grid-template-columns: repeat(2,1fr); gap: 14px; }
.account-form select { width: 100%; margin-bottom: 14px; padding: 11px 12px; color: var(--text-primary); background: var(--bg); border: 1px solid var(--panel-line); border-radius: 7px; }
.plan-saved-summary { min-height: 20px; margin: 0 0 10px; color: var(--signal-cyan); font-family: var(--font-mono); font-size: .76rem; }
.inline-button { display: inline-block; margin-top: 16px; }

@media (max-width: 900px) {
  .hero-v38, .account-value { grid-template-columns: 1fr; }
  .hero-proof { grid-template-columns: repeat(3,1fr); }
  .hero-proof > div { border-right: 1px solid var(--panel-line); border-bottom: 0; }
  .hero-proof > div:last-child { border-right: 0; }
  .metrics { grid-template-columns: repeat(3,1fr); }
  .connection-info { grid-template-columns: repeat(2,1fr); }
}
@media (max-width: 640px) {
  .hero-v38 { padding-top: 38px; }
  .hero-copy h1 { font-size: clamp(2.15rem,10vw,3rem); }
  .hero-proof, .feature-grid, .account-dashboard-grid, .plan-metrics-grid, .plan-speed-fields { grid-template-columns: 1fr; }
  .hero-proof > div { border-right: 0; border-bottom: 1px solid var(--panel-line); }
  .instrument { margin-top: 12px; padding-top: 22px; }
  .connection-info { grid-template-columns: 1fr 1fr; }
  .connection-row { padding: 10px; }
  .plan-advisor-prompt { align-items: flex-start; flex-direction: column; }
  .account-value { margin-top: 48px; }
}

/* v38.1 layout normalization ------------------------------------------------
   Navigation links are anchored to the viewport centre, so a long signed-in
   email or a page-specific install control cannot push the menu sideways.
   The drawer breakpoint matches nav.js exactly. */
@media (min-width: 1240px) {
  .site-header {
    display: flex;
    position: sticky;
    min-height: 68px;
    padding: 14px 24px;
  }
  .site-header .logo { flex: 0 0 auto; }
  .site-nav { flex: 1 1 auto; justify-content: flex-end; position: static; }
  .site-header .nav-links {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    gap: 2px;
  }
  [dir="rtl"] .site-header .nav-links {
    left: auto;
    right: 50%;
    transform: translateX(50%);
  }
  .site-nav .nav-links a { padding: 8px 10px; font-size: .84rem; }
  .nav-controls { margin-inline-start: auto; }
}

.service-strip { max-width: min(1120px, calc(100% - 32px)); }

/* Advertising configuration must never draw empty boxes or resize the core
   experience. A placement becomes visible only after the loader installs a
   real unit. */
body.ads-reserved { --content-max: 1280px; }
body.ads-reserved .promo-rail,
body.ads-reserved .promo-panel { display: none; border: 0; opacity: 1; }
body.ads-reserved .promo-rail.ad-live,
body.ads-reserved .promo-panel.ad-live { display: block; }
.promo-rail-left,
body.ads-reserved .promo-rail-left,
.promo-rail-left.ad-live { display: none !important; }
.promo-rail-right { right: max(12px, calc((100vw - 1280px) / 2 - 308px)); }
@media (max-width: 1799px) {
  .promo-rail-right,
  .promo-rail-right.ad-live,
  body.ads-reserved .promo-rail-right { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   v40 — transparent navigation, deliberate advertising frame, ISP ratings
   ═══════════════════════════════════════════════════════════════════════ */

/* The navigation belongs to the page rather than sitting on a frosted slab.
   The mobile drawer keeps its own opaque surface, so legibility is preserved
   when it opens over page content. */
.site-header {
  background: transparent;
  backdrop-filter: none;
  border-bottom-color: color-mix(in srgb, var(--panel-line) 72%, transparent);
}

/* Cleaner, consistent control treatment across the public interface. */
.btn-solid,
.btn-ghost,
.btn-start {
  border-radius: 9px;
  min-height: 40px;
  font-family: var(--font-body);
  font-weight: 600;
  letter-spacing: -0.01em;
  transition: transform .16s ease, border-color .16s ease, background .16s ease, box-shadow .16s ease;
}
.btn-solid {
  box-shadow: 0 8px 22px color-mix(in srgb, var(--signal-cyan) 19%, transparent);
}
.btn-ghost {
  border-color: color-mix(in srgb, var(--panel-line) 86%, var(--text-muted));
  background: color-mix(in srgb, var(--panel) 56%, transparent);
}
.btn-solid:hover,
.btn-ghost:hover,
.btn-start:hover { transform: translateY(-1px); }
.nav-controls .theme-btn,
.nav-controls .account-btn,
.lang-switch button#langBtn {
  border-color: color-mix(in srgb, var(--panel-line) 80%, var(--text-muted));
  background: color-mix(in srgb, var(--panel) 38%, transparent);
  box-shadow: none;
}

/* Reserved advertising space is visible as a quiet layout frame as soon as
   the operator has a valid network configuration. The bottom placement is
   available on every public page except My Account. */
body.ads-reserved { --content-max: 1040px; }
body.ads-reserved .promo-panel {
  display: grid;
  place-items: center;
  min-height: 280px;
  margin: 40px auto 24px;
  border: 1px solid color-mix(in srgb, var(--panel-line) 76%, transparent);
  background: color-mix(in srgb, var(--panel) 34%, transparent);
  opacity: 1;
}
body.ads-reserved .promo-panel:not(.ad-live)::before,
body.ads-reserved .promo-rail:not(.ad-live)::before {
  content: 'Advertisement';
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  opacity: .72;
}
body.ads-reserved .promo-panel.ad-live {
  display: block;
  border: 0;
  background: transparent;
}

/* Two standard 300 × 600 rails fit at the 1536px CSS width commonly produced
   by a 1920px Windows display at 125% scaling. The content narrows to 880px
   in that band, then returns to 1040px on wider monitors. */
@media (min-width: 1520px) {
  body.ads-reserved { --content-max: 880px; }
  body.ads-reserved .promo-rail,
  body.ads-reserved .promo-rail-left,
  body.ads-reserved .promo-rail-right,
  body.ads-reserved .promo-rail.ad-live {
    display: grid !important;
    place-items: center;
    width: 300px;
    min-height: 600px;
    top: 96px;
    border: 1px solid color-mix(in srgb, var(--panel-line) 76%, transparent);
    background: color-mix(in srgb, var(--panel) 34%, transparent);
    opacity: 1;
  }
  body.ads-reserved .promo-rail.ad-live {
    display: block !important;
    border: 0;
    background: transparent;
  }
  .promo-rail-left,
  body.ads-reserved .promo-rail-left { left: max(12px, calc(50% - 756px)); }
  .promo-rail-right,
  body.ads-reserved .promo-rail-right { right: max(12px, calc(50% - 756px)); }
}
@media (min-width: 1720px) {
  body.ads-reserved { --content-max: 1040px; }
  .promo-rail-left,
  body.ads-reserved .promo-rail-left { left: max(12px, calc(50% - 840px)); }
  .promo-rail-right,
  body.ads-reserved .promo-rail-right { right: max(12px, calc(50% - 840px)); }
}
@media (max-width: 1519px) {
  .promo-rail,
  .promo-rail.ad-live,
  body.ads-reserved .promo-rail { display: none !important; }
  body.ads-reserved { --content-max: 1280px; }
}

/* v41 — placement-safe advertising.
   Rails behave like sticky fixed placements and are aligned to <main> by
   funding.js, so a service announcement can never sit underneath them. Keep the reserved
   frame for live slots too: an approved but temporarily unfilled/blocked ad
   remains a visible, stable location instead of making the bottom area seem
   missing and causing layout shift when a creative arrives. */
body.ads-reserved .promo-panel.ad-live {
  display: grid;
  place-items: stretch;
  min-height: 280px;
  border: 1px solid color-mix(in srgb, var(--panel-line) 76%, transparent);
  background: color-mix(in srgb, var(--panel) 24%, transparent);
}
body.ads-reserved .promo-panel .adsbygoogle { width: 100%; min-height: 280px; }
@media (min-width: 1520px) {
  body.ads-reserved .promo-rail,
  body.ads-reserved .promo-rail-left,
  body.ads-reserved .promo-rail-right,
  body.ads-reserved .promo-rail.ad-live {
    position: fixed;
    top: auto;
    border: 1px solid color-mix(in srgb, var(--panel-line) 76%, transparent);
    background: color-mix(in srgb, var(--panel) 24%, transparent);
  }
}

.isp-rating-card,
.isp-ranking-card { margin-top: 18px; }
.verified-rating-badge {
  padding: 5px 9px;
  border: 1px solid color-mix(in srgb, var(--signal-cyan) 34%, transparent);
  border-radius: 999px;
  color: var(--signal-cyan);
  font-family: var(--font-mono);
  font-size: .65rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.isp-stars { display: flex; gap: 8px; margin-top: 18px; }
.isp-stars button {
  width: 48px;
  height: 48px;
  border: 1px solid var(--panel-line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--panel) 74%, var(--bg));
  color: var(--text-muted);
  cursor: pointer;
  font-size: 1.55rem;
  line-height: 1;
  transition: color .14s ease, border-color .14s ease, transform .14s ease, background .14s ease;
}
.isp-stars button:hover,
.isp-stars button.selected {
  color: var(--pulse-amber);
  border-color: color-mix(in srgb, var(--pulse-amber) 55%, var(--panel-line));
  background: color-mix(in srgb, var(--pulse-amber) 10%, var(--panel));
  transform: translateY(-1px);
}
.isp-stars button:disabled { cursor: wait; opacity: .68; }
.isp-rating-status { min-height: 1.3em; margin: 12px 0 0; color: var(--signal-cyan); font-size: .8rem; }
.account-provider-ratings { display: grid; gap: 12px; color: var(--text-muted); font-size: .82rem; }
.account-provider-rating-row { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding-top: 12px; border-top: 1px solid var(--panel-line); }
.account-provider-rating-row > div:first-child { display: grid; gap: 3px; min-width: 0; }
.account-provider-rating-row strong { color: var(--text-primary); overflow-wrap: anywhere; }
.account-provider-rating-row span { color: var(--text-muted); font-size: .72rem; }
.compact-stars { margin-top: 0; gap: 4px; flex-shrink: 0; }
.compact-stars button { width: 34px; height: 34px; border-radius: 8px; font-size: 1.05rem; }
.isp-ranking-table-wrap { margin-top: 18px; overflow-x: auto; }
.isp-ranking-table { width: 100%; border-collapse: collapse; font-size: .82rem; }
.isp-ranking-table th,
.isp-ranking-table td { padding: 11px 12px; border-bottom: 1px solid var(--panel-line); text-align: left; white-space: nowrap; }
.isp-ranking-table th { color: var(--text-muted); font-family: var(--font-mono); font-size: .66rem; letter-spacing: .06em; text-transform: uppercase; }
.isp-ranking-table tbody tr:last-child td { border-bottom: 0; }
.isp-score { color: var(--pulse-amber); font-weight: 600; }
.isp-ranking-empty { padding: 24px !important; color: var(--text-muted); text-align: center !important; white-space: normal !important; }
@media (max-width: 640px) {
  .isp-stars { justify-content: space-between; gap: 6px; }
  .isp-stars button { width: 44px; height: 44px; }
  .account-provider-rating-row { align-items: flex-start; flex-direction: column; }
  .compact-stars button { width: 38px; height: 38px; }
}

/* v43 direct-sold banners vs. the v40/v41 "reserved ad frame" rules above.
   Those rules (body.ads-reserved .promo-panel.ad-live and its @media
   siblings around line 2320-2410) grew across several earlier releases to
   guarantee a network ad unit — AdSense/Monetag — always has a real,
   non-collapsing box to render into: fixed min-height, display:grid with
   place-items:stretch/center so the unit fills that box edge to edge.
   That's correct for a network unit, which has no size of its own until a
   creative loads. It's wrong for an advertiser's own uploaded image,
   which already has a natural size: place-items:stretch was pulling the
   box taller than the image (stretching the grid item to fill a
   min-height:280px cell) and leaving empty space below it, framed by the
   box's own border/background — the mismatched "frame" this overrides.
   !important is deliberate here rather than chasing selector specificity
   through that whole chain again: this is one narrow, well-understood
   exception (real creative must render at its own size, full stop), not
   a general pattern, and the earlier, lower-specificity attempt at this
   same fix (search .custom-ad-link above) was silently losing to exactly
   these rules — the failure mode this is specifically guarding against. */
body.ads-reserved .promo-panel.ad-live:has(.custom-ad-link) {
  display: block !important;
  min-height: 0 !important;
}
/* Rails get the same reset, but only at the widths where rails are ever
   shown in the first place (matching the @media (max-width: 1699px) /
   1399px breakpoints above that hide them below that). An earlier pass of
   this exact fix applied display:block !important to rails unconditionally,
   which beat the mobile-hiding rule outright — that rule has no
   !important of its own, so it loses to any !important rule regardless of
   whether its own @media condition even matches the viewport. Scoping this
   one inside the matching min-width query means it simply doesn't exist as
   far as the cascade is concerned below that width, so the hiding rule
   there is never in a fight it can lose. */
@media (min-width: 1400px) {
  body.ads-reserved .promo-rail.ad-live:has(.custom-ad-link) {
    display: block !important;
    min-height: 0 !important;
  }
}
