/**************************************************************
Base
***************************************************************/
/* Mobile-first: these values target phones. Larger screens adjust them in Media queries. */
:root {
  /* Colors */
  --colorBg: #f3f5f7;
  --colorSurface: #ffffff;
  --colorText: #0f172a;
  --colorTextMuted: #475569;
  --colorBorder: #cbd5e1;
  --colorPrimary: #0b5d7a;
  --colorPrimaryText: #ffffff;
  --colorHeaderBg: #0b5d7a;
  --colorHeaderText: #ffffff;
  --colorLive: #047857;
  --colorDanger: #b91c1c;
  --colorDangerBg: #fee2e2;
  --colorFocus: #b45309;
  --colorRail: #142e57; /* Skyline's own color from the GTFS feed */
  --colorYouAreHere: #2563eb;
  --shadowRaised: 0 -2px 16px rgb(15 23 42 / 0.18);
  --shadowMarker: 0 1px 4px rgb(15 23 42 / 0.35);
  /* A route's second direction. The first uses the route's own color (--routeColor). */
  --colorDirectionB: #c2410c;
  --colorDirectionBText: #ffffff;

  /* Type */
  --fontBody: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --fontSizeSm: 0.9375rem;
  --fontSizeBase: 1.125rem;
  --fontSizeLg: 1.375rem;
  --fontSizeXl: 2rem;

  /* Spacing */
  --spaceXs: 0.25rem;
  --spaceSm: 0.5rem;
  --spaceMd: 1rem;
  --spaceLg: 1.5rem;
  --spaceXl: 2.5rem;

  /* Shape and sizing */
  --radiusSm: 0.5rem;
  --radiusMd: 0.875rem;
  --radiusPill: 999px;
  --tapTarget: 3rem;
  --contentWidth: 40rem;
  --tabBarHeight: 3.75rem;
  /* Space the fixed bottom tab bar takes, including the iPhone home indicator */
  --bottomBarSpace: calc(var(--tabBarHeight) + env(safe-area-inset-bottom));
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Component display rules shouldn't be able to override the hidden attribute */
[hidden] {
  display: none !important;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  padding-bottom: var(--bottomBarSpace); /* Keep content clear of the tab bar */
  background: var(--colorBg);
  color: var(--colorText);
  font-family: var(--fontBody);
  font-size: var(--fontSizeBase);
  line-height: 1.5;
}

main {
  flex: 1;
}

h1,
h2,
p {
  margin: 0;
}

h1 {
  font-size: var(--fontSizeXl);
  line-height: 1.15;
}

h2 {
  font-size: var(--fontSizeLg);
  line-height: 1.25;
}

a {
  color: var(--colorPrimary);
}

:focus-visible {
  outline: 3px solid var(--colorFocus);
  outline-offset: 3px;
}

/**************************************************************
Utilities
***************************************************************/
.container {
  width: 100%;
  max-width: var(--contentWidth);
  margin-inline: auto;
  padding-inline: var(--spaceMd);
}

.visuallyHidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/**************************************************************
Components
***************************************************************/
/* Buttons */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tapTarget);
  padding: var(--spaceSm) var(--spaceLg);
  border: 2px solid var(--colorPrimary);
  border-radius: var(--radiusPill);
  font: inherit;
  font-weight: 700;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
}

.buttonPrimary {
  background: var(--colorPrimary);
  color: var(--colorPrimaryText);
}

.buttonSecondary {
  background: var(--colorSurface);
  color: var(--colorPrimary);
}

.button[aria-disabled='true'] {
  opacity: 0.6;
  cursor: progress;
}

.buttonIcon {
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
}

/* Form controls */
.formLabel {
  font-size: var(--fontSizeLg);
  font-weight: 700;
}

.formHint {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

.formError {
  color: var(--colorDanger);
  font-weight: 600;
}

.textInput {
  width: 100%;
  min-height: 3.75rem;
  padding: var(--spaceSm) var(--spaceMd);
  border: 2px solid var(--colorBorder);
  border-radius: var(--radiusMd);
  background: var(--colorSurface);
  color: var(--colorText);
  font: inherit;
}

.textInput[aria-invalid='true'] {
  border-color: var(--colorDanger);
}

/* Arrival card */
.arrivalCard {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--spaceMd);
  padding: var(--spaceMd);
  border: 1px solid var(--colorBorder);
  border-radius: var(--radiusMd);
  background: var(--colorSurface);
}

.routeBadge {
  display: grid;
  place-items: center;
  min-width: var(--tapTarget);
  min-height: var(--tapTarget);
  padding-inline: var(--spaceXs);
  border-radius: var(--radiusSm);
  /* TheBus's route colors where it sets them (routesList.js and mapView.js set these) */
  background: var(--routeColor, var(--colorPrimary));
  color: var(--routeTextColor, var(--colorPrimaryText));
  font-size: var(--fontSizeLg);
  font-weight: 800;
  line-height: 1;
}

/* Long route names (SKYLINE) would make the badge about 100px wide and squeeze the headsign on phones */
.routeBadge.isLongName {
  font-size: var(--fontSizeSm);
}

.arrivalDetails {
  min-width: 0;
}

.arrivalHeadsign {
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.arrivalMeta {
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--spaceSm);
  margin-top: var(--spaceXs);
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

.arrivalStatus {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  font-weight: 700;
}

.arrivalCard[data-status='live'] .arrivalStatus {
  color: var(--colorLive);
}

/* Pulsing dot next to "Live" */
.arrivalCard[data-status='live'] .arrivalStatus::before {
  content: '';
  width: 0.55em;
  height: 0.55em;
  border-radius: 50%;
  background: currentColor;
  animation: livePulse 2s ease-in-out infinite;
}

.arrivalCard[data-status='canceled'] .arrivalStatus {
  color: var(--colorDanger);
}

.arrivalCard[data-status='canceled'] .arrivalTime,
.arrivalCard[data-status='canceled'] .arrivalMinutes {
  color: var(--colorTextMuted);
  text-decoration: line-through;
}

.arrivalMinutes {
  line-height: 1;
  text-align: right;
  white-space: nowrap;
}

.minutesValue {
  display: block;
  font-size: var(--fontSizeXl);
  font-weight: 800;
}

.minutesUnit {
  display: block;
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
  font-weight: 600;
}

/* "Next bus arriving to this stop:" sits above the first card, and a divider below that card
   sets it apart from the later buses. The divider only shows while the label does. */
.nextBusLabel {
  font-size: var(--fontSizeSm);
  font-weight: 700;
}

.nextBusLabel:not([hidden]) + .arrivalList > .arrivalCard:first-child:not(:last-child) {
  position: relative;
  margin-bottom: var(--spaceMd);
}

.nextBusLabel:not([hidden]) + .arrivalList > .arrivalCard:first-child:not(:last-child)::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: calc((var(--spaceMd) + var(--spaceSm)) / -2 - 1px);
  left: 0;
  border-bottom: 2px solid var(--colorTextMuted);
}

@keyframes livePulse {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.35;
  }
}

/**************************************************************
Header
***************************************************************/
.siteHeader {
  padding-top: env(safe-area-inset-top);
  background: var(--colorHeaderBg);
  color: var(--colorHeaderText);
}

.siteHeader :focus-visible {
  outline-color: var(--colorHeaderText);
}

.siteHeaderInner {
  display: flex;
  align-items: center;
  min-height: 3.5rem;
}

.brandLink {
  display: inline-flex;
  align-items: center;
  min-height: var(--tapTarget);
  color: inherit;
  font-size: var(--fontSizeLg);
  font-weight: 800;
  text-decoration: none;
}

/**************************************************************
Tab bar
***************************************************************/
/* Fixed to the bottom on phones, within thumb reach. Becomes the top navigation on wider screens. */
.tabBar {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1100; /* Above Leaflet's map controls (1000) and the map sheet */
  padding-bottom: env(safe-area-inset-bottom);
  border-top: 1px solid var(--colorBorder);
  background: var(--colorSurface);
}

.tabBarBrand {
  display: none;
}

.tabList {
  display: grid;
  grid-auto-columns: 1fr;
  grid-auto-flow: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

.tabLink {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spaceXs);
  min-height: var(--tabBarHeight);
  border-top: 3px solid transparent;
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
}

/* The current tab gets a bar as well as color, so it doesn't rely on color alone */
.tabLink[aria-current='page'] {
  border-top-color: var(--colorPrimary);
  color: var(--colorPrimary);
  font-weight: 800;
}

.tabIcon {
  width: 1.5rem;
  height: 1.5rem;
}

/**************************************************************
Search
***************************************************************/
.searchPage {
  padding-block: var(--spaceXl);
}

.searchTitle {
  margin-bottom: var(--spaceSm);
}

.searchIntro {
  margin-bottom: var(--spaceLg);
  color: var(--colorTextMuted);
}

.stopForm {
  display: grid;
  gap: var(--spaceSm);
}

.stopInput {
  font-size: var(--fontSizeXl);
  font-weight: 700;
  letter-spacing: 0.08em;
}

.stopForm .button {
  width: 100%;
  margin-top: var(--spaceSm);
}

/**************************************************************
Routes
***************************************************************/
.routesPage {
  display: grid;
  gap: var(--spaceMd);
  padding-block: var(--spaceLg);
}

.routeFilterField {
  display: grid;
  gap: var(--spaceXs);
}

.routeCount {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

.routeList {
  display: grid;
  gap: var(--spaceSm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.routeLink {
  display: flex;
  align-items: center;
  gap: var(--spaceMd);
  min-height: var(--tapTarget);
  padding: var(--spaceSm) var(--spaceMd);
  border: 1px solid var(--colorBorder);
  border-radius: var(--radiusMd);
  background: var(--colorSurface);
  color: var(--colorText);
  text-decoration: none;
}

/* Same width for every badge, so the route names line up down the list */
.routeLink .routeBadge {
  flex-shrink: 0;
  min-width: 5.5rem;
}

.routeDescription {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.routeEmpty {
  padding: var(--spaceMd);
  border: 1px solid var(--colorBorder);
  border-radius: var(--radiusMd);
  background: var(--colorSurface);
}

/* "Can't find your bus?" at the bottom of the routes list, the way to the stop number search */
.searchPrompt {
  display: grid;
  justify-items: start;
  gap: var(--spaceSm);
  padding: var(--spaceLg) 0 var(--spaceMd);
}

.searchPromptTitle {
  font-size: var(--fontSizeLg);
}

.searchPromptText {
  color: var(--colorTextMuted);
}

.searchPrompt .button {
  gap: var(--spaceSm);
  margin-top: var(--spaceXs);
}

/**************************************************************
Stop arrivals
***************************************************************/
.stopArrivals {
  display: flex;
  flex-direction: column;
  gap: var(--spaceMd);
  padding-top: var(--spaceLg);
}

.stopHeader {
  display: grid;
  gap: var(--spaceXs);
}

.stopHeaderRow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spaceMd);
}

.stopNumber {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
  font-weight: 700;
}

.changeStopLink {
  display: inline-flex;
  align-items: center;
  min-height: var(--tapTarget);
  font-weight: 600;
}

/* Stop names are long and all caps ("S BERETANIA ST + PUNCHBOWL ST"), so start smaller on phones */
.stopTitle {
  font-size: var(--fontSizeLg);
  overflow-wrap: anywhere;
}

.stopSubtitle {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

.arrivalList {
  display: grid;
  gap: var(--spaceSm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.stopMessage {
  display: grid;
  gap: var(--spaceSm);
  padding: var(--spaceLg);
  border: 1px solid var(--colorBorder);
  border-radius: var(--radiusMd);
  background: var(--colorSurface);
}

.unavailableMessage {
  border-color: var(--colorDanger);
  background: var(--colorDangerBg);
}

/**************************************************************
Refresh bar
***************************************************************/
/* Sticks just above the tab bar so Refresh stays within thumb reach */
.refreshBar {
  position: sticky;
  bottom: var(--bottomBarSpace);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spaceMd);
  margin-top: auto;
  margin-inline: calc(var(--spaceMd) * -1);
  padding: var(--spaceSm) var(--spaceMd);
  border-top: 1px solid var(--colorBorder);
  background: var(--colorSurface);
}

.updatedTime {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

/**************************************************************
Map
***************************************************************/
/* The map page fills the screen above the tab bar and never scrolls */
.mapBody {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

.mapPage {
  position: relative;
  min-height: 0;
}

.map {
  position: absolute;
  inset: 0;
}

.map.leaflet-container {
  background: var(--colorBg);
  font-family: var(--fontBody);
}

/* Floating notes over the map: the zoom hint, location messages, and the no-JS notice */
.mapNotice {
  position: absolute;
  top: var(--spaceMd);
  left: 50%;
  z-index: 1001;
  width: max-content;
  max-width: calc(100% - 7rem); /* Clear of the zoom buttons */
  padding: var(--spaceSm) var(--spaceMd);
  border: 1px solid var(--colorBorder);
  border-radius: var(--radiusMd);
  background: var(--colorSurface);
  color: var(--colorText);
  font-size: var(--fontSizeSm);
  font-weight: 600;
  text-align: center;
  transform: translateX(-50%);
}

.mapMessage {
  top: calc(var(--spaceMd) * 4);
}

/* Invisible while empty, but still in the page so screen readers announce new messages */
.mapMessage:empty {
  opacity: 0;
  pointer-events: none;
}

/* "Show stops near me" and "Route 42 stops": bottom center, within thumb reach, clear of the map credits */
.floatingButton {
  position: absolute;
  bottom: calc(var(--spaceXl) * 1.5);
  left: 50%;
  z-index: 1001;
  gap: var(--spaceSm);
  box-shadow: var(--shadowRaised);
  white-space: nowrap;
  transform: translateX(-50%);
}

/* Stop pins: the Leaflet marker is a 32px tap area around a smaller visible dot.
   The dot (::before) and the street-level icon share one grid cell so they stack. */
.stopPin {
  display: grid;
  place-items: center;
  border-radius: 50%;
}

.stopPin::before,
.stopPinIcon {
  grid-area: 1 / 1;
}

.stopPin::before {
  content: '';
  width: 1rem;
  height: 1rem;
  border: 3px solid var(--colorSurface);
  border-radius: 50%;
  background: var(--colorPrimary);
  box-shadow: 0 0 0 1px var(--colorPrimary);
  transition: transform 0.12s ease, background-color 0.12s ease;
}

.stopPinIcon {
  display: none;
  width: 1rem;
  height: 1rem;
  color: var(--colorSurface);
  pointer-events: none;
}

/* Pressed: the dot shrinks and darkens, so a tap feels like pressing a button */
.stopPin:active::before {
  filter: brightness(0.75);
  transform: scale(0.85);
}

/* Skyline stations are squares in Skyline's color, so they differ by shape as well as color */
.railPin::before {
  width: 1.125rem;
  height: 1.125rem;
  border-radius: 3px;
  background: var(--colorRail);
  box-shadow: 0 0 0 1px var(--colorRail);
}

/* Zoomed out, pins shrink to small dots so a route's line stays visible between them.
   The selected stop keeps its full size. mapView.js sets isZoomedOut. */
.map.isZoomedOut .stopPin:not(.isSelected)::before {
  width: 0.5rem;
  height: 0.5rem;
  border-width: 2px;
}

.stopPin.isSelected::before {
  width: 1.5rem;
  height: 1.5rem;
  border-width: 4px;
  box-shadow: 0 0 0 3px var(--colorText);
}

/* Street level: pins grow into stop markers with an icon, a clear change from the dots */
.map.isStreetLevel .stopPin::before {
  width: 1.75rem;
  height: 1.75rem;
}

.map.isStreetLevel .railPin::before {
  border-radius: 6px;
}

.map.isStreetLevel .stopPin.isSelected::before {
  width: 2.25rem;
  height: 2.25rem;
}

.map.isStreetLevel .stopPinIcon {
  display: block;
}

.youAreHere {
  border: 3px solid var(--colorSurface);
  border-radius: 50%;
  background: var(--colorYouAreHere);
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--colorYouAreHere) 30%, transparent);
}

/* The sheet rises from the bottom on phones and docks beside the map on wider screens */
.mapSheet {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1002;
  display: flex;
  flex-direction: column;
  max-height: 60%;
  border-top: 1px solid var(--colorBorder);
  border-radius: var(--radiusMd) var(--radiusMd) 0 0;
  background: var(--colorBg);
  box-shadow: var(--shadowRaised);
}

.sheetHeader {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  column-gap: var(--spaceSm);
  padding: var(--spaceSm) var(--spaceMd);
}

.sheetBack {
  display: inline-flex;
  flex-basis: 100%;
  align-items: center;
  gap: var(--spaceXs);
  min-height: var(--tapTarget);
  padding: 0;
  border: 0;
  background: none;
  color: var(--colorPrimary);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.sheetHeading {
  flex: 1;
  min-width: 0;
  padding-top: var(--spaceSm);
}

.sheetEyebrow {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
  font-weight: 700;
}

.sheetTitle {
  font-size: var(--fontSizeLg);
  overflow-wrap: anywhere;
}

/* Focus lands here when the sheet opens so screen readers read the title; it isn't a control */
.sheetTitle:focus {
  outline: none;
}

.sheetClose {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: var(--tapTarget);
  height: var(--tapTarget);
  padding: 0;
  border: 1px solid var(--colorBorder);
  border-radius: 50%;
  background: var(--colorSurface);
  color: var(--colorText);
  cursor: pointer;
}

.sheetBody {
  display: grid;
  gap: var(--spaceSm);
  padding: 0 var(--spaceMd) var(--spaceMd);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.sheetNearby,
.sheetStop {
  display: grid;
  gap: var(--spaceSm);
}

.sheetRoutes,
.sheetUpdated,
.sheetBody .attribution {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

.sheetNote {
  padding: var(--spaceMd);
  border: 1px solid var(--colorBorder);
  border-radius: var(--radiusMd);
  background: var(--colorSurface);
}

.sheetStopLink {
  width: 100%;
}

.nearbyList {
  display: grid;
  gap: var(--spaceSm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.nearbyStop {
  display: grid;
  gap: var(--spaceXs);
  width: 100%;
  min-height: var(--tapTarget);
  padding: var(--spaceMd);
  border: 1px solid var(--colorBorder);
  border-radius: var(--radiusMd);
  background: var(--colorSurface);
  color: var(--colorText);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.nearbyName {
  font-weight: 700;
  overflow-wrap: anywhere;
}

.nearbyMeta {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

/* Route lines. CSS stroke overrides Leaflet's default blue, so lines follow the route color. */
.routeLineCasing {
  stroke: var(--colorSurface);
}

.routeLine {
  stroke: var(--routeColor, var(--colorPrimary));
}

.routeLine.isDirectionB {
  stroke: var(--colorDirectionB);
}

/* The trip back: round dots, faded, so the selected direction reads first */
.routeLine.isReturn {
  opacity: 0.6;
  stroke-dasharray: 1 10;
}

/* The flow: light dashes gliding along the selected line in the direction buses travel.
   It plays 4 times (about 6 seconds) when a route opens or the direction changes, then
   mapView.js removes it. It only animates the dash offset of one thin line, and it never loops forever. */
.routeFlow {
  stroke: var(--colorSurface);
  stroke-dasharray: 6 14;
  stroke-linecap: round;
  animation: routeFlow 1.5s linear 4;
}

/* The dash pattern repeats every 20px, so sliding 200px moves it smoothly with no jump between passes */
@keyframes routeFlow {
  from {
    stroke-dashoffset: 200;
  }

  to {
    stroke-dashoffset: 0;
  }
}

/* Each direction's color, used by its button, the stop timeline, and the line legend.
   The first direction uses the route's color; the second is orange. */
.sheetRoute {
  --directionAColor: var(--routeColor, var(--colorPrimary));
  --directionATextColor: var(--routeTextColor, var(--colorPrimaryText));
  --selectedDirectionColor: var(--directionAColor);
  --otherDirectionColor: var(--colorDirectionB);
}

.sheetRoute.isDirectionB {
  --selectedDirectionColor: var(--colorDirectionB);
  --otherDirectionColor: var(--directionAColor);
}

.directionToggle {
  display: grid;
  gap: var(--spaceSm);
}

/* An outlined button with a thick color stripe and a switch icon, so it reads as tappable */
.directionButton {
  --directionColor: var(--directionAColor);
  --directionTextColor: var(--directionATextColor);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spaceSm);
  min-height: var(--tapTarget);
  padding: var(--spaceSm) var(--spaceMd);
  border: 2px solid var(--directionColor);
  border-left-width: 0.5rem;
  border-radius: var(--radiusMd);
  background: var(--colorSurface);
  color: var(--colorText);
  font: inherit;
  font-size: var(--fontSizeSm);
  font-weight: 700;
  text-align: left;
  cursor: pointer;
  transition: transform 0.12s ease;
}

.directionButton.isDirectionB {
  --directionColor: var(--colorDirectionB);
  --directionTextColor: var(--colorDirectionBText);
}

.directionSwitchIcon {
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
}

/* The chosen direction fills with its color and gets a check mark, so it doesn't rely on color alone */
.directionButton[aria-pressed='true'] {
  background: var(--directionColor);
  color: var(--directionTextColor);
}

.directionButton[aria-pressed='true'] .directionLabel::before {
  content: '✓  ' / '';
}

.directionButton[aria-pressed='true'] .directionSwitchIcon {
  display: none;
}

.directionButton:active {
  transform: scale(0.98);
}

/* A small legend with line samples in each direction's color. Plain text, so it doesn't look like a button. */
.routeHint {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spaceXs) var(--spaceLg);
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

.routeHintItem {
  display: inline-flex;
  align-items: center;
  gap: var(--spaceSm);
}

.lineSample {
  width: 1.75rem;
  border-top: 4px solid var(--selectedDirectionColor);
  border-radius: 2px;
}

.lineSample.isDotted {
  border-top-style: dotted;
  border-top-color: var(--otherDirectionColor);
}

/* The route's stops as a timeline: a line down the left with a ring at each stop */
.routeStopList {
  margin: 0;
  padding: 0;
  list-style: none;
}

.routeStopItem {
  position: relative;
  padding-left: var(--spaceLg);
}

.routeStopItem::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--spaceSm);
  width: 4px;
  background: var(--selectedDirectionColor);
  transform: translateX(-50%);
}

.routeStopItem:first-child::before {
  top: 50%;
}

.routeStopItem:last-child::before {
  bottom: 50%;
}

.routeStopItem::after {
  content: '';
  position: absolute;
  top: 50%;
  left: var(--spaceSm);
  width: 0.875rem;
  height: 0.875rem;
  border: 3px solid var(--selectedDirectionColor);
  border-radius: 50%;
  background: var(--colorSurface);
  transform: translate(-50%, -50%);
}

/* Each stop row ends in a chevron, so the list reads as tappable */
.routeStop {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--spaceSm);
  width: 100%;
  min-height: var(--tapTarget);
  padding: var(--spaceSm) var(--spaceMd);
  border: 0;
  border-bottom: 1px solid var(--colorBorder);
  background: none;
  color: var(--colorText);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.routeStop:active {
  background: var(--colorSurface);
}

.routeStopText {
  display: grid;
}

.rowChevron {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--colorTextMuted);
}

.routeStopName {
  font-size: var(--fontSizeSm);
  font-weight: 700;
  overflow-wrap: anywhere;
}

.routeStopMeta {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

.busSummary {
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
  font-weight: 600;
}

/* Live buses: a pill with a bus glyph and the bus number in the route's colors.
   The Leaflet marker is the tap area; the pill can spill past it for longer numbers. */
.busMarker {
  display: grid;
  place-items: center;
}

.busBadge {
  display: inline-flex;
  align-items: center;
  gap: var(--spaceXs);
  padding: var(--spaceXs) var(--spaceSm);
  border: 2px solid var(--colorSurface);
  border-radius: var(--radiusPill);
  background: var(--routeColor, var(--colorPrimary));
  color: var(--routeTextColor, var(--colorPrimaryText));
  box-shadow: var(--shadowMarker);
  font-size: var(--fontSizeSm);
  font-weight: 800;
  line-height: 1;
  white-space: nowrap;
}

.busGlyph {
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
}

/* Buses take their direction's color, and fade when heading the other way, like the dotted line */
.busMarker.isDirectionB .busBadge {
  background: var(--colorDirectionB);
  color: var(--colorDirectionBText);
}

.busMarker.isOtherDirection .busBadge {
  opacity: 0.5;
}

/* Sonar: one ring that grows from a bus and fades when it reports a new position.
   It only animates size and transparency, which phones handle on the graphics chip.
   z-index -1 keeps the ring behind the badge. mapView.js adds isPinging. */
.busMarker::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: -1;
  width: 3.5rem;
  height: 3.5rem;
  border: 3px solid var(--routeColor, var(--colorPrimary));
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -50%) scale(0.8);
}

.busMarker.isDirectionB::before {
  border-color: var(--colorDirectionB);
}

.busMarker.isPinging::before {
  animation: busPing 1.4s ease-out 1;
}

/* The ring starts about the badge's width, so it's visible as soon as it grows past it */
@keyframes busPing {
  0% {
    opacity: 0.9;
    transform: translate(-50%, -50%) scale(0.8);
  }

  70% {
    opacity: 0.4;
  }

  100% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(2);
  }
}

/* Zoomed out, buses crowd together downtown, so they show just the glyph.
   The number is still in each bus's details. */
.map.isZoomedOut .busNumber {
  display: none;
}

/* Bus details: Leaflet's popup, recolored so it follows dark mode */
.map .leaflet-popup-content-wrapper,
.map .leaflet-popup-tip {
  background: var(--colorSurface);
  color: var(--colorText);
}

.busDetails {
  display: grid;
  gap: var(--spaceXs);
  font-family: var(--fontBody);
  font-size: var(--fontSizeSm);
}

/* Leaflet gives popup paragraphs large margins */
.map .busDetails p {
  margin: 0;
}

.busDetailsTitle {
  font-size: var(--fontSizeBase);
  font-weight: 800;
}

/**************************************************************
Error page
***************************************************************/
.errorPage {
  display: grid;
  justify-items: start;
  gap: var(--spaceMd);
  padding-block: var(--spaceXl);
}

/**************************************************************
Footer
***************************************************************/
.siteFooter {
  padding-block: var(--spaceLg);
  color: var(--colorTextMuted);
  font-size: var(--fontSizeSm);
}

/**************************************************************
Media queries
***************************************************************/
@media (prefers-color-scheme: dark) {
  :root {
    --colorBg: #0b1220;
    --colorSurface: #131c2e;
    --colorText: #e5e9f0;
    --colorTextMuted: #a3adbd;
    --colorBorder: #2b3850;
    --colorPrimary: #5cc8e8;
    --colorPrimaryText: #062330;
    --colorHeaderBg: #0f2a3a;
    --colorHeaderText: #e5e9f0;
    --colorLive: #6ee7b7;
    --colorDanger: #fca5a5;
    --colorDangerBg: #3a1418;
    --colorFocus: #fbbf24;
    --colorRail: #9ab8ea;
    --colorYouAreHere: #60a5fa;
    --shadowRaised: 0 -2px 16px rgb(0 0 0 / 0.5);
    --shadowMarker: 0 1px 4px rgb(0 0 0 / 0.6);
    --colorDirectionB: #fb923c;
    --colorDirectionBText: #1c0d02;
  }

  /* OpenStreetMap only has light tiles; invert them so the map doesn't glare at night */
  .leaflet-tile-pane {
    filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9);
  }
}

/* Tablets and desktops */
@media (min-width: 48rem) {
  :root {
    --contentWidth: 46rem;
    --fontSizeXl: 2.25rem;
  }

  /* The tab bar moves to the top and takes over from the site header */
  body {
    padding-bottom: 0;
  }

  .siteHeader {
    display: none;
  }

  .tabBar {
    position: sticky;
    top: 0;
    bottom: auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--spaceLg);
    padding: env(safe-area-inset-top) var(--spaceLg) 0;
    border-top: 0;
    background: var(--colorHeaderBg);
    color: var(--colorHeaderText);
  }

  .tabBar :focus-visible {
    outline-color: var(--colorHeaderText);
  }

  .tabBarBrand {
    display: inline-flex;
    align-items: center;
    min-height: 3.5rem;
    color: inherit;
    font-size: var(--fontSizeLg);
    font-weight: 800;
    text-decoration: none;
  }

  .tabList {
    display: flex;
    gap: var(--spaceSm);
  }

  .tabLink {
    flex-direction: row;
    gap: var(--spaceSm);
    min-height: 3.5rem;
    padding-inline: var(--spaceMd);
    border-top: 0;
    border-bottom: 3px solid transparent;
    color: var(--colorHeaderText);
    font-size: var(--fontSizeBase);
  }

  .tabLink[aria-current='page'] {
    border-bottom-color: var(--colorHeaderText);
    color: var(--colorHeaderText);
  }

  .tabIcon {
    width: 1.25rem;
    height: 1.25rem;
  }

  .searchPage {
    padding-block: calc(var(--spaceXl) * 2);
  }

  .routeList {
    grid-template-columns: repeat(2, 1fr);
  }

  .stopForm {
    max-width: 26rem;
  }

  .stopTitle {
    font-size: var(--fontSizeXl);
  }

  .arrivalCard {
    padding: var(--spaceMd) var(--spaceLg);
  }

  /* No thumb to reach for, so float the bar as a card instead of an edge-to-edge strip */
  .refreshBar {
    bottom: var(--spaceMd);
    margin-inline: 0;
    margin-bottom: var(--spaceMd);
    border: 1px solid var(--colorBorder);
    border-radius: var(--radiusMd);
  }

  /* The sheet docks as a side panel so the map stays visible beside it */
  .mapSheet {
    top: var(--spaceMd);
    right: auto;
    bottom: var(--spaceMd);
    left: var(--spaceMd);
    width: 24rem;
    max-height: none;
    border: 1px solid var(--colorBorder);
    border-radius: var(--radiusMd);
  }
}

/* Hover effects only on devices with a mouse, so they don't stay stuck on after a tap on phones */
@media (hover: hover) {
  .stopPin:hover::before {
    background: var(--colorFocus);
    box-shadow: 0 0 0 1px var(--colorFocus);
    transform: scale(1.2);
  }

  /* Pressing with a mouse still shrinks the dot, even though it's also hovered */
  .stopPin:hover:active::before {
    transform: scale(0.9);
  }

  .directionButton[aria-pressed='false']:hover {
    background: color-mix(in srgb, var(--directionColor) 15%, var(--colorSurface));
  }

  .routeStop:hover {
    background: var(--colorSurface);
  }

  .routeStop:hover .rowChevron {
    color: var(--colorText);
  }
}

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