/* =========================================================
   PLANESDB — ATC Console Design System
   Hand-authored, dependency-free CSS.
   ========================================================= */

:root {
  /* --- Core palette (dark ATC scope) --- */
  --bg-0: #060a10;           /* deepest background */
  --bg-1: #0a0f18;           /* page background */
  --bg-2: #0e1520;           /* panel background */
  --bg-3: #121a27;           /* card background */
  --bg-4: #182233;           /* hover / raised */
  --border-0: #1c2635;
  --border-1: #263248;
  --border-glow: rgba(84, 255, 170, 0.35);

  /* --- Radar / signal accents --- */
  --radar: #4dffb0;          /* primary scope green */
  --radar-dim: #2c8f68;
  --radar-glow: rgba(77, 255, 176, 0.55);
  --amber: #f5b942;          /* action / search amber */
  --amber-dim: #c8922c;
  --cyan: #4fd7ff;
  --red: #ff5c5c;
  --violet: #b45cff;         /* general-milestone accent (timeline tab) */
  --orange: #ff8a4d;         /* Power & Payload category (Specifications
                                 tab) - distinct from --amber, which every
                                 row label already uses, so that category's
                                 header row doesn't blend into its own rows */

  /* --- Text --- */
  --text-0: #eaf3ee;
  --text-1: #b9c9c2;
  --text-2: #7f8f8a;
  --text-3: #57645f;

  /* --- Type --- */
  --font-mono: "JetBrains Mono", "Share Tech Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* --- Scale --- */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 14px;
  --gap: 1rem;
  --container: 1320px;

  /* --- Accessibility text scale (overridden by JS) --- */
  --scale: 1;

  /* --- Translucent chrome (sticky topbar / mobile tabbar backdrop) --- */
  --chrome-bg: rgba(6,10,16,0.86);
  --chrome-bg-strong: rgba(6,10,16,0.92);
}

/* =========================================================
   LIGHT THEME — "aged sectional chart" take on the same ATC-console
   layout as the dark theme above. Rather than just being a paler
   version of the dark scope, this leans into a second real cockpit
   reference point: the well-thumbed paper aeronautical chart -
   warm parchment stock (kept intentionally closer to neutral gray
   than a golden-yellow, so it reads as worn paper rather than a
   sepia filter), faded chart-ink green, worn brass instrument
   amber, and a faint grain/vignette texture (added below via
   body::before/::after) instead of a glowing digital grid.
   Toggled via the moon button in the top bar (see [data-theme-toggle]
   / main.js), applied as a data-theme attribute on <html>. Defaults
   to dark; this block only takes effect once the attribute is set.
   Note: --bg-0 stays dark intentionally - it doubles as the
   site-footer background AND (in the dark theme) the contrast text
   colour stamped on bright --radar buttons/chips. That second job
   doesn't carry over here: on paper, "dark-on-accent" would mean
   dark text on a dark-ish chart green, which doesn't read - so the
   handful of components that stamp --bg-0 onto a --radar background
   (.chip[aria-pressed], .compare-check.checked, .pagination
   button[aria-current]) get an explicit light-theme override lower
   down using a light parchment text colour instead.
   The radar-scope graphic itself (.scope, .mini-scope canvas) is
   also left on its own hard-coded dark colours in both themes,
   like a real cockpit instrument display. */
html[data-theme="light"] {
  color-scheme: light;

  --bg-0: #1c150e;
  --bg-1: #eae4d7;
  --bg-2: #f0ebde;
  --bg-3: #f8f5ea;
  --bg-4: #ddd4bd;
  --border-0: #d5cbb0;
  --border-1: #b7a985;
  --border-glow: rgba(31, 122, 68, 0.3);

  /* Accent trio (radar/amber/cyan) deepened and more saturated than an
     early pass of this theme - the original muted tones shared too much
     hue/lightness with the warm parchment background and visually
     "disappeared" as text (links, tab labels, spec labels, quick-facts
     index chips, etc.), even though they technically passed contrast.
     These read as a clear, punchy accent against --bg-1/--bg-2 the way
     the neon dark-theme colours read against the dark background. */
  --radar: #1f7a44;
  --radar-dim: #4f8f68;
  --radar-glow: rgba(31, 122, 68, 0.4);
  --amber: #9c530a;
  --amber-dim: #c07f2e;
  --cyan: #0f6d7d;
  --red: #a24632;
  --violet: #79579a;
  --orange: #a6601f;

  --text-0: #2a2115;
  --text-1: #514734;
  --text-2: #6f6248;
  --text-3: #948a6c;

  --chrome-bg: rgba(248,245,234,0.86);
  --chrome-bg-strong: rgba(248,245,234,0.92);
}

/* Faded chart-contour grid instead of the dark theme's neon scope
   lines, plus a light paper grain layered on top for surface
   character - kept subtle so the page still reads clean, not dirty. */
html[data-theme="light"] body::before {
  background-image:
    linear-gradient(rgba(90, 80, 55, 0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(90, 80, 55, 0.055) 1px, transparent 1px);
}
html[data-theme="light"] body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image:
    radial-gradient(ellipse at center, transparent 60%, rgba(60, 50, 30, 0.08) 100%),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch' result='noise'/><feColorMatrix in='noise' type='matrix' values='0 0 0 0 0.4  0 0 0 0 0.38  0 0 0 0 0.32  0 0 0 0.35 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: cover, 140px 140px;
  mix-blend-mode: multiply;
  opacity: 0.45;
}
html[data-theme="light"] .hero {
  background:
    radial-gradient(ellipse 70% 60% at 78% 15%, rgba(63,122,85,0.08), transparent 60%),
    var(--bg-1);
}
/* The scope keeps its own hard-coded dark instrument colours in both
   themes (see the note above), but its outer glow is tuned per theme -
   a very low-alpha bright green reads as a clear halo against the dark
   page, but is nearly invisible against parchment at the same alpha.
   Retinted brownish (rather than green) so it reads as a warm light
   spilling off an old instrument lamp, not a mismatched neon accent. */
html[data-theme="light"] .scope {
  box-shadow: inset 0 0 60px rgba(0,0,0,0.6), 0 0 46px rgba(143,94,23,0.4);
}
/* Homepage "Search" button - the muted brass --amber used for text/
   borders elsewhere reads fine, but as a solid CTA fill against
   parchment it doesn't pop the way the neon amber does against the
   dark page. Give the button its own brighter, more saturated gold. */
html[data-theme="light"] .btn-primary {
  background: #c9891f;
  border-color: #c9891f;
  color: #241a08;
}
html[data-theme="light"] .btn-primary:hover {
  background: #dda23c;
  border-color: #dda23c;
  box-shadow: 0 0 14px rgba(201,137,31,0.35);
}
/* Plane-card role badge + category chips overlaid on the thumbnail -
   these hard-coded a near-black translucent chip designed to contrast
   with the dark theme's neon text/icon colour. Against this theme's
   muted, darker accent colours that combination goes dark-on-dark and
   becomes unreadable, so give both a light parchment chip instead. */
html[data-theme="light"] .plane-card-badge {
  background: rgba(248,242,224,0.94);
  border-color: var(--border-1);
  color: var(--text-0);
}
html[data-theme="light"] .cat-badge {
  background: rgba(248,242,224,0.94);
  border-color: var(--border-1);
  color: var(--radar);
}
/* Timeline category icons - a few shades brighter/more saturated than
   their text-safe counterparts, since as small icons against parchment
   they read as flat rather than as distinct accent colours. */
html[data-theme="light"] .timeline-development.timeline-item .timeline-dot,
html[data-theme="light"] .timeline-scale-dot.timeline-development,
html[data-theme="light"] .timeline-legend-dot.timeline-development { color: #2f9a5e; }
html[data-theme="light"] .timeline-first-flight.timeline-item .timeline-dot,
html[data-theme="light"] .timeline-scale-dot.timeline-first-flight,
html[data-theme="light"] .timeline-legend-dot.timeline-first-flight { color: #1f8a9c; }
html[data-theme="light"] .timeline-combat.timeline-item .timeline-dot,
html[data-theme="light"] .timeline-scale-dot.timeline-combat,
html[data-theme="light"] .timeline-legend-dot.timeline-combat { color: #b8790f; }
html[data-theme="light"] .timeline-incident.timeline-item .timeline-dot,
html[data-theme="light"] .timeline-scale-dot.timeline-incident,
html[data-theme="light"] .timeline-legend-dot.timeline-incident { color: #c2422a; }
html[data-theme="light"] .timeline-retirement.timeline-item .timeline-dot,
html[data-theme="light"] .timeline-scale-dot.timeline-retirement,
html[data-theme="light"] .timeline-legend-dot.timeline-retirement { color: #8a7440; }
html[data-theme="light"] .timeline-milestone.timeline-item .timeline-dot,
html[data-theme="light"] .timeline-scale-dot.timeline-milestone,
html[data-theme="light"] .timeline-legend-dot.timeline-milestone { color: #9463c4; }
/* Incidents tab (747-test experiment, see incidentsTab() in detail.js) -
   reuses the same .timeline-* dot/scale/legend/date rules above, just
   under two new category keys ("fatal"/"safe") instead of the Timeline
   tab's development/combat/etc, so both tabs share one visual language
   without either affecting the other's colour set. */
html[data-theme="light"] .timeline-fatal.timeline-item .timeline-dot,
html[data-theme="light"] .timeline-scale-dot.timeline-fatal,
html[data-theme="light"] .timeline-legend-dot.timeline-fatal { color: #c2422a; }
html[data-theme="light"] .timeline-safe.timeline-item .timeline-dot,
html[data-theme="light"] .timeline-scale-dot.timeline-safe,
html[data-theme="light"] .timeline-legend-dot.timeline-safe { color: #2f9a5e; }
html[data-theme="light"] .timeline-hull-loss-legend { color: #b8790f; }
html[data-theme="light"] .search-box:focus-within {
  box-shadow: 0 0 0 3px rgba(63,122,85,0.16);
}
html[data-theme="light"] .badge-radar {
  background: rgba(63,122,85,0.10);
}
html[data-theme="light"] .detail-hero-media {
  background: radial-gradient(ellipse 90% 130% at 20% 0%, rgba(63,122,85,0.12), transparent 60%), var(--bg-2);
}
html[data-theme="light"] .flag-select-menu li[aria-selected="true"] {
  background: rgba(63,122,85,0.10);
}
/* --bg-0 no longer works as "on-accent" text in this theme (see note
   above) - swap in a light parchment tone wherever --radar is used as
   a solid background so the active/checked/current state stays readable. */
html[data-theme="light"] .chip[aria-pressed="true"],
html[data-theme="light"] .compare-check.checked,
html[data-theme="light"] .pagination button[aria-current="true"] {
  color: #f6f1e3;
}

/* Records/leaderboards row-fill gradients - hard-coded at a low, fixed
   alpha tuned for the dark theme's near-black panel background, where
   even a faint tint reads as a glow. Against this theme's light parchment
   panels the same alpha is nearly invisible, so the gold/green leaderboard
   bars need their own, stronger-alpha version here rather than just
   inheriting the dark-theme rgba values. */
html[data-theme="light"] .record-bar {
  background: linear-gradient(90deg, rgba(31,122,68,0.22), rgba(31,122,68,0.05));
  border-right-color: rgba(31,122,68,0.45);
}
html[data-theme="light"] .record-leader .record-bar {
  background: linear-gradient(90deg, rgba(156,83,10,0.26), rgba(156,83,10,0.05));
  border-right-color: rgba(156,83,10,0.5);
}

/* Hero photo credit chip - hard-coded a near-black translucent pill
   designed to contrast with the dark theme's light text. Against this
   theme's dark parchment text that combination goes dark-on-dark, so
   give it the same light parchment chip treatment already used for the
   category tiles' badges (.plane-card-badge / .cat-badge above). */
html[data-theme="light"] .hero-photo-credit {
  background: rgba(248,242,224,0.94);
  border-color: var(--border-1);
  color: var(--text-0);
}

/* Flightradar24 sidebar widget - the cyan wash background is another
   dark-theme-tuned low alpha that goes nearly flat against parchment. */
html[data-theme="light"] .fr24-box {
  background: linear-gradient(160deg, rgba(15,109,125,0.14), transparent);
}
html[data-theme="light"] .fr24-box .fr24-code {
  border-color: var(--cyan);
}

/* Footer - a first pass gave this its own near-black brown band (matching
   --bg-0, which doubles as "text on bright accent" elsewhere - see the
   big note above) with a bespoke light-parchment text set painted on top.
   That meant anything NOT explicitly repainted - like the social .icon-btn
   links, which set their own light --bg-3 fill and dark --text-1 icon
   colour - kept its normal light-theme colours and turned into a washed-
   out pale square sitting on the dark band instead of blending in.
   Simpler and more consistent: just use --bg-4, the theme's own "slightly
   darker/raised" surface tone (already used for hovers elsewhere), so the
   footer reads as a subtly recessed panel within the same warm-parchment
   palette rather than a separate dark island - every element already
   styled for the light theme (text colours, icon buttons, links) keeps
   working here with no extra overrides needed. */
html[data-theme="light"] .site-footer {
  background: var(--bg-4);
}
html[data-theme="light"] .footer-bottom {
  color: var(--text-2);
}

/* Homepage hero - the accent-coloured "One database." line and the
   eyebrow line above it are this page's single biggest visual hook, so
   give them a bit more than the base --radar bump above: a stronger glow
   on the headline (closer to how it reads against the dark theme) and
   bolder weight on the small-caps eyebrow line. */
html[data-theme="light"] .hero h1 span {
  text-shadow: 0 0 18px rgba(31,122,68,0.4);
}
html[data-theme="light"] .hero.hero-count h1 span.hc-num {
  text-shadow: 0 0 18px rgba(31,122,68,0.4);
}
html[data-theme="light"] .hero-eyebrow {
  font-weight: 700;
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
/* Recurring gotcha on this site: any component class that sets its own
   `display` (`.btn`, `.search-box`, `.chip`, etc - and there are a lot of
   them) is an author rule, so it wins the cascade over the UA stylesheet's
   `[hidden]{display:none}` even though [hidden] is "just" an attribute
   selector - author origin beats UA origin at equal specificity. Rather
   than re-discovering and re-patching this one component at a time (it's
   bitten .adv-filters, .chip-more-count, .flag-select-menu and the
   compare-page search box so far), one global override guarantees any
   element marked hidden actually stays hidden regardless of what other
   classes it carries. */
[hidden] { display: none !important; }
html { color-scheme: dark; }
body, h1, h2, h3, h4, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img { display: block; max-width: 100%; }
svg { display: inline-block; max-width: 100%; height: auto; vertical-align: middle; }

/* Icons used inline in buttons/labels/nav have no sizing context of their
   own (unlike icon-btn/cat-tile/plane-card-media which are fixed-size
   containers), so they must be capped explicitly or they render at the
   browser's default intrinsic SVG size. */
.search-box svg,
.btn svg,
.card-title svg,
.section-title svg,
.spec-item .spec-label svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  vertical-align: -3px;
}

/* Explicit sizes for icon buttons. Without these, Safari (iOS in
   particular) falls back to the SVG default 300x150 box and the icons
   render blown-up/clipped inside their containers. */
.icon-btn svg { width: 18px; height: 18px; }

/* Country-of-origin flag badge - shown next to manufacturer/origin text */
.flag-icon {
  display: inline-flex;
  width: 24px;
  height: 16px;
  margin-right: 0.45rem;
  border-radius: 2px;
  overflow: hidden;
  box-shadow: 0 0 0 1px var(--border-1);
  vertical-align: -3px;
  flex-shrink: 0;
}
.flag-icon svg { width: 100%; height: 100%; display: block; }
.detail-title .flag-icon { width: 30px; height: 20px; vertical-align: -4px; }
.kv-row .v .flag-icon { vertical-align: -3px; }
a { color: inherit; text-decoration: none; }
button, input, select { font: inherit; color: inherit; }
button { cursor: pointer; }

html {
  font-size: calc(16px * var(--scale));
  scroll-behavior: smooth;
}

body {
  background: var(--bg-1);
  color: var(--text-0);
  font-family: var(--font-sans);
  line-height: 1.5;
  min-height: 100vh;
  position: relative;
}

/* faint scope grid across whole page */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image:
    linear-gradient(rgba(77,255,176,0.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(77,255,176,0.035) 1px, transparent 1px);
  background-size: 42px 42px;
  mask-image: radial-gradient(ellipse at top, black, transparent 75%);
}

.container {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 1.5rem;
  position: relative;
  z-index: 1;
}

.mono { font-family: var(--font-mono); letter-spacing: 0.02em; }

/* ---------- Utility ---------- */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
/* padding-top/bottom only (not the `padding: Xrem 0` shorthand) - the shorthand
   also sets padding-left/right:0, which silently wins over .container's
   left/right padding on any element carrying both classes (e.g.
   class="section container ..."), since .section is later in this file at
   equal specificity. That clobbering caused a real 24px horizontal
   misalignment on every "section container" block site-wide (popular
   comparisons, homepage sections, etc). Longhand avoids the conflict entirely. */
.section { padding-top: 3.25rem; padding-bottom: 3.25rem; position: relative; z-index: 1; }
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 1.25rem; gap: 1rem; flex-wrap: wrap;
}
.section-title {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-1);
  display: flex; align-items: center; gap: 0.6rem;
}
.section-title::before {
  content: "";
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--radar);
  box-shadow: 0 0 8px var(--radar-glow);
}
.section-link {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--amber);
  white-space: nowrap;
}
.section-link:hover { text-decoration: underline; }

/* Page-level <h1> modifier - .section-title on its own is sized for small
   sub-section headers, not a page title. Every page's main h1 pairs this
   class alongside .section-title instead of a one-off inline font-size, so
   there's a single place to change the page-title size site-wide. */
.page-title { font-size: 1.3rem; margin-bottom: 0.5rem; }

/* Inline "color:var(--radar)" appeared on ~11 one-off in-prose links across
   several templates - a real, repeated pattern, just never given a class. */
.link-radar { color: var(--radar); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.5rem;
  padding: 0.65rem 1.1rem;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid var(--border-1);
  background: var(--bg-3);
  color: var(--text-0);
  transition: border-color .15s ease, background .15s ease, box-shadow .15s ease, transform .1s ease;
}
.btn:hover { border-color: var(--radar-dim); background: var(--bg-4); }
.btn:active { transform: translateY(1px); }
.btn-primary {
  background: var(--amber);
  border-color: var(--amber);
  color: #14100a;
}
.btn-primary:hover { background: #ffcb63; border-color: #ffcb63; box-shadow: 0 0 18px rgba(245,185,66,0.35); }
.btn-ghost { background: transparent; }
.btn-radar { border-color: var(--radar-dim); color: var(--radar); }
.btn-radar:hover { box-shadow: 0 0 14px var(--border-glow); }
.btn-sm { padding: 0.4rem 0.75rem; font-size: 0.72rem; }
/* Icon-only button that still reads as one of the .btn family - square, with
   the horizontal padding cut back to match the vertical so the glyph sits
   centred. aspect-ratio rather than a fixed width so it tracks whatever
   height .btn currently has, including the 44px touch-target minimum the
   tablet/phone tiers set. Give every .btn-icon an aria-label: there is no
   visible text for a screen reader to announce. */
/* The height comes from the flex row stretching this to match the labelled
   button beside it, which is taller than this one's content (a text line box
   vs a 17px icon). The horizontal padding is therefore set slightly wider
   than the vertical to land on a true square rather than a 40x42 near-miss -
   aspect-ratio alone cannot do it, because a flex item's main size is taken
   from its content before the ratio is applied. */
.btn-icon { padding: 0.65rem 0.72rem; aspect-ratio: 1; gap: 0; flex: none; }
.btn-sm.btn-icon { padding: 0.4rem; }
.btn-block { width: 100%; }
.btn[disabled] { opacity: 0.4; cursor: not-allowed; }

/* ---------- Toast ---------- */
/* Transient confirmation for an action that otherwise gives no visible
   result - currently just the detail page's Share button, which copies the
   page address. Created on demand by showToast() in main.js; the host is a
   single live region so repeated presses announce cleanly rather than
   stacking up new regions. pointer-events:none throughout, so a toast can
   never swallow a click meant for whatever is underneath it. */
.toast-host {
  position: fixed; z-index: 120;
  left: 50%; bottom: calc(1.4rem + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
  pointer-events: none;
}
.toast {
  display: inline-flex; align-items: center; gap: 0.5rem;
  max-width: min(92vw, 26rem);
  font-family: var(--font-mono); font-size: 0.74rem; letter-spacing: 0.03em;
  color: var(--text-0);
  background: var(--chrome-bg-strong);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 1px solid var(--radar-dim); border-radius: 999px;
  padding: 0.6rem 1rem;
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.45);
  opacity: 0; transform: translateY(10px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.toast.is-in { opacity: 1; transform: none; }
.toast svg { width: 14px; height: 14px; flex: none; color: var(--radar); }
@media (prefers-reduced-motion: reduce) {
  .toast { transition: none; }
}
/* Clear of the mobile tab bar, which is fixed to the bottom of the
   viewport at this breakpoint. */
@media (max-width: 720px) {
  .toast-host { bottom: calc(5.2rem + env(safe-area-inset-bottom, 0px)); }
}

/* ---------- Top bar / nav ---------- */
.topbar {
  position: sticky; top: 0; z-index: 50;
  background: var(--chrome-bg);
  /* Both spellings, matching .toast and .tabbar - this rule carried only the
     unprefixed one, so on a browser that supports just -webkit- the blur
     silently didn't render and the 86%-opaque --chrome-bg was left to hide
     the page on its own, which reads as a see-through header. */
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border-0);
}
/* If the blur cannot render at all, the translucency has nothing behind it
   doing the work and page content shows straight through the header. Fall
   back to a fully opaque bar: slightly flatter than intended, but legible. */
@supports not ((backdrop-filter: blur(10px)) or (-webkit-backdrop-filter: blur(10px))) {
  .topbar { background: var(--bg-1); }
}
.topbar-inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1.75rem;
  /* Longhand top/bottom only - a shorthand "0.85rem 0" here would zero out
     the left/right padding this element inherits from also carrying the
     .container class, which is invisible on wide viewports (.container's
     own centering already leaves whitespace at the sides) but leaves the
     brand mark and icon buttons sitting flush against the screen edge on
     any viewport at or below .container's max-width - i.e. every mobile
     and tablet width. Same fix applied to the mobile override below. */
  padding-top: 0.85rem;
  padding-bottom: 0.85rem;
}
.brand { display: flex; align-items: center; margin-right: 0.5rem; justify-self: start; }
/* Brand lockup: dark/light PNG wordmarks (src/assets/img/logo/), swapped
   via the html[data-theme] attribute the same way every other themed
   colour swaps - dark logo shows by default, light logo takes over once
   the light theme is active (see theme-toggle in main.js). Both images
   sit in the same grid cell so the swap doesn't shift layout. */
/* height:auto + max-height (rather than a flat fixed height) so the image
   always scales down preserving its own aspect ratio instead of
   distorting. With a hard `height: 41px`, the global `img { max-width:
   100% }` reset (see the Reset section) could still clamp the rendered
   WIDTH down to fit a narrow container while height stayed pinned at
   41px, squashing the logo horizontally - exactly what happened on
   narrow phones, where the topbar's brand column is well under the
   ~264px this logo needs at 41px tall. max-width: 100% here (redundant
   with the global img rule, kept explicit so this component's sizing
   intent is self-contained) plus max-height together let the browser's
   normal replaced-element sizing pick whichever constraint is tighter,
   never distorting. See the mobile override below for the deliberately
   smaller cap on phones. */
.brand-logo {
  height: auto;
  width: auto;
  max-width: 100%;
  max-height: 41px;
  display: block;
}
.brand-logo-light { display: none; }
html[data-theme="light"] .brand-logo-dark { display: none; }
html[data-theme="light"] .brand-logo-light { display: block; }

.nav { display: flex; align-items: center; justify-content: center; gap: 1.4rem; justify-self: center; }
.nav a {
  font-family: var(--font-mono); font-size: 0.78rem; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-1);
  padding: 0.4rem 0.1rem; border-bottom: 2px solid transparent;
}
.nav a:hover, .nav a[aria-current="page"] { color: var(--radar); border-color: var(--radar); }

.topbar-actions { display: flex; align-items: center; gap: 0.6rem; justify-self: end; }
.icon-btn {
  width: 36px; height: 36px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-1); background: var(--bg-3); color: var(--text-1);
}
.icon-btn:hover { color: var(--radar); border-color: var(--radar-dim); }
.theme-toggle[aria-pressed="true"] { color: var(--text-1); border-color: var(--border-1); }
/* Theme-toggle icon swap - sun (dark theme, click to go light) / moon+stars
   (light theme, click to go dark), same html[data-theme] swap pattern as
   .brand-logo-dark/-light. Both icons render into the button; only one is
   ever displayed, so there's no layout shift or flash on toggle. */
.theme-toggle-icon { display: none; }
.theme-toggle-icon-sun { display: flex; align-items: center; justify-content: center; }
html[data-theme="light"] .theme-toggle-icon-sun { display: none; }
html[data-theme="light"] .theme-toggle-icon-moon { display: flex; align-items: center; justify-content: center; }

/* Font-size accessibility control */
/* Traditional "small A / medium A / large A" text-size control - the glyph
   itself grows across the three buttons so the size difference is visible
   at a glance, not just implied by a "+" suffix. */
.text-scale { display: flex; align-items: flex-end; border: 1px solid var(--border-1); border-radius: var(--radius-sm); overflow: hidden; }
.text-scale button {
  background: var(--bg-3); border: none; color: var(--text-2);
  font-family: var(--font-sans); font-weight: 700; padding: 0 0.6rem; height: 36px;
  border-right: 1px solid var(--border-1);
  display: flex; align-items: flex-end; justify-content: center; line-height: 1;
}
.text-scale button.ts-small { font-size: 0.75rem; padding-bottom: 0.5rem; }
.text-scale button.ts-medium { font-size: 1rem; padding-bottom: 0.5rem; }
.text-scale button.ts-large { font-size: 1.3rem; padding-bottom: 0.4rem; }
.text-scale button:last-child { border-right: none; }
.text-scale button[aria-pressed="true"] { color: var(--radar); background: var(--bg-4); }
.text-scale button:hover { color: var(--radar); }

.mobile-toggle { display: none; }
/* NOTE: the topbar is position:sticky (see its base rule) - which also
   makes it the containing block for the absolutely-positioned mobile nav
   panel, so no extra positioning is needed here. A `position: relative`
   override briefly lived on this line and silently killed the sticky
   header (and with it the detail pages' stuck tab bar) - don't re-add it. */

/* ---------- Hero (radar scope) ---------- */
/* NOTE: deliberately no overflow:hidden here. The decorative radar scope
   (.scope) clips its own sweep/blip animation itself, and clipping at the
   .hero level was cutting off the search results dropdown - a descendant
   of this element - any time it grew taller than the remaining hero
   space, making results invisible on both desktop and mobile. */
.hero {
  position: relative;
  border-bottom: 1px solid var(--border-0);
  background:
    radial-gradient(ellipse 70% 60% at 78% 15%, rgba(77,255,176,0.08), transparent 60%),
    var(--bg-1);
}
.hero-inner {
  position: relative;
  /* .hero-inner is also a `.container`, which sets position:relative +
     z-index:1 to sit above the fixed decorative grid background. That
     z-index:1 makes .hero-inner its own stacking context - and because
     every `.section` below it (e.g. "Explore Aircraft by Category") is
     ALSO pinned at z-index:1 via the shared `.section` rule, the two tie
     and painting falls back to DOM order, so the later section paints
     OVER the search-results dropdown even though the dropdown itself is
     z-index:40 (that 40 only counts inside .hero-inner's own context).
     Bumping this above every `.section`'s z-index:1 fixes it. */
  z-index: 2;
  /* Longhand top/bottom only - same fix as .section and .topbar-inner
     elsewhere in this file: a shorthand "3.6rem 0 3rem" here zeroes out
     the left/right padding this element inherits from also carrying the
     .container class, which pulled the hero content and radar scope 1.5rem
     further out than every other .container-based section (category
     tiles, featured aircraft, the brand mark) - a real edge-to-edge
     misalignment on every viewport down to the 1080px tablet tier (the
     720px mobile block below already re-declares its own explicit
     left/right padding, so it wasn't affected). */
  padding-top: 3.6rem;
  padding-bottom: 3rem;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: 2.5rem;
  align-items: center;
}
.hero-eyebrow {
  font-family: var(--font-mono); font-size: 0.75rem; color: var(--radar);
  letter-spacing: 0.18em; text-transform: uppercase; margin-bottom: 0.9rem;
  display: flex; align-items: center; gap: 0.5rem;
}
.hero-eyebrow .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--radar); box-shadow: 0 0 8px var(--radar-glow); animation: blink 2s infinite; }
.hero h1 {
  font-family: var(--font-mono);
  font-size: clamp(2rem, 4vw, 2.9rem);
  line-height: 1.12;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--text-0);
}
.hero h1 span { color: var(--radar); text-shadow: 0 0 24px var(--radar-glow); }
.hero p.lede { margin-top: 1.1rem; color: var(--text-1); font-size: 1.03rem; max-width: 46ch; }

/* ---------- Hero variant: "count" (the number is the headline) ----------
   Paired with HERO_VARIANT in src/templates/home.js - the section only
   carries .hero-count when that constant is "count", so switching the
   template back to "classic" leaves every rule below inert rather than
   needing them removed.

   Everything is nested inside the <h1> (see heroCount() for why), so the
   first rule neutralises the base `.hero h1` type styling and the accent
   `.hero h1 span` colour before each piece sets its own. Selectors are
   deliberately doubled (.hero.hero-count) to out-specify both of those
   and the light-theme `html[data-theme="light"] .hero h1 span` glow. */
.hero.hero-count h1.hc-block {
  font-size: inherit; line-height: inherit; letter-spacing: normal;
  text-transform: none; margin: 0; font-weight: 400;
}
.hero.hero-count h1 span { color: inherit; text-shadow: none; }

.hero.hero-count .hc-row {
  display: flex; align-items: center; gap: 1.4rem; margin-bottom: 0.4rem;
}
.hero.hero-count h1 span.hc-num {
  font-family: var(--font-mono); font-weight: 700;
  font-size: clamp(4.5rem, 11vw, 8.5rem); line-height: 0.85;
  color: var(--radar); text-shadow: 0 0 34px var(--radar-glow);
  font-variant-numeric: tabular-nums; letter-spacing: -0.04em;
}
.hero.hero-count .hc-side { border-left: 1px solid var(--border-1); padding-left: 1.4rem; }
/* Three lines of pairs sit beside the number, so this runs a step smaller
   than .hc-tail (which is the same size the two-line version used to be) -
   at the old size the third line pushed the block taller than the figure
   next to it and the longest pair crowded the radar on tablets. */
.hero.hero-count .hc-l1 {
  display: block;
  font-family: var(--font-mono); font-size: clamp(0.9rem, 1.75vw, 1.2rem); font-weight: 700;
  text-transform: uppercase; letter-spacing: -0.01em; line-height: 1.2; color: var(--text-0);
}
.hero.hero-count .hc-l2 {
  display: block;
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--text-2); margin-top: 0.5rem;
}
.hero.hero-count .hc-tail {
  display: block;
  font-family: var(--font-mono); font-size: clamp(1rem, 2.1vw, 1.45rem); font-weight: 700;
  text-transform: uppercase; color: var(--text-0); line-height: 1.15;
}
/* nowrap so a narrow screen breaks between "documented" and "and
   counting." rather than stranding "and" at the end of the first line. */
.hero.hero-count .hc-tail em { font-style: normal; color: var(--text-2); white-space: nowrap; }

/* ---------- Hero variant: "flap" (split-flap counter) ----------------
   HERO_VARIANT = "flap" in src/templates/home.js. Everything from the
   "count" variant above still applies - the section carries BOTH
   .hero-count and .hero-flap - and only the figure changes: instead of
   a plain numeral that counts up, each digit becomes a Solari-style
   leaf that flips to its character on load (assets/js/split-flap.js).

   How one unit is put together, since the markup is generated and
   there's nothing to read in the templates:

     .sf-half.t          static - TOP half of the INCOMING character,
                         sitting behind, revealed as the leaf falls
     .sf-half.b          static - BOTTOM half of the OUTGOING character,
                         visible until the falling leaf covers it
     .sf-front           the outgoing character's top half, hinged on
                         its bottom edge, rotating 0 -> -90deg
     .sf-back            the incoming character's bottom half, hinged on
                         its top edge, rotating 90deg -> 0

   Each glyph is drawn at full card height inside a half-height,
   overflow-hidden box, so the split lands exactly through the middle of
   the numeral. That mid-glyph seam is the whole illusion - if you change
   --sf-h you must leave .g at the same height or the halves stop
   lining up.

   With JavaScript off, the row is just the number as plain text (see
   heroFlap() in home.js), styled to match, so nothing is lost.
   -------------------------------------------------------------------- */
.hero.hero-flap h1 span.sf-row {
  display: inline-flex; gap: 0.24em;
  font-family: var(--font-mono); font-weight: 700;
  font-size: var(--sf-fs); line-height: 1;
  color: var(--sf-ink); text-shadow: none; letter-spacing: normal;
  font-variant-numeric: tabular-nums;
}
.hero.hero-flap .sf {
  position: relative; display: inline-block;
  width: 0.78em; height: var(--sf-h); line-height: var(--sf-h);
  perspective: 340px; border-radius: var(--sf-r);
  background: var(--sf-card);
  box-shadow: inset 0 0 0 1px var(--sf-edge), 0 2px 6px rgba(0,0,0,0.5);
}
.hero.hero-flap .sf-half {
  position: absolute; left: 0; right: 0; height: 50%; overflow: hidden;
  background: var(--sf-card); backface-visibility: hidden;
}
.hero.hero-flap .sf-half.t {
  top: 0; border-radius: var(--sf-r) var(--sf-r) 0 0;
  background: linear-gradient(180deg, var(--sf-card-hi), var(--sf-card));
}
.hero.hero-flap .sf-half.b {
  bottom: 0; border-radius: 0 0 var(--sf-r) var(--sf-r);
  background: linear-gradient(180deg, var(--sf-card), var(--sf-card-lo));
}
/* .g must stay the FULL card height inside its half-height box - that's
   what splits the numeral across the seam. Colour is set here at a
   weight that clears the `.hero.hero-count h1 span { color: inherit }`
   reset above, which would otherwise repaint the glyphs. */
.hero.hero-flap h1 .sf-half span.g {
  position: absolute; left: 0; width: 100%; height: var(--sf-h);
  text-align: center; color: var(--sf-ink); text-shadow: none;
}
.hero.hero-flap .sf-half.t .g { top: 0; }
.hero.hero-flap .sf-half.b .g { bottom: 0; }
/* hinge seam and pins, drawn over the leaves */
.hero.hero-flap .sf::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 2px;
  transform: translateY(-1px); background: var(--sf-seam); z-index: 6; pointer-events: none;
}
.hero.hero-flap .sf::before {
  content: ""; position: absolute; top: 50%; left: -2px; right: -2px; height: 7px;
  transform: translateY(-3.5px); z-index: 7; pointer-events: none; border-radius: 2px;
  background: linear-gradient(90deg, var(--sf-pin) 0 4px, transparent 4px calc(100% - 4px), var(--sf-pin) calc(100% - 4px) 100%);
}
.hero.hero-flap .sf-front, .hero.hero-flap .sf-back { z-index: 5; }
.hero.hero-flap .sf-front { transform-origin: bottom; }
.hero.hero-flap .sf-back  { transform-origin: top; transform: rotateX(90deg); }
.hero.hero-flap .sf.flip .sf-front { animation: sf-fall var(--sf-dur) cubic-bezier(.36,.06,.31,1) forwards; }
.hero.hero-flap .sf.flip .sf-back  { animation: sf-rise var(--sf-dur) cubic-bezier(.36,.06,.31,1) forwards; }
@keyframes sf-fall { 0% { transform: rotateX(0); }    50%,100% { transform: rotateX(-90deg); } }
@keyframes sf-rise { 0%,50% { transform: rotateX(90deg); } 100% { transform: rotateX(0); } }
/* Shading as the leaf turns. Cheap, and it's what stops the flip
   reading as a flat card swap. */
.hero.hero-flap .sf.flip .sf-front .g { animation: sf-dark var(--sf-dur) linear forwards; }
.hero.hero-flap .sf.flip .sf-back  .g { animation: sf-lift var(--sf-dur) linear forwards; }
@keyframes sf-dark { 0% { filter: brightness(1); }    50%,100% { filter: brightness(0.45); } }
@keyframes sf-lift { 0%,50% { filter: brightness(0.45); } 100% { filter: brightness(1); } }

/* Palette + size. Dark theme: a departure board is a dark object, so
   the leaves are near-black with the neon radar green as ink. */
.hero.hero-flap h1 span.sf-row {
  --sf-card: #0e1620; --sf-card-hi: #16202c; --sf-card-lo: #0a1017;
  --sf-edge: #1d2b3a; --sf-seam: #05080c; --sf-pin: #38485c;
  --sf-ink: var(--radar);
  --sf-h: 1.18em; --sf-r: 10px; --sf-dur: 90ms;
  --sf-fs: clamp(4rem, 9.6vw, 7.4rem);
}

/* Light theme: ivory leaves with printed ink, rather than the dark
   board retinted. The light theme's whole character is paper - contour
   grid, paper grain, parchment ground - so a cream departure board with
   near-black ink belongs to it in a way a dark widget does not.

   The risk the dark palette was avoiding is real and is handled here:
   pale leaves on a pale page lose the seam. Three things prevent that -
   the card sits a shade LIGHTER than --bg-1 rather than darker, the
   seam and pins are taken well past the leaf edge in tone, and a warm
   shadow under each leaf separates it from the page. Lighten the seam
   or flatten the shadow and the split stops reading. */
html[data-theme="light"] .hero.hero-flap h1 span.sf-row {
  --sf-card: #f3eee0; --sf-card-hi: #fcf9f1; --sf-card-lo: #e2d9c2;
  --sf-edge: #c0b18e; --sf-seam: #8d7c58; --sf-pin: #5d5139;
  --sf-ink: #23301f;
}
html[data-theme="light"] .hero.hero-flap .sf {
  box-shadow: inset 0 0 0 1px var(--sf-edge), 0 2px 7px rgba(84,72,44,0.28);
}

.search-panel { margin-top: 1.9rem; }
/* Sized to line up with .select/.input (the "Sort by" dropdown sits right
   next to this on the Aircraft Database toolbar) rather than its own
   larger padding/font-size - it used to run noticeably taller than every
   other control next to it. */
.search-box {
  display: flex; align-items: center; gap: 0.5rem;
  background: var(--bg-2); border: 1px solid var(--border-1); border-radius: var(--radius-md);
  padding: 0.22rem 0.3rem 0.22rem 0.85rem;
}
.search-box:focus-within { border-color: var(--radar-dim); box-shadow: 0 0 0 3px rgba(77,255,176,0.12); }
.search-box svg { color: var(--text-2); flex-shrink: 0; }
.search-box input {
  flex: 1; background: transparent; border: none; outline: none;
  color: var(--text-0); font-size: 0.85rem; padding: 0.5rem 0;
  /* Placeholder/value that still doesn't fit fades out with an ellipsis
     instead of clipping mid-word against the attached Search button. */
  min-width: 0; text-overflow: ellipsis;
}
.search-box input::placeholder { color: var(--text-3); }
/* Hero search's attached "Search" button (see home.js) - without this it
   keeps the default .btn padding, which is taller than the box above once
   that box is shrunk, and ends up dictating the whole row's height again. */
.search-box .btn { padding: 0.5rem 0.9rem; font-size: 0.85rem; }
.search-popular { margin-top: 0.7rem; font-size: 0.78rem; color: var(--text-3); font-family: var(--font-mono); }
.search-popular a { color: var(--text-1); }
.search-popular a:hover { color: var(--amber); }

/* Search dropdown results (used in header + hero search) */
[data-search-box] { position: relative; }
[data-search-results] {
  position: absolute; top: calc(100% + 0.5rem); left: 0; right: 0; z-index: 40;
  background: var(--bg-2); border: 1px solid var(--border-1); border-radius: var(--radius-md);
  box-shadow: 0 16px 40px rgba(0,0,0,0.5); overflow-y: auto; max-height: 420px;
}
.search-result { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 0.9rem; border-bottom: 1px solid var(--border-0); }
.search-result:last-child { border-bottom: none; }
.search-result:hover, .search-result:focus-visible { background: var(--bg-4); }

/* Small silhouette thumbnail so a result is recognisable before the page
   even loads - reuses the same silhouette artwork as the plane cards. */
.sr-thumb {
  width: 44px; height: 44px; flex-shrink: 0; border-radius: var(--radius-sm);
  background: linear-gradient(160deg, var(--bg-4), var(--bg-2)); border: 1px solid var(--border-0);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.sr-thumb svg { width: 85%; height: 85%; }
/* Generic "real photo instead of the SVG silhouette" thumbnail - used
   wherever an aircraft has images.hero, sized to fill whatever small icon
   box it's dropped into (card media, compare slots/table, presets, search
   results) exactly the way the silhouette SVGs already did. */
.thumb-photo { width: 100%; height: 100%; object-fit: cover; display: block; }

.sr-info { flex: 1; min-width: 0; }
.sr-name-row { display: flex; align-items: center; gap: 0.4rem; }
.sr-name { font-weight: 600; font-size: 0.88rem; }
.sr-status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--radar); box-shadow: 0 0 6px var(--radar-glow); flex-shrink: 0; }
.sr-status-dot.retired { background: var(--text-3); box-shadow: none; }
.sr-status-dot.development { background: var(--cyan); box-shadow: 0 0 6px rgba(79,215,255,0.5); }
.sr-status-dot.cancelled { background: var(--orange); box-shadow: 0 0 6px rgba(255,138,77,0.5); }
.sr-meta {
  display: block; margin-top: 0.15rem; font-size: 0.7rem; color: var(--text-3);
  text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sr-meta .flag-icon { margin-right: 0.35rem; }
.sr-stat { flex-shrink: 0; text-align: right; font-size: 0.76rem; color: var(--radar); white-space: nowrap; }
.sr-stat small { display: block; font-size: 0.6rem; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.1rem; }
.search-empty { padding: 0.9rem 1rem; font-size: 0.8rem; color: var(--text-3); }
.search-more { display: block; text-align: center; padding: 0.7rem; font-family: var(--font-mono); font-size: 0.76rem; color: var(--amber); }
.search-more:hover { background: var(--bg-4); }

/* Radar scope visual */
.scope {
  position: relative; aspect-ratio: 1 / 1; width: 100%; max-width: 460px; justify-self: end;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, #0c1a15 0%, #08120e 70%, #060b09 100%);
  border: 1px solid var(--border-1);
  box-shadow: inset 0 0 60px rgba(0,0,0,0.6), 0 0 60px rgba(77,255,176,0.06);
  overflow: hidden;
}
.scope::before, .scope::after {
  content: ""; position: absolute; border: 1px solid rgba(77,255,176,0.16); border-radius: 50%;
}
.scope::before { inset: 12%; }
.scope::after { inset: 32%; }
.scope-ring-outer { position: absolute; inset: 4%; border: 1px solid rgba(77,255,176,0.12); border-radius: 50%; }
.scope-cross { position: absolute; inset: 0; }
.scope-cross::before, .scope-cross::after {
  content: ""; position: absolute; background: rgba(77,255,176,0.14);
}
.scope-cross::before { left: 0; right: 0; top: 50%; height: 1px; }
.scope-cross::after { top: 0; bottom: 0; left: 50%; width: 1px; }
.scope-sweep {
  position: absolute; inset: 0; border-radius: 50%;
  /* Like a real PPI display: a definite bright line LEADS the sweep, with
     the phosphor glow trailing ~85deg behind it and fading out. The line
     sits at 90deg in the gradient, so the beam's actual bearing at any
     moment is (rotation + 90deg) - the 404 blip timing in @keyframes
     nf-ping is derived from that. */
  background: conic-gradient(from 0deg,
    transparent 0deg,
    rgba(77, 255, 176, 0.02) 20deg,
    rgba(77, 255, 176, 0.10) 60deg,
    rgba(77, 255, 176, 0.28) 85deg,
    rgba(77, 255, 176, 0.60) 89.3deg,
    rgba(170, 255, 210, 0.95) 89.7deg,
    rgba(170, 255, 210, 0.95) 90deg,
    transparent 90.4deg);
  animation: sweep 4.5s linear infinite;
  mix-blend-mode: screen;
}
.blip { position: absolute; width: 7px; height: 7px; border-radius: 50%; background: #4dffb0; box-shadow: 0 0 10px rgba(77,255,176,0.55); }
.blip-label {
  position: absolute; font-family: var(--font-mono); font-size: 0.62rem; color: #4dffb0;
  white-space: nowrap; transform: translateY(-140%);
  text-shadow: 0 0 6px rgba(0,0,0,0.9);
}
.blip-1 { top: 22%; left: 30%; } .blip-2 { top: 62%; left: 68%; } .blip-3 { top: 40%; left: 78%; }
.blip-4 { top: 74%; left: 24%; } .blip-5 { top: 15%; left: 62%; }

@keyframes sweep { to { transform: rotate(360deg); } }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

/* ---------- Category tiles ---------- */
.cat-grid {
  /* One tile per category in a single row on desktop (grid-auto-flow:
     column makes every child its own equal column); the tablet/phone
     media queries below flip auto-flow back to row and wrap into
     4 / 3 / 2 columns. */
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 0.8rem;
  align-items: stretch;
}
.cat-tile {
  background: var(--bg-3); border: 1px solid var(--border-0); border-radius: var(--radius-md);
  padding: 1rem 0.45rem; text-align: center; transition: border-color .15s, transform .15s, background .15s;
  /* Flex + center, not just text-align, so the icon/name/count block stays
     vertically centered even when a neighbouring tile's longer label
     (e.g. "Fighter Jets") wraps to two lines and stretches the row. */
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.cat-tile:hover { border-color: var(--radar-dim); background: var(--bg-4); transform: translateY(-2px); }
.cat-tile svg { margin: 0 auto 0.55rem; color: var(--radar); width: 26px; height: 26px; flex-shrink: 0; }
.cat-tile .cat-name { font-size: 0.82rem; font-weight: 600; }
.cat-tile .cat-count { display: block; font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-3); margin-top: 0.2rem; }

/* ---------- Cards / grids ---------- */
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.1rem; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.1rem; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.1rem; }

.plane-card {
  background: var(--bg-3); border: 1px solid var(--border-0); border-radius: var(--radius-lg);
  overflow: hidden; display: flex; flex-direction: column;
  transition: border-color .15s, transform .15s, box-shadow .15s;
}
.plane-card:hover { border-color: var(--radar-dim); transform: translateY(-3px); box-shadow: 0 14px 30px rgba(0,0,0,0.35); }
.plane-card-media {
  aspect-ratio: 16/10; background: linear-gradient(160deg, var(--bg-4), var(--bg-2));
  display: flex; align-items: center; justify-content: center; position: relative;
  border-bottom: 1px solid var(--border-0);
}
.plane-card-media svg { width: 78%; opacity: 0.92; }
.plane-card-badge {
  position: absolute; top: 0.6rem; left: 0.6rem;
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.08em; text-transform: uppercase;
  background: rgba(6,10,16,0.75); border: 1px solid var(--border-1); color: var(--radar);
  padding: 0.2rem 0.5rem; border-radius: 999px;
}

/* Category badge row - small icon-only chips (bottom-left of the media
   area) showing every category an aircraft belongs to, e.g. a plane
   tagged both "military" and "cargo" shows both icons, not just its
   single primary role. Deliberately tiny/quiet so it doesn't compete
   with the role badge or the silhouette artwork. */
.plane-card-cats { position: absolute; bottom: 0.6rem; left: 0.6rem; z-index: 2; display: flex; gap: 0.3rem; }
.cat-badge {
  width: 22px; height: 22px; border-radius: 50%;
  background: rgba(6,10,16,0.78); border: 1px solid var(--border-1); color: var(--radar);
  display: flex; align-items: center; justify-content: center;
}
.cat-badge svg { width: 12px; height: 12px; }
.plane-card-body { padding: 0.95rem 1rem 1.1rem; flex: 1; display: flex; flex-direction: column; gap: 0.4rem; }
.plane-card-body h3 { font-size: 1rem; font-weight: 700; }
.plane-card-body .role { font-size: 0.78rem; color: var(--text-2); }
.plane-card-stats {
  display: flex; flex-wrap: wrap; gap: 0.7rem 1rem; margin-top: auto; padding-top: 0.7rem; border-top: 1px dashed var(--border-1);
  font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-2);
}
.plane-card-stats span { min-width: 0; max-width: 100%; }
.plane-card-stats b { color: var(--text-0); display: block; font-size: 0.82rem; max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* News cards */
.news-card { background: var(--bg-3); border: 1px solid var(--border-0); border-radius: var(--radius-md); overflow: hidden; }
.news-card-media { aspect-ratio: 16/9; background: linear-gradient(160deg, var(--bg-4), var(--bg-2)); display:flex; align-items:center; justify-content:center; }
.news-card-media svg { width: 46%; opacity: 0.5; }
.news-card-body { padding: 0.85rem 0.95rem 1rem; }
.news-card-body h4 { font-size: 0.9rem; line-height: 1.35; font-weight: 600; }
.news-meta { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-3); margin-bottom: 0.45rem; text-transform: uppercase; letter-spacing: 0.04em; }
.news-meta .tag { color: var(--amber); }
.news-meta .featured { color: var(--radar); }

/* RSS source directory (News page) */

/* Aircraft detail — photo gallery (local user-supplied images) */
.photo-gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.9rem; margin-top: 0.4rem; }
.photo-gallery-item { background: var(--bg-3); border: 1px solid var(--border-0); border-radius: var(--radius-md); overflow: hidden; margin: 0; }
.photo-gallery-open {
  display: block; width: 100%; padding: 0; border: none; margin: 0; cursor: zoom-in;
  background: none; position: relative;
}
.photo-gallery-open::after {
  content: ""; position: absolute; inset: 0; opacity: 0; transition: opacity .15s ease;
  background: rgba(6,10,16,0.25);
}
.photo-gallery-open:hover::after, .photo-gallery-open:focus-visible::after { opacity: 1; }
.photo-gallery-item img { width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; }
.photo-gallery-item figcaption { padding: 0.5rem 0.7rem; font-size: 0.72rem; color: var(--text-2); }

/* ---------- Gallery lightbox: caption + next/prev nav ----------
   Extends the .lightbox/.lightbox-close/.lightbox-inner base built for the
   compare page's click-to-zoom - same overlay, same close behaviour, this
   just adds a caption line and prev/next arrows for stepping through a set
   of photos instead of viewing a single image. */
.lightbox-caption {
  color: var(--text-2); font-size: 0.8rem; text-align: center;
  max-width: 720px; margin: 0.9rem auto 0; padding: 0 1rem;
}
.lightbox-nav {
  position: fixed; top: 50%; transform: translateY(-50%); z-index: 501;
  width: 48px; height: 48px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-2); border: 1px solid var(--border-1); color: var(--text-0); cursor: pointer;
}
.lightbox-nav:hover { border-color: var(--radar-dim); color: var(--radar); }
.lightbox-nav svg { width: 22px; height: 22px; }
.lightbox-prev { left: 1.2rem; }
.lightbox-prev svg { transform: rotate(180deg); }
.lightbox-next { right: 1.2rem; }
@media (max-width: 720px) {
  .lightbox-nav { width: 40px; height: 40px; }
  .lightbox-prev { left: 0.5rem; }
  .lightbox-next { right: 0.5rem; }
  .lightbox-caption { font-size: 0.72rem; }
}

/* ---------- "On This Day" banner ----------
   Sits just above the Quick Facts/Airshows strip (see onThisDayBanner()
   in home.js) as a stack of up to 5 colour-coded callouts, one per
   event matching today's date. Ships server-side as an empty [hidden]
   list - see src/assets/js/otd.js, which builds the actual banner(s) and
   only clears [hidden] once it's found at least one real match for
   today's date, so a visitor never sees an empty list flash before JS
   runs, and a day with nothing in the database for it just quietly has
   no banner at all.
   Each banner's colour comes from its category (see otdCategory() in
   build.js and CATEGORY_INFO in otd.js): amber = incident, green = first
   flight, blue = development starts, red = retirement, purple =
   milestone/everything else. Top-aligned rather than centered since the
   fact text can run to a full sentence or two and wrap across lines. */
.otd-list { display: flex; flex-direction: column; gap: 0.6rem; margin: 1.6rem 0; }
.otd-banner {
  /* Accent colour is a custom property so each otd-c-<category> variant
     below is a one-line override - nothing else in the rule changes
     between categories. */
  --otd-accent: var(--amber);
  --otd-accent-bg: rgba(245,185,66,0.07);
  --otd-accent-border: rgba(245,185,66,0.3);
  display: flex; align-items: flex-start; gap: 0.7rem;
  background: var(--otd-accent-bg); border: 1px solid var(--otd-accent-border); border-left: 3px solid var(--otd-accent);
  border-radius: var(--radius-md); padding: 0.9rem 1.1rem;
}
.otd-icon { color: var(--otd-accent); flex: none; display: inline-flex; margin-top: 0.15rem; }
.otd-icon svg { width: 20px; height: 20px; }
.otd-body { display: flex; flex-direction: column; gap: 0.55rem; min-width: 0; flex: 1 1 auto; }
.otd-text { font-size: 0.88rem; line-height: 1.55; color: var(--text-1); }
.otd-label { color: var(--text-0); }
.otd-label span { color: var(--otd-accent); font-family: var(--font-mono); }
/* Clickable jump-through to the mentioned aircraft's own page - a small
   pill rather than inline text so it reads as an action, not more prose. */
.otd-plane-tile {
  display: inline-flex; align-items: center; gap: 0.4rem; align-self: flex-start;
  background: var(--bg-1); border: 1px solid var(--otd-accent-border);
  border-radius: var(--radius-sm); padding: 0.3rem 0.65rem;
  font-family: var(--font-mono); font-size: 0.78rem; font-weight: 600;
  color: var(--otd-accent); text-decoration: none;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.otd-plane-tile:hover { background: var(--otd-accent-bg); border-color: var(--otd-accent); }
.otd-plane-icon { display: inline-flex; flex: none; }
.otd-plane-icon svg { width: 14px; height: 14px; }
@media (max-width: 640px) {
  .otd-banner { padding: 0.75rem 0.9rem; }
  .otd-text { font-size: 0.8rem; }
}

.otd-c-incident     { --otd-accent:#f5b942; --otd-accent-bg:rgba(245,185,66,0.07);  --otd-accent-border:rgba(245,185,66,0.3); }
.otd-c-first-flight { --otd-accent:#4dffb0; --otd-accent-bg:rgba(77,255,176,0.07);  --otd-accent-border:rgba(77,255,176,0.3); }
.otd-c-development  { --otd-accent:#4da6ff; --otd-accent-bg:rgba(77,166,255,0.07);  --otd-accent-border:rgba(77,166,255,0.3); }
.otd-c-retirement   { --otd-accent:#ff5c5c; --otd-accent-bg:rgba(255,92,92,0.07);   --otd-accent-border:rgba(255,92,92,0.3); }
.otd-c-milestone    { --otd-accent:#b380ff; --otd-accent-bg:rgba(179,128,255,0.07); --otd-accent-border:rgba(179,128,255,0.3); }

/* ---------- Recently Updated (homepage maintenance log) ---------- */
/* Intentionally NOT a .panel/.card: this block is the one place on the
   homepage that should read as a record rather than a feature, so it uses
   hairline rows on the page background instead of card chrome. The amber
   left edge only appears on hover, so a static screenshot stays quiet. */
.updates-log { border-top: 1px solid var(--border-0); }

.update-row {
  display: grid;
  grid-template-columns: 4.6rem minmax(0, 1fr) 8.5rem;
  gap: 0 1.2rem;
  padding: 0.75rem 0.7rem;
  border-bottom: 1px solid var(--border-0);
  border-left: 2px solid transparent;
  color: var(--text-0);
  transition: background 0.15s ease, border-left-color 0.15s ease;
}
.update-row:hover { background: var(--bg-2); border-left-color: var(--amber); }

.update-when {
  font-size: 0.68rem; line-height: 1.35; color: var(--text-3);
  white-space: nowrap; padding-top: 0.12rem;
}
.update-when b {
  display: block; font-size: 0.76rem; font-weight: 600;
  color: var(--amber); letter-spacing: 0.03em;
}

.update-body { min-width: 0; }
/* Wraps because a grouped row lists up to four aircraft; each name carries
   its own flag, which does the separating a middot would otherwise do. */
.update-planes {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0.2rem 0.75rem; margin-bottom: 0.24rem;
}
.update-planes a {
  display: inline-flex; align-items: center; gap: 0.38rem;
  font-size: 0.92rem; font-weight: 600; color: var(--text-0);
  transition: color 0.15s ease;
}
.update-planes a:hover { color: var(--radar); }
.update-planes .flag-icon { flex: none; }
.update-more { font-family: var(--font-mono); font-size: 0.7rem; color: var(--text-3); }
.update-text {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
  /* Capped measure: the grid column is far wider than comfortable reading
     length on a 1440px screen, and an uncapped line never reaches the
     two-line clamp it was written for. */
  max-width: 82ch;
  font-size: 0.82rem; line-height: 1.45; color: var(--text-2);
}

.update-who {
  text-align: right; font-size: 0.68rem; line-height: 1.35;
  color: var(--text-3); white-space: nowrap; padding-top: 0.12rem;
}
.update-who b { display: block; font-weight: 500; font-size: 0.74rem; color: var(--text-1); }
.update-revs { display: block; }

.updates-foot { margin-top: 0.85rem; font-size: 0.7rem; color: var(--text-3); }

/* Phone: date and author share the top line, the entry drops underneath.
   The entry count goes - it is the least useful number here at this
   width, and dropping it keeps the author on one line. */
@media (max-width: 720px) {
  .update-row { grid-template-columns: minmax(0, 1fr) auto; gap: 0.25rem 0.8rem; padding: 0.8rem 0.6rem; }
  .update-when { grid-column: 1; grid-row: 1; }
  .update-when b { display: inline; }
  .update-who { grid-column: 2; grid-row: 1; }
  .update-who b { display: inline; }
  .update-revs { display: none; }
  .update-body { grid-column: 1 / -1; grid-row: 2; }
  /* Four names + flags only fit two to a line at 390px; a slightly smaller
     name keeps a grouped row to two lines instead of three. */
  .update-planes { gap: 0.2rem 0.6rem; }
  .update-planes a { font-size: 0.86rem; }
}

/* ---------- Bottom info strip ---------- */
.strip { display: grid; grid-template-columns: 1.1fr 1fr 1.3fr; gap: 1.1rem; }
.strip-2 { grid-template-columns: 1fr 1fr; }
.panel { background: var(--bg-2); border: 1px solid var(--border-0); border-radius: var(--radius-md); padding: 1.1rem 1.2rem; }
.panel h3 { font-family: var(--font-mono); font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-1); margin-bottom: 0.9rem; display:flex; align-items:center; justify-content:space-between; }
.route-list, .forum-list { display: flex; flex-direction: column; gap: 0.65rem; }
.route-list li, .forum-list li { display: flex; justify-content: space-between; font-size: 0.84rem; gap: 0.6rem; }
.route-list .r-rank { color: var(--amber); font-family: var(--font-mono); margin-right: 0.4rem; }
.route-list .r-count { font-family: var(--font-mono); color: var(--text-3); white-space: nowrap; }
.forum-list a { color: var(--text-0); }
.forum-list a:hover { color: var(--radar); }
.forum-meta { font-family: var(--font-mono); font-size: 0.7rem; color: var(--text-3); }

.live-stats-source { margin-top: 0.8rem; font-size: 0.66rem; font-family: var(--font-mono); color: var(--text-3); }

/* ---------- Footer ---------- */
.site-footer { border-top: 1px solid var(--border-0); background: var(--bg-0); padding: 2.6rem 0 1.6rem; margin-top: 2rem; }
.footer-grid { display: grid; grid-template-columns: 1.4fr repeat(3, 1fr); gap: 2rem; }
.footer-grid h4 { font-family: var(--font-mono); font-size: 0.74rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-2); margin-bottom: 0.9rem; }
.footer-grid ul { display: flex; flex-direction: column; gap: 0.5rem; }
.footer-grid a { font-size: 0.85rem; color: var(--text-1); }
.footer-grid a:hover { color: var(--radar); }
.footer-about p { color: var(--text-2); font-size: 0.85rem; max-width: 34ch; margin-top: 0.7rem; }
.social-row { display: flex; gap: 0.6rem; margin-top: 1rem; }
.footer-bottom { margin-top: 2.2rem; padding-top: 1.2rem; border-top: 1px solid var(--border-0); display: flex; justify-content: space-between; font-size: 0.76rem; color: var(--text-3); font-family: var(--font-mono); }

/* ---------- Breadcrumb ---------- */
.breadcrumb { display: flex; align-items: center; gap: 0.45rem; font-size: 0.8rem; color: var(--text-2); padding: 1.1rem 0; font-family: var(--font-mono); flex-wrap: wrap; }
.breadcrumb a:hover { color: var(--radar); }
.breadcrumb .sep { color: var(--text-3); }

/* ---------- Detail header ---------- */
.detail-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1.5rem; flex-wrap: wrap; padding-bottom: 1.6rem; }
/* Without an explicit basis, this column shrinks to fit its own widest
   child (usually the name/badges row) rather than growing to fill the
   space .detail-actions doesn't need - which was capping .detail-sub's
   available width well short of its own 60ch max-width, especially once
   the tagline became 2 sentences. flex-grow lets it claim the rest of the
   row; min-width: 0 stops a long unbroken plane name from blowing the
   flex item wider than the row instead of wrapping normally. */
.detail-head > div:first-child { flex: 1 1 420px; min-width: 0; }
.detail-title { display: flex; align-items: center; gap: 0.8rem; flex-wrap: wrap; }
.detail-title h1 { font-family: var(--font-mono); font-size: clamp(1.6rem, 3vw, 2.1rem); text-transform: uppercase; }
.badge { display: inline-flex; align-items: center; gap: 0.35rem; font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase; padding: 0.28rem 0.6rem; border-radius: 999px; border: 1px solid var(--border-1); }
.badge-radar { color: var(--radar); border-color: var(--radar-dim); background: rgba(77,255,176,0.08); }
.badge-amber { color: var(--amber); border-color: var(--amber-dim); background: rgba(245,185,66,0.08); }
.badge-red { color: var(--red); border-color: #7a2f2f; background: rgba(255,92,92,0.08); }
.badge-cyan { color: var(--cyan); border-color: var(--cyan); background: rgba(79,215,255,0.08); }
.badge-orange { color: var(--orange); border-color: var(--orange); background: rgba(255,138,77,0.08); }
/* Detail-page title badges: role + status render twice - as the two separate
   badges above (.badge-split) - on phones the ROLE half is dropped rather
   than merged into the status (swapped at the 720px breakpoint,
   where the separate pair usually wrapped to a second line under the h1).
   The status suffix keeps its usual amber/red state colour. */
/* The role and status pills are always separate now - the merged
   "Role \u00b7 Status" phone badge that used to live here is gone; see the
   note beside .badge-split in the 720px block. */
/* Was capped at 60ch, then 74ch to match .prose (the site's other
   long-form-text width) - but this row isn't running body-copy prose next
   to other columns, it's the only thing in its flex item (see
   ".detail-head > div:first-child" above, which already grows to fill the
   row up to wherever .detail-actions sits), so a ch-based cap just left a
   dead gap between the end of the text and the actions column instead of
   using that space. Uncapped here; the flex item's own width (bounded by
   the row and the actions column next to it) is what keeps lines from
   running edge-to-edge on very wide screens. */
.detail-sub { color: var(--text-1); margin-top: 0.5rem; max-width: none; }
/* Two variants of the same line (see firstSentence()/detail.js): the full
   2-sentence tagline is the desktop default, and a first-sentence-only
   version swaps in at mobile width (below) so the extra sentence doesn't
   push the hero photo further down a phone screen before anything else
   is visible. */
.detail-sub-short { display: none; }
.detail-actions { display: flex; gap: 0.6rem; }
/* "Last updated <date>" now sits under the name/tagline (not over the
   hero photo - see detail.js render() for why) as a normal inline pill,
   not an absolutely-positioned overlay. Wrapped in .updated-row alongside a
   second, mobile-only compact "Compare" button (.detail-actions-mobile) -
   hidden here by default and swapped in at the 720px breakpoint below in
   place of the desktop .detail-actions block, which was rendering as an
   oversized full-width button on a phone (see .detail-actions{width:100%}
   further down). margin-top now lives on the row rather than the flag
   itself so the two sit level with each other instead of the flag getting
   pushed down by its own top margin inside the flex row. */
.updated-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.7rem; }
/* Phone-width copy of the pill's label; the long one is hidden there
   instead. See the note beside this markup in detail.js render(). */
.updated-date-short { display: none; }
.updated-flag {
  font-family: var(--font-mono); font-size: 0.68rem; color: var(--text-2);
  white-space: nowrap;
  background: var(--bg-2); border: 1px solid var(--border-1); padding: 0.3rem 0.6rem; border-radius: 999px;
  display: inline-flex; align-items: center; gap: 0.4rem;
}
.detail-actions-mobile { display: none; }
/* The mobile Compare + Share pair, kept together on one line - see the
   comment beside this markup in detail.js render(). */
.updated-row-actions { gap: 0.6rem; }
.updated-flag .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--radar); box-shadow: 0 0 6px var(--radar-glow); }

/* Hero media strip for detail page */
.detail-hero-media {
  position: relative; border: 1px solid var(--border-0); border-radius: var(--radius-lg); overflow: hidden;
  background: radial-gradient(ellipse 90% 130% at 20% 0%, rgba(77,255,176,0.10), transparent 60%), var(--bg-2);
  padding: 2.4rem 2rem; display:flex; align-items:center; justify-content:center; min-height: 260px;
}
.detail-hero-media svg { width: min(680px, 90%); }
/* Real-photo hero variant (opt-in per aircraft via images.hero) - the
   silhouette version is a padded icon on a flat panel, but a photo wants to
   fill the frame edge-to-edge like a proper header image. */
.detail-hero-media.has-photo { padding: 0; min-height: 0; background: var(--bg-2); }
/* height:auto (not a fixed max-height + object-fit:cover) so the box always
   matches the photo's own aspect ratio - the whole aircraft stays in frame
   no matter how the photographer composed the shot. max-height caps how tall
   the hero is allowed to get, and contain (not cover) means that cap
   letterboxes instead of cropping.

   This base rule still applies as-is to the Aircraft of the Week card
   (.aotw-media in aotw.js, which reuses these classes), where the photo
   should fill its card edge-to-edge. The detail page's own hero overrides
   it below. */
.detail-hero-photo { display: block; width: 100%; height: auto; max-height: 620px; object-fit: contain; margin: 0 auto; }

/* Shrink-wrapped detail-page hero.
   ---------------------------------
   The panel used to be a full-column box that the photo was stretched into.
   Because 40 of the ~51 hero photos are around 3:2 - taller, proportionally,
   than a 1272px-wide box capped at 620px - `object-fit: contain` letterboxed
   them, leaving up to ~235px of empty panel background as visible bars down
   either side of the picture.

   So the box now sizes itself to the photo rather than the other way round:
   `width: fit-content` shrinks it to whatever the image actually renders at,
   and it stays left-aligned with the rest of the column. The image keeps the SAME
   maximum it had before (the column's full width, and 620px tall), so a
   large photo is displayed exactly as large as it always was - it is only
   the empty box around it that goes. `width: auto` means a photo smaller
   than those limits is never stretched up to fill the column either; the box
   closes in around it instead, which keeps it sharp.

   Scoped away from .aotw-media on purpose: that card has its own min-height
   and sits in a fixed-width panel, where a shrink-wrapped photo would just
   move the gap somewhere else. */
.detail-hero-media.has-photo:not(.aotw-media) {
  width: fit-content;
  max-width: 100%;
  /* Left-aligned in the column, flush with the breadcrumb, title and the
     spec tiles below it, rather than floating centred between them. */
  margin-left: 0;
  margin-right: auto;
}
.detail-hero-media.has-photo:not(.aotw-media) .detail-hero-photo {
  width: auto;
  max-width: 100%;
}
.hero-photo-credit {
  /* Above the photo. The Aircraft of the Week banner gives its photo
     z-index:1 so it stacks over .aotw-media-backdrop, and without a z-index
     of its own this pill lost the paint order to it: the pill rendered
     underneath the photo, so only the part hanging past the photo's edge -
     over the blurred backdrop - was visible, which read as a credit line
     floating in the top-right corner with its first half chopped off. */
  position: absolute; z-index: 2;
  bottom: 0.9rem; left: 1rem; max-width: calc(100% - 2rem);
  font-family: var(--font-mono); font-size: 0.66rem; color: var(--text-2);
  background: rgba(6,10,16,0.7); border: 1px solid var(--border-1); padding: 0.3rem 0.6rem; border-radius: 999px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 720px) {
  .hero-photo-credit { left: 0.6rem; right: 0.6rem; max-width: none; bottom: 0.6rem; }
}

/* ---------- Tabs ---------- */
/* .tabs-wrap carries the scroll-edge fades below - it doesn't scroll itself,
   so the fades stay pinned to the viewport edge while .tabs scrolls beneath
   them. JS (initTabScrollFades in main.js) toggles has-scroll-left/right on
   it based on actual scroll position, so a fade only shows on the side
   there's genuinely more to see - e.g. no left fade when already at the
   start. This exists mainly so mobile users notice there ARE more tabs
   (Variants/Operators/Gallery/etc.) instead of assuming Overview is it. */
/* Sticky on every screen size (not just mobile): once the tab bar's natural
   position scrolls above the topbar, it pins there instead of scrolling
   out of view, and un-pins itself the moment the user scrolls back above
   its original spot - all native `position: sticky` behaviour, no scroll
   listener needed. `top: 64px` matches the topbar's rendered height
   (0.85rem top/bottom padding + a 36px icon-btn/brand-mark + 1px border;
   the mobile topbar works out to the same ~64px with its own smaller
   padding/icon sizes, see the mobile override below) so the tabs dock
   directly under it rather than overlapping. The solid `--bg-1`
   background - the same colour as `body` - stops panel content from
   showing through once the bar is pinned and content scrolls underneath.
   That background is only painted in while the bar is actually pinned
   (see .tabs-wrap.is-stuck below) - always-on opaque `--bg-1` used to sit
   as a visibly flatter rectangle behind the bar's resting position too,
   since both the light theme's paper-grain/vignette texture and the dark
   theme's faint scope-grid are painted as fixed full-viewport layers
   behind `body` (see body::before/::after up top) that a permanently
   opaque bar simply blocks out - obvious as a "banner" on the light theme
   in particular. Toggled via initStickyTabBackgrounds() in main.js
   (IntersectionObserver, not a scroll listener) the moment the bar's top
   edge reaches its sticky offset. No-JS fallback: background stays
   transparent throughout, so a JS-less visitor gets the old "content
   scrolls through the pinned bar" look rather than the old flat-banner
   look - a fair trade given how rare that is, and consistent with how
   `top: 64px` already degrades gracefully without JS below. */
/* top: --topbar-h is measured from the real header by main.js
   (initTopbarHeightVar) because the header's height varies - the brand
   tagline wraps to two lines on narrow phones, which made any hardcoded
   offset crop the header into the tab buttons. Falls back to 64px if JS
   is unavailable. */
.tabs-wrap { position: sticky; top: var(--topbar-h, 64px); z-index: 30; background: transparent; margin-top: 1.6rem; }
.tabs-wrap.is-stuck { background: var(--bg-1); }
.tabs-wrap::before, .tabs-wrap::after {
  content: ""; position: absolute; top: 0; bottom: 1px; width: 32px;
  pointer-events: none; z-index: 1; opacity: 0; transition: opacity 0.2s ease;
}
.tabs-wrap::before { left: 0; background: linear-gradient(to right, var(--bg-1), transparent); }
.tabs-wrap::after { right: 0; background: linear-gradient(to left, var(--bg-1), transparent); }
.tabs-wrap.has-scroll-left::before { opacity: 1; }
.tabs-wrap.has-scroll-right::after { opacity: 1; }
/* Small amber chevrons sat on top of the fades - on a dark-on-dark theme the
   gradient alone barely registers, so this is the part that actually catches
   the eye and reads as "there's more this way" rather than just a soft edge. */
.tabs-fade {
  position: absolute; top: 0; bottom: 1px; z-index: 2; width: 22px;
  display: flex; align-items: center; justify-content: center;
  color: var(--amber); pointer-events: none; opacity: 0; transition: opacity 0.2s ease;
}
.tabs-fade svg { width: 15px; height: 15px; }
.tabs-fade-left { left: 0; transform: scaleX(1); }
.tabs-fade-left svg { transform: rotate(90deg); }
.tabs-fade-right { right: 0; }
.tabs-fade-right svg { transform: rotate(-90deg); }
.tabs-wrap.has-scroll-left .tabs-fade-left { opacity: 1; }
.tabs-wrap.has-scroll-right .tabs-fade-right { opacity: 1; }
.tabs { display: flex; gap: 0.4rem; border-bottom: 1px solid var(--border-0); margin-top: 0; overflow-x: auto; }
.tab-btn {
  background: transparent; border: none; border-bottom: 2px solid transparent;
  padding: 0.75rem 0.9rem; font-family: var(--font-mono); font-size: 0.78rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-2); white-space: nowrap;
}
.tab-btn:hover { color: var(--text-0); }
.tab-btn[aria-selected="true"] { color: var(--amber); border-color: var(--amber); }
.tab-panel { display: none; padding-top: 1.8rem; }
.tab-panel.active { display: block; }

/* ---------- Detail layout ---------- */
.detail-layout { display: grid; grid-template-columns: 1.55fr 0.9fr; gap: 1.4rem; align-items: start; }
/* The left column's tab panels carry 1.8rem top padding; the sidebar
   needs the same so Quick Facts lines up with the first card. */
.detail-layout > aside { padding-top: 1.8rem; }
/* Copies of the sidebar cards rendered inside the tab panels (see
   tabExtras in detail.js render()). Hidden by default; swapped in for the
   aside at the 1080px breakpoint below, where .detail-layout stacks
   single-column and the aside would otherwise dangle off the bottom of the
   page duplicating the Operators tab. */
.aside-in-tabs-cards { display: none; }
.card { background: var(--bg-2); border: 1px solid var(--border-0); border-radius: var(--radius-md); padding: 1.25rem 1.4rem; }
.card + .card { margin-top: 1.1rem; }
.card h3.card-title { font-family: var(--font-mono); font-size: 0.78rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-1); margin-bottom: 1rem; }
.card p { color: var(--text-1); line-height: 1.65; }
.card p + p { margin-top: 0.8rem; }
.read-more-link { color: var(--amber); font-family: var(--font-mono); font-size: 0.78rem; margin-top: 0.9rem; display: inline-block; }

.spec-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.1rem 1rem; margin-top: 0.6rem; }
.spec-item .spec-label { font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--amber); display:flex; align-items:center; gap:0.4rem;}
/* Back to the mono spec font used everywhere else on this quick-glance row
   (not the sans-serif used for the Full Specifications tab's longer, more
   sentence-like values) - just dropped the bold weight. Still clamped to 2
   lines so the row stays scannable and roughly the same height from one
   aircraft to the next; the untruncated text is a hover (title attr) or a
   click (the "See SPECIFICATIONS" hint above the grid) away. */
.spec-item .spec-value {
  font-family: var(--font-mono); font-size: 1.05rem; font-weight: 400; color: var(--text-0); margin-top: 0.3rem;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.spec-more-hint { font-size: 0.78rem; color: var(--text-3); margin-top: 1.1rem; }
.spec-more-hint a { color: var(--text-3); font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; }
.spec-more-hint a:hover { text-decoration: underline; color: var(--text-1); }

/* Shared "what this tab is/how to read it" caption, sits directly under a
   card-title before the tab's real content (Timeline's scale explainer,
   References'/Further Reading's numbering note). Previously each of these
   repeated the same inline style="font-size:0.85rem;color:var(--text-2);
   margin-bottom:1rem" - consolidated into one class so the three stay in
   sync if this styling ever changes. */
/* ".card p" (class+element) would otherwise outrank a bare ".tab-intro"
   class for the colour, so this is written as ".card p.tab-intro" to
   actually win - the same requirement the old inline styles satisfied
   automatically by being inline. */
.card p.tab-intro { font-size: 0.85rem; color: var(--text-2); margin-bottom: 1rem; }

/* Variant card - the header row (code + one-line desc) is unchanged from
   the original compact layout; "detail" paragraphs and "sources" links
   (see variantCard() in detail.js) are optional additions that only
   appear once an aircraft's JSON has been researched to that depth, so
   older entries with just a one-liner still look identical to before. */
.variant-card { padding: 0.6rem 0; border-bottom: 1px solid var(--border-0); }
.variant-card:last-child { border-bottom: none; }
.variant-card .variant-row { padding: 0; border-bottom: none; }
.variant-row { display: flex; justify-content: space-between; gap: 1rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border-0); }
.variant-row:last-child { border-bottom: none; }
.variant-row .v-code { font-family: var(--font-sans); font-weight: 600; color: var(--radar); min-width: 90px; }
.variant-row .v-desc { color: var(--text-1); font-size: 0.85rem; flex: 1; }
.variant-detail { margin-top: 0.6rem; padding-left: calc(90px + 1rem); }
.variant-detail p { font-size: 0.85rem; color: var(--text-1); line-height: 1.6; }
.variant-detail p + p { margin-top: 0.6rem; }
.variant-card > .i-sources { padding-left: calc(90px + 1rem); }

@media (max-width: 640px) {
  .variant-row { flex-direction: column; gap: 0.2rem; }
  .variant-row .v-code { min-width: 0; }
  .variant-detail, .variant-card > .i-sources { padding-left: 0; margin-top: 0.5rem; }
}

/* Variants table - the only variants design - fixes the card
   layout's misalignment: .v-code above is only min-width, so a longer
   variant code pushes .v-desc to a different starting x-position on
   every row. table-layout: fixed with an explicit width on the code
   column guarantees every description lines up regardless of code
   length. Same visual language as .spec-table (mono/amber header caps,
   hover wash, bottom-rule rows) so it reads as the same design system,
   not a one-off. A variant's optional detail/sources render as a
   full-width row directly under its header row (see variantTableRows()
   in detail.js) rather than a third column, since prose doesn't suit a
   fixed-width cell. */
.variant-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
/* Fixed code column. table-layout:fixed makes the browser honour this width
   instead of sizing the column to its longest cell, which is the whole point:
   without it one long variant code ("Spirit modernisation (2000s-2020s)")
   stretches the column and every description on the page shifts right with
   it. 30% leaves the description the room it needs while staying wide enough
   for a long code to wrap to two lines rather than shredding. */
.variant-table col.variant-col-code { width: 30%; }
.variant-table th, .variant-table td { padding: 0.8rem 0.9rem; text-align: left; vertical-align: top; }
/* No white-space:nowrap here. It was harmless while the table was
   table-layout:auto (the column simply grew to fit the longest code), but
   with the fixed 30% column a long code like "Spirit modernisation
   (2000s-2020s)" had nowhere to go and ran straight over the description.
   Wrapping keeps every code inside its own column. */
.variant-table td.v-code { font-family: var(--font-sans); font-weight: 600; font-size: 0.9rem; color: var(--radar); overflow-wrap: break-word; word-wrap: break-word; hyphens: auto; }
.variant-table td.v-desc { font-family: var(--font-sans); font-size: 0.85rem; color: var(--text-1); line-height: 1.6; overflow-wrap: anywhere; word-wrap: break-word; }
.variant-table tbody tr.vt-row { border-bottom: 1px solid var(--border-0); transition: background-color 0.12s ease; }
.variant-table tbody tr.vt-row:hover { background: var(--bg-3); }
.variant-table tr.variant-detail-row td { padding-top: 0; border-bottom: 1px solid var(--border-0); }
.variant-table tbody tr:last-child { border-bottom: none; }
.variant-table .variant-detail { margin: 0; }
.variant-table .variant-detail p { font-size: 0.85rem; color: var(--text-1); line-height: 1.6; }
.variant-table .variant-detail p + p { margin-top: 0.6rem; }
.variant-table .i-sources { margin-top: 0.4rem; }

.kv-list { display: flex; flex-direction: column; gap: 0.65rem; }
.kv-row { display: flex; justify-content: space-between; font-size: 0.85rem; gap: 1rem; }
.kv-row .k { color: var(--text-3); }
/* Sans-serif (matches the description/history/service prose) rather than the
   monospace used for short glanceable labels elsewhere on the page - the
   Full Specifications tab, Sources list and "At a Glance" sidebar can carry
   full-sentence values (crew, capacity, engines...) that get hard to read
   in mono at length, especially on mobile. */
/* min-width:0 + overflow-wrap:anywhere: source URLs are single unbroken
   tokens (long PDF filenames etc.) that flex refuses to shrink, so without
   these one long link stretches the whole page wider than a phone viewport
   - the "Sources tab breaks the layout on mobile" bug. */
.kv-row .k { min-width: 0; }
.kv-row .v { color: var(--text-0); text-align: right; font-family: var(--font-sans); min-width: 0; overflow-wrap: anywhere; }

/* Full Specifications tab v4 - colour-coded table, US units only. Replaces
   v2's icon-tile grid (which spread related figures across separate boxes
   and switched fonts inconsistently) and v3's US/Metric/Both toggle (one
   system is simpler, so the metric figure is now stripped at render time
   via usOnly() in detail.js rather than hidden with CSS/JS). Every row
   lives in one table - mono/amber labels, bold sans values, always, no
   exceptions - and colour-codes groups with a left rail so
   Performance/Airframe/Power/Program still read as distinct sections.
   Piloted on the F-117 Nighthawk; every aircraft shares the same specs
   shape so this applies database-wide with no JSON changes. */
/* ============================================================
   CANONICAL DESIGN - Full Specifications tab (v6, dated 2026-07-27).
   This is the current, user-approved standard for every aircraft's
   Specifications tab - iconed colour-coded category banners (fixed
   35.2px/2.2rem tall, vertically centred, identical across all four
   categories), roomier label/value type, and row hover feedback. It was
   built and refined over several rounds directly against the C-130
   Hercules entry and is meant to apply site-wide with no per-aircraft
   JSON changes.
   If you are looking at an OLDER version of this block (no
   .spec-cat-inner span, category rows using display:flex directly on
   the <td>, or a plain text-only category header with no icon) - that
   is a regression, most likely from an out-of-date cached copy of this
   file overwriting the current one. Restore this version rather than
   treating the older one as a new starting point.
   Nothing under data/aircraft/*.json (including anything written via
   the planesdb-aircraft-entry skill) should ever need to touch this
   file or src/templates/detail.js - if a change here seems necessary
   to add an aircraft, that's a sign something's being done in the
   wrong file.
   ============================================================ */
.spec-table-card { padding: 1.25rem 1.4rem 0.7rem; }

/* Optional variant note banner - sits between the "Full Specifications"
   title and the table itself (see specNoteBanner() in detail.js), driven
   by the optional plane.specifications.specificationsNote string. v2 (toned down
   from the first pass, which used a bold amber left-accent + tinted
   background and read as an alert box competing with the table for
   attention). Modelled after GitHub/Docusaurus-style "note" admonitions
   but deliberately muted well below their default contrast: a thin
   neutral border (not a colour-coded one), a small dim icon, and text a
   shade quieter than the surrounding card copy - closer to the site's
   existing .spec-more-hint caption treatment than a warning callout.
   Renders nothing at all when specificationsNote is unset or empty, so existing entries
   are unaffected. */
.spec-note {
  display: flex; align-items: flex-start; gap: 0.5rem;
  background: var(--bg-1);
  border: 1px solid var(--border-0);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.8rem;
  margin-bottom: 1.1rem;
}
.spec-note-icon { flex-shrink: 0; width: 14px; height: 14px; margin-top: 0.15rem; color: var(--text-3); }
.spec-note-icon svg { display: block; width: 100%; height: 100%; }
.spec-note-text { font-family: var(--font-sans); font-size: 0.82rem; line-height: 1.5; color: var(--text-2); }
.spec-note-text strong {
  font-family: inherit; font-weight: inherit; font-size: inherit;
  color: inherit;
  margin-right: 0.15rem;
}

.spec-table { width: 100%; border-collapse: collapse; }
.spec-table th, .spec-table td { padding: 0.8rem 0.9rem; text-align: left; vertical-align: top; }
/* One font per column, always - the labels are always mono/caps/amber
   (matches every other label on the page: .spec-item .spec-label,
   .kv-row .k), the values are always sans and always bold, whether the
   value is a short number or a full sentence like Combat Losses. No
   switching fonts or weights depending on content length.
   v5 bumped both columns up a notch (label 0.72->0.76rem, value
   0.85->0.94rem, value line-height 1.55->1.6) and gave values a touch
   more letter-spacing room - on the v4 pass the value column read small
   and cramped next to the roomier type used everywhere else on the
   detail page (e.g. .qf-text, .incident-item p at 0.84-0.88rem). */
.spec-table th {
  font-family: var(--font-mono); font-weight: 500; font-size: 0.76rem; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--amber); white-space: nowrap; width: 1%;
}
.spec-table th svg { width: 15px; height: 15px; margin-right: 0.45rem; vertical-align: -3px; flex-shrink: 0; }
.spec-table td { font-family: var(--font-sans); font-weight: 600; font-size: 0.94rem; color: var(--text-0); line-height: 1.6; letter-spacing: 0.005em; }
.spec-table tbody tr.spec-row { border-bottom: 1px solid var(--border-0); border-left: 4px solid transparent; transition: background-color 0.12s ease; }
.spec-table tbody tr.spec-row:last-child { border-bottom: none; }
/* Hover feedback on data rows - a static table otherwise gives no signal
   that a row is being scanned; a faint highlight (matches the wash used
   on .op-row a:hover elsewhere on the site) helps the eye track across a
   wide value cell without needing to re-find the row. */
.spec-table tbody tr.spec-row:hover { background: var(--bg-3); }

/* Category header rows, v5 - previously a single line of small coloured
   caps text that read as barely more than a divider; on the "make
   categories more obvious" request these are now a full-width tinted
   banner (soft colour wash + an icon + a solid underline in the same
   category colour) so Performance/Airframe/Power/Program register
   as distinct sections on a quick scan, not just on close reading. */
.spec-cat-row td {
  /* Fixed height + vertical-align:middle (both table-cell-native, no flex
     needed on the <td> itself) - every category row gets the SAME
     overall box, regardless of label length or row position.
     Deliberately NOT display:flex here (that was the v5 bug fixed
     earlier) - setting display:flex on a <td> strips its table-cell box
     type, so the browser stops honouring its colspan="2" and the
     anonymous cell it falls back to shrink-wraps to content width
     instead of spanning the row. Kept as a plain table-cell; the
     icon+label centring inside it is handled by .spec-cat-inner below,
     not by anything on the td. */
  height: 2.2rem; box-sizing: border-box; padding: 0 0.9rem;
  vertical-align: middle; line-height: 1;
  border-bottom: 2px solid transparent;
}
/* Icon + label live in their own inline-flex span rather than making the
   td a flex container (see above) - this also sidesteps a baseline quirk
   where an icon sitting as a bare inline-block next to a text node (the
   original v5/v6 approach, aligned with a hand-tuned vertical-align:-3px)
   quietly inflates the line box taller than the text's own line-height,
   which was why shrinking `height` alone didn't actually shrink the row.
   Flexbox centres both against this span's own box instead, so the row's
   rendered height now matches `height` above exactly. */
.spec-cat-row td, .spec-cat-inner { font-family: var(--font-mono); font-size: 0.8rem; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; }
.spec-cat-inner { display: inline-flex; align-items: center; height: 100%; }
.spec-cat-icon { display: inline-flex; flex-shrink: 0; margin-right: 0.55rem; }
.spec-cat-icon svg { width: 15px; height: 15px; display: block; }
.spec-cat-row.spec-cat-performance td { color: var(--radar); background: linear-gradient(90deg, rgba(77,255,176,0.12), transparent 75%); border-bottom-color: rgba(77,255,176,0.4); }
.spec-row.spec-cat-performance { border-left-color: var(--radar-dim); }
.spec-cat-row.spec-cat-airframe td { color: var(--cyan); background: linear-gradient(90deg, rgba(79,215,255,0.12), transparent 75%); border-bottom-color: rgba(79,215,255,0.4); }
.spec-row.spec-cat-airframe { border-left-color: var(--cyan); }
.spec-cat-row.spec-cat-power td { color: var(--orange); background: linear-gradient(90deg, rgba(255,138,77,0.12), transparent 75%); border-bottom-color: rgba(255,138,77,0.4); }
.spec-row.spec-cat-power { border-left-color: var(--orange); }
.spec-cat-row.spec-cat-program td { color: var(--violet); background: linear-gradient(90deg, rgba(180,92,255,0.12), transparent 75%); border-bottom-color: rgba(180,92,255,0.4); }
.spec-row.spec-cat-program { border-left-color: var(--violet); }
/* Data rows carry the same spec-cat-* class as their section's header row
   (set directly in specRow(), not inferred via sibling selectors) so the
   left-rail colour is explicit and correct regardless of row order. The
   ".spec-cat-row.spec-cat-* td { color }" rules above are scoped to the
   header row specifically (needs BOTH classes) so a data row's own
   label/value colours (amber label, text-0 value, set on th/td above)
   are untouched - only its left rail picks up the category colour. */

/* Base .op-icon size/shape - the single source of truth for operator badge
   sizing everywhere on the site (an aircraft's own Operators tab via
   .operator-row below, plus the /operators/ index and detail pages via
   .op-row - see src/templates/operators-index.js and operator-detail.js).
   Used to only be defined scoped under ".operator-row .op-icon", which
   meant badges rendered anywhere else (the /operators/ index rows) picked
   up no sizing at all - unscoped here so one 30x30 badge size applies
   consistently no matter which page it's on. */
.op-icon { width: 30px; height: 30px; border-radius: var(--radius-sm); background: var(--bg-3); border: 1px solid var(--border-1); display:flex; align-items:center; justify-content:center; color: var(--radar); flex-shrink:0; }

.operator-row { display: flex; align-items: flex-start; gap: 0.7rem; padding: 0.7rem 0; border-bottom: 1px solid var(--border-0); }
.operator-row:last-child { border-bottom: none; }
.operator-row .op-name { font-weight: 600; font-size: 0.9rem; }
.operator-row .op-meta { font-size: 0.72rem; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.04em; font-family: var(--font-mono); }

/* /operators/ index rows (.op-row, used by operators-index.js) - name and
   aircraft-type count stacked on two lines rather than crammed onto one,
   which is what was previously crushing the count down to an unreadable
   sliver next to a long operator name in a narrow grid column. */
.op-row a { display: flex; align-items: center; gap: 0.6rem; padding: 0.4rem 0.5rem; border-radius: var(--radius-sm); text-decoration: none; min-width: 0; }
.op-row a:hover { background: var(--bg-3); }
.op-row-text { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.op-row-text b { font-size: 0.85rem; color: var(--text-0); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.op-row-text .az-sub { white-space: nowrap; }

/* Retired/historical section heading modifier (operator-detail.js) -
   every .section-title already renders its own green dot via the
   ::before rule above; this just recolours THAT SAME dot to red for a
   retired-fleet heading rather than adding a second dot alongside it. */
.section-title-red::before { background: var(--red); box-shadow: 0 0 8px rgba(255,92,92,0.55); }

/* "Official website" link on an operator's page (operator-detail.js) - a
   bare inline ui.link SVG has no size of its own outside of a scoping
   rule like this, so without it the icon renders at its full intrinsic
   size (see .incident-item .i-sources svg above for the established
   11px-icon convention this mirrors). */
.op-website-link { font-size: 0.85rem; color: var(--text-2); margin-top: 0.6rem; }
.op-website-link svg { width: 13px; height: 13px; vertical-align: -2px; opacity: 0.8; }
.op-website-link a { color: var(--radar); text-decoration: none; }
.op-website-link a:hover { text-decoration: underline; }

/* Operator flag badge - used when no licensed logo file is supplied for the
   operator (replaced an initials monogram, see opBadge() in detail.js).
   The flag SVGs are 3:2 in a 30x30 square badge, so they are set to the box
   width and centred, leaving an even 5px band above and below rather than
   being stretched or cropped. overflow:hidden + the inner radius keep the
   corners inside the badge's own border. */
.op-flag { overflow: hidden; padding: 3px; }
.op-flag svg {
  width: 100%; height: auto; display: block; border-radius: 1px;
  box-shadow: 0 0 0 1px var(--border-0);
}
.op-logo { overflow: hidden; background: #fff; }
.op-logo img { width: 100%; height: 100%; object-fit: contain; padding: 2px; }

/* ~30% bigger badge (30px -> 39px), used only next to the H1 on an
   operator's own /operators/<slug>/ page (operator-detail.js) - everywhere
   else (an aircraft's Operators tab, the /operators/ index rows) stays the
   standard 30px .op-icon size. */
.op-icon-lg { width: 39px; height: 39px; }

/* "Show N more" expandable for long operator lists (native <details>) */
.op-more > summary {
  list-style: none; cursor: pointer;
  font-family: var(--font-mono); font-size: 0.74rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--amber);
  padding: 0.65rem 0 0.2rem; display: flex; align-items: center; gap: 0.4rem;
}
.op-more > summary::-webkit-details-marker { display: none; }
.op-more > summary svg { width: 14px; height: 14px; transition: transform .15s ease; }
.op-more[open] > summary svg { transform: rotate(180deg); }
.op-more > summary:hover { color: #ffcb63; }

.fr24-box { background: linear-gradient(160deg, rgba(79,215,255,0.08), transparent); border: 1px solid var(--border-1); }
.fr24-box .fr24-code { display:inline-block; font-family: var(--font-mono); background: var(--bg-3); border:1px solid var(--border-1); color: var(--cyan); padding: 0.25rem 0.55rem; border-radius: var(--radius-sm); font-weight:700; margin: 0.2rem 0.35rem 0.2rem 0; }
/* Untrackable variant (see fr24Box() in detail.js) - same shell, muted
   cyan wash traded for a neutral one so the box doesn't visually promise
   a tracking hint it can't actually deliver. */
.fr24-box-untrackable { background: linear-gradient(160deg, rgba(127,143,138,0.08), transparent); }
/* The box's two branches (trackable / untrackable) both step through the
   same "primary line, then two progressively-muted notes" hierarchy -
   previously each repeated its own inline font-size/color instead of
   sharing these two classes. */
.fr24-box p { font-size: 0.85rem; }
.fr24-box .fr24-note { color: var(--text-2); font-size: 0.78rem; margin-top: 0.6rem; }
.fr24-box .fr24-subnote { color: var(--text-3); font-size: 0.74rem; margin-top: 0.5rem; }

.incident-item { padding: 0.85rem 0; border-bottom: 1px solid var(--border-0); }
.incident-item:last-child { border-bottom: none; }
.incident-item .i-date { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-3); letter-spacing: 0.04em; }
.incident-item h4 { margin-top: 0.3rem; font-size: 0.9rem; font-weight: 600; }
.incident-item p { margin-top: 0.35rem; font-size: 0.84rem; }

/* News tab category colour-coding (see newsCategoryMeta() in detail.js).
   Reuses the exact same shape+colour language as the Timeline tab
   (.timeline-incident is red, .timeline-development is green, etc.) so a
   reader who's already learned "red triangle = incident" from one tab gets
   it for free on the other. A left accent bar on each story - not just the
   badge text colour - means the category reads even in a quick vertical
   scan, not just when your eye lands on the small icon. */
.news-item { border-left: 3px solid var(--border-1); padding-left: 0.7rem; margin-left: -0.7rem; }
/* Empty News tab - an entry with no story published about it yet. Set as a
   plain read rather than a warning/notice box: nothing is wrong, there is
   just no news, and a coloured callout would imply the entry is incomplete. */
.news-empty { font-size: 0.88rem; color: var(--text-2); max-width: 60ch; margin: 0.2rem 0 0; }
.news-cat-badge { display: inline-flex; align-items: center; gap: 0.3rem; }
.news-cat-badge svg { width: 12px; height: 12px; flex-shrink: 0; }
.news-cat-incident { border-left-color: var(--red); }
.news-cat-incident .news-cat-badge, .news-cat-incident .featured { color: var(--red); }
.news-cat-operations { border-left-color: var(--amber); }
.news-cat-operations .news-cat-badge, .news-cat-operations .featured { color: var(--amber); }
.news-cat-upgrade { border-left-color: var(--radar); }
.news-cat-upgrade .news-cat-badge, .news-cat-upgrade .featured { color: var(--radar); }
.news-cat-milestone { border-left-color: var(--violet); }
.news-cat-milestone .news-cat-badge, .news-cat-milestone .featured { color: var(--violet); }
.news-cat-policy { border-left-color: var(--text-2); }
.news-cat-policy .news-cat-badge, .news-cat-policy .featured { color: var(--text-2); }
.news-cat-analysis { border-left-color: var(--cyan); }
.news-cat-analysis .news-cat-badge, .news-cat-analysis .featured { color: var(--cyan); }
.news-cat-general { border-left-color: var(--border-1); }
.news-cat-general .news-cat-badge, .news-cat-general .featured { color: var(--text-3); }

/* ---------- Quick Facts (trivia card, Overview tab) ---------- */
/* No coloured left rule: .qf-card is a plain .card like every other box on a
   database entry. It carried a 2px --radar border-left until September 2026,
   which made it the only card on the page with one. */
.qf-list { list-style: none; margin: 0; padding: 0; }
.qf-item {
  display: flex; gap: 0.85rem; align-items: baseline;
  padding: 0.7rem 0; border-bottom: 1px dashed var(--border-0);
}
.qf-item:last-child { border-bottom: none; }
.qf-index {
  flex: 0 0 auto; font-family: var(--font-mono); font-size: 0.68rem;
  color: var(--amber); letter-spacing: 0.08em;
  border: 1px solid var(--border-0); border-radius: 4px;
  padding: 0.15rem 0.4rem; background: var(--bg-3);
}
.qf-text { font-size: 0.88rem; color: var(--text-1); }

/* Homepage Quick Facts panel variant - plain panel (no coloured side rule)
   with a small amber plane-icon bullet instead of the detail page's
   "FACT-01" mono index chip, which read as too busy at this compact size.
   Overrides .qf-item's align-items:baseline with flex-start - baseline
   alignment worked fine for the old inline SVG glyph, but the PNG bullet
   (yellow-arrow-icon.png) has no text baseline of its own to align with,
   so the flex box fell back to its bottom edge and left the icon sitting
   noticeably higher than the first line of text. flex-start + a
   hand-tuned margin-top on .qf-plane-icon (checked against a row of
   margin-top test values 0-6px) centres it against the text instead. */
.qf-item-home { gap: 0.7rem; align-items: flex-start; }
.qf-plane-icon { flex: 0 0 auto; color: var(--amber); margin-top: 3px; }
.qf-plane-icon svg { width: 16px; height: 16px; }
/* Facts of the Day bullet is a small PNG (yellow-arrow-icon.png), not an
   inline SVG like the rest of the site's iconography - display:block
   drops the inline-image's default baseline gap under the icon. */
.qf-plane-icon img { width: 16px; height: 16px; display: block; }

/* Loading placeholder shown in the homepage Facts of the Day panel until
   src/assets/js/fotd.js swaps in today's real picks - reuses the same
   generic .shimmer animation as the Aircraft of the Week skeleton. */
.fotd-skel-line { display: inline-block; width: 70%; height: 0.85rem; border-radius: 999px; }

/* ---------- Upcoming Airshows panel (homepage) ---------- */
.airshow-list { list-style: none; margin: 0.4rem 0 0; padding: 0; }
.airshow-list li {
  display: flex; gap: 0.7rem; align-items: flex-start;
  padding: 0.6rem 0; border-bottom: 1px dashed var(--border-0);
}
.airshow-list li:last-child { border-bottom: none; }
.airshow-list .flag-icon { flex: 0 0 auto; margin-top: 0.15rem; }
.airshow-body { display: flex; flex-direction: column; gap: 0.12rem; min-width: 0; }
.airshow-name { font-size: 0.86rem; font-weight: 600; color: var(--text-0); text-decoration: none; }
.airshow-name:hover { color: var(--radar); text-decoration: underline; }
.airshow-meta { font-size: 0.68rem; color: var(--amber); letter-spacing: 0.03em; }
.airshow-desc { font-size: 0.76rem; color: var(--text-2); }

/* News tab headline links */
.news-item h4 a { color: var(--text-0); text-decoration: none; }
.news-item h4 a:hover { color: var(--radar); text-decoration: underline; }
.news-item h4 a svg { width: 11px; height: 11px; vertical-align: -1px; opacity: 0.6; }

/* Quick Facts sits in the narrow sidebar - tighten it slightly */
aside .qf-item { gap: 0.6rem; padding: 0.55rem 0; }
aside .qf-text { font-size: 0.8rem; }

/* Per-incident source links (deep-dive entries + the Timeline tab, which
   reuses this same "i-sources" markup for its own per-milestone sources). */
.incident-item .i-sources, .timeline-item .i-sources, .variant-card .i-sources, .variant-table .i-sources,
.inc-entry .i-sources, .inc-card .i-sources { margin-top: 0.4rem; font-size: 0.72rem; color: var(--text-3); }
.incident-item .i-sources svg, .timeline-item .i-sources svg, .variant-card .i-sources svg, .variant-table .i-sources svg,
.inc-entry .i-sources svg, .inc-card .i-sources svg { width: 11px; height: 11px; vertical-align: -1px; opacity: 0.7; }
.incident-item .i-sources a, .timeline-item .i-sources a, .variant-card .i-sources a, .variant-table .i-sources a,
.inc-entry .i-sources a, .inc-card .i-sources a { color: var(--radar); text-decoration: none; }
.incident-item .i-sources a:hover, .timeline-item .i-sources a:hover, .variant-card .i-sources a:hover, .variant-table .i-sources a:hover,
.inc-entry .i-sources a:hover, .inc-card .i-sources a:hover { text-decoration: underline; }

/* ---------- Timeline tab (graphical milestone view, experimental) ----------
   Two parts: a horizontal colour-coded "scale" giving an at-a-glance read of
   how events cluster over the aircraft's life, then the same milestones as
   a detailed vertical timeline underneath. Each category gets its own icon
   AND colour (development=wrench/radar-green, first-flight=arrow/cyan,
   combat=target/amber, incident=warning triangle/red, retirement=flag/grey,
   milestone=star/violet) - shared across the scale, legend and vertical
   markers, so a shape means the same thing everywhere, not just a colour.
   The icons stand on their own (no circular badge behind them) - a plain
   badge ring made the icon itself smaller and harder to read at a glance,
   so the coloured glyph IS the marker here. */
.timeline-legend { display: flex; flex-wrap: wrap; gap: 0.6rem 1.2rem; margin: 0.9rem 0 1.6rem; }
.timeline-legend-item { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.72rem; color: var(--text-2); font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.04em; }
.timeline-legend-dot { display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.timeline-legend-dot svg { width: 16px; height: 16px; }

.timeline-scale { position: relative; height: 62px; margin: 0 0.5rem; }
.timeline-scale-track { position: absolute; left: 0; right: 0; top: 28px; height: 2px; background: var(--border-1); }
/* Now a real <button> (see timelineScale() in detail.js) rather than a
   decorative <span>, so this resets the default button chrome (border,
   padding, font) and adds a hover/focus affordance - a slight scale-up plus
   a ring, in the category's own colour via currentColor - so it reads as
   clickable rather than just another label. */
.timeline-scale-dot {
  position: absolute; top: 28px; width: 28px; height: 28px; margin-left: -14px; margin-top: -14px;
  cursor: pointer; background: var(--bg-2); border: none; padding: 0; border-radius: 50%; font: inherit;
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.timeline-scale-dot svg { width: 22px; height: 22px; }
.timeline-scale-dot:hover, .timeline-scale-dot:focus-visible {
  transform: scale(1.15); box-shadow: 0 0 0 3px currentColor; outline: none;
}
.timeline-scale-label { position: absolute; top: 46px; font-family: var(--font-mono); font-size: 0.7rem; color: var(--text-3); }
.timeline-scale-label-start { left: 0; }
.timeline-scale-label-end { right: 0; }

/* "Axis break" marker for a compressed quiet stretch (see
   timelineScalePositions() in detail.js) - a dotted zigzag on the track
   itself plus a small "N yrs" label, so squeezing a long empty gap down
   to a fixed width reads as an intentional compression, not a bug. */
.timeline-scale-break { position: absolute; top: 28px; transform: translate(-50%, -50%); text-align: center; pointer-events: none; }
.timeline-scale-break-zigzag { display: block; font-size: 0.6rem; letter-spacing: 0.1em; color: var(--text-3); background: var(--bg-2); padding: 0 0.15rem; }
.timeline-scale-break-label { display: block; margin-top: 0.65rem; font-family: var(--font-mono); font-size: 0.6rem; color: var(--text-3); white-space: nowrap; }

.timeline { list-style: none; margin: 0; padding: 0; position: relative; }
.timeline::before { content: ""; position: absolute; left: 12px; top: 4px; bottom: 4px; width: 2px; background: var(--border-1); }
.timeline-item { position: relative; padding: 0 0 1.6rem 3.2rem; border-radius: var(--radius-sm); transition: background-color 0.3s ease; }
.timeline-item:last-child { padding-bottom: 0; }
/* Landed-on highlight after clicking a scale icon above (or a future deep
   link) - same colour/duration as .ref-item.ref-flash on the Sources tab,
   just under a different name since this isn't a "reference". */
.timeline-item.timeline-flash { background-color: rgba(77, 255, 176, 0.14); }
.timeline-dot {
  position: absolute; left: 0; top: -0.2rem; width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center; background: var(--bg-2);
}
.timeline-dot svg { width: 24px; height: 24px; }
.timeline-date { display: inline-block; font-size: 0.72rem; color: var(--text-2); letter-spacing: 0.04em; margin-bottom: 0.25rem; }
.timeline-body h4 { font-size: 0.94rem; margin-bottom: 0.3rem; }
.timeline-body p { font-size: 0.85rem; color: var(--text-1); }

/* Category colours - applied to the class on .timeline-item / the scale dot
   / the legend dot, so one class drives the icon's colour (via currentColor)
   everywhere it appears. */
.timeline-development.timeline-item .timeline-dot, .timeline-scale-dot.timeline-development, .timeline-legend-dot.timeline-development { color: var(--radar); }
.timeline-first-flight.timeline-item .timeline-dot, .timeline-scale-dot.timeline-first-flight, .timeline-legend-dot.timeline-first-flight { color: var(--cyan); }
.timeline-combat.timeline-item .timeline-dot, .timeline-scale-dot.timeline-combat, .timeline-legend-dot.timeline-combat { color: var(--amber); }
.timeline-incident.timeline-item .timeline-dot, .timeline-scale-dot.timeline-incident, .timeline-legend-dot.timeline-incident { color: var(--red); }
.timeline-retirement.timeline-item .timeline-dot, .timeline-scale-dot.timeline-retirement, .timeline-legend-dot.timeline-retirement { color: var(--text-3); }
.timeline-milestone.timeline-item .timeline-dot, .timeline-scale-dot.timeline-milestone, .timeline-legend-dot.timeline-milestone { color: var(--violet); }
.timeline-development.timeline-item .timeline-date { color: var(--radar); }
.timeline-first-flight.timeline-item .timeline-date { color: var(--cyan); }
.timeline-combat.timeline-item .timeline-date { color: var(--amber); }
.timeline-incident.timeline-item .timeline-date { color: var(--red); }
.timeline-milestone.timeline-item .timeline-date { color: var(--violet); }

/* Incidents tab (747-test experiment, see incidentsTab() in detail.js) -
   "fatal"/"safe" are a second, independent category set layered onto the
   same .timeline-scale-dot/.timeline-legend-dot/.timeline-item machinery
   the real Timeline tab above uses, so the two tabs share one visual
   language (and one set of CSS rules for the shape/positioning) without
   either one's colours leaking into the other's category list. */
.timeline-fatal.timeline-item .timeline-dot, .timeline-scale-dot.timeline-fatal, .timeline-legend-dot.timeline-fatal { color: var(--red); }
.timeline-safe.timeline-item .timeline-dot, .timeline-scale-dot.timeline-safe, .timeline-legend-dot.timeline-safe { color: var(--radar); }
.timeline-fatal.timeline-item .timeline-date { color: var(--red); }
.timeline-safe.timeline-item .timeline-date { color: var(--radar); }
/* Static third legend entry explaining the independent "Hull Loss" badge
   (see incidentBadges() in detail.js) - amber to match the badge-amber
   pill it's describing, but not itself a .timeline-scale-dot category
   since hull loss isn't one of the two severities the scale/dots colour
   by (a hull-loss aircraft can have zero fatalities, and vice versa). */
.timeline-hull-loss-legend { color: var(--amber); }

/* Incidents tab row furniture: the flight/callsign aside next to an
   incident's title, the fatality/hull-loss badge pair, and the small
   mono location line - siblings of the shared .i-sources/.chip/.badge
   classes above rather than new one-off styles. */
.incident-flight { font-weight: 400; color: var(--text-2); font-size: 0.85rem; }
.incident-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0.5rem 0; }
.incident-location { display: inline-flex; align-items: center; gap: 0.3rem; font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-3); }
.incident-location svg { width: 13px; height: 13px; flex-shrink: 0; }

/* =========================================================
   INCIDENTS TAB - MINIMAL-SCHEMA DESIGNS B ("log") and C
   ("decades"). See the block comment above incidentsLogTab() in
   src/templates/detail.js for the six-key object both read.

   Deliberately small: the two designs share almost all their
   furniture (.inc-name, .inc-meta, .inc-toll, .inc-tag) and differ
   only in how entries are arranged, so there's roughly one screen of
   CSS here rather than a bespoke component set per design. No
   JavaScript is involved in either.
   ========================================================= */

/* Shared row furniture ---------------------------------------------- */
.inc-name { margin: 0 0 0.4rem; font-size: 0.9rem; font-weight: 600; line-height: 1.35; color: var(--text-0); }
.inc-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem; margin-bottom: 0.5rem; }
.inc-meta:empty { display: none; }
/* The toll is the one derived value doing real work - it's the whole
   reason there's no "severity" key in the JSON, so it's styled to be
   the thing your eye lands on after the title. */
.inc-toll {
  font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 0.24rem 0.55rem; border-radius: 999px; border: 1px solid;
}
.inc-toll-fatal { color: var(--red); border-color: #7a2f2f; background: rgba(255,92,92,0.08); }
.inc-toll-safe { color: var(--radar); border-color: var(--radar-dim); background: rgba(77,255,176,0.08); }
/* Free text, so it gets a neutral treatment - no colour coding that
   would imply a category list the JSON doesn't actually have. */
.inc-tag {
  font-family: var(--font-mono); font-size: 0.68rem; color: var(--text-2);
  padding: 0.24rem 0.55rem; border: 1px solid var(--border-1); border-radius: 999px;
}.inc-entry p{ font-size: 0.86rem; line-height: 1.65; color: var(--text-1); margin: 0; }

/* Design B: the log ---------------------------------------------------
   A two-column grid per entry - mono date, then everything else -
   with a left rule carrying the fatal/no-fatalities colour. */
.inc-list { list-style: none; margin: 1rem 0 0; padding: 0; }
.inc-entry {
  display: grid; grid-template-columns: 7.5rem 1fr; gap: 1.1rem;
  padding: 1rem 0 1rem 0.9rem; border-top: 1px solid var(--border-0);
  border-left: 2px solid var(--border-1);
}
.inc-entry:first-child { border-top: none; }
.inc-entry.inc-fatal { border-left-color: var(--red); }
.inc-entry.inc-safe { border-left-color: var(--radar-dim); }
/* No fatalities figure supplied - the rule stays neutral rather than
   implying an outcome the entry never claimed. Dashed rather than a
   plain grey solid so it reads as 'indeterminate' instead of as the
   colour coding being absent or broken. See incidentToll(). */
.inc-entry.inc-unknown { border-left-color: var(--border-1); border-left-style: dashed; }
.inc-when { font-size: 0.72rem; color: var(--text-1); padding-top: 0.12rem; }
.inc-card.inc-fatal { border-top-color: var(--red); }
.inc-card.inc-safe { border-top-color: var(--radar-dim); }
.inc-card.inc-unknown { border-top-color: var(--border-1); border-top-style: dashed; }

@media (max-width: 720px) {
  /* The date stops being its own column and becomes a line above the
     title - the only structural change either design needs at phone
     width, which is itself a fair argument for how plain they are. */
  .inc-entry { grid-template-columns: 1fr; gap: 0.3rem; padding-left: 0.8rem; }
  .inc-when { padding-top: 0; font-size: 0.7rem; }
}

/* Light theme: only the two rules that carry a scope-green need
   restating - everything else is already theme-token driven. */
html[data-theme="light"] .inc-toll-fatal { color: #c2422a; border-color: #c2422a; background: rgba(194,66,42,0.08); }
html[data-theme="light"] .inc-toll-safe { color: #2f9a5e; border-color: #2f9a5e; background: rgba(47,154,94,0.08); }
html[data-theme="light"] .inc-entry.inc-fatal, html[data-theme="light"] .inc-card.inc-fatal { border-left-color: #c2422a; border-top-color: #c2422a; }
html[data-theme="light"] .inc-entry.inc-safe, html[data-theme="light"] .inc-card.inc-safe { border-left-color: #2f9a5e; border-top-color: #2f9a5e; }

.related-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }

/* ---------- Database / listing page ---------- */
.db-toolbar {
  display: grid; grid-template-columns: 1fr auto auto auto auto; gap: 0.55rem; align-items: center;
  background: var(--bg-2); border: 1px solid var(--border-0); border-radius: var(--radius-md);
  padding: 0.55rem 0.7rem; margin-bottom: 1.3rem;
}
.db-toolbar .select { padding: 0.45rem 0.5rem; font-size: 0.78rem; }
.db-toolbar .btn { padding: 0.45rem 0.7rem; font-size: 0.72rem; white-space: nowrap; }
.db-toolbar .search-box input { padding: 0.45rem 0.4rem; }
/* Clears the sticky header when revealSearch() in filter.js scrolls the
   box into view - without this the search field lands underneath the
   topbar and looks like the scroll missed. --topbar-h is measured from
   the real header by main.js; the fallback matches the header's height. */
.db-toolbar .search-box { scroll-margin-top: calc(var(--topbar-h, 64px) + 14px); }
.select, .input {
  background: var(--bg-3); border: 1px solid var(--border-1); border-radius: var(--radius-sm);
  padding: 0.55rem 0.7rem; font-size: 0.82rem; color: var(--text-0); font-family: var(--font-mono);
}
.filter-row { display: flex; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 1.1rem; align-items: center; }
/* On desktop the chip list never needs its own scroll container - collapse
   the wrapper out of the layout so its chips join .filter-row's own flex-
   wrap directly (see mobile override, where this becomes a real scroller
   so "More Filters" can stay outside it, always visible). */
.filter-row-scroll { display: contents; }
.chip {
  font-family: var(--font-mono); font-size: 0.74rem; padding: 0.4rem 0.75rem; border-radius: 999px;
  border: 1px solid var(--border-1); background: var(--bg-3); color: var(--text-1);
}
.chip[aria-pressed="true"] { color: var(--bg-0); background: var(--radar); border-color: var(--radar); font-weight: 700; }
.chip svg { width: 13px; height: 13px; vertical-align: -2px; margin-right: 0.15rem; }

/* "More Filters" toggle chip - pinned to the end of the row, amber accent
   so it reads as an action rather than another category to pick. */
.chip-more {
  margin-left: auto; display: inline-flex; align-items: center; gap: 0.3rem;
  color: var(--amber); border-color: var(--amber-dim); white-space: nowrap;
}
.chip-more:hover { background: rgba(245,185,66,0.08); }
.chip-more svg { transition: transform .15s ease; }
.chip-more[aria-expanded="true"] svg { transform: rotate(180deg); }
.chip-more-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 0.3rem; border-radius: 999px;
  background: var(--amber); color: #14100a; font-size: 0.64rem; font-weight: 700;
}
/* Advanced filter panel (manufacturer / era / status / country) - hidden
   until "More Filters" is toggled open. */
.adv-filters {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0.7rem;
  align-items: end;
  background: var(--bg-2); border: 1px solid var(--border-0); border-radius: var(--radius-md);
  padding: 1rem 1.1rem; margin-bottom: 1.1rem;
}
.adv-filter-item { display: flex; flex-direction: column; gap: 0.35rem; }
.adv-filter-item label {
  font-family: var(--font-mono); font-size: 0.64rem; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--text-3);
}
.adv-filter-item .select { width: 100%; }
.adv-filter-clear { justify-self: start; white-space: nowrap; }

/* Card-stats picker - lets visitors choose which stats show on the
   /aircraft/ listing tiles (default: Max Speed + Built). Spans the full
   width of the advanced-filters panel since it holds a whole row of
   chips rather than one dropdown. */
.adv-filter-stats { grid-column: 1 / -1; }
.stat-picker { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.stat-picker-hint { text-transform: none; letter-spacing: normal; color: var(--text-3); font-size: 0.62rem; }

/* Custom flag-select (Country of Origin) - a native <select> can't render
   inline SVG flags in its option list in any browser, so this is a small
   button + listbox that shows real flag icons, backed by a real (visually
   hidden) <select> that filter.js's normal state-binding still drives. */
.flag-select { position: relative; }
.flag-select-trigger {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem; text-align: left; cursor: pointer;
}
.flag-select-trigger span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; }
.flag-select-trigger svg:last-child { width: 15px; height: 15px; flex-shrink: 0; transition: transform .15s ease; color: var(--text-2); }
.flag-select-trigger[aria-expanded="true"] svg:last-child { transform: rotate(180deg); }
.flag-select-trigger .flag-icon { margin-right: 0.5rem; }
.flag-select-menu {
  position: absolute; top: calc(100% + 0.4rem); left: 0; right: 0; z-index: 45;
  max-height: 280px; overflow-y: auto; margin: 0; padding: 0.35rem;
  background: var(--bg-2); border: 1px solid var(--border-1); border-radius: var(--radius-md);
  box-shadow: 0 16px 40px rgba(0,0,0,0.5);
}
.flag-select-menu li {
  display: flex; align-items: center; padding: 0.5rem 0.6rem; border-radius: var(--radius-sm);
  font-size: 0.82rem; cursor: pointer;
}
.flag-select-menu li:hover, .flag-select-menu li:focus-visible { background: var(--bg-4); outline: none; }
.flag-select-menu li[aria-selected="true"] { color: var(--radar); background: rgba(77,255,176,0.08); }
.flag-select-menu li .flag-icon { margin-right: 0.6rem; flex-shrink: 0; }
.results-meta { font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-2); margin-bottom: 0.9rem; }
.results-meta b { color: var(--radar); }
.empty-state { text-align: center; padding: 3rem 1rem; color: var(--text-2); font-family: var(--font-mono); }
/* This is a <button> (see filter.js), so browser UA styles (padding, border,
   background) need resetting explicitly - otherwise it shows its own default
   button "box" around the small .compare-check square inside it. */
.compare-select {
  position: absolute; top: 0.6rem; right: 0.6rem; z-index: 2;
  appearance: none; background: none; border: none; padding: 0; margin: 0;
  display: block; line-height: 0; cursor: pointer;
}
.compare-check { width: 22px; height: 22px; border-radius: 5px; background: rgba(6,10,16,0.75); display:flex; align-items:center; justify-content:center; color: transparent; }
.compare-check.checked { color: var(--bg-0); background: var(--radar); }
.compare-bar {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 60;
  background: var(--bg-2); border-top: 1px solid var(--border-1);
  padding: 0.8rem 1.5rem; display: flex; align-items: center; gap: 1rem;
  transform: translateY(110%); transition: transform .25s ease;
}
.compare-bar.visible { transform: translateY(0); }
.compare-bar .compare-slots { display: flex; gap: 0.5rem; flex: 1; }
.compare-slot { font-family: var(--font-mono); font-size: 0.78rem; background: var(--bg-3); border: 1px solid var(--border-1); border-radius: var(--radius-sm); padding: 0.4rem 0.7rem; display:flex; align-items:center; gap:0.5rem; }
.compare-slot button { color: var(--text-3); background:none; border:none; font-size: 0.9rem; }

.pagination { display: flex; justify-content: center; gap: 0.4rem; margin-top: 2rem; }
.pagination button { width: 34px; height: 34px; border-radius: var(--radius-sm); border: 1px solid var(--border-1); background: var(--bg-3); font-family: var(--font-mono); font-size: 0.8rem; }
.pagination button[aria-current="true"] { background: var(--radar); color: var(--bg-0); border-color: var(--radar); font-weight: 700; }

/* ---------- Compare page ---------- */
.compare-table-wrap { overflow-x: auto; border: 1px solid var(--border-0); border-radius: var(--radius-md); }
table.compare-table { width: 100%; border-collapse: collapse; min-width: 640px; }
table.compare-table th, table.compare-table td { padding: 0.85rem 1.1rem; border-bottom: 1px solid var(--border-0); text-align: left; font-size: 0.9rem; color: var(--text-0); }
table.compare-table thead th { font-family: var(--font-mono); background: var(--bg-2); text-transform: uppercase; font-size: 0.72rem; letter-spacing: 0.06em; color: var(--text-2); }
table.compare-table tbody th { font-family: var(--font-mono); color: var(--amber); font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.05em; background: var(--bg-2); white-space: nowrap; }
table.compare-table td.best { color: var(--radar); font-weight: 700; }
table.compare-table tbody tr:hover td, table.compare-table tbody tr:hover th { background: var(--bg-4); }

/* Aircraft-column headers carry a silhouette thumbnail + name (not just
   plain text) so each column is instantly recognisable, and link through
   to that aircraft's own page. */
table.compare-table thead th.compare-th-plane { text-align: center; padding: 0.9rem 0.9rem; color: var(--text-0); }
.compare-th-plane a { display: flex; flex-direction: column; align-items: center; color: inherit; }
.compare-th-media {
  width: 180px; height: 120px; margin: 0 auto 0.5rem; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, var(--bg-4), var(--bg-2)); border: 1px solid var(--border-1); border-radius: var(--radius-sm);
}
.compare-th-media svg { width: 88%; height: 88%; }
.compare-th-name { display: block; font-size: 0.86rem; font-weight: 700; letter-spacing: 0.02em; text-transform: none; }
.compare-th-plane a:hover .compare-th-name { color: var(--radar); }
.compare-th-plane a:hover .compare-th-media { border-color: var(--radar-dim); }

.compare-picker { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.1rem; margin-bottom: 1.8rem; }
.compare-card-slot {
  width: auto; min-height: 220px; border: 1px dashed var(--border-1); border-radius: var(--radius-md);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.7rem;
  font-family: var(--font-mono); font-size: 0.84rem; color: var(--text-2); text-align:center; padding: 1.2rem;
  position: relative;
}
.compare-card-slot.filled { border-style: solid; border-color: var(--border-1); color: var(--text-0); background: var(--bg-3); }
.compare-slot-empty-label { color: var(--text-2); }
.compare-slot-thumb { width: 100%; max-width: 230px; height: 138px; display: flex; align-items: center; justify-content: center; overflow: hidden; border-radius: var(--radius-sm); }
.compare-slot-thumb svg { width: 100%; height: 100%; }
.compare-slot-add { display: inline-flex; align-items: center; gap: 0.35rem; }
.compare-slot-add svg { width: 15px; height: 15px; }
.compare-slot-default { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; width: 100%; }
.compare-slot-or { font-family: var(--font-mono); font-size: 0.66rem; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em; }
.compare-slot-select { width: 100%; font-size: 0.8rem; }
.compare-slot-search { width: 100%; padding: 0.3rem 0.3rem 0.3rem 0.7rem; }
.compare-slot-search input { font-size: 0.82rem; padding: 0.5rem 0; }
.compare-slot-results {
  position: absolute; top: calc(100% + 0.4rem); left: 50%; transform: translateX(-50%); z-index: 45;
  width: 270px; max-height: 280px; overflow-y: auto; text-align: left;
  background: var(--bg-2); border: 1px solid var(--border-1); border-radius: var(--radius-md);
  box-shadow: 0 16px 40px rgba(0,0,0,0.5);
}
button.search-result { background: none; border: none; border-bottom: 1px solid var(--border-0); width: 100%; text-align: left; }
button.search-result:last-child { border-bottom: none; }

/* Image-stats picker - a distinct bordered panel (not just loose chips)
   so it visually reads as "settings for the card below", with a live
   count and checkmarks that appear on selected chips to make the
   toggle-ability unmistakable at a glance. */
.compare-img-stats { background: var(--bg-2); border: 1px solid var(--border-0); border-radius: var(--radius-md); overflow: hidden; margin: 1.1rem 0 1.3rem; }
/* The whole head is now a real <button> - clicking anywhere on it (icon,
   heading, or blurb) expands/collapses the stat picker below. Collapsed by
   default so the page doesn't front-load a wall of chips. */
.compare-img-stats-head {
  display: flex; align-items: center; gap: 0.9rem; width: 100%;
  background: none; border: none; padding: 1.1rem 1.2rem; margin: 0; text-align: left; cursor: pointer;
  font: inherit; color: inherit;
}
.compare-img-stats-head:hover { background: var(--bg-3); }
.compare-img-stats-toggle-icon {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--bg-3); border: 1px solid var(--border-1); color: var(--amber);
}
.compare-img-stats-head[aria-expanded="true"] .compare-img-stats-toggle-icon { color: var(--radar); border-color: var(--radar-dim); }
.compare-img-stats-toggle-icon svg { width: 22px; height: 22px; }
.compare-img-stats-head-text { flex: 1; min-width: 0; }
.compare-img-stats-head-text h3 {
  font-family: var(--font-mono); font-size: 0.86rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-0);
}
.compare-img-stats-head-text p { font-size: 0.8rem; color: var(--text-2); margin: 0.3rem 0 0; max-width: 60ch; }
.compare-img-stats-chevron { display: flex; flex-shrink: 0; color: var(--text-2); transition: transform .18s ease; }
.compare-img-stats-chevron svg { width: 18px; height: 18px; }
.compare-img-stats-head[aria-expanded="true"] .compare-img-stats-chevron { transform: rotate(180deg); color: var(--text-0); }
.compare-img-stats-body { padding: 0 1.2rem 1.1rem; }
.compare-img-stats-bar { display: flex; align-items: center; justify-content: space-between; gap: 0.7rem; margin-bottom: 0.7rem; }
.compare-img-stats-count { font-family: var(--font-mono); font-size: 0.7rem; color: var(--radar); text-transform: uppercase; letter-spacing: 0.05em; }

.stat-toggle-chip { display: inline-flex; align-items: center; }
.stat-toggle-check {
  display: inline-flex; align-items: center; width: 0; overflow: hidden; opacity: 0;
  transition: width .15s ease, opacity .15s ease, margin .15s ease;
}
.stat-toggle-check svg { width: 12px; height: 12px; flex-shrink: 0; }
/* Nine stats is the cap (MAX_IMG_STATS in src/assets/js/compare.js). At the
   cap the stats that are OFF go dead rather than the click being silently
   dropped, which reads as the page being broken. */
.stat-toggle-chip.is-blocked { opacity: 0.42; cursor: not-allowed; }
.stat-toggle-chip[aria-pressed="true"] .stat-toggle-check { width: 12px; opacity: 1; margin-right: 0.32rem; }

#compare-canvas-wrap { margin: 1.4rem 0 2.75rem; }
#compare-export-canvas {
  display: block; border-radius: var(--radius-md); border: 1px solid var(--border-0);
  width: 100%; max-width: 720px; cursor: zoom-in; transition: border-color .15s ease;
}
#compare-export-canvas:hover, #compare-export-canvas:focus-visible { border-color: var(--radar-dim); }
/* The 4:5 card is 1080x1350, so at the wide card's 720px cap it would stand
   900px tall on the page and push the download button off-screen on a laptop.
   Capped narrower instead - it's a preview, and the lightbox is a click away
   for anyone who wants to read it at size. */
#compare-export-canvas.is-portrait { max-width: 430px; }
/* Set only when the 4:5 card has had to leave stats out to keep the rest
   readable (src/assets/js/compare.js, fitPortraitGrid). Hidden and empty at
   every other moment, including on the wide card. */
.compare-export-note {
  max-width: 720px; margin: 0.6rem 0 0;
  font-size: 0.8rem; line-height: 1.5; color: var(--text-2);
  border-left: 2px solid var(--amber); padding-left: 0.7rem;
}
.compare-export-actions {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  max-width: 720px; margin-top: 0.8rem;
}
.compare-export-hint {
  display: flex; align-items: center; gap: 0.4rem;
  font-family: var(--font-mono); font-size: 0.7rem; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.04em;
}
.compare-export-hint svg { width: 14px; height: 14px; color: var(--radar); flex-shrink: 0; }

/* ---------- Exported-image switches (Compare + Aircraft of the Week) ----------
   Two controls share these classes: the THEME switch built by
   src/assets/js/export-theme.js (mounted on both pages) and the SHAPE switch
   built by src/assets/js/export-layout.js (Compare only, and carrying the
   extra .export-layout class). They're the same small segmented control, and
   giving the second one its own near-identical copy of these rules would only
   let the two drift apart.

   The theme switch picks the palette the PNG is drawn with, which is
   independent of the site theme you're browsing in - you can be reading in
   dark mode and still export a light card for a post. The shape switch picks
   between the wide card and the 4:5 one. On the Compare page the preview
   canvas directly above redraws as you flip either; on Aircraft of the Week
   there's nothing to preview, so the theme switch just sits under the share
   heading with the two download buttons it applies to. */
.export-theme { display: flex; align-items: center; gap: 0.55rem; flex-wrap: wrap; }
/* Both switches sit on a row of their own under the hint and the Download
   button: three items sharing one space-between row wrapped into a lopsided
   L as soon as the shape switch joined the theme one. */
.compare-export-controls {
  order: 3; flex-basis: 100%;
  display: flex; align-items: center; gap: 1.4rem; flex-wrap: wrap;
  margin-top: 0.15rem;
}
.compare-export-actions [data-export-image] { order: 2; }
.aotw-share-block .export-theme { margin-top: 0.75rem; }
.export-theme-label {
  font-family: var(--font-mono); font-size: 0.66rem; color: var(--text-2);
  text-transform: uppercase; letter-spacing: 0.08em;
}
.export-theme-group {
  display: inline-flex; gap: 2px; padding: 2px;
  background: var(--bg-2); border: 1px solid var(--border-0); border-radius: 999px;
}
.export-theme-btn {
  font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 0.3rem 0.8rem;
  background: none; border: 0; border-radius: 999px; color: var(--text-2);
  cursor: pointer; transition: background .15s ease, color .15s ease;
}
.export-theme-btn:hover { color: var(--text-0); }
/* Accent fill on the selected side, with the deepest background colour as the
   text on top of it - the same treatment the pressed filter chips use, so the
   control reads as "one of these two is on" at a glance rather than as two
   buttons waiting to be pressed. */
.export-theme-btn.is-active { background: var(--radar); color: var(--bg-0); font-weight: 600; }
/* --bg-0 is a near-black in both themes, which is right on the dark theme's
   neon green but only about 3.5:1 on the light theme's deeper green. Pale
   parchment on that green is the legible way round. */
html[data-theme="light"] .export-theme-btn.is-active { color: var(--bg-3); }
.export-theme-btn:focus-visible { outline: 2px solid var(--radar-dim); outline-offset: 2px; }

/* ---------- Compare page: click-to-zoom lightbox ---------- */
.lightbox {
  position: fixed; inset: 0; z-index: 500; background: rgba(3, 6, 10, 0.92);
  display: flex; align-items: center; justify-content: center; padding: 5vh 4vw;
  overflow: auto; cursor: zoom-out;
}
.lightbox-inner { max-width: 100%; max-height: 100%; cursor: default; }
.lightbox-img {
  display: block; max-width: 92vw; max-height: 90vh; width: auto; height: auto;
  border-radius: var(--radius-md); border: 1px solid var(--border-1);
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}
.lightbox-close {
  position: fixed; top: 1.2rem; right: 1.2rem; z-index: 501;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-2); border: 1px solid var(--border-1); color: var(--text-0); cursor: pointer;
}
.lightbox-close:hover { border-color: var(--radar-dim); color: var(--radar); }
.lightbox-close svg { width: 20px; height: 20px; }
@media (max-width: 720px) {
  .lightbox { padding: 4vh 3vw; }
  .lightbox-close { top: 0.8rem; right: 0.8rem; width: 40px; height: 40px; }
}

/* ---------- Compare page: popular-comparison shortcuts ----------
   This section earns its own "console module" treatment - a bordered,
   radar-cornered panel with a live-blip eyebrow - rather than reading as
   an afterthought tacked below the main tool. */
.compare-presets-panel {
  position: relative;
  padding: 2.2rem 2rem 2.4rem;
  background: linear-gradient(180deg, var(--bg-2), var(--bg-1) 75%);
  border: 1px solid var(--border-0);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.compare-presets-panel::before,
.compare-presets-panel::after {
  content: ""; position: absolute; width: 26px; height: 26px; pointer-events: none;
  border: 2px solid var(--radar-dim); opacity: 0.7;
}
.compare-presets-panel::before { top: 12px; left: 12px; border-right: none; border-bottom: none; }
.compare-presets-panel::after { bottom: 12px; right: 12px; border-left: none; border-top: none; }
.compare-presets-head { display: block; margin-bottom: 1.7rem; }
.compare-presets-hint { color: var(--text-2); font-size: 0.86rem; max-width: 62ch; margin-top: 0.6rem; }
.compare-presets-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.6rem; }
.compare-preset-card {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 0.8rem; text-align: center;
  background: var(--bg-3); border: 1px solid var(--border-0); border-top: 3px solid var(--border-1); border-radius: var(--radius-md);
  padding: 1.9rem 1.8rem 1.7rem; transition: border-color .15s ease, transform .15s ease, background .15s ease, box-shadow .15s ease;
}
.compare-preset-card:nth-child(4n+1) { border-top-color: var(--radar-dim); }
.compare-preset-card:nth-child(4n+2) { border-top-color: var(--amber-dim); }
.compare-preset-card:nth-child(4n+3) { border-top-color: var(--cyan); }
.compare-preset-card:nth-child(4n+4) { border-top-color: var(--red); }
.compare-preset-card:hover {
  border-color: var(--radar-dim); transform: translateY(-3px); background: var(--bg-4);
  box-shadow: 0 12px 30px rgba(0,0,0,0.35);
}
.compare-preset-rank {
  position: absolute; top: 0.9rem; left: 1rem; font-family: var(--font-mono); font-size: 0.7rem;
  color: var(--text-3); letter-spacing: 0.05em;
}
/* nowrap (not wrap) is deliberate: with 3-plane presets the old wrapping
   flex row would let the "VS" label push the third thumbnail onto its own
   line, breaking the horizontal alignment the eye expects. Thumbs now
   shrink together via flex-basis instead, so the row always stays intact
   and centered no matter how many planes or how narrow the card gets. */
.compare-preset-thumbs { display: flex; flex-wrap: nowrap; align-items: center; justify-content: center; gap: 0.6rem; margin-top: 0.3rem; }
.compare-preset-thumb {
  flex: 1 1 0; min-width: 0; max-width: 190px; height: 120px; display: flex; align-items: center; justify-content: center; overflow: hidden;
  background: linear-gradient(160deg, var(--bg-4), var(--bg-2)); border: 1px solid var(--border-1); border-radius: var(--radius-sm);
  transition: border-color .15s ease;
}
.compare-preset-card:hover .compare-preset-thumb { border-color: var(--radar-dim); }
.compare-preset-thumb svg { width: 88%; height: 88%; }
.compare-preset-vs { flex: 0 0 auto; font-family: var(--font-mono); font-size: 0.68rem; color: var(--amber); font-weight: 700; }
.compare-preset-title { font-weight: 700; font-size: 1.05rem; color: var(--text-0); }
.compare-preset-sub { font-size: 0.82rem; color: var(--text-2); }
.compare-preset-cta {
  display: inline-flex; align-items: center; gap: 0.35rem; margin-top: 0.3rem;
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--radar-dim); opacity: 0; transform: translateY(4px);
  transition: opacity .15s ease, transform .15s ease, color .15s ease;
}
.compare-preset-card:hover .compare-preset-cta { opacity: 1; transform: translateY(0); color: var(--radar); }
.compare-preset-cta svg { width: 13px; height: 13px; }
@media (max-width: 720px) {
  .compare-presets-panel { padding: 1.3rem 1rem 1.5rem; }
  .compare-presets-panel::before, .compare-presets-panel::after { width: 16px; height: 16px; top: 8px; left: 8px; }
  .compare-presets-panel::after { top: auto; left: auto; bottom: 8px; right: 8px; }
  .compare-presets-head { margin-bottom: 1.1rem; }
  .compare-presets-hint { font-size: 0.8rem; }
  .compare-presets-grid { grid-template-columns: 1fr; gap: 0.8rem; }
  .compare-preset-card { padding: 1.6rem 0.9rem 0.9rem; gap: 0.5rem; border-top-width: 2px; }
  .compare-preset-rank { top: 0.6rem; left: 0.65rem; font-size: 0.62rem; }
  .compare-preset-thumbs { gap: 0.4rem; margin-top: 0.1rem; }
  .compare-preset-thumb { height: 78px; max-width: 130px; }
  .compare-preset-vs { font-size: 0.6rem; }
  .compare-preset-title { font-size: 0.9rem; }
  .compare-preset-sub { font-size: 0.74rem; }
  .compare-preset-cta { opacity: 1; transform: none; margin-top: 0.1rem; font-size: 0.66rem; }
}

/* ---------- Misc pages ---------- */
.prose { max-width: 74ch; }
.prose h2 { font-family: var(--font-mono); text-transform: uppercase; font-size: 1.1rem; margin: 1.8rem 0 0.7rem; color: var(--radar); }
.prose p { color: var(--text-1); line-height: 1.7; margin-bottom: 0.9rem; }
.prose ul { padding-left: 1.2rem; list-style: disc; color: var(--text-1); margin-bottom: 0.9rem; }
.prose li { margin-bottom: 0.4rem; }
.prose code.mono, .prose code { background: var(--bg-3); border: 1px solid var(--border-0); color: var(--cyan); padding: 0.12rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.86em; word-break: break-word; }
.prose .qf-card.card { margin: 1.6rem 0; }
.prose .qf-list .qf-text b { color: var(--text-0, var(--text-1)); }

/* ---------- Mobile bottom tab bar ---------- */
/* Rendered on every page by layout.js; hidden on desktop, becomes the
   primary navigation on phones. Styled as an ATC-console instrument
   strip: dark glass, mono labels, radar-green active glow. */
.tabbar { display: none; }

/* ---------- Responsive ---------- */
/* This tier (721-1080px) is the tablet/iPad breakpoint - portrait iPads
   (~768-834px) and landscape iPads (~1024px, iPad Pro landscape is 1366px
   and gets the full desktop layout instead) both land here. It used to
   just squeeze the desktop layout down to 2 columns and stop there, which
   is why it started feeling behind the mobile/desktop passes above - a
   tablet has real screen space (often close to desktop width) but is a
   touch device like a phone, so it needs desktop-ish density with
   mobile-ish tap targets, not either extreme on its own. */
@media (max-width: 1080px) {
  /* Side padding: the base 1.5rem was meant for full desktop width - on
     an actual iPad, or a desktop window resized down into this tier,
     content (nav brand, buttons, card edges, body text) was landing
     noticeably closer to the screen edge than intended, reading as
     unfinished rather than deliberately edge-to-edge. Every element that
     uses `.container` (topbar, sections, the hero) picks this up
     automatically since they all share the one class. */
  .container { padding: 0 1.75rem; }

  /* Hero: keep the radar scope beside the headline/eyebrow at every
     width, the same way the ≤720px mobile tier below already does -
     this used to drop straight to `grid-template-columns: 1fr`, which
     stacked the (now quite large, `max-width: 320px`) scope underneath
     the search bar. That's fine on an actual phone where there's no
     alternative, but on an iPad or a resized desktop window landing in
     this 721-1080px tier, there's clearly still room down the side for
     it, so it read as an unpolished, unintentional stack rather than a
     deliberate layout. Reuses the mobile tier's grid-template-areas
     approach (see the ≤720px block further down) with a larger scope
     sized for tablet width; the mobile block's own narrower values still
     win at ≤720px since it's declared later in the stylesheet. */
  .hero-inner {
    grid-template-columns: 1fr clamp(200px, 30vw, 340px);
    grid-template-areas:
      "eyebrow eyebrow"
      "title   scope"
      "lede    lede"
      "search  search";
    row-gap: 1rem;
  }
  .hero-inner > div:first-child { display: contents; }
  .hero-eyebrow { grid-area: eyebrow; margin-bottom: 0; }
  .hero h1 { grid-area: title; }
  .hero p.lede { grid-area: lede; margin-top: 0; }
  .hero-inner .search-panel { grid-area: search; margin-top: 0.2rem; }
  /* Scales with viewport width (200px at this tier's narrow 721px end up
     to 340px near its 1080px top) rather than a flat size, so a tall,
     narrow browser window - which usually still has plenty of width even
     though it triggered this "tablet" tier - gets a scope closer to
     desktop's, instead of the same small fixed size a genuinely narrow
     iPad portrait view gets. */
  .scope { grid-area: scope; width: clamp(200px, 30vw, 340px); max-width: clamp(200px, 30vw, 340px); justify-self: end; }
  /* The "count" hero's headline block is shorter than the classic
     three-line headline, so the desktop-sized scope set the row height on
     its own and left a visible gap between the headline and the lede.
     A slightly smaller scope in this tier closes it. */
  .hero.hero-count .scope { width: clamp(180px, 24vw, 280px); max-width: clamp(180px, 24vw, 280px); }
  /* The h1 holds long mono strings, so without min-width:0 its min-content
     width wins over the 1fr track and the headline runs under the scope. */
  .hero.hero-count h1.hc-block { min-width: 0; }
  .hero.hero-flap h1 span.sf-row { --sf-fs: clamp(3.4rem, 7.4vw, 5.4rem); }
  .blip-label { display: none; } /* unreadable at this size - just show blips, matches the mobile tier below */
  .cat-grid { grid-auto-flow: row; grid-auto-columns: auto; grid-template-columns: repeat(4, 1fr); }
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .strip { grid-template-columns: 1fr; }
  .detail-layout { grid-template-columns: 1fr; }
  /* Single-column detail layout: swap the trailing aside for the card
     copies inside the tab panels (see .aside-in-tabs-cards by the
     .detail-layout rules above). */
  .detail-layout > aside.aside-in-tabs { display: none; }
  .aside-in-tabs-cards { display: block; }
  .aside-in-tabs-cards .card { margin-top: 1.1rem; }
  .footer-grid { grid-template-columns: repeat(3, 1fr); }
  .related-strip { grid-template-columns: repeat(2, 1fr); }
  /* 3 columns rather than the plain .grid-4 override's 2 - a tablet is wide
     enough that 2 columns leaves the quick-glance spec tiles looking sparse
     and stretched; 3 keeps them dense and balanced up to 4 rows max
     instead of 2 columns' taller stack. */
  .spec-grid { grid-template-columns: repeat(3, 1fr); }
  /* First Flight and Last Flight are a matched pair - on the 4-column
     desktop grid they sit one above the other in the last column, which
     reads as a pair. At 3 or 2 columns the source order pulls them apart
     onto different rows with unrelated stats between them. Ordering them
     last and forcing First Flight to column 1 starts a fresh final row,
     and Last Flight lands beside it. Works at either column count, and
     survives Weight being absent (which changes the tile count by one and
     would otherwise flip the pairing). */
  .spec-item-flight-first { order: 98; grid-column: 1; }
  .spec-item-flight-last { order: 99; }
  .spec-table th, .spec-table td { padding: 0.55rem 0.6rem; }
  /* The tablet-tier padding reset just above also (unintentionally, until
     this was added) matched the category banner's <td>, quietly undoing
     its fixed height/padding any time the VIEWPORT (not the narrower
     card - the browser window) dropped to 1080px or below, which is an
     everyday width even on a widescreen desktop once a sidebar is
     present. Re-asserted at the same specificity as the phone-tier
     override further down this file (".spec-table tr.spec-cat-row td"),
     so at ≤720px the phone block - which comes later in the file and
     switches this row to block/flex layout anyway - still wins as
     intended; this only fixes the >720-and-<=1080px gap in between. */
  .spec-table tr.spec-cat-row td { padding: 0 0.9rem; height: 2.2rem; }

  /* Touch targets: an iPad is touch-first exactly like a phone, so the
     larger tap targets originally written as mobile-only apply from this
     tier up - the narrower @media (max-width: 720px) block further down
     no longer needs to repeat them, it only adds phone-specific sizing on
     top where a control needs to shrink further still. */
  .btn { min-height: 44px; }
  .chip { min-height: 40px; display: inline-flex; align-items: center; }
  .pagination button { width: 42px; height: 42px; }
  .tab-btn { min-height: 46px; }

  /* Homepage "Featured Aircraft": 3 columns x 2 rows (6 of the 8 planes
     build.js now sends) rather than inheriting the plain 2-column
     `.grid-4` override above, which would stack all 8 into 4 long rows -
     technically fine but a lot more scrolling than the tidy 2-row grid
     desktop and mobile both now get. */
  .featured-grid { grid-template-columns: repeat(3, 1fr); }
  .featured-grid > :nth-child(n+7) { display: none; }

  /* Homepage "Latest News & Articles": same compact horizontal-row card
     used on mobile (see the narrower query below), just with a bigger
     thumbnail and a touch more breathing room since a tablet column is
     roughly double a phone's width. The desktop 16:9 banner-on-top card
     reads as too tall once squeezed into this tier's narrower columns. */
  .news-card { display: flex; flex-direction: row; align-items: stretch; }
  .news-card-media { aspect-ratio: auto; width: 96px; height: auto; flex-shrink: 0; border-right: 1px solid var(--border-0); }
  .news-card-media svg { width: 50%; }
  .news-card-body { padding: 0.7rem 0.9rem; display: flex; flex-direction: column; justify-content: center; gap: 0.2rem; min-width: 0; }
  .news-card-body h4 { font-size: 0.85rem; line-height: 1.3; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
  .news-meta { margin-bottom: 0.15rem; }
}
/* =========================================================
   MOBILE (≤720px) — native phone experience.
   Bottom tab bar navigation, thumb-sized touch targets,
   iOS-safe inputs and safe-area insets.
   ========================================================= */
@media (max-width: 720px) {

  /* --- Page chrome --- */
  body { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
  /* Was 1rem - actually LESS side padding than the desktop base (1.5rem),
     which is backwards: a phone is exactly where content pressed right up
     to the screen edge (nav brand, buttons, card text) reads as most
     unfinished. Bumped to 1.25rem, then eased back slightly to 1.1rem -
     1.25rem read as a touch too generous once the header padding bug
     (see .topbar-inner above) was also fixed and the brand/icon-buttons
     started actually respecting this value too. The three spots below
     that hard-code this same value to line up with it (.hero-inner's own
     padding override, .tabs-wrap's edge-to-edge negative margins) are
     updated to match. */
  .container { padding: 0 1.1rem; }
  .section { padding-top: 2.1rem; padding-bottom: 2.1rem; }

  /* Aircraft detail page: show only the first-sentence tagline under the
     plane's name (see detail.js/firstSentence()) so the now-2-sentence
     tagline doesn't push the hero photo further down a phone screen
     before anything else is visible - the full version remains the
     desktop default above this breakpoint, and either way the complete
     2-sentence tagline still goes out in the page's meta description
     (see build.js), so it isn't lost for SEO purposes. */
  .detail-sub-full { display: none; }
  .detail-sub-short { display: block; }

  /* --- Top bar: brand + search only (tab bar handles nav) --- */
  .nav, .topbar-actions .text-scale { display: none; }
  /* Deliberately smaller than the 41px desktop cap - even with the
     distortion bug above fixed, 41px tall still wants ~264px of width,
     which is wider than the brand column has room for on most phones
     once the remaining icon buttons (theme toggle, search, hamburger)
     take their share, so it would still end up shrunk down to fit
     (correctly proportioned, just smaller than intended). Capping the
     height here directly gives the mobile logo an intentional size
     instead of just whatever's left over. */
  .brand-logo { max-height: 30px; }
  /* Mobile nav: the dice makes way for a hamburger that opens the FULL
     site menu (the tab bar keeps the four main sections one tap away;
     this panel covers everything else - Records, A-Z, About...).
     The Aircraft sub-pages render permanently expanded inside the panel,
     so the hover-dropdown mechanics aren't needed on touch. */
  .topbar-actions > [data-random-aircraft] { display: none; }
  .mobile-toggle { display: inline-flex; }
  .nav.nav-open {
    display: flex; flex-direction: column; align-items: stretch; gap: 0;
    position: absolute; top: 100%; left: 0; right: 0; z-index: 90;
    background: var(--bg-2); border-bottom: 1px solid var(--border-1);
    box-shadow: 0 18px 34px rgba(0, 0, 0, 0.55);
    padding: 0.4rem 1rem 0.9rem;
  }
  .nav.nav-open > a, .nav.nav-open .nav-item > a {
    display: block; padding: 0.7rem 0.2rem; border-bottom: 1px solid var(--border-0);
    font-size: 0.9rem;
  }
  .nav.nav-open .nav-item { display: block; position: static; }
  .nav.nav-open .nav-item > a { border-bottom: none; }
  .nav.nav-open .nav-caret { display: none; }
  .nav.nav-open .nav-dropdown {
    display: block; position: static; transform: none; min-width: 0;
    border: none; box-shadow: none; background: transparent;
    padding: 0 0 0.3rem 0.9rem; border-bottom: 1px solid var(--border-0);
  }
  .nav.nav-open .nav-dropdown::before { display: none; }
  /* Longhand top/bottom only, same reasoning as the base .topbar-inner
     rule above - a shorthand "0.7rem 0" here would zero the horizontal
     padding this element gets from also carrying .container, which is
     exactly the bug that left the brand mark and hamburger/icon buttons
     sitting flush against the phone screen edge. */
  .topbar-inner { display: flex; justify-content: space-between; padding-top: 0.7rem; padding-bottom: 0.7rem; }
  .icon-btn { width: 40px; height: 40px; }

  /* --- Bottom tab bar --- */
  .tabbar {
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 70;
    display: grid; grid-template-columns: repeat(4, 1fr);
    background: var(--chrome-bg-strong);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid var(--border-1);
    padding: 0.3rem 0.3rem calc(0.3rem + env(safe-area-inset-bottom));
  }
  .tabbar a {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; min-height: 50px; padding: 0.25rem 0;
    color: var(--text-2); border-radius: var(--radius-sm);
    -webkit-tap-highlight-color: transparent;
  }
  .tabbar-icon { display: flex; }
  .tabbar-icon svg { width: 22px; height: 22px; }
  .tabbar-label {
    font-family: var(--font-mono); font-size: 0.58rem;
    letter-spacing: 0.08em; text-transform: uppercase;
  }
  .tabbar a[aria-current="page"] { color: var(--radar); }
  .tabbar a[aria-current="page"] .tabbar-icon svg { filter: drop-shadow(0 0 7px var(--radar-glow)); }
  .tabbar a:active { background: var(--bg-3); }

  /* --- Inputs: 16px floor stops iOS Safari auto-zooming on focus --- */
  .search-box input, .input, .select { font-size: 16px; }
  /* The placeholder can render smaller than the 16px the input itself
     needs - iOS's focus-zoom keys off the input's font-size, not the
     placeholder's - buying back room for the (already shortened, see
     initShortPlaceholders in main.js) placeholder next to the button. */
  .search-box input::placeholder { font-size: 0.9rem; }

  /* Touch targets are set at the tablet tier above (720px-1080px query) and
     already apply here too - nothing further to override at phone width. */

  /* --- Detail page tabs: mobile-only tweaks to the sticky bar above ---
     The sticky offset itself comes from the base `.tabs-wrap` rule's
     var(--topbar-h) (measured from the real header by main.js - the old
     hardcoded 63px here cropped the header into the tab buttons once the
     brand tagline started wrapping to two lines on phones). This block
     just bleeds the bar's background out to the screen edges via negative
     margins matching `.container`'s 1.1rem mobile padding - technically
     the page background is the same colour either way, but this keeps the
     sticky rectangle's edges exactly aligned with the topbar above it
     rather than leaving an inset that looks slightly off once pinned. */
  .tabs-wrap {
    margin-left: -1.1rem; margin-right: -1.1rem; padding: 0.3rem 1.1rem 0;
  }

  /* --- Hero: headline + small radar side by side (like desktop),
         lede and search full-width below - keeps the page compact --- */
  .hero-inner {
    /* NB: .hero-inner and .container are the same element on the home
       page, and this padding overrides the container's - so the 1.1rem
       side padding must be repeated here or the text hits the edge. */
    padding: 1.7rem 1.1rem 1.8rem;
    grid-template-columns: 1fr 128px;
    grid-template-areas:
      "eyebrow eyebrow"
      "title   scope"
      "lede    lede"
      "search  search";
    gap: 0.85rem 0.8rem;
    align-items: center;
  }
  .hero-inner > div:first-child { display: contents; }
  .hero-eyebrow { grid-area: eyebrow; margin-bottom: 0; }
  .hero h1 { grid-area: title; font-size: 1.5rem; }
  /* Hero "count" variant: the h1 is the whole count block here, so the
     1.5rem above is irrelevant to it - scale its own pieces instead. */
  .hero.hero-count h1.hc-block { min-width: 0; }
  /* Re-state the phone scope size at this variant's specificity: the
     .hero.hero-count .scope rule in the 1080px block above outranks the
     plain .scope rule here, so without this the tablet size leaks down
     onto phones and the radar eats the headline. Same reason at 390px. */
  .hero.hero-count .scope { width: 128px; max-width: 128px; }
  .hero.hero-count .hc-row { gap: 1rem; margin-bottom: 0.3rem; }
  .hero.hero-count h1 span.hc-num { font-size: 3.6rem; }
  .hero.hero-flap h1 span.sf-row { --sf-fs: 3.1rem; --sf-r: 6px; }
  .hero.hero-count .hc-tail { font-size: 1.05rem; }
  /* No room for the "specs / history / variants / operators" block beside
     both the number and the scope at phone widths - it either overflows
     under the radar or shreds into four lines. The lede directly below
     says the same thing in full, so drop it here rather than shrink it. */
  .hero.hero-count .hc-side { display: none; }
  .hero p.lede { grid-area: lede; margin-top: 0; font-size: 0.93rem; }
  .hero-inner .search-panel { grid-area: search; margin-top: 0.2rem; }
  .scope { grid-area: scope; width: 128px; max-width: 128px; justify-self: end; }
  .blip-label { display: none; }   /* unreadable at this size - just show blips */
  .search-box .btn { padding: 0.5rem 0.85rem; }
  [data-search-results] { max-height: 55vh; }

  /* --- Grids & cards --- */
  .cat-grid { grid-auto-flow: row; grid-auto-columns: auto; grid-template-columns: repeat(3, 1fr); gap: 0.6rem; }
  .cat-tile { padding: 0.85rem 0.5rem; }
  .grid-4, .grid-3, .grid-2 { grid-template-columns: 1fr; gap: 0.55rem; }

  /* Plane cards: compact horizontal row instead of the desktop's tall
     banner-on-top layout - a small square thumbnail on the left, name/
     manufacturer/stats on the right, one card is only ~90px tall instead
     of ~380px. Lets a phone screen show 5-6 aircraft at a glance instead
     of barely one, matching the same "wider than tall" tile shape used
     on desktop's stat tiles. Applies everywhere .plane-card is used
     (Aircraft listing, homepage Featured strip, detail-page Related). */
  .plane-card { flex-direction: row; align-items: stretch; }
  .plane-card-media {
    /* Widened ~20% from the original 88px square (106 vs 88) - photos are
       landscape to start with, and object-fit:cover was cropping wingtips
       off to force them into a square. A wider box needs less side-cropping
       to fill the same height, so more of each aircraft's wingspan stays
       visible. Height is NOT fixed: the box stretches to the full row
       height (the card is align-items:stretch), because a fixed 88px left
       a dead gap under the photo whenever the text side wrapped taller.
       min-height keeps short cards at the same ~90px compact row. */
    width: 106px; height: auto; min-height: 88px; aspect-ratio: auto;
    align-self: stretch; flex-shrink: 0;
    border-bottom: none; border-right: 1px solid var(--border-0);
  }
  .plane-card-media svg { width: 82%; }
  /* The role chip has nowhere to go at 88px wide without colliding with
     the compare checkbox in the same corner - drop it on mobile rather
     than let the two overlap; the aircraft name/manufacturer below is
     enough context for scanning a compact list. */
  .plane-card-badge { display: none; }
  .plane-card-cats { bottom: 0.3rem; left: 0.3rem; gap: 0.2rem; }
  .cat-badge { width: 16px; height: 16px; }
  .cat-badge svg { width: 9px; height: 9px; }
  .compare-select { top: 0.3rem; right: 0.3rem; }
  .compare-check { width: 20px; height: 20px; }
  .plane-card-body { padding: 0.55rem 0.75rem; gap: 0.15rem; justify-content: center; overflow: hidden; }
  .plane-card-body h3 { font-size: 0.86rem; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .plane-card-body .role { font-size: 0.68rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; }
  /* Stat values (e.g. max speed) can run long ("1,204 mph (1,937 km/h) at
     altitude (Mach 1.8)") - clamp to one line so a verbose spec string on
     one card can't blow its height out and break the uniform compact row
     the rest of the list relies on. */
  .plane-card-stats { gap: 0.9rem; margin-top: 0.3rem; padding-top: 0.3rem; flex-wrap: nowrap; }
  .plane-card-stats span { font-size: 0.6rem; min-width: 0; overflow: hidden; }
  .plane-card-stats b { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .plane-card-stats b { font-size: 0.72rem; }

  /* Homepage "Featured Aircraft": the cap to 6 planes is already set at the
     tablet tier above (721-1080px query) and applies here too - mobile
     only needs the single-column layout from the shared .grid-4 rule
     further up, matching the compact "5-6 aircraft at a glance" feel the
     .plane-card comment above already aims for. */

  /* Homepage "Latest News & Articles": the tablet tier above already
     converts this to a compact horizontal row; phones just need it
     shrunk further still - a smaller square icon tile and tighter type so
     four of these don't eat the whole screen on a narrow phone. */
  .news-card { display: flex; flex-direction: row; align-items: stretch; }
  .news-card-media { aspect-ratio: auto; width: 64px; height: 64px; flex-shrink: 0; border-right: 1px solid var(--border-0); }
  .news-card-media svg { width: 55%; }
  .news-card-body { padding: 0.5rem 0.7rem; display: flex; flex-direction: column; justify-content: center; gap: 0.15rem; min-width: 0; }
  .news-card-body h4 { font-size: 0.78rem; line-height: 1.25; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
  .news-meta { margin-bottom: 0; font-size: 0.62rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

  /* --- Database toolbar: compact two-row layout ---
         row 1: full-width search / row 2: sort + compare + reset, all
         shrunk down to small icon-forward controls so the row reads as
         a toolbar strip instead of three full-size buttons. Select text
         stays at the 16px iOS-zoom floor set earlier; everything around
         it (padding, height, icon size) is what shrinks. --- */
  .db-toolbar {
    /* Search spans the top row; per-page / sort / Compare / Reset sit in a
       tidy 2x2 beneath it. */
    grid-template-columns: 1fr 1fr;
    gap: 0.4rem;
    padding: 0.5rem;
    margin-bottom: 0.75rem;
  }
  .db-toolbar .search-box { grid-column: 1 / -1; padding-top: 0.1rem; padding-bottom: 0.1rem; }
  .db-toolbar .search-box input { padding: 0.4rem 0; }
  .db-toolbar .select { min-width: 0; padding: 0.4rem 0.45rem; }
  .db-toolbar .btn {
    padding: 0.4rem 0.55rem; font-size: 0.68rem; gap: 0.3rem; min-height: 38px;
    justify-content: center;
  }
  .db-toolbar .btn svg { width: 14px; height: 14px; }
  .breadcrumb { padding: 0.7rem 0; font-size: 0.72rem; }

  /* Category chips scroll horizontally as before, but "More Filters" now
     lives outside that scroller (see .filter-row-scroll below) so it's
     always visible at the row's right edge without having to scroll the
     categories out of the way to reach it. */
  .filter-row { flex-wrap: nowrap; gap: 0.4rem; margin-bottom: 0.9rem; }
  .filter-row-scroll {
    display: flex; gap: 0.5rem; flex: 1; min-width: 0;
    overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
    padding-bottom: 0.4rem;
  }
  .filter-row-scroll::-webkit-scrollbar { display: none; }
  .chip { white-space: nowrap; flex-shrink: 0; padding: 0.38rem 0.65rem; font-size: 0.7rem; }
  .chip-more {
    flex-shrink: 0; margin-left: 0; padding: 0.38rem 0.6rem; font-size: 0.7rem;
  }
  /* "More Filters" -> just "Filters" at this width so the pinned button
     stays as compact as the scrolling chips beside it. */
  .chip-more-label { font-size: 0; }
  .chip-more-label::before { content: "Filters"; font-size: 0.7rem; }

  /* Records page category toggle (see .record-category-toggle above) uses
     the same .filter-row/.filter-row-scroll scrolling strip, but its own
     ".record-category-toggle .chip" rule is more specific than the plain
     ".chip" shrink rule just above, so it needs its own mobile override
     here to actually shrink instead of silently winning the cascade. */
  .record-category-toggle .chip { padding: 0.38rem 0.65rem; font-size: 0.7rem; }
  .record-category-toggle .chip svg { width: 12px; height: 12px; }

  /* Advanced filters stack to one column and each select gets a
     16px+ font floor so iOS Safari doesn't auto-zoom on focus. */
  .adv-filters { grid-template-columns: 1fr; padding: 0.85rem; gap: 0.8rem; }
  .adv-filter-item select { font-size: 16px; }
  .adv-filter-clear { width: 100%; justify-content: center; }

  .cat-badge { width: 20px; height: 20px; }
  .cat-badge svg { width: 11px; height: 11px; }

  /* --- Compare tray floats above the tab bar --- */
  .compare-bar {
    bottom: calc(58px + env(safe-area-inset-bottom));
    padding: 0.7rem 1rem;
    flex-wrap: wrap;
    border-bottom: 1px solid var(--border-1);
  }
  .compare-bar .btn { min-height: 40px; }

  /* --- Compare page: compact picker + dense table that uses the
         full screen width instead of forcing a horizontal scroll --- */
  .compare-picker { display: grid; grid-template-columns: 1fr 1fr; gap: 0.55rem; margin-bottom: 1rem; }
  .compare-card-slot { width: auto; min-width: 0; min-height: 0; padding: 0.55rem; gap: 0.35rem; font-size: 0.72rem; }
  .compare-card-slot .compare-slot-add,
  .compare-card-slot .compare-slot-search { width: 100%; }
  .compare-slot-results { position: static; width: 100%; left: auto; transform: none; margin-top: 0.4rem; max-height: 220px; }
  table.compare-table { min-width: 0; width: 100%; }
  table.compare-table th, table.compare-table td {
    padding: 0.45rem 0.5rem;
    font-size: 0.74rem;
    line-height: 1.35;
    overflow-wrap: anywhere;
  }
  table.compare-table thead th { font-size: 0.6rem; }
  table.compare-table thead th.compare-th-plane { padding: 0.5rem 0.4rem; }
  .compare-th-media { width: 96px; height: 64px; margin-bottom: 0.3rem; }
  .compare-th-name { font-size: 0.66rem; }
  table.compare-table tbody th {
    font-size: 0.58rem; white-space: normal;
    width: 70px; min-width: 70px;
    position: sticky; left: 0; z-index: 2;
    box-shadow: 1px 0 0 var(--border-0);
  }
  table.compare-table thead th:first-child {
    position: sticky; left: 0; z-index: 2;
    box-shadow: 1px 0 0 var(--border-0);
  }
  .compare-table-wrap { -webkit-overflow-scrolling: touch; }

  /* --- Detail pages --- */
  /* Collapse the full "Home › Role › Name" trail to a single "‹ Role"
     back-link: the trail was eating a whole line (often two, once the
     aircraft name wrapped) before the title. Home stays one tap away via
     the topbar brand, and the name is repeated in the h1 directly below. */
  .breadcrumb-detail a[href="/"], .breadcrumb-detail .sep,
  .breadcrumb-detail > span:not(.sep) { display: none; }
  .breadcrumb-detail a:not([href="/"])::before { content: "\2039\00a0"; }
  /* Phone: show the STATUS pill only. The role pill repeats the collapsed
     breadcrumb immediately above it word for word (that breadcrumb is
     reduced to "\2039 <role>" a few rules up), and at this width a role like
     "Multirole / Swing-Role Combat Aircraft" spends two whole lines saying
     it a second time. Dropping it lifts everything below it up the page. */
  .badge-split:not(.badge-status) { display: none; }
  /* The tab strip is directly below the spec tiles on a phone - the "See
     Specifications for full detail" hint just spends a line pointing at it. */
  .spec-more-hint { display: none; }
  /* Sources rows: name-left / URL-right doesn't survive a 360px viewport -
     stack each row (name above, link below) like the spec table does,
     instead of squeezing both into one cramped line. */
  .kv-row { flex-direction: column; gap: 0.15rem; }
  .kv-row .v { text-align: left; }
  .detail-hero-media { padding: 1.5rem 1rem; min-height: 190px; }
  .detail-hero-photo { max-height: 320px; }
  /* The full-width Add to Compare button (+ the now-removed star button)
     used to live here, stretched across the whole row - oversized next to
     everything else on the page. Replaced on mobile by the compact button
     next to the "Last updated" pill (see .updated-row /
     .detail-actions-mobile above); this desktop block is hidden entirely
     at this width instead. */
  .detail-actions { display: none; }
  .detail-actions-mobile { display: inline-flex; }
  /* Buttons ride on the same line as the "Last updated" pill, pushed to the
     right edge, instead of wrapping onto a line of their own. margin-left
     rather than justify-content so the row still behaves if it ever gains a
     third item. Paired with the shorter "Updated 31 Jul 2026" label below,
     which is what makes all three fit across a 390px screen. */
  .updated-row { flex-wrap: nowrap; }
  .updated-row-actions { display: flex; margin-left: auto; flex: none; }
  .updated-flag { min-width: 0; }
  .updated-date-full { display: none; }
  .updated-date-short { display: inline; }
  .tabs { -webkit-overflow-scrolling: touch; scrollbar-width: none; }
  .tabs::-webkit-scrollbar { display: none; }
  /* Wider, stronger fade on mobile since the scrollable tab strip is
     otherwise the easiest "hey, there's more here" cue to miss on a phone. */
  .tabs-wrap::before, .tabs-wrap::after { width: 40px; }
  .spec-grid { grid-template-columns: repeat(2, 1fr); }
  /* A real <table> stops working once the label column and value column
     both need real width on a ~340px screen - the label wraps or the value
     gets squeezed to nothing. Below this width each row becomes its own
     block (label above, value below) instead, keeping the colour-coded
     left rail and category grouping intact. */
  .spec-table, .spec-table tbody { display: block; width: 100%; }
  .spec-table tr.spec-row { display: block; padding: 0.55rem 0.7rem; }
  .spec-table tr.spec-cat-row { display: block; }
  .spec-table th, .spec-table td { display: block; padding: 0; width: auto; white-space: normal; }
  .spec-table th { margin-bottom: 0.3rem; }
  /* Category banner (v5) loses its table-cell padding/flex above along
     with every other th/td, same as the rest of this block - restored
     here as block-level padding on the row itself so the colour wash +
     icon + label still reads as a clear section banner at phone width,
     not a bare line of text glued to the row below it. */
  .spec-table tr.spec-cat-row { padding: 1rem 0.7rem 0.55rem; }
  /* height:auto overrides the desktop fixed-height row - at phone width a
     long label can still wrap to two lines (see the .spec-table th/td
     width:auto rule above), and the desktop's single-line fixed height
     would otherwise clip it. .spec-cat-inner switches from inline-flex to
     a wrapping flex row spanning the full width, so the label wraps
     under/after the icon instead of overflowing (inline-flex items don't
     wrap by default). */
  .spec-table tr.spec-cat-row td { padding: 0; height: auto; }
  .spec-cat-inner { display: flex; flex-wrap: wrap; align-items: center; width: 100%; height: auto; }
  /* Same fixed-column-stops-working problem as .spec-table above: a
     240px code column plus a real description on a ~340px screen leaves
     no room for either. Collapse to the same label-above-value block
     pattern instead. */
  .variant-table, .variant-table tbody { display: block; width: 100%; }
  .variant-table thead { display: none; }
  .variant-table tr.vt-row { display: block; padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--border-0); }
  .variant-table tr.variant-detail-row { display: block; border-bottom: 1px solid var(--border-0); }
  .variant-table tr:last-child { border-bottom: none; }
  .variant-table th, .variant-table td { display: block; padding: 0; width: auto; white-space: normal; }
  .variant-table td.v-code { margin-bottom: 0.3rem; }
  .variant-table tr.variant-detail-row td { padding: 0.55rem 0.7rem; }
  .spec-table tr.spec-cat-row:first-child { padding-top: 0.4rem; }
  .photo-gallery-grid { grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
  .related-strip { grid-template-columns: 1fr; }
  /* Timeline: the interactive scale is a desktop/tablet-only feature - its
     year-proportional dot spacing crowds together too much at phone width
     to tap reliably, and the vertical list below already tells the full
     story on its own, so mobile just hides the scale rather than trying to
     replace it with something smaller. */
  .timeline-scale { display: none; }

  /* --- Footer --- */
  .footer-grid { grid-template-columns: repeat(2, 1fr); gap: 1.4rem; }
  .footer-bottom { flex-direction: column; gap: 0.4rem; text-align: center; }
}

/* Small phones (iPhone mini / SE class) */
@media (max-width: 390px) {
  .hero h1 { font-size: 1.3rem; }
  .hero.hero-count h1 span.hc-num { font-size: 3rem; }
  .hero.hero-flap h1 span.sf-row { --sf-fs: 2.6rem; }
  .hero.hero-flap .sf::before { display: none; }  /* pins read as a smudge this small */
  .hero.hero-count .hc-tail { font-size: 0.95rem; }
  .hero.hero-count .scope { width: 108px; max-width: 108px; }
  .hero-inner { grid-template-columns: 1fr 108px; }
  .scope { width: 108px; max-width: 108px; }
  .cat-grid { grid-auto-flow: row; grid-auto-columns: auto; grid-template-columns: repeat(2, 1fr); }
  .tabbar-label { font-size: 0.55rem; }
}

/* ============ Long-form article system (Overview, Wikipedia-depth) ============
   Used when an aircraft's JSON carries an "article" object. Keeps the same
   .card shell as the rest of the site but adds: a mono read-time/stats
   strip, a sticky-feeling table of contents, numbered section headings and
   inline citation superscripts - so a much longer read still feels like it
   belongs on an ATC console, not a Wikipedia mirror. The article body is
   deliberately prose only: headings and paragraphs, no boxed callouts or
   pull-quotes. Both existed until September 2026 and were removed. */
.article-card { padding-top: 1.1rem; }

.article-meta { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; font-size: 0.7rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-3); padding-bottom: 0.9rem; border-bottom: 1px dashed var(--border-0); margin-bottom: 1.1rem; }

.article-lede p { font-size: 1.02rem; line-height: 1.75; color: var(--text-0); }
.article-lede p + p { margin-top: 0.9rem; }

.article-section { margin-top: 1.9rem; padding-top: 1.6rem; border-top: 1px solid var(--border-0); scroll-margin-top: 90px; }
.article-section:first-of-type { margin-top: 1.6rem; }
/* Plain block heading. Was display:flex with a gap to seat a two-digit
   index badge alongside the text; the badge and the layout that used it
   were removed in September 2026 (see articleSectionHeading in detail.js). */
.article-section h3.article-section-heading {
  display: block; font-size: 1.08rem; font-weight: 700; color: var(--text-0); margin-bottom: 0.9rem;
}
.article-section p { font-size: 0.94rem; }
/* Overview articles are prose only. A boxed callout and a pull-quote used
   to be renderable here from optional per-section keys; both were removed
   in September 2026 along with their CSS (see the note above articleReadMeta
   in detail.js). The zero-specificity backstop below stays regardless: the
   global `svg` rule near the top of this file sets max-width:100% and
   height:auto but no width, so any icon rendered into article prose without
   a component rule of its own would fill the column. That is exactly what
   happened the one time a callout shipped. Article prose is authored from
   JSON, so this guarantees a future field can never repeat it. */
:where(.article-section, .article-lede) :where(svg:not([width])) { width: 14px; height: 14px; }


/* Citation superscripts - deliberately understated (not a bright badge)
   since they appear many times per paragraph; colour + underline on hover
   is enough of a click affordance without turning prose into a rash of
   green numbers. */
.cite { font-size: 0.7em; margin-left: 1px; }
.cite a { color: var(--cyan); text-decoration: none; padding: 0 1px; }
.cite a:hover { text-decoration: underline; color: var(--radar); }

/* ============ References list (Sources tab, article-driven) ============ */
.ref-count { margin-left: 0.5rem; color: var(--text-3); font-weight: 400; text-transform: none; letter-spacing: 0; }
.ref-list { list-style: none; margin: 0; padding: 0; counter-reset: none; }
.ref-item { display: flex; align-items: baseline; gap: 0.7rem; padding: 0.55rem 0.3rem; border-bottom: 1px solid var(--border-0); border-radius: var(--radius-sm); transition: background-color 0.3s ease; }
.ref-item:last-child { border-bottom: none; }
.ref-num { color: var(--cyan); font-size: 0.72rem; flex-shrink: 0; width: 2.6rem; }
.ref-body { flex: 1; font-size: 0.9rem; color: var(--text-1); display: flex; flex-direction: column; gap: 0.1rem; }
.ref-body a { color: var(--text-0); font-weight: 600; }
.ref-body a:hover { color: var(--radar); }
.ref-publisher { font-size: 0.72rem; color: var(--text-3); }
.ref-uses { font-size: 0.72rem; color: var(--text-3); flex-shrink: 0; }
.ref-item.ref-flash { background-color: rgba(77, 255, 176, 0.14); }

/* ============ "Where to See One" sidebar box ============
   Same list rhythm as the homepage's .airshow-list (icon/serial + linked
   name + mono meta + short note), just swapped to sit in the detail
   page's right-hand column - a serial number takes the flag-icon's slot
   since there's no flag to show here. */
.survivors-list { list-style: none; margin: 0.2rem 0 0; padding: 0; }
.survivors-list li { display: flex; gap: 0.7rem; align-items: flex-start; padding: 0.6rem 0; border-bottom: 1px dashed var(--border-0); }
.survivors-list li:last-child { border-bottom: none; }
.survivors-flag { flex: 0 0 auto; margin-top: 0.2rem; }
.survivors-body { display: flex; flex-direction: column; gap: 0.12rem; min-width: 0; }
.survivors-name { font-size: 0.86rem; font-weight: 600; color: var(--text-0); text-decoration: none; }
.survivors-name:hover { color: var(--radar); text-decoration: underline; }
.survivors-meta { font-size: 0.68rem; color: var(--amber); letter-spacing: 0.02em; }
.survivors-note { font-size: 0.76rem; color: var(--text-2); }

@media (max-width: 900px) {
}
@media (max-width: 720px) {
  .article-meta { font-size: 0.64rem; gap: 0.3rem 0.7rem; }
  .ref-item { flex-wrap: wrap; }
}

/* =====================================================================
   Live News page (/news/) - featured story + trending rail + story grid.
   Article images are hot-linked from the publisher and sit on top of the
   hand-drawn newspaper placeholder inside .news-media; if the publisher
   blocks hot-linking the <img> removes itself and the placeholder shows.
   ===================================================================== */
.news-top-grid { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 1.1rem; margin-bottom: 2rem; align-items: start; }
.news-media { position: relative; overflow: hidden; background: linear-gradient(160deg, var(--bg-4), var(--bg-2)); display: flex; align-items: center; justify-content: center; }
.news-media > svg { width: 18%; max-width: 64px; opacity: 0.35; }
.news-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.news-featured { background: var(--bg-2); border: 1px solid var(--border-0); border-radius: var(--radius-md); overflow: hidden; }
.news-featured-media { aspect-ratio: 16 / 9; }
.news-featured-body { padding: 1.15rem 1.3rem 1.35rem; }
.news-featured-body h2 { font-size: 1.3rem; line-height: 1.3; margin: 0.55rem 0 0.6rem; }
.news-featured-body h2 a { color: var(--text-0); text-decoration: none; }
.news-featured-body h2 a:hover { color: var(--radar); text-decoration: underline; }
.news-kicker { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--amber); display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.news-kicker svg { width: 13px; height: 13px; }
.news-kicker-group { color: var(--text-3); }
.news-kicker-group::before { content: "// "; color: var(--border-1); }
.news-excerpt { color: var(--text-2); font-size: 0.88rem; line-height: 1.6; margin-bottom: 0.75rem; }
.news-byline { font-family: var(--font-mono); font-size: 0.67rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-3); display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; }
.news-byline a { color: var(--amber); text-decoration: none; }
.news-byline a:hover { text-decoration: underline; }
.news-byline .sep { color: var(--border-1); }
.news-trending { background: var(--bg-2); border: 1px solid var(--border-0); border-radius: var(--radius-md); padding: 1.05rem 1.15rem 0.6rem; }
.news-trending ol { list-style: none; }
.trending-item { display: flex; align-items: flex-start; gap: 0.6rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border-0); }
.trending-item:last-child { border-bottom: none; }
.trending-rank { color: var(--radar); font-size: 0.78rem; padding-top: 0.15rem; min-width: 1.3rem; }
.trending-thumb { width: 68px; height: 46px; flex: none; border-radius: 4px; }
.trending-thumb > svg { width: 40%; }
.trending-item h4 { font-size: 0.78rem; line-height: 1.4; font-weight: 600; margin-bottom: 0.2rem; }
.trending-item h4 a { color: var(--text-0); text-decoration: none; }
.trending-item h4 a:hover { color: var(--radar); text-decoration: underline; }
.trending-item .news-meta { margin-bottom: 0; font-size: 0.62rem; }
.news-filters { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 1.2rem; }
.news-list-grid .news-story-card { display: flex; flex-direction: column; }
.news-story-card .news-card-media { aspect-ratio: 16 / 9; }
.news-story-card .news-card-body { display: flex; flex-direction: column; gap: 0.45rem; flex: 1; }
.news-story-card .news-card-body h4 a { color: var(--text-0); text-decoration: none; }
.news-story-card .news-card-body h4 a:hover { color: var(--radar); text-decoration: underline; }
.news-story-card .news-excerpt { font-size: 0.8rem; margin-bottom: 0; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.news-story-card .news-byline { margin-top: auto; padding-top: 0.3rem; }
/* On tablet/phone widths the generic .news-card rule (1080px tier above)
   switches news cards to a compact thumbnail-left horizontal row - but the
   .news-list-grid .news-story-card rule above (written for desktop's
   3-column grid of banner-on-top cards) out-specifies it and forces these
   back to a column, while the 96px media width from the same tier still
   applied: 96px thumb top-left, text underneath, dead space beside the
   image. Re-assert the horizontal row for story cards at the same
   breakpoint; the media stretches to the row's full height (its img is
   absolutely positioned + object-fit:cover, so it just fills). */
@media (max-width: 1080px) {
  .news-list-grid .news-story-card { flex-direction: row; align-items: stretch; }
  .news-story-card .news-card-media { aspect-ratio: auto; width: 96px; flex-shrink: 0; }
  .news-story-card .news-card-body { justify-content: center; }
}
.news-aircraft-chips { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-top: 0.6rem; }
.news-aircraft-chips a { display: inline-flex; align-items: center; gap: 0.3rem; font-family: var(--font-mono); font-size: 0.65rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--radar); border: 1px solid var(--radar-dim); border-radius: 999px; padding: 0.18rem 0.6rem; text-decoration: none; }
.news-aircraft-chips a:hover { background: rgba(77, 255, 176, 0.08); box-shadow: 0 0 10px var(--border-glow); }
.news-aircraft-chips a svg { width: 12px; height: 12px; }
.news-attribution { font-size: 0.68rem; color: var(--text-3); letter-spacing: 0.03em; margin-top: 1.6rem; max-width: 78ch; }
@media (max-width: 900px) {
  .news-top-grid { grid-template-columns: 1fr; }
  .news-list-grid { grid-template-columns: 1fr; }
}

/* Live (auto-fetched) stories on aircraft News tabs - radar-green accent to
   match the rest of the "live data" language on the site. */
.news-cat-live { border-left-color: var(--radar); }
.news-cat-live .news-cat-badge, .news-cat-live .featured { color: var(--radar); }

/* "PlanesDB file photo" tag - shown when a story with no usable article
   image borrows the matched aircraft's own database photo instead. */
.file-photo-tag { position: absolute; bottom: 6px; right: 6px; z-index: 1; font-size: 0.55rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-2); background: rgba(6, 10, 16, 0.75); border: 1px solid var(--border-0); border-radius: 4px; padding: 0.1rem 0.4rem; }
.trending-thumb .file-photo-tag { display: none; }
.news-continue { margin-bottom: 0.8rem; }
.news-continue a { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--radar); text-decoration: none; }
.news-continue a:hover { text-decoration: underline; }

/* =====================================================================
   "Aircraft" nav dropdown - the label links to the database, the caret
   opens the Explore sub-pages (Records, Aircraft of the Week).
   Opens on hover/focus-within (desktop) or via the caret button (touch/
   keyboard - toggled by initNavDropdown in main.js).
   ===================================================================== */
.nav-item { position: relative; display: inline-flex; align-items: center; gap: 0.1rem; }
.nav-caret { background: none; border: none; padding: 0.2rem 0.1rem; cursor: pointer; color: var(--text-2); display: inline-flex; align-items: center; }
.nav-caret svg { width: 12px; height: 12px; transition: transform 0.15s ease; }
.nav-item:hover .nav-caret svg, .nav-item.open .nav-caret svg { transform: rotate(180deg); color: var(--radar); }
.nav-dropdown { position: absolute; top: calc(100% + 8px); left: 50%; transform: translateX(-50%); min-width: 280px; background: var(--bg-2); border: 1px solid var(--border-0); border-radius: var(--radius-md); padding: 0.45rem; display: none; z-index: 80; box-shadow: 0 10px 34px rgba(0, 0, 0, 0.5); }
.nav-dropdown::before { content: ""; position: absolute; top: -9px; left: 0; right: 0; height: 10px; } /* hover bridge so the menu doesn't close crossing the gap */
.nav-item:hover .nav-dropdown, .nav-item:focus-within .nav-dropdown, .nav-item.open .nav-dropdown { display: block; }
.nav-dropdown a { display: flex; align-items: flex-start; gap: 0.6rem; padding: 0.55rem 0.65rem; border-radius: var(--radius-sm); border: none; }
.nav-dropdown a:hover, .nav-dropdown a[aria-current="page"] { background: var(--bg-3); }
.nav-dd-icon { flex: none; display: inline-flex; padding-top: 0.15rem; color: var(--radar); }
.nav-dd-icon svg { width: 15px; height: 15px; }
.nav-dd-text { display: flex; flex-direction: column; gap: 0.1rem; }
.nav-dd-text b { font-size: 0.82rem; color: var(--text-0); font-weight: 600; }
.nav-dd-desc { font-size: 0.7rem; color: var(--text-3); }
.nav-dd-soon { font-size: 0.55rem; letter-spacing: 0.08em; color: var(--amber); border: 1px solid var(--amber); border-radius: 3px; padding: 0 0.25rem; vertical-align: 1px; }

/* =====================================================================
   Records & Leaderboards page (/records/). Each board is a Top 10 with a
   value bar behind every row, scaled relative to the record holder.
   ===================================================================== */
/* .record-category-toggle rides on the .filter-row / .filter-row-scroll
   classes (same markup pattern as the /aircraft/ database's category
   chips - see the @media (max-width: 720px) block below) so mobile gets
   the same horizontally-swipeable, hidden-scrollbar chip strip for free
   instead of nine chips wrapping across several lines. This block only
   adds the extra sizing this page's chips need on top of that shared
   layout. It also carries the .tabs-wrap class (see the "Tabs" section
   above) so it pins under the topbar on scroll and gets the same
   scroll-fade chevrons as the aircraft detail page's tab strip - .tabs-wrap
   assumes it's sitting right under a hero/header block though, so its
   default 1.6rem top margin is overridden back to 0 here since this page
   has its own intro paragraph directly above instead.

   Pinning: uses the shared .tabs-wrap `top: var(--topbar-h)` (the real
   measured header height - see initTopbarHeightVar in main.js) so the bar
   sits flush under the topbar with NO offset gap. This used to be a
   hand-tuned `top: 76px` - 12px of "breathing room" below the 64px desktop
   header - but that buffer was empty space OUTSIDE the bar's background,
   so page text scrolled visibly through the slot, and the hardcoded value
   drifted the moment the mobile header grew to two lines. The breathing
   room lives INSIDE the bar as padding-top now, where the bar's own
   background paints over anything scrolling beneath it. */
.record-category-toggle.tabs-wrap { margin-top: 0; margin-bottom: 1.4rem; padding-top: 0.55rem; }
.record-category-toggle .chip { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.78rem; padding: 0.45rem 0.95rem; }
.record-category-toggle .chip svg { width: 14px; height: 14px; flex: none; }
.records-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.1rem; align-items: start; }
.records-grid .card + .card { margin-top: 0; }
.record-board { scroll-margin-top: 90px; }
.record-desc { font-size: 0.78rem; color: var(--text-3); margin: -0.4rem 0 0.8rem; }
.record-list { list-style: none; margin: 0; padding: 0; }
.record-row a { position: relative; display: flex; align-items: center; gap: 0.65rem; padding: 0.5rem 0.6rem; border-radius: var(--radius-sm); overflow: hidden; text-decoration: none; }
.record-row + .record-row { margin-top: 0.25rem; }
.record-row a:hover { outline: 1px solid var(--radar-dim); }
.record-bar { position: absolute; inset: 0 auto 0 0; background: linear-gradient(90deg, rgba(77, 255, 176, 0.10), rgba(77, 255, 176, 0.03)); border-right: 1px solid rgba(77, 255, 176, 0.25); pointer-events: none; }
.record-rank { flex: none; min-width: 1.6rem; font-size: 0.78rem; color: var(--text-3); position: relative; }
.record-rank svg { width: 15px; height: 15px; color: var(--amber); }
.record-leader-media { flex: none; width: 64px; height: 42px; border-radius: 4px; overflow: hidden; background: linear-gradient(160deg, var(--bg-4), var(--bg-2)); display: flex; align-items: center; justify-content: center; position: relative; }
.record-leader-media img { width: 100%; height: 100%; object-fit: cover; }
.record-leader-media svg { width: 70%; opacity: 0.7; }
.record-name { position: relative; display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; min-width: 0; }
.record-name b { font-size: 0.82rem; color: var(--text-0); font-weight: 600; }
.record-name .flag-icon { flex: none; display: inline-flex; }
.record-mfr { font-size: 0.68rem; color: var(--text-3); }
.record-value { position: relative; margin-left: auto; flex: none; font-size: 0.8rem; color: var(--radar); }
.record-leader a { padding: 0.65rem 0.6rem; }
.record-leader .record-name b { font-size: 0.95rem; }
.record-leader .record-value { color: var(--amber); font-weight: 700; font-size: 0.9rem; }
.record-leader .record-bar { background: linear-gradient(90deg, rgba(245, 185, 66, 0.12), rgba(245, 185, 66, 0.03)); border-right-color: rgba(245, 185, 66, 0.4); }
.record-podium .record-rank { color: var(--radar); font-weight: 700; }
@media (max-width: 860px) {
  .records-grid { grid-template-columns: 1fr; }
  .record-mfr { display: none; }
}

/* =====================================================================
   404 "lost from radar" page - reuses the homepage .scope styling with a
   centred layout, a blinking UNKNOWN blip and the big 404 readout.
   ===================================================================== */
.nf-wrap { text-align: center; padding-top: 2.5rem; padding-bottom: 4rem; }
.nf-scope { max-width: 300px; margin: 0 auto 1.6rem; justify-self: auto; }
.nf-blip { top: 30%; left: 63%; opacity: 0.5; animation: nf-ping 4.5s linear infinite; }
/* The lost contact snaps to full brightness the instant the sweep's
   leading line crosses its bearing (~33deg from north; the line rides
   90deg ahead of the gradient rotation, so it arrives at rotation 303deg
   = ~84% of the 4.5s cycle), then decays slowly - wrapping through 0% so
   the fade continues smoothly into the next revolution. Both animations
   are linear/infinite and start together, keeping them in phase. */
@keyframes nf-ping {
  0% { opacity: 0.85; }
  30% { opacity: 0.55; }
  60% { opacity: 0.30; }
  82% { opacity: 0.16; }
  84% { opacity: 0.14; }
  85% { opacity: 1; }
  100% { opacity: 0.85; }
}
.nf-blip .blip-label { transform: translateY(-120%) translateX(-30%); }
.nf-code { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 3.4rem; font-weight: 700; color: var(--radar); text-shadow: 0 0 24px var(--radar-glow); letter-spacing: 0.1em; }
.nf-squawk { color: var(--amber); font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase; margin-bottom: 0.6rem; }
.nf-title { font-size: 1.5rem; margin-bottom: 0.6rem; }
.nf-copy { color: var(--text-1); max-width: 52ch; margin: 0 auto 1.4rem; }
.nf-search { max-width: 440px; margin: 0 auto 1.4rem; position: relative; text-align: left; }
.nf-actions { display: flex; gap: 0.7rem; justify-content: center; flex-wrap: wrap; }
.nf-actions .btn svg { width: 14px; height: 14px; }

/* =====================================================================
   A-Z index + glossary shared styles (letter strip, sections, rows).
   ===================================================================== */
.az-strip { display: flex; gap: 0.3rem; flex-wrap: wrap; margin-bottom: 1.8rem; }
.az-letter { display: inline-flex; align-items: center; justify-content: center; min-width: 1.9rem; height: 1.9rem; border: 1px solid var(--border-0); border-radius: var(--radius-sm); font-size: 0.78rem; color: var(--radar); text-decoration: none; }
.az-letter:hover { border-color: var(--radar); box-shadow: 0 0 10px var(--border-glow); }
.az-letter-empty { color: var(--text-3); opacity: 0.35; }
.az-section { margin-bottom: 1.8rem; scroll-margin-top: 90px; }
.az-heading { font-size: 1rem; color: var(--radar); border-bottom: 1px solid var(--border-0); padding-bottom: 0.4rem; margin-bottom: 0.8rem; display: flex; align-items: center; gap: 0.5rem; }
.az-heading svg { width: 15px; height: 15px; }
.az-count { font-family: var(--font-mono); font-size: 0.68rem; color: var(--text-3); font-weight: 400; }
.az-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 0.25rem 1rem; }
.az-row a { display: flex; align-items: center; gap: 0.5rem; padding: 0.35rem 0.4rem; border-radius: var(--radius-sm); text-decoration: none; }
.az-row a:hover { background: var(--bg-3); }
.az-row b { font-size: 0.85rem; color: var(--text-0); font-weight: 600; white-space: nowrap; }
.az-row .flag-icon { flex: none; display: inline-flex; }
.az-sub { font-size: 0.68rem; color: var(--text-3); font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Glossary definition list */
.glossary-list { margin: 0; }
.glossary-item { padding: 0.65rem 0.7rem; border-radius: var(--radius-sm); scroll-margin-top: 90px; }
.glossary-item:nth-child(odd) { background: var(--bg-2); }
.glossary-item:target { outline: 1px solid var(--radar-dim); background: rgba(77, 255, 176, 0.05); }
.glossary-item dt { font-weight: 700; color: var(--text-0); font-size: 0.9rem; margin-bottom: 0.2rem; }
.glossary-item dd { margin: 0; color: var(--text-1); font-size: 0.85rem; line-height: 1.6; }

/* Inline glossary tooltips on spec-table labels */
.term { position: relative; border-bottom: 1px dotted var(--amber); cursor: help; }
.term-tip { display: none; position: absolute; bottom: calc(100% + 8px); left: 0; z-index: 60; width: 280px; max-width: 74vw; background: var(--bg-3); border: 1px solid var(--border-1); border-radius: var(--radius-sm); box-shadow: 0 8px 26px rgba(0, 0, 0, 0.5); padding: 0.6rem 0.7rem; font-size: 0.74rem; line-height: 1.55; color: var(--text-1); font-weight: 400; font-family: var(--font-sans, inherit); text-transform: none; letter-spacing: normal; white-space: normal; }
.term-tip b { color: var(--radar); }
.term-tip a { color: var(--amber); font-family: var(--font-mono); font-size: 0.68rem; text-decoration: none; white-space: nowrap; }
.term-tip a:hover { text-decoration: underline; }
.term:hover .term-tip, .term:focus .term-tip, .term:focus-within .term-tip { display: block; }

/* Entry changelog card (detail pages, bottom of Overview tab) */
.changelog-list { list-style: none; margin: 0; padding: 0; }
.changelog-list li { display: flex; align-items: flex-start; gap: 0.8rem; padding: 0.5rem 0; border-bottom: 1px solid var(--border-0); }
.changelog-list li:last-child { border-bottom: none; }
.cl-date { flex: none; font-size: 0.72rem; color: var(--text-3); padding-top: 0.1rem; }
.cl-text { font-size: 0.84rem; color: var(--text-1); line-height: 1.55; }
.cl-author { font-size: 0.78rem; color: var(--text-3); font-weight: 600; text-decoration: none; white-space: nowrap; }
.cl-author:hover { color: var(--radar); text-decoration: underline; }

/* =====================================================================
   Sticky footer: on short pages (404, stubs, glossary letters...) the
   footer used to ride up mid-viewport. Flex-column body + margin-top:auto
   pins it to the bottom no matter how little content a page has.
   ===================================================================== */
/* body already has min-height:100vh (base rule); a sticky footer with
   top:100vh gets pushed down to the body's bottom edge on short pages and
   behaves as plain flow content on long ones - no body layout change. */
.site-footer { position: sticky; top: 100vh; }

/* Listing-tile photo is a link to the entry too; compare button + badges
   sit above it so they stay clickable. */
.plane-card-media-link { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.plane-card-media .compare-select, .plane-card-media .plane-card-badge, .plane-card-media .plane-card-cats { z-index: 2; }

/* Database pagination (page tabs + infinite-scroll status) */
.db-pagination { display: flex; align-items: center; justify-content: center; gap: 0.35rem; flex-wrap: wrap; margin-top: 1.6rem; }
.page-btn { min-width: 2.1rem; height: 2.1rem; padding: 0 0.5rem; font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-1); background: var(--bg-2); border: 1px solid var(--border-0); border-radius: var(--radius-sm); cursor: pointer; }
.page-btn:hover:not(:disabled) { color: var(--radar); border-color: var(--radar-dim); }
.page-btn:disabled { opacity: 0.35; cursor: default; }
.page-btn.page-current { color: var(--bg-0); background: var(--radar); border-color: var(--radar); font-weight: 700; }
.page-ellipsis { color: var(--text-3); padding: 0 0.2rem; }
.page-loading { font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-3); }

/* Mobile article accordion: on phones each numbered Overview section
   collapses behind its heading (wired up by initArticleAccordion in
   main.js). Desktop never gets .sec-collapsible, so nothing changes there. */
@media (max-width: 720px) {
  /* The Contents box duplicates the section headings that are already
     right there as tappable accordion rows - hidden on phones. */
  .article-section.sec-collapsible:not(.sec-open) > :not(h3) { display: none; }
  .article-section.sec-collapsible > h3 { cursor: pointer; user-select: none; display: flex; align-items: center; margin-bottom: 0; }
  .article-section.sec-collapsible.sec-open > h3 { margin-bottom: 0.7rem; }
  .article-section.sec-collapsible > h3::after {
    content: ""; margin-left: auto; flex: none;
    width: 9px; height: 9px; border-right: 2px solid var(--radar); border-bottom: 2px solid var(--radar);
    transform: rotate(45deg); transition: transform 0.15s ease;
  }
  .article-section.sec-collapsible.sec-open > h3::after { transform: rotate(225deg); }
}

/* =====================================================================
   Homepage radar blips ping with the sweep (same behaviour as the 404).
   Each blip snaps bright the instant the sweep's leading line crosses its
   bearing, then fades until the next revolution. The line rides 90deg
   ahead of the gradient rotation (see .scope-sweep), so for a blip at
   bearing B (clockwise from north, measured from the scope centre) the
   pass happens at t = ((B - 90 + 360) % 360) / 360 of the 4.5s cycle -
   implemented as a negative animation-delay of -(4.5s - t*4.5s) per blip.
   Bearings for the current blip positions:
     blip-1 (30%,22%) ~325deg -> pass at 65%  -> delay -1.57s
     blip-2 (68%,62%) ~124deg -> pass at 9.4% -> delay -4.08s
     blip-3 (78%,40%) ~70deg  -> pass at 94.5% -> delay -0.25s
     blip-4 (24%,74%) ~227deg -> pass at 38.1% -> delay -2.79s
   (If a blip is ever repositioned, recompute: B = atan2(dx, -dy).)
   Scoped to .hero so the 404's own nf-ping timing is untouched.
   ===================================================================== */
@keyframes scope-ping {
  0% { opacity: 1; }
  35% { opacity: 0.55; }
  75% { opacity: 0.25; }
  99% { opacity: 0.12; }
  100% { opacity: 1; }
}
.hero .blip { animation: scope-ping 4.5s linear infinite; }
.hero .blip-1 { animation-delay: -1.57s; }
.hero .blip-2 { animation-delay: -4.08s; }
.hero .blip-3 { animation-delay: -0.25s; }
.hero .blip-4 { animation-delay: -2.79s; }
/* Respect users who turn animations off - blips stay visible, sweep still
   rotates only if the browser allows it. */
@media (prefers-reduced-motion: reduce) {
  .hero .blip, .nf-blip { animation: none; opacity: 1; }
}

/* =====================================================================
   Aircraft of the Week (/aircraft-of-the-week/). The server ships only a
   loading skeleton (see .aotw-skeleton below) - src/assets/js/aotw.js
   swaps it for the real spotlight card once the search index loads and
   this week's pick is computed. See that file + the comment above
   aircraftOfTheWeek() in simple-pages.js for why this page works
   differently to the rest of the (server-rendered) site. (Originally a
   daily rotation, hence the class prefix's "aotd" history still visible
   in a few code comments elsewhere - renamed to weekly since the
   database is small enough that daily burns through every entry, and
   starts repeating, within a couple of months.)
   ===================================================================== */
.aotw-head { margin-bottom: 1.2rem; }
.aotw-meta-row { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.8rem; }
/* Amber reads as "time-limited", matching the amber accent used for ATC
   alerts/warnings elsewhere - fitting for a ticking countdown. (A
   date-range chip and a visit-streak counter used to share this row;
   both removed for now, see the header comment above.) */
.aotw-countdown {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.72rem; letter-spacing: 0.04em;
  padding: 0.4rem 0.75rem; border-radius: 999px; border: 1px solid var(--border-1);
  color: var(--amber); border-color: rgba(245,185,66,0.35); background: rgba(245,185,66,0.06);
}
.aotw-countdown svg { width: 14px; height: 14px; flex: none; }

/* ---------- Spotlight card ---------- */
/* Mobile-first base = the original stacked layout (contain-fit photo,
   bottom-left credit pill, split action rows) which already worked well
   on narrow screens. A min-width override further down swaps in the
   desktop-only hero-banner treatment - see that block for why. */
.aotw-spotlight { padding: 0; overflow: hidden; border-color: var(--radar-dim); }
.aotw-card-inner { display: block; }
.aotw-media { position: relative; border-radius: 0; border: none; border-bottom: 1px solid var(--border-0); min-height: 220px; }
.aotw-media svg { width: min(420px, 85%); }
/* Blurred, dimmed copy of the hero photo sitting behind the real one, so the
   fixed-shape banner is filled with the photo's own colours instead of a flat
   panel wherever the contain-fitted image does not reach. Scaled up past the
   box so the blur's soft edge falls outside it rather than showing as a pale
   rim. Hidden by default and switched on only in the desktop banner block
   below; aria-hidden + empty alt in aotw.js keep it out of the a11y tree. */
.aotw-media-backdrop {
  display: none;
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1.2);
  filter: blur(26px) brightness(0.5) saturate(1.15);
  pointer-events: none;
}
.aotw-body { padding: 1.6rem 1.7rem; display: flex; flex-direction: column; gap: 0.6rem; }
/* Plain inline text flow (not flex) - long role names (e.g. "Strategic/
   Tactical Airlift / Aerial Refueling") need to wrap like an ordinary
   sentence. A flex row here previously let its items shrink unevenly on
   narrow widths, stranding a bare "&middot;" alone on its own line and
   forcing the flag's raw <svg> into a fixed square that squashed its
   proportions - see the comment above this markup in renderSpotlight()
   (aotw.js) for the full story. */
.aotw-eyebrow {
  font-size: 0.68rem; letter-spacing: 0.08em; line-height: 1.6;
  text-transform: uppercase; color: var(--text-3);
}
/* Status pill inside the eyebrow (see statusLabel() in aotw.js) - a quiet
   radar-green dot rather than a loud badge, since it sits alongside the
   flag/role text rather than replacing them. Sized and nudged the same
   way .flag-icon is elsewhere on the site (vertical-align, not flex) so
   it sits on the text's optical middle rather than its line-box middle. */
.aotw-status-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--radar); box-shadow: 0 0 6px var(--radar-glow);
  vertical-align: middle; margin: 0 0.45em 0.15em 0;
}
.aotw-eyebrow .flag-icon { margin: 0 0.05rem; }
/* Red, not grey - matches the badge-red treatment every aircraft detail
   page already uses for a retired/cancelled status (see detail.js), which
   this dot previously didn't, making "Retired" read as a quiet, almost
   ignorable grey here while the same aircraft's own page flagged it red. */
.aotw-status-dot.is-retired { background: var(--red); box-shadow: 0 0 6px rgba(255,92,92,0.5); }
.aotw-status-dot.is-development { background: var(--cyan); box-shadow: 0 0 6px rgba(79,215,255,0.5); }
.aotw-status-dot.is-cancelled { background: var(--orange); box-shadow: 0 0 6px rgba(255,138,77,0.5); }
.aotw-name { font-size: 1.5rem; font-weight: 700; line-height: 1.15; }
.aotw-fullname { font-size: 0.85rem; color: var(--text-2); margin-top: -0.3rem; }
.aotw-tagline { color: var(--text-1); }
/* Six tiles (Max Speed, Range, Service Ceiling, Crew, First Flight, Unit
   Cost) read better as a tidy 3x2 grid than the base 4-column .spec-grid
   leaving an awkward 4+2 split. */
.aotw-spec-grid { margin-top: 0.4rem; grid-template-columns: repeat(3, 1fr); }
/* Quick Facts used to be its own boxed "card" (background fill + padding)
   sitting inside the page like a separate widget - deliberately de-boxed
   here so it reads as part of the same content flow as the spec tiles
   above it: no fill, no side rule, just a hairline top rule to separate
   it from the specs, plus its own top padding instead of all-round. */
.aotw-qf-card {
  margin-top: 0.9rem; padding: 1.1rem 0 0;
  background: none; border: none; border-top: 1px solid var(--border-0); border-radius: 0;
}
/* All actions (View Profile, Share, and both downloads) share the same
   plain .btn sizing - the downloads used to be smaller .btn-sm buttons in
   their own row, which read as inconsistent next to the others. */
.aotw-actions { display: flex; flex-wrap: wrap; gap: 0.7rem; margin-top: 0.4rem; }
.aotw-actions svg { width: 15px; height: 15px; }
/* Share + both PNG downloads grouped under their own small heading (same
   mono/uppercase treatment as the Quick Facts and spec-tile labels) rather
   than sitting flush against the primary View Profile button - they're a
   secondary, related cluster of actions, not one continuous row. */
.aotw-share-block { margin-top: 0.9rem; padding-top: 0.9rem; border-top: 1px solid var(--border-0); }
.aotw-share-title {
  display: flex; align-items: center; gap: 0.45rem;
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-2); margin: 0 0 0.7rem;
}
.aotw-share-title svg { width: 14px; height: 14px; flex: none; color: var(--radar); }
.aotw-error { text-align: center; color: var(--text-2); padding: 2rem 1rem; }

/* ---------- Skeleton / loading state ---------- */
.aotw-skeleton { display: block; min-height: 320px; }
.aotw-skel-media { border-bottom: 1px solid var(--border-0); min-height: 220px; }
.aotw-skel-body { padding: 1.6rem 1.7rem; display: flex; flex-direction: column; gap: 0.9rem; }
.aotw-skel-line { height: 0.75rem; border-radius: 999px; }
@keyframes aotw-shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
.shimmer {
  background: linear-gradient(90deg, var(--bg-3) 25%, var(--bg-4) 37%, var(--bg-3) 63%);
  background-size: 200% 100%;
  animation: aotw-shimmer 1.6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .shimmer { animation: none; }
}

@media (max-width: 720px) {
  /* The intro paragraph under the H1 (see aircraftOfTheWeek() in
     simple-pages.js) just spends valuable above-the-fold space on a phone
     explaining a page the H1 + countdown chip already make obvious. */
  .aotw-intro { display: none; }
  .aotw-body, .aotw-skel-body { padding: 1.2rem 1.1rem; }
  .aotw-name { font-size: 1.25rem; }
  /* Six tiles at 2-per-row (3 rows) reads far better on a phone than the
     desktop 3-column layout, which would squeeze long values like
     "150 kn (173 mph / 279 km/h)" into an unreadably narrow column. */
  .aotw-spec-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ---------- Desktop-only hero-banner treatment ---------- */
/* Mobile keeps the original contain-fit photo / bottom-left credit (see the
   mobile-first base rules above) - it already worked well there. Desktop
   instead gets a magazine-style full-width banner crop with the credit
   pill tucked into its top-right corner. */
@media (min-width: 861px) {
  .aotw-media { min-height: 0; }
  /* The banner keeps its fixed shape - 16/10 capped at 380px tall, which at
     the card's ~1050px width is the height that keeps the photo prominent
     without pushing the name/specs/facts below the fold.

     What changed is how the photo is fitted into it. `object-fit: cover`
     used to scale until the box was full and clip the rest, which at this
     panel's ~2.5:1 shape meant cutting the top and bottom off a typical 3:2
     hero - wingtips, tail fins and often most of the sky. It is `contain`
     now, so the whole aircraft is always in frame and never distorted, and
     the space the photo does not reach is filled by .aotw-media-backdrop
     below rather than left as flat empty panel. */
  .aotw-media.has-photo { width: 100%; aspect-ratio: 16 / 10; max-height: 380px; }
  .aotw-media.has-photo .detail-hero-photo {
    position: relative; z-index: 1;
    width: 100%; height: 100%; max-height: none; object-fit: contain; margin: 0;
  }
  /* Desktop-only: the mobile layout below 861px sizes the banner to the
     photo itself, so there is no gap to fill there. */
  .aotw-media.has-photo .aotw-media-backdrop { display: block; }
  .aotw-media .hero-photo-credit { top: 0.8rem; right: 0.9rem; bottom: auto; left: auto; max-width: calc(100% - 1.6rem); }
  .aotw-skel-media { aspect-ratio: 16 / 10; min-height: 0; }
}

/* =========================================================
   OPERATOR DETAIL PAGE  (/operators/<slug>/)
   ---------------------------------------------------------
   An operator entry is mostly prose and a list of aircraft
   names, so this page deliberately drops the panelled,
   instrument-style treatment used elsewhere on the site: a
   full-bleed hero, one row of large figures, the bio set as
   a proper read, and the fleet as a quiet hairline list
   rather than a card grid. The only loud things are the
   numbers.

   The per-row tile photo is deliberately small and fixed -
   big enough to recognise the aircraft, not big enough to
   turn the list back into a grid - and a fixed box with
   object-fit means wildly different source crops still line
   up into one clean column down the page.

   All colours come from the theme variables, so the same
   restraint reads as dark scope or worn chart paper.
   ========================================================= */

/* ---- hero ------------------------------------------------------------ */
.opd-hero{margin-top:1.5rem;padding:3.5rem 0 3rem;text-align:center;
  border-top:1px solid var(--border-0);border-bottom:1px solid var(--border-0);
  background:radial-gradient(120% 140% at 50% 0%,var(--bg-2) 0%,transparent 70%)}
.opd-hero-inner{display:flex;flex-direction:column;align-items:center}
.opd-mark{display:block;width:88px;height:88px;margin-bottom:1.5rem}
.opd-mark img{width:100%;height:100%;object-fit:contain}
/* Flag fallback for an operator with no sourced logo. A rounded rectangle,
   not the circle the old monogram used: a 3:2 flag cropped to a circle loses
   the edges that identify most of them (the Canadian bars, the Emirati hoist).
   height:auto lets the 3:2 flag keep its shape inside the 88px square. */
.opd-mark.is-flag{display:grid;place-items:center;background:none;border:none}
/* No box-shadow ring here, unlike the 30px badge: the flag assets carry their
   own dark backing rect, which at 88px already reads as an edge - adding a
   border on top of it doubled the outline. */
.opd-mark.is-flag svg{width:88px;height:auto;display:block;border-radius:var(--radius-sm)}
/* Centred TEXT, not a flex row: as a flex container the whole
   "country - type - formed" string is one anonymous item, so on a narrow
   screen the flag gets a line of its own and the words wrap under it.
   Setting the flag inline instead lets the line break wherever it needs to
   and keeps it reading as one sentence. */
.opd-eyebrow{display:block;max-width:100%;text-align:center;margin:0 0 1rem;line-height:1.8;
  font-family:var(--font-mono);font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
.opd-eyebrow .flag-icon{vertical-align:-3px}
.opd-hero h1{font-size:clamp(2.2rem,6.5vw,4.2rem);line-height:1.02;letter-spacing:-.03em;margin:0 0 1.1rem;
  max-width:16ch}
.opd-lede{max-width:44ch;margin:0;font-size:clamp(1rem,1.6vw,1.2rem);line-height:1.55;color:var(--text-1)}

/* ---- stat line -------------------------------------------------------- */
.opd-stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1.5rem;
  padding:2.5rem 0;border-bottom:1px solid var(--border-0)}
.opd-stat{text-align:center}
.opd-stat b{display:block;font-size:clamp(2rem,4.5vw,3rem);font-weight:600;line-height:1;color:var(--text-0);
  letter-spacing:-.02em}
.opd-stat span{display:block;margin-top:.55rem;font-size:.74rem;letter-spacing:.06em;color:var(--text-3);line-height:1.4}

/* ---- the read --------------------------------------------------------- */
.opd-read{max-width:66ch;margin:3rem auto 3.5rem}
.opd-read p{margin:0 0 1.3rem;font-size:1.02rem;line-height:1.8;color:var(--text-1)}
/* A restrained lead-in rather than a drop cap - a first line set slightly
   larger and lighter carries the same "start reading here" signal without
   fighting the geometric type the rest of the site uses. */
.opd-read .opd-first{font-size:1.14rem;line-height:1.72;color:var(--text-0)}
.opd-link,.opd-hq{display:flex;align-items:center;gap:.45rem;font-size:.82rem;color:var(--text-3);margin-bottom:.5rem}
.opd-link svg,.opd-hq svg{width:12px;height:12px;flex:none}

/* ---- fleet list -------------------------------------------------------- */
.opd-group{margin-bottom:3rem}
.opd-group-head{display:flex;align-items:baseline;gap:.9rem;margin:0 0 .5rem;
  font-size:.78rem;font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:var(--text-2)}
/* The two fleet headings carry the colour of the status they name - green
   for in service, red for retired - so the split reads at a glance from the
   heading alone, the same pairing the status dots use in search results and
   on the Aircraft of the Week eyebrow. Both --radar and --red are retuned
   per theme, so this stays legible on parchment as well as on the dark
   ground. */
.opd-group-head--active{color:var(--radar)}
.opd-group-head--retired{color:var(--red)}
/* Explicit colour, not inherited - the note describes the sort order, not
   the status, and following the heading into green or red would make it read
   as part of the label. */
.opd-group-note{font-family:var(--font-mono);font-size:.62rem;letter-spacing:.1em;color:var(--text-3);
  text-transform:none;margin-left:auto}
.opd-rows{border-top:1px solid var(--border-0)}
.opd-row{display:flex;align-items:center;justify-content:space-between;gap:2rem;
  padding:1rem .5rem;border-bottom:1px solid var(--border-0);text-decoration:none;
  transition:background .15s ease,padding-left .15s ease}
.opd-row:hover{background:var(--bg-2);padding-left:1rem}
.opd-row-lead{display:flex;align-items:center;gap:1.15rem;min-width:0;flex:1}
.opd-row-body{min-width:0}

/* The photo is deliberately small and fixed - big enough to recognise the
   aircraft at a glance, not big enough to turn a quiet list back into a card
   grid. A fixed box with object-fit also means wildly different source crops
   still line up into one clean column down the page. */
.opd-thumb{flex:none;display:block;width:112px;height:74px;overflow:hidden;
  border-radius:var(--radius-sm);border:1px solid var(--border-0);background:var(--bg-2)}
.opd-thumb img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .35s ease}
.opd-row:hover .opd-thumb img{transform:scale(1.05)}
.opd-thumb.is-silhouette{display:grid;place-items:center;padding:.5rem}
.opd-thumb.is-silhouette svg{width:100%;height:100%;opacity:.55;color:var(--text-3)}
.opd-row-name{display:block;font-size:1.32rem;font-weight:600;letter-spacing:-.015em;color:var(--text-0);line-height:1.2}
.opd-row-meta{display:block;margin-top:.35rem;font-size:.82rem;color:var(--text-3);line-height:1.5}
.opd-dot{margin:0 .5rem;color:var(--border-1)}
.opd-row-figures{display:flex;gap:2.25rem;flex:none;text-align:right}
.opd-fig b{display:block;font-size:1.35rem;font-weight:600;color:var(--text-0);line-height:1.1;letter-spacing:-.01em}
.opd-fig i{display:block;margin-top:.25rem;font-style:normal;font-size:.62rem;letter-spacing:.12em;
  text-transform:uppercase;color:var(--text-3)}
.opd-empty{color:var(--text-3);margin:2rem 0}

/* ---- responsive --------------------------------------------------------- */
@media (max-width:900px){
  .opd-hero{padding:2.5rem 0 2.25rem}
  .opd-stats{grid-template-columns:repeat(2,minmax(0,1fr));gap:2rem 1rem;padding:2rem 0}
  .opd-read{margin:2.25rem auto 2.5rem}
}
@media (max-width:720px){
  .opd-mark{width:64px;height:64px;margin-bottom:1.1rem}
  .opd-read p{font-size:.96rem;line-height:1.72}
  .opd-read .opd-first{font-size:1.04rem}
  /* Name above, figures below on their own line - side-by-side would
     squeeze a long type name against a long fleet figure and neither
     would survive it. */
  .opd-row{flex-direction:column;align-items:flex-start;gap:.85rem;padding:1.1rem .25rem}
  .opd-row:hover{padding-left:.25rem}
  .opd-row-lead{width:100%;gap:.85rem}
  .opd-thumb{width:88px;height:60px}
  .opd-row-name{font-size:1.05rem}
  .opd-row-figures{width:100%;justify-content:flex-start;gap:2.5rem;text-align:left;
    padding-top:.75rem;border-top:1px dashed var(--border-0)}
  .opd-fig b{font-size:1.15rem}
  .opd-group-note{margin-left:0;width:100%}
  .opd-group-head{flex-wrap:wrap;gap:.3rem}
}
@media (max-width:480px){
  .opd-stats{gap:1.5rem 1rem}
  .opd-stat b{font-size:1.9rem}
  .opd-thumb{width:72px;height:50px}
  .opd-row-lead{gap:.7rem}
}
/* Hover-driven zoom is decoration, not information. */
@media (prefers-reduced-motion:reduce){
  .opd-thumb img{transition:none}
  .opd-row:hover .opd-thumb img{transform:none}
}

/* --- "Other operators you may be interested in" -----------------------
   Sits after the fleet list. Three sibling operators of the same type, as
   small cards - the one place on this page that uses a box, because these
   are departures FROM the page rather than more of its content, and a row
   of hairline rules would read as a continuation of the fleet list directly
   above it. */
.opd-related{margin:1rem 0 0;padding-top:2.25rem;border-top:1px solid var(--border-0)}
.opd-related-head{margin:0 0 1.1rem;font-size:.78rem;font-weight:600;letter-spacing:.18em;
  text-transform:uppercase;color:var(--text-2)}
.opd-rel-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}
.opd-rel-card{display:flex;align-items:center;gap:.9rem;min-width:0;padding:1rem;
  background:var(--bg-2);border:1px solid var(--border-0);border-radius:var(--radius-md);
  text-decoration:none;transition:background .15s ease,border-color .15s ease,transform .15s ease}
.opd-rel-card:hover{background:var(--bg-3);border-color:var(--border-1);transform:translateY(-2px)}
.opd-rel-mark{flex:none;display:grid;place-items:center;width:44px;height:44px;
  font-family:var(--font-mono);font-size:.82rem;color:var(--radar)}
.opd-rel-mark img{width:100%;height:100%;object-fit:contain}
.opd-rel-mark.is-flag svg{width:100%;height:auto;display:block;border-radius:2px;
  box-shadow:0 0 0 1px var(--border-1)}
.opd-rel-text{min-width:0}
.opd-rel-name{display:block;font-size:.95rem;font-weight:600;color:var(--text-0);line-height:1.3;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.opd-rel-meta{display:flex;align-items:center;flex-wrap:wrap;margin-top:.3rem;font-size:.74rem;color:var(--text-3)}
.opd-rel-meta .flag-icon{width:18px;height:12px;margin-right:.4rem;vertical-align:-1px}

@media (max-width:900px){
  .opd-rel-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* An odd one out on its own half-width row looks like a mistake, so the
     third card takes the full width instead. */
  .opd-rel-card:nth-child(3):last-child{grid-column:1 / -1}
}
@media (max-width:640px){
  .opd-rel-grid{grid-template-columns:minmax(0,1fr)}
  .opd-rel-card:nth-child(3):last-child{grid-column:auto}
  .opd-rel-name{white-space:normal}
}
@media (prefers-reduced-motion:reduce){
  .opd-rel-card:hover{transform:none}
}
