/* Analysis feature — ride history list + per-ride detail. */

#analysis-panel {
  color: var(--txt);
  font-family: var(--font-data);
}

/* Sub-navigation tabs at top of analysis panel */
.analysis-tabs {
  display: flex;
  gap: 2px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--bg3);
  padding-bottom: 0;
}
.analysis-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--dim);
  font-family: var(--font-text);
  font-size: var(--fs-base);
  font-weight: 500;
  padding: 8px 16px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.analysis-tab:hover {
  color: var(--txt);
}
.analysis-tab:focus,
.analysis-tab:focus-visible {
  outline: none;
}
.analysis-tab.active {
  color: var(--txt-hi);
  border-bottom-color: var(--amber-ink);
}

.analysis-header h2,
.analysis-detail-title h2 {
  margin: 0 0 4px 0;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--txt-hi);
}

.analysis-header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
}

.analysis-upload-btn {
  background: var(--bg1);
  border: 1px solid var(--bg3);
  color: var(--txt-hi);
  padding: 6px 14px;
  border-radius: 4px;
  cursor: pointer;

  font-size: var(--fs-xs);
}

.analysis-upload-btn:hover {
  background: var(--bg3);
  border-color: var(--bdr2);
}

.analysis-upload-status {
  color: var(--dim2);
  font-size: var(--fs-xs);
}

/* ── Title search ──────────────────────────────────────────────────────────
   Sits with the other list filters. Sized to match .analysis-upload-btn so the
   header row reads as one strip of controls. */
.analysis-search {
  background: var(--bg0);
  border: 1px solid var(--bg3);
  border-radius: 4px;
  color: var(--txt-hi);
  font-family: var(--font-text);
  font-size: var(--fs-xs);
  padding: 6px 10px;
  width: 180px;
}

.analysis-search::placeholder { color: var(--dim2); }

.analysis-search:focus {
  outline: none;
  border-color: var(--sky);
}

/* The scope line only exists while a search is narrowing a bounded range, so
   it can afford to be a full-width row under the filters. */
.analysis-search-scope {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  color: var(--dim2);
  font-size: var(--fs-xs);
}

.analysis-search-scope[hidden] { display: none; }

.analysis-search-all {
  background: none;
  border: none;
  color: var(--sky);
  cursor: pointer;
  font-family: var(--font-text);
  font-size: var(--fs-xs);
  padding: 0;
}

.analysis-search-all:hover { text-decoration: underline; }

@media (pointer: coarse) {
  .analysis-search { font-size: var(--fs-md); padding: 9px 12px; width: 200px; }
  .analysis-search-scope, .analysis-search-all { font-size: var(--fs-base); }
}

/* ── Column picker ─────────────────────────────────────────────────────────
   Dropdown under the Columns button in the activity-history header. Rows are
   built by renderColumnPicker() from LIST_COLUMNS. */
.analysis-colpicker-wrap {
  position: relative;
}

.analysis-colpicker {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  width: 300px;
  /* Seventeen rows don't fit a short window, so the list scrolls rather than
     running off the viewport. */
  max-height: 72vh;
  overflow-y: auto;
  background: var(--bg0);
  border: 1px solid var(--bg3);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
  padding: 10px;
}

.analysis-colpicker[hidden] { display: none; }

.acp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 4px 8px 10px;
  border-bottom: 1px solid var(--bg1);
  color: var(--dim);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: .04em;
}

.acp-reset {
  background: none;
  border: none;
  color: var(--sky);
  cursor: pointer;
  font-size: var(--fs-sm);
  padding: 4px 2px;
  text-transform: none;
  letter-spacing: 0;
}

.acp-reset:hover { text-decoration: underline; }

.acp-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
}

.acp-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 8px;
  border-radius: 5px;
  /* The drop indicator is a border, so reserve it on every row — otherwise
     rows jump by a pixel as the dragged item passes over them. */
  border-top: 1px solid transparent;
  border-bottom: 1px solid transparent;
  cursor: grab;
}

.acp-item:hover { background: var(--bg1); }

.acp-item.acp-dragging { opacity: .4; }
.acp-item.acp-drop-before { border-top-color: var(--sky); }
.acp-item.acp-drop-after  { border-bottom-color: var(--sky); }

/* The grip is the whole left edge of the row, not just the glyph — a 12px
   target is a target you miss. */
.acp-grip {
  color: var(--dim2);
  font-size: var(--fs-lg);
  line-height: 1;
  padding: 2px 2px 2px 0;
  cursor: grab;
}

.acp-item:hover .acp-grip { color: var(--dim); }

.acp-label {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;
  color: var(--txt);
  font-size: var(--fs-base);
  cursor: pointer;
}

.acp-label span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.acp-check {
  width: 16px;
  height: 16px;
  flex: none;
  cursor: pointer;
}

/* A fixed column (Title) shows its checkbox ticked and disabled rather than
   hiding it — an absent control reads as a rendering bug. */
.acp-check:disabled { opacity: .45; cursor: not-allowed; }
.acp-check:disabled + span { color: var(--dim); }

.acp-moves {
  display: flex;
  gap: 4px;
  flex: none;
}

.acp-move {
  background: none;
  border: 1px solid transparent;
  border-radius: 4px;
  color: var(--dim2);
  cursor: pointer;
  font-size: var(--fs-base);
  line-height: 1;
  /* Square target rather than a bare glyph — these are the reorder control on
     any device that can't drag. */
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.acp-move:hover:not(:disabled) {
  color: var(--txt);
  background: var(--bg1);
  border-color: var(--bg3);
}

.acp-move:disabled { opacity: .25; cursor: default; }

/* Touch: no hover to hint with and no drag at all, so the rows and the arrows
   have to be hittable with a thumb. The iPad app reaches this same panel. */
@media (pointer: coarse) {
  .analysis-colpicker { width: 340px; padding: 12px; }
  .acp-item      { padding: 11px 8px; gap: 12px; }
  .acp-label     { font-size: var(--fs-md); }
  .acp-check     { width: 20px; height: 20px; }
  .acp-grip      { font-size: var(--fs-xl); }
  .acp-move      { width: 40px; height: 40px; font-size: var(--fs-md); }
  .acp-head      { font-size: var(--fs-base); }
  .acp-reset     { font-size: var(--fs-base); padding: 6px 2px; }
}

.analysis-empty {
  color: var(--dim2);
  font-size: var(--fs-sm);
  margin-top: 8px;
}

/* ── Overview view ────────────────────────────────────────────────────── */

.ov-header {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.ov-header h2 {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--txt-hi);
}

.ov-range-buttons {
  display: flex;
  gap: 4px;
}

.ov-range-btn {
  background: var(--bg1);
  border: 1px solid var(--bg3);
  color: var(--dim);
  padding: 4px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-size: var(--fs-xs);
  font-family: var(--font-text);
  transition: all 0.15s;
}

.ov-range-btn:hover {
  background: var(--bg3);
  color: var(--txt-hi);
}

.ov-range-btn.active {
  background: var(--amber);
  border-color: var(--amber);
  color: var(--on-amber);
  font-weight: 700;
}

.ov-sport-picker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  font-family: var(--font-data);
  color: var(--dim);
  margin-left: 8px;
}
.ov-sport-picker select.ov-range-btn {
  appearance: none;
  -webkit-appearance: none;
  padding: 4px 22px 4px 10px;
  background-image: linear-gradient(45deg, transparent 50%, var(--dim) 50%),
                    linear-gradient(135deg, var(--dim) 50%, transparent 50%);
  background-position: calc(100% - 11px) center, calc(100% - 7px) center;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}

/* Projection-horizon picker, sitting in the PMC legend row next to the
   projection toggle. Drives the PMC, Weekly Load and Ramp Rate charts. */
.dash-pmc-weeks {
  appearance: none;
  -webkit-appearance: none;
  background: var(--bg1);
  border: 1px solid var(--bg3);
  border-radius: 4px;
  color: var(--txt2);
  font: 10px 'JetBrains Mono', ui-monospace, monospace;
  padding: 2px 18px 2px 7px;
  margin-left: -8px;   /* pull it up against the toggle it belongs to */
  cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--dim) 50%),
                    linear-gradient(135deg, var(--dim) 50%, transparent 50%);
  background-position: calc(100% - 9px) center, calc(100% - 6px) center;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}
.dash-pmc-weeks:disabled { opacity: 0.4; cursor: default; }

.ov-pmc-box {
  height: 300px;
}

.ov-weekly-box {
  height: 200px;
}

.ov-pdc-box {
  height: 260px;
}

.ov-zones-box {
  height: 240px;
}

.ov-ramp-box {
  height: 180px;
}

.ov-ef-box {
  height: 200px;
}

.ov-fatigue-box {
  height: 200px;
}

.ov-peak-box {
  height: 220px;
}

.ov-peak-summary {
  margin-top: 10px;
  font-size: var(--fs-xs);
  color: var(--dim);
  line-height: 1.6;
}

.ov-peak-highlight {
  color: var(--amber-ink);
  font-weight: 600;
}

.ov-peak-status {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 3px;
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-left: 4px;
}

.ov-peak-building {
  background: color-mix(in srgb, var(--blue) 15%, transparent);
  color: var(--blue);
  border: 1px solid color-mix(in srgb, var(--blue) 30%, transparent);
}

.ov-peak-peaking {
  background: color-mix(in srgb, var(--green) 15%, transparent);
  color: var(--green);
  border: 1px solid color-mix(in srgb, var(--green) 30%, transparent);
}

.ov-peak-fatigued {
  background: color-mix(in srgb, var(--red) 15%, transparent);
  color: var(--red);
  border: 1px solid color-mix(in srgb, var(--red) 30%, transparent);
}

.ov-peak-fresh {
  background: rgba(var(--amber-wash), 0.15);
  color: var(--amber-ink);
  border: 1px solid rgba(var(--amber-wash), 0.3);
}

.ov-pr-table {
  font-size: var(--fs-xs);
}

.ov-pr-table table {
  width: 100%;
  border-collapse: collapse;
}

.ov-pr-table th {
  text-align: left;
  color: var(--dim2);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 6px 10px;
  border-bottom: 1px solid var(--bg3);
  font-weight: 600;
}

.ov-pr-table td {
  padding: 6px 10px;
  border-bottom: 1px solid var(--bg1);
  color: var(--txt2);
}

/* A record that links to the activity that set it (see prs.js): the whole row
   for the all-time best, plus inner spans on the label and the season value —
   the label span is the keyboard target, the season span opens a different
   ride. background-color on the cells, not the row, so it paints under a
   collapsed border. */
.ov-pr-link {
  cursor: pointer;
}

tr.ov-pr-link:hover td {
  background: rgba(var(--amber-wash), 0.08);
}

span.ov-pr-link:hover,
span.ov-pr-link:focus-visible {
  text-decoration: underline;
}

span.ov-pr-link:focus-visible {
  outline: 1px solid var(--amber);
  outline-offset: 2px;
}

.ov-pr-val {
  color: var(--amber-ink);
  font-weight: 600;
}

.ov-pr-recent {
  color: var(--blue);
  font-weight: 600;
}

.ov-pr-delta-pos {
  color: var(--green);
  font-size: var(--fs-xs);
}

.ov-pr-delta-neg {
  color: var(--red);
  font-size: var(--fs-xs);
}

.ov-pr-new {
  display: inline-block;
  background: var(--green);
  color: var(--bg0);
  font-size: var(--fs-micro);
  font-weight: 700;
  padding: 1px 5px;
  border-radius: 3px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-left: 6px;
}

/* ── Compare view ────────────────────────────────────────────────────── */

.compare-chart-box {
  height: 260px;
}

.compare-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 8px;
  margin-bottom: 16px;
}

.compare-metric {
  background: var(--bg0);
  border: 1px solid var(--bg3);
  border-radius: 6px;
  padding: 10px;
}

.compare-metric-k {
  color: var(--dim2);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 6px;
}

.compare-metric-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 6px;
}

.compare-metric-val {
  font-size: var(--fs-base);
  font-weight: 600;
}

.compare-metric-a {
  color: var(--amber-ink);
}

.compare-metric-b {
  color: var(--blue);
}

.compare-metric-delta {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-align: center;
  margin-top: 4px;
}

.compare-delta-pos {
  color: var(--green);
}

.compare-delta-neg {
  color: var(--red);
}

.compare-delta-neutral {
  color: var(--dim2);
}

.compare-legend {
  display: flex;
  gap: 16px;
  margin-bottom: 12px;
  font-size: var(--fs-xs);
}

.compare-legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
}

.compare-legend-swatch {
  width: 12px;
  height: 3px;
  border-radius: 1px;
}

.compare-legend-label {
  color: var(--dim);
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#analysisBulkCompare {
  background: var(--blue);
  border-color: var(--blue);
  color: #fff;
}

#analysisBulkCompare:hover {
  background: color-mix(in srgb, var(--blue) 34%, transparent);
  color: color-mix(in srgb, var(--blue) 80%, var(--txt));
}

.ov-footer {
  margin-top: 14px;
  display: flex;
  justify-content: flex-end;
}

/* ── List view ────────────────────────────────────────────────────────── */

.analysis-table-wrap {
  margin-top: 12px;
  border: 1px solid var(--bg3);
  border-radius: 6px;
  overflow: auto;
  background: var(--bg0);
}

.analysis-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-xs);
}

.analysis-sort {
  cursor: pointer;
  user-select: none;
}

.analysis-sort:hover {
  color: var(--txt-hi);
}

.analysis-sort.sort-asc::after,
.analysis-sort.sort-desc::after {
  content: '';
  display: inline-block;
  margin-left: 5px;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  vertical-align: middle;
}

.analysis-sort.sort-asc::after {
  border-bottom: 5px solid var(--amber-ink);
}

.analysis-sort.sort-desc::after {
  border-top: 5px solid var(--amber-ink);
}

.analysis-table thead th {
  position: sticky;
  top: 0;
  background: var(--bg1);
  color: var(--dim);
  text-align: left;
  font-weight: 600;
  padding: 8px 10px;
  border-bottom: 1px solid var(--bg3);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.analysis-table tbody td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--bg1);
  color: var(--txt2);
}

.analysis-row {
  cursor: pointer;
  transition: background 0.1s;
}

.analysis-row:hover {
  background: var(--bg1);
}

/* Mode cell — two stacked labels: "Outdoor/Indoor/—" on top, trainer mode
   ('erg', 'slope', 'gpx' …) underneath in a quieter colour. */
.mode-cell        { display: flex; flex-direction: column; gap: 1px; line-height: 1.15; }
.mode-route       { color: var(--green); font-weight: 600; }
.mode-indoor      { color: var(--amber-ink); font-weight: 600; }
.mode-unknown     { color: var(--dim2); }
.mode-trainer     { color: var(--dim2); font-size: var(--fs-xs); text-transform: lowercase; }

.analysis-rename-btn {
  background: var(--bg1);
  border: 1px solid var(--bg3);
  border-radius: 3px;
  color: var(--dim);
  cursor: pointer;
  font-size: var(--fs-xs);
  padding: 2px 5px;
  margin-right: 6px;
  opacity: 0;
  transition: opacity 0.15s;
  vertical-align: middle;
}
.analysis-row:hover .analysis-rename-btn { opacity: 1; }
.analysis-rename-btn:hover { color: var(--amber-ink); border-color: var(--amber-ink); }

.analysis-rename-input {
  background: var(--bg0);
  border: 1px solid var(--amber-ink);
  border-radius: 3px;
  color: var(--txt-hi);
  font-family: var(--font-text);
  font-size: var(--fs-xs);
  padding: 3px 6px;
  width: 100%;
  outline: none;
}

.analysis-check-cell {
  width: 28px;
  text-align: center;
  padding: 0 4px !important;
}

.analysis-check-cell input[type="checkbox"] {
  accent-color: var(--amber);
  cursor: pointer;
}

.analysis-row.selected {
  background: rgba(var(--amber-wash), 0.08);
}

.analysis-row.selected:hover {
  background: rgba(var(--amber-wash), 0.14);
}

.analysis-bulk-bar {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  background: rgba(var(--amber-wash), 0.1);
  border: 1px solid var(--amber-ink);
  border-radius: 4px;
}

.analysis-bulk-count {
  color: var(--amber-ink);
  font-size: var(--fs-xs);
  font-weight: 600;
}

.analysis-bulk-btn {
  background: var(--bg1);
  border: 1px solid var(--bg3);
  color: var(--txt2);
  padding: 4px 10px;
  border-radius: 3px;
  cursor: pointer;

  font-size: var(--fs-xs);
}

.analysis-bulk-btn:hover {
  background: var(--bg3);
  color: var(--txt-hi);
}

.analysis-bulk-delete {
  background: color-mix(in srgb, var(--red) 20%, transparent);
  border-color: var(--red);
  color: var(--red);
}

.analysis-bulk-delete:hover {
  background: var(--red);
  color: var(--bg0);
}

.analysis-actions-cell {
  text-align: right;
  width: 32px;
}


.analysis-title-cell {
  color: var(--txt-hi);
  font-weight: 500;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The title is an <a> ONLY to get the browser's native "Copy link address" on
   right-click (the href carries ?ride=N). It must be visually indistinguishable
   from plain text — inherit font + colour in every link state, never underline. */
.analysis-title-link,
.analysis-title-link:link,
.analysis-title-link:visited,
.analysis-title-link:hover,
.analysis-title-link:focus,
.analysis-title-link:active {
  color: inherit;
  font: inherit;
  text-decoration: none;
  background: none;
  cursor: inherit;
  outline: none;
}

/* ── Detail view ──────────────────────────────────────────────────────── */

.analysis-detail-header {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 14px;
}

#analysisResetZoom {
  margin-left: auto;
}

.analysis-back {
  background: var(--bg1);
  border: 1px solid var(--bg3);
  color: var(--txt2);
  padding: 6px 12px;
  border-radius: 4px;
  cursor: pointer;

  font-size: var(--fs-xs);
}

.analysis-back:hover {
  background: var(--bg3);
  color: var(--txt-hi);
}

.analysis-protocol-picker {
  background: var(--bg1);
  border: 1px solid var(--bg3);
  color: var(--txt2);
  padding: 6px 10px;
  border-radius: 4px;
  font-size: var(--fs-xs);
}

/* ⚡ next to ride title when there's at least one saved FTP test row. */
.ftp-test-badge {
  display: inline-block;
  margin-left: 6px;
  color: var(--amber-ink);
  font-size: var(--fs-sm);
  line-height: 1;
}

/* Strava provenance badges — pushed (↑) or pulled (↓). */
.strava-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-decoration: none;
  vertical-align: middle;
  transition: opacity 0.15s;
}
.strava-badge:hover {
  opacity: 0.85;
}
.strava-pulled {
  background: #fc4c02;
  color: #fff;
}
.strava-pushed {
  background: transparent;
  color: #fc4c02;
  border: 1px solid #fc4c02;
}
/* Neutral alias state — Strava ID is known but TourKit didn't perform the
   pull or push (Intervals carried the ID forward as cross-source dedup). */
.strava-alias {
  background: transparent;
  color: var(--dim);
  border: 1px solid var(--bg3);
}

/* Intervals.icu badges — pull-only, mirrors Strava's visual rhythm. */
.intervals-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-decoration: none;
  vertical-align: middle;
  transition: opacity 0.15s;
}
.intervals-badge:hover { opacity: 0.85; }
.intervals-pulled {
  background: var(--teal);
  color: var(--bg0);
}
.intervals-alias {
  background: transparent;
  color: var(--teal);
  border: 1px solid var(--teal);
}

/* Provenance chain — replaces single intervals/strava badges when the ride
   has fit_source='intervals'. Renders as "Intervals <- Strava <- Garmin"
   with each hop visually distinct: Intervals matches the pulled badge,
   Strava picks up the Strava orange, device is a neutral pill. */
.prov-chain {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: 6px;
  vertical-align: middle;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.03em;
}
.prov-hop {
  padding: 1px 6px;
  border-radius: 3px;
  text-decoration: none;
  transition: opacity 0.15s;
}
.prov-hop:hover { opacity: 0.85; }
.prov-intervals { background: var(--teal); color: var(--bg0); }
.prov-strava    { background: #fc4c02; color: #fff; }
.prov-device {
  background: transparent;
  color: var(--dim);
  border: 1px solid var(--bg3);
}
.prov-arrow { color: var(--bdr2); font-weight: 400; }

/* Saved-tests panel inside the detail view. */
.analysis-saved-tests {
  grid-column: 1 / -1;
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 6px;
  padding: 12px 14px;
}

.saved-test-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 12px;
  align-items: center;
  padding: 6px 0;
  border-top: 1px solid var(--bg1);
  font-size: var(--fs-sm);
  color: var(--txt2);
}

.saved-test-row:first-of-type { border-top: none; }

.saved-test-row .st-proto  { color: var(--txt); font-weight: 500; }
.saved-test-row .st-ftp    { color: var(--amber-ink); font-family: var(--font-data); font-variant-numeric: tabular-nums; }
.saved-test-row .st-date   { color: var(--dim2); font-size: var(--fs-xs); }

.saved-test-row .badge.exp {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--purple) 28%, transparent);
  color: color-mix(in srgb, var(--purple) 80%, var(--txt));
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 1px;
}

/* ── Sub-Max Stepped expanded detail ───────────────────────────────────── */

.saved-test-row.expandable {
  cursor: pointer;
  grid-template-columns: auto 1fr auto auto;
}
.saved-test-row.expandable:hover { background: var(--bg0); }
.saved-test-row .st-caret {
  color: var(--dim2);
  font-size: var(--fs-xs);
  transition: transform 120ms ease;
  display: inline-block;
}
.saved-test-row.expanded .st-caret { transform: rotate(90deg); }

.saved-test-detail {
  grid-column: 1 / -1;
  padding: 12px 6px 4px 6px;
  border-top: 1px dashed var(--bg1);
  margin-top: 4px;
  margin-bottom: 8px;
}
.saved-test-loading {
  padding: 8px 0;
  color: var(--dim2);
  font-size: var(--fs-xs);
}

.sm-header {
  margin-bottom: 12px;
}
.sm-ftp-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 4px;
}
.sm-ftp {
  color: var(--amber-ink);
  font-size: var(--fs-lg);
  font-weight: 500;
  font-family: var(--font-data); font-variant-numeric: tabular-nums;
}
.sm-ci {
  color: var(--txt2);
  font-size: var(--fs-sm);
  font-family: var(--font-data); font-variant-numeric: tabular-nums;
}
.sm-conf {
  font-size: var(--fs-micro);
  padding: 2px 8px;
  border-radius: 3px;
  text-transform: uppercase;
  letter-spacing: 1px;
}
.sm-conf-high   { background: color-mix(in srgb, var(--green) 20%, transparent); color: var(--green); }
.sm-conf-medium { background: color-mix(in srgb, var(--amber) 26%, transparent); color: var(--amber-ink); }
.sm-conf-low    { background: color-mix(in srgb, var(--red) 20%, transparent); color: var(--red); }
.sm-sub {
  color: var(--dim);
  font-size: var(--fs-xs);
  margin-top: 2px;
  font-family: var(--font-data); font-variant-numeric: tabular-nums;
}

.sm-markers-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-xs);
  font-family: var(--font-data); font-variant-numeric: tabular-nums;
  margin-bottom: 14px;
}
.sm-markers-table th {
  text-align: left;
  padding: 4px 8px;
  color: var(--dim2);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  font-size: var(--fs-micro);
  border-bottom: 1px solid var(--bg1);
}
.sm-markers-table td {
  padding: 6px 8px;
  color: var(--txt2);
  border-bottom: 1px solid var(--bg0);
  vertical-align: top;
}
.sm-markers-table td:first-child {
  color: var(--txt);
  font-weight: 500;
  width: 40px;
}
.sm-marker-row.sm-marker-skip td { color: var(--dim2); font-style: italic; }
.sm-outlier {
  display: inline-block;
  margin-left: 4px;
  padding: 0 4px;
  background: color-mix(in srgb, var(--red) 20%, transparent);
  color: var(--red);
  font-size: var(--fs-micro);
  border-radius: 2px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-style: normal;
}

.sm-charts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 10px;
  margin-bottom: 12px;
}
.sm-chart-card {
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 5px;
  padding: 8px 10px;
}
.sm-chart-card-wide { grid-column: 1 / -1; }
.sm-chart-title {
  color: var(--dim);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.8px;
  margin-bottom: 6px;
}
.sm-chart-wrap {
  height: 180px;
  position: relative;
}
.sm-chart-card-wide .sm-chart-wrap { height: 220px; }
.sm-canvas { width: 100% !important; height: 100% !important; }

.sm-flags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-top: 4px;
}
.sm-flags-label {
  color: var(--dim2);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.8px;
  margin-right: 4px;
}
.sm-flag {
  padding: 2px 8px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--amber) 26%, transparent);
  color: var(--amber-ink);
  font-size: var(--fs-xs);
  font-family: var(--font-data);
}

/* Full-page variant: used by the dedicated Sub-Max Stepped results view.
   Overrides the compact sizing that's right for an inline saved-test-row. */
.submax-full-page {
  border-top: none;
  padding: 16px 8px;
  margin: 0;
}
.submax-full-page .sm-ftp { font-size: var(--fs-hero); }
.submax-full-page .sm-ci  { font-size: var(--fs-base); }
.submax-full-page .sm-sub { font-size: var(--fs-base); }
.submax-full-page .sm-markers-table { font-size: var(--fs-base); margin-bottom: 20px; }
.submax-full-page .sm-markers-table th { font-size: var(--fs-xs); padding: 8px; }
.submax-full-page .sm-markers-table td { padding: 10px 8px; }
.submax-full-page .sm-charts-grid {
  grid-template-columns: repeat(auto-fit, minmax(460px, 1fr));
  gap: 16px;
}
.submax-full-page .sm-chart-card { padding: 14px 16px; }
.submax-full-page .sm-chart-title { font-size: var(--fs-sm); margin-bottom: 10px; }
.submax-full-page .sm-chart-wrap { height: 360px; }
.submax-full-page .sm-chart-card-wide .sm-chart-wrap { height: 420px; }

/* Dual-result hero row — Physiology Fusion + ML side-by-side at the top. */
.sm-dual-hero {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 20px;
}
.sm-hero-card {
  padding: 16px 20px;
  border: 1px solid var(--bg3);
  border-radius: 8px;
  background: var(--bg0);
}
.sm-hero-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--dim2);
  margin-bottom: 8px;
}
.sm-hero-card .sm-ftp {
  font-size: var(--fs-xxl);
}
.sm-hero-card .sm-ci {
  font-size: var(--fs-base);
}
.sm-hero-card .sm-sub {
  font-size: var(--fs-sm);
  margin-top: 4px;
}
@media (max-width: 700px) {
  .sm-dual-hero { grid-template-columns: 1fr; }
}

/* Legacy ML slot — kept for saved-test row expansion. */
.sm-ml-slot {
  margin-top: 20px;
  padding: 16px;
  border: 1px solid var(--bg3);
  border-radius: 8px;
  background: var(--bg0);
}
.sm-ml-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.sm-ml-title {
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--txt);
  letter-spacing: 0.3px;
}
.sm-ml-body {
  font-size: var(--fs-base);
  color: var(--dim);
}
.sm-ml-pending {
  font-style: italic;
  color: var(--dim2);
}
.sm-conf-pending { background: var(--bg1); color: var(--dim2); }

/* FTP test detection banner — shown below metrics when ride is an FTP test. */
.analysis-ftp-banner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  margin-bottom: 16px;
  border: 1px solid var(--blue);
  border-radius: 8px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--blue) 20%, transparent) 0%, color-mix(in srgb, var(--purple) 24%, transparent) 100%);
}
.ftp-banner-icon {
  font-size: var(--fs-xl);
  flex-shrink: 0;
}
.ftp-banner-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ftp-banner-text strong {
  font-size: var(--fs-base);
  color: var(--txt);
  font-weight: 600;
}
.ftp-banner-text span {
  font-size: var(--fs-xs);
  color: var(--dim);
}
.ftp-banner-btn {
  flex-shrink: 0;
  padding: 6px 16px;
  border: 1px solid var(--blue);
  border-radius: 6px;
  background: var(--blue);
  color: #fff;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s;
}
.ftp-banner-btn:hover {
  background: #2563eb;
}

.analysis-detail-sub {
  color: var(--dim2);
  font-size: var(--fs-xs);
}

.analysis-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 8px;
  margin-bottom: 16px;
}

.analysis-metric {
  background: var(--bg0);
  border: 1px solid var(--bg3);
  border-radius: 6px;
  padding: 8px 10px;
}

.analysis-metric-k {
  color: var(--dim2);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 4px;
}

.analysis-metric-v {
  color: var(--amber-ink);
  font-size: var(--fs-md);
  font-weight: 600;
}

.analysis-charts {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.analysis-chart-card {
  background: var(--bg0);
  border: 1px solid var(--bg3);
  border-radius: 6px;
  padding: 12px;
}

.analysis-chart-label {
  color: var(--dim);
  font-size: var(--fs-xs);
  margin-bottom: 8px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.analysis-chart-box {
  position: relative;
  height: 240px;
}

.analysis-zone-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

/* Stand-in when an activity carries no chartable channel at all. */
.analysis-no-channels {
  background: var(--bg0);
  border: 1px solid var(--bg3);
  border-radius: 6px;
  padding: 18px;
  color: var(--dim);
  font-size: var(--fs-sm);
  text-align: center;
}

/* Only one of the two zone charts is available (e.g. HR-only ride, or no FTP
   set) — let the survivor take the full width instead of leaving a hole. */
.analysis-zone-row.single {
  grid-template-columns: 1fr;
}

.analysis-zone-card {
  min-width: 0;
}

.analysis-zone-box {
  height: 200px;
}

@media (max-width: 700px) {
  .analysis-zone-row {
    grid-template-columns: 1fr;
  }
}

/* ── FTP Estimation results (inside detail view) ── */

#ftpResultsSection {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--bg3);
}

.ftp-result-hero { background: var(--bg0); border: 1px solid var(--bg3); border-radius: 8px; padding: 20px; display: flex; align-items: center; gap: 16px; }
.ftp-value { font-size: var(--fs-mega); font-weight: 800; letter-spacing: -2px; line-height: 1; color: var(--amber, var(--amber)); text-shadow: 0 0 30px rgba(245,158,11,0.2); }
.ftp-unit { font-size: var(--fs-base); color: var(--dim2); font-weight: 400; letter-spacing: 2px; }
.ftp-result-details { display: flex; flex-direction: column; gap: 4px; }
.ftp-ci-range { font-size: var(--fs-xs); color: var(--dim2); letter-spacing: 0.5px; }

.ftp-confidence-badge { display: inline-block; padding: 3px 10px; border-radius: 4px; font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase; letter-spacing: 1.5px; width: fit-content; }
.ftp-confidence-badge.confidence-high { background: color-mix(in srgb, var(--green) 27%, transparent); color: var(--green); border: 1px solid color-mix(in srgb, var(--green) 30%, transparent); }
.ftp-confidence-badge.confidence-medium { background: rgba(var(--amber-wash), 0.13); color: var(--amber-ink); border: 1px solid rgba(var(--amber-wash), 0.3); }
.ftp-confidence-badge.confidence-low { background: color-mix(in srgb, var(--red) 8%, transparent); color: var(--red); border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); }

.ftp-metrics-grid { display: flex; gap: 10px; }
.ftp-metric-card { flex: 1; background: var(--bg0); border: 1px solid var(--bg3); border-radius: 6px; padding: 10px 8px; text-align: center; min-width: 0; }
.ftp-metric-card .value { font-size: var(--fs-base); font-weight: 800; line-height: 1.2; white-space: nowrap; }
.ftp-metric-card .label { font-size: var(--fs-micro); letter-spacing: 1.5px; color: var(--dim2); text-transform: uppercase; margin-top: 4px; white-space: nowrap; }

.ftp-charts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 700px) { .ftp-charts-grid { grid-template-columns: 1fr; } }
.ftp-chart-card { background: var(--bg0); border: 1px solid var(--bg3); border-radius: 8px; padding: 14px 10px 8px; }
.ftp-chart-card h3 { font-size: var(--fs-micro); font-weight: 600; letter-spacing: 3px; color: var(--dim2); text-transform: uppercase; margin-bottom: 8px; margin-left: 6px; }
.ftp-chart-card canvas { width: 100% !important; max-height: 280px; }

.ftp-warnings:empty { display: none; }
.ftp-warning-item { background: color-mix(in srgb, var(--orange) 13%, transparent); border: 1px solid color-mix(in srgb, var(--orange) 25%, transparent); border-radius: 6px; padding: 8px 12px; margin-bottom: 6px; font-size: var(--fs-xs); color: var(--orange); display: flex; align-items: flex-start; gap: 6px; letter-spacing: 0.3px; }

@media (max-width: 600px) {
  .ftp-metrics-grid { flex-wrap: wrap; }
  .ftp-metric-card { flex: 1 1 calc(33% - 8px); min-width: 80px; }
}

/* ── Sub-tabs (Data | View route) ─────────────────────────────────────── */

.analysis-subtabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--bg3);
  margin-bottom: 14px;
}

.analysis-subtab {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--dim);
  padding: 8px 14px;
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.4px;
  transition: color 0.12s, border-color 0.12s;
}

.analysis-subtab:hover {
  color: var(--txt);
}

.analysis-subtab.active {
  color: var(--txt-hi);
  border-bottom-color: var(--amber-ink);
}

/* ── View route tab — drawer + map + resizable elevation profile ──────── */
/* Layout mirrors the route planner: a flex row with a 280px climb drawer
   on the left (collapsible to 0) and a flex-column main area on the right
   that stacks the map (flex:1) and a resizable elevation panel below it.
   The whole thing fills a tall block of vertical space so the map has room
   to breathe; fullscreen mode (atk-fullscreen on the detail view) hides the
   analysis page chrome so the map + profile take the whole panel. It does NOT
   hide the climbs drawer — this said it did, and no rule ever has — and it
   does not cover the app header either, see TKB-298 at that block. */

/* The detail view is a flex column so the View route panel can fill exactly
   the space left below the page chrome instead of guessing at it. It used to
   be `.atk { height: calc(100vh - 240px) }`, where 240 stood for the header,
   the title block, the metric tiles and the sub-tabs — but that stack has no
   fixed height. The PR chips wrap to a second row on a ride holding several
   records, and the route-match, recording-rate and FTP banners each appear
   only sometimes. On a two-chip-row ride the block ran ~100px past the fold,
   so a panel that is meant to be self-contained put a scrollbar on
   #analysis-panel and hid the bottom of the elevation profile (TKB-295).

   No JS measurement is needed for this: #analysis-panel is `position: fixed`
   with both `top` and `bottom` set (core/shell.js _showAnalysisTab), so its
   height is definite and the percentage below resolves against it. */
#analysis-detail-view {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}

.atk {
  display: flex;
  /* basis 0, not auto: with `auto` the panel measures itself from its own
     content — a 500px profile makes it ask for 523px — and a container that is
     only min-height-constrained grows to that rather than shrinking it, which
     puts the scrollbar straight back on a laptop. Sized from the free space
     alone it is exactly what is left, and min-height is the only floor. */
  flex: 1 1 0;
  min-height: 480px;   /* a short viewport scrolls rather than crush the map */
  position: relative;
  border: 1px solid var(--bg3);
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg0);
}

/* ── Sidebar drawer (climbs + Save as route) ──────────────────────────── */
.atk-sidebar {
  width: 280px;
  flex-shrink: 0;
  background: var(--bg0);
  border-right: 1px solid var(--bg3);
  display: flex;
  flex-direction: column;
  transition: width 0.18s ease;
  overflow: hidden;
}

.atk-sidebar.collapsed {
  width: 0;
  border-right: 0;
}

.atk-sidebar-head {
  flex-shrink: 0;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 3px;
  color: var(--dim2);
  text-transform: uppercase;
  padding: 12px 14px 8px;
  border-bottom: 1px solid var(--bg1);
}

.atk-toolbar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--bg1);
}

.atk-toolbar-status {
  flex: 1;
  font-size: var(--fs-xs);
  color: var(--dim);
  line-height: 1.3;
  word-break: break-word;
}
.atk-toolbar-status a { color: var(--amber-ink); text-decoration: none; }
.atk-toolbar-status a:hover { text-decoration: underline; }

.atk-btn {
  background: var(--bg1);
  border: 1px solid var(--bg3);
  color: var(--txt2);
  padding: 7px 12px;
  border-radius: 4px;
  cursor: pointer;
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
}
.atk-btn:hover { background: var(--bg3); color: var(--txt-hi); }
.atk-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ── Detection thresholds (TKF-256) ───────────────────────────────────────
   Two selects onto the settings the planner's Climbs section also writes. They
   sit OUTSIDE .atk-sidebar-body on purpose: that body is rewritten wholesale
   on every render, and these are wanted most when it says "no significant
   climbs" — which is a scroll away from useless if they scroll with the list.
   Chrome matches .atk-toolbar above rather than the rows below, because that
   is what they are. */
.atk-climb-opts {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 9px 14px;
  border-bottom: 1px solid var(--bg1);
}
.atk-climb-opts[hidden] { display: none; }

.atk-climb-opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: var(--fs-xs);
  color: var(--dim);
  cursor: pointer;
}
/* Both sides must be allowed to shrink — the drawer is 280px and "Min gradient"
   is "Mindestgefälle" in German. Same reasoning as #route-planner-panel
   .rp-rp-field, whose select chrome these copy. */
.atk-climb-opt > span {
  min-width: 0;
  overflow-wrap: anywhere;
}
.atk-climb-opt select {
  background: var(--bg2);
  color: var(--txt);
  border: 1px solid var(--bdr);
  border-radius: 4px;
  padding: 3px 5px;
  font: inherit;
  font-size: var(--fs-xs);
  min-width: 0;
  max-width: 55%;
  text-overflow: ellipsis;
  cursor: pointer;
}

/* The climb list is the only thing rendered in here, and since TKB-288 its
   rows are flat with full-bleed hairlines between them — so the inset and the
   inter-card gap move onto the rows themselves. */
.atk-sidebar-body {
  flex: 1;
  overflow-y: auto;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.atk-sidebar-body .empty {
  font-size: var(--fs-xs);
  color: var(--dim2);
  font-style: italic;
  padding: 12px;
  text-align: center;
}

/* Drawer collapse handle — straddles the sidebar's right edge, like the
   planner's. Stays clickable when the drawer is collapsed so the user
   can pop it back out. Positioned absolute relative to .atk so it floats
   over the map. */
.atk-sidebar-collapse {
  position: absolute;
  top: 12px;
  left: 280px;
  transform: translateX(-50%);
  width: 22px;
  height: 36px;
  background: var(--bg1);
  color: var(--txt);
  border: 1px solid var(--bg3);
  border-radius: 4px;
  cursor: pointer;
  z-index: 1000;
  font-size: var(--fs-base);
  line-height: 1;
  transition: left 0.18s ease;
}
.atk-sidebar.collapsed ~ .atk-sidebar-collapse {
  left: 0;
  transform: none;
}
.atk-sidebar-collapse:hover { border-color: var(--amber-ink); }

/* Offset Leaflet's top-left controls so they clear the collapse handle. */
.atk-main .leaflet-top.leaflet-left { left: 21px; }
.atk:has(.atk-sidebar.collapsed) .atk-main .leaflet-top.leaflet-left {
  left: 32px;
}

/* ── Layer switcher position ─────────────────────────────────────────────
   The control itself is core/map-layers.js + core/map-layers.css, shared with
   the route planner (TKF-257). What stayed here is the only part that is this
   map's own: where the Leaflet corner sits. The ~60 lines of chrome that used
   to follow were a copy of .rp-map-layers-* — the comment on them even said so
   — and they are gone with the copy. */
.atk-main .leaflet-top.leaflet-right .tk-ml-ctl {
  margin-top: 12px;                  /* == the planner's inset */
  margin-right: 18px;
}

/* ── Main column (map + profile) ──────────────────────────────────────── */
.atk-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  position: relative;
}

.atk-map {
  flex: 1;
  min-height: 0;
  background: var(--bg0);
  -webkit-touch-callout: none;
  -webkit-user-select: none;
}

.atk-profile-toggle {
  flex-shrink: 0;
  align-self: flex-end;
  background: none;
  border: none;
  color: var(--dim);
  padding: 2px 10px;
  font-size: var(--fs-xs);
  cursor: pointer;
}
.atk-profile-toggle:hover { color: var(--txt-hi); }
.atk-profile-toggle[hidden] { display: none; }

.atk-profile {
  flex-shrink: 0;
  height: 180px;
  /* The drag handle clamps to an absolute 80–500px and persists the result
     (track-view.js _wireProfileResize), which says nothing about the panel it
     has to fit inside. A profile dragged to 500 on a desktop then reopens on a
     laptop where .atk is only ~570px tall and leaves the map ~40px — and since
     this is flex-shrink:0 inside overflow:hidden, the map does not scroll, it
     just isn't there. Cap it against the container so the map always keeps
     40%; at every size the drag already allowed, this changes nothing. */
  max-height: 60%;
  background: var(--bg0);
  border-top: 1px solid var(--bg3);
  padding: 0 10px 4px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}
.atk-profile[hidden] { display: none; }

.atk-profile-resize {
  height: 6px;
  cursor: ns-resize;
  background: transparent;
  position: relative;
  flex-shrink: 0;
}
.atk-profile-resize::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 2px;
  transform: translateX(-50%);
  width: 36px;
  height: 3px;
  border-radius: 2px;
  background: var(--dim);
  opacity: 0.35;
}
.atk-profile-resize:hover::after { opacity: 0.7; }

.atk-profile-chart-wrap {
  flex: 1;
  min-height: 0;
  min-width: 0;
  width: 100%;
  position: relative;
  overflow: hidden;
}
.atk-profile-chart-wrap canvas { display: block; }

/* ── Fullscreen mode ──────────────────────────────────────────────────── */
/* Toggled by the Leaflet ⛶ button on the map. Hides the page chrome — back
   button, title, metrics, sub-tabs, charts — so the map and profile take the
   whole panel.
 *
 * THE PANEL, not the whole viewport: the app header stays, wordmark and Coach
 * Chad pill included, which is what the route planner's full screen does
 * (TKB-298). Its .rp-fullscreen only collapses the two sidebars and leaves the
 * panel where shell.js put it, at top:<header height>.
 *
 * So there is no positioning here at all. This used to be `position: fixed;
 * top: 0` over everything, which then needed three `left:` rules to dodge the
 * nav sidebar it had climbed on top of. Since TKB-295 the detail view is a flex
 * column and .atk is `flex: 1 1 0`, so once the chrome above it is display:none
 * the panel is the only thing left and it fills the space by itself. Deleting
 * the fixed positioning deleted the sidebar rules with it — and the FAB
 * collision that TKB-296 had to hide the pill to solve, since both were only
 * ever at top:12px because this had climbed to top:0. */
#analysis-detail-view.atk-fullscreen .analysis-detail-header,
#analysis-detail-view.atk-fullscreen .analysis-metrics,
#analysis-detail-view.atk-fullscreen .analysis-load-streams,
#analysis-detail-view.atk-fullscreen .analysis-subtabs,
#analysis-detail-view.atk-fullscreen #analysisCharts {
  display: none !important;
}
/* Edge to edge, which is the other half of matching the planner (TKB-300).
   #analysis-panel carries `padding: 14px` — the planner's panel carries none —
   so stopping at the header still left a 14px frame of panel background around
   the map. !important because that padding is an INLINE style written by
   core/shell.js _showAnalysisTab, which a plain rule cannot outrank.

   overflow with it: the panel is `overflow-y: auto`, and in full screen there is
   deliberately nothing to scroll — .atk fills it exactly — so a scrollbar here
   could only be a rounding artefact eating a few pixels of map. */
#analysis-panel:has(#analysis-detail-view.atk-fullscreen) {
  padding: 0 !important;
  overflow: hidden !important;
}

#analysis-detail-view.atk-fullscreen .atk {
  /* The 480px floor is what makes a SHORT viewport scroll rather than crush the
     map; in full screen there is nothing above to make room for, so filling the
     panel exactly is the right answer even when that is less. */
  min-height: 0;
  border: 0;
  border-radius: 0;
}


/* Reusable Leaflet fullscreen button — same styling as the planner's. */
.atk-fullscreen-btn {
  width: 34px;
  height: 34px;
  background: var(--bg1);
  color: var(--txt);
  border: 2px solid rgba(0, 0, 0, 0.2);
  border-radius: 4px;
  font-size: var(--fs-lg);
  line-height: 30px;
  text-align: center;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.atk-fullscreen-btn:hover { background: var(--bg3); }

/* Fallback banner: the climbs drawer is DEM-detected so it agrees with the
   route planner, and this appears only when the height service was unreachable
   and it fell back to the activity's recorded altitude (TKB-290). Dim and
   above the rows — it qualifies the whole list, not any one climb. */
.tk-climb-note {
  padding: 9px 12px;
  border-bottom: 1px solid var(--bdr);
  font-size: var(--fs-xs);
  line-height: 1.45;
  color: var(--dim);
}

/* ── Climb rows ───────────────────────────────────────────────────────── */
/* Flat rows parted by a hairline, matching the planner's climb list — see
   TKB-288 for why the cards went. The amber left rule replaces the border
   colour as the map↔list hover link. */
.tk-climb-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--bdr);
  cursor: pointer;
  transition: background 0.12s;
}

.tk-climb-row:last-child { border-bottom: 0; }

.tk-climb-row::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--amber-ink);
  opacity: 0;
  transition: opacity 0.12s;
}

.tk-climb-row:hover,
.tk-climb-row.is-hover {
  background: var(--bg1);
}

.tk-climb-row:hover::before,
.tk-climb-row.is-hover::before { opacity: 1; }

.tk-climb-cat {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 38px;
  height: 22px;
  padding: 0 6px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  border-radius: 4px;
  background: var(--bg3);
  color: var(--txt2);
}

/* No category means no colour to carry, so no pill — a plain tabular index at
   the pill's width, so titles line up either way. Mirrors .rp-climb-cat-none
   in the planner (TKB-288). The second selector catches the same badge before
   the -none class existed: track-view.js is imported bare from features/, so a
   returning rider can hold a month-old copy of it (TKB-213). */
.tk-climb-cat-none,
.tk-climb-cat:not([class*="tk-climb-cat-"]) {
  background: none;
  color: var(--dim);
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  padding: 0;
}

/* The one climb-category ramp, shared with the planner and the public route
   page — solid theme-independent fills, HC heaviest (TKF-226). The ink changes
   halfway down because white fails 4.5:1 on the light half and near-black
   passes; route-planner.css carries the full reasoning and the numbers
   (TKB-294).
   This drawer had its own until TKB-293: coloured text on a coloured fill,
   which put Cat 2 at 1.08:1 in light theme, and #ca8a04 on Cat 3 where the
   other two surfaces mean Cat 4 by it — the same climb, a different colour
   depending on which page the rider was looking at. */
.tk-climb-cat-hc   { background: #7f1d1d; color: #fff; }
.tk-climb-cat-cat1 { background: #b91c1c; color: #fff; }
.tk-climb-cat-cat2 { background: #ea580c; color: #1b1813; }
.tk-climb-cat-cat3 { background: #d97706; color: #1b1813; }
.tk-climb-cat-cat4 { background: #ca8a04; color: #1b1813; }

.tk-climb-main { flex: 1; min-width: 0; }
.tk-climb-title { font-size: var(--fs-sm); font-weight: 600; color: var(--txt-hi); }
.tk-climb-meta  { font-size: var(--fs-xs); color: var(--dim); margin-top: 2px; }

/* ── Durability Analysis view ──────────────────────────────────────────── */

/* Direction lives on the arrow (.dur-head-arrow below), not on the container:
   the coloured left edge that used to say it as well was double-encoding, and
   the pattern that made every card look machine-made (TKF-310). */
.dur-headline {
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 8px;
  padding: 14px 16px;
  margin-bottom: 16px;
}
.dur-head-top { display: flex; align-items: center; gap: 8px; }
.dur-head-arrow { font-size: var(--fs-base); color: var(--dim); }
.dur-dir-improving .dur-head-arrow { color: var(--green); }
.dur-dir-declining .dur-head-arrow { color: var(--red); }
/* "holding" means "fitted, but not distinguishable from noise" — that is a
   neutral state, not a caution, so it reads slate rather than amber. */
.dur-dir-holding   .dur-head-arrow { color: var(--dim); }
.dur-head-label { font-size: var(--fs-md); font-weight: 600; color: var(--txt-hi); }
.dur-head-detail { font-size: var(--fs-sm); color: var(--txt2); margin-top: 6px; }
.dur-head-def { font-size: var(--fs-xs); color: var(--dim2); margin-top: 8px; line-height: 1.5; }

/* Ride accounting under the headline — what qualified, what didn't, and the
   rules. Deliberately plain: it's a footnote to the claim above it, not a card
   competing with the tiles below. */
.dur-diagnostics {
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--txt2);
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 16px;
}
.dur-diagnostics:empty { display: none; }
.dur-diag-line strong { color: var(--txt-hi); font-family: var(--font-data); }
.dur-diag-muted { color: var(--dim2); font-size: var(--fs-xs); }

.dur-multiday {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
  margin-bottom: 16px;
}
.dur-tile {
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 8px;
  padding: 12px 14px;
}
.dur-tile-v   { font-size: var(--fs-lg); font-weight: 700; color: var(--txt-hi); font-family: var(--font-data); }
.dur-tile-l   { font-size: var(--fs-xs); color: var(--dim); margin-top: 4px; }
.dur-tile-sub { font-size: var(--fs-xs); color: var(--dim2); margin-top: 2px; }

.dur-chart-box { height: 300px; position: relative; }

/* What the drawn trend line actually covers: n fitted, length adjustment, R²,
   significance. Sits directly under the chart so the line is never read without
   its caveats. */
.dur-trend-note {
  font-size: var(--fs-sm);
  color: var(--dim2);
  line-height: 1.5;
  padding: 8px 4px 0;
  font-family: var(--font-text);
}
.dur-trend-note:empty { display: none; }

.dur-empty {
  color: var(--dim2);
  font-size: var(--fs-xs);
  line-height: 1.6;
  padding: 24px 16px;
  text-align: center;
}

/* Premium upsell (only shown at prod for free users) */
.dur-locked-card {
  text-align: center;
}
.dur-lock-title { font-size: var(--fs-md); font-weight: 600; color: var(--txt-hi); }
.dur-lock-body  { font-size: var(--fs-sm); color: var(--txt2); margin: 8px auto 14px; max-width: 520px; line-height: 1.6; }
.dur-upgrade-btn {
  display: inline-block;
  background: var(--amber);
  color: var(--on-amber);
  font-weight: 600;
  font-size: var(--fs-sm);
  text-decoration: none;
  padding: 8px 18px;
  border-radius: 6px;
}
.dur-upgrade-btn:hover { background: var(--amber-ink); }
.analysis-durability-view.dur-locked .analysis-charts,
#analysis-durability-view.dur-locked .analysis-charts { display: none; }

/* ── VO2max Trends view ─────────────────────────────────────────────────── */

/* Direction is on the .vo2-arrow, not the container — see .dur-headline. */
.vo2-headline {
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 8px;
  padding: 14px 16px;
  margin-bottom: 16px;
}

.vo2-head-main { display: flex; align-items: center; gap: 18px; }
.vo2-big-wrap { display: flex; flex-direction: column; gap: 3px; }
.vo2-band-range { font-size: var(--fs-xs); color: var(--dim2); font-family: var(--font-data); }
.vo2-big {
  font-size: var(--fs-hero);
  font-weight: 700;
  color: var(--txt-hi);
  font-family: var(--font-data);
  line-height: 1;
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.vo2-unit { font-size: var(--fs-xs); font-weight: 500; color: var(--dim2); font-family: inherit; }
.vo2-head-side { display: flex; flex-direction: column; gap: 4px; }
.vo2-head-label { font-size: var(--fs-base); font-weight: 600; color: var(--txt2); }
.vo2-head-trend { font-size: var(--fs-sm); color: var(--dim); }
.vo2-arrow { font-size: var(--fs-sm); }
.vo2-dir-up   .vo2-arrow { color: var(--green); }
.vo2-dir-down .vo2-arrow { color: var(--red); }
.vo2-dir-flat .vo2-arrow { color: var(--amber-ink); }
.vo2-head-sub {
  display: flex;
  gap: 18px;
  margin-top: 10px;
  font-size: var(--fs-xs);
  color: var(--dim2);
}
.vo2-head-sub strong { color: var(--txt2); font-weight: 600; }
/* Personal-economy anchor badge — calibrated against Garmin's embedded values. */
.vo2-anchor { color: var(--green); cursor: help; }
/* Cycling / Running switch — separate metrics, so it leads the control row. */
.vo2-sport-switch { display: inline-flex; gap: 4px; margin-right: 14px; }
.vo2-head-empty { font-size: var(--fs-sm); color: var(--dim); line-height: 1.6; }

/* Age/sex-graded rating pill (right side of the headline). */
.vo2-rating {
  margin-left: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 16px;
  border-radius: 8px;
  font-size: var(--fs-md);
  font-weight: 700;
  text-align: center;
  /* One mechanism, five --tint values — the shape the Route Manager chips use.
     The base derives all three halves, so a step follows the theme instead of
     pinning a value to one ground. */
  --tint: var(--dim);
  color: color-mix(in srgb, var(--tint) 80%, var(--txt));
  background: color-mix(in srgb, var(--tint) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--tint) 30%, transparent);
}
.vo2-rating-sub { font-size: var(--fs-xs); font-weight: 500; color: var(--dim2); letter-spacing: 0.02em; }
/* Chart ramp: Below Average → Top 1% (orange · yellow · lime · green · teal).
   Half of it used to invert. Bands 0 and 3 were tokens and went dark on paper;
   1 and 2 were the dark-theme literals #eab308 and #84cc16 and stayed bright,
   at 1.54:1 and 1.58:1 — so the ramp only held together in one theme. --yellow
   and --lime are new in tokens.css because the semantic set carried neither.

   Band 0 moves off --amber-ink onto --orange. Amber is the brand signal and
   tokens.css asks it to stay the one high-chroma value on screen; a data scale
   spending it on its LOWEST step is how it becomes one more colour. It also
   opens the ramp's first gap: orange sits 16° from --yellow where amber sat 8°.

   The ink is the token mixed 20% toward --txt for the reason the chips give:
   the pill sits on its OWN 10% tint, and --teal lands at 4.29:1 on paper
   without the mix. With it every step clears 5.3:1 on both grounds. (TKF-226) */
.vo2-band-0 { --tint: var(--orange); }
.vo2-band-1 { --tint: var(--yellow); }
.vo2-band-2 { --tint: var(--lime); }
.vo2-band-3 { --tint: var(--green); }
.vo2-band-4 { --tint: var(--teal); }

.vo2-chart-box { height: 320px; position: relative; }
.vo2-empty {
  color: var(--dim2);
  font-size: var(--fs-xs);
  line-height: 1.6;
  padding: 24px 16px;
  text-align: center;
}
.vo2-method-note {
  font-size: var(--fs-xs);
  color: var(--dim2);
  line-height: 1.6;
  margin-top: 14px;
  padding: 12px 14px;
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 8px;
}
.vo2-method-note strong { color: var(--dim); }

/* ── eFTP tab ─────────────────────────────────────────────────────────────── */
.eftp-chart-box { height: 340px; position: relative; }

.eftp-head-title { font-size: var(--fs-base); font-weight: 600; color: var(--txt2); margin-bottom: 10px; }
.eftp-head-title span { color: var(--dim2); font-weight: 400; }
.eftp-models-label {
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--dim2);
  margin: 14px 0 6px;
}

/* Two side-by-side model cards — each states one model's FTP clearly. */
.eftp-model-cards { display: flex; flex-wrap: wrap; gap: 12px; }
.eftp-model-card {
  flex: 1 1 220px;
  padding: 12px 14px;
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 8px;
}
.eftp-model-card-empty { opacity: 0.7; }
.eftp-card-name {
  font-family: var(--font-text);
  font-size: var(--fs-sm);
  font-weight: 700;
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.eftp-card-name span { color: var(--dim2); font-weight: 400; font-size: var(--fs-xs); }
.eftp-card-ftp {
  font-family: var(--font-data);
  font-size: var(--fs-xxl);
  font-weight: 700;
  color: var(--txt-hi);
  line-height: 1.1;
  margin-top: 4px;
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.eftp-card-ftp .vo2-unit { font-size: var(--fs-sm); }
.eftp-card-na { color: var(--dim2); }
.eftp-card-delta { font-size: var(--fs-xs); font-weight: 500; color: var(--dim); margin-left: auto; }
.eftp-card-wkg { font-family: var(--font-data); font-size: var(--fs-sm); color: var(--dim); margin-top: 2px; }
.eftp-card-detail { font-family: var(--font-data); font-size: var(--fs-xs); color: var(--dim2); margin-top: 6px; }
.eftp-card-detail strong { color: var(--txt2); }
.eftp-wprime-approx  { color: var(--amber-ink); font-weight: 600; }   /* fits disagree — rough size only */
.eftp-wprime-missing { color: var(--bdr2); }                     /* diagnostic, deliberately muted */

.eftp-agree { font-size: var(--fs-xs); color: var(--dim2); margin-top: 10px; }

/* HRV corroboration line — an independent third signal (DFA a1) agreeing the
   binding effort was threshold work. Loaded async, so the row simply stays empty
   when there's no usable RR data; :empty keeps it from reserving space. */
.eftp-hrv { font-size: var(--fs-xs); color: var(--dim2); margin-top: 6px; line-height: 1.5; }
.eftp-hrv:empty { display: none; }
.eftp-hrv strong { color: var(--txt2); }
.eftp-hrv-ok { color: var(--purple); font-weight: 600; }   /* purple — distinct from power (amber) and HR (cyan) */
.eftp-hrv-note { color: var(--bdr2); }
.eftp-ftp-line { font-size: var(--fs-sm); color: var(--dim); margin-top: 8px; }
.eftp-ftp-line strong { color: var(--txt2); }

/* One-click "Set as FTP" buttons. */
.eftp-setftp-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.eftp-setftp {
  cursor: pointer;
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--bg0);
  background: var(--amber);
  border: 1px solid var(--amber);
  border-radius: 6px;
  padding: 6px 12px;
  transition: background 0.15s, opacity 0.15s;
}
.eftp-setftp:hover:not(:disabled) { background: var(--amber-ink); }
.eftp-setftp:disabled { opacity: 0.55; cursor: default; }
.eftp-setftp-alt {
  color: var(--amber-ink);
  background: transparent;
  border-color: var(--bg3);
}
.eftp-setftp-alt:hover:not(:disabled) { background: var(--bg1); }

/* PR badge on the ride-detail title — set from pr-events.php (app/lib/pr_query.php). */
.ride-pr-badge {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: var(--fs-xs);
  font-weight: 600;
  vertical-align: middle;
  background: rgba(var(--amber-wash), 0.14);
  color: var(--amber-ink);
  border: 1px solid rgba(var(--amber-wash), 0.35);
}

/* PR records under the activity-detail header (which record was set) + the
   history-list trophy. Sourced from pr-bests.php → ride_efforts. */
.analysis-detail-pr { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.pr-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.6;
}
.pr-chip-all    { background: rgba(var(--amber-wash), 0.14); color: var(--amber-ink); border: 1px solid rgba(var(--amber-wash), 0.35); }
.pr-chip-season { background: color-mix(in srgb, var(--blue) 12%, transparent); color: var(--blue); border: 1px solid color-mix(in srgb, var(--blue) 30%, transparent); }
/* Podium (2nd/3rd) — a strong effort short of a record; dimmer/neutral so the
   held-record chips still read as the headline. */
.pr-chip-podium { background: color-mix(in srgb, var(--dim) 12%, transparent); color: var(--dim); border: 1px solid color-mix(in srgb, var(--dim) 28%, transparent); }

/* eFTP chip — the switch for the eFTP effort window. Cyan once the effort is
   HR-confirmed, amber while it's a raw Short-CP projection: the same colour
   language the tile, the curve reference line and the shaded window all use. */
.pr-chip-eftp    { background: rgba(var(--amber-wash), 0.12); color: var(--amber-ink); border: 1px solid rgba(var(--amber-wash), 0.32); }
.pr-chip-eftp-hr { background: color-mix(in srgb, var(--sky) 12%, transparent); color: var(--sky); border: 1px solid color-mix(in srgb, var(--sky) 32%, transparent); }

/* "Clear" — appears only while something is highlighted, so it never adds noise
   to a clean row. Neutral, so it reads as a control rather than an achievement. */
.pr-chip-clear {
  background: color-mix(in srgb, var(--dim) 10%, transparent); color: var(--dim);
  border: 1px solid color-mix(in srgb, var(--dim) 30%, transparent);
  cursor: pointer; user-select: none;
}
.pr-chip-clear:hover { background: color-mix(in srgb, var(--dim) 18%, transparent); color: var(--txt2); }
.pr-chip-clear:focus-visible { outline: 2px solid var(--dim); outline-offset: 2px; }
.pr-chip-clear[hidden] { display: none; }

/* Selectable chips — click one to show just that window on the charts. Nothing is
   shaded until one is on. Only power records get this (they're the only ones with
   a time window); distance/elevation records stay as plain chips. */
.pr-chip-sel { cursor: pointer; user-select: none; transition: transform 0.08s ease, box-shadow 0.12s ease; }
.pr-chip-sel:hover { transform: translateY(-1px); }
.pr-chip-sel:focus-visible { outline: 2px solid var(--dim); outline-offset: 2px; }
/* Active = this record is the one being shown; the others are hidden from the charts. */
.pr-chip-active { box-shadow: 0 0 0 2px currentColor inset; filter: brightness(1.18); }

/* Arrived here by clicking this record in Personal Records. A one-shot pulse,
   not a state: the switchable chips already show they're on (pr-chip-active),
   and for a distance/elevation best — which has no window to shade — this is
   the only thing that says "this is the record you came for". */
.pr-chip-focus { animation: pr-chip-pulse 1.6s ease-out 1; }
@keyframes pr-chip-pulse {
  0%   { box-shadow: 0 0 0 0 currentColor; }
  60%  { box-shadow: 0 0 0 6px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .pr-chip-sel { transition: none; }
  .pr-chip-sel:hover { transform: none; }
  /* No pulse, but the chip still has to be findable — hold the ring instead. */
  .pr-chip-focus { animation: none; box-shadow: 0 0 0 2px currentColor; }
}
.ride-list-pr   { margin-left: 5px; font-size: var(--fs-sm); }
.ride-list-pr-season { opacity: 0.5; }   /* season best — dimmer than an all-time trophy */

/* ── Load context (loaded touring / e-bike) ── */
/* Inline badge in the ride list. */
.analysis-lc-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 4px;
  font-size: var(--fs-xs);
  font-weight: 600;
  vertical-align: middle;
  white-space: nowrap;
}
.analysis-lc-badge.lc-loaded { background: color-mix(in srgb, var(--green) 12%, transparent); color: var(--green); border: 1px solid color-mix(in srgb, var(--green) 30%, transparent); }
.analysis-lc-badge.lc-ebike  { background: color-mix(in srgb, var(--purple) 14%, transparent); color: var(--purple); border: 1px solid color-mix(in srgb, var(--purple) 32%, transparent); }

/* Smart Recording (non-1 Hz file). Slate, deliberately quieter than the
   load-context badges: this is a property of the file, not a choice the rider
   made about the ride, and the numbers are correct either way. */
.analysis-smart-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 4px;
  font-size: var(--fs-xs);
  font-weight: 600;
  vertical-align: middle;
  white-space: nowrap;
  background: color-mix(in srgb, var(--dim2) 14%, transparent);
  color: var(--dim2, var(--dim));
  border: 1px solid color-mix(in srgb, var(--dim2) 30%, transparent);
  cursor: help;
}

/* Detail-view recording notice — one quiet line under the metric tiles. */
.analysis-recording-notice {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 14px;
  padding: 8px 12px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--dim2) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--dim2) 20%, transparent);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--dim2, var(--dim));
}
.analysis-recording-notice .analysis-smart-badge { margin-left: 0; }

/* Detail-view three-way picker. */
.analysis-load-context {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-top: 8px;
}
.analysis-lc-label {
  font-size: var(--fs-micro); letter-spacing: 1px; text-transform: uppercase;
  color: var(--dim2, var(--dim2)); margin-right: 2px;
}
.analysis-lc-opt {
  padding: 3px 10px;
  border-radius: 5px;
  font: inherit; font-size: var(--fs-xs); font-weight: 600;
  background: transparent;
  color: var(--dim1, var(--dim));
  border: 1px solid var(--bdr, var(--bg3));
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.analysis-lc-opt:hover { color: var(--txt, var(--txt)); border-color: var(--dim2, var(--dim2)); }
.analysis-lc-opt.active {
  background: color-mix(in srgb, var(--blue) 14%, transparent);
  color: var(--blue);
  border-color: color-mix(in srgb, var(--blue) 40%, transparent);
}
.analysis-lc-note { font-size: var(--fs-xs); color: var(--dim2, var(--dim2)); font-style: italic; margin-left: 2px; }
.analysis-metric .metric-pr { font-size: var(--fs-xs); margin-left: 3px; }

/* Duration-axis zoom control under the power-duration curves (eFTP page + ride detail) */
.chart-range-ctl {
  display: flex; align-items: center; gap: 6px;
  margin: 2px 0 8px; padding: 0 2px;
  font: 11px/1 'JetBrains Mono', monospace; color: var(--dim2);
}
.chart-range-ctl:empty { display: none; }
.chart-range-lbl { text-transform: uppercase; letter-spacing: 0.05em; }
.chart-range-arrow { color: var(--bdr2); }
.chart-range-sel {
  /* appearance:none so the native (white) select chrome is replaced by the
     TourKit dark style + a CSS-gradient caret — mirrors .ov-range-btn. */
  appearance: none;
  -webkit-appearance: none;
  color: var(--txt2);
  border: 1px solid var(--bg3); border-radius: 4px;
  padding: 2px 20px 2px 8px; font: 11px 'JetBrains Mono', monospace; cursor: pointer;
  background-color: var(--bg0);
  background-image: linear-gradient(45deg, transparent 50%, var(--dim) 50%),
                    linear-gradient(135deg, var(--dim) 50%, transparent 50%);
  background-position: calc(100% - 11px) center, calc(100% - 7px) center;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}
.chart-range-sel:hover { border-color: var(--bdr2); }
.chart-range-reset {
  background: transparent; color: var(--dim2);
  border: 1px solid var(--bg3); border-radius: 4px;
  padding: 2px 7px; cursor: pointer; font: 11px 'JetBrains Mono', monospace;
}
.chart-range-reset:hover:not(:disabled) { color: var(--txt2); border-color: var(--bdr2); }
.chart-range-reset:disabled { opacity: 0.4; cursor: default; }

/* "Update your max HR" nudge — eFTP headline + ride detail (shared) */
.eftp-maxhr-nudge {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 10px 0 4px; padding: 9px 12px; border-radius: 8px;
  background: color-mix(in srgb, var(--red) 10%, transparent); border: 1px solid color-mix(in srgb, var(--red) 28%, transparent);
  color: var(--red); font-size: var(--fs-sm); line-height: 1.45;
}
.eftp-maxhr-nudge strong { color: var(--red); }
.eftp-maxhr-btns { display: inline-flex; gap: 6px; margin-left: auto; }
.eftp-maxhr-update, .eftp-maxhr-dismiss {
  font-family: var(--font-data); font-size: var(--fs-xs);
  border-radius: 4px; padding: 4px 10px; cursor: pointer; white-space: nowrap;
}
.eftp-maxhr-update { background: var(--red); border: 1px solid var(--red); color: #fff; font-weight: 600; }
.eftp-maxhr-update:hover { background: var(--red); border-color: var(--red); }
.eftp-maxhr-dismiss { background: transparent; border: 1px solid color-mix(in srgb, var(--red) 40%, transparent); color: var(--red); }
.eftp-maxhr-dismiss:hover { border-color: var(--red); color: var(--red); }
.eftp-maxhr-done {
  margin: 10px 0 4px; padding: 9px 12px; border-radius: 8px;
  background: color-mix(in srgb, var(--green) 10%, transparent); border: 1px solid color-mix(in srgb, var(--green) 28%, transparent);
  color: color-mix(in srgb, var(--green) 80%, var(--txt)); font-size: var(--fs-sm);
}

/* Circular "?" help icons on the eFTP page (model cards + aggregate window) */
.eftp-help {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; margin-left: 5px; border-radius: 50%;
  border: 1px solid var(--bdr2); color: var(--dim);
  font-size: var(--fs-xs); font-weight: 700; line-height: 1; cursor: help;
  vertical-align: middle; user-select: none;
}
.eftp-help:hover, .eftp-help:focus-visible { border-color: var(--dim); color: var(--txt2); outline: none; }

/* Tooltip bubble. Deliberately NOT the native `title` attribute: that takes
   ~1-2 s to appear, can't be styled, and never fires on touch (the iPad
   WKWebView app), which is why the "?" appeared to do nothing. Shown on hover
   AND :focus-visible so keyboard users get it too. pointer-events:none so the
   bubble can never swallow a click meant for what's underneath. */
.eftp-help::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  width: max-content;
  max-width: min(320px, 70vw);
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--bg1);
  border: 1px solid var(--bg3);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
  color: var(--txt2);
  font-family: system-ui, -apple-system, sans-serif;
  font-size: var(--fs-xs); font-weight: 400; line-height: 1.45;
  text-align: left; white-space: normal;
  opacity: 0; visibility: hidden;
  transition: opacity 0.12s ease;
  pointer-events: none;
  z-index: 60;
}
.eftp-help:hover::after, .eftp-help:focus-visible::after { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) { .eftp-help::after { transition: none; } }

/* Per-chart range control: label left, range buttons pinned top-right of the card.
   Used on VO2Max / eFTP / Durability so the range's scope is visually unambiguous. */
.analysis-chart-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 4px 14px; flex-wrap: wrap; margin-bottom: 8px;
}
.analysis-chart-head .analysis-chart-label { margin-bottom: 0; flex: 1 1 auto; min-width: 0; }
.analysis-chart-head .chart-range-tabs { flex: 0 0 auto; }

/* ── Repeated routes (TKF-019) ─────────────────────────────────────────────
   The banner, the overview card and the group view. Green throughout, not the
   amber used for records: this is a fitness TREND on your own roads, and it
   should not read as another trophy. */

/* Ride-detail banner. A button, not a div-with-onclick — it is a navigation
   target, so it needs to be tabbable and to announce itself as one. */
.rm-banner { margin-top: 6px; }
.rm-banner-btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 11px 5px 9px;
  background: color-mix(in srgb, var(--green) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--green) 32%, transparent);
  border-radius: 999px;
  color: var(--green);
  font: inherit; font-size: var(--fs-sm);
  cursor: pointer; text-align: left;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.rm-banner-btn:hover,
.rm-banner-btn:focus-visible { background: color-mix(in srgb, var(--green) 18%, transparent); border-color: color-mix(in srgb, var(--green) 55%, transparent); }
.rm-banner-icon { font-size: var(--fs-base); line-height: 1; }
.rm-banner-name { font-weight: 700; color: var(--green); }
.rm-banner-bits { color: var(--green); }

/* .rm-row / .rm-row-name / .rm-row-meta / .rm-row-last removed 2026-08-22 with
   the Overview "Repeated routes" card — the Routes tab renders a table, not
   these rows, and nothing else emitted them. .rm-list stays: #routeListAll
   still carries it. */
.rm-list { display: flex; flex-direction: column; gap: 4px; }

/* ── the Routes TABLE ──────────────────────────────────────────────────────
   Rides on .analysis-table, so the header, the sort arrows and the sticky
   thead are Activity History's — this is the sibling table, not a new one. */
.rm-list-table tbody tr { cursor: pointer; transition: background 0.1s; }
.rm-list-table tbody tr:hover { background: var(--bg1); }
.rm-list-table .rm-num { font-family: var(--font-data); font-variant-numeric: tabular-nums; text-align: right; }
.rm-list-table th.rm-num { text-align: right; }
/* The star column is chrome: fixed width, no label, never in the picker. The
   star is --fs-xl to match route-manager's, which is taller than a table row's
   --fs-micro text — the tighter padding here keeps the row from growing
   around it. */
.rm-fav-col { width: 34px; padding: 2px 0 2px 8px !important; text-align: center; }
.rm-name-cell { color: var(--txt); font-weight: 600; }
.rm-name { vertical-align: middle; }

/* The sport marker (TKF-284) — always on, never a column.
   A rider who runs AND rides the same loop has two clusters with the same
   name, distance and map, so the row has to say which is which without the
   column picker. Fixed width so the names still line up: the glyphs are not
   the same advance width, and a ragged left edge on the one column people
   actually read is worse than the space it costs. */
.rm-sport {
  display: inline-block; width: 1.35em; margin-right: 2px;
  font-size: var(--fs-sm); line-height: 1; vertical-align: middle;
  /* Colour emoji ignore `color`, so this only reaches the '•' fallback. */
  color: var(--dim2);
}

/* Deliberately NOT .rm-star: route-manager.css owns that class and both
   stylesheets are live in the same document. Sized to match it, though — the
   two Routes screens should not have two different stars. */
.rm-fav {
  background: none; border: none; padding: 0 2px; cursor: pointer;
  color: var(--bdr2); font-size: var(--fs-xl); line-height: 1; vertical-align: middle;
  transition: color 0.12s ease, transform 0.12s ease;
}
/* Gold, not ink — same call as .rm-star in route-manager.css. --amber-ink is
   the WRITING value and drops to #935f06 on paper; a filled star is a fill,
   so it takes --amber and stays #f59e0b on both grounds. */
.rm-fav:hover { color: var(--amber); transform: scale(1.18); }
.rm-fav.on { color: var(--amber); }

/* Sits immediately after the name, inside the same cell. Dim until the row is
   hovered, then legible — always present so it is discoverable, never loud
   enough to compete with the name it belongs to. */
.rm-rowedit {
  background: none; border: none; padding: 0 4px; margin-left: 4px; cursor: pointer;
  color: var(--bg3); font-size: var(--fs-sm); line-height: 1; vertical-align: middle;
  transition: color 0.12s ease;
}
.rm-list-table tbody tr:hover .rm-rowedit { color: var(--dim); }
.rm-rowedit:hover,
.rm-rowedit:focus-visible { color: var(--amber-ink); }

/* The Columns button row. The picker panel is anchored right:0 for Activity
   History, whose Columns button sits at the right end of a full-width toolbar.
   Here the button is at the LEFT edge, so a 300px panel hanging off its right
   edge would run off the side of the window. */
.rm-listbar { display: flex; align-items: center; gap: 8px; margin: 8px 0 0; }
.rm-listbar .analysis-colpicker { right: auto; left: 0; }

/* Group view */
.rm-dir-tabs { display: flex; gap: 6px; margin: 4px 0 12px; }
.rm-dir-tab {
  padding: 5px 12px; border-radius: 6px;
  background: var(--bg0); border: 1px solid var(--bg1);
  color: var(--dim); font: inherit; font-size: var(--fs-sm); cursor: pointer;
}
.rm-dir-tab.active { background: color-mix(in srgb, var(--green) 12%, transparent); border-color: color-mix(in srgb, var(--green) 45%, transparent); color: var(--green); }
.rm-dir-n { color: var(--dim2); font-size: var(--fs-xs); margin-left: 3px; }
.rm-dir-tab.active .rm-dir-n { color: var(--green); }

.rm-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 8px; margin-bottom: 14px;
}
.rm-stat { background: var(--bg0); border: 1px solid var(--bg1); border-radius: 6px; padding: 9px 11px; }
.rm-stat-k { color: var(--dim2); font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.6px; }
.rm-stat-v { color: var(--txt); font-size: var(--fs-md); font-weight: 700; font-family: var(--font-data); font-variant-numeric: tabular-nums; margin-top: 2px; }
.rm-up   { color: var(--green); }
.rm-down { color: var(--red); }

.rm-ef-box { height: 260px; }
.rm-empty  { color: var(--dim2); font-size: var(--fs-xs); margin-top: 8px; }

.rm-rides { margin-top: 14px; overflow-x: auto; }
.rm-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.rm-table th {
  text-align: left; padding: 7px 10px;
  color: var(--dim2); font-size: var(--fs-micro); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.6px;
  border-bottom: 1px solid var(--bg1); white-space: nowrap;
}
.rm-table td { padding: 8px 10px; border-bottom: 1px solid var(--bg0); color: var(--txt2); font-family: var(--font-data); font-variant-numeric: tabular-nums; }
.rm-tr { cursor: pointer; }
.rm-tr:hover td { background: var(--bg0); }
.rm-fastest { color: var(--green); }

/* "This route" — the ride-detail sub-tab (TKF-019). The whole panel answers one
   question: how did THIS ride go against every other time on the same roads. So
   the current ride is marked in three places (headline, chart bar, table row)
   rather than being left for the reader to find. */
.analysis-repeats { margin-top: 4px; }
.rm-intro { color: var(--dim); font-family: var(--font-text); font-size: var(--fs-sm); max-width: 68ch; margin: -4px 0 14px; }

.rm-compare {
  background: var(--bg0); border: 1px solid var(--bg1);
  border-radius: 8px;
  padding: 12px 14px; margin-bottom: 14px;
}
.rm-compare-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
/* Inside the group header's <h2> it takes the heading's size, not the table's. */
#rmTitle .rm-sport { width: auto; font-size: inherit; margin-right: 6px; }

/* In the flex head the fixed width is alignment nobody needs — it only has to
   sit next to the name, and the row's own gap already spaces it. */
.rm-compare-head .rm-sport { width: auto; margin-right: 0; }
.rm-compare-name { font-size: var(--fs-md); font-weight: 700; color: var(--green); }
.rm-compare-sub  { font-size: var(--fs-sm); color: var(--green); }
.rm-compare .rm-stats { margin-bottom: 0; }

.rm-times-box { height: 220px; }

/* The row for the ride being viewed. Tinted AND chipped: the tint alone is
   easy to miss when scanning, and colour on its own carries no meaning for a
   reader who can't distinguish it. */
.rm-tr-me td { background: rgba(var(--amber-wash), 0.10); }
.rm-tr-me:hover td { background: rgba(var(--amber-wash), 0.16); }
.rm-tr-me { cursor: default; }
.rm-me-chip {
  display: inline-block; margin-left: 6px; padding: 1px 6px;
  border-radius: 999px; background: rgba(var(--amber-wash), 0.18);
  color: var(--amber-ink); font-size: var(--fs-micro); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.4px;
}

/* ── Aerobic Efficiency (TKF-115) ──────────────────────────────────────────
   Heart rate at a given power. The headline is four tiles rather than one
   sentence, because this metric has two equally valid readings — bpm at a
   reference power and watts at a reference heart rate — and hiding either one
   loses half the audience. Tiles borrow the Durability card palette so the two
   analysis sub-views read as one family. */
.eff-headline {
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 8px;
  padding: 14px 16px;
  margin-bottom: 12px;
}
.eff-empty-msg { font-size: var(--fs-sm); line-height: 1.7; color: var(--txt2); }
.eff-empty-msg strong { color: var(--txt-hi); }

.eff-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
}
.eff-tile {
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 6px;
  padding: 10px 12px;
  min-width: 0;
}
/* Direction colours the trend figure (.eff-tile-trend below) and matches
   Durability exactly: green improving, red declining, slate for "fitted but
   not distinguishable from noise" — a neutral state, not a caution. */

.eff-tile-label {
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--dim2);
  margin-bottom: 4px;
}
.eff-tile-value {
  font-family: var(--font-data);
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--txt-hi);
  line-height: 1.1;
  display: flex;
  align-items: baseline;
  gap: 5px;
  flex-wrap: wrap;
}
.eff-tile-small { font-size: var(--fs-lg); }
.eff-tile-unit { font-size: var(--fs-xs); font-weight: 400; color: var(--dim2); }
.eff-tile-trend { font-size: var(--fs-md); gap: 7px; }
.eff-trend-txt { font-family: inherit; font-size: var(--fs-sm); color: var(--txt2); font-weight: 500; }
.eff-dir-improving .eff-tile-trend { color: var(--green); }
.eff-dir-declining .eff-tile-trend { color: var(--red); }
.eff-dir-holding   .eff-tile-trend { color: var(--dim); }
.eff-tile-sub { font-size: var(--fs-xs); color: var(--dim2); margin-top: 6px; line-height: 1.5; }

/* Ride accounting: what the headline read, and what it left out. A footnote to
   the claim above, not a card competing with it. */
.eff-diagnostics {
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--txt2);
  background: var(--bg0);
  border: 1px solid var(--bg1);
  border-radius: 6px;
  padding: 10px 12px;
  margin-bottom: 16px;
}
.eff-diagnostics:empty { display: none; }
.eff-diagnostics strong { color: var(--txt-hi); }
/* The seasonal-temperature warning. Amber, because it does not invalidate the
   reading — it tells you which part of it to distrust. */
.eff-warn {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--bg1);
  color: var(--amber-ink);
}
.eff-note-quiet { margin-top: 8px; color: var(--dim2); }

.eff-chart-box { height: 300px; position: relative; }

/* Route map on the repeated-route view. Sized like the activity track view's
   map rather than left to Leaflet, which measures 0×0 in a flex parent and
   renders a grey box. */
.rm-map-wrap { margin: 0 0 14px; }
.rm-map {
  height: 340px; min-height: 240px;
  border: 1px solid var(--bg1); border-radius: 8px;
  overflow: hidden; background: var(--bg0);
}
@media (max-width: 700px) { .rm-map { height: 260px; } }
