:root {
  --primary: #FF6B35; --on-primary: #FFFFFF; --background: #E9E9EC;
  --surface: #FFFFFF; --border: rgba(0, 0, 0, 0.13);
  --text-primary: #1A1A1A; --text-secondary: #6B6B6B;
  --success: #2ECC71; --error: #E74C3C;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05), 0 1px 1px rgba(0, 0, 0, 0.04);
  --shadow-md: 0 6px 16px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.06);
  --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.12), 0 4px 10px rgba(0, 0, 0, 0.08);
  --content-width: 640px;
}
@media (prefers-color-scheme: dark) {
  :root {
    --primary: #FF8659; --on-primary: #1A1200; --background: #0B0B0D;
    --surface: #1C1C1F; --border: rgba(255, 255, 255, 0.12);
    --text-primary: #F5F5F5; --text-secondary: #9A9A9E;
    --success: #4ADE80; --error: #F87171;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 1px rgba(0, 0, 0, 0.24);
    --shadow-md: 0 6px 16px rgba(0, 0, 0, 0.36), 0 2px 6px rgba(0, 0, 0, 0.28);
    --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.44), 0 4px 10px rgba(0, 0, 0, 0.32);
  }
}
* { box-sizing: border-box; }
/* Bare content links (e.g. the Strava activity link in the upload card) would otherwise fall
   back to the browser's default blue/purple, unreadable against --surface in dark mode.
   Elements with their own explicit color (nav links, badges, buttons) override this. */
a { color: var(--primary); }
html {
  background: var(--background); scrollbar-gutter: stable;
  /* Without this, dragging down at the top of the page also triggers the browser's own
     native pull-to-refresh (full reload), fighting with our custom in-page one below. */
  overscroll-behavior-y: contain;
}
body {
  background: var(--background); color: var(--text-primary);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  line-height: 1.45;
  max-width: var(--content-width); margin: 0 auto; padding: 0 1.25rem 2.5rem;
}
.topbar {
  display: flex; align-items: center; gap: 1.1rem; height: 3.5rem;
  /* No horizontal padding of its own - .topbar is already a direct child of the padded
     <body>, so it's confined to the same content box as the cards below it. An extra
     padding here would double-inset the icons relative to where the cards' edges sit. */
  position: sticky; top: 0; z-index: 1200; /* above Leaflet's own panes/controls (max ~1000) */
  background: color-mix(in srgb, var(--background) 88%, transparent);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}
.topbar-logo { display: flex; align-items: center; flex-shrink: 0; align-self: stretch; }
.brand-mark { width: 1.6rem; height: 1.6rem; display: block; }
.topbar-nav {
  display: flex; align-items: stretch; min-width: 0; height: 100%; flex: 1;
  align-self: stretch;
}
.topbar-wordmark {
  display: flex; align-items: center;
  font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em; color: var(--text-primary);
}
.topbar-back {
  display: flex; align-items: center; gap: 0.9rem;
  text-decoration: none; color: var(--text-primary);
  font-size: 0.92rem; font-weight: 600; letter-spacing: -0.01em;
  min-width: 0;
}
/* Ellipsis truncation belongs on the text node, not the flex row itself -- applied to
   .topbar-back above, the flex container just clipped the title (no visible ellipsis,
   inconsistent across browsers) once it ran out of room next to the icon, which is why
   "Session preview" was rendering as a bare, mid-word-truncated "Session". A flex item's
   default min-width:auto also refuses to shrink below its content size, which is why this
   span needs its own min-width:0 too, or it'd overflow the row instead of eliding. */
.topbar-back span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.topbar-back svg { flex-shrink: 0; color: var(--text-secondary); transition: color 0.15s; }
.topbar-back:hover svg { color: var(--text-primary); }
main { padding-top: 1rem; }
h1 { font-size: 1.5rem; margin: 0; letter-spacing: -0.01em; }
h2 {
  font-size: 0.8rem; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--text-secondary); margin: 0 0 0.9rem;
}
p { margin: 0 0 0.75rem; }
p:last-child { margin-bottom: 0; }
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 1.25rem 1.4rem;
  margin: 0 0 1.25rem; box-shadow: var(--shadow-md);
}
.muted { color: var(--text-secondary); font-size: 0.88rem; }
.success { color: var(--success); font-weight: 500; }
.error { color: var(--error); }
.field-row { display: flex; align-items: center; gap: 0.6rem; }
input[type="email"], input[type="text"], input[type="number"], input[type="date"] {
  padding: 0.65rem 0.8rem; border-radius: 9px;
  border: 1px solid var(--border); font-size: 1rem;
  background: var(--background); color: var(--text-primary);
}
input[type="email"], input[type="text"] { width: 100%; margin-bottom: 0.75rem; }
.field-row input[type="number"] {
  flex: 1; min-width: 0; padding: 0.4rem 0.7rem; font-size: 0.88rem;
}
input:focus { outline: 2px solid var(--primary); outline-offset: 1px; }
.login-card .muted { font-size: 0.8rem; }
input.login-email-input { font-size: 0.85rem; }
button, .button {
  background: var(--primary); color: var(--on-primary); border: none;
  border-radius: 9px; padding: 0.65rem 1.25rem; font-size: 0.95rem; line-height: 1.2;
  font-weight: 500; cursor: pointer; text-decoration: none;
  display: inline-block; transition: opacity 0.15s, box-shadow 0.15s, transform 0.1s;
  flex-shrink: 0; box-shadow: var(--shadow-sm);
}
button:hover, .button:hover { opacity: 0.92; box-shadow: var(--shadow-md); }
button:active, .button:active { transform: translateY(1px); box-shadow: var(--shadow-sm); }
button:disabled, .button:disabled {
  opacity: 0.55; cursor: default; box-shadow: none; transform: none;
}
button.btn-success, .button.btn-success { background: var(--success); color: #FFFFFF; }
button.ghost, .button.ghost {
  background: color-mix(in srgb, var(--background) 35%, transparent);
  color: var(--text-primary); box-shadow: none;
  border: 1px solid color-mix(in srgb, var(--text-primary) 30%, transparent); font-weight: 500;
}
button.ghost:hover, .button.ghost:hover { box-shadow: none; background: var(--background); }
/* Compound selector so it beats plain button.ghost's own color/border - matches iOS's
   confirmationDialog, which marks "Log Out of All Devices" as its one `role: .destructive`
   option. */
button.ghost.logout-all-btn, .button.ghost.logout-all-btn {
  color: var(--error); border-color: color-mix(in srgb, var(--error) 40%, transparent);
}
.logout-confirm-buttons { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.logout-confirm-buttons form { display: contents; }
button.small, .button.small {
  padding: 0.4rem 0.85rem; font-size: 0.82rem; border-radius: 8px;
}
/* Fixed widths so the button doesn't reflow/shift the row when its label toggles between the
   "action" and "done" state (Upload/Uploaded, Save cuts/Saved). */
.strava-upload-btn { min-width: 6.5rem; text-align: center; }
/* width, not min-width - "Save cuts" and "Saved ✓" (and "Saving…") have different natural
   content widths, so a min-width floor only pins the narrowest of the three and still lets
   the others grow past it, jumping on every toggle. A hard width forces all three to match. */
#save-cuts { width: 6.5rem; text-align: center; }
.login-submit-btn { min-width: 8.5rem; text-align: center; }
.pill {
  background: transparent; color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: 8px;
  padding: 0.3rem 0.75rem; font-size: 0.8rem; font-weight: 500;
  white-space: nowrap; flex-shrink: 0; text-decoration: none;
  transition: color 0.15s, border-color 0.15s;
}
.pill:hover { color: var(--primary); border-color: var(--primary); opacity: 1; }
.pill.success { color: var(--success); border-color: var(--success); }
code {
  background: var(--background); padding: 0.2rem 0.4rem; border-radius: 4px;
  word-break: break-all;
}
ul { list-style: none; margin: 0; padding: 0; }
li {
  position: relative;
  display: flex; flex-direction: column; gap: 0.55rem; padding: 0.9rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: var(--shadow-sm);
  transition: background 0.15s, box-shadow 0.15s;
}
li:hover { background: color-mix(in srgb, var(--background) 25%, var(--surface)); box-shadow: var(--shadow-md); }
.session-actions {
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem 0.75rem; flex-wrap: wrap; min-width: 0;
}
.session-actions p { margin: 0; }
/* Long emails would otherwise overflow the Account card's row (or force it wider than its
   sibling cards) - shrink and truncate instead, full address still available via title=. */
.account-email-line {
  min-width: 0; flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.session-files { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.session-row {
  display: flex; align-items: center; gap: 1rem; min-width: 0;
  color: inherit; text-decoration: none; cursor: pointer;
}
/* Stretches the row's click target to the full <li> (including the gap below the text),
   while .strava-badge sits above it via z-index so it stays independently clickable. */
.session-row::after { content: ""; position: absolute; inset: 0; }
.session-row strong { font-size: 0.98rem; font-weight: 600; }
.session-row-text { display: flex; flex-direction: column; gap: 0.4rem; min-width: 0; }
.session-row .session-row-text { flex: 2 1 155px; }
/* Row when the stats have their line to themselves (looks cramped/awkward otherwise, with a
   lot of empty trailing space); column whenever they're sharing that space with a sibling -
   a dashboard row's thumbnail sitting beside it, or the preview page's Export/Cuts column -
   where a row would either wrap badly or get squeezed. Tied directly to the same breakpoints
   that control each of those layouts, not a fuzzy width guess. */
.session-stats { display: flex; flex-direction: column; flex-wrap: nowrap; gap: 0.3rem; }
@media (max-width: 420px) {
  .session-row .session-stats { flex-direction: row; gap: 1rem; }
}
.session-stats > div { display: flex; flex-direction: column; gap: 0.1rem; }
.session-stats .stat-label {
  font-size: 0.68rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-secondary); white-space: nowrap;
}
.session-stats .stat-value { font-size: 0.85rem; font-weight: 600; white-space: nowrap; }
/* Live recalculated figure once cuts are edited (see session_preview.js's
   updateRecalculatedStats) - sits right after the original value in whichever direction
   .session-stats > div already flows (below it here, since that's a column), empty/hidden
   until there's actually a cut to reflect. */
.stat-recalc {
  font-size: 0.72rem; font-weight: 600; color: var(--primary); white-space: nowrap;
}
.stat-recalc:empty { display: none; }
.strava-badge {
  /* Above Leaflet's own panes (tile/overlay/marker/popup panes go up to z-index 700). */
  position: absolute; top: 0.7rem; right: 0.7rem; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem; border-radius: 50%;
  background: var(--success); color: #fff; font-size: 0.85rem; font-weight: 700;
  text-decoration: none; box-shadow: var(--shadow-sm);
}
.session-stats .stat-value-error { color: var(--error); }
/* Opposite corner from .strava-badge so both can show on the same row without overlapping. */
.corrupt-badge {
  position: absolute; top: 0.7rem; left: 0.7rem; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  width: 1.6rem; height: 1.6rem; border-radius: 50%;
  background: var(--error); color: #fff; font-size: 0.85rem; font-weight: 700;
  box-shadow: var(--shadow-sm);
}
.session-list {
  margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 0.7rem;
}
.session-thumb {
  flex: 5 1 150px; min-width: 150px; max-width: 320px; height: auto; aspect-ratio: 1 / 1;
  border-radius: 10px; background: var(--background); display: block; overflow: hidden;
}
@media (max-width: 420px) {
  .session-row { flex-wrap: wrap; }
  /* Specificity must match (or beat) the always-active ".session-row .session-row-text" /
     ".session-thumb" rules above, or this forced full-width stacking silently loses the
     cascade and wrapping becomes incidental (only when content happens not to fit). */
  .session-row .session-row-text { flex: 1 1 100%; }
  .session-row .session-thumb { flex: 1 1 100%; width: 100%; height: auto; aspect-ratio: 1 / 1; }
}
#load-sentinel { height: 1px; }

/* Skeleton rows: outline the next batch of session tiles while they're being fetched,
   shaped like a real .session-row (same <li> card, text lines, and thumbnail box) so the
   list doesn't visually jump once the real rows arrive. */
@keyframes skeleton-shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
/* Applied directly to real elements (h2, strong, .stat-label/.stat-value, button, .pill,
   #map, .scrubber, .session-thumb - see skeletonize() in dashboard.js) instead of separate
   hand-sized placeholder markup: real classes already give each element its real font-size/
   line-height/padding/fixed dimensions, so whatever text or box is actually there occupies
   its true size - color/border/shadow just get suppressed and a shimmer painted in behind
   it, so sizing can never drift out of sync with the real CSS the way hand-measured
   placeholder widths/heights did. */
.skeleton-shimmer {
  /* !important on the text/paint-hiding properties: this is meant to unconditionally win over
     whatever state-variant class (.btn-success, .stat-value, ...) the placeholder element also
     carries - e.g. button.btn-success's own color/background are a more specific compound
     selector that would otherwise beat a plain .skeleton-shimmer class and leave real-looking
     text ("Saved", "No cuts yet.") plainly readable instead of hidden behind the shimmer. */
  background: linear-gradient(
    90deg, var(--background) 25%,
    color-mix(in srgb, var(--background) 45%, var(--surface)) 50%, var(--background) 75%
  ) !important;
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
  border-radius: 6px;
  color: transparent !important; border-color: transparent !important;
  box-shadow: none !important; pointer-events: none;
}
.skeleton-row { pointer-events: none; }
.skeleton-row .session-row { cursor: default; }
/* .session-thumb's real 10px border-radius, reasserted: .skeleton-shimmer's own 6px (same
   specificity, declared later) would otherwise win. */
.skeleton-row .session-thumb { border-radius: 10px; }

.account-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem;
}
.account-grid .card { margin: 0; }
.filter-form .field-row { margin-bottom: 0.75rem; }
.filter-form .field-row input { flex: 1; min-width: 0; }
.filter-form input[type="date"] { padding: 0.45rem 0.5rem; font-size: 0.82rem; }
.filter-label {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-secondary); margin: 0.6rem 0 0.3rem;
}
.filter-label:first-child { margin-top: 0; }
/* A mouse click leaves the default focus ring stuck on the button, which reads as a
   persistent "marked" state and gets confused with .active. Keep a ring for keyboard nav
   (:focus-visible) but drop it for mouse/touch clicks. */
.sort-segment:focus,
.sort-dir-toggle:focus,
.scrubber-zoom-btn:focus,
.map-control-btn:focus,
.topbar-filter-btn:focus,
.topbar-user-btn:focus,
.filter-close-btn:focus,
.filter-reset-link:focus { outline: none; }
.sort-segment:focus-visible,
.sort-dir-toggle:focus-visible,
.scrubber-zoom-btn:focus-visible,
.map-control-btn:focus-visible,
.topbar-filter-btn:focus-visible,
.topbar-user-btn:focus-visible,
.filter-close-btn:focus-visible,
.filter-reset-link:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; z-index: 1; }

/* Dual-handle range slider: two overlaid native <input type=range>s, positioned/labeled by
   dashboard.js. Track/highlight are separate divs since a single <input> can't show a
   between-two-handles fill on its own. */
.range-slider { position: relative; height: 1.6rem; margin: 0.3rem 0 0.2rem; }
/* Inset 8px each side to match where the native thumb (16px wide) actually reaches at its
   min/max, not the literal 0/100% edges - otherwise the track visibly overshoots the thumb. */
.range-slider-base {
  position: absolute; left: 8px; right: 8px; top: 50%; transform: translateY(-50%);
  height: 4px; border-radius: 2px; background: var(--border);
}
.range-slider-highlight {
  position: absolute; top: 50%; transform: translateY(-50%);
  height: 4px; border-radius: 2px; background: var(--primary);
}
.range-slider input[type="range"] {
  position: absolute; left: 0; right: 0; top: 0; width: 100%; height: 100%; margin: 0;
  background: transparent; pointer-events: none; -webkit-appearance: none; appearance: none;
}
/* When both handles land on the same value, they visually and functionally overlap - without
   an explicit z-index the browser's default paint order gives the later element (max) hit-
   test priority, permanently trapping min underneath whenever they coincide (the reported
   "stuck, can't grab it again" bug). Force min to always win overlapping clicks; once
   dragged even slightly, they no longer overlap and max is grabbable normally again. */
.range-slider input[type="range"]:first-of-type { z-index: 2; }
.range-slider input[type="range"]:last-of-type { z-index: 1; }
.range-slider input[type="range"]::-webkit-slider-runnable-track { background: transparent; height: 4px; }
.range-slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto; cursor: pointer;
  width: 16px; height: 16px; margin-top: -6px; border-radius: 50%;
  background: var(--primary); border: 2px solid var(--surface); box-shadow: var(--shadow-sm);
}
.range-slider input[type="range"]::-moz-range-thumb {
  pointer-events: auto; cursor: pointer; width: 16px; height: 16px; border-radius: 50%;
  background: var(--primary); border: 2px solid var(--surface); box-shadow: var(--shadow-sm);
}
.range-slider input[type="range"]::-moz-range-track { background: transparent; border: none; height: 4px; }
.range-slider-values {
  display: flex; justify-content: space-between; font-size: 0.78rem;
  color: var(--text-secondary); margin-bottom: 0.3rem;
}

.pull-indicator {
  display: flex; justify-content: center;
  opacity: 0; transform: translateY(0); margin-top: -1.5rem; padding-bottom: 0.5rem;
  transition: opacity 0.15s;
}
.pull-spinner {
  width: 1.3rem; height: 1.3rem; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--text-primary) 20%, transparent);
  border-top-color: var(--primary);
}
.pull-spinner.spinning { animation: pull-spin 0.7s linear infinite; }
@keyframes pull-spin { to { transform: rotate(360deg); } }
/* Mirrors the iOS app's floating scroll-to-top button (chevron in a circular primary-colored
   button, bottom-right) -- toggled via dashboard.js's own scroll listener, not Datastar, since
   it's pure client-side UI state with no server-relevant signal. Faded via opacity/scale
   (never removed from the DOM) so it can transition smoothly instead of popping in/out. */
.scroll-top-btn {
  position: fixed; right: 1.25rem; bottom: 1.25rem; z-index: 900;
  display: flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; border-radius: 10px; border: none;
  background: var(--primary); color: var(--on-primary); box-shadow: var(--shadow-md);
  cursor: pointer; opacity: 0; transform: scale(0.85); pointer-events: none;
  transition: opacity 0.15s, transform 0.15s;
}
.scroll-top-btn.visible { opacity: 1; transform: scale(1); pointer-events: auto; }
.topbar-user-btn, .topbar-filter-btn, .topbar-refresh-btn {
  display: flex; align-items: center; justify-content: center; align-self: center;
  position: relative; width: 2.1rem; height: 2.1rem; border-radius: 50%;
  background: transparent; color: var(--text-secondary); border: none; box-shadow: none;
  padding: 0; cursor: pointer; text-decoration: none; transition: background 0.15s, color 0.15s;
}
.topbar-user-btn:hover, .topbar-filter-btn:hover, .topbar-refresh-btn:hover,
.topbar-filter-btn.active {
  background: color-mix(in srgb, var(--background) 55%, transparent); color: var(--text-primary);
}
/* Filter button leads the right-aligned icon group (pushed via auto margin); refresh and
   account follow immediately after with a small gap each. */
.topbar-filter-btn { margin-left: auto; }
.topbar-refresh-btn { margin-left: 0.2rem; }
.topbar-user-btn { margin-left: 0.2rem; }
.topbar-refresh-btn.spinning svg { animation: pull-spin 0.7s linear infinite; }
.topbar-filter-badge {
  position: absolute; top: 0.15rem; right: 0.15rem; width: 0.5rem; height: 0.5rem;
  border-radius: 50%; background: var(--primary);
}
/* Dropdown/popover, not a full-pane tab - dismissable by clicking anywhere outside the panel
   (the overlay itself is the click-catcher; the panel stops that click from propagating to
   it). Lives at the top level of the page body, NOT nested in .topbar - .topbar's
   backdrop-filter would otherwise create a new containing block for this fixed overlay,
   confining it to the topbar's own small box instead of the full viewport. Positioned to
   align with the same centered content column/edge padding as the rest of the page, so it
   stays fully on-screen (including the panel's own width) even on narrow viewports. */
.filter-popover-overlay { position: fixed; inset: 0; z-index: 1499; }
.filter-popover {
  position: absolute; top: 4.5rem; z-index: 1500;
  right: max(1.25rem, calc((100vw - var(--content-width)) / 2 + 1.25rem));
  width: min(22rem, calc(100vw - 2.5rem));
  max-height: min(75vh, 640px); overflow-y: auto;
  box-shadow: var(--shadow-lg); border-radius: 14px;
}
.filter-popover .card { margin: 0; }
.filter-header {
  display: flex; align-items: center; justify-content: flex-end; gap: 0.9rem;
  margin-bottom: 1.1rem;
}
.filter-close-btn {
  width: 1.8rem; height: 1.8rem; padding: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: transparent; color: var(--text-secondary); border: none; box-shadow: none;
  cursor: pointer; transition: background 0.15s, color 0.15s;
}
.filter-close-btn:hover {
  background: color-mix(in srgb, var(--background) 55%, transparent); color: var(--text-primary);
}
.filter-reset-link {
  width: 1.8rem; height: 1.8rem; padding: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary); transition: background 0.15s, color 0.15s;
}
.filter-reset-link:hover {
  background: color-mix(in srgb, var(--background) 55%, transparent); color: var(--primary);
}
.cuts-buttons { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
/* .filter-reset-link supplies size/color/hover; a <button> needs its own reset on top since,
   unlike the <a> it started on, it ships a native border/background/font by default. */
.cuts-reset-btn { border: none; background: transparent; font: inherit; cursor: pointer; }
/* [hidden] alone loses to .filter-reset-link's own `display: flex` at equal specificity,
   depending on cascade order - pair it with the class to force it to win. */
.cuts-reset-btn[hidden] { display: none; }
/* Segmented control (joined squared-off segments, one rounded container) instead of
   individually-rounded pills - reads as one connected "pick one" group of 4, not 4
   disconnected buttons that happen to wrap awkwardly at popover width. */
.sort-row { display: flex; align-items: stretch; gap: 0.4rem; }
.sort-segmented {
  display: flex; flex: 1; min-width: 0; border: 1px solid var(--border);
  border-radius: 8px; overflow: hidden;
}
.sort-segment {
  flex: 1; min-width: 0; background: var(--background); color: var(--text-secondary);
  border: none; border-right: 1px solid var(--border); border-radius: 0;
  padding: 0.3rem 0.2rem; font-size: 0.72rem; font-weight: 500; cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.sort-segment:last-child { border-right: none; }
.sort-segment.active { background: var(--primary); color: var(--on-primary); }
.sort-dir-toggle {
  width: 1.9rem; min-width: 0; flex-shrink: 0; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--background); color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
}
.sort-dir-toggle:hover { border-color: var(--primary); }
.sort-dir-toggle svg { transition: transform 0.15s; }
.sort-dir-toggle.sort-dir-asc svg { transform: rotate(180deg); }
.map-shell { position: relative; }
#map {
  height: 340px; border-radius: 12px; overflow: hidden;
  border: 1px solid var(--border); background: var(--background);
}
/* Floating icon controls in the map's corner (Recenter/heatmap toggle), instead of a
   separate button row below - keeps the map from looking empty and matches the usual
   map-control convention (zoom/locate buttons overlaid on the map itself). */
.map-controls {
  position: absolute; top: 0.6rem; right: 0.6rem; z-index: 500;
  display: flex; flex-direction: column; gap: 0.4rem;
}
.map-control-btn {
  width: 2.1rem; height: 2.1rem; padding: 0; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  color: var(--text-primary); border: 1px solid var(--border);
  box-shadow: var(--shadow-sm); cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.map-control-btn:hover { background: var(--surface); }
.map-control-btn[aria-pressed="true"] { background: var(--primary); color: var(--on-primary); }
/* One chip per contiguous kept/excluded track segment, colored to match its polyline on the
   map above (see KEPT_COLORS/EXCLUDED_COLORS in session_preview.js) so a chip is recognizable
   as "that stretch of track" at a glance. Clicking dims a chip and hides its segment on the
   map - lets a segment in the way of placing a new cut be tucked out of view temporarily. */
.segment-toggles {
  display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.7rem;
}
.segment-toggle-chip {
  width: 1.9rem; height: 0.5rem; padding: 0; border-radius: 3px; border: none;
  cursor: pointer; background: var(--chip-color); opacity: 1; transition: opacity 0.15s;
}
.segment-toggle-chip.excluded {
  background: repeating-linear-gradient(
    90deg, var(--chip-color) 0, var(--chip-color) 4px, transparent 4px, transparent 7px
  );
}
.segment-toggle-chip:hover { opacity: 0.7; }
.segment-toggle-chip[aria-pressed="false"] { opacity: 0.25; }
.segment-toggle-chip[aria-pressed="false"]:hover { opacity: 0.45; }
.scrubber-wrap { margin-top: 1.1rem; }
.scrubber-shell { position: relative; }
/* Overlaid on the scrubber's own lower-right corner instead of a separate toolbar row -
   #scrubber's innerHTML is fully rebuilt on every render, so these live as a sibling
   positioned on top of it rather than as children that would get wiped each time. */
.scrubber-zoom-controls {
  position: absolute; right: 0.4rem; bottom: 0.4rem; z-index: 3;
  display: flex; gap: 0.3rem;
}
.scrubber-zoom-btn {
  width: 1.6rem; height: 1.6rem; padding: 0; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  color: var(--text-primary); border: 1px solid var(--border);
  box-shadow: var(--shadow-sm); font-size: 0.85rem; line-height: 1; cursor: pointer;
}
.scrubber-zoom-btn:hover { background: var(--surface); }
.scrubber {
  position: relative; height: 56px; background: var(--background);
  border: 1px solid var(--border); border-radius: 10px;
  cursor: crosshair; overflow: hidden; touch-action: none; user-select: none;
}
.cut-range {
  position: absolute; top: 0; bottom: 0;
  background: color-mix(in srgb, var(--error) 32%, transparent);
  border-left: 2px solid var(--error); border-right: 2px solid var(--error);
}
.cut-handle {
  position: absolute; top: 0; bottom: 0; width: 10px; cursor: ew-resize;
}
.cut-handle.left { left: -5px; } .cut-handle.right { right: -5px; }
/* Live readout of a selection's timecode while it's being created or resized - a small chip
   (not just plain text) so it stays legible even when it overlaps the static view-range labels
   that sit in normal flow right below the scrubber. */
.cut-time-label {
  position: absolute; top: 100%; margin-top: 0.3rem; z-index: 4;
  /* Centered on the selection's midpoint at its own natural width (not stretched to the
     selection's width) - a narrow just-started selection is far smaller than the text itself. */
  transform: translateX(-50%);
  font-size: 0.72rem; color: var(--text-primary); text-align: center;
  white-space: nowrap; pointer-events: none;
  background: var(--surface); border: 1px solid var(--border); border-radius: 4px;
  padding: 1px 6px; box-shadow: var(--shadow-sm);
}
.scrubber-labels {
  display: flex; justify-content: space-between; margin-top: 0.35rem;
  font-size: 0.75rem; color: var(--text-secondary);
}
/* Shows where the (possibly zoomed-in) scrubber view sits within the full session duration.
   Only shown while zoomed in - at full zoom the scrubber already shows everything. */
.scrubber-overview {
  position: relative; height: 10px; margin-top: 0.5rem; border-radius: 5px;
  background: var(--background); border: 1px solid var(--border);
  overflow: hidden; display: none; touch-action: none;
}
.scrubber-overview.active { display: block; }
.scrubber-overview-window {
  position: absolute; top: -1px; bottom: -1px;
  background: color-mix(in srgb, var(--primary) 45%, transparent);
  border-left: 1px solid var(--primary); border-right: 1px solid var(--primary);
  cursor: grab;
}
.scrubber-overview-window:active { cursor: grabbing; }
.cuts-summary { margin: 0.6rem 0 1rem; }
.preview-grid {
  /* Export/Cuts column gets more room than Data, which only needs to fit a date and three
     short stats. */
  display: grid; grid-template-columns: minmax(220px, 1fr) minmax(240px, 1.3fr); gap: 1rem;
}
@media (max-width: 560px) {
  .preview-grid { grid-template-columns: 1fr; }
  /* Below this width the grid collapses to one column, so the Data card (and its stats) has
     the full row to itself again - see the .session-stats rule above for why that means row. */
  .preview-grid .session-stats { flex-direction: row; gap: 1.25rem; }
}
.preview-grid .card { margin: 0; }
.preview-grid .preview-map-card, .preview-grid #strava-reset-card { grid-column: 1 / -1; }
.preview-side-cards { display: flex; flex-direction: column; gap: 1rem; }
.modal-overlay {
  position: fixed; inset: 0; z-index: 2000;
  background: var(--background);
  display: flex; align-items: flex-start; justify-content: center;
  overflow-y: auto; scrollbar-gutter: stable;
}
.modal-overlay[hidden] { display: none; }
.modal-panel {
  position: relative; width: 100%; max-width: var(--content-width); margin: 0 auto;
  padding: 0 1.25rem 2.5rem;
}
.modal-panel > :not(.topbar) { padding-top: 1rem; }
