/* ============================================================
   PULS — Oslo i sanntid
   Design system. Dark-first: the city at night, the network as
   the light source. Chrome is quiet glass; data carries colour.
   ============================================================ */

@font-face {
  font-family: 'Inter';
  src: url('fonts/InterVariable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
}

:root {
  /* mode identity (Ruter semantics — do not reinvent) */
  --m-bus: #e60000;
  --m-tram: #0b91ef;
  --m-metro: #ec700c;
  --m-rail: #003087;
  --m-ferry: #682c88;
  /* lifted variants that survive a dark map */
  --m-bus-hi: #ff5a4f;
  --m-tram-hi: #2ea8ff;
  --m-metro-hi: #ff8e2b;
  --m-rail-hi: #5b8def;
  --m-ferry-hi: #a76bcf;

  --live: #2fd06c;
  --live-text: #2fd06c; /* dark: the bright green already reads on near-black */
  --planned: #a78bfa;
  --planned-deep: #7c5cd6;
  --warn: #f2b63c;
  --danger: #f0564a;

  --r-panel: 16px;
  --r-ctl: 12px;
  --r-pill: 999px;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 260ms;

  font-family: 'Inter', system-ui, -apple-system, sans-serif;
}

:root,
:root[data-theme='dark'] {
  color-scheme: dark;
  --bg: #12151a;
  --surface: rgba(17, 20, 26, 0.82);
  --surface-2: rgba(28, 33, 41, 0.85);
  --surface-solid: #171b22;
  --stroke: rgba(255, 255, 255, 0.09);
  --stroke-strong: rgba(255, 255, 255, 0.16);
  --text: #eef1f5;
  --text-2: #a2abb8;
  --text-3: #8892a0; /* ≥4.5:1 on panel glass (was #667080 · 3.68:1) */
  --shadow: 0 12px 40px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow-sm: 0 4px 16px rgba(0, 0, 0, 0.4);
  --hover: rgba(255, 255, 255, 0.07);
  --active: rgba(255, 255, 255, 0.12);
  --chip-ok-bg: rgba(47, 208, 108, 0.14);
  --chip-ok-fg: #4ade80;
  --chip-warn-bg: rgba(242, 182, 60, 0.14);
  --chip-warn-fg: #fbbf5d;
  --chip-late-bg: rgba(240, 86, 74, 0.16);
  --chip-late-fg: #ff8078;
  --chip-info-bg: rgba(255, 255, 255, 0.08);
  --chip-info-fg: #a2abb8;
  --scrim: rgba(6, 8, 11, 0.5);
}

:root[data-theme='light'] {
  --live-text: #0d8a43; /* 5.1:1 on light glass — #2fd06c was 2.01:1 */
  color-scheme: light;
  --bg: #f4f5f7;
  --surface: rgba(255, 255, 255, 0.88);
  --surface-2: rgba(244, 246, 249, 0.92);
  --surface-solid: #ffffff;
  --stroke: rgba(16, 24, 40, 0.1);
  --stroke-strong: rgba(16, 24, 40, 0.18);
  --text: #131a24;
  --text-2: #4d5866;
  --text-3: #6b7684; /* ≥4.5:1 on light glass (was #8b95a3 · 3.00:1) */
  --shadow: 0 12px 40px rgba(16, 24, 40, 0.16), 0 2px 8px rgba(16, 24, 40, 0.08);
  --shadow-sm: 0 4px 14px rgba(16, 24, 40, 0.12);
  --hover: rgba(16, 24, 40, 0.05);
  --active: rgba(16, 24, 40, 0.09);
  --chip-ok-bg: #dcf5e5;
  --chip-ok-fg: #157a3d;
  --chip-warn-bg: #fdf0d3;
  --chip-warn-fg: #8f6400;
  --chip-late-bg: #fbdedb;
  --chip-late-fg: #b02d24;
  --chip-info-bg: rgba(16, 24, 40, 0.06);
  --chip-info-fg: #4d5866;
  --scrim: rgba(16, 24, 40, 0.28);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  font-family: 'Inter', system-ui, sans-serif;
  color: var(--text);
  overflow: hidden;
  /* MapLibre already claims touch-action:none on its own canvas, but that
     only covers the map itself — double-tapping a UI element (the mode
     dock, a button, the layers panel) has no such protection and the OS's
     native double-tap-to-zoom fires there instead, zooming the whole PAGE.
     `manipulation` = pan + pinch stay allowed (so #layers-pop/#side can
     still scroll), only double-tap-zoom is dropped — CSS touch-action
     intersects down through descendants, so this covers every button and
     panel at once without listing them individually, and doesn't fight
     MapLibre's own (stricter) rule on the canvas. */
  touch-action: manipulation;
}

#map { position: absolute; inset: 0; transition: opacity 240ms var(--ease-out); }
#map:focus-visible { outline: none; }

button {
  font-family: inherit;
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--m-tram-hi);
  outline-offset: 2px;
  border-radius: 6px;
}

.num { font-variant-numeric: tabular-nums; }

/* ---- glass panel primitive ------------------------------------ */
.glass {
  background: var(--surface);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  backdrop-filter: blur(16px) saturate(1.3);
  border: 1px solid var(--stroke);
  border-radius: var(--r-panel);
  box-shadow: var(--shadow-sm);
}

/* ================= top-left: identity + live status ============ */
#topbar {
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 10px;
}

#brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 14px 9px 11px;
  cursor: default;
  user-select: none;
}

#brand .logo { width: 22px; height: 22px; flex-shrink: 0; }

#brand .word {
  font-size: 16px;
  font-weight: 830;
  letter-spacing: 0.1em;
  line-height: 1;
}

#brand .city {
  font-size: 10.5px;
  font-weight: 550;
  letter-spacing: 0.32em;
  color: var(--text-3);
  line-height: 1;
  margin-top: 3px;
}

/* live status pill — the truth signal. Green = live SSE frames. */
#status-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  border-radius: var(--r-pill);
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
}

#status-pill .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--live);
  box-shadow: 0 0 0 0 rgba(47, 208, 108, 0.45);
  animation: live-pulse 2.2s infinite;
  flex-shrink: 0;
}

@keyframes live-pulse {
  0% { box-shadow: 0 0 0 0 rgba(47, 208, 108, 0.45); }
  70% { box-shadow: 0 0 0 7px rgba(47, 208, 108, 0); }
  100% { box-shadow: 0 0 0 0 rgba(47, 208, 108, 0); }
}

#status-pill .mode-word { color: var(--live-text); letter-spacing: 0.08em; font-weight: 750; font-size: 11.5px; }
#status-pill #age { color: var(--text-3); font-weight: 500; }
#status-pill #count { font-weight: 750; }
#status-pill .veh-word { color: var(--text-2); font-weight: 500; }

#status-pill.reconnecting .dot { background: var(--warn); animation: none; }
#status-pill.reconnecting .mode-word { color: var(--warn); }

/* planned (PLANLAGT) state flips the truth signal to violet */
[data-timemode='planned'] #status-pill .dot { background: var(--planned); animation: none; }
[data-timemode='planned'] #status-pill .mode-word { color: var(--planned); }

/* ================= top-right controls ========================== */
#ctl {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 20;
  display: flex;
  gap: 8px;
}

/* "More" menu: on desktop #more-pop has no box of its own, so About/Min
   PULS/Theme sit inline in #ctl exactly as always (#more-btn stays hidden —
   nothing to collapse when there's room). Only the mobile media query below
   turns it into a real dropdown. */
#more-pop { display: contents; }
#more-btn { display: none; }
.morelbl { display: none; }

.iconbtn {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 1px solid var(--stroke);
  border-radius: var(--r-ctl);
  background: var(--surface);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  backdrop-filter: blur(16px) saturate(1.3);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  position: relative;
  transition: background var(--dur) var(--ease-out), transform 120ms var(--ease-out);
}

.iconbtn:hover { background: var(--surface-2); }
.iconbtn:active { transform: scale(0.94); }
.iconbtn svg { width: 18px; height: 18px; stroke: var(--text-2); fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.iconbtn.on svg { stroke: var(--text); }

#locate-btn.on svg circle { fill: var(--live); stroke: var(--live); }
#locate-btn.on svg path { stroke: var(--live); }
#locate-btn.loading svg { animation: soft-pulse 1s ease-in-out infinite; }
#locate-btn.locate-error svg { stroke: var(--danger); }

.iconbtn .bubble {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--warn);
  color: #201500;
  font-size: 10.5px;
  font-weight: 800;
  display: grid;
  place-items: center;
  line-height: 1;
}

/* ---- layers popover ---- */
#layers-pop {
  position: absolute;
  top: 50px;
  right: 0;
  width: 248px;
  padding: 14px;
  display: none;
  flex-direction: column;
  gap: 10px;
  box-shadow: var(--shadow);
  /* the menu grew past the viewport once the data layers were added — scroll
     inside the panel rather than spilling the legend off-screen */
  max-height: calc(100vh - 70px);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* browsers default to an overlay scrollbar that's invisible until hovered,
     so overflow content cutting off at the panel edge reads as "the panel
     just ends here" rather than "scroll for more" — keep a thin one visible */
  scrollbar-width: thin;
  scrollbar-color: var(--stroke) transparent;
}
#layers-pop::-webkit-scrollbar { width: 6px; }
#layers-pop::-webkit-scrollbar-thumb { background: var(--stroke); border-radius: 3px; }
#layers-pop::-webkit-scrollbar-track { background: transparent; }

#layers-pop.open { display: flex; animation: pop-in var(--dur) var(--ease-out); }

@keyframes pop-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.98); }
  to { opacity: 1; transform: none; }
}

#layers-pop h3 {
  margin: 0;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
}

.lrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  font-weight: 550;
}

/* iOS-style switch, mode-agnostic */
.switch { position: relative; width: 36px; height: 21px; flex-shrink: 0; }
.switch input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.switch .track {
  position: absolute; inset: 0;
  border-radius: 11px;
  background: var(--active);
  transition: background var(--dur);
}
.switch .track::after {
  content: '';
  position: absolute;
  top: 2.5px; left: 2.5px;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  transition: transform var(--dur) var(--ease-out);
}
.switch input:checked + .track { background: var(--live); }
.switch input:checked + .track::after { transform: translateX(15px); }
.switch input:focus-visible + .track { outline: 2px solid var(--m-tram-hi); outline-offset: 2px; }

.tbane-legend { display: flex; gap: 5px; align-items: center; }
.tbane-legend .lb {
  width: 24px; height: 20px;
  border-radius: 6px;
  display: grid; place-items: center;
  color: #fff;
  font-size: 11.5px;
  font-weight: 800;
}

.legend-note {
  font-size: 11px;
  color: var(--text-3);
  line-height: 1.5;
  border-top: 1px solid var(--stroke);
  padding-top: 9px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Precipitation readout. Sits under the header and follows the map centre —
   the one sentence the nowcast layer exists to produce. */
#rain-pill {
  position: absolute;
  top: 64px;
  left: 14px;
  z-index: 30;
  display: none;
  flex-direction: column;
  padding: 7px 11px;
  font-size: 12px;
  line-height: 1.35;
  max-width: 210px;
}
#rain-pill.on { display: flex; animation: pop-in var(--dur) var(--ease-out); }
#rain-pill b { font-weight: 650; }
#rain-pill span { color: var(--text-3); font-size: 10.5px; }
@media (max-width: 560px) { #rain-pill { top: 58px; font-size: 11.5px; } }

/* Collapsible groups in the layer menu. The menu grew from 2 toggles to 9 plus
   three legends, and a flat list of everything makes nothing findable — so the
   two things people change often (routes, stops) stay at the top level and the
   rest is one click away. Native <details> keeps it keyboard- and
   screen-reader-accessible for free. */
.lgroup { border-top: 1px solid var(--stroke); padding-top: 8px; }
.lgroup > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  list-style: none;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
  user-select: none;
}
.lgroup > summary::-webkit-details-marker { display: none; }
.lgroup > summary::before {
  content: '';
  width: 5px; height: 5px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--dur) var(--ease-out);
  flex-shrink: 0;
}
.lgroup[open] > summary::before { transform: rotate(45deg); }
.lgroup > summary:hover { color: var(--text-2); }
.lgroup > *:not(summary) { margin-top: 8px; }
/* Count of active layers inside a collapsed group — otherwise a layer you
   switched on is invisible once the group folds shut. */
.gcount {
  margin-left: auto;
  min-width: 15px;
  padding: 0 4px;
  border-radius: 7px;
  background: var(--accent, #2fd06c);
  color: #08130c;
  font-size: 9.5px;
  line-height: 15px;
  text-align: center;
  letter-spacing: 0;
}
.gcount:empty { display: none; }

.seasonnote {
  margin: 6px 0 0;
  font-size: 10.5px;
  line-height: 1.45;
  color: var(--text-3);
}
.seasonnote.warn { color: var(--warn); }

.rain-legend { display: flex; flex-direction: column; gap: 3px; }
.rain-legend .ramp {
  height: 7px;
  border-radius: 4px;
  background: linear-gradient(90deg,
    rgba(105,200,255,0.5), rgba(56,162,240,0.75), rgba(42,95,208,0.9), rgba(138,63,208,0.95), rgba(190,60,150,1));
}
.rain-legend .ends { display: flex; justify-content: space-between; font-size: 10px; color: var(--text-3); }

.aboutlink {
  border-top: 1px solid var(--stroke);
  padding-top: 9px;
  font-size: 11.5px;
  color: var(--text-3);
  text-decoration: none;
}
.aboutlink:hover { color: var(--text-2); text-decoration: underline; }

/* Freshness chip under each external-data toggle. This is a compliance
   surface, not decoration: NLOD §6 forbids presenting data in a distorted or
   incorrect manner, so every live layer states when its data actually arrived
   and says "ukjent" — in the warning colour — once it can no longer vouch
   for it. Never hide this to save space. */
.fresh {
  font-size: 10.5px;
  color: var(--text-3);
  margin: -6px 0 2px 0;
  font-variant-numeric: tabular-nums;
}
.fresh.stale { color: var(--warn); font-weight: 600; }

/* About/credits panel. The attribution block is a licence obligation, so it is
   styled to be read, not to be skipped past. */
.credit-h {
  margin: 14px 0 4px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
}
.body-note.credit { margin: 0 0 7px; }
.credit-note { display: block; color: var(--text-3); font-size: 11px; margin-top: 1px; }

.legend-note .k { display: flex; align-items: center; gap: 7px; }
.legend-note .sym { width: 14px; text-align: center; flex-shrink: 0; font-weight: 700; color: var(--text-2); }

/* ================= search overlay ============================== */
#search-scrim {
  position: absolute;
  inset: 0;
  z-index: 40;
  background: var(--scrim);
  display: none;
}
#search-scrim.open { display: block; animation: fade-in 180ms ease; }
@keyframes fade-in { from { opacity: 0; } }

#search-box {
  position: absolute;
  top: 84px;
  left: 50%;
  transform: translateX(-50%);
  width: min(520px, calc(100vw - 28px));
  z-index: 41;
  display: none;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow);
}
#search-box.open { display: flex; animation: pop-in var(--dur) var(--ease-out); }

#search-box .inrow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 16px;
  border-bottom: 1px solid var(--stroke);
}
#search-box .inrow svg { width: 17px; height: 17px; stroke: var(--text-3); fill: none; stroke-width: 2; flex-shrink: 0; }

#search {
  flex: 1;
  border: none;
  background: none;
  color: var(--text);
  font-family: inherit;
  font-size: 15.5px;
  padding: 13px 0;
  outline: none;
}
#search::placeholder { color: var(--text-3); }
#search::-webkit-search-cancel-button { display: none; }

#search-box kbd {
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 650;
  color: var(--text-3);
  border: 1px solid var(--stroke-strong);
  border-radius: 5px;
  padding: 2px 6px;
}

#search-results { max-height: min(380px, 46vh); overflow-y: auto; overscroll-behavior: contain; }

.search-hit {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 16px;
  cursor: pointer;
  font-size: 13.5px;
}
.search-hit:hover, .search-hit.sel { background: var(--hover); }
.search-hit .mico {
  width: 26px; height: 26px;
  border-radius: 8px;
  display: grid; place-items: center;
  flex-shrink: 0;
}
.search-hit .mico svg { width: 14px; height: 14px; fill: #fff; }
.search-hit .nm { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 550; }
.search-hit .loc { color: var(--text-3); font-weight: 450; }
.search-empty { padding: 14px 16px; color: var(--text-3); font-size: 13px; }
.search-label {
  padding: 10px 16px 4px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* ================= mode dock =================================== */
#dock {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 20;
  display: flex;
  align-items: stretch;
  gap: 2px;
  padding: 5px;
}

.dockbtn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 7px 13px 6px;
  border: none;
  background: none;
  border-radius: 11px;
  cursor: pointer;
  transition: background 160ms var(--ease-out);
  min-width: 58px;
}

.dockbtn:hover { background: var(--hover); }
.dockbtn svg { width: 17px; height: 17px; fill: var(--text-3); transition: fill 160ms; }
.dockbtn .lbl {
  font-size: 10px;
  font-weight: 650;
  letter-spacing: 0.02em;
  color: var(--text-2);
}
.dockbtn .cnt {
  font-size: 10px;
  font-weight: 600;
  color: var(--text-3);
  min-height: 12px;
}

/* Quiet resting state: the dock is chrome, not data — an 18% tint with the
   icon carrying the mode colour replaces the former 11-swatch rainbow strip
   ("chrome is quiet glass; data carries colour"). Full fill only on press. */
.dockbtn.on { background: color-mix(in srgb, var(--mode-bg, var(--active)) 18%, transparent); }
.dockbtn.on svg { fill: var(--mode-bg, var(--active)); }
.dockbtn.on .lbl { color: var(--text-1); }
.dockbtn.on .cnt { color: var(--text-3); }
.dockbtn.on:active { background: var(--mode-bg, var(--active)); }
.dockbtn.on:active svg { fill: #fff; }

.dockbtn.off svg { fill: var(--text-3); opacity: 0.45; }

/* stroke-drawn dock icons (bike) follow the same colour states as fills */
.dockbtn svg.stroke-ic { fill: none !important; stroke: var(--text-3); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.dockbtn.on svg.stroke-ic { stroke: var(--mode-bg, var(--active)); }
.dockbtn.off svg.stroke-ic { stroke: var(--text-3); opacity: 0.45; }
.dockbtn.off .lbl, .dockbtn.off .cnt { color: var(--text-3); opacity: 0.6; }

#dock .sep { width: 1px; background: var(--stroke); margin: 6px 4px; }

#tl-toggle { min-width: 50px; }
#tl-toggle svg { fill: none !important; stroke: var(--text-2); stroke-width: 1.8; stroke-linecap: round; }
#tl-toggle.on { background: var(--active); }
#tl-toggle.on svg, [data-timemode='planned'] #tl-toggle svg { stroke: var(--text); }
[data-timemode='planned'] #tl-toggle { background: var(--planned-deep); }
[data-timemode='planned'] #tl-toggle svg { stroke: #fff; }
[data-timemode='planned'] #tl-toggle .lbl { color: #fff; }

/* ================= timeline ==================================== */
#timeline {
  position: absolute;
  left: 50%;
  bottom: 88px;
  transform: translateX(-50%) translateY(8px);
  z-index: 19;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  width: min(560px, calc(100vw - 24px));
  opacity: 0;
  pointer-events: none;
  /* visibility too: a closed (invisible) timeline was still tabbable — a
     keyboard user could flip into PLANLAGT from a control they can't see */
  visibility: hidden;
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out),
    visibility 0s var(--dur);
}

#timeline.open {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out),
    visibility 0s 0s;
  transform: translateX(-50%) translateY(0);
}

#tl-play {
  width: 34px; height: 34px;
  border-radius: 50%;
  border: none;
  background: var(--text);
  color: var(--bg);
  display: grid;
  place-items: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 120ms var(--ease-out);
}
#tl-play:active { transform: scale(0.92); }
#tl-play svg { width: 13px; height: 13px; fill: var(--bg); }
[data-timemode='planned'] #tl-play { background: var(--planned); }
[data-timemode='planned'] #tl-play svg { fill: #201542; }

.tl-mid { flex: 1; display: flex; flex-direction: column; gap: 5px; min-width: 0; }

#tl-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 22px;
  background: none;
  cursor: pointer;
  margin: 0;
}
#tl-slider::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(to right, var(--planned) var(--fill, 0%), var(--active) var(--fill, 0%));
}
#tl-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: #fff;
  border: none;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
  margin-top: -6px;
}
#tl-slider::-moz-range-track { height: 4px; border-radius: 2px; background: var(--active); }
#tl-slider::-moz-range-progress { height: 4px; border-radius: 2px; background: var(--planned); }
#tl-slider::-moz-range-thumb { width: 16px; height: 16px; border-radius: 50%; background: #fff; border: none; box-shadow: 0 1px 4px rgba(0,0,0,.4); }

.tl-row2 { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

#tl-clock { font-size: 12px; font-weight: 650; color: var(--text-2); }
[data-timemode='planned'] #tl-clock { color: var(--planned); }

.seg { display: flex; background: var(--active); border-radius: 8px; padding: 2px; gap: 1px; }
.seg button {
  border: none;
  background: none;
  color: var(--text-3);
  font-size: 10.5px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 6px;
  cursor: pointer;
}
.seg button.on { background: var(--surface-solid); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.25); }

#tl-live {
  border: none;
  border-radius: var(--r-pill);
  background: var(--live);
  color: #063018;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.04em;
  padding: 6px 13px;
  cursor: pointer;
  flex-shrink: 0;
  display: none;
}
[data-timemode='planned'] #tl-live { display: block; }

/* ---- PLANLAGT ribbon + viewport frame ---- */
#planned-ribbon {
  position: absolute;
  top: 14px;
  left: 50%;
  transform: translateX(-50%) translateY(-6px);
  z-index: 18;
  background: var(--planned-deep);
  color: #fff;
  border-radius: var(--r-pill);
  padding: 8px 18px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.03em;
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
  gap: 9px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur), transform var(--dur) var(--ease-out);
  white-space: nowrap;
}
#planned-ribbon .sub { font-weight: 500; color: rgba(255, 255, 255, 0.75); }
[data-timemode='planned'] #planned-ribbon { opacity: 1; transform: translateX(-50%) translateY(0); }
/* the open timeline card carries the planned context itself — no ribbon too */
[data-timemode='planned'] #timeline.open ~ #planned-ribbon { opacity: 0; }

#tl-planned-head {
  display: none;
  width: 100%;
  font-size: 11px;
  font-weight: 750;
  letter-spacing: 0.05em;
  color: var(--planned);
  padding-bottom: 2px;
}
[data-timemode='planned'] #timeline #tl-planned-head { display: block; }
[data-timemode='planned'] #timeline { flex-wrap: wrap; border-color: var(--planned-deep); }

#planned-frame {
  position: absolute;
  inset: 0;
  z-index: 17;
  pointer-events: none;
  box-shadow: inset 0 0 0 3px var(--planned-deep);
  opacity: 0;
  transition: opacity var(--dur);
}
[data-timemode='planned'] #planned-frame { opacity: 0.85; }

/* ================= detail side panel =========================== */
#side {
  position: absolute;
  top: 14px;
  right: 14px;
  max-height: calc(100% - 28px);
  z-index: 30;
  width: 344px;
  max-width: calc(100vw - 28px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow);
  transform: translateX(calc(100% + 20px));
  transition: transform 320ms var(--ease-out);
  visibility: hidden;
}
#side.open { transform: none; visibility: visible; }

#side header { padding: 16px 18px 12px; border-bottom: 1px solid var(--stroke); position: relative; flex-shrink: 0; }

.line-badge {
  display: inline-grid;
  place-items: center;
  min-width: 40px;
  height: 32px;
  padding: 0 9px;
  border-radius: 9px;
  font-weight: 830;
  font-size: 17px;
  letter-spacing: 0.01em;
  vertical-align: middle;
}

#side header h2 {
  display: inline;
  font-size: 15.5px;
  font-weight: 700;
  margin: 0 0 0 4px;
  vertical-align: middle;
  letter-spacing: -0.01em;
}

#side header .chips { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-weight: 700;
  font-size: 11.5px;
  line-height: 1.5;
  border: none;
}
.chip.ok { background: var(--chip-ok-bg); color: var(--chip-ok-fg); }
.chip.warn { background: var(--chip-warn-bg); color: var(--chip-warn-fg); }
.chip.late { background: var(--chip-late-bg); color: var(--chip-late-fg); }
.chip.info { background: var(--chip-info-bg); color: var(--chip-info-fg); font-weight: 600; }
.chip.clickable { cursor: pointer; }

#side .close, #side .follow {
  position: absolute;
  top: 12px;
  border: none;
  background: none;
  border-radius: 9px;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--text-2);
  transition: background 140ms;
}
#side .close { right: 12px; }
#side .follow { right: 48px; }
#side .close:hover, #side .follow:hover { background: var(--hover); color: var(--text); }
#side .close svg, #side .follow svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; }
#side .follow.on { background: var(--chip-ok-bg); color: var(--chip-ok-fg); }

#side .body { overflow-y: auto; overscroll-behavior: contain; flex: 1; padding: 14px 18px; font-size: 13px; }

#side footer {
  padding: 10px 18px;
  border-top: 1px solid var(--stroke);
  color: var(--text-3);
  font-size: 11px;
  line-height: 1.5;
  flex-shrink: 0;
}

/* next-stop hero block */
/* prominent action link: open operator app / buy ticket */
.cta-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: fit-content;
  max-width: 100%;
  margin: 12px auto 0;
  padding: 9px 16px;
  border-radius: 10px;
  background: var(--cta, #2f6fe0);
  color: var(--cta-fg, #fff);
  font-weight: 650;
  font-size: 12.5px;
  text-decoration: none;
  text-align: center;
  transition: filter 140ms, transform 90ms;
}
.cta-btn:hover { filter: brightness(1.12); }
.cta-btn:active { transform: scale(0.98); }
.buy-hint { font-size: 11px; color: var(--text-3); margin: 7px 2px 0; line-height: 1.4; }

.brand-logo {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  vertical-align: -5px;
  margin-right: 8px;
}

.next-hero { margin-bottom: 14px; }
.next-hero .k { font-size: 10.5px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-3); margin-bottom: 3px; }
.next-hero .v { font-size: 16.5px; font-weight: 750; letter-spacing: -0.01em; }
.next-hero .t { font-size: 12.5px; color: var(--text-2); margin-top: 2px; }
.next-hero .t b { color: var(--text); }

/* journey rail — stop progress with a coloured spine */
.journey { list-style: none; margin: 4px 0 0; padding: 0; }
.journey li {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 4.5px 0;
  position: relative;
  padding-left: 22px;
  font-size: 12.5px;
}
.journey li::before {
  /* spine segment */
  content: '';
  position: absolute;
  left: 6.5px;
  top: -2px;
  bottom: -2px;
  width: 3px;
  background: var(--rail-c, var(--text-3));
  opacity: 0.9;
}
.journey li::after {
  /* stop dot */
  content: '';
  position: absolute;
  left: 3.5px;
  top: 50%;
  transform: translateY(-50%);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--surface-solid);
  border: 2.5px solid var(--rail-c, var(--text-3));
}
.journey li:first-child::before { top: 50%; }
.journey li:last-child::before { bottom: 50%; }
.journey li.past { opacity: 0.38; }
.journey li.next { font-weight: 750; }
.journey li.next::after {
  background: var(--rail-c, var(--text));
  animation: live-pulse 2s infinite;
}
.journey .snm { flex: 1; min-width: 0; }
.journey .t { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--text-2); }
.journey .t .late { color: var(--chip-late-fg); font-weight: 700; }
.journey .t s { color: var(--text-3); }


details.jwrap summary {
  cursor: pointer;
  color: var(--text-2);
  font-size: 12px;
  font-weight: 600;
  padding: 6px 0;
  user-select: none;
}
details.jwrap summary:hover { color: var(--text); }

/* departure board */
.dep-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 0;
  border-bottom: 1px solid var(--stroke);
}
.dep-row:last-child { border-bottom: none; }
/* Car-share photos. Hyre ships a 640×480 press shot per model, Getaround the
   owner's own 320×240 snapshot — both 4:3-ish, so a fixed ratio box keeps the
   panel from reflowing as they load. Hidden entirely if the fetch fails. */
.car-photo {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: 12px;
  background: var(--surface-2);
  margin-bottom: 12px;
  display: block;
}
.car-photo[hidden] { display: none; }
/* Scooter feeds (Ryde) ship a studio render on white rather than a photo of
   the actual vehicle — cropping it to fill would cut the handlebar off, so
   letterbox it on its own white card instead. */
.car-photo.product { object-fit: contain; background: #fff; padding: 6px; }
/* one Hyre model per row: thumbnail, name + specs, count */
.model-row { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--stroke); }
.model-row:last-child { border-bottom: none; }
.model-row img { width: 56px; height: 40px; object-fit: cover; border-radius: 7px; background: var(--surface-2); flex-shrink: 0; }
.model-row .m-main { flex: 1; min-width: 0; }
.model-row .m-name { font-size: 13.5px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.model-row .m-sub { font-size: 11.5px; color: var(--text-3); margin-top: 1px; }
.model-row .m-count { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 15px; flex-shrink: 0; }
/* clickable departures: bigger comfortable tap target + affordance */
.dep-row.clickable {
  cursor: pointer;
  padding: 13px 8px;
  margin: 0 -8px;
  border-radius: 10px;
  border-bottom: 1px solid var(--stroke);
  min-height: 30px;
  transition: background 130ms;
}
.dep-row.clickable:hover, .dep-row.clickable:focus-visible { background: var(--hover); outline: none; }
.dep-row.clickable:active { background: var(--active); }
.dep-row .dep-go { color: var(--text-3); font-size: 20px; line-height: 1; margin-left: -4px; flex-shrink: 0; }
.dep-row.flight-row.open { background: var(--hover); border-bottom-color: transparent; }
/* expanded flight info under an airport board row */
.flight-detail {
  padding: 4px 10px 12px;
  margin: 0 -8px 2px;
  border-radius: 0 0 10px 10px;
  border-bottom: 1px solid var(--stroke);
  background: color-mix(in srgb, var(--hover) 55%, transparent);
}
.flight-detail .dep-row { padding: 5px 0; border-bottom: none; }
.flight-detail .dep-row .dest { font-weight: 550; color: var(--text-2); }
.dep-row .badge {
  min-width: 36px;
  text-align: center;
  padding: 3px 7px;
  border-radius: 7px;
  font-weight: 800;
  font-size: 12.5px;
  flex-shrink: 0;
  color: #fff;
}
.dep-row .dest { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 550; font-size: 13px; }
.dep-row .plat { color: var(--text-3); font-size: 10.5px; flex-shrink: 0; font-weight: 550; }
.dep-row .when { font-weight: 750; white-space: nowrap; font-variant-numeric: tabular-nums; font-size: 13px; }
.dep-row .when.late { color: var(--chip-late-fg); }
.dep-row .when .rt { color: var(--live-text); font-size: 8px; vertical-align: 2px; margin-right: 3px; }
.dep-row.cancelled .dest, .dep-row.cancelled .when { text-decoration: line-through; color: var(--text-3); }

/* alerts */
.alert-card {
  border: 1px solid var(--stroke);
  border-left: 3px solid var(--warn);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 10px;
  position: relative;
}
.alert-card b { font-size: 13px; display: block; padding-right: 22px; }
.alert-card .desc { font-size: 12px; color: var(--text-2); margin-top: 4px; line-height: 1.5; }
.alert-card .meta { font-size: 10.5px; color: var(--text-3); margin-top: 6px; }
.alert-card .lines { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.alert-card .lines span {
  background: var(--chip-warn-bg);
  color: var(--chip-warn-fg);
  border-radius: 5px;
  padding: 1px 7px;
  font-size: 11px;
  font-weight: 700;
}
.alert-card .dismiss {
  position: absolute;
  top: 8px; right: 8px;
  border: none;
  background: none;
  color: var(--text-3);
  cursor: pointer;
  font-size: 13px;
  padding: 2px 5px;
  border-radius: 6px;
}
.alert-card .dismiss:hover { background: var(--hover); color: var(--text); }

/* skeleton loading shimmer */
.skel { border-radius: 8px; background: var(--hover); position: relative; overflow: hidden; }
.skel::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--active), transparent);
  animation: shimmer 1.4s infinite;
}
@keyframes shimmer { 100% { transform: translateX(100%); } }
.skel-row { height: 15px; margin: 11px 0; }

.body-note { color: var(--text-2); font-size: 12.5px; line-height: 1.55; }
.body-note.err { color: var(--chip-late-fg); }
.closed-note {
  background: var(--chip-info-bg);
  border-radius: 10px;
  padding: 11px 13px;
  color: var(--text-2);
  font-size: 12.5px;
  line-height: 1.55;
}

/* PLANLAGT-mode panel note: same shape as closed-note, violet-tinted so the
   time-travel context is unmistakable inside the panel too */
.planned-note {
  background: color-mix(in srgb, var(--planned, #8b6ad6) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--planned, #8b6ad6) 35%, transparent);
  border-radius: 10px;
  padding: 11px 13px;
  color: var(--text-2);
  font-size: 12.5px;
  line-height: 1.55;
  margin-bottom: 10px;
}

/* second line under the departure time: struck rutetid for late rows,
   "rutetid" tag for schedule-only rows — visible on touch, not tooltip-only */
.dep-row .when .tsub {
  display: block;
  font-size: 9.5px;
  font-weight: 500;
  color: var(--text-3);
}
.dep-row .when.late .tsub { color: var(--chip-warn-fg, #e8a13c); }

/* cancelled stop rows in the journey list */
.journey li.cancelled .snm { color: var(--text-3); }


.live-dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--live);
  margin-right: 5px;
  animation: soft-pulse 1.8s infinite;
}
@keyframes soft-pulse { 50% { opacity: 0.35; } }

/* ================= account + favourites ======================== */
#fav-ind {
  display: inline-flex;
  align-items: center;
  border: none;
  background: var(--chip-warn-bg);
  color: var(--chip-warn-fg);
  border-radius: var(--r-pill);
  padding: 2px 9px;
  font-size: 10.5px;
  font-weight: 750;
  letter-spacing: 0.03em;
  cursor: pointer;
  font-family: inherit;
}

.auth-box {
  border: 1px solid var(--stroke);
  border-radius: 12px;
  padding: 12px;
  margin-bottom: 16px;
}

.uinput {
  width: 100%;
  box-sizing: border-box;
  background: var(--chip-info-bg);
  border: 1px solid var(--stroke);
  border-radius: 9px;
  color: var(--text);
  font-family: inherit;
  font-size: 13.5px;
  padding: 9px 11px;
  margin-bottom: 8px;
  outline: none;
}
.uinput:focus-visible { border-color: var(--m-tram-hi); outline: none; }
.uinput::placeholder { color: var(--text-3); }

.auth-row { display: flex; gap: 8px; margin-top: 2px; }
.auth-err { color: var(--chip-late-fg); font-size: 12px; min-height: 16px; margin: 2px 0 4px; }

.ubtn {
  flex: 1;
  border: 1px solid var(--stroke-strong);
  background: none;
  color: var(--text);
  border-radius: 9px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 700;
  padding: 8px 10px;
  cursor: pointer;
  transition: background 140ms;
}
.ubtn:hover { background: var(--hover); }
.ubtn.primary { background: var(--text); color: var(--bg); border-color: transparent; }
.ubtn.primary:hover { opacity: 0.9; }

.usec {
  margin: 4px 0 10px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
}

.favlist, .favsugg { display: flex; flex-wrap: wrap; gap: 6px; }
.favsugg { margin-top: 8px; }

.favline {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 4px 9px;
  font-size: 12.5px;
  font-weight: 800;
  font-family: inherit;
}
.favline svg, .favline .fl-ico svg { width: 12px; height: 12px; fill: #fff; }
.favline .fl-ico { display: inline-flex; }
.favline button {
  border: none;
  background: rgba(0, 0, 0, 0.25);
  color: #fff;
  border-radius: 50%;
  width: 16px;
  height: 16px;
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  margin-left: 2px;
}
.favline.add { cursor: pointer; opacity: 0.92; }
.favline.add:hover { opacity: 1; }

.chip.favstar {
  background: var(--chip-info-bg);
  color: var(--text-2);
  cursor: pointer;
  font-size: 13px;
  padding: 2px 9px;
  font-family: inherit;
}
.chip.favstar.on { background: var(--chip-warn-bg); color: var(--chip-warn-fg); }

.linkbtn {
  border: none;
  background: none;
  color: var(--text-2);
  text-decoration: underline;
  cursor: pointer;
  font-family: inherit;
  font-size: 11px;
  padding: 0;
}
.linkbtn:hover { color: var(--text); }

/* ================= stale banner ================================ */
#stale-banner {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  z-index: 45;
  background: var(--warn);
  color: #251a00;
  padding: 7px 18px;
  border-radius: 0 0 12px 12px;
  font-size: 12.5px;
  font-weight: 650;
  box-shadow: var(--shadow-sm);
  display: none;
}

/* boot hint toast (GTFS still loading server-side) */
#boot-toast {
  position: absolute;
  bottom: 88px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 18;
  padding: 9px 16px;
  border-radius: var(--r-pill);
  font-size: 12.5px;
  font-weight: 550;
  color: var(--text-2);
  display: none;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}
#boot-toast.show { display: flex; }
#boot-toast .spin {
  width: 12px; height: 12px;
  border: 2px solid var(--active);
  border-top-color: var(--text-2);
  border-radius: 50%;
  animation: rot 0.9s linear infinite;
  flex-shrink: 0;
}
@keyframes rot { to { transform: rotate(360deg); } }

/* ================= maplibre chrome ============================= */
.maplibregl-ctrl-bottom-right { bottom: 6px; right: 6px; }
.maplibregl-ctrl-bottom-left { bottom: 6px; left: 6px; }

.maplibregl-ctrl-group {
  background: var(--surface) !important;
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  border: 1px solid var(--stroke);
  border-radius: var(--r-ctl) !important;
  box-shadow: var(--shadow-sm) !important;
  overflow: hidden;
}
.maplibregl-ctrl-group button { width: 36px !important; height: 34px !important; background: none !important; }
.maplibregl-ctrl-group button + button { border-top: 1px solid var(--stroke) !important; }
.maplibregl-ctrl-group button .maplibregl-ctrl-icon { filter: var(--ctl-icon-filter, none); }
:root[data-theme='dark'] { --ctl-icon-filter: invert(0.85); }

.maplibregl-ctrl-attrib {
  background: var(--surface) !important;
  border-radius: 8px 0 0 0 !important;
  font-size: 10px !important;
  color: var(--text-3) !important;
  font-family: 'Inter', system-ui, sans-serif !important;
}
.maplibregl-ctrl-attrib a { color: var(--text-3) !important; }

.maplibregl-ctrl-scale {
  background: none !important;
  border: 1px solid var(--text-3) !important;
  border-top: none !important;
  color: var(--text-3) !important;
  font-size: 9.5px !important;
  font-family: 'Inter', system-ui, sans-serif !important;
}

/* ================= mobile ====================================== */
@media (max-width: 640px) {
  #topbar { top: 10px; left: 10px; gap: 6px; }
  #brand { padding: 8px 11px 8px 9px; }
  #brand .city { display: none; }
  #brand .word { display: none; } /* logo alone — the status pill must never truncate to "PLA" */
  #status-pill { padding: 7px 10px; font-size: 11.5px; gap: 6px; }
  #status-pill .veh-word { display: none; }
  /* the exact vehicle count is nice-to-have telemetry, not core info — on a
     narrow phone it's what was pushing the pill wide enough to run under the
     control row (a real overlap, not just tight: status text was rendering
     underneath the locate button). "LIVE" is the signal that matters. */
  #status-pill #count { display: none; }

  #ctl { top: 10px; right: 10px; gap: 4px; }
  .iconbtn { width: 36px; height: 36px; }

  /* collapse About/Min PULS/Theme into a dropdown so the always-visible row
     (locate, search, layers, more) stays short enough not to collide with
     the status pill on narrow phones */
  #more-btn { display: grid; place-items: center; }
  #more-pop {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    flex-direction: column;
    gap: 2px;
    padding: 6px;
    width: 190px;
    box-shadow: var(--shadow);
  }
  #more-pop.open { display: flex; animation: pop-in var(--dur) var(--ease-out); }
  #more-pop .iconbtn {
    display: flex;
    align-items: center;
    width: 100%;
    height: auto;
    justify-content: flex-start;
    gap: 12px;
    padding: 10px 12px;
    border: none;
    box-shadow: none;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  #more-pop .iconbtn:active { background: var(--active); }
  #more-pop .iconbtn svg { width: 18px; height: 18px; flex-shrink: 0; }
  .morelbl { display: inline; font-size: 14px; font-weight: 600; color: var(--text); }

  #search-box { top: 60px; }

  #dock {
    bottom: 10px;
    width: calc(100vw - 20px);
    justify-content: space-between;
    padding: 4px;
    /* 10 toggles no longer fit a phone width — swipe the dock sideways */
    overflow-x: auto;
    scrollbar-width: none;
    /* …but a hidden scrollbar meant the last buttons (Elspark, Delebil,
       Bysykkel, Tid) were simply invisible on a phone, with nothing to suggest
       they existed. Fade the edge that still has content behind it. */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
  }
  #dock.at-start { -webkit-mask-image: none; mask-image: none; }
  #dock.at-end {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 26px);
    mask-image: linear-gradient(to right, transparent, #000 26px);
  }
  #dock::-webkit-scrollbar { display: none; }
  /* the fade tells thumbs there is more dock off-screen (Tid lives there) */
  #dock { mask-image: linear-gradient(to right, black calc(100% - 26px), transparent); }
  .dockbtn { min-width: 44px; flex: 1 0 auto; padding: 7px 4px 5px; }
  #dock .sep { margin: 4px 2px; }

  #timeline { bottom: 78px; padding: 10px 12px; gap: 9px; }

  #planned-ribbon { top: auto; bottom: 140px; max-width: calc(100vw - 20px); white-space: normal; text-align: center; }

  /* bottom sheet */
  #side {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    max-width: none;
    max-height: 62vh;
    border-radius: 18px 18px 0 0;
    transform: translateY(105%);
  }
  #side.open { transform: none; }
  #side::before {
    content: '';
    display: block;
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: var(--stroke-strong);
    margin: 8px auto 0;
    flex-shrink: 0;
  }
  #side header { padding-top: 8px; }
  #side .close, #side .follow { width: 38px; height: 38px; }

  .maplibregl-ctrl-bottom-right .maplibregl-ctrl-group { display: none; }
  #boot-toast { bottom: 78px; }
}

/* iOS safe areas */
@supports (padding: env(safe-area-inset-bottom)) {
  @media (max-width: 640px) {
    #dock { bottom: calc(10px + env(safe-area-inset-bottom)); }
    #side { padding-bottom: env(safe-area-inset-bottom); }
  }
}

/* ================= reduced motion ============================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---- W3 accessibility additions ---- */

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* touch targets: expand the tiny ✕/segment controls to ≥24px hit areas
   without changing their visual size. .alert-card .dismiss is already
   position:absolute (its own rule above) — that already establishes a
   positioning context for the ::before below, so it must NOT be re-declared
   position:relative here (same specificity, later in cascade, would silently
   knock it out of its top/right-anchored slot). */
.favline button {
  position: relative;
}
.favline button::before, .alert-card .dismiss::before {
  content: '';
  position: absolute;
  inset: -8px;
}
#tl-speed button { min-width: 34px; min-height: 30px; }
#tl-slider::-webkit-slider-thumb { width: 20px; height: 20px; }
#tl-slider::-moz-range-thumb { width: 20px; height: 20px; }

/* focus ring on keyboard-focused departure rows was replaced by an invisible
   1.07:1 background change — restore the standard ring */
.dep-row.clickable:focus-visible {
  outline: 2px solid var(--m-tram-hi, #2ea8ff);
  outline-offset: -2px;
}

/* following pill (UX J5): follow-mode must announce itself */
#follow-pill {
  position: absolute;
  top: 64px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 18;
  display: none;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 650;
}
#follow-pill.on { display: inline-flex; }
#follow-pill button {
  border: none;
  background: var(--hover);
  color: inherit;
  border-radius: 999px;
  padding: 3px 9px;
  cursor: pointer;
  font-size: 10.5px;
}

/* On mid-width desktops the centered dock's right end runs under the open
   side panel — the Tid toggle became unclickable exactly when a board was
   open. Keep the panel above the dock row there. */
@media (min-width: 641px) and (max-width: 1450px) {
  #side { bottom: 96px; }
}

/* account danger zone */
.danger-zone { margin-top: 16px; border-top: 1px solid var(--stroke); padding-top: 10px; }
.danger-zone summary { cursor: pointer; font-size: 12px; color: var(--chip-late-fg); font-weight: 650; }
.ubtn.danger { background: var(--chip-late-bg); color: var(--chip-late-fg); width: 100%; margin-top: 8px; }
