:root {
  color-scheme: light dark;
  --font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Segoe UI", sans-serif;

  --bg-base: #f5f6f8;
  /* Two tints share one blur. --bg-elevated is the near-opaque one, for
     surfaces with nothing interesting behind them (the sidebar, its
     dropdowns) and for the profile chart, whose lines would compete with
     the map. --glass-bg is the see-through one for popups floating over the
     map: thin enough, and blurred little enough, that shapes and colours
     behind them still read, like macOS/iOS widgets. */
  --bg-elevated: rgba(255, 255, 255, 0.62);
  --glass-bg: rgba(255, 255, 255, 0.4);
  --glass-filter: blur(14px) saturate(180%);
  --bg-elevated-2: rgba(15, 23, 42, 0.05);
  --bg-elevated-3: rgba(15, 23, 42, 0.09);
  --border-subtle: rgba(15, 23, 42, 0.09);
  --text-primary: #0f172a;
  --text-secondary: #4b5568;
  --text-tertiary: #8a93a6;
  --accent: #007aff;
  --accent-hover: #0064d6;
  --accent-soft: rgba(0, 122, 255, 0.12);
  --accent-contrast: #ffffff;
  --danger: #ff3b30;
  --warning: #b5750a;
  --shadow-color: rgba(15, 23, 42, 0.16);
  --chart-elevation-fill: rgba(15, 23, 42, 0.1);
  --chart-elevation-stroke: rgba(15, 23, 42, 0.28);
  --chart-grid: rgba(15, 23, 42, 0.07);

  --radius-sm: 10px;
  --radius-md: 12px;
  --radius-lg: 16px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 40px;
  --space-8: 48px;

  --transition: 160ms ease;
}

/* color-scheme drives native scrollbars and form controls. "light dark" on
   its own lets the browser follow the OS, so a light page on a dark OS got
   dark scrollbars; an explicit pick pins it. */
:root[data-theme="light"] {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg-base: #0b0d12;
  --bg-elevated: rgba(22, 24, 32, 0.72);
  --glass-bg: rgba(22, 24, 32, 0.45);
  --bg-elevated-2: rgba(255, 255, 255, 0.06);
  --bg-elevated-3: rgba(255, 255, 255, 0.1);
  --border-subtle: rgba(255, 255, 255, 0.1);
  --text-primary: #f2f4f8;
  --text-secondary: #a6adba;
  --text-tertiary: #6b7284;
  --accent: #0a84ff;
  --accent-hover: #3399ff;
  --accent-soft: rgba(10, 132, 255, 0.18);
  --accent-contrast: #ffffff;
  --danger: #ff453a;
  --warning: #d99b2b;
  --shadow-color: rgba(0, 0, 0, 0.5);
  --chart-elevation-fill: rgba(255, 255, 255, 0.1);
  --chart-elevation-stroke: rgba(255, 255, 255, 0.3);
  --chart-grid: rgba(255, 255, 255, 0.07);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg-base: #0b0d12;
    --bg-elevated: rgba(22, 24, 32, 0.72);
    --glass-bg: rgba(22, 24, 32, 0.45);
    --bg-elevated-2: rgba(255, 255, 255, 0.06);
    --bg-elevated-3: rgba(255, 255, 255, 0.1);
    --border-subtle: rgba(255, 255, 255, 0.1);
    --text-primary: #f2f4f8;
    --text-secondary: #a6adba;
    --text-tertiary: #6b7284;
    --accent: #0a84ff;
    --accent-hover: #3399ff;
    --accent-soft: rgba(10, 132, 255, 0.18);
    --accent-contrast: #ffffff;
    --danger: #ff453a;
    --warning: #d99b2b;
    --shadow-color: rgba(0, 0, 0, 0.5);
    --chart-elevation-fill: rgba(255, 255, 255, 0.1);
    --chart-elevation-stroke: rgba(255, 255, 255, 0.3);
    --chart-grid: rgba(255, 255, 255, 0.07);
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  font-family: var(--font-family);
  color: var(--text-primary);
  background: var(--bg-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.hidden {
  display: none !important;
}

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

/* ---------- layout ---------- */

.app-shell {
  --profile-height: 240px;
  /* Eases in as well as out: a curve that starts at full speed makes the
     opening look like a jump, since its first frames cover most of the distance. */
  --profile-transition: 500ms cubic-bezier(0.4, 0, 0.2, 1);
  display: grid;
  grid-template-columns: 1fr 380px;
  height: 100vh;
}

/* Clips the profile panel while it waits below the map's bottom edge.
   `clip`, not just `hidden`: an overflow: hidden box is still a scroll
   container, so the browser can scroll it (shifting the whole map) to
   reveal something inside; `hidden` stays as the fallback for older browsers.
   isolation keeps Leaflet's high z-indexes (panes up to 700, controls 1000)
   inside this box, so the panel only has to out-stack them here. */
#mapPanel {
  position: relative;
  grid-column: 1;
  grid-row: 1;
  min-height: 0;
  overflow: hidden;
  overflow: clip;
  isolation: isolate;
}

#map {
  width: 100%;
  height: 100%;
  background: var(--bg-elevated-2);
}

.map-empty-state {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  color: var(--text-tertiary);
  font-size: 0.95rem;
  z-index: 400;
}

.map-empty-state[hidden] {
  display: none;
}

#sidebar {
  grid-column: 2;
  grid-row: 1;
  min-height: 0;
  overflow-y: auto;
  background: var(--bg-elevated);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
  border-left: 1px solid var(--border-subtle);
  box-shadow: -20px 0 60px var(--shadow-color);
}

/* The icon buttons share a row with the title only; the subtitle gets the
   full width below, otherwise the buttons squeeze it onto two lines. */
.sidebar-header {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: var(--space-3);
  padding: var(--space-5) var(--space-5) var(--space-4);
}

.sidebar-title {
  display: contents;
}

.sidebar-header .subtle {
  grid-column: 1 / -1;
}

.sidebar-header .header-actions {
  grid-column: 2;
  grid-row: 1;
}

.sidebar-header h1 {
  margin: 0;
  font-size: 1.3rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.subtle {
  margin: var(--space-1) 0 0;
  color: var(--text-secondary);
  font-size: 0.85rem;
}

/* Positioning context for .shortcuts-panel, which hangs below these buttons. */
.header-actions {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.theme-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color var(--transition), background var(--transition), transform var(--transition);
}

/* #aboutLink is an <a> sharing the icon-button look. */
a.theme-toggle {
  text-decoration: none;
}

.theme-toggle:hover {
  color: var(--text-primary);
  background: var(--bg-elevated-2);
}

/* Only with a real hover: a touch screen keeps :hover on whatever was last
   tapped, which would leave the button floating a pixel up. */
@media (hover: hover) {
  .theme-toggle:hover {
    transform: translateY(-1px);
  }
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Anchored under the header buttons rather than centred over the page: the
   sidebar is its own grid column, so this never has to out-stack Leaflet's
   panes (which run up to ~700) the way a full-page modal would. */
.shortcuts-panel {
  position: absolute;
  right: 0;
  top: calc(100% + var(--space-2));
  z-index: 30;
  width: 236px;
  padding: var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-base);
  box-shadow: 0 12px 32px var(--shadow-color);
}

.shortcuts-panel-heading {
  margin: 0 0 var(--space-2);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-secondary);
}

.shortcut-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 3px 0;
}

.shortcut-keys {
  display: flex;
  flex-shrink: 0;
  gap: 3px;
}

.shortcut-keys kbd {
  padding: 1px 5px;
  border: 1px solid var(--border-subtle);
  border-radius: 5px;
  background: var(--bg-elevated-2);
  color: var(--text-secondary);
  font-family: var(--font-family);
  font-size: 0.7rem;
  line-height: 1.5;
}

.shortcut-label {
  font-size: 0.78rem;
  color: var(--text-tertiary);
  text-align: right;
}

.sidebar-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: 0 var(--space-5) var(--space-6);
}

.control {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.control-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.control-label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-secondary);
}

/* ---------- inputs ---------- */

.text-input {
  width: 100%;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: var(--bg-elevated-2);
  color: var(--text-primary);
  font: inherit;
  transition: border-color var(--transition), box-shadow var(--transition);
}

.text-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Chrome/Edge/Safari only (no equivalent pseudo-element in Firefox): the
   native calendar icon on <input type="date"> is small and easy to miss —
   this pads out its clickable area without changing how it looks. */
input[type="date"]::-webkit-calendar-picker-indicator {
  padding: var(--space-2);
  margin-inline-start: var(--space-1);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

input[type="date"]::-webkit-calendar-picker-indicator:hover {
  background: var(--accent-soft);
}

/* ---------- dropzone ---------- */

.dropzone {
  position: relative;
  border: 1.5px dashed var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-5) var(--space-4);
  text-align: center;
  cursor: pointer;
  background: var(--bg-elevated-2);
  transition: border-color var(--transition), background var(--transition);
}

.dropzone:hover,
.dropzone:focus-visible {
  border-color: var(--accent);
  background: var(--accent-soft);
  outline: none;
}

.dropzone.is-dragover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.dropzone-icon {
  display: block;
  font-size: 1.4rem;
  margin-bottom: var(--space-2);
  color: var(--text-tertiary);
}

.dropzone-text {
  margin: 0;
  font-size: 0.88rem;
  color: var(--text-secondary);
}

.dropzone-link {
  color: var(--accent);
  font-weight: 600;
}

.dropzone-filename {
  margin: var(--space-2) 0 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-primary);
}

/* Deliberately quiet: tertiary text, so it reassures without competing with
   the dropzone. The full story lives on about.html#privacy. */
.privacy-note {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0 var(--space-1);
  margin: 0;
  font-size: 0.78rem;
  color: var(--text-tertiary);
  text-align: center;
}

.privacy-note svg {
  flex-shrink: 0;
}

/* Small inline "learn more" links inside tertiary hint text. */
.privacy-note a,
.control-hint a,
.anchor-empty a {
  color: var(--accent);
  text-decoration: none;
}

.privacy-note a:hover,
.control-hint a:hover,
.anchor-empty a:hover {
  text-decoration: underline;
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
  font-size: 0.88rem;
  color: var(--text-primary);
  cursor: pointer;
}

.checkbox-row input {
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.control-hint {
  margin: calc(-1 * var(--space-1)) 0 0;
  padding-left: 21px;
  font-size: 0.78rem;
  color: var(--text-tertiary);
}

/* A hint under a full-width control, not indented under a checkbox's label. */
.control-hint--flush {
  padding-left: 0;
}

/* ---------- pace smoothness slider ---------- */

.range-input {
  width: 100%;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.smoothness-value {
  min-width: 1.5em;
  font-size: 0.82rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  text-align: right;
}

.range-captions {
  display: flex;
  justify-content: space-between;
  margin-top: calc(-1 * var(--space-1));
  font-size: 0.78rem;
  color: var(--text-tertiary);
}

.route-summary {
  display: flex;
  gap: var(--space-4);
}

.summary-stat {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated-2);
}

.summary-value {
  font-size: 1.05rem;
  font-weight: 600;
}

.summary-label {
  font-size: 0.75rem;
  color: var(--text-tertiary);
}

/* ---------- segmented control ---------- */

.segmented {
  display: flex;
  gap: 2px;
  padding: 3px;
  border-radius: var(--radius-sm);
  background: var(--bg-elevated-2);
}

.segmented-option {
  flex: 1;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  padding: 0.5rem 0.6rem;
  border-radius: calc(var(--radius-sm) - 3px);
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}

.segmented-option.is-active {
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}

/* ---------- time toggle ---------- */

.time-toggle {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Duration-only toggles (e.g. a stop's "Duration only" mode) have no mode
   buttons of their own, so there's nothing above their fields to create a
   gap via .time-toggle's flex gap — this restores that spacing as real
   padding instead, so they don't sit flush against whatever the caller put
   above them. */
.time-toggle--duration-only {
  padding-top: var(--space-3);
}

.duration-fields,
.other-fields {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

.duration-input {
  width: 4.5rem;
  text-align: center;
}

/* The avg-speed mode: one row of boxes per unit, only the active one
   rendered. The rows themselves reuse .duration-fields, since they are the
   same centered box+suffix run. */
.speed-fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Just the typed boxes — the row also holds the trailing unit suffix and the
   switch button, and both sit outside this. Positioned because the quick-pick
   list hangs from it and is centred on it, so every extra thing inside would
   push the list right of the numbers by half that thing's width. */
.speed-entry {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* A single box holding "4.5" needs a little more room than one holding "59". */
.speed-fields-kmh .duration-input {
  width: 5.5rem;
}

/* The unit switch, in the entry row rather than on a line of its own. It
   names the unit it switches *to*, so it is deliberately quieter than the
   suffix beside it — it's an alternative on offer, not the current state. */
.speed-unit-button {
  margin-left: var(--space-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated-2);
  color: var(--text-tertiary);
  font: inherit;
  font-size: 0.75rem;
  font-weight: 500;
  padding: 0.25rem 0.5rem;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--transition), color var(--transition);
}

.speed-unit-button:hover {
  background: var(--bg-elevated);
  color: var(--text-primary);
}

/* Reuses .time-field-dropdown / .time-field-option wholesale (see
   dateTimeField.js) — same list, same interaction, just a different set of
   values in it. What differs is width: every label here is four characters
   at most ("10.0", "3:30"), so the list is sized to them and centered under
   the row rather than stretched across it, with a slightly rounder edge to
   suit the narrower box.

   `scrollbar-gutter: both-edges` is what keeps the labels visually centered:
   the list always scrolls (15 km/h entries against a 12rem max-height), and
   on a box this narrow a scrollbar down one side alone throws the numbers
   noticeably off-centre. Reserving the same width on both sides costs
   nothing here, since the width is fixed either way.

   Both classes are in the selector on purpose: .time-field-dropdown is
   defined *below* this block, so at equal specificity its `left: 0; right: 0`
   would win on source order and stretch the list across the entry — then
   translateX(-50%) would drag that full-width box half its own width to the
   left, off the sidebar entirely. */
.time-field-dropdown.speed-preset-dropdown {
  left: 50%;
  right: auto;
  transform: translateX(-50%);
  width: 6rem;
  border-radius: var(--radius-lg);
  scrollbar-gutter: stable both-edges;
}

.input-suffix {
  color: var(--text-tertiary);
  font-size: 0.85rem;
}

.time-toggle-preview {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

/* ---------- date/time fields ---------- */
/* Custom replacements for native <input type="time"> / type="datetime-local">
   — see dateTimeField.js for why. */

.date-time-field {
  display: flex;
  flex: 1;
  gap: var(--space-2);
}

.date-time-field-date {
  flex: 1;
}

.date-time-field-time {
  flex: 1;
}

.time-field {
  position: relative;
  flex: 1;
}

.time-field-boxes {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
}

.time-field-box {
  width: 3.30rem;
  text-align: center;
}

.time-field-colon {
  color: var(--text-tertiary);
}

.time-field-dropdown {
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 0;
  right: 0;
  z-index: 20;
  max-height: 12rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-elevated);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
  box-shadow: 0 20px 60px var(--shadow-color);
}

.time-field-option {
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  font-size: 0.82rem;
  padding: 0.4rem 0.6rem;
  cursor: pointer;
  text-align: center;
  transition: background var(--transition), color var(--transition);
}

.time-field-option:hover {
  background: var(--accent);
  color: var(--accent-contrast);
}

/* Stacks the (optional, multi-day-only) day selector above the time-of-day
   clock boxes inside a 'durationOrTimeOfDay' toggle's "Time of day" mode. */
.time-of-day-stack {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2);
  width: 100%;
}

/* ---------- day selector ---------- */
/* Thin "Day N · <date>" button + dropdown, shown only above a time-of-day
   field when the activity spans more than one calendar day — see
   createDaySelector in dateTimeField.js. */

.day-selector {
  position: relative;
}

.day-selector-button {
  width: 100%;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated-2);
  color: var(--text-secondary);
  font: inherit;
  font-size: 0.78rem;
  font-weight: 500;
  padding: 0.35rem 0.6rem;
  cursor: pointer;
  text-align: center;
  transition: background var(--transition), color var(--transition);
}

.day-selector-button:hover {
  background: var(--bg-elevated);
  color: var(--text-primary);
}

.day-selector-dropdown {
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 0;
  right: 0;
  z-index: 20;
  max-height: 10rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-elevated);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
  box-shadow: 0 20px 60px var(--shadow-color);
}

.day-selector-option {
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  font-size: 0.8rem;
  padding: 0.4rem 0.6rem;
  cursor: pointer;
  text-align: center;
  transition: background var(--transition), color var(--transition);
}

.day-selector-option:hover {
  background: var(--accent);
  color: var(--accent-contrast);
}

/* ---------- anchor list ---------- */

.anchor-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.anchor-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition);
}

.anchor-row:hover,
.anchor-row.is-active {
  background: var(--accent-soft);
}

.anchor-row-info {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.anchor-row-number {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 0.72rem;
  font-weight: 700;
  flex-shrink: 0;
}

.anchor-row-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.anchor-row-distance {
  font-size: 0.85rem;
  font-weight: 600;
}

.anchor-row-time {
  font-size: 0.76rem;
  color: var(--text-tertiary);
}

.anchor-row-delete {
  border: none;
  background: transparent;
  color: var(--text-tertiary);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  transition: color var(--transition), background var(--transition);
}

.anchor-row-delete:hover {
  color: var(--danger);
  background: rgba(255, 59, 48, 0.1);
}

.anchor-row.is-problem .anchor-row-time {
  color: var(--danger);
}

/* Kept but not used: an anchor or stop outside the activity's current
   start–end (see activityWindow.js). Amber rather than red, since moving the
   start back makes it active again. */
.anchor-row.is-inactive .anchor-row-distance {
  opacity: 0.55;
}

.anchor-row.is-inactive .anchor-row-time {
  color: var(--warning);
}

.anchor-empty {
  margin: 0;
  padding: var(--space-4) var(--space-2);
  text-align: center;
  font-size: 0.82rem;
  color: var(--text-tertiary);
}

/* ---------- buttons / status ---------- */

.primary-button {
  position: relative;
  width: 100%;
  border: none;
  padding: 0.75rem 1rem;
  border-radius: var(--radius-md);
  background: var(--accent);
  color: var(--accent-contrast);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--transition), transform var(--transition), opacity var(--transition);
}

.primary-button:hover:not(:disabled) {
  background: var(--accent-hover);
}

/* See .theme-toggle:hover. */
@media (hover: hover) {
  .primary-button:hover:not(:disabled) {
    transform: translateY(-1px);
  }
}

.primary-button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.primary-button.is-loading {
  color: transparent;
}

.primary-button.is-loading::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 16px;
  height: 16px;
  margin: -8px 0 0 -8px;
  border: 2px solid rgba(255, 255, 255, 0.4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 700ms linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.status {
  font-size: 0.82rem;
  color: var(--text-secondary);
  min-height: 1.2em;
}

.status-error {
  color: var(--danger);
}

/* Distinct from .status-error: a problem traced back to something the user
   entered (bad anchor/stop times, missing setup) rather than an internal
   failure — see format.js's describeError. Amber instead of red so it reads
   as "fix this input" rather than "something broke". */
.status-input {
  color: var(--warning);
}

.download-link {
  display: block;
  text-align: center;
  padding: 0.7rem 1rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--accent);
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  transition: background var(--transition);
}

.download-link:hover {
  background: var(--accent-soft);
}

/* This link receives focus programmatically after a successful convert (see
   main.js), so the ring has to be visible — it's the cue that Enter now
   downloads. */
.download-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- map markers / popover ---------- */

.anchor-pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-contrast);
  font-size: 0.72rem;
  font-weight: 700;
  box-shadow: 0 2px 8px var(--shadow-color);
  border: 2px solid var(--accent-contrast);
  transition: transform var(--transition);
}

.anchor-pin-wrapper.is-active .anchor-pin {
  transform: scale(1.2);
}

.stop-pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
  background: #ff9500;
  color: var(--accent-contrast);
  font-size: 0.72rem;
  font-weight: 700;
  box-shadow: 0 2px 8px var(--shadow-color);
  border: 2px solid var(--accent-contrast);
  transition: transform var(--transition);
}

.stop-pin-wrapper.is-active .stop-pin {
  transform: scale(1.2);
}

.photo-pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #af52de;
  color: var(--accent-contrast);
  font-size: 0.72rem;
  font-weight: 700;
  box-shadow: 0 2px 8px var(--shadow-color);
  border: 2px solid var(--accent-contrast);
  transition: transform var(--transition);
}

.photo-pin-wrapper.is-active .photo-pin {
  transform: scale(1.2);
}

/* The map side of .anchor-row.is-inactive (see map.js's setMarkerDimmed). */
.leaflet-marker-icon.is-dimmed {
  opacity: 0.4;
}

.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
  background: var(--glass-bg);
  color: var(--text-primary);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
}

.leaflet-popup-content-wrapper {
  border-radius: var(--radius-md);
  box-shadow: 0 20px 60px var(--shadow-color);
}

.anchor-popover .leaflet-popup-content {
  margin: var(--space-3);
  width: 240px !important;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  transition: width 220ms ease;
}

/* Only the stop popover's "Start & end time" mode switches to this —
   everything else (the anchor popover, and the stop popover's "Duration
   only" mode) stays at the width above. Wide enough that each of the two
   columns gets the same ~240px an arrival/departure toggle already needs
   when stacked full-width, so nothing inside them wraps. */
.anchor-popover .leaflet-popup-content.is-wide {
  width: 492px !important;
}

.stop-time-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}

.popover-heading {
  margin: 0;
  font-weight: 600;
  font-size: 0.9rem;
}

.popover-hint {
  margin: 0;
  font-size: 0.78rem;
  color: var(--text-tertiary);
}

.popover-hint.is-warning {
  color: var(--warning);
}

.popover-confirm {
  padding: 0.55rem 0.8rem;
}

.candidate-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.candidate-option {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  border: 1px solid var(--border-subtle);
  background: var(--bg-elevated-2);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  text-align: left;
  transition: background var(--transition), border-color var(--transition);
}

.candidate-option:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.candidate-distance {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-primary);
}

.candidate-estimate {
  font-size: 0.76rem;
  color: var(--text-tertiary);
}

/* ---------- activity profile panel ---------- */

/* Overlays the bottom of the map instead of taking layout space, so the map
   never resizes (no re-zoom, no extra tile loads). Waits just below the
   map's bottom edge, clipped by #mapPanel, and slides up when opened. */
#profilePanel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1100;
  height: var(--profile-height);
  display: flex;
  flex-direction: column;
  background: var(--bg-elevated);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
  border-top: 1px solid var(--border-subtle);
  box-shadow: 0 -12px 40px var(--shadow-color);
  transform: translateY(calc(100% + 40px));
  transition: transform var(--profile-transition);
  /* Keeps the panel on its own compositor layer ahead of time, so the first
     frames of the slide aren't spent promoting it (and its backdrop blur). */
  will-change: transform;
}

.app-shell.has-profile #profilePanel {
  transform: none;
}

/* Leaflet's bottom corners (the tile attribution) ride up with the panel so they stay visible. */
#mapPanel .leaflet-bottom {
  transition: transform var(--profile-transition);
}

.app-shell.has-profile #mapPanel .leaflet-bottom {
  transform: translateY(calc(-1 * var(--profile-height)));
}

.profile-show-button {
  position: absolute;
  left: 50%;
  bottom: var(--space-4);
  z-index: 1050;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--border-subtle);
  border-radius: 999px;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
  box-shadow: 0 8px 24px var(--shadow-color);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 500;
  cursor: pointer;
}

.profile-show-button[hidden] {
  display: none;
}

.profile-show-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.profile-panel-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-2) var(--space-3) 0 var(--space-4);
}

.profile-panel-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.profile-legend {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 0.78rem;
  color: var(--text-secondary);
}

.profile-legend-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.25rem 0.5rem;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: background var(--transition), opacity var(--transition);
}

.profile-legend-item:hover:not(:disabled) {
  background: var(--bg-elevated-2);
}

.profile-legend-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* The last visible series can't be hidden; it keeps its normal look, just not clickable. */
.profile-legend-item:disabled {
  cursor: default;
}

.profile-legend-item[aria-pressed="false"] {
  opacity: 0.45;
  text-decoration: line-through;
}

.profile-legend-swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 3px;
}

.profile-legend-swatch--speed {
  height: 3px;
  background: var(--accent);
}

.profile-legend-swatch--elevation {
  background: var(--chart-elevation-fill);
  border: 1px solid var(--chart-elevation-stroke);
}

.segmented--compact .segmented-option {
  padding: 0.25rem 0.75rem;
  font-size: 0.78rem;
  /* Otherwise a squeezed header wraps a label onto two lines, making that control taller than its neighbours. */
  white-space: nowrap;
}

.profile-chart-area {
  position: relative;
  flex: 1;
  min-height: 0;
}

.profile-chart svg {
  display: block;
  /* Keeps vertical page scrolling working on touch screens while a finger drags across the chart. */
  touch-action: pan-y;
}

.profile-grid line {
  stroke: var(--chart-grid);
  stroke-width: 1;
}

.profile-elevation-area {
  fill: var(--chart-elevation-fill);
}

.profile-elevation-line {
  fill: none;
  stroke: var(--chart-elevation-stroke);
  stroke-width: 1;
}

.profile-speed-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.75;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* The average moving speed: the speed line's own colour, thinner, dotted and
   faded, so it reads as a reference the series is measured against rather
   than a second series. */
.profile-average-line {
  stroke: var(--accent);
  stroke-width: 1;
  stroke-dasharray: 1 4;
  stroke-linecap: round;
  opacity: 0.55;
}

.profile-average-label {
  fill: var(--accent);
  font-family: var(--font-family);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  opacity: 0.7;
}

/* Stops: the same weight as the speed line, just amber and a little faded,
   so they read as "paused here" without adding a loud new colour. */
.profile-stop-line {
  stroke: var(--warning);
  stroke-width: 1.75;
  stroke-linecap: round;
  opacity: 0.8;
}

.profile-stop-dot {
  fill: var(--warning);
  opacity: 0.8;
}

.profile-axis-label {
  fill: var(--text-tertiary);
  font-family: var(--font-family);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.profile-axis-label--speed {
  fill: var(--accent);
}

.profile-crosshair {
  stroke: var(--text-tertiary);
  stroke-width: 1;
  stroke-dasharray: 3 3;
}

.profile-speed-dot {
  fill: var(--accent);
  stroke: var(--bg-base);
  stroke-width: 2;
}

.profile-speed-dot.is-stop {
  fill: var(--warning);
}

.profile-elevation-dot {
  fill: var(--chart-elevation-stroke);
  stroke: var(--bg-base);
  stroke-width: 1.5;
}

.profile-tooltip {
  position: absolute;
  z-index: 5;
  min-width: 150px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-base);
  box-shadow: 0 8px 24px var(--shadow-color);
  font-size: 0.76rem;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.profile-tooltip-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
}

.profile-tooltip-label {
  color: var(--text-tertiary);
}

@media (prefers-reduced-motion: reduce) {
  .app-shell {
    --profile-transition: 0s;
  }
}

/* ---------- device picker ---------- */

/* Styled like a filled input rather than a button: it shows a value, the
   current device, and only opens a picker for it. */
.device-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated-2);
  color: var(--text-primary);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition);
}

.device-button::after {
  content: "›";
  color: var(--text-tertiary);
  font-size: 1.1rem;
  line-height: 1;
}

.device-button.is-empty {
  color: var(--text-tertiary);
}

.device-button:hover {
  border-color: var(--accent);
}

.device-button:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* The <dialog> itself is only a transparent, centred box; the frosted panel
   inside it fills it exactly, so a click whose target is the dialog element
   can only have landed on the backdrop (see devicePicker.js). */
.device-dialog {
  width: min(440px, calc(100vw - 2 * var(--space-4)));
  max-height: min(80vh, 720px);
  padding: 0;
  border: none;
  background: transparent;
  color: var(--text-primary);
  overflow: visible;
}

.device-dialog::backdrop {
  background: rgba(15, 23, 42, 0.22);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

.device-dialog-panel {
  display: flex;
  flex-direction: column;
  max-height: inherit;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-filter);
  -webkit-backdrop-filter: var(--glass-filter);
  box-shadow: 0 20px 60px var(--shadow-color);
}

.device-dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-4) var(--space-2) var(--space-5);
}

.device-dialog-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
}

.device-dialog-close {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 50%;
  background: var(--bg-elevated-2);
  color: var(--text-secondary);
  cursor: pointer;
}

.device-dialog-close:hover {
  background: var(--bg-elevated-3);
  color: var(--text-primary);
}

.device-dialog-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-5) var(--space-5);
  overflow-y: auto;
}

.device-brand {
  margin: var(--space-2) 0 calc(-1 * var(--space-1));
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.device-catalog {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.device-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}

.device-option {
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated-2);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition);
}

.device-option:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.device-option.is-selected {
  background: var(--accent-soft);
  border-color: var(--accent);
  font-weight: 600;
}

.device-option--none {
  color: var(--text-secondary);
}

.device-custom {
  margin-top: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}

.device-custom summary {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
}

.device-custom-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.device-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: 0.78rem;
  color: var(--text-secondary);
}

.device-field--wide {
  grid-column: 1 / -1;
}

.device-custom-error {
  margin: var(--space-2) 0 0;
  font-size: 0.78rem;
  color: var(--warning);
}

.device-custom-apply {
  margin-top: var(--space-3);
  padding: 0.45rem 0.9rem;
  border: none;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-contrast);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.device-custom-apply:hover {
  background: var(--accent-hover);
}

/* The embedded-name field closes the list off from the custom IDs above. */
.device-custom + .device-field {
  margin-top: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}

/* ---------- route dialog (phone layout's anchor/stop popovers) ---------- */

/* Borrows the device picker's dialog and panel, and lays its body out like
   .anchor-popover's popup content, since the same builders fill both. The
   close button floats over the corner: the builders bring their own heading,
   so a header row would only add an empty line above it. */
.route-dialog {
  max-height: min(85vh, 720px);
  max-height: min(85dvh, 720px);
}

.route-dialog .device-dialog-panel {
  position: relative;
}

.route-dialog .device-dialog-close {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
}

.route-dialog-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  overflow-y: auto;
}

/* Clear of the close button. */
.route-dialog-body > .popover-heading:first-child {
  padding-right: var(--space-7);
}

/* One column: arrival above departure. Side by side they need the 492px
   the wide map popup gets, which a phone doesn't have. */
.route-dialog .stop-time-columns {
  grid-template-columns: 1fr;
}

/* ---------- about page (about.html) ---------- */

.about-page {
  max-width: 720px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-4) var(--space-8);
}

.about-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 0.85rem;
  color: var(--text-secondary);
  text-decoration: none;
  transition: color var(--transition);
}

.about-back:hover {
  color: var(--accent);
}

.about-header {
  margin: var(--space-5) 0 var(--space-4);
}

.about-header h1 {
  margin: 0;
  font-size: 1.8rem;
  font-weight: 600;
  letter-spacing: -0.015em;
}

.about-header .subtle {
  font-size: 1rem;
}

.about-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
}

.about-nav a {
  padding: 4px 12px;
  border: 1px solid var(--border-subtle);
  border-radius: 999px;
  background: var(--bg-elevated-2);
  color: var(--text-secondary);
  font-size: 0.82rem;
  text-decoration: none;
  transition: color var(--transition), border-color var(--transition), background var(--transition);
}

.about-nav a:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}

.about-section {
  margin-bottom: var(--space-4);
  padding: var(--space-5);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-elevated);
  /* Keeps the heading clear of the top edge when jumped to via #anchor. */
  scroll-margin-top: var(--space-4);
}

.about-section h2 {
  margin: 0 0 var(--space-3);
  font-size: 1.1rem;
  font-weight: 600;
}

.about-section h3 {
  margin: var(--space-4) 0 var(--space-2);
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text-secondary);
}

.about-section p,
.about-section li {
  font-size: 0.92rem;
  color: var(--text-secondary);
}

.about-section p {
  margin: 0 0 var(--space-3);
}

.about-section p:last-child {
  margin-bottom: 0;
}

.about-section ul {
  margin: 0 0 var(--space-3);
  padding-left: 1.2rem;
}

.about-section li + li {
  margin-top: var(--space-1);
}

.about-section strong {
  color: var(--text-primary);
  font-weight: 600;
}

.about-section a,
.about-footer a {
  color: var(--accent);
  text-decoration: none;
}

.about-section a:hover,
.about-footer a:hover {
  text-decoration: underline;
}

/* The privacy section is the one people land on from "How?", so it gets a
   faint accent to set it apart from the explanatory cards. */
.about-section--privacy {
  border-color: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}

.about-footer {
  margin-top: var(--space-6);
  font-size: 0.78rem;
  color: var(--text-tertiary);
  text-align: center;
}

.about-footer p {
  margin: 0 0 var(--space-2);
}

@media (max-width: 480px) {
  .about-page {
    padding-top: var(--space-5);
  }

  .about-section {
    padding: var(--space-4);
  }
}

/* ---------- responsive ---------- */

/* The phone layout: a scroll sheet. The map fills the screen, fixed, and the
   sidebar is a sheet in the page's normal flow that starts low enough to
   show --sheet-max-map of it. js/mobileLayout.js opens the page scrolled to
   the half-way point. From there the sheet scrolls down to show more map, or
   up to show every control. It is all native scrolling: touches on the map
   pan the map, and touches on the sheet scroll the page. Keep the width in
   step with PHONE_LAYOUT_QUERY in mobileLayout.js. */
@media (max-width: 860px) {
  /* Pulling down at the top would otherwise pull-to-refresh, and a reload
     drops the route, anchors and stops. */
  html {
    overscroll-behavior-y: none;
  }

  .app-shell {
    --sheet-max-map: 85svh;
    --profile-height: 200px;
    display: block;
    height: auto;
  }

  /* lvh: the map reaches the bottom of the screen even while the browser's
     toolbar is showing, so the toolbar collapsing never uncovers a gap. */
  #mapPanel {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 100vh;
    height: 100lvh;
  }

  /* In the middle of the part of the map the sheet leaves visible at first. */
  .map-empty-state {
    bottom: 50vh;
    bottom: 50svh;
  }

  #sidebar {
    position: relative;
    z-index: 1;
    margin-top: 85vh;
    margin-top: var(--sheet-max-map);
    min-height: 100vh;
    min-height: 100svh;
    /* The page scrolls, not the sheet. A scrolling sheet would trap touches
       and fight the page scroll that moves it. */
    overflow: visible;
    /* Nearly opaque: this sheet sits over busy map tiles, not a plain page. */
    background: var(--bg-base);
    background: color-mix(in srgb, var(--bg-base) 90%, transparent);
    border-left: none;
    border-top: 1px solid var(--border-subtle);
    border-radius: 20px 20px 0 0;
    box-shadow: 0 -12px 40px var(--shadow-color);
  }

  /* A grab handle, purely a cue: the sheet moves with the page scroll. */
  #sidebar::before {
    content: "";
    display: block;
    width: 36px;
    height: 5px;
    margin: var(--space-2) auto 0;
    border-radius: 3px;
    background: var(--text-tertiary);
    opacity: 0.5;
  }

  .sidebar-header {
    padding-top: var(--space-3);
  }

  /* mobileLayout.js moves the profile panel to the top of the sheet, right
     under the visible map, where it grows open instead of sliding. The
     sheet already has the glass, so the panel drops its own. */
  #sidebar #profilePanel {
    position: relative;
    height: 0;
    overflow: hidden;
    transform: none;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-top: none;
    border-bottom: 1px solid var(--border-subtle);
    box-shadow: none;
    transition: height var(--profile-transition);
  }

  .app-shell.has-profile #sidebar #profilePanel {
    height: var(--profile-height);
  }

  .app-shell.has-profile #mapPanel .leaflet-bottom {
    transform: none;
  }

  #sidebar .profile-show-button {
    position: static;
    transform: none;
    margin: var(--space-3) auto 0;
  }

  /* Keeps the Download link, the sheet's last control, clear of the iPhone
     home bar. (Convert/Download deliberately don't stick to the screen:
     pinned, they covered too much of a phone.) */
  .sidebar-body {
    padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom));
  }
}

/* ---------- touch screens ---------- */

.copy-touch {
  display: none;
}

/* A phone or tablet with no mouse: "tap" wording, and no keyboard-shortcuts
   button (the bindings stay, for anyone who does attach a keyboard). */
@media (hover: none) and (pointer: coarse) {
  .copy-pointer {
    display: none;
  }

  .copy-touch {
    display: inline;
  }

  #shortcutsToggle {
    display: none;
  }
}

/* Fingertip-sized targets. */
@media (pointer: coarse) {
  .theme-toggle,
  .device-dialog-close {
    width: 40px;
    height: 40px;
  }

  .anchor-row-delete {
    min-width: 40px;
    min-height: 40px;
  }

  .profile-legend-item,
  .segmented--compact .segmented-option,
  .speed-unit-button {
    min-height: 36px;
  }

  .checkbox-row input {
    width: 20px;
    height: 20px;
  }

  /* Leaflet gives its container font-size: 0.75rem, so popup inputs inherit
     12px, and iOS zooms the whole page into any input under 16px. Phones
     get the route dialog instead; this covers touch tablets, which keep the
     map popups. */
  .leaflet-popup .text-input {
    font-size: 16px;
  }
}
