:root {
  --ink: #16232B;
  --muted: #5C7180;
  --faint: #8B9AA5;
  --paper: #FAFAF8;
  --hair: #D4DDE3;
  --hair-soft: #E7EDF1;
  --accent: #8CBF70;
  --wash: #F2F7EC;
  --free: #4E7A38;
  --free-hair: #C6DEB4;
  --warn: #A5701F;
  --warn-wash: #FBF2E2;
  --warn-hair: #EBD5AC;
  --stop: #A8483A;
  --stop-wash: #FAEBE8;
  --stop-hair: #EEC9C1;
}

/* An author `display` rule outranks the browser's `[hidden] { display: none }`
   by construction, so the only way for `hidden` to keep meaning hidden is to
   put it out of reach: `.list { display: grid }` below would otherwise leave a
   stale list on screen under every empty-state message. */
[hidden] { display: none !important; }

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: 1.5rem clamp(1rem, 4vw, 2.5rem) 4rem;
  background: var(--paper);
  color: var(--ink);
  font-family: "Avenir Next", Futura, "Segoe UI", system-ui, sans-serif;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* `.state` is not here: it lives inside `.cols`, which carries the cap for it.
   Repeating the auto margins on a grid item would be worse than redundant --
   they absorb the free space in the track and turn off the stretch the item
   would otherwise get, so the message's box would quietly stop being the width
   of the column it stands in. */
.head, .cols { max-width: 68rem; margin-left: auto; margin-right: auto; }

.head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--hair);
}

h1 { margin: 0; font-size: 1.45rem; font-weight: 600; letter-spacing: -0.015em; }

.tally {
  margin: 0;
  font-size: 0.85rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.cols { display: grid; gap: 1.75rem; padding-top: 1.1rem; }

@media (min-width: 58rem) {
  .cols { grid-template-columns: 16.5rem 1fr; }
  .fbar { display: none; }

  /* The message replaces the list, so it stands in the list's cell rather than
     flowing to a row of its own beneath a filter column a screen tall. `#list`
     is hidden whenever this shows, so the two never contend for the cell --
     and naming the cell means that if [hidden] ever stopped meaning hidden
     they would overlap loudly instead of the message quietly dropping out of
     sight, which is the failure this stylesheet has shipped once already. */
  .cols > .state {
    grid-row: 1;
    grid-column: 2;
    align-self: start;
    padding-top: 0;
    /* A <p> carries a 1em margin from the browser, and grid items do not
       collapse margins -- left alone it drops the message 16px below the
       panel it is supposed to stand level with. */
    margin-top: 0;
  }

  /* Three messages arrive with no filter column beside them: the boot failure,
     an empty payload, and the "Loading…" the HTML ships with. Pinned to the
     second column each would be a sentence indented past 16.5rem of nothing.
     `~` and not `+`, because #list sits between the two; the selector reads
     the attribute, which an element generating no box still carries. */
  #filters[hidden] ~ .state {
    grid-column: 1 / -1;
    padding-top: 3rem;
  }
}

.state { padding: 3rem 0; text-align: center; color: var(--muted); }

/* filters */

.filters {
  display: grid;
  gap: 1rem;
  align-content: start;
  font-size: 0.82rem;
  border: 0;
  margin: 0;
  padding: 0;
}

/* Placed after the reset above rather than beside the grid in the layout
   section: a media query grants no extra specificity, so `.filters { padding: 0 }`
   up there wins on source order alone and a gutter declared before it would
   silently do nothing -- which is exactly how the scrollbar came to be painting
   over the calendar.

   Sticky keeps the panel in place; the height and the scroll keep it reachable.
   Two calendars stand taller than most windows, and a sticky column taller than
   the viewport pins its top and hides its bottom: the price, the airport and
   the passport could then only be read by scrolling the page, which drags the
   list away to reach a control that never moved. */
@media (min-width: 58rem) {
  .filters {
    position: sticky;
    top: 1rem;
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    /* The panel's scroll is its own. Chained to the document, reaching the
       bottom of the calendar carries straight on into the page and drags the
       list away from under whoever was reading it. */
    overscroll-behavior: contain;
    /* Where the platform draws an overlay scrollbar -- macOS by default -- it
       takes no width and paints over what it scrolls, which here means over
       Saturday and Sunday in every calendar. Nothing in CSS makes it reserve
       space: overlay scrollbars are exempt from scrollbar-gutter, and
       measuring one in Chromium 148 confirms all four ways of asking. So the
       gutter is ours to keep, and 1rem clears the widest state the bar
       expands to. The two properties below only ask for a narrower bar in the
       palette's grey wherever the platform lays a real one out. */
    scrollbar-width: thin;
    scrollbar-color: var(--hair) transparent;
    padding-right: 1rem;
  }
}

/* Everything in the panel except the `Country` group, which app.js keeps out
   of it so that a country switch can replace this whole subtree without
   touching the radio the visitor is standing on. `contents` because the
   boundary is wanted in the DOM and not in the layout: the children below
   stay rows of the filter grid, at its gap, exactly as they were when they
   were `#filters`' own. */
.panel { display: contents; }

.filters label { display: block; color: var(--muted); }
.filters label b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }

/* The price slider: the one range input the panel holds inside a label of its
   own, where a leg's two sliders sit in a `.range` track beside their label
   rather than in it. This read `.filters > label` until the country switch
   put a panel between those two, and it still spells both steps out --
   `.panel input[type="range"]` would take the leg sliders with it. */
.panel > label > input[type="range"] {
  display: block;
  width: 100%;
  margin: 0.4rem 0 0;
  accent-color: var(--accent);
}

.fbar {
  width: 100%;
  padding: 0.45rem 0.7rem;
  font: inherit;
  font-size: 0.85rem;
  text-align: left;
  color: var(--ink);
  background: #FFFFFF;
  border: 1px solid var(--hair);
  border-radius: 3px;
  cursor: pointer;
}

@media (max-width: 57.99rem) {
  .filters:not(.open) { display: none; }
  .cols { grid-template-columns: 1fr; }
}

#all, .cal button {
  font: inherit;
  font-size: 0.78rem;
  color: var(--ink);
  background: #FFFFFF;
  border: 1px solid var(--hair);
  border-radius: 3px;
  cursor: pointer;
}

#all { width: 100%; padding: 0.35rem 0; }

#all.active, .cal button.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
  font-weight: 600;
}

.cal-month { margin: 0.6rem 0 0.3rem; font-size: 0.78rem; font-weight: 600; }

.cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }

.cal button { padding: 0.22rem 0; font-variant-numeric: tabular-nums; }

.cal button:disabled {
  color: var(--faint);
  background: transparent;
  border-color: transparent;
  cursor: default;
}

.cal .wd, .cal .gap {
  padding: 0.15rem 0;
  font-size: 0.64rem;
  color: var(--muted);
  text-align: center;
}

/* Two stacked native ranges make one dual-handle slider: the inputs ignore the
   pointer, only their thumbs catch it. */

.range { position: relative; height: 22px; margin-top: 0.4rem; }

.range .track, .range .fill {
  position: absolute;
  top: 8.5px;
  height: 5px;
  border-radius: 3px;
}

.range .track { left: 0; right: 0; background: var(--hair); }
.range .fill { background: var(--accent); }

.range input[type="range"] {
  position: absolute;
  inset: 0;
  width: 100%;
  margin: 0;
  background: none;
  pointer-events: none;
  -webkit-appearance: none;
  appearance: none;
}

.range input[type="range"]::-webkit-slider-runnable-track {
  height: 22px;
  background: none;
}

.range input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 15px;
  height: 15px;
  /* 15px content + 2px border = a 19px box; 1.5px of margin puts its centre at
     11px -- the middle of the 22px container and of the track. */
  margin-top: 1.5px;
  border-radius: 50%;
  background: #FFFFFF;
  border: 2px solid var(--accent);
  pointer-events: auto;
  cursor: pointer;
}

.range input[type="range"]::-moz-range-thumb {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #FFFFFF;
  border: 2px solid var(--accent);
  pointer-events: auto;
  cursor: pointer;
}

/* The caption above an unlabelled radio group -- origin, passport, visa --
   matches the label style Price, Departure and Arrival already use; only the
   bottom margin is new, to separate the caption text from the row of radios
   beneath it, the way an input already separates a value from its label. */
.group-caption { margin: 0 0 0.35rem; }

/* The one-airport case: `Airport` still names the departure city, in the
   same colour a chosen radio's label would carry, but as plain text -- there
   is nothing to choose, so nothing here is a control. */
.airport-name { margin: 0; color: var(--ink); }

/* Named for the shape, not the control: two groups already share the row and
   a third would too, so a name like `.origins` on the passport radios would
   say something untrue about them. A row for options that are one word each,
   a column for "Without a consulate" and "Fully visa-free", which are not. */
.radio-row { display: flex; gap: 0.9rem; flex-wrap: wrap; }
.radio-row label { display: flex; align-items: center; gap: 0.3rem; color: var(--ink); }
.radio-row input { accent-color: var(--accent); margin: 0; }

.radio-column { display: grid; gap: 0.28rem; }
.radio-column label { display: flex; align-items: center; gap: 0.34rem; color: var(--ink); }
.radio-column input { accent-color: var(--accent); margin: 0; }
.note-line { margin: 0.2rem 0 0; font-size: 0.72rem; color: var(--faint); }

/* What a failed country switch says, beside the control that asked for it.
   Not the faint grey of the note it borrows its size from: this one is a
   failure, and it is empty whenever there is nothing to report -- `:empty`
   rather than `hidden`, so app.js has one string to set and no second state
   to keep in step with it. */
.country-note { color: var(--stop); }
.country-note:empty { display: none; }

/* Four time sliders in a flat column are four unlabelled sliders. The rule
   groups each leg under its own name, and the accent edge is what makes the
   return block read as belonging to the checkbox above it. */
.leg {
  border-left: 2px solid var(--accent);
  padding-left: 0.7rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.leg-name {
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: -0.005em;
}

.horizon-note { margin: 0; font-size: 0.72rem; color: var(--faint); }

.check {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
}

/* the list */

/* One grid for the whole list, so every city row measures its columns against
   every other row. With a grid per row, each row sized its own tracks: "1
   flight" and "122 flights" put the count at ten different x positions down the
   list, and a two-digit price started the word "from" eight pixels right of a
   three-digit one. */
.list {
  display: grid;
  grid-template-columns: 1fr auto auto 1rem;
  column-gap: 0.8rem;
  align-content: start;
}

.hidden-line {
  grid-column: 1 / -1;
  margin: 0 0 0.6rem;
  padding: 0.4rem 0.6rem;
  font-size: 0.78rem;
  color: var(--muted);
  background: #FFFFFF;
  border: 1px solid var(--hair-soft);
  border-left: 2px solid var(--stop-hair);
  border-radius: 2px;
}

.hidden-line summary { cursor: pointer; }
.hidden-line .names { padding-top: 0.3rem; color: var(--faint); }

/* The counterpart to .hidden-line, under the list rather than over it: what
   the timetable removed, and the one control that can bring it back. */
.thin-line {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 1rem 0 0;
  padding: 0.5rem 0 0;
  font-size: 0.78rem;
  color: var(--muted);
  border-top: 1px solid var(--hair-soft);
}

.offer {
  font: inherit;
  font-size: 0.78rem;
  color: var(--ink);
  background: #FFFFFF;
  border: 1px solid var(--hair);
  border-radius: 3px;
  padding: 0.2rem 0.5rem;
  cursor: pointer;
}

/* Full-width bands between the city rows they separate. */
.country, .flights, .two, .trip-total { grid-column: 1 / -1; }

.country {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.8rem;
  margin: 1.15rem 0 0.1rem;
  padding-bottom: 0.22rem;
  border-bottom: 1px solid var(--hair);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.country:first-child { margin-top: 0; }

.country .left { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }

/* The word is the message; the colour only agrees with it. A badge that says
   nothing without its hue would be invisible to a colour-blind visitor and to
   a screen reader alike. */
.visa {
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  padding: 0.06rem 0.34rem;
  border-radius: 2px;
  border: 1px solid;
  white-space: nowrap;
}

.visa.free { color: var(--free); background: var(--wash); border-color: var(--free-hair); }
.visa.soft { color: var(--warn); background: var(--warn-wash); border-color: var(--warn-hair); }
.visa.hard { color: var(--stop); background: var(--stop-wash); border-color: var(--stop-hair); }

.country .why {
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  font-size: 0.75rem;
  color: var(--muted);
}

.country em {
  font-style: normal;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--faint);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}

.city {
  display: grid;
  /* Declared twice on purpose: a browser without subgrid keeps the first and
     behaves exactly as it did before, one grid per row. */
  grid-template-columns: 1fr auto auto 1rem;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: baseline;
  gap: 0.8rem;
  padding: 0.42rem 0.4rem 0.42rem 0.7rem;
  border-bottom: 1px solid var(--hair-soft);
  cursor: pointer;
}

.city:hover { background: #FFFFFF; }
.city:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.city.open { background: var(--wash); border-bottom-color: transparent; }
.city .name { font-size: 0.94rem; }

/* Both columns end on a line rather than starting on one: the count and the
   price are what the eye compares between rows, and a shared track is only
   worth having if the numbers inside it line up. */
.city .cnt {
  color: var(--muted);
  font-size: 0.78rem;
  white-space: nowrap;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.city .from {
  font-size: 0.85rem;
  white-space: nowrap;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Centred, not on the baseline: the glyph is drawn sitting on it, so baseline
   alignment leaves the triangle a hair below the middle of the row. */
.city .chev {
  color: var(--faint);
  font-size: 0.68rem;
  text-align: right;
  align-self: center;
}

.iata, .num, .org {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  font-variant-numeric: tabular-nums;
}

.iata { font-size: 0.7rem; letter-spacing: 0.06em; color: var(--faint); }
.num { font-size: 0.82rem; }
.price { font-weight: 600; }
.noprice { color: var(--faint); }
.dim { color: var(--muted); }

.flights {
  background: var(--wash);
  padding: 0.2rem 0.4rem 0.55rem 1.5rem;
  border-bottom: 1px solid var(--hair-soft);
}

.fl {
  display: grid;
  grid-template-columns: 8.2rem 3.8rem 1fr 2.4rem 3.2rem auto;
  align-items: baseline;
  gap: 0.7rem;
  padding: 0.3rem 0;
  font-size: 0.82rem;
  border-bottom: 1px solid rgba(140, 191, 112, 0.18);
  /* The row is an anchor: without these it goes blue and underlined. */
  color: inherit;
  text-decoration: none;
}

.fl:hover { background: #FFFFFF; }
.fl:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.fl:last-child { border-bottom: 0; }
.fl .who b { font-weight: 500; }
.fl .craft { color: var(--faint); font-size: 0.76rem; }
.fl .p { text-align: right; }
.fl .date { color: var(--muted); font-size: 0.76rem; }
.plus1 { color: var(--faint); font-size: 0.68rem; }

/* The link marker rides with the price rather than taking a column: the row
   is already six wide, and on a narrow screen it folds away with it. */
.fl .go { color: var(--faint); font-size: 0.72rem; }
.fl:hover .go, .fl:focus-visible .go { color: var(--accent); }

/* Read by a screen reader, drawn for nobody. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.org {
  font-size: 0.66rem;
  letter-spacing: 0.05em;
  color: var(--muted);
  border: 1px solid var(--hair);
  border-radius: 2px;
  padding: 0.05rem 0.28rem;
  justify-self: start;
}

/* Two columns rather than a list of pairs: 15 flights each way is 30 rows
   here and 225 as pairs, and the total below says what the chosen two cost. */
.two { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; padding: 0.5rem 0.3rem 0.3rem 0.8rem; }
.col { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.col-name { font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); }

.pick {
  display: grid;
  grid-template-columns: 3.6rem 3rem minmax(0, 1fr) 3rem;
  gap: 0.4rem;
  align-items: baseline;
  text-align: left;
  font: inherit;
  font-size: 0.78rem;
  background: none;
  border: 1px solid transparent;
  border-radius: 2px;
  padding: 0.15rem 0.25rem;
  cursor: pointer;
  color: inherit;
}
.pick.on { background: var(--wash); border-color: var(--accent); }

/* Ruled out by the outbound that is chosen: it leaves before that flight
   lands. Shown rather than removed, so the choice explains itself -- and
   disabled, because picking one would build a trip nobody can fly. */
.pick.dim { opacity: 0.45; cursor: default; }
.pick .p { text-align: right; }
.pick .who b { font-weight: 600; }

.trip-total {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0.3rem 0.3rem 0.6rem 0.8rem;
  padding-top: 0.35rem;
  border-top: 1px solid var(--hair);
  font-size: 0.82rem;
}
.trip-total .trip { color: var(--muted); font-variant-numeric: tabular-nums; }
/* The quiet half of the total: what the number is, said beside it. */
.trip-total .how { color: var(--faint); font-size: 0.72rem; }
.trip-total b { font-size: 1rem; font-variant-numeric: tabular-nums; }
.trip-total .tick {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--free);
  border: 1px solid var(--free-hair);
  border-radius: 2px;
  padding: 0 0.25rem;
}
/* The pair sits at the right end of the summary; only the first needs the
   push, and the flex gap spaces the second from it. */
.book-leg:first-of-type { margin-left: auto; }

.book-leg {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--free);
  border: 1px solid var(--accent);
  background: var(--wash);
  border-radius: 2px;
  padding: 0.1rem 0.5rem;
  text-decoration: none;
}

@media (max-width: 34rem) { .two { grid-template-columns: 1fr; } }

@media (max-width: 40rem) {
  /* Five columns do not fit in 360 pixels: the row becomes two lines, airline
     over time, with the price holding the right edge. */
  .fl { grid-template-columns: 1fr auto; }
  .fl .num:first-child { grid-row: 2; }
  .fl .dim, .fl .org, .fl .date { display: none; }
  .flights { padding-left: 0.9rem; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
