/* The installed web app: one column, phone first. The card itself is the
   account page's preview (.widget in account.css). */

.app-main { max-width: 560px; display: grid; align-content: start; gap: 16px; padding-bottom: 32px; }

/* On the page, not in a box: the answer is the screen. */
.app-card { padding: 4px 0 0; background: none; border: 0; box-shadow: none; border-radius: 0; }
.app-card .widget { box-shadow: none; border: 0; padding: 0; background: none; }
.app-card .widget .chips { display: none; }
/* One quiet line: "Updated 9:41 · Is this wrong?". */
.app-card .card-foot { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 6px; margin-top: 12px; }
/* "Checking…" takes the room the usual card will (a trip: three lines in the
   sky, its steps below the horizon), so the card arrives in place. */
.app-card.loading .sky-head { min-height: 7.9rem; }
.app-card.loading .card-foot { margin-top: 23rem; }
.app-card .updated { font-size: 0.8rem; }
.app-card .updated:empty { display: none; }
.app-card .updated:has(+ .report-open)::after { content: '·'; margin-left: 6px; }
/* Anything else under them (the report's sheet is out of the flow), on a line of its own. */
.app-card .card-foot > :not(.report-open, .report-sheet, .updated) { flex-basis: 100%; }
.app-card .card-foot > p { order: 1; margin: 0; }
body.is-offline .app-card .widget { opacity: 0.6; }
/* The day plan's next thing, offline: still the thing to do, so not dimmed (the banner says offline). */
body.is-offline .app-card .widget.offline-plan { opacity: 1; }

/* Stretched so the icon buttons (search, an added place's X) are as tall as the text chips. */
.app-chips { display: flex; align-items: stretch; gap: 8px; overflow-x: auto; scrollbar-width: none; margin-bottom: -8px; padding: 4px 2px; margin-top: -4px; }
/* (The padding is room for a chip's focus ring, which the scrolling row would clip.) */
.app-chips::-webkit-scrollbar { display: none; }
.app-chips button {
  flex: none;
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}
.app-chips button[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--bg); }
@media (forced-colors: active) {
  .app-chips button[aria-pressed="true"], .app-chips .chip-added:has(> button[aria-pressed="true"]) .chip-x { forced-color-adjust: none; background: Highlight; border-color: Highlight; color: HighlightText; }
  .map-pills .dot, .road-bus, .bt-bus { forced-color-adjust: none; }
}
/* Elsewhere at the end of the chips, worded so it reads as one more place to go, and the search it opens. */
.app-chips .chip-search { display: inline-flex; align-items: center; gap: 5px; padding: 7px 14px 7px 10px; }
.app-chips .chip-search[aria-expanded="true"] { color: var(--ink); border-color: var(--ink); }
.chip-search svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.where-box input { width: 100%; }
/* A place added from the search: its chip, and an X to take it off. */
.app-chips .chip-added { flex: none; display: inline-flex; align-items: stretch; }
.app-chips .chip-added > button:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: 0; padding-right: 8px; }
.app-chips .chip-x { display: grid; place-items: center; padding: 7px 9px 7px 6px; border-top-left-radius: 0; border-bottom-left-radius: 0; color: var(--muted); }
.app-chips .chip-added:has(> button[aria-pressed="true"]) .chip-x { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.chip-x svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; }
.go-later { display: block; margin: 10px auto 0; }
/* Go later: Settings' rows (account.css), then the buttons. */
.go-later-form { display: grid; gap: 10px; min-width: 0; margin-top: 10px; }
.go-later-form > .eyebrow { margin: 0 0 0 16px; }
/* "Be there by" labels the time field, but reads as a row's title like "Day". */
.go-later-form label.row-title { margin: 0; font-size: inherit; color: var(--ink); }
/* The stepper's middle is the browser's time picker, drawn as the stepper's value. */
.stepper-time {
  width: 5.2rem; min-height: 32px; padding: 2px 4px; border: 0; border-radius: 8px; background: none;
  color: var(--ink); font: inherit; font-variant-numeric: tabular-nums; text-align: center; cursor: pointer;
}
.stepper-time:hover { background: var(--surface-2); }
.stepper-time::-webkit-calendar-picker-indicator { display: none; }
.stepper-time::-webkit-datetime-edit { padding: 0; }
.stepper-time::-webkit-datetime-edit-fields-wrapper { display: flex; justify-content: center; }
/* The server's refusal ("That time has passed today."), with a nudge so a second try is noticed too. */
.go-later-error {
  display: flex; align-items: flex-start; gap: 10px; margin: 0; padding: 10px 14px;
  border: 1px solid color-mix(in srgb, var(--bad) 35%, transparent); border-radius: 12px;
  background: color-mix(in srgb, var(--bad) 10%, transparent); color: var(--bad-ink); font-size: 0.9rem; font-weight: 500;
  animation: go-later-nudge 0.32s ease-out;
}
.go-later-error svg { width: 18px; height: 18px; flex: none; margin-top: 1px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
@keyframes go-later-nudge {
  0%, 100% { transform: none; }
  25% { transform: translateX(-4px); }
  50% { transform: translateX(3px); }
  75% { transform: translateX(-2px); }
}
@media (prefers-reduced-motion: reduce) { .go-later-error { animation: none; } }

/* Nearby: each stop as its sign (app.js NearbyCard). The plate is the
   sign's inverted colours; under it a row per service, its bus on a road. */
.nearby { display: grid; gap: 12px; }
.stop-sign .detail { padding: 12px 14px; }
.nearby-row { display: flex; align-items: center; gap: 10px; width: 100%; padding: 7px 14px; border: 0; background: none; color: inherit; font: inherit; text-align: left; font-variant-numeric: tabular-nums; }
button.nearby-row { cursor: pointer; }
button.nearby-row:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
.nearby-row + .nearby-row { border-top: 1px solid var(--line); }
.stop-sign.nearest .nearby-row { padding-block: 10px; }
/* Fixed columns either side (in rem, the same on every sign), so every road
   starts and ends at the same place and a bus is as far along on each. */
.nearby-row .svc-col { flex: none; display: flex; width: 3.4rem; }
.nearby-row .eta { flex: none; min-width: 4.6rem; text-align: right; font-weight: 800; font-size: 0.95rem; white-space: nowrap; }
.stop-sign.nearest .nearby-row .eta { font-size: 1.1rem; }
/* The star on a sign's plate: pins the stop to the Stops tab. */
.plate-star { flex: none; display: grid; place-items: center; width: 34px; height: 34px; margin-right: -2px; border: 0; border-radius: 50%; background: none; color: var(--muted); cursor: pointer; }
.plate-star svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linejoin: round; }
.plate-star.on { color: var(--warn); }
.plate-star.on svg { fill: currentColor; }
/* Under the signs: the way to the Stops tab, for any other stop or a whole line. */
.to-stops { display: flex; align-items: center; gap: 12px; width: 100%; padding: 12px 10px 12px 14px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
.to-stops > span { flex: 1; min-width: 0; display: grid; gap: 2px; }
.to-stops b { font-weight: 600; font-size: 0.95rem; }
.to-stops > span > span { color: var(--muted); font-size: 0.82rem; }
.to-stops-icon { flex: none; width: 22px; height: 22px; fill: none; stroke: var(--accent); stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.to-stops-go { flex: none; width: 18px; height: 18px; fill: none; stroke: var(--muted); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
/* The road to the stop (the ring at its end), and the buses on it, nearer the sooner they're due. */
.road { position: relative; flex: 1; height: 18px; min-width: 40px; }
.road::before { content: ''; position: absolute; left: 0; right: 10px; top: 8px; height: 2px; background: repeating-linear-gradient(90deg, var(--line-strong) 0 6px, transparent 6px 10px); }
.road::after { content: ''; position: absolute; right: 0; top: 3px; width: 12px; height: 12px; box-sizing: border-box; border-radius: 50%; border: 2.5px solid var(--ink); background: var(--surface); }
.road-bus { position: absolute; top: 3px; left: calc((100% - 38px) * (1 - var(--far))); width: 18px; height: 12px; box-sizing: border-box; border-radius: 4px; border: 2px solid var(--svc); }
.road-bus.live { background: var(--svc); }
/* The buses after the next one: there, but not the one to catch. */
.road-bus.later { opacity: 0.6; }

/* Its status line stays on the page; empty, it takes no room in Now's column. */
.banner-slot:empty { position: absolute; }
.offline {
  padding: 10px 14px;
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 0.9rem;
}

.install h2 { font-size: 1.05rem; margin: 0 0 8px; }
.install-steps { margin: 0 0 8px; padding-left: 1.2em; display: grid; gap: 6px; }
.ios-share { display: inline-flex; vertical-align: -3px; color: var(--accent); }


.today .label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 8px;
}
.today-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.today-item { display: grid; grid-template-columns: 5.5em 1fr auto; gap: 8px; align-items: baseline; }
.today-item .at { font-variant-numeric: tabular-nums; color: var(--ink); }
.today-item .what { display: grid; gap: 2px; min-width: 0; }
.today-item .title { overflow-wrap: anywhere; }
.today-item .sub { font-size: 0.85rem; color: var(--muted); }
.today-item.next .at, .today-item.next .title, .today-item.now .at, .today-item.now .title { font-weight: 600; }
.today-item.done, .today-item.skipped { color: var(--muted); }
.today-item.done .at, .today-item.skipped .at { color: var(--muted); }
.today-item.skipped .title { text-decoration: line-through; }

/* Taking an entry off today: an × on each row still to come, and Undo after. */
.remove-today { background: none; border: 0; color: var(--muted); font: inherit; font-size: 1.1rem; line-height: 1; padding: 6px 10px; margin: -6px -10px -6px 0; border-radius: 8px; cursor: pointer; }
.remove-today:hover { color: var(--bad); }
.today-item.removed, .today-item.removed .at { color: var(--muted); }
.today-item .undo-today { padding: 6px 8px; margin: -6px -8px -6px 0; color: var(--accent); font-weight: 600; }

/* Its search takes the header's place, as Settings' title does, at the top
   of the sky. */
body.on-buses > .nav { display: none; }
.buses-tab { max-width: 560px; padding-bottom: 32px; }

/* The sky (buses.js Sky), as Settings' band: the search at its top, then
   each page's own sky from the same colour down to its low hills, so the
   two meet without a seam. The home's title, a stop's name, a line's
   title and Back sit in it, in the sky's ink (.sky-head). */
/* Above the page under it, so its search's list comes down over it. */
.bt-sky-top { position: relative; z-index: 5; isolation: isolate; margin: 0 -16px; padding: max(12px, env(safe-area-inset-top)) 16px 0; background: var(--s0); }
/* Overscrolled at the top: still the sky up there. */
.bt-sky-top::before { content: ''; position: absolute; z-index: -1; inset: -240px 0 auto 0; height: 240px; background: var(--s0); }
.bt-sky { margin: 0 -16px; padding: 0 16px; background: linear-gradient(var(--s0), var(--s1) 45%, var(--s2) 80%, var(--s3)); }
/* A page of its own starts the sky itself. */
.bt-page.alone > .bt-sky, .buses-tab > .bt-sky { padding-top: env(safe-area-inset-top); }
.bt-sky .sky-head { --room: 0px; --moon: 22px; padding-bottom: 2px; }
/* Stars across the whole sky; the moon low on the right, clear of a long name. */
.bt-sky .celestial { height: 100%; }
body.sky .bt-sky .night-sky .moon { top: auto; bottom: 0; right: 30px; }
/* A line's title has its words on the right: the moon up by Back instead. */
body.sky .buses-tab > .bt-sky .night-sky .moon { top: calc(env(safe-area-inset-top) + 12px); bottom: auto; }
/* Within the page, not across the window as on Now. */
.bt-sky .horizon.low { width: auto; height: 52px; margin: 0 -16px; }
.bt-sky + .bt-sides { margin-top: 4px; }
.bt-sky .bt-nav { height: 48px; }
.bt-sky .bt-kicker-icon { fill: var(--k-accent-small); }
/* The search box takes the sky's colours; its list comes down over the
   page, so it keeps the page's own. */
body.sky .bt-sky-top :is(input, .bt-search-icon) {
  --surface-2: var(--k-surface-2); --ink: var(--k-ink); --muted: var(--k-muted); --accent: var(--k-accent);
  color-scheme: var(--k-scheme); color: var(--ink);
}
body.sky .bt-sky-top input::placeholder { color: var(--k-muted); opacity: 0.85; }
/* Wider than a phone, the column's sky has rounded top corners rather than
   stopping short of the window's edges. */
@media (min-width: 600px) {
  .bt-sky-top, .buses-tab > .bt-sky, .bt-page.alone > .bt-sky { margin-top: 12px; border-radius: 18px 18px 0 0; }
  .bt-sky-top::before { display: none; }
}
.bt-search { position: relative; }
.bt-search input { height: 46px; padding-left: 42px; border-radius: 14px; border-color: var(--line-strong); background: var(--surface-2); }
.bt-search input:focus-visible { outline-offset: 0; }
.bt-search-icon { position: absolute; z-index: 1; left: 14px; top: 13px; width: 20px; height: 20px; fill: none; stroke: var(--muted); stroke-width: 2; stroke-linecap: round; pointer-events: none; }

/* A stop's own page, the column's width, edge to edge. */
.bt-page { min-width: 0; padding: 0 16px; }
.bt-page.alone { margin: 0 -16px; }

/* The home (buses.js Home): its title in the sky, then your pinned stops,
   each a name with its star over its first rows, then every service. */
.bt-home .bt-top { padding-bottom: 6px; }
.bt-section { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 22px 6px 8px; }
.bt-section > span { font-size: 0.75rem; color: var(--muted); text-align: right; }
.bt-pin + .bt-pin { margin-top: 18px; }
.bt-pin-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 0 0 0 6px; }
.bt-pin-name { display: flex; align-items: center; gap: 4px; min-width: 0; padding: 6px 0; border: 0; background: none; color: var(--ink); font: 700 1.1rem/1.2 var(--font); text-align: left; cursor: pointer; }
.bt-pin-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bt-pin-name svg { flex: none; width: 16px; height: 16px; fill: none; stroke: var(--muted); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.bt-pin-name:hover span { text-decoration: underline; text-underline-offset: 3px; }
.bt-star.small { width: 36px; height: 36px; background: none; }
.bt-pin .bt-board { margin-top: 6px; }
.bt-pin .bt-empty { padding: 14px 4px; }
.bt-pin-error { padding: 6px 6px 0; font-size: 0.8rem; text-align: right; }
.bt-more { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; padding: 12px 14px; border: 0; border-top: 1px solid var(--line); background: none; color: var(--accent); font: 600 0.9rem var(--font); text-align: left; cursor: pointer; }
.bt-more .bt-go { stroke: currentColor; }
/* Nothing pinned yet: where the star is, said once, in a dashed outline. */
.bt-no-pins { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border: 1.5px dashed var(--line-strong); border-radius: 14px; color: var(--muted); font-size: 0.9rem; line-height: 1.45; }
.bt-no-pins svg { flex: none; width: 20px; height: 20px; fill: none; stroke: var(--warn); stroke-width: 1.9; stroke-linejoin: round; }
.bt-services { padding: 0; overflow: hidden; }
.bt-service { display: flex; align-items: center; gap: 12px; width: 100%; padding: 11px 10px 11px 14px; border: 0; border-top: 1px solid var(--line); background: none; color: var(--ink); font: 600 0.95rem/1.3 var(--font); text-align: left; cursor: pointer; }
.bt-service:first-child { border-top: 0; }
.bt-service > span { flex: 1; min-width: 0; }
.bt-service .bt-chip { flex: none; }

.bt-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 18px 4px 0; }
.bt-title { min-width: 0; }
.bt-kicker { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 0.85rem; font-weight: 500; color: var(--muted); }
.bt-kicker-icon { width: 13px; height: 13px; fill: var(--accent); }
.bt-kicker-icon.star { fill: var(--warn); }
.bt-title h2 { margin-top: 6px; font: 700 2.2rem/1.05 var(--display); letter-spacing: -0.025em; overflow-wrap: anywhere; }
.bt-code { margin-top: 6px; font: 600 0.7rem/1 var(--display); letter-spacing: 0.08em; color: var(--muted); }
.bt-star { flex: none; display: grid; place-items: center; width: 40px; height: 40px; border: 0; border-radius: 50%; background: var(--surface-2); color: var(--muted); cursor: pointer; }
.bt-star svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linejoin: round; }
.bt-star.on { color: var(--warn); }
.bt-star.on svg { fill: currentColor; }

.bt-sides { margin-top: 12px; }
/* An opposite stop named in full (PGP Foyer) may take two lines. */
.bt-sides span { line-height: 1.25; }

/* The board: a row per service, sorted as the API sorts them. */
.bt-board { margin-top: 12px; padding: 0; overflow: hidden; }
.bt-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "chip dir big" "meta meta then";
  align-items: center; gap: 7px 12px; width: 100%; padding: 14px 16px 13px;
  border: 0; border-top: 1px solid var(--line); background: none; color: var(--ink); font: inherit; text-align: left;
}
.bt-row:first-child { border-top: 0; }
button.bt-row { cursor: pointer; }
@media (hover: hover) { button.bt-row:hover { background: var(--surface-2); } }
button.bt-row:focus-visible { outline-offset: -2px; }
/* A row that opens its line's page (the Buses tab): a chevron at its end says so. */
.bt-row.go { grid-template-columns: auto minmax(0, 1fr) auto auto; grid-template-areas: "chip dir big go" "meta meta then go"; padding-inline-end: 8px; }
.bt-go { grid-area: go; align-self: center; width: 20px; height: 20px; fill: none; stroke: var(--muted); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.bt-go:dir(rtl) { transform: scaleX(-1); }
/* On the map, the row of the line picked there: a wash of its colour, as its pill is pressed. */
.bt-row.picked { background: color-mix(in srgb, var(--svc) 16%, transparent); }
@media (hover: hover) { button.bt-row.picked:hover { background: color-mix(in srgb, var(--svc) 22%, transparent); } }
/* Times that couldn't be refreshed: counted down, but faded so they don't pass for live. */
.bt-row.old .bt-big, .bt-row.old .bt-then { opacity: 0.55; }
.bt-chip { grid-area: chip; align-self: start; min-width: 46px; height: 32px; font: 700 0.95rem/1 var(--display); border-radius: 9px; }
.bt-chip.big { min-width: 54px; height: 40px; font-size: 1.15rem; border-radius: 11px; }
.svc-tag.small { min-width: 2.4em; padding: 2px 6px; font-size: 0.7rem; border-radius: 5px; }
/* Wide enough for two characters, so a stop's P and R2 are the same size. */
.svc-tag.tiny { min-width: 2.4em; padding: 1px 5px; font-size: 0.65rem; border-radius: 4px; }
.svc-tag .fare { margin-left: 3px; font-size: 0.8em; }
/* A running service: where it's going over the next stop and its tags, one block down the rows beside the times. */
.bt-row.run { grid-template-areas: "chip text big" "chip text then"; }
.bt-row.run.go { grid-template-areas: "chip text big go" "chip text then go"; }
.bt-text { grid-area: text; align-self: start; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
/* As wide as the column (not its words), so a long name ends in "…" before the time. */
.bt-text .bt-dir { align-self: stretch; }
.bt-row .bt-text .bt-meta { padding-left: 0; }
/* Where it's going, on one line. */
.bt-dir { grid-area: dir; align-self: start; padding-top: 2px; font-size: 0.95rem; font-weight: 600; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The big time, with the live dot beside it when it's live. */
.bt-at { grid-area: big; justify-self: end; display: flex; align-items: center; gap: 8px; }
.bt-dot { flex: none; }
.bt-big { grid-area: big; justify-self: end; font: 700 2.6rem/0.9 var(--display); letter-spacing: -0.03em; font-variant-numeric: tabular-nums; white-space: nowrap; }
.bt-big small { margin-left: 3px; font: 600 0.85rem var(--font); letter-spacing: 0; color: var(--muted); }
.bt-big.now { color: var(--accent); }
.bt-big.none { font: 500 0.9rem/1.3 var(--font); color: var(--muted); letter-spacing: 0; text-align: right; }
/* Under the chip's column too, lined up with the words beside the chip. */
.bt-meta { grid-area: meta; display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; padding-left: 58px; font-size: 0.8rem; color: var(--muted); min-height: 1px; }
.bt-live { display: inline-flex; align-items: center; gap: 6px; color: var(--good-ink); font-weight: 600; }
.bt-live .dot, .bt-dot { box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 22%, transparent); }
/* Under the board the word explains the rows' dot, in the line's own grey, as the apps say it. */
.bt-updated .bt-live { color: inherit; font-weight: inherit; }
.bt-sched, .bt-stale { padding: 2px 7px; border-radius: 6px; font-weight: 600; font-size: 0.75rem; }
.bt-sched { color: var(--warn-ink); background: color-mix(in srgb, var(--warn) 14%, transparent); }
.bt-stale { color: var(--muted); background: var(--surface-2); }
.bt-crowd { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: 999px; background: var(--surface-2); color: var(--muted); font-size: 0.75rem; font-weight: 600; }
.bt-crowd svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.bt-crowd.low { color: var(--good-ink); background: color-mix(in srgb, var(--good) 12%, transparent); }
.bt-crowd.high { color: var(--warn-ink); background: color-mix(in srgb, var(--warn) 15%, transparent); }
.bt-then { grid-area: then; justify-self: end; font: 600 0.85rem/1.2 var(--display); color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* A service not running: greyed, its words where the minutes go. */
.bt-row.stopped { color: var(--muted); }
.bt-row.stopped .bt-dir { color: var(--muted); font-weight: 500; }
.bt-chip.muted { background: color-mix(in srgb, var(--svc) 30%, var(--surface-2)); color: var(--ink); }
.bt-row.stopped .bt-big.none { color: var(--muted); font-weight: 600; }
/* No minutes after it: its line takes the row's width. */
.bt-row.stopped .bt-meta { grid-column: meta-start / then-end; color: var(--muted); }
.bt-stopped-note { display: grid; gap: 2px; margin: 12px 0 0; padding: 10px 14px; border-radius: 12px; background: var(--surface-2); color: var(--muted); font-size: 0.85rem; }
.bt-stopped-note span:first-child { color: var(--ink); font-weight: 600; font-size: 0.95rem; }

.bt-foot { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; padding: 10px 6px 0; font-size: 0.8rem; color: var(--muted); }
.bt-ends { display: grid; gap: 4px; }
.bt-ends > div { display: flex; align-items: center; gap: 6px; }
.bt-status { display: grid; gap: 2px; margin-left: auto; text-align: right; }
.bt-error { color: var(--warn-ink); }
.bt-hint { margin-top: 6px; padding: 0 6px; font-size: 0.78rem; color: var(--muted); }
.bt-empty { padding: 28px 4px; text-align: center; }

/* A page of its own (a stop, a line): Back to where it came from. */
.bt-nav { display: flex; align-items: center; height: 40px; }
.bt-back { display: inline-flex; align-items: center; gap: 2px; margin-left: -8px; padding: 6px 8px 6px 2px; border: 0; border-radius: 10px; background: none; color: var(--accent); font: inherit; font-weight: 600; cursor: pointer; }
.bt-back svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }

/* A service's line: its stops down a line in its colour, its buses on it. */
.bt-line-title { display: flex; align-items: center; gap: 12px; padding: 8px 4px 0; }
.bt-line-title h2 { font: 700 2.2rem/1 var(--display); letter-spacing: -0.025em; }
.bt-line-title .hint { margin-top: 4px; font-size: 0.85rem; }
.bt-summary { margin: 10px 4px 0; font-size: 0.85rem; color: var(--ink); }
.bt-summary:empty { display: none; }
.bt-note { margin: 10px 0 0; padding: 8px 12px; border-radius: 10px; background: var(--surface-2); color: var(--muted); font-size: 0.8rem; }
.bt-line-label { display: flex; justify-content: space-between; align-items: baseline; margin: 14px 4px 0; padding-top: 12px; border-top: 1px solid var(--line); font-size: 0.8rem; color: var(--muted); }
.bt-route { position: relative; list-style: none; margin: 6px 0 0; padding: 0; }
.bt-route::before { content: ''; position: absolute; z-index: 1; left: 21px; top: 24px; bottom: 24px; width: 6px; border-radius: 3px; background: var(--svc); pointer-events: none; }
.bt-stop button { position: relative; display: flex; align-items: center; gap: 14px; width: 100%; min-height: 50px; padding: 6px 8px 6px 14px; border: 0; border-radius: 12px; background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
@media (hover: hover) { .bt-stop button:hover { background: var(--surface-2); } }
.bt-stop + .bt-stop button::after { content: ''; position: absolute; left: 50px; right: 8px; top: 0; height: 1px; background: var(--line); }
.bt-stop.mine button::after, .bt-stop.mine + .bt-stop button::after { display: none; }
.bt-node { flex: none; width: 20px; height: 20px; display: grid; place-items: center; z-index: 2; }
.bt-node::before { content: ''; width: 14px; height: 14px; border-radius: 50%; box-sizing: border-box; background: var(--surface); border: 3px solid var(--svc); }
.bt-stop-text { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; }
.bt-stop-name { flex-basis: 100%; font-weight: 600; font-size: 0.95rem; line-height: 1.25; }
.bt-others { display: inline-flex; flex-wrap: wrap; gap: 3px; }
.bt-stop.mine button { margin: 2px 0; background: var(--accent-soft); }
.bt-stop.mine .bt-node::before { width: 18px; height: 18px; border-width: 4px; box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 25%, transparent); }
.bt-stop.mine .bt-stop-name { font-size: 1.02rem; }
.bt-mine-tag { display: inline-flex; align-items: center; gap: 3px; padding: 1px 7px 1px 5px; border-radius: 10px; border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); background: var(--surface); color: var(--accent); font-size: 0.72rem; font-weight: 700; }
.bt-mine-tag svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2.2; }
.bt-yours { flex: none; display: grid; justify-items: end; gap: 4px; }
/* Out of the board row's grid areas: here they stack. */
.bt-yours > * { grid-area: auto; }
.bt-yours .bt-big { font-size: 1.7rem; color: var(--accent); }
.bt-yours .bt-big.none { font-size: 0.8rem; color: var(--muted); }
.bt-yours .bt-meta { padding: 0; justify-content: flex-end; font-size: 0.75rem; }
.bt-yours .bt-then { font-size: 0.75rem; }
/* A bus on the line: over the stop's dot, or on the line between two. */
.bt-bus { flex: none; z-index: 2; display: grid; place-items: center; width: 28px; height: 28px; margin: 0 -4px; border-radius: 9px; background: var(--svc); color: var(--svc-ink); border: 2.5px solid var(--bg); box-shadow: 0 2px 8px -2px rgb(28 25 23 / 0.45); }
.bt-bus svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.bt-gap { display: flex; align-items: center; gap: 14px; min-height: 36px; padding: 2px 8px 2px 14px; }
.bt-bus-info { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 0.8rem; color: var(--muted); }
.bt-plate { padding: 2px 5px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--surface); color: var(--ink); font: 600 0.7rem/1.2 ui-monospace, Menlo, Consolas, monospace; letter-spacing: 0.04em; }

/* Full screen above the tab bar; the header steps aside for it. */
body.on-map { overflow: hidden; }
body.on-map > .nav, body.on-map #tab-now { display: none; }
.map-tab { position: fixed; inset: 0 0 calc(var(--tabbar-h) + env(safe-area-inset-bottom)) 0; background: var(--bg); }
/* Sized, not positioned: MapLibre makes it position: relative. */
.map { width: 100%; height: 100%; }
.map-top {
  position: absolute;
  inset: 0 0 auto 0;
  padding: max(10px, env(safe-area-inset-top)) 12px 0;
  display: grid;
  gap: 8px;
  justify-items: start;
  pointer-events: none;
}
.map-top > * { pointer-events: auto; }
.map-pills { width: 100%; margin: 0; padding-bottom: 2px; }
/* Edge to edge: scrolled pills go off the screen's edge, not under an inset
   line 12 px in. They still start 12 px in, lined up with what's below. */
.map-top > .map-pills { width: calc(100% + 24px); margin: 0 -12px; padding: 4px 0 2px 12px; scroll-padding-inline: 12px; }
/* Room after the last pill (end padding in a scrolling row isn't reliable). */
.map-pills::after { content: ''; flex: none; width: 4px; }
.map-pills button { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; box-shadow: 0 1px 4px rgb(0 0 0 / 0.12); }
.map-pills .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--svc); box-shadow: 0 0 0 1px rgb(0 0 0 / 0.15); }
.map-pills button[aria-pressed="true"] { background: var(--svc); border-color: var(--svc); color: var(--svc-ink, #fff); }
.map-pills button[aria-pressed="true"] .dot { background: currentColor; box-shadow: none; }
.map-status {
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-size: 0.85rem;
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.12);
}
/* The stops as a list (map.js StopList): seen only while it has the focus. */
.map-stops { display: flex; gap: 6px; max-width: 100%; }
.map-stops select { width: auto; min-width: 0; box-shadow: 0 1px 4px rgb(0 0 0 / 0.12); }
.map-stops .btn { flex: none; box-shadow: 0 1px 4px rgb(0 0 0 / 0.12); }
.map-stops:not(:focus-within) { position: absolute; width: 1px; height: 1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); }

/* A stop's or a bus's details, over the bottom of the map. */
.map-sheet {
  position: absolute;
  inset: auto 0 0 0;
  max-height: 62%;
  /* The part above the footer scrolls (.sheet-scroll); the footer stays in view. */
  overflow: hidden;
  background: var(--surface);
  border-radius: 18px 18px 0 0;
  box-shadow: 0 -2px 16px rgb(0 0 0 / 0.16);
  /* Over MapLibre's controls (z-index 2). */
  z-index: 3;
  display: flex;
  flex-direction: column;
  /* Let go short of closing (map.js Frame), it springs back. */
  transition: transform 0.2s ease-out;
}
.map-sheet.dragging { transition: none; }
@media (prefers-reduced-motion: reduce) { .map-sheet { transition: none; } }
/* A short window (a phone on its side, or zoomed in a long way): the sheet
   leaves more of the map, and MapLibre's buttons stay above it. */
@media (max-height: 420px) {
  .map-sheet { max-height: 50%; }
  /* The buttons side by side, not stacked: they fit between the pills and the sheet. */
  .map .maplibregl-ctrl-bottom-right { display: flex; align-items: flex-end; }
  /* With the sheet open there's room for one: Back to campus (the keys and a pinch still zoom). */
  .map-tab:has(.map-sheet) .maplibregl-ctrl-group:has(.maplibregl-ctrl-zoom-in) { display: none; }
}
@media (max-width: 699.98px) {
  .map-tab:has(.map-sheet) .maplibregl-ctrl-bottom-right { bottom: var(--sheet-h, 0px); }
}
@media (min-width: 700px) {
  .map-sheet { inset: auto auto 16px 16px; width: 360px; border-radius: 18px; max-height: calc(100% - 96px); }
}
.sheet-head { display: flex; align-items: center; gap: 12px; padding: 0 10px 0 16px; }
.sheet-head h2 { margin: 0; font: 600 1.15rem/1.25 var(--display); }
.sheet-head .hint { margin: 2px 0 0; font-size: 0.85rem; }
.sheet-titles { flex: 1; min-width: 0; }
/* A bus's number plate, like the plate on the bus. */
.map-sheet .plate { display: inline-block; padding: 1px 7px; border: 1px solid var(--line-strong); border-radius: 5px; font: 600 0.78rem/1.5 ui-monospace, Menlo, Consolas, monospace; letter-spacing: 0.05em; color: var(--muted); }
.sheet-close { flex: none; width: 36px; height: 36px; border: 0; border-radius: 50%; background: var(--surface-2); color: var(--ink); font-size: 1.2rem; line-height: 1; cursor: pointer; }
/* The tile that leads the header, the same size for a stop as for a bus:
   a bus's service on its colour, a stop's pin on grey. */
.sheet-tile { flex: none; display: grid; place-items: center; width: 48px; height: 48px; border-radius: 12px; }
.sheet-tile.bus-svc { background: var(--svc); color: var(--svc-ink); font: 800 1.25rem/1 var(--display); white-space: nowrap; }
.sheet-tile.stop-tile { background: var(--line); color: var(--ink); }
.stop-tile svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linejoin: round; }
/* The grab handle: dragged down, the sheet closes; a bus's, up, opens its stops. */
.sheet-grab { flex: none; height: 20px; position: relative; }
.sheet-grab.live { cursor: grab; }
/* The handle and header drag the sheet down (map.js Frame), so a finger there doesn't scroll or select. */
.sheet-top { touch-action: none; user-select: none; -webkit-user-select: none; }
.sheet-grab::before { content: ""; position: absolute; left: calc(50% - 18px); width: 36px; top: 8px; height: 4px; border-radius: 2px; background: var(--line-strong); opacity: 0.6; }
.sheet-facts { padding: 10px 16px 0; }
.sheet-scroll { flex: 0 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.sheet-body { margin-top: 12px; }
.sheet-scroll:last-child .sheet-body { padding-bottom: 16px; }
/* A grey band, edge to edge, naming what's under it; a bus's opens and closes its stops. */
.sheet-band { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; min-height: 44px; margin: 0; padding: 0 16px; border: 0; border-radius: 0; background: var(--surface-2); color: var(--muted); font: 600 0.875rem/1.2 var(--font); text-align: left; }
button.sheet-band { cursor: pointer; }
button.sheet-band:focus-visible { outline-offset: -2px; }
.sheet-band .chev { flex: none; width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.15s; }
.sheet-band[aria-expanded="true"] .chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .sheet-band .chev { transition: none; } }
.sheet-note { display: grid; gap: 12px; padding: 16px; }
.sheet-foot { flex: none; padding: 12px 16px; border-top: 1px solid var(--line); }
.sheet-foot .hint { flex-basis: 100%; margin: 0; }
.bus-info { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 12px; font-size: 0.85rem; color: var(--muted); }
.bus-moving, .bus-crowd { display: inline-flex; align-items: center; gap: 6px; }
.bus-moving .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); }
/* Three rising bars, as many filled as the bus is full. */
.bus-crowd .bars { display: inline-flex; align-items: flex-end; gap: 2px; height: 14px; }
.bus-crowd .bars i { width: 4px; border-radius: 1px; background: var(--line); }
.bus-crowd .bars i:nth-child(1) { height: 6px; }
.bus-crowd .bars i:nth-child(2) { height: 10px; }
.bus-crowd .bars i:nth-child(3) { height: 14px; }
.bus-crowd.low .bars i:nth-child(-n + 1) { background: var(--good); }
.bus-crowd.medium .bars i:nth-child(-n + 2) { background: var(--warn); }
.bus-crowd.high .bars i { background: var(--bad); }
/* The stops ahead on a rail in the route's colour, grey up to the bus. */
.bus-strip { list-style: none; margin: 0; padding: 6px 0; }
/* Each stop's row is a button opening that stop, edge to edge, tall enough to tap; a chevron at its end says so. */
.bus-stop .bus-row { display: grid; grid-template-columns: 24px 1fr auto; align-items: center; gap: 10px; width: 100%; min-height: 40px; margin: 0; padding: 0 16px; border: 0; border-radius: 0; background: none; color: inherit; font: inherit; text-align: start; }
button.bus-row { cursor: pointer; }
button.bus-row:hover { background: var(--surface-2); }
button.bus-row:focus-visible { outline-offset: -2px; }
.bus-row .go { width: 18px; height: 18px; fill: none; stroke: var(--faint); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.bus-row .go:dir(rtl) { transform: scaleX(-1); }
.bus-stop .rail { position: relative; align-self: stretch; display: grid; place-items: center; }
.bus-stop .rail::before { content: ""; position: absolute; top: 0; bottom: 0; left: calc(50% - 1.5px); width: 3px; background: linear-gradient(var(--top) 50%, var(--bottom) 50%); }
.bus-stop .dot { position: relative; width: 10px; height: 10px; box-sizing: border-box; border-radius: 50%; border: 2.5px solid var(--svc); background: var(--surface); }
.bus-stop .marker { position: relative; display: grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; background: var(--svc); box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgb(0 0 0 / 0.15); }
.bus-stop .marker svg { width: 12px; height: 12px; fill: none; stroke: var(--svc-ink); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.bus-stop.passed .name { color: var(--faint); }
.bus-stop.passed .dot { border-color: var(--line-strong); }
.bus-stop.bus .bus-row { min-height: 30px; }
.bus-stop.bus .name { font-size: 0.85rem; color: var(--muted); }
/* Where the bus is and where it's going next, bold on a taller row. */
.bus-stop.next .bus-row, .bus-stop.here .bus-row { min-height: 44px; }
.bus-stop.next .name, .bus-stop.here .name { font-weight: 700; font-size: 1.05rem; }
/* More stops beyond the last shown: the line fading out, the button under it shows them. */
.bus-stop.fade { display: grid; grid-template-columns: 24px; min-height: 16px; padding-inline: 16px; }
.bus-stop.fade .rail::before { background: linear-gradient(color-mix(in srgb, var(--svc) 35%, transparent), transparent); }
.svc-tag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.6em;
  padding: 3px 8px;
  border-radius: 7px;
  background: var(--svc, var(--muted));
  /* White or dark, whichever reads on the colour (inkOn in account/dom.js);
     with no colour of its own, the page's on its grey. */
  color: var(--svc-ink, var(--bg));
  font-weight: 700;
  font-size: 0.85rem;
  border: 0;
  font-family: inherit;
}
button.svc-tag { cursor: pointer; }
.svc-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.sheet-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.sheet-actions .btn { flex: 1 1 auto; text-align: center; }
/* Walking directions and favourite: icons beside Go there, which takes the rest of the row. */
.sheet-actions .btn.icon { flex: none; display: inline-grid; place-items: center; width: 44px; padding: 0; }
.sheet-actions .btn.icon svg { width: 20px; height: 20px; fill: none; }
.sheet-actions .btn.icon.on svg { fill: var(--accent); color: var(--accent); }
.sheet-actions .btn.icon.on:disabled { opacity: 1; }
/* The Buses tab's board, smaller: rows edge to edge in the sheet, the time beside the words. */
.sheet-board { margin: 0; }
.sheet-board .bt-row { padding: 10px 16px; gap: 3px 12px; }
.sheet-board .bt-chip { min-width: 40px; height: 26px; font-size: 0.85rem; border-radius: 7px; }
.sheet-board .bt-dir { font-size: 0.9rem; }
/* Only the minutes (and "now", the same size): a stopped service's words keep their own small size. */
.sheet-board .bt-big:not(.none) { font-size: 1.6rem; }
.sheet-board .bt-meta { padding-left: 52px; font-size: 0.75rem; }
.sheet-board .bt-then { font-size: 0.78rem; }
.sheet-more { display: block; width: 100%; margin: 0; padding: 10px 16px; border: 0; border-top: 1px solid var(--line); background: none; text-align: left; color: var(--accent); font: 600 0.875rem var(--font); cursor: pointer; }
/* A short window: the sheet's parts closer together (after the rules they tighten). */
@media (max-height: 420px) {
  .sheet-grab { height: 14px; }
  .sheet-grab::before { top: 5px; }
  .sheet-head { padding-left: 14px; }
  .sheet-facts { padding: 6px 14px 0; }
  .sheet-body { margin-top: 8px; }
  .sheet-band, .bus-stop .bus-row, .bus-stop.fade, .sheet-note, .sheet-more { padding-inline: 14px; }
  .sheet-note { padding-block: 10px; }
  .sheet-foot { padding: 8px 14px; }
  .sheet-board .bt-row { padding-inline: 14px; }
}
.bus-line { display: flex; align-items: center; gap: 10px; }

/* MapLibre's own controls, in the app's colours. */
.map .map-recentre { display: grid; place-items: center; color: var(--ink); }
.map .maplibregl-ctrl-attrib,
.map .maplibregl-ctrl-attrib.maplibregl-compact { background: color-mix(in srgb, var(--surface) 85%, transparent); color: var(--muted); font-size: 11px; }
.map .maplibregl-ctrl-attrib-button { background-color: transparent; }
.map .maplibregl-ctrl-attrib a { color: var(--muted); }
.map .maplibregl-ctrl-group { background: var(--surface); }
.map .maplibregl-ctrl-group button + button { border-top-color: var(--line); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .map .maplibregl-ctrl button .maplibregl-ctrl-icon,
  :root:not([data-theme="light"]) .map .maplibregl-ctrl-attrib-button { filter: invert(1); }
}
:root[data-theme="dark"] .map .maplibregl-ctrl button .maplibregl-ctrl-icon,
:root[data-theme="dark"] .map .maplibregl-ctrl-attrib-button { filter: invert(1); }

/* The header row's height, the same on every tab that has one. */
:root { --header-h: 60px; }

/* Settings (account/settings.js). Its title ("Settings", or a page's with a
   back arrow) takes the header's place, at the header's height, so the top
   of the screen stays put between tabs. */
.settings-tab { padding-bottom: 32px; }
body.on-settings > .nav { display: none; }
.settings-tab .settings { padding-top: 0; }
/* The list starts at the very top here, so it sticks there: at 16px (as on the
   account page, under its header) it moved down beside a page taller than it. */
.settings-tab .settings-side { top: 0; }
/* Its list's horizon runs the width of the window (and pages slide in from
   the window's edge), clipped by the page rather than the column. */
body.app { overflow-x: clip; }
/* Set on the root too: otherwise CSS hands body's clip to the window, where
   it becomes hidden, body clips nothing, and a page mid-slide widens the
   window (mobile browsers then show a sideways scrollbar). */
html { overflow-x: clip; }
.settings-tab .settings { overflow-x: visible; }
body.app > .nav { height: var(--header-h); padding-top: 0; padding-bottom: 0; }
.settings-tab .settings-title, .settings-tab .page-head { display: flex; align-items: center; height: var(--header-h); margin-bottom: -4px; }
body.app .toast { bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 12px); }

/* Now's sky (sky.js): from the top of the page down to the horizon at the
   end of the card's top, in the hour's colours: dawn, day, the golden hour,
   dusk and night, by the clock. The header, the chips and
   the card's top take the sky's ink: dark on a light sky, light on a dark
   one, and always light on a dark phone, whose skies are deep and muted so
   they never glare. The horizon's near hill is the page's own colour. */
body.sky { position: relative; overflow-x: clip;
  --s0: #121a33; --s1: #181a30; --s2: #24243a; --s3: #2e2a44; --h-far: #3a3550; --h-tree: #5b5568; --h-city: #45405f;
  --h-road: #d6cfc7; --h-post: #1c1917; --h-post-ink: #fafaf9; --sun: transparent;
}
/* Light ink, for a dark sky; dark ink, for a light one. Each colour, the
   good, late and warning ones too, reads at 4.5:1 or more over the sky
   between the words, large ones at 3:1 (the sun's end of the top is for the
   wordmark and the chips). Small coloured words (Live, a notice) sit on a chip: darker than a
   dark sky, lighter than a light one, so they read wherever they fall. */
body.sky, body.sky-dusk, body.sky-night {
  --k-bg: #121a33; --k-surface: rgb(255 255 255 / 0.08); --k-surface-2: rgb(255 255 255 / 0.13); --k-ink: #f5f3f0; --k-muted: #e7e5e4; --k-faint: #d6d3d1;
  --k-line: rgb(255 255 255 / 0.18); --k-line-strong: rgb(255 255 255 / 0.34); --k-accent: #fb923c; --k-accent-soft: rgb(251 146 60 / 0.18); --k-scheme: dark;
  --k-good: #86efac; --k-warn: #fcd34d; --k-bad: #fecaca; --k-chip: rgb(0 0 0 / 0.22); --k-accent-small: #fed7aa;
  --h-win: #fde9c9;
}
body.sky-dawn, body.sky-day, body.sky-golden {
  --k-bg: #fafaf9; --k-surface: rgb(255 255 255 / 0.5); --k-surface-2: rgb(255 255 255 / 0.65); --k-ink: #1c1917; --k-muted: #36312d; --k-faint: #3a3633;
  --k-line: rgb(28 25 23 / 0.16); --k-line-strong: rgb(28 25 23 / 0.6); --k-accent: #9a3412; --k-accent-soft: rgb(154 52 18 / 0.12); --k-scheme: light;
  --k-good: #14532d; --k-warn: #78350f; --k-bad: #701818; --k-chip: rgb(255 255 255 / 0.65); --k-accent-small: #6c2710;
  --h-win: #dbeafe;
}
body.sky-dawn { --s0: #8fc1e8; --s1: #b7d6ee; --s2: #f1d6c2; --s3: #f7c9a4; --h-far: #b3b0c3; --h-tree: #6a7568; --h-city: #cfc8d8; --sun: #fff1d6; }
body.sky-day { --s0: #5ea8e5; --s1: #8ec4ec; --s2: #c7e2f4; --s3: #e3f0f8; --h-far: #9fbcae; --h-tree: #4f6f58; --h-city: #b7cbd9; }
body.sky-golden { --s0: #78aadb; --s1: #a7c3dc; --s2: #f0cf9c; --s3: #f4b46c; --h-far: #c4a983; --h-tree: #5e5a42; --h-city: #dcc6a2; --sun: #ffe2a8; }
body.sky-dusk { --s0: #2c3566; --s1: #4a4275; --s2: #6c4260; --s3: #e08a62; --h-far: #4b3f62; --h-tree: #2c2440; --h-city: #7d6a90; --sun: #ffb37a; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body.sky {
    --k-bg: #0f0e0d; --k-surface: rgb(255 255 255 / 0.08); --k-surface-2: rgb(255 255 255 / 0.13); --k-ink: #f5f3f0; --k-muted: #e7e5e4; --k-faint: #d6d3d1;
    --k-line: rgb(255 255 255 / 0.18); --k-line-strong: rgb(255 255 255 / 0.34); --k-accent: #fb923c; --k-accent-soft: rgb(251 146 60 / 0.18); --k-scheme: dark;
    --k-good: #86efac; --k-warn: #fcd34d; --k-bad: #fecaca; --k-chip: rgb(0 0 0 / 0.22); --k-accent-small: #fed7aa;
    --h-road: #2c2926; --h-post: #d6d3d1; --h-post-ink: #0f0e0d; --h-far: #191826; --h-tree: #121110;
  }
  :root:not([data-theme="light"]) body.sky-dawn { --s0: #1d3550; --s1: #2a465f; --s2: #5c4d58; --s3: #8a6656; --h-far: #2a3446; --h-tree: #151c24; --h-city: #3d4658; --sun: #f3dcc0; --h-win: #b9c7d6; }
  :root:not([data-theme="light"]) body.sky-day { --s0: #173350; --s1: #1f4262; --s2: #2d5674; --s3: #3f6b86; --h-far: #20384a; --h-tree: #132330; --h-city: #35506a; --h-win: #b9c7d6; }
  :root:not([data-theme="light"]) body.sky-golden { --s0: #1f3550; --s1: #334356; --s2: #5e4a37; --s3: #9a6a3c; --h-far: #3a3326; --h-tree: #1a1712; --h-city: #4d4536; --sun: #f3d29a; --h-win: #b9c7d6; }
  :root:not([data-theme="light"]) body.sky-dusk { --s0: #1a1c3a; --s1: #322b54; --s2: #6c4260; --s3: #b8644f; --h-far: #2b2340; --h-tree: #17121f; --h-city: #4f4266; --sun: #f0915e; }
}
:root[data-theme="dark"] body.sky {
  --k-bg: #0f0e0d; --k-surface: rgb(255 255 255 / 0.08); --k-surface-2: rgb(255 255 255 / 0.13); --k-ink: #f5f3f0; --k-muted: #e7e5e4; --k-faint: #d6d3d1;
  --k-line: rgb(255 255 255 / 0.18); --k-line-strong: rgb(255 255 255 / 0.34); --k-accent: #fb923c; --k-accent-soft: rgb(251 146 60 / 0.18); --k-scheme: dark;
  --k-good: #86efac; --k-warn: #fcd34d; --k-bad: #fecaca; --k-chip: rgb(0 0 0 / 0.22); --k-accent-small: #fed7aa;
  --h-road: #2c2926; --h-post: #d6d3d1; --h-post-ink: #0f0e0d; --h-far: #191826; --h-tree: #121110;
}
:root[data-theme="dark"] body.sky-dawn { --s0: #1d3550; --s1: #2a465f; --s2: #5c4d58; --s3: #8a6656; --h-far: #2a3446; --h-tree: #151c24; --h-city: #3d4658; --sun: #f3dcc0; --h-win: #b9c7d6; }
:root[data-theme="dark"] body.sky-day { --s0: #173350; --s1: #1f4262; --s2: #2d5674; --s3: #3f6b86; --h-far: #20384a; --h-tree: #132330; --h-city: #35506a; --h-win: #b9c7d6; }
:root[data-theme="dark"] body.sky-golden { --s0: #1f3550; --s1: #334356; --s2: #5e4a37; --s3: #9a6a3c; --h-far: #3a3326; --h-tree: #1a1712; --h-city: #4d4536; --sun: #f3d29a; --h-win: #b9c7d6; }
:root[data-theme="dark"] body.sky-dusk { --s0: #1a1c3a; --s1: #322b54; --s2: #6c4260; --s3: #b8644f; --h-far: #2b2340; --h-tree: #17121f; --h-city: #4f4266; --sun: #f0915e; }
/* On Now, from the window's top down to its horizon; until sky.js has
   measured where that is, about where "Checking…" puts it, so the first
   frame is already under the sky. Now's tab draws it, reaching up behind the
   header and out to the window's sides, so it fades and zooms with its hills
   when the tab changes (drawn behind the page, it showed through the fading
   hills), with the header kept over it. It stops inside the near hill, solid
   for the strip's bottom 40 px or so, so their edges never meet on one line
   and blur together in the zoom. */
body.sky { --end: var(--sky-end, 22.5rem); }
body.sky #tab-now { position: relative; }
body.sky #tab-now::before {
  content: ''; position: absolute; z-index: -1; top: calc(-1 * var(--sky-top, var(--header-h, 0px))); left: calc(50% - 50vw); width: 100vw;
  height: calc(var(--end) - 24px); pointer-events: none;
  background: linear-gradient(var(--s0) 0, var(--s1) calc(var(--end) * 0.5), var(--s2) calc(var(--end) * 0.86), var(--s3) var(--end));
}
body.sky > .nav { position: relative; z-index: 1; }
body.sky:not(.on-map, .on-settings, .on-buses) > .nav, body.sky .app-chips:not(.map-pills), body.sky .where-box, body.sky .sky-head {
  --bg: var(--k-bg); --surface: var(--k-surface); --surface-2: var(--k-surface-2); --ink: var(--k-ink); --muted: var(--k-muted); --faint: var(--k-faint);
  --line: var(--k-line); --line-strong: var(--k-line-strong); --accent: var(--k-accent); --accent-soft: var(--k-accent-soft);
  --good: var(--k-good); --good-ink: var(--k-good); --warn: var(--k-warn); --warn-ink: var(--k-warn); --bad: var(--k-bad); --bad-ink: var(--k-bad);
  color-scheme: var(--k-scheme); color: var(--ink);
}
/* The card's top, with a short room above its words: the card is the
   picture, not the sky. At night the moon is in it. */
/* A focus ring in the sky's ink: the accent can be under 3:1 on a day sky. */
body.sky:not(.on-map, .on-settings, .on-buses) > .nav, body.sky .app-chips:not(.map-pills), body.sky .where-box, body.sky .sky-head { --focus: var(--k-ink); }
.sky-head { position: relative; isolation: isolate; padding-top: var(--room); --room: 34px; --moon: 28px; }
.nearby-sky { --room: 26px; --moon: 22px; }
/* Small words in the accent (a countdown, the trip's phase, when the next
   class is) take its stronger shade: the headline's is for large words. */
body.sky .sky-head :is(.countdown:not(.gone), .phase, .upcoming .eyebrow) { color: var(--k-accent-small); }
/* Small coloured words on the sky's chip (see its ink above). */
body.sky .sky-head :is(.tag, .tag.live, .phase) { background: var(--k-chip); }
body.sky .sky-head :is(.notice, .warning) { width: fit-content; padding: 4px 10px; border-radius: 10px; background: var(--k-chip); }
/* A slim band of the sky at the top of Settings and of each of its pages:
   the title in it with the moon on the right at night, ending on the low
   horizon (just the hills). The page's controls stay on the plain page under it. The page
   has a background of its own (it slides over the list), so the band draws
   its sky itself, across the window and up past the page's top. */
.page-band { position: relative; isolation: isolate; margin: 0 calc(50% - 50vw); padding: 0 calc(50vw - 50%);
  background: linear-gradient(var(--s0), var(--s1) 50%, var(--s2) 86%, var(--s3)); }
.page-band::before { content: ''; position: absolute; z-index: -1; inset: -240px 0 auto 0; height: 240px; background: var(--s0); }
.page-band .sky-head { --room: var(--header-h); --moon: 24px; padding-top: 0; }
body.sky .page-band .night-sky .moon { top: calc(50% - 12px); right: 5px; }
.page-band .horizon.low { height: 52px; margin-top: 0; }
.celestial { position: absolute; z-index: -1; inset: 0 0 auto 0; height: var(--room); pointer-events: none; }
/* Depth as the page scrolls (sky.js sets the --par-* variables): the moon
   and the stars lag and fade behind the words, and the far hills, the city
   and a low sun sink behind the near one (.horizon .depth). */
.celestial .night-sky { opacity: var(--par-fade, 1); transform: translateY(var(--par-sky, 0px)); }
.horizon .depth { transform: translateY(var(--par-far, 0px)); }
.celestial .night-sky { display: none; }
body.sky-night .celestial .night-sky { display: block; }
.sky-head .night-sky .moon { top: 6px; right: 30px; width: var(--moon); height: var(--moon);
  -webkit-mask: radial-gradient(44.6% 44.6% at 76% 28%, transparent 97%, #000 100%); mask: radial-gradient(44.6% 44.6% at 76% 28%, transparent 97%, #000 100%); }
/* Done for today: open on the page, not a panel, with more room above. */
.day-done.open .done-panel { --room: 72px; --moon: 44px; overflow: visible; padding: var(--room) 0 0; border-radius: 0; background: none; }
.day-done.open .done-label { font-size: 2.9rem; letter-spacing: -0.03em; }
/* The horizon (sky.js Horizon): across the whole window, the near hill in the
   page's colour, the rest in the hour's. */
.horizon { width: 100vw; height: 115px; margin: 10px calc(50% - 50vw) 0; }
.horizon svg { display: block; }
.horizon .far { fill: var(--h-far); }
.horizon .tree { fill: var(--h-tree); }
.horizon .city { fill: var(--h-city); }
.horizon .pole { stroke: var(--h-tree); stroke-width: 1.2; }
.horizon .flag-red { fill: #ef3340; }
.horizon .flag-white { fill: #f2efeb; }
.horizon .near { fill: var(--bg); }
.horizon .road { stroke: var(--h-road); stroke-width: 1.5; stroke-dasharray: 6 6; }
.horizon .sun { display: none; fill: var(--sun); }
body.sky-dawn .horizon .sun.dawn, body.sky-golden .horizon .sun.golden, body.sky-dusk .horizon .sun.dusk { display: inline; }
.horizon.low .sun { display: none !important; }
/* Lights on at dusk and at night: the buildings' windows and a shuttle's headlights. */
.horizon .lit, .horizon .beam { display: none; fill: #fde9c9; }
body.sky-dusk .horizon :is(.lit, .beam), body.sky-night .horizon :is(.lit, .beam) { display: inline; }
.horizon .beam { opacity: 0.12; }
.horizon .dim { opacity: 0.6; }
.horizon .bus { fill: #24211e; }
.horizon .stripe { fill: #d32f2f; }
.horizon .win { fill: var(--h-win); }
.horizon .tyre { fill: #151311; }
.horizon .hub { fill: #8a847d; }
.horizon .arch { fill: rgb(0 0 0 / 0.35); }
.horizon .lamp { fill: #fef3c7; }
.horizon .tail { fill: #ef4444; }
/* Your stop's sign (a bus on it), and your bus in its colour on the way to it. */
.horizon .sign line { stroke: var(--h-post); stroke-width: 1.6; }
.horizon .sign rect { fill: var(--h-post); }
.horizon .sign .mark { fill: var(--h-post-ink); }
.horizon .coming .body { fill: var(--svc); }
.horizon .coming .band { fill: #fff; opacity: 0.85; }
/* An estimate is the same bus drawn in outline, so it never passes for live. */
.horizon .coming.guess :is(.body, .arch) { fill: none; stroke: var(--svc); stroke-width: 1.2; stroke-linejoin: round; }
.horizon .coming.guess .roof { stroke-width: 0.8; }
.horizon .coming.guess .win { fill: none; stroke: var(--svc); stroke-width: 0.6; }
.horizon .coming.guess :is(.band, .lamp, .tail) { display: none; }
/* The road in words, under it on the ground: the scene has none. */
.road-line { display: flex; align-items: center; gap: 8px; margin-top: -12px; font-size: 1rem; }
.day-done.open .going { margin-top: 4px; }

/* Less see-through, when the device asks: the glass on the sky solid. */
@media (prefers-reduced-transparency: reduce) {
  body.sky:not(.on-map, .on-settings, .on-buses) > .nav, body.sky .app-chips:not(.map-pills), body.sky .where-box, body.sky .sky-head { --surface: var(--k-bg); --surface-2: var(--k-bg); --k-chip: var(--k-bg); }
}

/* Pull to refresh (pull.js): the sky stretches. The room above the words
   grows by --stretch, so Now's sky (down to its horizon) and a page's own
   sky get taller with it; the header, the chips and the search box follow a
   little (--lead). No browser pull of its own (Chrome's reloads the page,
   and Safari's bounce would fight this one). */
html { overscroll-behavior-y: none; }
body.pulled .sky-head { padding-top: calc(var(--room) + var(--stretch, 0px)); }
body.pulled .celestial { height: calc(var(--room) + var(--stretch, 0px)); }
body.pulled > .nav, body.pulled .app-chips:not(.map-pills), body.pulled .bt-sky-top > * { translate: 0 var(--lead, 0px); }
/* While a pull's bus drives, the horizon's own buses step aside. The pull's
   comes back to the horizon's own place and hands over at once; the ones
   driving past fade back in. */
.horizon .driving { transition: opacity 0.3s; }
body.pull-drive .horizon :is(.coming, .parked, .driving, .shuttle) { opacity: 0; transition: none; }
@media (prefers-reduced-motion: reduce) { .horizon .driving { transition: none; } }
.pull { position: fixed; z-index: 1; inset: 0; pointer-events: none; visibility: hidden; }
body.pulled .pull { visibility: visible; }
/* Over the horizon on screen, in its own numbers (pull.js sets where). */
.pull-scene { position: fixed; display: block; overflow: visible; }
.pull .p-sign line { stroke: var(--h-post, #1c1917); stroke-width: 1.6; }
.pull .p-sign :is(.plate, .glyph) { fill: var(--h-post, #1c1917); transition: fill 0.2s; }
.pull .p-sign .mark { fill: var(--h-post-ink, #fafaf9); }
.pull .p-sign .halo { fill: var(--accent-bright); opacity: 0; }
/* Your stop lights up as the bus reaches it, and goes green when it's done. */
.pull .p-sign.on .plate { fill: var(--accent-bright); }
.pull .p-sign.on .halo { opacity: 0.3; }
.pull .p-sign.done .plate { fill: var(--good); }
.pull .p-sign.done .halo { opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
  .pull .p-sign.on .halo { animation: pull-halo 0.9s ease-out infinite; transform-box: fill-box; transform-origin: center; }
}
@keyframes pull-halo { from { opacity: 0.45; transform: scale(0.8); } to { opacity: 0; transform: scale(1.6); } }
.pull .body { fill: var(--svc); }
.pull .band { fill: #fff; opacity: 0.85; }
/* No service on screen: the horizon's own shuttle, as the sky draws it. */
.pull .shuttle .body { fill: #24211e; }
.pull .shuttle .band { fill: #d32f2f; opacity: 1; }
.pull .win { fill: var(--h-win, #dbeafe); }
.pull .dim { opacity: 0.6; }
.pull .lamp { fill: #fef3c7; }
.pull .tail { fill: #ef4444; }
.pull .tyre { fill: #151311; }
.pull :is(.hub, .spoke) { fill: #8a847d; }
.pull .arch { fill: rgb(0 0 0 / 0.35); }
.pull .p-door { fill: #fbbf24; opacity: 0; }
.pull .p-honk { fill: none; stroke: var(--ink); stroke-width: 0.8; stroke-linecap: round; }
.pull .p-puffs { fill: var(--line-strong); }
/* Its headlight at dusk and at night, as on the horizon. */
.pull .beam { display: none; fill: #fde9c9; opacity: 0.12; }
body.sky-dusk .pull .beam, body.sky-night .pull .beam { display: inline; }
/* The pill, in the sky's own ink, in the room the stretch opened. */
.pull-hint { position: fixed; left: 50%; translate: -50% 0; display: flex; align-items: center; gap: 6px; margin: 0; padding: 4px 12px 4px 9px;
  border-radius: 999px; background: var(--k-chip, rgb(255 255 255 / 0.65)); color: var(--k-ink, var(--ink)); font-size: 0.8125rem; font-weight: 600; white-space: nowrap; }
.pull-hint svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; transition: rotate 0.2s; }
.pull-hint.is-armed svg { rotate: 180deg; }
.pull-hint:is(.is-busy, .is-updated, .is-fresh, .is-failed) svg { display: none; }
.pull-hint:is(.is-updated, .is-fresh)::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--k-good, var(--good)); }
.pull-hint.is-failed::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--k-warn, var(--warn)); }
@media (prefers-reduced-motion: reduce) { .pull-hint svg { transition: none; } }
