/* [split 9/9] servbg.com stylesheet: authoritative <=932px mobile block; MUST stay last and alone in this file. Split from style.css on 2026-10-12 (lines 7256-7855 of the original); the nine css/*.css parts load in numbered order, then the terminal css/app.css. See style.css stub. */
/* ═══════════════════════════════════════════════════════════════════════════
   AUTHORITATIVE MOBILE LAYOUT — placed LAST so it wins the cascade. Earlier
   base rules (e.g. .news-panel{display:flex} at ~L1113) were overriding the
   early mobile @media block, leaving the desktop 3-column shell active on
   phones (content squished to ~200px). This block is the single source of
   truth for ≤932px — covers portrait AND landscape phones (up to iPhone Pro Max
   landscape, 932px). Must sit at/above the 900px layout breakpoint or landscape
   phones fall into a broken half-mobile shell (squished terminal → box-art wraps
   and overlaps).
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 932px) {
  html, body { max-width: 100%; overflow-x: hidden; }

  .app {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto auto auto auto;
    /* Wave 3 item 2 (2026-08-29): "news" area added — News is reachable on
       mobile (collapsible) instead of being unreachable (display:none).
       2026-09-26: tools moved up to sit directly under the terminal. It used
       to start at y~1878 on a Pixel 7 — below the terminal, the quiz, the
       employer card, the leaderboards AND the news feed — so the whole tool
       rail was effectively undiscoverable. Reordering "console" alone only
       reached y~1710 (the quiz stack is ~1200px on its own), so .center-stack
       goes display:contents here and its two children take their own rows,
       exactly as the tablet block below already does. */
    grid-template-areas: "header" "terminal" "tools" "quiz" "news" "footer";
    height: auto;
    min-height: 100dvh;
    overflow: visible;
  }

  /* Wave 3 item 2 (2026-08-29): News is reachable on mobile — collapsible
     "SECURITY NEWS ▸" section below the terminal/quiz, collapsed by default
     (see the ≤767px block further down) via the static `.news-collapsed`
     class in the HTML + `toggleNewsPanel()`. Feed markup/loader are
     untouched (`loadNews()` isn't width-gated) — this only restyles/
     relocates the existing panel via `grid-area`, it does not duplicate DOM.
     Tablet 768-932: always expanded, see the dedicated tablet block below. */
  .news-panel {
    grid-area: news;
    display: flex;
    flex-direction: column;
    border-left: none;
    border-top: 1px solid var(--border-color);
    max-height: none;
    overflow: visible;
  }
  .news-header { cursor: pointer; user-select: none; }
  .news-toggle-indicator { display: inline-block; }
  .news-feed { max-height: 60vh; }

  /* Header: logo on its own row, badges in a tidy 2-col grid below (instead of
     the logo vertically centring against a ragged wrapped badge block). */
  .header { grid-area: header; height: auto; flex-wrap: wrap; gap: 8px; padding: 8px 12px; }
  .brand { flex: 1 1 100%; min-width: 0; }
  .logo { font-size: 14px; display: flex; align-items: center; min-height: 44px; padding: 4px 0; }
  /* 2026-09-26 hero line: 11.5px keeps it on one line at 412px and the header
     grows 95px -> 110px, inside the 18px budget set for this change. The vw
     cap stops it being ellipsised on a 375px iPhone SE (it needed 359px of a
     351px box) without a second media block. */
  .brand-tagline { font-size: min(11.5px, 2.9vw); letter-spacing: 0; }
  /* Terminal COPY/LINK actions: 44px tap floor (app.css sets 40px for the
     denser CLS/maximize buttons). */
  #sbgCopyResult, #sbgCopyLink { min-height: 44px; min-width: 44px; }
  /* Wave 3 item 1 (2026-08-29): collapse the visitor-info strip to ONE
     compact line (IP + Location) on mobile — it was eating ~250px (~37% of
     the iPhone SE fold) before any content. OS/Browser/ISP/ASN badges
     (`.v-tag-extra`, added in index.php) are hidden until the "▸ more"
     toggle expands `.visitor-info.expanded` back to the full grid. All six
     fields stay in the DOM at all times (JS fill code untouched) — only
     visibility changes. Desktop is untouched: this whole block is ≤932px. */
  .visitor-info { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; overflow-x: visible; justify-content: flex-start; padding: 0; }
  .visitor-info .v-tag-extra { display: none; }
  .visitor-info:not(.expanded) .v-tag .val { max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: bottom; }
  .visitor-info.expanded { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 4px 6px; }
  .visitor-info.expanded .v-tag-extra { display: flex; }
  .v-more-toggle {
    display: inline-flex; align-items: center; min-height: 26px;
    padding: 4px 8px; background: var(--bg-primary); border: 1px solid var(--border-color);
    border-radius: 4px; color: var(--accent); font-size: 10.5px; font-weight: 600;
    font-family: inherit; cursor: pointer; white-space: nowrap;
  }
  .v-tag { justify-content: flex-start; min-width: 0; padding: 4px 7px; font-size: 10.5px; gap: 5px; }
  .v-tag svg { width: 11px; height: 11px; }
  .v-tag .val { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* Center stack: terminal + quiz flow full-width and size to content.
     2026-09-26: display:contents so "tools" can sit between them. */
  .center-stack { display: contents; }
  /* SECOND + THIRD specificity bugs found + fixed 2026-08-29 (same family as
     the #dosTerminal ID one). There were actually TWO higher-specificity
     rules quietly defeating the plain `.console-panel{min-height:40vh}`
     rule below (1 class = specificity 0,1,0), both via the combinator
     selector `.center-stack > .console-panel` (2 classes = 0,2,0):
       1. A DUPLICATE of that combinator selector inside this same 932
          block also set min-height:0 — removed (moved its min-height:0
          onto .quiz-inline only, which is the one that actually wants it).
       2. The ORIGINAL base (non-media-query) rule at ~L443 — applies at
          every width, `.center-stack > .console-panel, .center-stack >
          .quiz-inline { min-height: 0; overflow: hidden; }` — same 0,2,0
          specificity as #1, and since the plain `.console-panel` rule below
          is lower specificity, it could never win against this base rule no
          matter where either was placed in the file. Fix: set min-height
          directly on the higher-specificity `.center-stack > .console-panel`
          selector here so it wins on both specificity AND source order
          (this 932 block is last in the file) against the base rule. */
  .center-stack > .console-panel { grid-area: terminal; height: auto; min-height: 40vh; overflow: visible; }
  .center-stack > .quiz-inline { grid-area: quiz; height: auto; min-height: 0; overflow: visible; }
  /* Shorter terminal on phones so the quiz strip is reachable without a long
     scroll. 40vh is the single source of truth for mobile — set above on
     `.center-stack > .console-panel` (see note) and mirrored here on the
     plain class for any markup not nested under .center-stack. min-width:0
     migrated from the old ≤900px "let the terminal scroll horizontally"
     block so a wide BBS/boot line can't blow out the grid column. */
  .console-panel { min-height: 40vh; min-width: 0; }
  .dos-screen { max-height: 46vh; overflow: auto; -webkit-overflow-scrolling: touch; }
  /* CLS (2026-09-26): the terminal sized to its content, so the BIOS overlay, the
     academy strip dos.js inserts and every boot line it printed grew the box and
     pushed the quiz strip, news column and footer down — 0.10 of a measured 0.12
     CLS on a 4x-throttled Pixel 7, spread over ~14 shifts between 2.6 s and 2.9 s.
     Pin the box at the height it grew to anyway (60vh ≈ the 543px it settled at on
     a 915px-tall viewport) and let .dos-screen flex inside it, so all of that lands
     in space that was already reserved. The ID beats `.center-stack >
     .console-panel { height: auto }` above; [data-maximized] is excluded so the
     maximize button still goes full-screen. The .dos-screen max-height it lifts is
     css/app.css's own 932 rule — with the outer box fixed, capping the inner one
     just leaves dead space under the input line. */
  #dosTerminal:not([data-maximized="true"]) { height: 60vh; }
  #dosTerminal:not([data-maximized="true"]) .dos-screen { max-height: none; }
  /* GFX eras are the exception: css/gfx.css turns .console-panel.gfx-active into a
     three-row grid (tabs, the monitor whose .gfx-glass is up to 34vh, then the
     screen at min-height 120px). That does not fit inside 60vh on a short phone,
     and #dosTerminal has overflow:hidden, so the bottom of the screen — the input
     line with it — was clipped away. Let a mounted monitor grow the box while
     keeping 60vh as its floor, so text eras still reserve the same height and the
     CLS fix above is untouched. monitor.js adds/removes .gfx-active on the panel
     (js/gfx/monitor.js showMonitor/hideMonitor), so the terminal snaps back to the
     fixed box the moment a GFX era is left. */
  #dosTerminal.gfx-active:not([data-maximized="true"]) { height: auto; min-height: 60vh; }
  /* Right-edge fade + thin scrollbar hints that GIT/SQL/PY tabs sit off-screen. */
  .term-tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; }
  /* The fade is a sticky flex item, not an absolute child: .term-tabs must stay unpositioned so
     the Academy progress pill (position:absolute; bottom:1px in css/app.css) anchors to
     .term-tabs-outer and sits on its own line under the row. With .term-tabs positioned, the
     overflow-x scroller clipped the pill away (2026-10-10). */
  .term-tabs::after {
    content: ""; flex: none; width: 24px; margin-left: -24px; align-self: stretch;
    position: sticky; right: 0; pointer-events: none;
    background: linear-gradient(to right, transparent, var(--bg-console));
  }

  .quiz-inline { grid-area: auto; max-height: none; height: auto; overflow: visible; }
  /* Quiz 4-panel grid → single column, readable, no clipping. */
  /* Stacked, so no internal scroll band: .quiz-inline owns the scrolling here. */
  .quiz-panels { grid-template-columns: 1fr; gap: 10px; overflow: visible; }
  .qp-card { overflow: visible; }
  /* Migrated from the old ≤900px block: keep the right quiz panel last in
     the single-column stack. grid-column is a no-op in a 1-col grid but kept
     for parity with the original rule. */
  .quiz-inline-right { order: 4; grid-column: 1 / -1; }

  /* Tools rail full width: a horizontal wrap of touch-sized chips. */
  .tools-panel { grid-area: tools; min-width: 0; border-right: none; border-top: 1px solid var(--border-color); }
  /* Dense, aligned icon grid instead of a ragged wrap. 5 fixed columns → the
     15 tools land as a tidy 5×3 block. Each tile gets a faint card border so it
     reads as a tappable button, not a floating icon in whitespace. */
  .tool-rail { display: grid; grid-template-columns: repeat(5, 1fr); height: auto; gap: 6px; padding: 12px; overflow: visible; }
  .rail-item { width: 100%; min-width: 0; min-height: 50px; padding: 8px 2px; gap: 4px;
               background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 8px; }
  .rail-item svg { width: 17px; height: 17px; }
  .rail-item span { font-size: 10px; }
  .rail-group-label, .rail-sep, .rail-spacer { display: none; }

  /* Flyouts become bottom sheets (thumb reach). */
  /* Forced override, kept on purpose: this sheet loads before index.php's inline
     terminal CSS, which sets .tool-flyout left/top; only forcing beats a later sheet. */
  .tool-flyout { left: 10px !important; right: 10px; top: auto !important; bottom: 10px; min-width: 0; max-width: none; width: auto; }
  .tool-flyout .flyout-body { flex-wrap: wrap; }
  .tool-flyout .flyout-body .go-btn { flex: 1 1 100%; }

  /* Footer: stack + wrap, thumb-sized links, respect the home-indicator. */
  .footer { grid-area: footer; flex-direction: column; align-items: stretch; height: auto;
            gap: 10px; padding: 12px 12px max(12px, env(safe-area-inset-bottom)); overflow-x: visible; }
  /* Stats as an aligned 2-col label/value table, not a ragged pipe-separated row. */
  .footer .stat-sep { display: none; }
  .footer-left { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 16px; align-items: center; }
  .footer-left .stat { display: flex; flex-direction: row; justify-content: space-between; align-items: baseline; gap: 8px; min-width: 0; }
  .footer-left .stat-val { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; }
  /* Long-value row (Top Countries) spans the full width so it doesn't clip.
     (Server-time stat removed 2026-08-28 — footer now has 4 stats.) */
  .footer-left .stat:nth-of-type(3) { grid-column: 1 / -1; }
  /* Links as an even wrapped row (was CSS Grid — see root-cause note below) instead
     of a ragged centred wrap.
     ROOT CAUSE (found 2026-08-29, headless-Chromium bisection): `.footer-right` is a
     flex item of `.footer` (flex-direction:column). Chromium resolves a `display:grid`
     flex item's main-size (height, here) via a stretch-fit pass BEFORE its own grid
     track sizing runs, independent of that grid's own align-content/grid-auto-rows/
     even an explicit `height` — none of those could shrink the outer box in testing
     (only removing `display:grid` from the flex item did). Net effect: the grid's
     "auto" row tracks then got equally stretched to fill that oversized box (~144px
     rows instead of ~35px content rows), inflating the whole footer to ~700px tall.
     Fix: make `.footer-right` a flex-wrap row (no longer `display:grid`), so it's no
     longer a grid-in-flex nesting — its height now comes from its own content again. */
  /* justify-content:center migrated from the old ≤900px .footer-right rule
     (residual 900-gap — the 932 rule never had it). */
  .footer-right { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
  .footer-right .stat-sep { display: none; }
  /* Wave 2 items 2+7 (2026-08-29): row-1 links (35px) and row-2 buttons
     (32px) both under the 44px tap floor; footer font bumped 10->12px
     (approved wave-3 item riding on the same selector). */
  .footer-link { padding: 9px 8px; text-align: center; flex: 1 1 84px; min-height: 44px; display: flex; align-items: center; justify-content: center; font-size: 12px; }

  /* Touch targets: inputs/buttons ≥44px; 16px font stops iOS zoom-on-focus. */
  .tool-input, .form-input, .flyout-body input { min-height: 44px; font-size: 16px; }
  .go-btn, .quiz-pill, .pl-btn, .emp-btn, .cpage-cta, .quiz-restart { min-height: 44px; }

  /* Modals full-screen on phone instead of a cramped centered box. */
  .modal-overlay .modal {
    width: 100%; max-width: 100%; height: 100dvh; max-height: 100dvh;
    border-radius: 0; margin: 0;
  }
  .modal-body { -webkit-overflow-scrolling: touch; padding-bottom: max(12px, env(safe-area-inset-bottom)); }
  /* Close × buttons measured 22×25 on mobile — below the 44px touch-target
     minimum. Desktop (.modal-close outside this media query) is unchanged. */
  .modal-close { min-width: 44px; min-height: 44px; font-size: 24px; }

  /* Content pages: comfortable mobile padding. */
  .cpage-main { padding: 20px 16px 48px; }
  .cpage-header { padding: 12px 16px; }

  /* ── Consolidated 2026-08-29 from legacy ≤900px blocks (mobile overhaul
     wave 1, fix 1) — profile modal, employer invite table, employer
     dashboard sidebar/kanban/drawer, and their touch targets. Selectors
     don't overlap anything above so nothing here changes prior behavior;
     this just gives ≤932px a single source of truth. ── */
  .profile-full {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "scores"
      "actions"
      "knowledge"
      "detailed"
      "footer";
  }

  .pl-invite-fields { grid-template-columns: 1fr; }
  .pl-invite-row { grid-template-columns: 1fr 1fr; row-gap: 4px; }
  .pl-invite-row.pl-invite-head { display: none; }
  .pl-invite-row > span:nth-child(1) { grid-column: 1 / -1; font-weight: 600; }
  .pl-invite-row > span:nth-child(6) { grid-column: 1 / -1; }
  .pl-company-section { margin: 0 12px 12px; }
  .pl-company-individual { flex-direction: column; align-items: stretch; }

  .emp-sidebar {
    position: fixed;
    left: 0; top: 0; bottom: 0;
    z-index: 1100;
    transform: translateX(-100%);
    transition: transform 0.22s;
    width: 240px;
  }
  .emp-sidebar.mobile-open { transform: translateX(0); }
  .emp-mobile-nav-btn {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: 5px;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 12px;
    font-family: inherit;
  }
  .emp-positions-grid { grid-template-columns: 1fr; padding: 12px; }
  .emp-kanban { flex-direction: column; min-width: unset; }
  .emp-kanban-col { width: 100%; }
  .emp-drawer {
    top: auto;
    bottom: 0;
    right: 0;
    left: 0;
    width: 100%;
    height: 92vh;
    border-left: none;
    border-top: 1px solid var(--border-color);
    border-radius: 12px 12px 0 0;
    transform: translateY(100%);
  }
  .emp-drawer.active { transform: translateY(0); }
  .emp-drawer-pull {
    width: 36px; height: 4px;
    background: var(--border-color);
    border-radius: 2px;
    margin: 8px auto 0;
  }
  .emp-compare-grid { overflow-x: auto; }
  .emp-form-2col { grid-template-columns: 1fr; }
  .emp-invite-grid { grid-template-columns: 1fr; }
  .ar-grid { grid-template-columns: 1fr; }
  /* .modal.employer-modal's old 100vw/100vh full-screen override is dropped
     here — it's dead weight, superseded by the general .modal-overlay .modal
     rule above (same specificity, declared later, now also correctly uses
     100dvh instead of 100vh). */

  .emp-btn, .emp-nav-item, .emp-tab, .emp-candidate-card { min-height: 44px; }
  /* Wave 2 item 3 (2026-08-29): sidebar "Log In" button (36px) under the
     44px tap floor. Padding-based, not font growth. */
  .tool-btn-special { min-height: 44px; }
  /* Wave 2 item 6 (2026-08-29): "Open full quiz ->" (95x23) and "Earn
     this!" (44x16) inline links under the 44px tap floor on mobile. */
  .quiz-inline-header .quiz-inline-link.console-btn { padding: 11px 12px; min-height: 40px; display: inline-flex; align-items: center; }
  .qia-cta { display: inline-flex; align-items: center; min-height: 40px; padding: 11px 4px; }
  /* Wave 4 item 9 (2026-08-29): leaderboard tables (nightmare + survival)
     were clipping columns at 375px with no way to reach them — give their
     stable container ids a scroll lane instead of shrinking the table
     illegibly. min-width keeps columns readable so overflow actually
     engages rather than the table just squishing to fit. */
  .survival-tab-content { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .leaderboard-table { min-width: 480px; }
  /* Wave 4 item 10 (2026-08-29): .modal-body already gets a safe-area
     bottom pad (see ~L7534); .emp-drawer's own scroll body did not. */
  .emp-drawer-body { padding-bottom: max(16px, env(safe-area-inset-bottom)); }
}

/* ═══════════════════════════════════════════════════════════════════════
   Wave 3 item 2 (2026-08-29) — phone-only news collapse. Scoped to ≤767px
   (below the 768px tablet tier) so the tablet block below can show News
   always-expanded without fighting this rule on specificity/order. The
   `.news-collapsed` class starts present in the HTML (phone default); the
   tablet block never adds/removes it and simply isn't targeted by this
   rule, so tablet always renders expanded regardless of the class.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 767px) {
  .news-panel.news-collapsed .news-search-wrap,
  .news-panel.news-collapsed .news-tag-chips,
  .news-panel.news-collapsed .news-feed { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Wave 3 item 3 (2026-08-29) — modest tablet tier, 768-932px. Phone
   (≤767px) and desktop (>932px) are untouched; this block only overrides
   the ≤932px authority block above for the 768-932 overlap, and relies on
   later-source-order-wins for same-specificity selectors (the established
   cascade pattern in this file — see the AUTHORITATIVE MOBILE LAYOUT note).
   Layout: terminal full-width top row, then quiz (60%) + news (40%) side by
   side below, tools/footer stay full-width single rows underneath.
   `.center-stack` becomes `display:contents` so its two children
   (`.console-panel`, `.quiz-inline`) can be placed as independent grid
   items of `.app` without touching the DOM — no duplication, matches the
   "restyle/relocate, don't duplicate DOM" guidance used for the news panel.
   Judgment call (explicitly invited by the spec): News is NOT collapsible
   here — it's a real 40%-width column exactly like desktop, so collapsing
   it to a thin bar next to a full-height quiz column would look broken.
   `.news-collapsed`'s hide rule is scoped to ≤767px (above) so it has no
   effect in this range; the toggle indicator is hidden here too. ─────── */
@media (min-width: 768px) and (max-width: 932px) {
  .app {
    grid-template-columns: 3fr 2fr;
    /* quiz/news row capped+scrollable (minmax(0,55vh)) instead of "auto" —
       "auto" let the row stretch to the taller column's full natural
       height (the 30-item news feed, ~6000px), leaving a large blank gap
       under the shorter quiz column via grid's default stretch. Both
       columns now get their own internal scroll within a shared bounded
       row instead.
       2026-09-26: tools moved above the quiz/news pair (same discovery fix as
       the phone block), so the capped row is now row 4, not row 3. */
    grid-template-rows: auto auto auto minmax(0, 55vh) auto;
    grid-template-areas: "header header" "terminal terminal" "tools tools" "quiz news" "footer footer";
  }

  .center-stack { display: contents; }
  .center-stack > .console-panel { grid-area: terminal; }
  .center-stack > .quiz-inline { grid-area: quiz; min-height: 0; height: 100%; overflow-y: auto; }

  .news-panel {
    grid-area: news;
    border-left: 1px solid var(--border-color);
    border-top: none;
    min-height: 0;
    height: 100%;
    max-height: none;
  }
  .news-feed { max-height: none; min-height: 0; }
  .news-header { cursor: default; }
  .news-toggle-indicator { display: none; }
}

/* Touch devices: don't rely on hover for the rail lift. */
@media (hover: none) {
  .rail-item:hover, .cpage-card:hover, .news-item:hover { transform: none; }
}

/* /projects and /friends site lists (2026-09-11). Mirrors the .link-item card used in the
   in-app modal so the crawlable pages and the modal look like the same thing. */
.cpage-sites { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.cpage-site {
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  padding: 14px 16px;
  transition: border-color 0.15s;
}
.cpage-site:hover { border-color: var(--accent); }
.cpage-site-name { color: var(--accent); font-weight: 600; font-size: 14px; text-decoration: none; }
a.cpage-site-name:hover { text-decoration: underline; }
.cpage-site-host { color: var(--text-muted); font-size: 11px; margin-left: 6px; }
.cpage-site-desc { color: var(--text-secondary); font-size: 13px; line-height: 1.6; margin: 6px 0 0; }
@media (max-width: 932px) {
  .cpage-site { padding: 12px 14px; }
  .cpage-site-host { display: block; margin-left: 0; margin-top: 2px; }
}

/* /sitemap — plain link list per section (2026-09-28). */
.cpage-sitemap-list { list-style: none; margin: 0 0 20px; padding: 0; columns: 2; column-gap: 24px; }
.cpage-sitemap-list li { break-inside: avoid; padding: 3px 0; font-size: 13px; }
.cpage-sitemap-list a { color: var(--text-secondary); text-decoration: none; }
.cpage-sitemap-list a:hover { color: var(--accent); text-decoration: underline; }

/* Homepage "Explore" crawlable nav block (2026-09-28) — outside .app, below
   the fold, matches the terminal look with the site's existing tokens.
   See openspec/specs/site-chrome-spa/spec.md "Crawlable explore block". */
.explore-nav {
  max-width: 1280px;
  margin: 0 auto;
  padding: 32px 20px 40px;
  border-top: 1px solid var(--border-color);
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
}
.explore-nav h2 { font-size: 18px; color: var(--text-primary); margin: 0 0 8px; }
.explore-lede { color: var(--text-secondary); font-size: 13.5px; line-height: 1.6; max-width: 76ch; margin: 0 0 22px; }
.explore-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; }
.explore-col h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); margin: 0 0 10px; }
.explore-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.explore-col li { font-size: 12.5px; color: var(--text-muted); line-height: 1.5; }
.explore-col a { color: var(--accent); text-decoration: none; font-weight: 600; }
.explore-col a:hover { text-decoration: underline; }
/* 2026-09-28: /bg/ stacks two link groups in one .explore-col (eras, then
   quiz categories); this is the second heading, same look as .explore-col h3
   but with breathing room above it instead of an inline style="margin-top". */
.explore-col-subhead { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); margin: 14px 0 10px; }

/* 2026-09-17 mobile/SEO pass: Lighthouse contrast + tap-target fixes */
.qp-title, .qia-empty, .quiz-inline-header span, .cpage-logo span { color: #8b949e; }
.qp-title { font-size: 10px; }
.term-tab-loading { opacity: .6; cursor: progress; }
/* News popup actions: full-width, 44px tap target on phones. */
@media (max-width: 932px) {
  a.news-read-more { min-height: 44px; padding: 0 4px; }
  .news-popup-actions { flex-direction: column; }
  .news-popup-btn {
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
  }
}

@media (max-width: 932px) {
  .cpage-nav a { min-height: 44px; display: inline-flex; align-items: center; }
}

/* 2026-09-26: mobile keeps the full-width bottom sheet, but the document end
   is padded by the sheet's height so the tools rail / footer can be scrolled
   clear of it. --consent-space is set by syncCookieBannerSpace(). */
@media (max-width: 932px) {
  body.consent-reserved { padding-bottom: var(--consent-space, 0px); }
}

/* .explore-nav mobile: one column, and the sitemap's two-column link list
   collapses to one so a link never straddles the fold. 2026-09-28. */
@media (max-width: 932px) {
  .explore-grid { grid-template-columns: 1fr; gap: 20px; }
  .cpage-sitemap-list { columns: 1; }
}

/* #newsSearch computed 11px on mobile (review finding, 2026-09-29) — below the
   16px iOS auto-zoom threshold, same fix as .dos-input/.tool-input. */
@media (max-width: 932px) {
  .news-search-input { font-size: 16px; }
}

/* CSP round-3 fix (2026-09-30): the full-bleed phone modal set above
   (`.modal-overlay .modal { width:100%; height:100dvh; margin:0; }`) left no
   pixel of `.modal-overlay` exposed below 932px, so the delegated outside-
   click close (an `e.target.dataset.modalOverlay` check in js/app.js — see
   "Modal overlays close on an outside click" in the site-chrome-spa spec)
   never fired; only the X button worked, and a still-open modal then
   swallowed clicks meant for whatever was underneath (reproduced: it also
   blocked the news-card tap from reaching showNewsPopup()). A slim dimmed
   strip above the sheet keeps it effectively full-screen while giving the
   outside-click handler a real target to land on. */
@media (max-width: 932px) {
  .modal-overlay { align-items: flex-start; }
  .modal-overlay .modal {
    margin-top: max(28px, env(safe-area-inset-top));
    height: calc(100dvh - max(28px, env(safe-area-inset-top)));
    max-height: calc(100dvh - max(28px, env(safe-area-inset-top)));
  }
}

/* "Wanted" live widget, 390px (task 4.5) — base/desktop rules live in css/app.css
   (.wanted-widget etc.), which is <link>ed AFTER style.css, so an ID selector is used here
   instead of a same-specificity class override: #wantedWidget beats .wanted-widget by
   specificity regardless of file load order, which a same-specificity rule in this trailing
   block could not do against a rule in a stylesheet that loads later. Caps the widget at the
   spec's budget by capping the row list's own scroll height, and keeps each row single-line
   so a long sentence never pushes the box taller than the cap. Row height/max-height were
   both bumped (Opus verification finding, item 9) so #wantedRows .wanted-ip's own tap target
   reaches the 24x24px Lighthouse `target-size` minimum via real padding rather than a
   negative-margin hitbox trick, which would have overlapped the next row's text — fewer rows
   are visible inside the cap as a result, which is an acceptable trade for a real, non-
   overlapping tap target. */
@media (max-width: 932px) {
  #wantedWidget { padding: 6px 10px 8px; }
  #wantedWidget .wanted-widget-h { font-size: 11px; margin-bottom: 4px; }
  #wantedRows { max-height: 132px; overflow: hidden; gap: 2px; }
  #wantedRows .wanted-row { font-size: 11px; line-height: 1.35; }
  #wantedRows .wanted-ip { display: inline-flex; align-items: center; min-height: 24px; padding: 2px 1px; }
  #wantedCounter { font-size: 11px; }
}

/* Threat board, 390px (task 4.3). Trailing block per the project rule — this must stay the
   LAST ≤932px block in the file so it wins on source order against any same-specificity rule
   written higher up. Tables scroll horizontally inside their own box instead of pushing the
   page wider than the viewport; the headline grid drops to one column. */
@media (max-width: 932px) {
  .threats-headline { grid-template-columns: 1fr; }
  .threats-wire-table, .threats-wanted-table, .threats-hoster-table, .threats-matrix-table,
  .threats-asn-table, .threats-country-table, .threats-asn-ip-table, .threats-evidence-table,
  .threats-enrich-table, .threats-ai-crawler-table {
    display: block; overflow-x: auto; white-space: nowrap;
  }
  /* .threats-about-table and .threats-report-table scroll inside their focusable .tw-scroll
     region (fix round 2026-10-08), so the table itself is never the scroller. */
  .threats-lookup input#threatsLookupInput { font-size: 16px; } /* iOS auto-zoom threshold, same fix as .news-search-input */
  .threats-timeline-row { grid-template-columns: 70px 1fr 32px; gap: 6px; }

  /* Terminal on phones (owner 2026-10-07, screenshot: "many elements are overcrowded").
     ID-qualified so these beat css/app.css's own 932 rules, which load after this file.
     1. COPY/LINK/CLS leave the tab row (CLS is a command anyway). Maximize + CRT stay in the
        row as two 32px icons pinned to its right edge (sticky inside the horizontal scroller),
        so they never sit over screen output. 2026-10-10: replaces the #termCorner strip that
        lived inside #dosScreen and floated over the top lines once the terminal auto-scrolled. */
  #termTabRow #sbgCopyResult, #termTabRow #sbgCopyLink, #termTabRow .term-tab-action[data-cmd="CLS"] { display: none; }
  #termTabRow #termMaxBtn, #termTabRow #crtToggleBtn {
    width: 32px; height: 32px; min-width: 32px; min-height: 32px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    border-radius: 6px; background: #111; color: #c9d1d9; font-size: 11px;
    position: sticky; right: 0; z-index: 1;
  }
  #termTabRow #crtToggleBtn { right: 38px; }
  /* Owner 2026-10-08: density over tap floor — era group tabs and era tabs 40 -> 32 px tall so more
     terminal text fits; ID-qualified to beat css/app.css's 932 rules (min-height 40). */
  #dosTerminal .term-group { min-height: 32px; padding: 5px 10px; }
  #dosTerminal .term-tab { min-height: 32px; padding: 5px 11px; }
  #dosTerminal .term-tabs-outer { padding-bottom: 16px; }
  /* The ⤢ glyph is missing from the phone's monospace font (rendered as an empty box), so the
     icon is drawn as a masked SVG: expand arrows, and collapse arrows while maximized. */
  #termTabRow #termMaxBtn { font-size: 0; }
  #termTabRow #termMaxBtn::before {
    content: ""; width: 16px; height: 16px; background: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='15 3 21 3 21 9'/%3E%3Cpolyline points='9 21 3 21 3 15'/%3E%3Cline x1='21' y1='3' x2='14' y2='10'/%3E%3Cline x1='3' y1='21' x2='10' y2='14'/%3E%3C/svg%3E") center / contain no-repeat;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='15 3 21 3 21 9'/%3E%3Cpolyline points='9 21 3 21 3 15'/%3E%3Cline x1='21' y1='3' x2='14' y2='10'/%3E%3Cline x1='3' y1='21' x2='10' y2='14'/%3E%3C/svg%3E") center / contain no-repeat;
  }
  #dosTerminal[data-maximized="true"] #termTabRow #termMaxBtn::before {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='4 14 10 14 10 20'/%3E%3Cpolyline points='20 10 14 10 14 4'/%3E%3Cline x1='14' y1='10' x2='21' y2='3'/%3E%3Cline x1='3' y1='21' x2='10' y2='14'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='4 14 10 14 10 20'/%3E%3Cpolyline points='20 10 14 10 14 4'/%3E%3Cline x1='14' y1='10' x2='21' y2='3'/%3E%3Cline x1='3' y1='21' x2='10' y2='14'/%3E%3C/svg%3E");
  }
  #termTabRow .term-tab-action[aria-pressed="true"] { color: #3fb950; border-color: #3fb950; }
  #termTabRow .academy-progress { right: 8px; left: auto; }
  /* 2. The typed line looked huge next to 10px output: app.css keeps .dos-input at 16px so iOS
        does not zoom on focus. Keep that computed size and scale the rendered glyphs down to the
        output's 10px (16 x .625); width compensates so the scaled box still fills the row. */
  #dosTerminal .dos-prompt-el { font-size: 10px; }
  #dosTerminal .dos-input { font-size: 16px; flex: none; width: 140%; transform: scale(.625); transform-origin: left center; }

  /* 2026-10-10 UX sweep (findings: uxsweep 3/10, uxvisual 1). Static-page header was three rows
     (169px) and sticky — a fifth of the screen on every scroll. Row 1: logo left, Open App right
     (the CTA is absolutely placed, header is its containing block); row 2: the five links spread
     across the width; not sticky. Overrides the 44px tap floor above (density, owner 2026-10-07). */
  .cpage-header { position: relative; top: auto; flex-wrap: wrap; gap: 6px 0; padding: 8px 12px; }
  .cpage-logo { font-size: 15px; line-height: 30px; }
  .cpage-nav { flex: 1 1 100%; justify-content: space-between; gap: 4px; }
  .cpage-nav a { min-height: 30px; font-size: 12px; padding: 4px 7px; }
  .cpage-nav a.cpage-nav-cta { position: absolute; top: 8px; right: 12px; padding: 0 10px; }
  /* Cookie banner was 139px (three text lines + 44px buttons). Text left, the two buttons beside
     it at 28px; the "Cookie Notice" title is dropped — the sentence says it. */
  .cookie-banner { padding: 6px 12px max(6px, env(safe-area-inset-bottom)); border-radius: 10px 10px 0 0; }
  .cookie-content { flex-wrap: nowrap; gap: 10px; }
  .cookie-text { min-width: 0; flex: 1 1 auto; }
  .cookie-text strong { display: none; }
  .cookie-text p { font-size: 10px; line-height: 1.3; }
  .cookie-buttons { margin-left: 0; gap: 6px; }
  .cookie-btn { min-height: 28px; padding: 4px 8px; font-size: 10px; }
}
