/* Ported from docs/backlog/v1-core-loop/comps/base.css + b-editorial.html
   (the calibration exemplar: comps/shots/b-editorial-390x844.png).
   Type scale, spacing and colour values are the comp's, unchanged.

   A printed timetable page: one column measure, one scale ladder,
   letterspaced small-caps labels, one heavy rule under the masthead,
   hairlines below it, colour once per screen as accent. System stack only —
   no webfont may sit between a cold open and the answer. */

:root {
  --bg:       #0A0B0D;
  --ink:      #F4F5F7;                 /* 17.6:1 on --bg */
  --ink-2:    rgba(244,245,247,0.66);  /* ~8:1   secondary reads */
  --ink-3:    rgba(244,245,247,0.46);  /* ~4.3:1 labels only */
  --rule:     rgba(244,245,247,0.10);  /* 1.24:1 hairline */
  --rule-2:   rgba(244,245,247,0.20);  /* 1.73:1 */
  --warn:     #FF7A5C;                 /* delay / cancellation. 7.4:1 */
  --live:     #4ADE80;

  /* TfNSW line colours, as badge accents. The board paints var(--line-XX);
     web/js/lines.js maps the API's badge code to the variable and
     web/test/theme.test.js fails if the two lists ever disagree. */
  --line-T1: #F99D1C; --line-T2: #0098CD; --line-T3: #F37021; --line-T4: #005AA3;
  --line-T5: #C4258F; --line-T7: #6F818E; --line-T8: #00954C; --line-T9: #D11F2F;
  --line-M1: #168388;
  --line-BMT: #F99D1C; --line-CCN: #D11F2F; --line-SCO: #0098CD;
  --line-SHL: #00954C; --line-HUN: #833134;

  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* ---- light scheme (owner ruling 2026-09-01 C) --------------------------- */

/* Not an inversion — a second printing of the same page. The dark palette's
   CONTRAST STRUCTURE is what carries over, and dark ink on paper does not
   reach it at the same alphas as light ink on black: ink-2 needs 0.75 here
   where it needs 0.66 there, ink-3 needs 0.60 against 0.46. Every value below
   was measured (WCAG relative luminance), not eyeballed; the ratios are in
   docs/STYLES.md beside the reasoning.

   The ground is warm paper (#FAF9F5), not #FFF: a language made entirely of
   hairlines and 250-weight numerals glares off pure white, and the exemplar's
   claim is a printed timetable page, which is never white either. */
@media (prefers-color-scheme: light) {
  :root {
    --bg:     #FAF9F5;                 /* warm paper */
    --ink:    #14120E;                 /* 17.76:1 on --bg */
    --ink-2:  rgba(20,18,14,0.75);     /* #4E4C48, 8.13:1 */
    --ink-3:  rgba(20,18,14,0.60);     /* #706E6A, 4.83:1 — labels clear 4.5 */
    --rule:   rgba(20,18,14,0.11);     /* 1.25:1 — the dark hairline's weight */
    --rule-2: rgba(20,18,14,0.25);     /* 1.74:1 */
    --warn:   #BF3418;                 /* 5.36:1. Same hue as #FF7A5C (10° vs
                                          11°), which is 2.3:1 here and unusable */
    --live:   #0F7A4A;                 /* 5.11:1 */

    /* Darkened per line, hue and saturation held, lightness dropped only until
       the badge clears 4.5:1 as text. Four lines were already dark enough to
       print unchanged. Measured ratios in docs/STYLES.md. */
    --line-T1: #A46204; /* 4.61 */  --line-T2: #0079A3; /* 4.68 */
    --line-T3: #BD4D0A; /* 4.71 */  --line-T4: #005AA3; /* 6.66, unchanged */
    --line-T5: #C4258F; /* 4.97, unchanged */
    --line-T7: #62727E; /* 4.72 */  --line-T8: #008041; /* 4.79 */
    --line-T9: #D11F2F; /* 5.05, unchanged */
    --line-M1: #157B7F; /* 4.78 */
    --line-BMT: #A46204; --line-CCN: #D11F2F; --line-SCO: #0079A3;
    --line-SHL: #008041; --line-HUN: #833134; /* 8.12, unchanged */
  }

  /* Antialiasing is the one rule that must NOT carry over. `antialiased`
     stops light-on-dark type fattening; applied to dark-on-light it thins it,
     and this page is set in 250- and 300-weight. The default smoothing is
     what makes the light board weigh the same as the dark one. */
  html, body { -webkit-font-smoothing: auto; }
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "ss01" 1;
}

body { display: flex; flex-direction: column; overflow: hidden; }

/* A single nowrap string (a long line name + headsign) otherwise inflates the
   flex line's cross size and shoves right-aligned content past the viewport
   clip — which looks like missing content rather than overflow. */
body > *, #app > * { width: 100%; min-width: 0; max-width: 100%; }

#app { display: flex; flex-direction: column; flex: 1; min-height: 0; }

input, button { font: inherit; color: inherit; background: none; border: 0; }
button { cursor: pointer; }

/* One label idiom for the whole system: letterspaced small caps. Nothing is
   ever sentence-case and small at the same time. */
.lbl {
  font-size: 10px; font-weight: 600; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-3);
}

/* Freshness: a hairline, never a spinner. */
.pulse { width: 5px; height: 5px; border-radius: 50%; background: var(--ink-3); flex: none; }
.pulse.live { background: var(--live); }
.pulse.stale { background: var(--warn); }

/* ---- masthead ---------------------------------------------------------- */

.mast { padding: 26px 22px 0; }
.mast .kicker { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.mast h1 {
  font-size: 29px; font-weight: 300; letter-spacing: -0.025em; line-height: 1.05;
  color: var(--ink);
}
.mast h1 em { font-style: normal; color: var(--ink-3); font-weight: 200; padding: 0 2px; }
.mast .tools { display: flex; gap: 20px; margin-top: 14px; padding-bottom: 14px; }
.mast .tools button {
  padding: 0; font-size: 10px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
}
.mast .tools button:active { color: var(--ink); }
.mast .rule { height: 2px; background: var(--ink); opacity: .82; }
.mast .lede {
  margin-top: 12px; font-size: 14px; font-weight: 300; line-height: 1.5;
  color: var(--ink-2);
}

/* ---- departure board --------------------------------------------------- */

.rows { flex: 1; overflow: hidden; padding: 0 22px; display: flex; flex-direction: column; }

.row {
  display: grid; grid-template-columns: 86px 1fr;
  gap: 0 16px; align-items: start; align-content: center;
  padding: 20px 0 21px 12px;
  border-bottom: 1px solid var(--rule);
  position: relative;
  transition: opacity .24s ease;
}
/* Fewer than six services distribute down the frame rather than leaving a
   short list floating in a void. */
.rows.sparse .row { flex: 1 1 auto; }
.row.gone { opacity: 0; }

/* Colour is an accent, so it marks the one service you are actually about to
   catch. The rest name their line in coloured small caps. */
.row.first::before {
  content: ''; position: absolute; left: 0; top: 26px; bottom: 27px;
  width: 2px; background: var(--stem);
}
.mins {
  font-size: 54px; font-weight: 250; letter-spacing: -0.045em; line-height: .84;
  color: var(--ink); text-align: right; padding-top: 2px;
}
.mins .prov {
  font-size: 10px; font-weight: 600; letter-spacing: .13em; color: var(--ink-3);
  display: block; margin-top: 9px; text-transform: uppercase;
}
.mins .prov.warn { color: var(--warn); }
/* The hours figure's unit (ruling B): the numeral keeps the headline size and
   the H is set small beside it, on the same baseline, in the figure's own
   colour. Flat at the hero size "3H" is 91px in an 86px column. */
.mins .unit { font-size: .40em; font-weight: 300; letter-spacing: 0; padding-left: .04em; }

.body { min-width: 0; }
/* Departure and arrival sit on one line because they are the pair you
   actually compare; the meta line is then free to carry platform and line. */
.dep {
  font-size: 20px; font-weight: 400; letter-spacing: -0.01em; color: var(--ink);
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
}
.dep del { color: var(--ink-3); font-size: 15px; font-weight: 300; }
.dep .to { color: var(--ink-3); font-size: 15px; font-weight: 300; letter-spacing: 0; }
.meta {
  margin-top: 9px; font-size: 10.5px; font-weight: 600; letter-spacing: .15em;
  text-transform: uppercase; color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.meta b { color: var(--ink-2); font-weight: 600; }
.meta i { font-style: normal; color: var(--stem); font-weight: 700; }
.row.cx .meta i { color: var(--ink-3); }
.dest {
  margin-top: 7px; font-size: 14px; font-weight: 300; letter-spacing: -0.005em;
  line-height: 19px; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The cancelled-lead note takes the third line's slot rather than adding a
   fourth, so the row is three lines in every state. Same line box height.

   Full label idiom, at every width (owner ruling 2026-09-01 A). The stopgap it
   replaces set this one line at 9px/.05em — the only type on the board outside
   the ladder — to fit the longer copy "…NEXT RUNNING SERVICE". The ruling
   shortened the copy instead: "22:48 CANCELLED · NEXT TRAIN" measures 205px at
   10px/.14em, against a 210px body column on the narrowest phone we support. */
.dest.note {
  font-size: 10px; font-weight: 600; letter-spacing: .14em; line-height: 19px;
  text-transform: uppercase; color: var(--warn);
}

.row.first .mins { font-size: 78px; }
.row.first { padding-top: 24px; padding-bottom: 26px; }
.row.first .dep { font-size: 23px; }

/* A three-character figure is wider than the column at the headline size:
   measured 129px against 86px on 2026-09-01, which ran the hero figure
   straight through the departure time beside it. It steps down one size rather
   than being clipped or allowed to eat the body column.

   Ruling B retired the case that found this ("187" is "3H" now), but not the
   rule: "Now" is three characters, and so is a service that rounds to ten
   hours or more. Reachable, so kept — and the in-browser figure-overflow
   invariant would catch it going the other way. */
.row.wide .mins { font-size: 44px; }
.row.first.wide .mins { font-size: 48px; }

.row.late .mins, .row.late .dep > strong { color: var(--warn); }
.row.sched .mins { color: var(--ink-2); font-weight: 200; }
.row.sched .mins .prov { color: var(--ink-3); }
/* No figure, because there is no wait — but the slot keeps its place in the
   ladder so the eye still counts six services. */
.row.cx .mins { color: var(--ink-3); font-weight: 200; font-size: 34px; }
.row.first.cx .mins { font-size: 34px; }
.row.cx .dep, .row.cx .dest { color: var(--ink-3); }
.row.cx .dep strong { text-decoration: line-through; text-decoration-thickness: 1.5px; font-weight: 400; }
.row.cx::before { background: var(--ink-3) !important; }

/* Stale board (owner ruling): a countdown computed from an old cache is a lie,
   a clock time is not. The figure slot empties but keeps its height, so the
   board reads as "the figures are gone", not as a different layout. */
.rows.stale .mins { min-height: 66px; }
.rows.stale .row.first .mins { min-height: 87px; }
/* The dimming belongs to the ROWS, not to the container. On the container it
   also dimmed the one-line hint that stands in for them ("no board saved for
   this trip yet") — and 55% of the coral over paper is 2.6:1, a washed salmon
   (found in light-cold-offline, 2026-09-01). Nothing is more load-bearing than
   the only sentence on the screen. */
.rows.stale .row { opacity: .55; }
/* ...and a row on its way out still goes all the way out. */
.rows .row.gone { opacity: 0; }

.ftr {
  display: flex; align-items: center; gap: 8px; padding: 15px 22px 18px;
  font-size: 10px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-3);
}
.ftr.offline { color: var(--warn); }

/* ---- setup, search, trips ---------------------------------------------- */

.sheet {
  flex: 1; min-height: 0; overflow-y: auto; padding: 0 22px;
  display: flex; flex-direction: column; -webkit-overflow-scrolling: touch;
}

.field { padding: 18px 0 0; border-bottom: 1px solid var(--rule); }
.field .lbl { display: block; margin-bottom: 8px; }
.field input {
  width: 100%; padding: 0 0 14px; font-size: 20px; font-weight: 400;
  letter-spacing: -0.01em; color: var(--ink);
}
.field input::placeholder { color: var(--ink-3); font-weight: 300; }
.field input:focus { outline: none; }
/* type=search brings its own clear button: a heavy white glyph that is the
   loudest thing on a page whose whole language is hairlines. The field is
   cleared by picking a station or by the keyboard. */
.field input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.field.done { color: var(--ink); }

.results { padding-bottom: 8px; }
.results button {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  width: 100%; text-align: left; padding: 15px 0 16px;
  border-bottom: 1px solid var(--rule);
}
.results button span:first-child {
  font-size: 17px; font-weight: 300; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.results button:active span:first-child { color: var(--ink-2); }

.hint { padding: 16px 0; font-size: 10px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3); }
.hint.warn { color: var(--warn); }

/* The action sits on the bottom edge, where the board's footer sits, so the
   space between the fields and it belongs to the search results. */
.act { margin-top: auto; padding: 16px 0 18px; border-top: 2px solid var(--ink); flex: none; }
.act button {
  font-size: 12px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink); padding: 4px 0;
}
.act button[disabled] { color: var(--ink-3); cursor: default; }

.trip {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 18px 0 19px; border-bottom: 1px solid var(--rule);
}
.trip .name {
  font-size: 20px; font-weight: 300; letter-spacing: -0.015em; color: var(--ink);
  text-align: left; flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.trip .name em { font-style: normal; color: var(--ink-3); font-weight: 200; padding: 0 2px; }
.trip.current .name { color: var(--ink); }
/* A non-breaking space: the ordinary one collapses against the ellipsis
   clamp and the dot lands touching the station name. */
.trip.current .name::after { content: '\00a0\00a0●'; color: var(--live); font-size: 8px; vertical-align: middle; }
.trip .acts { display: flex; gap: 14px; flex: none; }
.trip .acts button {
  font-size: 10px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3);
}
.trip .acts button[disabled] { opacity: .3; cursor: default; }
.trip .acts button.del:active { color: var(--warn); }

/* The narrow phone (360px) gets a narrower page margin, not smaller type: the
   type ladder and the label idiom are the design, the margin is the sheet it
   is printed on. 22px of margin at 390px is 18px at 360px in the same
   proportion, and those 8px are what let the cancelled-lead note print whole
   at the full idiom (205px of copy in a 210px column, measured). */
@media (max-width: 375px) {
  .mast { padding-left: 18px; padding-right: 18px; }
  .rows, .sheet { padding-left: 18px; padding-right: 18px; }
  .ftr { padding-left: 18px; padding-right: 18px; }
}

@media (min-width: 900px) {
  /* A wider screen must show MORE of the board, not less: all six services
     stay above the fold, and the empty margin reads as a printed page rather
     than as content that ran out.

     Scoped through #app because the overflow guard above (`#app > *`) carries
     an ID and so out-specifies a bare class: `max-width: 100%` was quietly
     beating `max-width: 940px`, and every desktop shot ran the full 1280px
     measure. Verified 2026-09-01 by measuring .rows, not by reading the CSS. */
  #app > .mast, #app > .rows, #app > .sheet, #app > .ftr {
    max-width: 940px; margin: 0 auto;
  }
  .mast { padding: 26px 64px 0; }
  .mast h1 { font-size: 36px; }
  .mast .tools { margin-top: 12px; padding-bottom: 12px; }
  .rows, .sheet { padding: 0 64px; width: 100%; }
  .row { grid-template-columns: 132px 1fr; padding: 12px 0 13px 20px; }
  .mins { font-size: 58px; }
  .row.first { padding-top: 18px; padding-bottom: 19px; }
  .row.first .mins { font-size: 76px; }
  /* The 132px column takes three characters at full size, so the phone's
     step-down would only make a wide desktop board smaller for nothing. */
  .row.wide .mins { font-size: 58px; }
  .row.first.wide .mins { font-size: 76px; }
  .dep { font-size: 23px; }
  .row.first .dep { font-size: 25px; }
  .dest { font-size: 15px; }
  .ftr { padding: 16px 64px 22px; width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .row { transition: none; }
}
