/* app.css — finalapproach.net.
   Most pages here are tabular data (CLAUDE.md §7: "Semantic HTML, accessible tables"),
   so the styling is deliberately quiet: legible tables, clear hierarchy, no ornament. */

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--fs-2);
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  -webkit-text-size-adjust: 100%;
}

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 var(--sp-4); }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent-dark); }

.skip-link {
  position: absolute; left: -9999px;
  background: var(--paper); color: var(--ink);
  padding: var(--sp-2) var(--sp-3); z-index: 10;
}
.skip-link:focus { left: var(--sp-3); top: var(--sp-3); }

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

/* --- chrome ------------------------------------------------------------------ */
header.site { border-bottom: 1px solid var(--rule); padding: var(--sp-4) 0 0; }
.masthead { display: flex; align-items: baseline; gap: var(--sp-4); flex-wrap: wrap; }
.brand {
  font-size: var(--fs-3); font-weight: 650; letter-spacing: -0.01em;
  color: var(--ink); text-decoration: none;
}
.brand span { color: var(--ink-faint); font-weight: 400; }
.tagline { margin: 0; color: var(--ink-muted); font-size: var(--fs-1); }

/* Primary nav and the resolver share one row: the nav pushed left, the form right,
   wrapping to two rows on a phone. No JS, no dropdown — six links is not a menu. */
.navrow {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3) var(--sp-4); flex-wrap: wrap;
  padding-top: var(--sp-3); padding-bottom: var(--sp-3);
}
nav.primary ul {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-4);
}
nav.primary a {
  color: var(--ink-muted); text-decoration: none; font-size: var(--fs-1);
  padding: 0.15rem 0; border-bottom: 2px solid transparent;
}
nav.primary a:hover { color: var(--accent); }
nav.primary a[aria-current] { color: var(--ink); border-bottom-color: var(--accent); }

form.lookup { display: flex; align-items: center; gap: var(--sp-2); }
form.lookup label {
  font-size: var(--fs-0); color: var(--ink-muted);
  text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap;
}
form.lookup input {
  font: inherit; font-size: var(--fs-1); color: var(--ink);
  background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 0.2rem var(--sp-2); width: 15rem; max-width: 42vw;
}
form.lookup input::placeholder { color: var(--ink-faint); }
form.lookup button {
  font: inherit; font-size: var(--fs-1); cursor: pointer;
  color: var(--ink); background: var(--paper-alt);
  border: 1px solid var(--rule); border-radius: var(--radius); padding: 0.2rem var(--sp-3);
}
form.lookup button:hover { border-color: var(--accent); color: var(--accent); }

main { padding: var(--sp-5) 0 var(--sp-6); }

footer.site {
  border-top: 1px solid var(--rule); padding: var(--sp-5) 0 var(--sp-6);
  color: var(--ink-muted); font-size: var(--fs-1);
}
footer.site p { margin: 0 0 var(--sp-2); max-width: 44rem; }
.footer-nav ul {
  list-style: none; margin: 0 0 var(--sp-3); padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-4);
}

/* --- text -------------------------------------------------------------------- */
h1 { font-size: var(--fs-5); line-height: 1.2; letter-spacing: -0.02em; margin: 0 0 var(--sp-3); }
h2 { font-size: var(--fs-3); margin: 0 0 var(--sp-3); }
.lede { font-size: var(--fs-3); color: var(--ink-muted); margin: 0 0 var(--sp-5); line-height: 1.45; }
.muted { color: var(--ink-muted); }
.provenance { font-size: var(--fs-1); margin-top: var(--sp-4); }
code { font-family: var(--mono); font-size: 0.9em; background: var(--paper-alt);
       padding: 0.1em 0.35em; border-radius: 3px; }

/* The separator is drawn, not written: an <ol> of four items should announce as a list of
   four items, not as four slashes. */
.crumbs { font-size: var(--fs-1); color: var(--ink-muted); margin-bottom: var(--sp-4); }
.crumbs ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; }
.crumbs li + li::before { content: "/"; color: var(--ink-faint); margin: 0 var(--sp-2); }

/* --- panels ------------------------------------------------------------------ */
.panel {
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-5); margin: 0 0 var(--sp-5); background: var(--paper);
}

.sector-route { display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-4); }
.sector-route .apt { font-family: var(--mono); letter-spacing: 0.02em; }
.sector-route .arrow { color: var(--ink-faint); }

/* --- stats ------------------------------------------------------------------- */
.stat-row { display: flex; flex-wrap: wrap; gap: var(--sp-5); margin: 0 0 var(--sp-5); }
.stat { display: flex; flex-direction: column; }
.stat .n { font-size: var(--fs-4); font-weight: 650; font-variant-numeric: tabular-nums; }
.stat .l { font-size: var(--fs-1); color: var(--ink-muted); }

/* --- tables ------------------------------------------------------------------ */
/* Wide tables scroll inside their own container so the page body never scrolls
   horizontally on a phone. */
table { border-collapse: collapse; width: 100%; font-size: var(--fs-1); }
caption { text-align: left; }
th, td { text-align: left; padding: var(--sp-2) var(--sp-3); vertical-align: top; }
th { font-weight: 600; }
.num { text-align: right; font-variant-numeric: tabular-nums; }

table.kv th { width: 12rem; color: var(--ink-muted); font-weight: 500; }
table.kv tr + tr th, table.kv tr + tr td { border-top: 1px solid var(--rule); }

table.data thead th {
  border-bottom: 2px solid var(--rule); font-size: var(--fs-0);
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink-muted);
}
table.data tbody tr + tr th, table.data tbody tr + tr td { border-top: 1px solid var(--rule); }
table.data tbody tr:hover { background: var(--paper-alt); }

/* Long tables: a type page lists up to ~2,000 airframes and /airlines/ lists 364, so the
   header scrolls off long before the reader is done with it. position: sticky needs no JS
   and costs nothing when the table is short. The background is opaque so rows pass behind
   it rather than through it. Applied to every data table rather than by opt-in class,
   because "is this table long enough to need it" is a question about the data, not the
   template. */
table.data thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--paper);
  box-shadow: inset 0 -2px 0 var(--rule);
  border-bottom: 0;
}

/* ⚠ overflow-x AND position: sticky FIGHT EACH OTHER, and this is why the wrapper only
   engages on narrow screens. Setting overflow-x: auto makes the wrapper a scroll container
   in BOTH axes as far as sticky is concerned — CSS forces the other axis from `visible` to
   `auto` — so `top: 0` would stick to the top of the box instead of the top of the
   viewport, and the header would never follow the page down.
   These tables have short cells and wrap rather than overflow at desktop widths, so the
   wrapper is a safety net for phones, where a sticky header is worth least anyway. */
@media (max-width: 40rem) {
  .scroll-x { overflow-x: auto; }
}

/* Lookup suggestions. Only ever created by app.js, so this styles nothing when JavaScript
   is off — the form below it works either way. */
form.lookup { position: relative; }
ul.suggest {
  position: absolute; z-index: 20; top: 100%; right: 0;
  min-width: min(22rem, 88vw); max-width: min(30rem, 90vw);
  max-height: 60vh; overflow-y: auto;
  list-style: none; margin: var(--sp-1) 0 0; padding: var(--sp-1) 0;
  background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: 0 6px 24px rgb(0 0 0 / 0.12);
}
ul.suggest li { display: flex; align-items: baseline; gap: var(--sp-2);
                padding: 0.3rem var(--sp-3); cursor: pointer; font-size: var(--fs-1); }
ul.suggest li.on, ul.suggest li:hover { background: var(--paper-alt); }
/* min-width: 0 is what lets a flex item shrink below its content width — without it a long
   vessel name pushes the row wider than the list box and the note falls off the edge. */
ul.suggest li > * { min-width: 0; }
ul.suggest a { text-decoration: none; color: var(--ink);
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
ul.suggest .muted { margin-left: auto; font-size: var(--fs-0); flex: 0 1 auto;
                    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.suggestions { list-style: none; margin: 0; padding: 0; }
.suggestions li { padding: 0.2rem 0; }
.suggestions li + li { border-top: 1px solid var(--rule); }
.suggestions .muted { margin-left: var(--sp-3); font-size: var(--fs-1); }

/* The resolver's own page gives the form room to breathe, unlike the masthead copy. */
form.lookup-page { flex-wrap: wrap; margin-bottom: var(--sp-3); }
form.lookup-page input { width: 22rem; }

/* .vh is the same rule under a shorter name, and it exists for one reason: the coverage
   matrix repeats it once per cell, ~150 times on the homepage. "visually-hidden" there
   costs 2 KB of markup on the site's most-fetched page for no added clarity. */
.visually-hidden, .vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* --- narrow viewports --------------------------------------------------------------
   THE RULE: the page itself never scrolls sideways. Where content genuinely cannot fit —
   a seven-column movements table on a 320px phone — it scrolls inside its own panel, and
   the reader keeps every column. Nothing is hidden that cannot be got back by turning the
   phone or widening the window.

   Measured minimum widths before this work (longest unbreakable token per column, plus
   padding): /ships/ 454px, /airlines/ 409px, an LHR day page 387px, /harbours/<cc>/ 378px,
   /routes/ 370px — against roughly 240px of usable width inside a 320px viewport. */

/* Last-resort guard. `clip` rather than `hidden` ON PURPOSE: hidden would make <html> a
   scroll container and break every position: sticky table header on the site, whereas clip
   creates no scroll container at all. Older browsers ignore it and simply behave as before,
   which is why the real fixes below do the actual work. */
html { overflow-x: clip; }

/* Grids must never demand more than the screen has. `minmax(19rem, …)` sets a hard 304px
   floor, which is wider than a 320px phone can give once padding is taken — so the
   homepage's two-column block was itself forcing a horizontal scrollbar. min(…, 100%)
   lets the floor collapse instead. */
.col2  { grid-template-columns: repeat(auto-fit,  minmax(min(19rem, 100%), 1fr)); }
.chips { grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr)); }

/* URL examples on the homepage and /about/ are single unbreakable tokens. */
code { overflow-wrap: anywhere; }

/* 16px, not 15px, and this is not a style choice: iOS Safari zooms the whole page when a
   focused input's font-size is under 16px, and the reader then has to pinch back out. */
form.lookup input { font-size: 1rem; }

@media (max-width: 40rem) {
  h1 { font-size: var(--fs-4); }
  .lede { font-size: var(--fs-2); }
  table.kv th { width: auto; }
  .stat-row { gap: var(--sp-4); }

  /* Give the content back the space the chrome was taking. */
  .wrap { padding: 0 var(--sp-3); }
  .panel { padding: var(--sp-3); }
  table.data { font-size: var(--fs-0); }
  th, td { padding: var(--sp-2); }

  /* `anywhere`, NOT `break-word`, and the difference is the entire point: only `anywhere`
     reduces a cell's min-content width, which is what table layout uses to decide the
     table cannot be narrower. `break-word` wraps text visually but leaves the table
     demanding its longest word's width, so the table stays too wide and scrolls anyway.
     With this, "International" and "SVITZER MUIDEN" break instead of setting a floor —
     and four tables that still overflowed by 12-32px after the column trimming fit. */
  th, td { overflow-wrap: anywhere; }

  /* Columns worth dropping before the table starts scrolling: an ICAO code beside the
     IATA one, a flag beside a vessel's name, a date range beside the counts. The primary
     identifier and the figures always stay. */
  .col-wide { display: none; }

  /* Prose and headings get the same treatment, so an unexpected long token — a vessel
     name, an airport with a very long single word — cannot set a floor either. */
  h1, h2, p, li, dd, dt { overflow-wrap: anywhere; }

  /* ⚠ NO blanket `.panel { overflow-x: auto }` here, deliberately. It would contain any
     overflow, but overflow-x: auto establishes a scroll container whether or not anything
     overflows — and that breaks position: sticky for every table header inside it. With
     the column trimming and break-anywhere above, the widest visible table now needs
     ~113px against ~270px available, so the blanket rule would cost sticky headers on
     every phone to guard against a scroll that cannot happen. The .scroll-x wrappers on
     the few widest tables stay as the visible fallback. */

  /* The resolver takes the full row rather than fighting the nav for space. */
  .navrow { gap: var(--sp-2); }
  form.lookup { width: 100%; }
  form.lookup input { flex: 1 1 auto; width: auto; max-width: none; }

  /* The coverage matrix is 37 columns wide and cannot usefully shrink below ~13px a
     cell, so on a phone it scrolls inside its own wrapper — the sanctioned fallback,
     and safe here because .heat is not a .data table and has no sticky header to
     break. The same years remain reachable from the airport pages either way. */
  table.heat { min-width: 30rem; }
  table.heat tbody th { width: 7rem; white-space: normal; }
  .barcol { width: 2.5rem; }

  /* Bigger tap targets: 15px links an eighth of an inch apart are not usable with a thumb. */
  nav.primary ul { gap: 0 var(--sp-4); }
  nav.primary a { padding: var(--sp-2) 0; }
  .footer-nav ul { gap: 0 var(--sp-4); }
  .footer-nav a { display: inline-block; padding: var(--sp-1) 0; }
  .crumbs li { padding: var(--sp-1) 0; }
}

.sector-route .route-link { font-size: var(--fs-1); font-weight: 400; }

/* --- archive coverage (§3) ---------------------------------------------------- */
/* The grade is information, not decoration: more than a third of the airport-years
   held are not a complete record, so this must read clearly rather than sit quietly. */
.coverage { font-size: var(--fs-1); border-left: 3px solid var(--rule);
            padding: var(--sp-2) var(--sp-3); margin: var(--sp-4) 0 0;
            background: var(--paper-alt); color: var(--ink-muted); }
.coverage-sightings, .coverage-hole { border-left-color: var(--grade-part); }
.coverage-thin, .coverage-partial   { border-left-color: var(--grade-part); }
.coverage-complete                  { border-left-color: var(--grade-full); }
.chip { font-size: var(--fs-0); text-transform: uppercase; letter-spacing: 0.04em;
        padding: 0.1em 0.45em; border-radius: 999px; border: 1px solid var(--rule);
        color: var(--ink-muted); white-space: nowrap; }
.chip-complete  { border-color: var(--grade-full); color: var(--grade-full); }
.chip-sightings, .chip-hole { border-color: var(--grade-part); color: var(--grade-part); }
/* The blanket grade the collapsed pre-1990 block carries. One badge for the whole block,
   never a per-year grade: every year of that material is a spotter's log, and grading
   them individually would imply a confidence the record does not support. */
.chip-fragmentary { border-color: var(--grade-part); color: var(--grade-part); }

/* --- occurrence detail -------------------------------------------------------- */
.delay { font-variant-numeric: tabular-nums; }
.delay.late    { color: #a8440f; }
.delay.early   { color: #2e7d32; }
.delay.on-time { color: var(--ink-muted); }
.flag { font-size: var(--fs-1); padding: var(--sp-2) var(--sp-3); border-radius: var(--radius);
        margin: 0 0 var(--sp-3); border-left: 3px solid var(--rule); background: var(--paper-alt); }
.flag-bad  { border-left-color: #a8440f; }
.flag-warn { border-left-color: #b8860b; }

/* --- homepage: the dashboard --------------------------------------------------- */
.stat-row-lead { gap: var(--sp-6); margin-bottom: var(--sp-3); }
.stat-row-lead .n { font-size: var(--fs-5); letter-spacing: -0.02em; }

/* ZONE A — KPI tiles. A tile is a link, so the whole rectangle is the target rather than
   the four words of the label. */
.kpis { display: grid; gap: var(--sp-3);
        grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
        margin: 0 0 var(--sp-3); }
.kpi {
  display: flex; flex-direction: column; gap: 0.15rem;
  border: 1px solid var(--rule); border-top-width: 3px; border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4); text-decoration: none; color: var(--ink);
  background: var(--paper);
}
.kpi:hover { border-color: var(--accent); background: var(--paper-alt); }
.kpi-air { border-top-color: var(--air); }
.kpi-sea { border-top-color: var(--sea); }
.kpi-air:hover { border-top-color: var(--air); }
.kpi-sea:hover { border-top-color: var(--sea); }
.kpi .n { font-size: var(--fs-4); font-weight: 650; letter-spacing: -0.02em;
          font-variant-numeric: tabular-nums; line-height: 1.15; }
.kpi .l { font-size: var(--fs-1); color: var(--ink-muted); }
.kpi .f { font-size: var(--fs-0); color: var(--ink-faint); }

/* The sparkline is drawn in currentColor, so the tone class is the only thing that
   differs between an air series and a sea one. */
.spark { display: block; max-width: 100%; height: auto; margin: var(--sp-2) 0 var(--sp-1); }
.spark polyline { fill: none; stroke: currentColor; stroke-width: 1.5;
                  stroke-linejoin: round; stroke-linecap: round;
                  vector-effect: non-scaling-stroke; }
.spark circle { fill: currentColor; }
.spark-air { color: var(--air); }
.spark-sea { color: var(--sea); }

.freshness { font-size: var(--fs-1); color: var(--ink-muted); margin: 0 0 var(--sp-5); }
.freshness strong { color: var(--ink); font-weight: 600; }

/* ZONE B — the coverage matrix. A real table, not a picture: every cell carries its
   grade as text for anything that cannot see colour, and the column headers carry the
   year even where only every fifth is drawn. */
table.heat { table-layout: fixed; border-collapse: separate; border-spacing: 1px;
             width: 100%; margin-bottom: var(--sp-3); }
table.heat th, table.heat td { padding: 0; }
table.heat tbody th { width: 9.5rem; font-size: var(--fs-0); font-weight: 500;
                      color: var(--ink-muted); padding-right: var(--sp-3);
                      white-space: nowrap; vertical-align: middle; }
table.heat thead th { height: 1rem; vertical-align: bottom; }
table.heat thead i { font-style: normal; font-size: 0.625rem; color: var(--ink-faint);
                     font-variant-numeric: tabular-nums; }
.hc { height: 1.1rem; border-radius: 2px; }
.hc a { display: block; height: 1.1rem; overflow: hidden;
        text-indent: -999em; white-space: nowrap; }
.hc-full { background: var(--grade-full); }
.hc-part { background: var(--grade-part); }
.hc-hole { background: transparent; box-shadow: inset 0 0 0 1.5px var(--grade-part); }
.hc-none { background: var(--paper-alt); box-shadow: inset 0 0 0 1px var(--rule); }

.heat-key { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
            font-size: var(--fs-0); color: var(--ink-muted); margin: 0; }
.heat-key .k { display: flex; align-items: center; gap: var(--sp-2); }
.heat-key .hc { display: inline-block; width: 0.85rem; height: 0.85rem; flex: none; }

/* ZONE C — the worked examples. */
.explore { margin: 0 0 var(--sp-5); }
.cards { display: grid; gap: var(--sp-3);
         grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); }
.card {
  display: flex; flex-direction: column; gap: 0.2rem;
  border: 1px solid var(--rule); border-left-width: 3px; border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4); text-decoration: none; color: var(--ink);
  background: var(--paper);
}
.card:hover { border-color: var(--accent); background: var(--paper-alt); }
.card-air { border-left-color: var(--air); }
.card-sea { border-left-color: var(--sea); }
.card-air:hover { border-left-color: var(--air); }
.card-sea:hover { border-left-color: var(--sea); }
.card .k { font-size: var(--fs-0); text-transform: uppercase; letter-spacing: 0.04em;
           color: var(--ink-faint); }
.card strong { font-size: var(--fs-2); font-weight: 650; color: var(--accent); }
.card:hover strong { text-decoration: underline; }
.card .d { font-size: var(--fs-1); color: var(--ink-muted); }

/* ZONE D — leaderboards. The accent says which half of the archive a card belongs to. */
.panel-air { border-top: 3px solid var(--air); }
.panel-sea { border-top: 3px solid var(--sea); }
.panel-ref { border-style: dashed; }

/* The mini-bar: the same value as the adjacent figure, as a length. */
.barcol { width: 4rem; padding-left: 0; }
.bar { display: block; height: 6px; border-radius: 3px; background: var(--paper-alt);
       box-shadow: inset 0 0 0 1px var(--rule); overflow: hidden; }
.bar i { display: block; height: 100%; }
.bar-air i { background: var(--air-fill); }
.bar-sea i { background: var(--sea-fill); }

/* Two panels side by side on wide screens, stacked on narrow. Plain grid so it
   degrades to a single column without a media query fighting it. */
.col2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
        gap: 0 var(--sp-5); }

.more { margin: var(--sp-3) 0 0; font-size: var(--fs-1); }

/* Airline chips: 140 carriers as a dense wrapped list rather than a 140-row table,
   which is what buried everything else on this page. */
.chips { list-style: none; margin: 0; padding: 0;
         display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
         gap: var(--sp-1) var(--sp-4); }
.chips a { display: flex; align-items: baseline; gap: var(--sp-2);
           padding: 0.15rem 0; text-decoration: none; font-size: var(--fs-1); }
.chips a:hover .nm { text-decoration: underline; }
.chips strong { font-family: var(--mono); min-width: 2.2rem; }
.chips .nm { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chips .qty { margin-left: auto; color: var(--ink-faint);
              font-variant-numeric: tabular-nums; font-size: var(--fs-0); }
