:root {
  --bg: #0b1020;
  --bg-raised: #141a2e;
  --bg-sunken: #090d19;
  --line: #232b45;
  --text: #eef1f8;
  --muted: #8b94ad;
  --faint: #5c6482;

  --ok: #3ddc97;
  --late: #ffb454;
  --bad: #ff6b6b;
  --early: #6bc6ff;
  --unknown: #7c86a3;
  --accent: #5b8cff;

  --radius: 14px;
  --radius-sm: 9px;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px -12px rgba(0, 0, 0, 0.6);
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f4f6fb;
    --bg-raised: #ffffff;
    --bg-sunken: #e9edf6;
    --line: #dde3f0;
    --text: #131829;
    --muted: #5d6580;
    --faint: #8a92aa;

    --ok: #0f9d63;
    --late: #b96a00;
    --bad: #d13438;
    --early: #0b72b5;
    --unknown: #7c86a3;
    --accent: #2b5fd9;
    --shadow: 0 1px 2px rgba(16, 24, 48, 0.06), 0 8px 24px -14px rgba(16, 24, 48, 0.28);
  }
}

* { box-sizing: border-box; }

/* A class-level `display` beats the UA stylesheet's [hidden] rule, so any
   element we toggle with .hidden = true needs this to actually disappear. */
[hidden] { display: none !important; }

body {
  margin: 0;
  /* All four insets. The top one was missing, so on an iPhone saved to the
     home screen the heading sat behind the Dynamic Island. It is 0 in a normal
     browser tab and on any device without a cutout, so max() keeps the ordinary
     case exactly as it was. */
  padding:
    env(safe-area-inset-top)
    max(14px, env(safe-area-inset-right))
    calc(28px + env(safe-area-inset-bottom))
    max(14px, env(safe-area-inset-left));
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
  max-width: 1100px;
  margin-inline: auto;
}

/* ------------------------------------------------------------------ topbar */

.topbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  /* The body already pads by the safe-area inset, so on a cutout phone this
     gives some of its own breathing room back rather than stacking 22px on top
     of the notch and pushing the heading down the screen. Falls back to the
     full 22px wherever the inset is 0, which is every desktop browser. */
  padding: max(10px, calc(22px - env(safe-area-inset-top))) 2px 14px;
  flex-wrap: wrap;
}

/* Without min-width:0 the heading refuses to shrink and pushes the refresh
   button off-screen on very narrow viewports. */
.topbar-main { min-width: 0; flex: 1 1 auto; }

.topbar h1 {
  margin: 0;
  font-size: 22px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.topbar h1 .arrow { color: var(--accent); font-weight: 400; }

.sub {
  margin: 3px 0 0;
  color: var(--muted);
  font-size: 13px;
}

.topbar-side {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.freshness {
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--faint);
  white-space: nowrap;
}
.freshness.stale { color: var(--late); }

.icon-btn {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--bg-raised);
  color: var(--muted);
  width: 34px;
  height: 34px;
  border-radius: 50%;
  font-size: 16px;
  cursor: pointer;
  transition: transform 0.4s ease, color 0.15s ease;
}
.icon-btn:hover { color: var(--text); }
.icon-btn.spinning { transform: rotate(360deg); }

/* -------------------------------------------------------------------- tabs */

.tabs {
  display: flex;
  gap: 4px;
  padding: 0 2px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line);
  /* Four short labels fit the narrowest phone; this is the safety net for
     large accessibility text sizes, which can push them past the edge. */
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab { flex: 0 0 auto; }

.tab {
  appearance: none;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  padding: 8px 12px;
  cursor: pointer;
  margin-bottom: -1px;
}
.tab.is-active {
  color: var(--text);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* ------------------------------------------------------------ alerts/banner */

.alerts { display: grid; gap: 8px; margin-bottom: 14px; }

/* Collapsed by default so alerts can't push the departures below the fold. */
.alerts-summary {
  appearance: none;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 9px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  text-align: left;
  color: var(--text);
  background: color-mix(in srgb, var(--late) 10%, var(--bg-raised));
  border: 1px solid color-mix(in srgb, var(--late) 40%, var(--line));
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  cursor: pointer;
}
.alerts-summary.severe {
  background: color-mix(in srgb, var(--bad) 12%, var(--bg-raised));
  border-color: color-mix(in srgb, var(--bad) 50%, var(--line));
}
.alerts-summary:hover { filter: brightness(1.12); }

.alerts-icon { font-size: 13px; flex-shrink: 0; color: var(--late); }
.alerts-summary.severe .alerts-icon { color: var(--bad); }
.alerts-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.alerts-chevron { color: var(--faint); flex-shrink: 0; font-size: 11px; }

.alerts-list { display: grid; gap: 8px; margin-top: 8px; }

.alert {
  border: 1px solid color-mix(in srgb, var(--late) 40%, var(--line));
  background: color-mix(in srgb, var(--late) 10%, var(--bg-raised));
  border-radius: var(--radius-sm);
  padding: 11px 13px;
}
.alert h3 { margin: 0 0 3px; font-size: 14px; font-weight: 620; }
.alert p { margin: 0; font-size: 13px; color: var(--muted); }
.alert a { color: var(--accent); }
.alert.severe {
  border-color: color-mix(in srgb, var(--bad) 45%, var(--line));
  background: color-mix(in srgb, var(--bad) 10%, var(--bg-raised));
}

.banner {
  border: 1px solid var(--line);
  background: var(--bg-raised);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-bottom: 14px;
  font-size: 13px;
  color: var(--muted);
}
.banner.error { border-color: color-mix(in srgb, var(--bad) 45%, var(--line)); color: var(--text); }
.banner code {
  background: var(--bg-sunken);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 12px;
}

/* ------------------------------------------------------------------ boards */

.boards {
  display: grid;
  /* An explicit breakpoint rather than auto-fit + minmax: the intrinsic version
     needed ~682px before it would place two columns, so the two directions
     dropped into one column on any less-than-maximised window. The point of
     this board is reading both directions at a glance, so hold them side by
     side down to phone width and only stack when there's genuinely no room. */
  /* Always two columns, one per direction, right down to the narrowest phone.
     Reading both directions at a glance is the entire point of this board, and
     stacking them buries the return sailings below the fold. The cards are
     compacted below 540px rather than the grid collapsing. */
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.board {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px;
  box-shadow: var(--shadow);
  min-width: 0;
}

.board h2 {
  margin: 0;
  font-size: 15px;
  font-weight: 620;
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
}
.dir-arrow { color: var(--accent); }
/* Keeps "→ Downtown" together when the heading wraps on a narrow phone. */
.dir-pair { white-space: nowrap; }
.dir-to { color: var(--muted); font-weight: 500; }

.board-note {
  margin: 4px 0 12px;
  font-size: 12px;
  color: var(--faint);
}

/* One legend instead of a pill on every row. */
.legend {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin: -4px 0 10px;
  padding: 0 2px;
  font-size: 12px;
  color: var(--muted);
}
.legend-item { display: inline-flex; align-items: center; gap: 6px; }

.dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-block;
}
/* A ring rather than a flat disc, so the two read apart even in greyscale. */
.dot-ok { background: var(--ok); box-shadow: 0 0 0 2.5px color-mix(in srgb, var(--ok) 22%, transparent); }
.dot-none { background: var(--unknown); opacity: 0.55; }
/* Delays and cancellations are spelled out on the card, but the legend has to
   carry the colour too: on a phone the coloured card edge is often the first
   thing seen, and the words only register on a second look. */
.dot-early { background: var(--early); }
.dot-late { background: var(--late); }
.dot-bad { background: var(--bad); }

/* ---------------------------------------------------------------- sailings */

.sailings { display: grid; gap: 10px; }

.sailing {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  padding: 12px 13px;
}
.sailing.next { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.sailing.cancelled { opacity: 0.75; }

/* The exceptions get a coloured edge as well as a worded badge, so they can be
   found by scanning the column rather than by reading every row. */
/* The last sailing is worth flagging even though the note may wrap - it sits at
   the bottom of the column, so there is nothing below it to push around. */
.sailing.is-last { border-color: color-mix(in srgb, var(--late) 40%, var(--line)); }
.last-note {
  margin-top: 6px;
  padding-top: 5px;
  border-top: 1px dashed color-mix(in srgb, var(--late) 35%, var(--line));
  font-size: 11px;
  font-weight: 600;
  color: var(--late);
  line-height: 1.3;
}

.sailing.is-late { border-left: 3px solid var(--late); }
.sailing.is-early { border-left: 3px solid var(--early); }
.sailing.is-cancelled { border-left: 3px solid var(--bad); opacity: 1; }
.sailing.is-cancelled .depart-time { opacity: 0.6; }

.sailing-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.depart-time {
  font-size: 26px;
  font-weight: 640;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.depart-time.struck { text-decoration: line-through; text-decoration-thickness: 2px; }

.countdown {
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.countdown strong { color: var(--text); font-weight: 620; }

.scheduled-was {
  font-size: 12px;
  color: var(--faint);
  text-decoration: line-through;
  font-variant-numeric: tabular-nums;
}

.pill {
  display: inline-block;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.02em;
  padding: 3px 8px;
  border-radius: 999px;
  white-space: nowrap;
}
.pill-ok      { background: color-mix(in srgb, var(--ok) 18%, transparent);      color: var(--ok); }
.pill-late    { background: color-mix(in srgb, var(--late) 20%, transparent);    color: var(--late); }
.pill-early   { background: color-mix(in srgb, var(--early) 18%, transparent);   color: var(--early); }
.pill-bad     { background: color-mix(in srgb, var(--bad) 18%, transparent);     color: var(--bad); }
.pill-muted   { background: color-mix(in srgb, var(--unknown) 16%, transparent); color: var(--unknown); }

.sailing-meta {
  margin-top: 5px;
  font-size: 12px;
  color: var(--faint);
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* ------------------------------------------------------------- connections */

.connections {
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px dashed var(--line);
  display: grid;
  gap: 5px;
}

.conn-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--faint);
  margin-bottom: 1px;
}

.conn {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  flex-wrap: wrap;
}
.conn-time { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 62px; }
.conn-line {
  font-size: 11px;
  font-weight: 650;
  padding: 2px 7px;
  border-radius: 5px;
  background: var(--line);
  color: var(--text);
  white-space: nowrap;
}
.conn-dest { color: var(--muted); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conn-slack { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.conn.tight .conn-slack { color: var(--late); }

.empty {
  color: var(--faint);
  font-size: 13px;
  padding: 10px 2px;
}
.empty-sub { margin-top: 3px; font-size: 12px; opacity: 0.8; }

/* --------------------------------------------------------------------- map */

.map-section { margin-top: 16px; }

.map-toggle {
  appearance: none;
  width: 100%;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 12px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  cursor: pointer;
}
.map-toggle[aria-expanded="true"] {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom-color: transparent;
}

.map {
  height: 340px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-sunken);
}
.map.hidden { display: none; }

.map-empty {
  margin: 0;
  padding: 14px;
  font-size: 13px;
  color: var(--faint);
  border: 1px solid var(--line);
  border-top: none;
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
  background: var(--bg-raised);
}

.leaflet-container { background: var(--bg-sunken); font: inherit; }
.boat-marker {
  width: 38px;
  height: 38px;
  line-height: 0;
  /* White halo so the hull separates from the water, then a soft shadow to lift
     it off the tiles. The ship carries its own colours - see PIRATE_SHIP. */
  filter: drop-shadow(0 0 1.5px #fff) drop-shadow(0 0 3px rgba(255, 255, 255, 0.85))
    drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
}

/* ----------------------------------------------------------------- planner */

.planner-controls {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
  display: grid;
  gap: 11px;
  margin-bottom: 14px;
}

.control-row {
  display: grid;
  grid-template-columns: 90px 1fr;
  gap: 12px;
  align-items: start;
}

.control-label {
  font-size: 12px;
  color: var(--faint);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding-top: 7px;
}

.chip-row { display: flex; gap: 6px; flex-wrap: wrap; }

.when-row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.when-row input[type="datetime-local"] {
  font: inherit;
  font-size: 13px;
  background: var(--bg-sunken);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 5px 9px;
  color-scheme: dark light;
}
.when-row input[type="datetime-local"]:focus { outline: none; border-color: var(--accent); }
.when-clear { font-size: 12px; }

.chip-text { display: block; }

.chip-sub {
  display: block;
  font-size: 11px;
  opacity: 0.7;
  font-weight: 400;
}

.chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-left: 8px;
  border-radius: 50%;
  font-size: 14px;
  line-height: 1;
  color: var(--faint);
  background: color-mix(in srgb, var(--text) 8%, transparent);
  cursor: pointer;
  flex-shrink: 0;
}
.chip-remove:hover { color: #fff; background: var(--bad); }
.chip { display: inline-flex; align-items: center; }

.save-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 7px;
}

.save-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  color: var(--text);
  background: var(--bg-sunken);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  border-radius: 999px;
  padding: 5px 12px;
  cursor: pointer;
}
.save-btn:hover { border-color: var(--accent); }
.save-btn:disabled { opacity: 0.5; cursor: default; }
.save-btn .star { color: var(--late); font-size: 14px; }

.save-hint { font-size: 12px; color: var(--faint); }

/* ------------------------------------------------------------------ account */

.account {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.account-email {
  font-size: 12px;
  color: var(--faint);
  max-width: 150px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-link {
  font-size: 12px;
  color: var(--accent);
  text-decoration: none;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line));
  border-radius: 999px;
  padding: 3px 10px;
  white-space: nowrap;
}
.account-link:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }

.search-wrap { position: relative; }

.search-wrap input {
  width: 100%;
  font: inherit;
  font-size: 14px;
  background: var(--bg-sunken);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 7px 11px;
}
.search-wrap input:focus {
  outline: none;
  border-color: var(--accent);
}

.search-results {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  overflow: hidden;
  max-height: 300px;
  overflow-y: auto;
}

.search-item {
  display: block;
  width: 100%;
  text-align: left;
  appearance: none;
  background: none;
  border: none;
  border-bottom: 1px solid var(--line);
  color: var(--text);
  font: inherit;
  padding: 9px 12px;
  cursor: pointer;
}
.search-item:last-child { border-bottom: none; }
.search-item:hover { background: var(--bg-sunken); }
.search-label { display: block; font-size: 14px; font-weight: 550; }
.search-sub { font-weight: 400; color: var(--muted); }
.search-full {
  display: block;
  font-size: 11px;
  color: var(--faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.search-empty { padding: 10px 12px; font-size: 13px; color: var(--faint); }

/* Carriage rules: what you can actually take on board. */
.carriage-note {
  border: 1px solid color-mix(in srgb, var(--late) 35%, var(--line));
  background: color-mix(in srgb, var(--late) 8%, var(--bg-raised));
  border-radius: var(--radius-sm);
  padding: 10px 13px;
  margin-bottom: 14px;
  display: grid;
  gap: 6px;
}
.carriage-row {
  display: flex;
  align-items: baseline;
  gap: 9px;
  font-size: 13px;
  color: var(--muted);
}
.carriage-row .pill { text-transform: capitalize; flex-shrink: 0; }

.plan-results { display: grid; gap: 14px; }

.journey {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
  box-shadow: var(--shadow);
}

.journey-head {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  padding-bottom: 10px;
  margin-bottom: 11px;
  border-bottom: 1px solid var(--line);
}
.journey-title { font-size: 14px; font-weight: 620; }

.options { display: grid; gap: 9px; }

.option {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  padding: 11px 12px;
}
.option:first-child { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }

.option-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.option-arrive {
  font-size: 21px;
  font-weight: 640;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.option-total { font-size: 12px; color: var(--muted); }

.legs { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

.leg {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--muted);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 10px;
  white-space: nowrap;
}
.leg-icon { font-size: 12px; }
.leg-route {
  font-weight: 650;
  color: var(--text);
  background: var(--line);
  border-radius: 4px;
  padding: 0 5px;
}
.leg-train { border-color: color-mix(in srgb, var(--early) 40%, var(--line)); }
.leg-bus   { border-color: color-mix(in srgb, var(--ok) 32%, var(--line)); }
.leg-ferry { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }

.option-status { margin-top: 7px; font-size: 12px; color: var(--faint); }

/* ---------------------------------------------------------------- schedule */

.schedule-controls {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  padding: 12px 14px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.date-quick { display: flex; gap: 6px; }

.chip {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--bg-sunken);
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
}
.chip.is-active { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }

.date-label {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--muted);
}
.date-label input[type="date"] {
  font: inherit;
  background: var(--bg-sunken);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 5px 9px;
  color-scheme: dark light;
}
.date-label.check { gap: 6px; cursor: pointer; }

.sched-list { display: grid; gap: 1px; background: var(--line); border-radius: var(--radius-sm); overflow: hidden; }

.sched-row {
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  background: var(--bg-sunken);
  font-size: 14px;
}
.sched-row.past { opacity: 0.45; }
.sched-row.cancelled .sched-depart { text-decoration: line-through; }

.sched-depart, .sched-arrive {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.sched-arrive { color: var(--muted); font-weight: 500; }
.sched-sep { color: var(--faint); }
.sched-conn { font-size: 12px; color: var(--faint); text-align: right; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------- foot */

.foot {
  margin-top: 26px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--faint);
}
.foot p { margin: 0 0 5px; }
.foot a { color: var(--muted); }
.caveat { font-style: italic; }

.skeleton {
  height: 78px;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--bg-sunken) 25%, var(--line) 50%, var(--bg-sunken) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer { to { background-position: -200% 0; } }

@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; }
  .icon-btn { transition: none; }
}

/* Narrow two-column range: the departure time is the widest thing in a card. */
@media (min-width: 540px) and (max-width: 820px) {
  .depart-time { font-size: 22px; }
  .board { padding: 13px; }
  .sailing { padding: 10px 11px; }
}

/* Phone: two columns of roughly 150px, so everything shrinks to suit. */
@media (max-width: 539px) {
  .boards { gap: 8px; }
  .board { padding: 10px 9px; border-radius: 11px; }
  .board h2 { font-size: 12.5px; gap: 4px; }
  .sailings { gap: 7px; }
  .sailing { padding: 8px 9px; }
  /* The whole point of the dot was keeping a sailing on one line, so the
     common row - time, dot, countdown - has to fit ~130px even at its widest
     ("10:45 pm  1h 47m"). Delayed and cancelled rows carry more and may wrap;
     that's fine, they're the exceptions. */
  .sailing-head { gap: 3px 5px; }
  .depart-time { font-size: 16px; letter-spacing: -0.035em; }
  .countdown { font-size: 10.5px; white-space: nowrap; }
  .dot { width: 7px; height: 7px; }
  .dot-ok { box-shadow: 0 0 0 2px color-mix(in srgb, var(--ok) 22%, transparent); }
  .sailing-meta { font-size: 10.5px; gap: 6px; margin-top: 3px; }
  .scheduled-was { font-size: 10px; }
  .pill { font-size: 9.5px; padding: 2px 6px; }
  .sched-row { padding: 7px 8px; font-size: 12px; gap: 4px; }
  .empty { font-size: 12px; padding: 8px 2px; }
  /* The crossing is always 12 minutes and the countdown says what matters, so
     the arrival line is the first thing to go when space is tight. */
  .sailing-meta { display: none; }
  .legend { font-size: 10.5px; gap: 4px 9px; margin: -2px 0 8px; }
}

/* Smallest phones still in use (iPhone SE 1st gen and similar) leave about
   100px per column, so the row needs one more notch to stay on a single line. */
@media (max-width: 360px) {
  .sailing-head { gap: 3px 4px; }
  .depart-time { font-size: 14px; }
  .countdown { font-size: 9.5px; }
  .dot { width: 6px; height: 6px; }
  .board { padding: 8px 7px; }
  .sailing { padding: 7px 7px; }
}

@media (max-width: 560px) {
  .topbar h1 { font-size: 19px; }
  .sched-row { grid-template-columns: auto auto 1fr; }
  .sched-conn { display: none; }

  /* Stack the planner controls; a 90px label gutter leaves nothing for chips. */
  .control-row { grid-template-columns: 1fr; gap: 5px; }
  .control-label { padding-top: 0; }
  .option-arrive { font-size: 19px; }
  .legs { gap: 5px; }
  .leg { font-size: 11.5px; padding: 3px 8px; }
}

/* Planner limitations. Collapsed by default - it should be findable without
   sitting between the traveller and their departure times. */
.planner-limits {
  margin-top: 1.5rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg-raised);
  padding: 0.35rem 0.9rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.planner-limits > summary {
  cursor: pointer;
  padding: 0.55rem 0;
  font-weight: 600;
  color: var(--text);
  list-style: none;
}
.planner-limits > summary::-webkit-details-marker { display: none; }
.planner-limits > summary::before {
  content: "▸ ";
  display: inline-block;
  transition: transform 0.15s ease;
}
.planner-limits[open] > summary::before { content: "▾ "; }
.planner-limits ul {
  margin: 0.2rem 0 0.6rem;
  padding-left: 1.1rem;
}
.planner-limits li { margin-bottom: 0.55rem; line-height: 1.5; }
.planner-limits strong { color: var(--text); font-weight: 600; }
.planner-limits p { margin: 0 0 0.6rem; }
.planner-limits a { color: var(--accent); }

/* -------------------------------------------------------- harbour crossing */

.boats-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 2px 2px 12px;
}
.boats-head h2 { font-size: 16px; margin: 0; }

.crossings { display: grid; gap: 12px; margin-bottom: 16px; }

.crossing {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px 14px 10px;
}

.crossing-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.crossing-name { font-weight: 600; font-size: 14px; }
.crossing-dir { font-size: 12px; color: var(--muted); }

/* The track itself. Devonport is always the left end, whichever way the boat
   is going - a track that flipped with direction would be unreadable. */
.track {
  position: relative;
  margin: 26px 0 6px;
  /* The ship is centred on its position, so the line is inset by half a ship
     at each end - otherwise a boat about to dock hangs over the end marker. */
  padding: 0 17px;
}

.track-line {
  position: relative;
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--accent) 45%, transparent),
    color-mix(in srgb, var(--accent) 18%, transparent)
  );
}

.track-dot {
  position: absolute;
  top: 50%;
  width: 9px;
  height: 9px;
  margin-top: -4.5px;
  border-radius: 50%;
  background: var(--bg-raised);
  border: 2px solid var(--accent);
}
.track-dot-a { left: -2px; }
.track-dot-b { right: -2px; }

.track-boat {
  position: absolute;
  top: 50%;
  width: 30px;
  height: 30px;
  margin: -15px 0 0 -15px;
  /* Positions only arrive every 50-60s, so glide between them rather than
     jumping. Deliberately not dead-reckoned - that would invent a position
     and then snap back when the real one lands. */
  transition: left 1.5s ease-in-out;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45));
}
.track-boat svg { width: 30px; height: 30px; display: block; }
/* The ship is drawn bow-right, so westbound is the only case that flips. */
.track-boat.is-flipped svg { transform: scaleX(-1); }

.track-end {
  position: absolute;
  top: -20px;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}
.track-end-left { left: 13px; }
.track-end-right { right: 13px; }

.crossing-meta {
  margin-top: 10px;
  font-size: 12px;
  color: var(--muted);
}

.map-note { font-size: 12px; color: var(--faint); margin: 8px 2px 0; }

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

/* Predicted next sailing. Visually quieter than the live figures above it,
   because it is inferred rather than reported. */
.crossing-next {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--line);
  font-size: 12px;
  color: var(--muted);
}
.crossing-next-label {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}

/* A boat AT has stopped reporting - almost always one turning around between
   trips. Kept on screen at its last known position, but visibly dimmed so it
   never reads as a live fix. */
.crossing.is-missing { opacity: 0.72; }
.boat-marker.is-missing { opacity: 0.55; }
