/* The route popup card (M-08-T4, restyled in v1.85 to the owner's
 * second pass on the mock-up).
 *
 * Owns the styling of everything `routecard.js` paints, the same way
 * `flightcard.css` owns the aircraft card: the module decides what is
 * said, this file decides how it looks, and neither reaches into the
 * other's business. It is loaded **after** `tokens.css` and `shell.css`
 * because two of the rules here deliberately override the shared
 * hover-card chrome that the airport card uses too — see the scoping note
 * below, which is the single most important thing in this file.
 *
 * --- what the card is now ----------------------------------------
 *
 * The label-row card is gone and so is the mock-up's *two-line* flight
 * block. One occurrence is one aligned line in a seven-column table:
 *
 *   LHR - LCA     London Heathrow
 *                 Larnaca
 *
 *   YY 3030   1234567  321  09:35  15:53  local
 *   YY 3032   1234567  CS1  06:35  12:56  local
 *
 * The aircraft type moved up onto the flight's own line and its *name*
 * moved into the type code's `title`; the UTC pair moved into the two
 * time cells' `title`s; the airline name moved behind the flight number;
 * the ICAO pair moved behind the header. All four hovers are built in
 * `routecard.js` and painted by `featurecard.js::renderTable` — nothing
 * here invents a tooltip, this file only sizes and colours what arrives.
 *
 * The type is `--asrm-fs-table`, 9px: the smallest thing on the site,
 * and the only exception to the 11px readable floor. That exception is
 * declared in
 * `backend/tests/test_rail_order_and_autoselect.py::SUBFLOOR_EXCEPTIONS`
 * — not here — because a sub-floor size that no test knows about is not an
 * exception, it is a hole. Writing it as a token rather than as
 * `calc(var(--asrm-fs-body) * 0.703)` is what makes it visible to that
 * test: a `calc()` used to resolve to nothing at all there, so a
 * multiplied size in a scanned file was invisible to the rule.
 *
 * Because the mock-up carries no `pax` / `cargo` word, **this stylesheet
 * paints no kind colour at all** — not on the rows, not on a left
 * border, not as a dot. That is a SPEC §28.5 decision, not an omission.
 * The old card had a three-colour left bar *plus* the kind word in the
 * row text, which is the compliant combination. A coloured bar with no
 * word would be colour as the sole carrier of a pax/cargo/unknown
 * distinction, on the commonest colour-blind pair, and §28.5 forbids
 * exactly that. So the choice was: keep the word, or drop the hue. The
 * mock-up dropped the word, so the hue goes with it, and
 * `tools/check-route-detail.mjs` asserts that no rule in this file
 * references the traffic-kind tokens at all (checked rule-by-rule after
 * comments are stripped, so this sentence naming them is not what the
 * check reads).
 *
 * Traffic colour itself is still governed by tokens and still agrees with
 * `map.js::ROUTE_STYLE`; that agreement is asserted against those two
 * files by the checker and is not this file's business.
 *
 * --- scoping: why every rule here is anchored ----------------------
 *
 * `.asrm-hover-card__head`, `__title`, `__sub`, `__body`, `__row`, `__k`
 * and `__v` are **shared with the airport card**, and neither
 * `shell.css` nor `featurecard.js` is editable from this task. So:
 *
 *   - rules that touch the shared chrome are scoped with `:has()` to a
 *     card whose body actually contains a route flight table. That is the
 *     pattern `shell.css` already uses (`.asrm-intro:has(.asrm-intro__pick:target)`),
 *     and it degrades safely: a browser without `:has()` keeps the
 *     stacked head from `shell.css` — the names appear under the title
 *     instead of beside it, and everything is still readable.
 *   - rules that touch only route-owned classes (`.asrm-rd-*`) need no
 *     anchoring, because no other card emits them.
 *
 * Note what that second bullet buys on the *failure* card: a `loading…`
 * or `notfound` card carries `.asrm-rd-note` and no `.asrm-rd-table`, so
 * it keeps the plain stacked head and a single row. It looked like that
 * before this change and it looks like that after, without a rule being
 * written about it.
 *
 * No colour literal appears in this file. Every colour is a `var(--asrm-*)`
 * from `tokens.css`, and every size derives from a token too
 * (`--asrm-fs-body`, `--asrm-fs-value`, `--asrm-fs-table`), so a route
 * card can never introduce a hue or a type size the rest of the site has
 * not agreed to.
 */

/* --- the head: big codes, names stacked beside -------------------
 *
 * `routecard.js::routeHeader` sends the title as `LHR - LCA` and the
 * subtitle as the two names joined with `\n`, and this rule is what
 * turns that pair into a row rather than a column. The subtitle's
 * `white-space: pre-line` is load-bearing: `textContent` preserves the
 * newline character in the DOM but under `white-space: normal` it folds
 * to a space and both names run together on one line.
 *
 * `align-items: baseline` rather than `flex-start` so the top line of the
 * names sits on the same baseline as the big codes. With `flex-start` the
 * names float into the title's ascender space and the pair reads as
 * misaligned — which is more noticeable at a 1.55× size difference than
 * at equal sizes.
 *
 * The right-hand padding is for MapLibre's own close button
 * (`.asrm-fc-popup .maplibregl-popup-close-button`, painted by
 * `flightcard.css` at the top-right corner of every popup). The
 * subtitle is the one line long enough to reach under it.
 *
 * The title must never wrap: `LHR - LCA` is three characters, a space, a
 * hyphen, three characters, and a wrapped title in a flex row would drop
 * the names down beside the second line.
 *
 * The head keeps its `font-weight: 600` and its 1.55× size. The owner's
 * "no bold" ruling of 2026-10-04 was scoped to the table body — "Table
 * body only" — so the pair of airport codes that says what this card is
 * about stays emphatic while the flights under it go flat. That contrast
 * is the point: bold means *heading*, and if nothing is bold the head has
 * to be read to be found.
 *
 * **Stacked, not side by side (v1.87).** The names used to sit to the right
 * of the codes in a flex row. Measured against the live 40-row `SIN -> KUL`
 * payload, that shape cannot be narrow: the codes take 110px, a 12px gap,
 * and the two names on one line 218px, so the head alone is 340px while
 * the flight table underneath wants 204px. Every row then ends ~136px
 * before the card does. With the head as a block, the names drop under the
 * codes and the whole card hugs to 258px. This is also what the owner
 * asked for: "The airport names in the title on to rows ... They should be
 * on a single row."
 */
.asrm-hover-card:has(.asrm-rd-table) .asrm-hover-card__head {
  display: block;
  padding-right: 30px;
}

.asrm-hover-card:has(.asrm-rd-table) .asrm-hover-card__title {
  font-size: calc(var(--asrm-fs-value) * 1.55);
  font-weight: 600;
  line-height: 1.1;
  white-space: nowrap;
}

.asrm-hover-card:has(.asrm-rd-table) .asrm-hover-card__sub {
  margin-top: 2px;
  font-size: var(--asrm-fs-value);
  line-height: 1.25;
  white-space: nowrap;
}

/* --- the flight table --------------------------------------------
 *
 * Seven columns, laid out with the same mechanism the airport card's
 * 4-column grid already proved: the parent owns the `grid-template`,
 * each row is `display: contents` so its cells become the real grid
 * items and share the column tracks. That is the whole of what makes the
 * owner's "elements are vertically aligned" true — the dep time of row 9
 * starts at the same x as the dep time of row 1, because they are the
 * same grid track, not because the text happens to be the same length.
 *
 * `max-content` on the six fixed columns rather than `auto`: `auto` is
 * `minmax(min-content, max-content)` and will *shrink* a column when the
 * container gets tight, which under `white-space: nowrap` means the cells
 * overflow their track and sit on top of their neighbour. `max-content`
 * fixes each track at the widest value in it, so nothing moves and
 * nothing overlaps.
 *
 * The notes column is `minmax(0, max-content)`, not `minmax(0, 1fr)`
 * (v1.87). Under `1fr` it swallowed every pixel of slack the card had on
 * offer, which is how a column holding nothing on 80 of 80 rows of the live
 * `SIN -> KUL` pair became 142px wide — the whole of the owner's "a lot of
 * empty space on the right" was this one track, stretched to fill a floor
 * the *header* required while contributing no content to any row.
 * `max-content` as the maximum makes the track hug its prose and stop there.
 * The `0` floor is deliberately kept: it is what lets `via SIN, BKK` wrap
 * inside its own column instead of widening the table when the card really
 * does run out of room.
 *
 * Row spacing is `row-gap`, not a margin on the rows. Under
 * `display: contents` a row has no box, so a `margin-top` on it — which
 * is exactly what the old two-line block used (`.asrm-rd-equip +
 * .asrm-rd-flight`) — buys literally nothing. That is one of the two
 * ways a `display: contents` table silently loses its vertical rhythm;
 * the other is the short-row shift, which `flightTableRow` prevents by
 * building from the frozen column list.
 *
 * `align-items: baseline` keeps the clock times in the dep and arr
 * columns on one baseline down the table even where one cell carries the
 * `+1` marker and its neighbour does not.
 */
.asrm-rd-table {
  display: grid;
  /* Order mirrors `routecard.js::TABLE_COLUMNS`: flight, days, equip,
   * dep, arr, basis, notes. The two lists must stay in step, and
   * `check-route-detail.mjs` asserts this rule has exactly as many
   * column tracks as that list has entries, so a column added to one
   * and not the other goes red rather than shifting every row below it
   * by one cell. */
  grid-template-columns: max-content max-content max-content max-content max-content max-content minmax(0, max-content);
  column-gap: 8px;
  row-gap: 2px;
  align-items: baseline;
}

.asrm-rd-table .asrm-hover-card__tr {
  display: contents;
}

/* No floor on the route card, deliberately (v1.87 — this reverses v1.85).
 *
 * v1.85 put `min-width: min(340px, calc(100vw - 20px))` here to stop the
 * notes track collapsing to 0.41px at a forced 240px card width and
 * wrapping `via SIN, BKK` to 125.9px tall. That was the right diagnosis
 * and the wrong drug. The floor did hold the track up; it also made the
 * card 136px wider than its content on every real route, because 340px is
 * what the *header* needed while the flight table needs 204px. The owner
 * read the result correctly as a popup with a lot of empty space on the
 * right.
 *
 * What the floor was really for was never the width — it was insurance
 * against the card being narrower than its own columns. With the floor gone
 * the card hugs to 258px, so it is never narrower than 258px, and the
 * only thing that can push width down is MapLibre's own
 * `min(460px, 100vw - 20px)` cap. Measured: at a forced 300px the notes
 * track keeps its 51.25px and every `via` row stays one line high. The
 * collapse does come back below the card's own content width — at a forced
 * 240px the two-hop `via` track falls to 2.45px and wraps to 10 lines, at
 * 204px to 0px and 10 lines — which is the same failure v1.85 measured,
 * not a new one. It is left unfixed on purpose because the widths that
 * cause it are unreachable in the product: a 240px card needs
 * `100vw - 20px <= 240`, i.e. a viewport of 260px or less, and there is
 * no such device. Restoring a 340px floor to prevent it would buy back
 * 136px of empty card to guard a case nobody can reach.
 *
 * If a future change ever sizes these popups to a fixed or fitted width
 * rather than letting them hug, this comment is the thing to revisit: the
 * fix is a floor on the *table* equal to its own max-content, not a floor
 * on the card. */

/* Every cell at 9px, flat, tabular, and never lower-cased.
 *
 * `font-weight: 400` is written out rather than left to the cascade
 * because the shared hover-card chrome is full of 600-weight labels —
 * `__title`, `__pairs .__v`, `__unit` nearby — and the owner's ruling
 * was that nothing in this table is emphatic. An inherited 400 is an
 * assumption; this is a statement, and `check-route-detail.mjs` asserts
 * it is here.
 *
 * `text-transform: none` kills the shared `__k` rule's
 * `text-transform: lowercase`. The table does not use `__k` — but the
 * cell that holds `YY 3030` must never, ever render `yy 3030`: the
 * airline code is data, not prose, and lowercasing it is a corruption of
 * the flight the visitor clicked. The old card needed this override to
 * survive its own label column; the table needs it as a guard against a
 * future shared rule reaching in. Cheap, and the failure it prevents is
 * total.
 *
 * `tabular-nums` on every cell, not only the time cells, so that
 * `1008` and `3030` occupy the same digit widths as `08:45` does and
 * the columns hold their edges. */
.asrm-rd-table .asrm-hover-card__cell {
  font-size: var(--asrm-fs-table);
  font-weight: 400;
  line-height: 1.4;
  color: var(--asrm-fg);
  font-variant-numeric: tabular-nums;
  text-transform: none;
  white-space: nowrap;
}

/* The last two columns describe the row rather than fly it.
 *
 * `local` / `hub` is the basis of the two times to its left, and `via …`
 * is a qualification of the routing the row is standing for. Neither is a
 * fact about the flight in the same sense as the number, the equipment or
 * the clock, so both sit in the muted colour — which in this palette is
 * `#e8eaed` against a `#222` panel at 13.2:1, so "muted" costs nothing
 * legible at 9px. It buys the row a reading order instead of seven
 * equally-loud columns.
 *
 * The notes column is also the only one allowed to wrap: it is prose
 * (`via SIN, BKK`), it is the `minmax(0, 1fr)` column, and a wrapped
 * note costs one line on one row where a wrapped time would break the
 * column. */
.asrm-rd-table .asrm-hover-card__cell:nth-child(6),
.asrm-rd-table .asrm-hover-card__cell:nth-child(7) {
  color: var(--asrm-fg-muted);
}

.asrm-rd-table .asrm-hover-card__cell:last-child {
  white-space: normal;
  overflow-wrap: anywhere;
}

/* --- the status line (every non-ok state) ------------------------
 *
 * Re-homed here from `shell.css` together with the rest of the route
 * card's styling. It reports on *our request*, not on the route, so it
 * must not look like one more data row: italic and muted put it in the
 * card's own voice, and a warning colour is reserved for the states that
 * genuinely went wrong, so a visitor waiting on `loading…` does not read
 * a failure that has not happened.
 *
 * It stays at `--asrm-fs-body` (12.8px) rather than coming down to the
 * table's sub-floor size — and one notch above the `--asrm-fs-title` it
 * carried until the owner's 2026-10-05 pass ("Increase the font size on
 * the route pop-up by one notch"). The floor exception is granted to the
 * *flight grid*, which earns it by repeating a known shape forty times;
 * a single sentence of status prose does not earn it, and a status line
 * the visitor cannot read is worse than a long card.
 * `test_no_subfloor_token_is_used_outside_the_file_granted_it`
 * is what keeps that distinction enforced rather than just intended.
 *
 * The four kinds of "no" stay distinguishable by word, not by hue — the
 * wording is what `check-route-detail.mjs` asserts, because a stylesheet
 * can take the colour and cannot take the word. The colour carries
 * *severity* on top of that, which is a second and independent message.
 */
.asrm-rd-note {
  font-style: italic;
  font-size: var(--asrm-fs-body);
}

.asrm-rd-note.is-rd-loading,
.asrm-rd-note.is-rd-empty,
.asrm-rd-note.is-rd-unknown {
  color: var(--asrm-fg-muted);
}

/* A 400 means we asked something wrong — louder than the neutral states
 * and deliberately distinct from a 404's quiet "we don't know this",
 * because the two need different follow-up from whoever reads the card. */
.asrm-rd-note.is-rd-badreq {
  color: var(--asrm-warn);
}

.asrm-rd-note.is-rd-failed {
  color: var(--asrm-danger);
}

/* --- the truncation line, last on the card -----------------------
 *
 * Re-homed with the rest. Set off by a rule rather than only by colour
 * because it qualifies everything above it — it is the one row the
 * visitor must not skim past, and the mock-up has two flights on it and
 * therefore no version of this line at all.
 *
 * At body size, not table size: like the status line, it is a sentence
 * about the list rather than a row of it, and the one line that must not
 * be skimmed is a poor candidate for the smallest type on the page.
 *
 * It says `showing 40 of 367` and nothing else. Not "the 40 most
 * frequent": in the worst real pair 156 rows tie at the top frequency
 * against a cap of 40, so a ranking claim would be false. See
 * `routecard.js::truncationText`.
 */
.asrm-rd-trunc {
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px dashed var(--asrm-line);
  color: var(--asrm-warn);
  font-size: var(--asrm-fs-body);
  font-variant-numeric: tabular-nums;
}

/* --- Stage B: the reverse direction (v1.86) ---------------------
 *
 * The owner's fourth bullet: the card showed one direction and *"it's a
 * gamble which side you see"*. The reverse is a second request whose
 * answer is appended under its own heading by
 * `featurecard.js::requestReverse`, and almost nothing has to be styled
 * here — which is the point. `.asrm-rd-reverse` is also
 * `.asrm-rd-table`, so it inherits the seven-column grid, the 9px
 * tabular cells, the muted last two columns and the card's width floor
 * unchanged. Two directions of the same question should be the same
 * table; if they were different sizes or different columns, the reader
 * would have to work out which is which instead of just reading it.
 *
 * The separating rule comes free from the shared chrome
 * (`.asrm-hover-card__section + .asrm-hover-card__section` in
 * `shell.css`), and the heading is the shared uppercase
 * `--asrm-fs-title` label treatment every other card section gets. The
 * heading text is the opposite airport pair — `KUL - SIN` under a card
 * headed `SIN - KUL` — and its hover carries the reversed ICAO pair, so
 * the codes that were behind the forward hover appear swapped here rather
 * than absent.
 *
 * What does need a rule is the case with no table at all.
 */

/* The return heading is the same size as the outbound one (v1.87).
 *
 * The owner's ruling: "the return IATA code pair should be the same size
 * as the header". The shared `__section-title` in `shell.css` is a small
 * uppercase label, which is right for a group label inside a card and
 * wrong here — `KUL - SIN` is not a label on a group of rows, it is the
 * title of the second half of the card and it is read as a route pair. A
 * visitor comparing the two directions should not have to re-scale
 * between them, so the pair gets the head's exact computed size rather
 * than a token that happens to be close: `calc(var(--asrm-fs-value) *
 * 1.55)` is the same expression `__title` above uses, so the two can
 * never drift apart when the token moves.
 *
 * `text-transform: none` and `letter-spacing: 0` undo the shared label
 * treatment. Uppercasing a route pair is the same corruption the table
 * guards against at the cell level — the codes are data, and `KUL - SIN`
 * set in caps and tracked out is a different string from the one the wire
 * sent. Emphasis stays on weight, not on case.
 *
 * This rule is scoped to `:has(.asrm-rd-table)` because a route card is
 * the only card that pairs a heading with a flight table, and targeting the
 * class bare would reach into any future non-route card that reused it.
 *
 * It then **excludes `.asrm-rd-detail`**, the two field-list sections
 * `Remarks` and `Validity` added on 2026-10-04. Before those existed the
 * return pair was the only heading a route card ever rendered, so the
 * selector could afford to mean "the heading in here". Now there are
 * three, and a `REMARKS` label set at 23.56px directly under `SIN - KUL`
 * reads as a second route pair rather than as what it is — a label over a
 * list. Excluded sections keep the shared `__section-title` treatment from
 * `shell.css`: small, uppercase, tracked, muted, the same voice as
 * `STATISTICS` on the airport card. */
.asrm-hover-card:has(.asrm-rd-table)
  .asrm-hover-card__section:not(.asrm-rd-detail)
  .asrm-hover-card__section-title {
  font-size: calc(var(--asrm-fs-value) * 1.55);
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.1;
}

/* The quiet line: the reverse asked and had nothing to show.
 *
 * This is the card speaking, not a row of data, so it takes the same
 * voice as the status line above — italic, `--asrm-fs-body`, muted (one
 * notch up from `--asrm-fs-title` with the rest of this popup's prose,
 * owner 2026-10-05). It deliberately does **not** take the table's
 * sub-floor size: the floor exception is granted to the flight grid
 * because that grid earns it by repeating one known shape forty times,
 * and a single sentence of prose does not earn it.
 *
 * The owner ruled on 2026-10-04 that the reverse must never surface as an
 * error, so **no warning or danger hue appears on either modifier**. The
 * two cases the card knows how to tell apart stay distinct **by word
 * alone** — `no scheduled service this way` is a fact about the route,
 * `reverse direction unavailable` is a fact about us — which is the same
 * rule §28.5 already enforces on the kind colours: where a distinction
 * carries meaning, the word carries it, not a hue that some visitors
 * cannot separate.
 *
 * Colour is set on the spans as well as the row because the shared
 * `__v` carries its own colour from `shell.css`; an override that stops
 * at the row would be out-specifed by it and leave one half of the line
 * loud. */
.asrm-rd-quiet,
.asrm-rd-quiet .asrm-hover-card__k,
.asrm-rd-quiet .asrm-hover-card__v {
  font-style: italic;
  font-size: var(--asrm-fs-body);
  color: var(--asrm-fg-muted);
  font-weight: 400;
}

/* --- motion ----------------------------------------------------
 *
 * Nothing in this file animates, and this restates that here rather than
 * relying on the absence being noticed later. The card replaces itself in
 * place — `loading…` becomes the answered table — and a cross-fade
 * around a list the visitor is already reading would move the text under
 * their eyes for no reason.
 *
 * This is the LAST `prefers-reduced-motion` block for these classes, in
 * this file, which is loaded last. `check-route-detail.mjs` asserts the
 * stronger property directly against this file: no `transition` or
 * `animation` declaration appears in any `.asrm-rd-*` rule outside this
 * block. A CSS guard alone could be out-specificed by a future
 * `.asrm-rd-x.asrm-rd-y { transition: … }`; the source scan cannot.
 */
@media (prefers-reduced-motion: reduce) {
  .asrm-rd-note,
  .asrm-rd-table,
  .asrm-rd-table .asrm-hover-card__cell,
  .asrm-rd-quiet,
  .asrm-rd-trunc {
    transition: none;
    animation: none;
  }
}
