/* ---------------------------------------------------------------- tokens --
   Light is the base; dark is a selected set of steps for the dark surface,
   declared under both the OS media query and the explicit theme stamp so the
   toggle wins in either direction. */
:root {
  color-scheme: light;
  --plane:      #f9f9f7;
  --surface:    #fcfcfb;
  --ink:        #0b0b0b;
  --ink-2:      #52514e;
  /* Darker than the dark-mode step below, which keeps #898781. One value cannot serve
     both: #898781 gives 5.4 and 4.9 on the dark surfaces and only 3.4 and 3.5 on the
     light ones, against 4.5 for normal text - and this token carries every caption,
     section heading, axis label, the footer and the disclaimer. 0.52 -> 0.42 lightness,
     hue and saturation unchanged, giving 4.9 and 5.0. */
  --muted:      #6f6d68;
  --grid:       #e1e0d9;
  --axis:       #c3c2b7;
  --border:     rgba(11,11,11,.10);
  --series-1:   #2a78d6;   /* arrivals, and any single-series mark */
  /* A selected control is not a chart mark. --series-1 is the dataviz palette's
     validated blue and must keep its measured value, but white on it is only
     4.42:1 - under the 4.5 that 12px text needs. So the toggle gets its own pair,
     free to be darker than the series colour without touching any chart. */
  --on-bg:      #2a6fc4;   /* white on this = 5.04:1 */
  --on-fg:      #ffffff;
  --series-2:   #eb6834;   /* departures, and the 22:00-24:00 band */
  --series-3:   #1baf7a;   /* the 00:00-06:00 band */
  /* The exact-hour bands are ORDERED - they run through one operating day - so they
     take a sequential ramp, one hue light-to-dark, not five categorical hues. Every
     step is the dataviz palette's own blue ramp; none is invented. Ordered from the
     start of the operating day (06:00, when the curfew lifts) through to the night,
     so the darkest step lands on 00:00-06:00, the hours a southern approach matters
     most. Validated 2026-09-02 against this surface: all five >= 3:1, OKLab lightness
     strictly monotonic (.622 .575 .527 .480 .385). */
  --band-1:     #3987e5;   /* 06:00-09:00 - the Sudanflug window */
  --band-2:     #2a78d6;   /* 09:00-18:00 */
  --band-3:     #256abf;   /* 18:00-20:00 */
  --band-4:     #1c5cab;   /* 20:00-24:00 */
  --band-5:     #104281;   /* 00:00-06:00 */
  /* The wind grid's diverging scale: tailwind component on runway 28. Two arms of the
     palette's own ramps around its neutral midpoint - blue for a headwind, red for a
     tailwind - validated 2026-09-08 per arm as ordinal steps (monotone lightness, light
     end 4.30:1 and 3.85:1 against this surface) and as a pair (CVD dE 21.6). The
     neutral is the palette's "nothing" grey, close to the surface on purpose. */
  --div-hw2:    #104281;   /* headwind 5 kt or more */
  --div-hw1:    #2a78d6;   /* headwind 2-5 */
  --div-0:      #f0efec;   /* within 2 kt, calm, variable */
  --div-tw1:    #e34948;   /* tailwind 2-5 */
  --div-tw2:    #a12f2f;   /* tailwind 5 kt or more - the east concept cannot be flown */
  --good:       #0ca30c;
  --warning:    #fab219;
  --critical:   #d03b3b;
  --good-text:  #006300;
}
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    color-scheme: dark;
    --plane: #0d0d0d; --surface: #1a1a19; --ink: #fff; --ink-2: #c3c2b7;
    --muted: #898781; --grid: #2c2c2a; --axis: #383835;
    --border: rgba(255,255,255,.10);
    --series-1: #3987e5; --series-2: #d95926; --series-3: #199e70; --good-text: #0ca30c;
    /* Selected, not inverted: the same blue ramp stepped lighter so every band
       clears 3:1 on #1a1a19. Validated 2026-09-02: all five pass, OKLab L
       .858 .764 .671 .622 .575. */
    --band-1: #b7d3f6; --band-2: #86b6ef; --band-3: #5598e7;
    --band-4: #3987e5; --band-5: #2a78d6;
    /* Selected for the dark surface, not inverted: light ends 4.79:1 and 5.39:1. */
    --div-hw2: #3987e5; --div-hw1: #86b6ef; --div-0: #383835;
    --div-tw1: #f0a3a3; --div-tw2: #e66767;
    /* Dark keeps the brighter fill - darkening it here would sink the chip into the
       surface - and flips the label instead: #0d0d0d on #3987e5 is 5.34:1. */
    --on-bg: #3987e5; --on-fg: #0d0d0d;
    /* Selected, not inherited: #d03b3b passes on the light surfaces (4.6 and 4.7)
       and fails on the dark ones (4.1 on the page, 3.6 on a card) - and it marks
       cancellations and delays past an hour, the rows most worth reading. Same hue
       and saturation, lightness 0.52 -> 0.65, giving 6.1 and 5.4. */
    --critical: #dc6f6f;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --plane: #0d0d0d; --surface: #1a1a19; --ink: #fff; --ink-2: #c3c2b7;
  --muted: #898781; --grid: #2c2c2a; --axis: #383835;
  --border: rgba(255,255,255,.10);
  --series-1: #3987e5; --series-2: #d95926; --series-3: #199e70; --good-text: #0ca30c;
  /* Selected, not inverted: the same blue ramp stepped lighter so every band
     clears 3:1 on #1a1a19. Validated 2026-09-02: all five pass, OKLab L
     .858 .764 .671 .622 .575. */
  --band-1: #b7d3f6; --band-2: #86b6ef; --band-3: #5598e7;
  --band-4: #3987e5; --band-5: #2a78d6;
  /* Selected for the dark surface, not inverted: light ends 4.79:1 and 5.39:1. */
  --div-hw2: #3987e5; --div-hw1: #86b6ef; --div-0: #383835;
  --div-tw1: #f0a3a3; --div-tw2: #e66767;
    /* Selected, not inherited: #d03b3b passes on the light surfaces (4.6 and 4.7)
       and fails on the dark ones (4.1 on the page, 3.6 on a card) - and it marks
       cancellations and delays past an hour, the rows most worth reading. Same hue
       and saturation, lightness 0.52 -> 0.65, giving 6.1 and 5.4. */
    --critical: #dc6f6f;
}

* { box-sizing: border-box; }
body {
  margin: 0; background: var(--plane); color: var(--ink);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
}
.wrap { max-width: 1240px; margin: 0 auto; padding: 28px 20px 64px; }

header { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin-bottom: 8px; }
h1 { font-size: 22px; margin: 0 0 4px; letter-spacing: -.01em; }
.sub { color: var(--ink-2); font-size: 13.5px; margin: 0; }
/* Visible without scrolling, on purpose. The domain and the page title both read like
   the airport's own, and a reader should not have to work out that they are not.
   One line, carrying only the affiliation: the provenance it used to spell out is in
   the footer in full and with links, and three lines of small print above the fold
   read as boilerplate to skip past - which is the one thing this must not be. */
.disclaimer { color: var(--muted); font-size: 12.5px; margin-top: 4px; max-width: 68ch; }
.disclaimer strong { color: var(--ink-2); font-weight: 600; }
.spacer { flex: 1 1 auto; }
button.theme {
  background: var(--surface); color: var(--ink-2); border: 1px solid var(--border);
  border-radius: 8px; padding: 7px 12px; font: inherit; font-size: 13px; cursor: pointer;
}
button.theme:hover { color: var(--ink); }

.notice {
  margin: 18px 0 0; padding: 11px 14px; border-radius: 10px; font-size: 13.5px;
  background: var(--surface); border: 1px solid var(--border); color: var(--ink-2);
}
.notice strong { color: var(--ink); font-weight: 600; }

h2 { font-size: 13px; text-transform: uppercase; letter-spacing: .07em;
     color: var(--muted); margin: 34px 0 12px; font-weight: 600; }

.tiles { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); margin-top: 18px; }
.tile { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; }
.tile .k { font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
.tile .v { font-size: 27px; font-weight: 600; letter-spacing: -.02em; line-height: 1.1; }
.tile .n { font-size: 12.5px; color: var(--ink-2); margin-top: 5px; }

/* align-items:start so a short card sizes to its content instead of stretching
   to match a tall neighbour - the airline list is short until enough carriers
   clear 20 movements. */
.grid2 { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
         align-items: start; }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px 16px 10px; }
.card h3 { font-size: 14px; margin: 0 0 2px; font-weight: 600; }
.card p.cap { font-size: 12.5px; color: var(--muted); margin: 0 0 10px; }
/* The period a chart covers is not a footnote — a share with no period attached is
   not a fact. Its own line, in ink rather than muted grey. */
.period { display: block; font-size: 13px; font-weight: 600; color: var(--ink-2);
          margin-bottom: 3px; }
.chart { width: 100%; }
/* A caption that follows a chart opens the next block of the same card - the second
   and third hour grids on /direction/ - so it needs air above it, or the grid runs
   straight into the next grid's heading. */
.card .chart + p.cap { margin-top: 14px; }
.empty { color: var(--muted); font-size: 13px; padding: 26px 0 30px; text-align: center; }

.legend { display: flex; gap: 14px; flex-wrap: wrap; margin: 0 0 8px; font-size: 12.5px; color: var(--ink-2); }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.swatch { width: 10px; height: 10px; border-radius: 3px; }

.daypick { display: flex; align-items: center; gap: 6px; }
.daypick label { font-size: 12.5px; color: var(--muted); }
.daypick select {
  background: var(--surface); color: var(--ink); border: 1px solid var(--border);
  border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px;
}
.daypick button { padding: 7px 10px; line-height: 1; }
.daypick button:disabled { opacity: .35; cursor: default; }

/* Deliberately does NOT wrap. The title block beside the toggles contains the card's
   caption, whose max-content width is far wider than any card, so allowing a wrap here
   drops the toggles onto their own line at every width instead of only narrow ones. */
.cardhead { display: flex; align-items: flex-start; gap: 12px; }
.cardhead > div:first-child { flex: 1 1 auto; }
.toggles { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; flex: none; }
/* Below this the toggle groups cannot share a line with a title: the southern-approach
   card carries two of them, 347 px against a card of about 307 px on a 375 px phone, and
   flex:none meant they simply hung over the edge - taking the whole document 78 px wider
   than the viewport, so every section scrolled sideways, not just this card. Handled by
   width rather than by letting them shrink, because the title block beside them holds the
   caption: its max-content width is enormous at every size, so anything shrink-based fires
   on a desktop too and splits a one-line control into two for no reason. */
@media (max-width: 520px) {
  .cardhead { flex-wrap: wrap; }
  .cardhead > div:first-child { min-width: 0; }
  .toggles { flex: 1 1 100%; justify-content: flex-start; min-width: 0; }
}
.toggle { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; flex: none; }
.toggle button {
  background: var(--surface); color: var(--muted); border: 0; padding: 5px 10px;
  font: inherit; font-size: 12px; cursor: pointer;
}
.toggle button + button { border-left: 1px solid var(--border); }
.toggle button.on { background: var(--on-bg); color: var(--on-fg); }
.scope { text-transform: none; letter-spacing: 0; color: var(--muted); font-weight: 400; }

/* Tooltip sits above everything and never intercepts the pointer. */
#tip {
  position: fixed; z-index: 40; pointer-events: none; opacity: 0;
  transition: opacity .08s; background: var(--surface); color: var(--ink);
  border: 1px solid var(--border); border-radius: 9px; padding: 8px 11px;
  font-size: 12.5px; line-height: 1.45; box-shadow: 0 6px 22px rgba(0,0,0,.14);
  max-width: 260px;
}
#tip b { font-weight: 600; }
#tip .row { display: flex; align-items: center; gap: 7px; white-space: nowrap; }
#tip .num { font-variant-numeric: tabular-nums; margin-left: auto; padding-left: 14px; }

.controls { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 10px; }
.controls input, .controls select {
  background: var(--surface); color: var(--ink); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 11px; font: inherit; font-size: 13.5px;
}
.controls input { min-width: 230px; }
.count { color: var(--muted); font-size: 13px; }

.tablewrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); }
table { border-collapse: collapse; width: 100%; font-size: 13.5px; }
th, td { text-align: left; padding: 8px 12px; white-space: nowrap; }
th {
  position: sticky; top: 0; background: var(--surface); color: var(--muted);
  font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; font-weight: 600;
  border-bottom: 1px solid var(--border); cursor: pointer; user-select: none;
}
th:hover { color: var(--ink); }
th[aria-sort]::after { content: " ↑"; }
th[aria-sort="descending"]::after { content: " ↓"; }
/* The header's sort control. Styled to inherit the cell completely so the table looks
   exactly as it did when the th itself was the click target - the change is that it is
   now reachable by keyboard. text-align inherits so the right-aligned Delay column
   stays right-aligned. */
.th-sort {
  all: unset; display: block; width: 100%; text-align: inherit;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  cursor: pointer;
}
/* No focus style existed anywhere on this site, so a keyboard user had only whatever
   the browser drew by default. :focus-visible, not :focus, keeps it off mouse clicks. */
.th-sort:focus-visible,
.skip:focus, .skip:focus-visible,
a:focus-visible, button:focus-visible, select:focus-visible, input:focus-visible {
  outline: 2px solid var(--series-1); outline-offset: 2px; border-radius: 3px;
}
/* Skip link: the first thing a keyboard reaches, and invisible until it does. Every
   page here opens with the same header, day picker and four-item nav, so without it
   reaching the actual content costs a dozen tabs on every page. Moved into flow on
   focus rather than merely made visible, so it cannot land on top of the header.
   :focus as well as :focus-visible - it is reached by keyboard by definition, and
   Safari has historically withheld :focus-visible from links. */
.skip {
  /* Parked ABOVE the viewport, not off to the left at -9999px: a left-parked element
     widens the scrollable area on its own, and this page already has a table wide
     enough that a stray extra pixel of page-level scroll would be blamed on that. */
  position: absolute; left: 0; top: -100px;
  background: var(--card); color: var(--fg);
  border: 1px solid var(--series-1); border-radius: 0 0 6px 0;
  padding: 10px 16px; z-index: 50; font-size: 14px; text-decoration: none;
}
.skip:focus, .skip:focus-visible { top: 0; }
/* The target takes focus programmatically, so it must not draw a ring of its own. */
main:focus { outline: none; }
td { border-bottom: 1px solid var(--grid); font-variant-numeric: tabular-nums; }
tbody tr:last-child td { border-bottom: 0; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
/* The date is redundant when the table holds a single day, and essential when it
   holds the whole archive. */
.col-date { display: none; }
body.all-days .col-date { display: table-cell; }
.pill { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 12px; font-weight: 600; }
.d-good { color: var(--good-text); }
.d-warn { color: #8a6100; }
.d-bad  { color: var(--critical); }
/* An explicit dark choice wins whatever the OS says, so this one stays outside. */
:root[data-theme="dark"] .d-warn { color: var(--warning); }
/* The system-dark variant has to live INSIDE the media block, and did not. Written as
   :where(:not([data-theme="light"])) it means "not explicitly light", NOT "dark" - so
   outside the block it also matched the default state, no attribute stamped, on a light
   OS. :where() adds no specificity but :root does, so at (0,2,0) it beat the (0,1,0)
   rule above and painted a 15-60 minute delay #fab219 on a light card: 1.79:1, against
   5.40 for the #8a6100 it was supposed to use. The state a first-time visitor sees. */
@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) .d-warn { color: var(--warning); }
}
.cancel { color: var(--critical); font-weight: 600; }

footer { margin-top: 40px; color: var(--muted); font-size: 12.5px; }
footer a { color: var(--ink-2); }

.pagenav { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 16px; }
.pagenav a {
  color: var(--ink-2); text-decoration: none; font-size: 13.5px;
  padding: 6px 12px; border-radius: 8px; border: 1px solid transparent;
}
.pagenav a:hover { color: var(--ink); background: var(--surface); }
.pagenav a[aria-current="page"] {
  color: var(--ink); background: var(--surface); border-color: var(--border); font-weight: 600;
}
