/* le-tour app styles. Every value comes from tokens.css — if you are
 * about to type a hex colour or a pixel radius here, add a token instead.
 *
 * Section 1 is the component vocabulary that components.py emits and
 * /design-system renders. Section 2 is what the 3D cockpit still needs;
 * it goes away when phase 2 rewrites that surface.
 */

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

html {
  background: var(--face);
  scrollbar-color: var(--rim-light) var(--face);
}

body {
  margin: 0;
  background: var(--face);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-synthesis: none;
  letter-spacing: var(--tracking-body);
  line-height: var(--leading-body);
  text-rendering: optimizeLegibility;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
select {
  font: inherit;
  letter-spacing: inherit;
}

::selection {
  background: var(--accent);
  color: var(--face);
}

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

/* ==========================================================================
   1. Component vocabulary
   ========================================================================== */

/* --- shell & header ------------------------------------------------------
 * Header and body share one background with no divider rule; alignment
 * alone separates them. The header's inner edges match .shell exactly, so
 * the wordmark sits on the same left boundary as every page title below.
 */

.site-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  width: 100%;
  max-width: calc(var(--content-max) + 2 * var(--page-padding));
  height: var(--header-height);
  margin-inline: auto;
  align-items: center;
  padding-inline: var(--page-padding);
}

.wordmark {
  display: inline-flex;
  justify-self: start;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-wordmark);
  font-weight: var(--weight-wordmark);
  letter-spacing: var(--tracking-wordmark);
  transition: color var(--dur-base) ease;
}

.brand-mark {
  display: block;
  width: calc(var(--space-6) + var(--space-1));
  height: auto;
  flex: none;
  overflow: visible;
}

.brand-mark__primary {
  color: var(--brand-primary);
}

.brand-mark__companion {
  color: var(--brand-companion);
}

.brand-disc {
  display: inline-block;
  width: var(--space-4);
  aspect-ratio: 1;
  border-radius: var(--radius-pill);
  flex: none;
}

.brand-disc--primary {
  background: var(--brand-primary);
}

.brand-disc--companion {
  background: var(--brand-companion);
}

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

.nav-links {
  display: flex;
  justify-self: center;
  gap: var(--space-5);
}

.nav-links a {
  color: var(--muted);
  font-weight: var(--weight-medium);
  transition: color var(--dur-base) ease;
}

.nav-links a:hover {
  color: var(--text);
}

.nav-links a.active {
  color: var(--accent);
}

.account-menu {
  position: relative;
  z-index: 2;
  justify-self: end;
}

.account-menu-trigger {
  position: relative;
  display: grid;
  width: calc(var(--space-6) + var(--space-2));
  aspect-ratio: 1;
  place-items: end center;
  overflow: hidden;
  border: 1px solid var(--rim-light);
  border-radius: var(--radius-pill);
  background: var(--rider-kit);
  cursor: pointer;
  padding: 0;
  transition:
    opacity var(--dur-base) ease,
    transform var(--dur-fast) var(--ease-out);
}

.account-menu-trigger__head {
  width: var(--space-4);
  height: var(--space-5);
  border-radius: var(--radius-pill) var(--radius-pill) 0 0;
  background: var(--rider-skin);
}

.account-menu-trigger:hover {
  opacity: 0.86;
}

.account-menu-trigger:active {
  transform: scale(0.94);
}

.account-menu-popover {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  min-width: calc(var(--space-8) + var(--space-8) + var(--space-6));
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  background: var(--overlay);
  padding: var(--space-2);
  transform-origin: top right;
  animation: account-menu-in var(--dur-base) var(--ease-out);
}

.account-menu-identity {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-3);
  border-bottom: 1px solid var(--hairline);
  margin-bottom: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-3);
}

.account-menu-avatar {
  width: calc(var(--space-6) + var(--space-1));
  aspect-ratio: 1;
  border-radius: var(--radius-pill);
  background: linear-gradient(
    to bottom,
    var(--rider-skin) 0 42%,
    var(--rider-kit) 42% 100%
  );
}

.account-menu-identity strong,
.account-menu-identity small {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-menu-identity strong {
  color: var(--text);
  font-size: var(--text-small);
}

.account-menu-identity small {
  color: var(--muted);
  font-size: var(--text-label);
}

.account-menu-action {
  display: flex;
  width: 100%;
  min-height: calc(var(--space-6) + var(--space-2));
  align-items: center;
  border: 0;
  border-radius: var(--radius-tight);
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  padding: 0 var(--space-3);
  text-align: left;
  transition:
    background-color var(--dur-base) ease,
    color var(--dur-base) ease;
}

.account-menu-action:hover:not(:disabled),
.account-menu-action:focus-visible {
  background: var(--panel-raised);
  color: var(--text-bright);
}

.account-menu-action:disabled {
  cursor: default;
  opacity: 0.55;
}

.rider-tone--rouleur {
  --rider-kit: var(--asset-rouleur);
  --rider-skin: var(--asset-rouleur-skin);
}

.rider-tone--coastline {
  --rider-kit: var(--asset-coastline);
  --rider-skin: var(--asset-coastline-skin);
}

.rider-tone--night_shift {
  --rider-kit: var(--asset-night-shift);
  --rider-skin: var(--asset-night-shift-skin);
}

@keyframes account-menu-in {
  from {
    opacity: 0;
    transform: translateY(calc(-1 * var(--space-1))) scale(0.98);
  }

  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@media (max-width: 40rem) {
  .wordmark__text {
    display: none;
  }

  .nav-links {
    gap: var(--space-2);
  }
}

.shell {
  width: 100%;
  max-width: calc(var(--content-max) + 2 * var(--page-padding));
  margin-inline: auto;
  padding: var(--space-5) var(--page-padding) var(--space-8);
}

/* The default product arrangement. A module that needs less space takes
   one column and stays left-aligned — it never centres under the title.
   Cards in the same row stretch to a shared height: a short card beside a
   tall one leaves a ragged edge that reads as a layout accident. */
.product-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--grid-gap);
  align-items: stretch;
}

/* Switchable panes stack in one grid cell, so the stack is always as tall
   as its tallest option. Without this, choosing a mode resizes the card and
   shoves everything below it out of alignment. */
.pane-stack {
  display: grid;
}

.pane-stack > [data-pane] {
  grid-area: 1 / 1;
  visibility: hidden;
}

.pane-stack > [data-pane].is-active {
  visibility: visible;
}

.page-title {
  margin-bottom: var(--space-5);
}

.page-title h1 {
  margin: 0;
  font-size: var(--text-title);
  font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-title);
  line-height: var(--leading-tight);
}

.page-subtitle {
  margin: var(--space-2) 0 0;
  max-width: 46ch;
  color: var(--muted);
}

.eyebrow {
  margin: 0 0 var(--space-2);
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.note {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
}

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

/* --- panels --------------------------------------------------------------
 * Deliberate surfaces, not boxes floating in space. Depth comes from
 * background and border; there are no drop shadows anywhere in this file.
 */

.panel {
  display: flex;
  min-width: 0;
  flex-direction: column;
  border: 1px solid var(--rim);
  border-radius: var(--radius-card);
  background: var(--panel);
  padding: var(--space-5);
}

.panel-wide {
  grid-column: 1 / -1;
}

.panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.panel-title {
  margin: 0;
  font-size: var(--text-panel);
  font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-heading);
  line-height: var(--leading-snug);
}

.panel-detail {
  margin: var(--space-1) 0 0;
  color: var(--muted);
  font-size: var(--text-small);
}

.panel-action {
  flex: 0 0 auto;
  color: var(--accent);
  font-size: var(--text-small);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

.panel-action:hover {
  color: var(--accent-bright);
}

.panel-tools {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-left: auto;
}

/* A ghost tool is text-only in a header. Remove its invisible side padding
   so its label aligns with the panel edge on desktop and after mobile wrap. */
.panel-tools > .btn-ghost {
  padding-inline: 0;
}

.panel-note {
  margin: 0 0 var(--space-4);
}

.panel-note:empty {
  display: none;
}

/* Action rows sit at the foot of a card. `mt: auto` pins them to the
   bottom so two cards in one grid row line their buttons up. */
.action-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: auto;
  padding-top: var(--space-5);
}

/* Commit actions close a card's reading order at the bottom-left. This
   keeps save controls aligned without making them look like "Next". */
.action-row-commit {
  justify-content: flex-start;
}

/* --- buttons -------------------------------------------------------------
 * Primary is off-white, not accent: a page of orange buttons would spend
 * the accent on navigation instead of on state.
 */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 2.6rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  padding: 0 var(--space-4);
  cursor: pointer;
  font-size: var(--text-small);
  font-weight: var(--weight-medium);
  transition:
    transform var(--dur-fast) var(--ease-out),
    background-color var(--dur-base) ease,
    border-color var(--dur-base) ease,
    color var(--dur-base) ease;
}

.btn:active {
  transform: scale(0.97);
}

.btn:disabled {
  cursor: default;
  opacity: 0.55;
  transform: none;
}

.btn-primary {
  background: var(--text);
  color: var(--face);
}

.btn-primary:hover:not(:disabled) {
  background: var(--text-bright);
}

.btn-quiet {
  border-color: var(--hairline);
  background: var(--panel-raised);
  color: var(--text-secondary);
}

.btn-quiet:hover:not(:disabled) {
  border-color: var(--rim-light);
  color: var(--text);
}

.btn-ghost {
  background: transparent;
  color: var(--muted);
}

.btn-ghost:hover:not(:disabled) {
  color: var(--text);
}

.btn-danger {
  border-color: color-mix(in srgb, var(--danger) 42%, transparent);
  background: transparent;
  color: var(--danger);
}

.btn-danger:hover:not(:disabled) {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 9%, transparent);
}

/* --- badges --------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  border: 1px solid var(--rim);
  border-radius: var(--radius-pill);
  padding: 0.25rem 0.6rem;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.badge-accent {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent);
}

.badge-ok {
  border-color: color-mix(in srgb, var(--progress) 40%, transparent);
  color: var(--progress);
}

.badge-danger {
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  color: var(--danger);
}

/* --- stat tiles ----------------------------------------------------------- */

.tile-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
  gap: var(--space-4) var(--space-5);
}

.tile {
  min-width: 0;
}

.tile-value {
  margin: 0;
  font-size: var(--text-heading);
  font-weight: var(--weight-strong);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-heading);
  line-height: var(--leading-tight);
  white-space: nowrap;
}

.tile-unit {
  margin-left: 0.15em;
  color: var(--muted);
  font-size: var(--text-small);
  font-weight: var(--weight-regular);
}

.tile-label {
  margin: var(--space-2) 0 0;
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

/* The mood dial a nature-world card carries (worlds.js rewrites the card's
   ride links from it). Sits between the usage line and the route rows. */
.world-moods {
  margin: var(--space-1) 0 var(--space-2);
  position: relative;
  z-index: 1; /* above the card's header overlay link, like .route-rows */
}

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

.segmented {
  display: inline-flex;
  width: 100%;
  gap: var(--space-1);
  border: 1px solid var(--rim);
  border-radius: var(--radius-pill);
  background: var(--panel-raised);
  padding: var(--space-1);
}

/* A contextual range or filter belongs in the panel header. Its compact
   variant keeps the three choices legible without becoming a second bar
   between the heading and the data it controls. */
.segmented-compact {
  width: auto;
  gap: var(--space-1);
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  padding: 0;
}

.segmented-compact .segment {
  flex: 0 0 auto;
  min-height: 2.15rem;
  border-radius: var(--radius-control);
  padding-inline: var(--space-3);
}

.segmented-compact .segment.active {
  background: var(--panel-raised);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--rim);
}

.segment {
  flex: 1 1 0;
  min-height: 2.3rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: var(--text-small);
  font-weight: var(--weight-medium);
  transition:
    background-color var(--dur-base) ease,
    color var(--dur-base) ease;
}

.segment:hover {
  color: var(--text);
}

.segment.active {
  background: var(--text);
  color: var(--face);
}

/* --- empty state ---------------------------------------------------------- */

.empty {
  display: grid;
  justify-items: start;
  gap: var(--space-2);
  padding: var(--space-4) 0 var(--space-2);
}

.empty-title {
  margin: 0;
  font-weight: var(--weight-strong);
}

.empty-detail {
  margin: 0 0 var(--space-3);
  max-width: 42ch;
  color: var(--muted);
  font-size: var(--text-small);
}

/* --- form fields ---------------------------------------------------------- */

.field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--space-4);
}

.field {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: var(--space-2);
}

.field-label {
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.field input,
.field select,
.field textarea,
#route-select {
  min-height: 2.6rem;
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  background: var(--face);
  color: var(--text);
  outline: none;
  padding: 0 var(--space-3);
  transition:
    border-color var(--dur-base) ease,
    box-shadow var(--dur-base) ease;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--text-secondary);
  box-shadow: 0 0 0 3px var(--accent-glow);
}

.field textarea {
  min-height: calc(var(--space-8) * 2);
  padding-block: var(--space-3);
  resize: vertical;
}

.field-grid .action-row {
  grid-column: 1 / -1;
}

/* A hint under an input, for a constraint the label cannot carry: password
 * length, an accepted format. Not a place to restate the label. */
.field-hint {
  font-size: var(--text-small);
  line-height: var(--leading-snug);
}

/* A consent or preference checkbox. The control keeps its own size while the
 * text wraps beside it, so a two-line label does not stretch the box. */
.checkbox {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
  line-height: var(--leading-snug);
  cursor: pointer;
}

.checkbox input[type="checkbox"] {
  appearance: none;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0;
  border: 1px solid var(--rim);
  border-radius: var(--radius-tight);
  background: var(--face);
  display: grid;
  place-content: center;
  transition:
    border-color var(--dur-base) ease,
    box-shadow var(--dur-base) ease;
}

.checkbox input[type="checkbox"]::before {
  content: "";
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 0.1rem;
  transform: scale(0);
  background: var(--accent);
  transition: transform var(--dur-fast) var(--ease-out);
}

.checkbox input[type="checkbox"]:checked {
  border-color: var(--accent);
}

.checkbox input[type="checkbox"]:checked::before {
  transform: scale(1);
}

.checkbox input[type="checkbox"]:focus-visible {
  border-color: var(--text-secondary);
  box-shadow: 0 0 0 3px var(--accent-glow);
  outline: none;
}

/* A button that reads as a link, for an action that is not a destination --
 * "Forgot your password?" posts a request, so it must not be an anchor. */
.link-button {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: var(--text-small);
  color: var(--muted);
  text-align: left;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  transition: color var(--dur-base) ease;
}

.link-button:hover,
.link-button:focus-visible {
  color: var(--accent);
}

.link-button:disabled {
  color: var(--future);
  cursor: default;
  text-decoration: none;
}

.settings-form {
  display: flex;
  flex: 1;
  flex-direction: column;
}

/* --- tables --------------------------------------------------------------- */

.table {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.table-head,
.table-row {
  display: grid;
  /* The first column identifies the row (a timestamp) and needs more room
     than the figures beside it. */
  grid-template-columns:
    minmax(0, 1.5fr)
    repeat(calc(var(--table-columns, 4) - 1), minmax(0, 1fr));
  gap: var(--space-3);
  align-items: center;
}

.table-head {
  padding: 0 var(--space-4) var(--space-2);
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.table-row {
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: var(--panel-raised);
  padding: var(--space-3) var(--space-4);
  font-variant-numeric: tabular-nums;
  transition:
    border-color var(--dur-base) ease,
    background-color var(--dur-base) ease;
}

a.table-row:hover {
  border-color: var(--rim);
}

.table-row span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* First column carries the identity of the row, so it stays full-strength
   while the figures beside it recede. */
.table-row span:first-child {
  color: var(--text-secondary);
}

.table-row-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: var(--panel-raised);
  transition: border-color var(--dur-base) ease;
}

.table-row-wrap .table-row {
  min-width: 0;
  border: 0;
  background: transparent;
}

.table-row-wrap:hover {
  border-color: var(--rim);
}

.table-row-wrap > .btn-danger {
  min-height: 2.3rem;
  margin-right: var(--space-2);
  padding-inline: var(--space-3);
}

/* --- elevation profiles -------------------------------------------------- */

.profile {
  display: block;
  width: 100%;
  height: 4.5rem;
  margin: var(--space-4) 0;
}

.profile-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.6;
  vector-effect: non-scaling-stroke;
}

.profile-fill {
  fill: var(--accent-glow);
  stroke: none;
}

.ride-elevation-profile {
  margin: var(--space-5) 0 0;
}

.ride-elevation-profile figcaption {
  margin-bottom: var(--space-2);
  color: var(--text-secondary);
  font-size: var(--text-small);
  font-weight: var(--weight-medium);
}

.ride-elevation-plot {
  overflow: hidden;
  border-radius: var(--radius-control);
  background: var(--panel-raised);
}

.ride-elevation-plot .profile {
  height: 7rem;
  margin: 0;
}

.ride-elevation-axis {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  margin-top: var(--space-2);
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-meta);
}

.ride-elevation-axis span:nth-child(2) {
  text-align: center;
}

.ride-elevation-axis span:last-child {
  text-align: right;
}

/* --- activity calendar ----------------------------------------------------
 * One column per week, Monday at the top. Level 0 is a rest day and stays
 * untinted, so the grid reads as "how often" before "how much".
 *
 * Tinted with the accent, not with --progress: this is a page-level data
 * display, i.e. chrome, and it shows the same history the distance chart
 * does. The progress/current pair stays inside the instruments.
 */

.activity {
  display: grid;
  gap: var(--space-2);
  overflow-x: auto;
}

.activity-grid,
.activity-months {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0.6rem, 1.25rem);
  justify-content: start;
  gap: 3px;
}

.activity-months {
  height: 1rem;
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
  text-transform: uppercase;
}

.activity-months span {
  grid-column: var(--column);
}

.activity-week {
  display: grid;
  grid-template-rows: repeat(7, 1fr);
  gap: 3px;
}

.day {
  display: block;
  aspect-ratio: 1;
  border-radius: 2px;
  background: var(--panel-raised);
}

.day.level-1 {
  background: color-mix(in srgb, var(--accent) 20%, var(--panel-raised));
}

.day.level-2 {
  background: color-mix(in srgb, var(--accent) 42%, var(--panel-raised));
}

.day.level-3 {
  background: color-mix(in srgb, var(--accent) 66%, var(--panel-raised));
}

.day.level-4 {
  background: var(--accent);
}

.activity-legend {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-1);
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
}

.activity-legend .day {
  width: 0.7rem;
}

.activity-legend span {
  margin-inline: var(--space-2);
}

/* --- bar chart ------------------------------------------------------------ */

/* The peak label gets its own band above the bars. The tallest bar reaches
   the top of the plot by definition, so a label overlaid on it would always
   sit on the one bar it is describing. */
.chart {
  position: relative;
  padding-top: var(--space-4);
}

.chart-svg {
  display: block;
  width: 100%;
  height: 7.5rem;
  fill: color-mix(in srgb, var(--accent) 62%, transparent);
}

.chart-peak {
  position: absolute;
  top: 0;
  right: 0;
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  /* One line of label, not one line of body copy — body leading here makes
     the mark taller than the band reserved for it. */
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-meta);
}

/* --- ride setup ----------------------------------------------------------- */

.setup-body {
  display: grid;
  gap: var(--space-5);
  padding-top: var(--space-4);
}

.setup-axes {
  display: grid;
  gap: var(--space-4);
}

.setup-axes > div,
.setup-control {
  display: grid;
  gap: var(--space-2);
}

.field-label-row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.field-label-row .field-label {
  margin: 0;
}

.info-help {
  position: relative;
  display: inline-flex;
}

.info-help summary {
  position: relative;
  display: grid;
  width: var(--space-4);
  height: var(--space-4);
  cursor: pointer;
  list-style: none;
  place-items: center;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
}

.info-help summary::after {
  position: absolute;
  inset: calc(var(--space-1) * -1);
  content: "";
}

.info-help summary::-webkit-details-marker {
  display: none;
}

.info-help summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: var(--space-1);
}

.info-help > p {
  position: absolute;
  z-index: 10;
  top: calc(100% + var(--space-2));
  left: 0;
  width: min(22rem, 70vw);
  margin: 0;
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  background: var(--face-raised);
  color: var(--text-secondary);
  padding: var(--space-3);
  font-size: var(--text-small);
  line-height: var(--leading-body);
}

.setup-route,
.setup-workout,
.workout-launch-options {
  display: grid;
  gap: var(--space-3);
}

.workout-launch-options {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.workout-library-card .tile-row {
  margin-top: var(--space-4);
}

.ride-workout-summary {
  margin-top: var(--space-4);
  border-top: 1px solid var(--hairline);
  padding-top: var(--space-4);
}

.ride-workout-summary p {
  margin: 0;
}

.workout-progress {
  display: grid;
  gap: var(--space-1);
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  background: var(--scrim);
  padding: var(--space-3);
  font-variant-numeric: tabular-nums;
}

.workout-progress strong,
.workout-progress b {
  color: var(--text-bright);
}

.workout-progress progress {
  width: 100%;
  accent-color: var(--accent);
}

@media (max-width: 42rem) {
  .workout-launch-options {
    grid-template-columns: 1fr;
  }
}

/* Panes are top-aligned inside the stack so the shorter ones do not float
   in the middle of the reserved height. */
.setup-body > [data-pane] {
  align-self: start;
}

.setup-hint {
  margin: var(--space-3) 0 0;
  max-width: 46ch;
  color: var(--muted);
  font-size: var(--text-small);
}

.setup-erg,
.setup-sim {
  display: grid;
  gap: var(--space-3);
}

/* --- the route picker ------------------------------------------------------
   Every route the rider can start, grouped by its world's setting. A
   fieldset carries the choice; the browser owns which one is picked, so
   nothing here draws a selected state that could disagree with what the
   ride actually starts on. */

.route-picker {
  display: grid;
  min-width: 0;
  gap: var(--space-4);
  border: 0;
  margin: 0;
  padding: 0;
}

/* A legend is not a grid item, so it does not take the fieldset's gap.
   Without the margin the field's own label sits hard against the first
   setting heading and the two mono labels read as one stack. */
.route-picker > legend {
  margin-bottom: var(--space-3);
  padding: 0;
}

.route-theme {
  display: grid;
  min-width: 0;
  gap: var(--space-2);
}

/* One word spaced out into a label, per the two mono trackings: the
   setting names a group, it does not read as a phrase. Muted, because the
   heading is wayfinding and the cards under it are the content. */
.route-theme-name {
  margin: 0;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.route-grid {
  display: grid;
  /* `min()` so a narrow phone gets one card that fits its column rather
     than a 13rem card pushing out of the panel's padding. */
  grid-template-columns: repeat(auto-fill, minmax(min(13rem, 100%), 1fr));
  align-items: stretch;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.route-card {
  display: flex;
  min-width: 0;
}

.route-pick {
  display: grid;
  width: 100%;
  min-width: 0;
  align-content: start;
  gap: var(--space-2);
  border: 1px solid var(--rim);
  border-radius: var(--radius-card);
  background: var(--panel);
  padding: var(--space-3);
  cursor: pointer;
  transition:
    border-color var(--dur-base) var(--ease-out),
    background var(--dur-base) var(--ease-out);
}

.route-pick:hover {
  border-color: var(--rim-light);
  background: var(--panel-raised);
}

/* The input is visually hidden, so its focus ring has to be drawn on the
   card the rider is actually looking at. */
.route-radio:focus-visible + .route-pick {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Colour marks state — the one job the accent has outside navigation. */
.route-radio:checked + .route-pick {
  border-color: var(--accent);
  background: var(--panel-raised);
}

.route-radio:checked + .route-pick .route-name {
  color: var(--accent);
}

/* The picture reserves its box before any script runs, so upgrading from
   the server-rendered profile to the compiled plan view cannot move
   anything below it. */
.route-art {
  display: block;
  overflow: hidden;
  border-radius: var(--radius-control);
  aspect-ratio: 16 / 10;
}

.route-art > svg {
  display: block;
  width: 100%;
  height: 100%;
}

.route-art .profile {
  height: 100%;
  margin: 0;
}

.route-name {
  color: var(--text);
  font-size: var(--text-body);
  font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-heading);
  line-height: var(--leading-snug);
}

.route-world {
  color: var(--muted);
  font-size: var(--text-small);
}

.route-card .tile-row {
  gap: var(--space-2) var(--space-3);
}

.route-card .tile-value {
  font-size: var(--text-body);
}

/* -- routes inside a world card (the shared picker) ----------------------- */

/* The card's overlay link would swallow the row actions; the rows form
   their own stacking context above it. */
.route-rows {
  position: relative;
  z-index: 1;
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.route-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--hairline);
}

.route-row .route-art {
  flex: none;
  width: var(--space-8);
  min-width: 0;
}

.route-row-main {
  min-width: 0;
  flex: 1;
}

.route-row-name {
  margin: 0;
  color: var(--text);
  font-size: var(--text-body);
  font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-heading);
  line-height: var(--leading-snug);
}

.route-row-facts {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-small);
  letter-spacing: var(--tracking-body);
}

.route-detail summary {
  cursor: pointer;
  color: var(--muted);
  font-size: var(--text-small);
}

.route-detail summary:hover {
  color: var(--text-secondary);
}

.route-detail-body {
  margin: var(--space-2) 0 0;
  color: var(--text-secondary);
  font-size: var(--text-small);
  line-height: var(--leading-body);
}

.route-row-action {
  margin-left: auto;
  flex: none;
}

/* The world's Setting — the art-direction token, worn as a quiet label. */
.world-setting {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

/* A stored render fills the same box the plan view draws in. */
.world-photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The stepper is one balanced control group: decrement, big value,
   increment. The bar spans the card, but the trio inside it stays a group
   and is centred — pinning the buttons to the far edges turns one control
   into three things that happen to share a box. */
.stepper {
  display: grid;
  grid-template-columns: repeat(3, auto);
  justify-content: center;
  align-items: center;
  gap: var(--space-5);
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  background: var(--panel-raised);
  padding: var(--space-3) var(--space-4);
}

/* Wide enough for the largest target the stepper reaches, so gaining a
   digit moves the figure's own edges and not the buttons. */
.stepper-value {
  margin: 0;
  min-width: 5ch;
  font-size: var(--text-display);
  font-weight: var(--weight-strong);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-display);
  line-height: 1;
  text-align: center;
}

.stepper-unit {
  margin-left: 0.1em;
  color: var(--muted);
  font-size: var(--text-panel);
  font-weight: var(--weight-regular);
}

/* --- devices -------------------------------------------------------------- */

.device-status {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--space-4);
}

.device-line {
  display: grid;
  gap: var(--space-2);
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  background: var(--panel-raised);
  padding: var(--space-3) var(--space-4);
}

.device-name {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.device-line.connected .device-name {
  color: var(--progress);
}

.device-result {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  align-items: center;
  gap: var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: var(--panel-raised);
  padding: var(--space-2) var(--space-4);
  margin-top: var(--space-2);
}

.device-result-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- page sections --------------------------------------------------------
 * A heading over a run of the page. Deliberately not a panel: a border
 * here would put a box around a row of boxes, and then neither frame
 * means anything.
 */

.page-section {
  min-width: 0;
  grid-column: 1 / -1;
}

.page-section + .page-section {
  margin-top: var(--space-5);
}

/* --- world collection -----------------------------------------------------
 * A card is a world you own, not a row in a list: it leads with its
 * picture, and every card in a row ends level so the grid reads as a
 * shelf rather than a ragged stack. The picture is the card's own face —
 * it carries no frame of its own.
 */

.card-grid {
  display: grid;
  /* `min()` so a narrow phone gets one card that fits its column rather
     than a 19rem card pushing out of the panel's padding. */
  grid-template-columns: repeat(auto-fill, minmax(min(19rem, 100%), 1fr));
  align-items: stretch;
  gap: var(--grid-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.world-card {
  position: relative;
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: var(--space-3);
  border: 1px solid var(--rim);
  border-radius: var(--radius-card);
  background: var(--panel);
  padding: var(--space-4);
  transition:
    border-color var(--dur-base) var(--ease-out),
    background var(--dur-base) var(--ease-out);
}

.world-card:hover {
  border-color: var(--rim-light);
  background: var(--panel-raised);
}

/* The picture reserves its box before any script runs, so upgrading from
   the server-rendered fallback to the compiled plan view cannot move
   anything below the grid. */
.world-art {
  overflow: hidden;
  aspect-ratio: 16 / 10;
}

.world-art > svg {
  display: block;
  width: 100%;
  height: 100%;
}

.world-art .profile {
  height: 100%;
  margin: 0;
}

.world-plan {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.world-meta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
}

.world-name {
  margin: 0;
  font-size: var(--text-panel);
  font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-heading);
  line-height: var(--leading-snug);
}

.world-link {
  color: var(--text);
  text-decoration: none;
}

/* The whole card is the click target; only the title carries the name. */
.world-link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-card);
}

.world-link:focus-visible::after {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.world-card .tile-row {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3) var(--space-4);
}

.world-card .tile-value {
  font-size: var(--text-panel);
}

.world-usage {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-small);
}

/* Pinned to the foot so buttons line up across a row, and stacked above
   the title's overlay so it stays a second, separate target. */
.world-actions {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: flex-end;
  margin-top: auto;
  padding-top: var(--space-2);
}

/* --- one world ------------------------------------------------------------ */

.world-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-5);
}

.world-hero .world-art {
  aspect-ratio: 16 / 10;
}

.world-hero .tile-row {
  margin-bottom: var(--space-5);
}

.world-facts {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

.world-facts .action-row {
  margin-top: auto;
}

/* --- instruments ----------------------------------------------------------
 * Sizes are reserved here so a custom element filling itself on upgrade
 * never reflows what surrounds it.
 */

le-dial,
le-pill {
  display: block;
  width: 100%;
}

le-dial svg,
le-pill svg {
  display: block;
  width: 100%;
  height: auto;
  font-family: var(--font-sans);
  font-weight: var(--weight-medium);
}

/* The small family members: bezel and rim only. The inner gradient the
   dial and pill carry collapses into a grey smudge at this size, so the
   recipe reduces rather than scales down. */
le-readout,
le-plate,
le-gear,
le-minimap {
  display: inline-grid;
  border-radius: var(--radius-control);
  background: linear-gradient(
    145deg,
    var(--dial-bezel-hi),
    var(--face-raised) 42%,
    var(--dial-bezel-lo)
  );
  padding: 2px;
}

.instrument-face {
  border: 1px solid var(--rim);
  border-radius: inherit;
  background: var(--dial-core);
}

le-readout .instrument-face {
  display: grid;
  justify-items: center;
  gap: 0.1rem;
  min-width: 4.75rem;
  padding: var(--space-2) var(--space-4);
}

.instrument-value {
  font-size: var(--text-heading);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-heading);
  line-height: 1.15;
}

.instrument-label,
.instrument-plate-detail {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-style: normal;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

/* The gear: number and cassette strip in one box, sized to sit beside the
   grade readout in the rail without out-shouting it. */
le-gear .instrument-face {
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  min-width: 4.75rem;
  padding: var(--space-2) var(--space-4);
}

.instrument-gear-row {
  display: flex;
  align-items: baseline;
  gap: 0.15rem;
}

.instrument-gear-count {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-style: normal;
}

.instrument-gear-ticks {
  display: block;
  width: 100%;
  line-height: 0;
}

/* Past the trainer's resistance ceiling the number moves and nothing else
   does. The rider should be able to see that without being alarmed by it. */
le-gear[data-limited] .instrument-value {
  color: var(--accent);
}

le-plate .instrument-face {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-small);
}

/* The minimap takes the reduced bezel too, but its face is a picture
   rather than type, so it gets padding instead of a text box: the plan
   must not run under the rim. Square, because a route's bounding box is
   any shape at all and `xMidYMid meet` should have room in both axes. */
le-minimap {
  width: 8.5rem;
  aspect-ratio: 1 / 1;
}

le-minimap .instrument-face {
  display: grid;
  padding: var(--space-3);
}

le-minimap svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Ahead, ridden, now — the elevation strip's three tiers, in plan. Strokes
   are non-scaling so a 900 m loop and a 17 km world read the same weight
   despite viewBoxes three orders of magnitude apart. */
.minimap-road,
.minimap-ridden,
.minimap-heading {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.minimap-road {
  stroke: var(--future);
  stroke-width: 1.5;
}

.minimap-ridden {
  stroke: var(--progress);
  stroke-width: 2;
}

.minimap-heading {
  stroke: var(--current-start);
  stroke-width: 1.5;
}

.minimap-rider {
  fill: var(--current-start);
}

le-dial {
  max-width: 26rem;
  aspect-ratio: 1 / 1;
}

le-pill {
  max-width: 27.5rem;
  aspect-ratio: 4 / 1;
}

/* ==========================================================================
   2. Cockpit chrome — still the pre-brand markup. Phase 2 replaces these
   with the instrument pair; nothing new should use them.
   ========================================================================== */

.muted {
  color: var(--muted);
}

.mode-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
}

.btn-accent {
  border-color: var(--text);
  background: var(--text);
  color: var(--face);
}

.btn-accent:hover {
  background: var(--text-bright);
}

.facts {
  display: flex;
  gap: var(--space-5);
  margin: 0 0 var(--space-4);
}

.facts.wide {
  flex-wrap: wrap;
  row-gap: var(--space-3);
}

.facts div {
  min-width: 5.5rem;
}

.facts dt {
  margin: 0;
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.facts dd {
  margin: var(--space-1) 0 0;
  font-size: var(--text-panel);
  font-weight: var(--weight-strong);
  font-variant-numeric: tabular-nums;
}

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

@media (max-width: 60rem) {
  .product-grid,
  .world-hero {
    grid-template-columns: minmax(0, 1fr);
  }

  .table-head {
    display: none;
  }

  .table-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
  }
}

@media (max-width: 40rem) {
  .panel-head {
    flex-wrap: wrap;
  }

  .panel-tools {
    width: 100%;
    justify-content: flex-start;
    margin-left: 0;
  }
}

/* --- the create flow -------------------------------------------------------
 * One column, read top to bottom. The rider's own words get a bubble; the
 * answer does not, because boxing the reply would put a frame around most
 * of the screen and make the page look like a chat client rather than a
 * place where worlds are made.
 */

/* A conversation is read, so it keeps a reading measure rather than
   filling the page container. */
.thread,
.composer {
  max-width: 62rem;
}

.thread {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.msg {
  min-width: 0;
}

.msg-rider {
  align-self: flex-end;
  max-width: 34rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  /* One corner squared off, so the bubble points at its sender without a
     tail to draw. */
  border-bottom-right-radius: var(--radius-tight);
  background: var(--panel-raised);
  padding: var(--space-4);
}

.msg-rider p {
  margin: 0;
}

.msg-label,
.thread-waiting {
  margin: 0 0 var(--space-4);
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.msg-note {
  margin: calc(var(--space-4) * -1) 0 var(--space-4);
  color: var(--text-secondary);
  font-size: var(--text-small);
}

.attach-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.attach-chip {
  border: 1px solid var(--rim);
  border-radius: var(--radius-tight);
  padding: 2px var(--space-2);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--text-small);
}

/* --- variants -------------------------------------------------------------- */

.variant-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  align-items: stretch;
  gap: var(--grid-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.variant-card {
  display: flex;
  min-width: 0;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--rim);
  border-radius: var(--radius-card);
  background: var(--panel);
  transition: border-color var(--dur-base) var(--ease-out);
}

.variant-card:hover {
  border-color: var(--rim-light);
}

/* Selection is a rim, not a tick or a tint: the picture must stay the
   thing being compared, and a wash over one card changes its colours. */
.variant-card.selected {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.variant-art {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
}

.variant-art > svg,
.build-art > svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* The label that stops a generated picture from reading as a preview of
   the world the renderer will actually build. */
.variant-tag {
  position: absolute;
  bottom: var(--space-3);
  left: var(--space-3);
  border-radius: var(--radius-tight);
  background: var(--overlay);
  padding: 3px var(--space-2);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.variant-body {
  padding: var(--space-4);
}

.variant-name {
  margin: 0 0 var(--space-1);
  font-size: var(--text-panel);
  font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-heading);
}

.variant-mood {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-small);
}

.variant-actions {
  margin-top: auto;
  padding: 0 var(--space-4) var(--space-4);
}

.variant-actions .btn {
  width: 100%;
}

.variant-card.selected .variant-pick {
  border-color: var(--accent);
  color: var(--accent);
}

.thread-commit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  margin-top: var(--space-5);
}

.build-note {
  margin: 0;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

/* --- the composer ---------------------------------------------------------
 * A prompt field with an attach control, the way every prompt field has
 * one. No separate drop zone: a panel saying "put an image here" would
 * make an ordinary affordance look like a feature of its own.
 */

.composer {
  margin-top: var(--space-7);
  border: 1px solid var(--rim);
  border-radius: var(--radius-card);
  background: var(--panel-raised);
  padding: var(--space-4);
}

.composer:focus-within {
  border-color: var(--rim-light);
}

.composer-input {
  display: block;
  width: 100%;
  border: 0;
  background: none;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  letter-spacing: var(--tracking-body);
  line-height: var(--leading-snug);
  resize: vertical;
}

.composer-input:focus {
  outline: none;
}

.composer-input::placeholder {
  color: var(--future);
}

.composer-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.composer-attach {
  display: grid;
  width: 2rem;
  height: 2rem;
  place-items: center;
  border: 1px solid var(--rim);
  border-radius: var(--radius-tight);
  color: var(--text-secondary);
  cursor: pointer;
}

.composer-attach:hover {
  border-color: var(--rim-light);
  color: var(--text);
}

#composer-files:focus-visible + .composer-files,
.composer-attach:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.composer-files {
  min-width: 0;
  overflow: hidden;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-small);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.composer-row .btn {
  margin-left: auto;
}

/* --- builds in the collection ---------------------------------------------
 * A world being made is a world you have, so it is a card in the same grid
 * — not a queue in a panel of its own that the world would have to move
 * out of the moment it finished.
 */

.build-card .world-name {
  margin-bottom: calc(var(--space-1) * -1);
}

.build-building {
  border-style: dashed;
  border-color: var(--rim-light);
}

.build-failed {
  border-color: var(--danger);
}

/* The mark is the arrival notice. It survives the tab being closed, which
   is the whole reason it is a mark and not a toast. */
.build-new {
  border-color: var(--accent);
}

.build-mark {
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.build-art {
  opacity: 0.4;
}

.build-ready .build-art,
.build-new .build-art {
  opacity: 1;
}

.build-stage {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--text-small);
}

.build-stage-failed {
  color: var(--danger);
}

.build-pulse {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  animation: build-pulse 1.8s var(--ease-out) infinite;
}

@keyframes build-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.25;
  }
}

.build-detail {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-small);
}

/* Phases, never a percentage: the duration is genuinely unknown, and a
   bar that guesses it is a promise the app cannot keep. */
.build-stages {
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-small);
}

.build-stages li {
  display: flex;
  gap: var(--space-3);
  padding: 1px 0;
}

.build-stages li::before {
  content: "○";
}

.build-stages li.done {
  color: var(--muted);
}

.build-stages li.done::before {
  content: "●";
}

.build-stages li.now {
  color: var(--accent);
}

.build-stages li.now::before {
  content: "◐";
}

.build-stages li.failed {
  color: var(--danger);
}

.build-stages li.failed::before {
  content: "×";
}

.build-elapsed {
  margin: 0;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.build-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* --- profile and Garage ------------------------------------------------- */

.profile-hero {
  display: grid;
  grid-template-columns: minmax(0, calc(var(--space-8) * 3)) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-6);
}

.profile-portrait {
  display: grid;
  min-height: calc(var(--space-8) * 4);
  place-items: end center;
  overflow: hidden;
  border-radius: var(--radius-control);
  background: var(--panel-raised);
}

.profile-identity h2,
.garage-card h3 {
  margin: 0;
  font-size: var(--text-heading);
  letter-spacing: var(--tracking-heading);
}

.profile-identity > p {
  max-width: 56ch;
  margin: var(--space-3) 0 0;
  color: var(--text-secondary);
}

.profile-name-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.profile-preview-control .segmented {
  max-width: calc(var(--space-8) * 7);
}

.private-data-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  margin: 0;
}

.private-data-list div {
  border-left: 1px solid var(--hairline);
  padding-left: var(--space-3);
}

.private-data-list dt {
  color: var(--muted);
  font-size: var(--text-small);
}

.private-data-list dd {
  margin: var(--space-1) 0 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.rider-figure {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 0;
}

.rider-figure__body {
  transform: scale(var(--rider-build, 1), var(--rider-height, 1));
  transform-box: fill-box;
  transform-origin: center bottom;
}

.rider-figure__skin {
  fill: var(--rider-skin);
}

.rider-figure__helmet {
  fill: var(--face-raised);
}

.rider-figure__kit {
  fill: var(--rider-kit);
}

.rider-figure__shorts {
  fill: var(--face-raised);
}

.rider-figure__limb {
  fill: none;
  stroke: var(--rider-skin);
  stroke-linecap: round;
  stroke-width: var(--space-3);
}

.rider-figure__leg {
  stroke: var(--text-secondary);
  stroke-width: var(--space-4);
}

.rider-figure__shoe {
  fill: none;
  stroke: var(--face-raised);
  stroke-linecap: round;
  stroke-width: var(--space-2);
}

.garage-collection {
  grid-column: 1 / -1;
}

.garage-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--grid-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.garage-card {
  display: flex;
  min-width: 0;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--rim);
  border-radius: var(--radius-card);
  background: var(--panel);
}

.garage-card.selected {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.garage-art {
  display: grid;
  height: calc(var(--space-8) * 3.5);
  place-items: end center;
  background: var(--panel-raised);
  padding: var(--space-3) var(--space-5) 0;
}

.garage-art--bike {
  place-items: center;
  padding: var(--space-4);
}

.garage-card-body {
  padding: var(--space-4);
}

.garage-card-body p {
  margin: var(--space-2) 0 0;
  color: var(--muted);
  font-size: var(--text-small);
}

.garage-card-actions {
  margin-top: auto;
  padding: 0 var(--space-4) var(--space-4);
}

.garage-card-actions .btn {
  width: 100%;
}

.bike-figure {
  display: block;
  width: 100%;
  max-height: 100%;
  color: var(--asset-bike-endurance);
}

.bike-figure--aero_road {
  color: var(--asset-bike-aero);
}

.bike-figure--climbing {
  color: var(--asset-bike-climbing);
}

.bike-figure__wheel,
.bike-figure__frame,
.bike-figure__detail {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.bike-figure__wheel {
  stroke: var(--future);
  stroke-width: var(--space-1);
}

.bike-figure__frame {
  stroke: currentColor;
  stroke-width: var(--space-1);
}

.bike-figure--aero_road .bike-figure__frame {
  stroke-width: var(--space-2);
}

.bike-figure--climbing .bike-figure__frame,
.bike-figure__detail {
  stroke-width: calc(var(--space-1) / 2);
}

.bike-figure__detail {
  stroke: var(--text-secondary);
}

.active-setup {
  display: grid;
  grid-template-columns: minmax(0, calc(var(--space-8) * 2.5)) minmax(0, calc(var(--space-8) * 4)) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-6);
}

.active-setup__rider,
.active-setup__bike {
  display: grid;
  height: calc(var(--space-8) * 3.5);
  place-items: end center;
}

.active-setup__bike {
  place-items: center;
}

.active-setup__summary > p {
  margin: 0 0 var(--space-3);
}

.inline-setting {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

.inline-setting .field {
  min-width: calc(var(--space-8) * 2.5);
}

@media (max-width: 60rem) {
  .active-setup {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .active-setup__summary {
    grid-column: 1 / -1;
  }
}

@media (max-width: 40rem) {
  .profile-hero,
  .active-setup,
  .garage-grid,
  .private-data-list {
    grid-template-columns: minmax(0, 1fr);
  }

  .profile-portrait,
  .active-setup__summary {
    grid-column: auto;
  }
}

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

/* --- Ride menu -----------------------------------------------------------
 * Shared between the live cockpit and the living design reference. The
 * cockpit stylesheet owns only top-layer placement; the component language
 * itself lives here.
 */

.ride-menu {
  color: var(--text);
}

.ride-menu-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-5) var(--space-4);
}

.ride-menu-head h2,
.ride-menu-panel h3,
.ride-finish-confirm h3 {
  margin: 0;
  color: var(--text-bright);
}

.ride-menu-head h2 {
  font-size: var(--text-heading);
  font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-heading);
}

.ride-menu-state,
.ride-trace-meta,
.ride-device-detail,
.ride-menu-note {
  color: var(--muted);
}

.ride-menu-state {
  margin: 0 0 var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

/* Auto-flow rather than a fixed count: the tab strip grew from three to
   four and should not need a CSS edit the next time it grows. */
.ride-menu-tabs {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  border-block: 1px solid var(--hairline);
}

.ride-menu-tabs button,
.ride-trace-metrics button,
.ride-choice-grid button {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
}

.ride-menu-tabs button:not(:disabled),
.ride-trace-metrics button:not(:disabled),
.ride-choice-grid button:not(:disabled) {
  cursor: pointer;
}

.ride-menu-tabs button {
  min-height: calc(var(--space-8) - var(--space-2));
  border-bottom: 2px solid transparent;
  padding: 0 var(--space-3);
  font-weight: var(--weight-medium);
}

.ride-menu-tabs button[aria-selected="true"] {
  border-bottom-color: var(--accent);
  color: var(--text-bright);
}

.ride-menu-body {
  min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;
}

.ride-menu-panel {
  display: grid;
  gap: var(--space-5);
  padding: var(--space-5);
}

.ride-menu-panel[hidden],
.ride-finish-confirm[hidden] {
  display: none;
}

.ride-menu-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0;
}

.ride-menu-stats div {
  border-top: 1px solid var(--hairline);
  padding: var(--space-3) 0;
}

.ride-menu-stats div:nth-child(even) {
  padding-left: var(--space-4);
}

.ride-menu-stats dt {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
}

.ride-menu-stats dd {
  margin: var(--space-1) 0 0;
  color: var(--text-bright);
  font-family: var(--font-mono);
  font-size: var(--text-panel);
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-medium);
}

.ride-trace-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
}

.ride-trace-head h3,
.ride-finish-confirm h3 {
  font-size: var(--text-panel);
  font-weight: var(--weight-strong);
  letter-spacing: var(--tracking-heading);
}

.ride-trace-meta,
.ride-device-detail,
.ride-menu-note,
.ride-finish-confirm p {
  margin: var(--space-1) 0 0;
  font-size: var(--text-small);
  line-height: var(--leading-body);
}

.ride-trace-metrics {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
}

.ride-trace-metrics button {
  border-bottom: 1px solid transparent;
  padding: var(--space-1) 0;
  font-size: var(--text-small);
}

.ride-trace-metrics button[aria-pressed="true"] {
  border-bottom-color: var(--accent);
  color: var(--text);
}

.ride-trace {
  position: relative;
  min-height: calc(var(--space-8) * 2.25);
  border-block: 1px solid var(--hairline);
}

.ride-trace canvas,
.ride-trace-preview {
  display: block;
  width: 100%;
  height: calc(var(--space-8) * 2.25);
}

.ride-trace-preview {
  position: absolute;
  inset: 0;
  fill: none;
  stroke: var(--accent);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

.ride-menu-actions,
.ride-device-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.ride-menu-actions {
  justify-content: space-between;
  padding-top: var(--space-2);
}

.ride-finish-confirm {
  display: grid;
  gap: var(--space-3);
  border-top: 1px solid var(--danger);
  padding-top: var(--space-4);
}

.ride-device-list {
  display: grid;
}

/* The Start Workout row: what it resolved to on the left, the commit on the
 * right. The line is the fix for a decision split across the page — the
 * button never commits to a route and control the rider cannot see. */
.workout-launch-row { justify-content: space-between; }
.workout-resolution { margin: 0; font-size: var(--text-small); }

/* A session the server still holds, offered: what is running, then the two
 * ways out. Lives beside the device rows because it shares their surfaces —
 * the start panel in product, the design system as specimen. */
.start-resume {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.start-resume-line {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--text-small);
}

.ride-device-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-4);
  border-top: 1px solid var(--hairline);
  padding: var(--space-4) 0;
}

/* -- the Gears panel ------------------------------------------------------ */

.gear-panel-now {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-4);
  padding-bottom: var(--space-4);
}

.gear-panel-readout {
  margin: var(--space-1) 0 0;
  color: var(--text-bright);
  font-size: var(--text-heading);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-heading);
}

.gear-panel-heading {
  margin: var(--space-5) 0 var(--space-3);
}

.gear-map {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-small);
}

.gear-map th,
.gear-map td {
  border-top: 1px solid var(--hairline);
  padding: var(--space-3) var(--space-2);
  text-align: left;
}

.gear-map thead th {
  border-top: 0;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.gear-map tbody th {
  color: var(--text-bright);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

/* A row whose meaning changes under ERG is the surprising one, so the ERG
   cell carries the accent rather than the whole row shouting. */
.gear-map tr[data-erg-differs="true"] td:last-child {
  color: var(--accent);
}

.gear-panel kbd {
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  background: var(--face-raised);
  padding: 0 var(--space-2);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--text-label);
}

.ride-device-name {
  margin: var(--space-1) 0 0;
  color: var(--text-bright);
  font-weight: var(--weight-medium);
}

.ride-device-row[data-connected="true"] .ride-device-name {
  color: var(--progress);
}

.ride-device-results {
  display: grid;
  gap: var(--space-2);
  color: var(--text-secondary);
  font-size: var(--text-small);
}

.ride-device-result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  border-top: 1px solid var(--hairline);
  padding-top: var(--space-3);
}

.ride-choice-group {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  border: 0;
  border-top: 1px solid var(--hairline);
  padding: var(--space-4) 0 0;
}

.ride-choice-group legend {
  padding: 0;
  color: var(--text-bright);
  font-size: var(--text-panel);
  font-weight: var(--weight-strong);
}

.ride-choice-group > p {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-small);
  line-height: var(--leading-body);
}

.ride-choice-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
}

.ride-choice-grid button {
  display: grid;
  min-height: calc(var(--space-8) + var(--space-3));
  align-content: center;
  justify-items: start;
  gap: var(--space-1);
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  padding: var(--space-3);
  text-align: left;
}

.ride-choice-grid button[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--text-bright);
}

.ride-choice-grid button:disabled {
  border-color: var(--hairline);
  color: var(--future);
  cursor: not-allowed;
}

.ride-choice-grid button span {
  font-weight: var(--weight-medium);
}

.ride-choice-grid button small {
  color: inherit;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
}

.ride-menu-specimen-panel {
  overflow: hidden;
}

.ride-menu-specimen {
  width: min(100%, calc(var(--space-8) * 8));
  border: 1px solid var(--rim);
  border-radius: var(--radius-card);
  background: var(--overlay);
}

@media (max-width: 40rem) {
  .ride-trace-head,
  .ride-device-row {
    align-items: stretch;
    grid-template-columns: minmax(0, 1fr);
  }

  .ride-trace-head {
    display: grid;
  }

  .ride-trace-metrics {
    justify-content: flex-start;
  }
}

/* Dev spawn panel — the ride surface's developer control. Rendered only for
   a request whose token carries the capability; the boundary that matters is
   in the database, not here. */
.dev-panel {
  display: grid;
  /* Pin the column to the panel. Left as `auto` it sizes to max-content --
     the number input's intrinsic width -- and every row then lays out wider
     than the painted background. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  background: var(--scrim);
}

.dev-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin: 0;
  color: var(--future);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.dev-panel-position {
  margin: 0;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--text-heading);
  font-variant-numeric: tabular-nums;
}

.dev-panel-unit {
  color: var(--muted);
  font-size: var(--text-body);
}

.dev-panel-seek {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
}

.dev-panel-seek .field {
  flex: 1 1 auto;
  min-width: 0;
}

/* A number input carries an intrinsic width of its own; without this it
   ignores the flex track and pushes the Go button off the panel. */
.dev-panel-seek .field input {
  width: 100%;
  min-width: 0;
}

.dev-panel-note {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
}

/* Why the handover is unavailable, said beside the button that is asking.
   Empty most of the time, so it takes no room until it has something. */
.dev-panel-hint {
  margin: 0;
  color: var(--accent);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
}
.dev-panel-hint:empty { display: none; }

/* The panel shrinks to its readout so it can sit on the cockpit all session
   without covering the world. Collapsing hides the controls, never the
   position — that line is the reason the panel is open in the first place. */
.dev-panel-collapse {
  width: var(--space-4);
  height: var(--space-4);
  padding: 0;
  border: 1px solid var(--rim);
  border-radius: var(--radius-tight);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  line-height: 1;
}

.dev-panel-collapse::before {
  content: "–";
}

.dev-panel-collapse:hover {
  color: var(--text);
  border-color: var(--rim-light);
}

.dev-panel.is-compact .dev-panel-collapse::before {
  content: "+";
}

.dev-panel.is-compact .dev-panel-body {
  display: none;
}

.dev-panel-body {
  display: grid;
  gap: var(--space-2);
}

.dev-panel-tools {
  display: flex;
  gap: var(--space-2);
}

.dev-panel-tools .btn {
  flex: 1 1 0;
  min-width: 0;
}

/* A pressed tool is state, which is what the accent is for. */
.dev-panel-tools .btn[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}

/* Ride configuration, folded away. Both axes default from settings and are
   changed rarely; open they are tall enough to push the page's only action
   below the fold on a 1440x900 laptop. */
.setup-more {
  border-top: 1px solid var(--rim);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
}

.setup-more > summary {
  color: var(--muted);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  list-style: none;
  text-transform: uppercase;
}

.setup-more > summary::-webkit-details-marker {
  display: none;
}

.setup-more > summary::after {
  content: " +";
}

.setup-more[open] > summary::after {
  content: " –";
}

.setup-more > summary:hover {
  color: var(--text);
}

.setup-more > .setup-body {
  margin-top: var(--space-4);
}
/* --- route tiles: a world's routes, as shapes ----------------------------
 * The world card's box, with the plan trace where the hero art sits. The
 * page these replaced listed every section of every route in a six-column
 * table — eighteen rows for the flagship — which reads as a spreadsheet
 * rather than as a choice. Shape is what tells two routes through one place
 * apart, so the trace is the card.
 */

/* A FIXED height, not an aspect ratio. The card is a column flex container,
   so this box is a flex item, and a flex item's automatic minimum size is
   its content — a tall trace then pushes the box past whatever ratio it was
   given and every tile in the row ends up a different height (measured: 201,
   348 and 338 px for the three Rivelet routes). The plan's own
   `preserveAspectRatio="xMidYMid meet"` letterboxes any shape inside a box
   of any size, so pinning the box is what makes the row read as a row. */
.route-tile-art {
  display: block;
  min-height: 0;
  height: 11rem;
  overflow: hidden;
  border-radius: var(--radius-control);
  background: var(--face);
}

.route-tile-art > svg {
  display: block;
  width: 100%;
  height: 100%;
}

.route-tile-art .world-plan {
  stroke: var(--accent);
  stroke-width: 2;
}

/* No overlay link on a route tile — the whole card is not a target, because
   the only action is Ride and it is its own control. Without this the hover
   treatment inherited from .world-card implies a click that does nothing. */
.route-tile:hover {
  border-color: var(--rim);
  background: var(--panel);
}

/* --- the landscape builder ------------------------------------------------
   Three steps, one surface live at a time. Every colour, size, radius and
   step here resolves through the token layer; the SVG editors draw
   geometry and nothing else, so what a point or a violation looks like is
   decided in this file rather than in JavaScript. */

/* Only the active step is on screen. Driven by an attribute on the root
   rather than by a class the script toggles on five elements, so a step
   change is one write and cannot leave two steps half-shown. */
.draw-step {
  display: none;
  gap: var(--space-5);
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  align-items: start;
}

#draw-root[data-step="shape"] .draw-step-shape,
#draw-root[data-step="climb"] .draw-step-climb,
#draw-root[data-step="world"] .draw-step-world,
#draw-root[data-step="finish"] .draw-step-finish {
  display: grid;
}

/* The figures head their step, full width above the surface they describe;
   the way on closes it, full width under both. */
.draw-step > .tile-row,
.draw-step > .step-nav {
  grid-column: 1 / -1;
}

/* The step bar and the way out of this drawing entirely, on one line. */
.draw-topline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

@media (max-width: 60rem) {
  .draw-step {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The step bar: three destinations, one of them where you are. */
.step-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.step {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--rim);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: var(--text-small);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.step:hover {
  color: var(--text);
  border-color: var(--rim-light);
}

.step.is-current {
  background: var(--text);
  border-color: var(--text);
  color: var(--face);
  font-weight: var(--weight-strong);
}

.step-number {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
}

/* The mark that says a step owns a problem, so the bar answers "where"
   without the rider going to look. */
.step-mark {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--danger);
}

/* One line, on every step, saying whether this can be saved. */
.draw-status {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-4) 0 0;
  border-top: 1px solid var(--hairline);
  color: var(--muted);
  font-size: var(--text-body);
}

.status-pip {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--progress);
  transform: translateY(-0.1em);
}

.draw-status.has-problem {
  color: var(--text);
}

.draw-status.has-problem .status-pip {
  background: var(--danger);
}

/* The jump from a problem to the step that owns it. Sits in the status
   line, so reading what is wrong and going to fix it is one movement. */
.status-jump {
  flex: none;
  margin-left: auto;
}

/* The way on, at the foot of the step it leaves. Space above it, because
   it is a conclusion rather than another row of the panel. */
.step-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--hairline);
}

/* A choice whose options need a sentence each. Cards rather than a select,
   because the difference between them is not their names. */
.choice-list {
  display: grid;
  gap: var(--space-2);
  margin: 0 0 var(--space-4);
}

.choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--rim);
  border-radius: var(--radius-control);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}

.choice:hover {
  border-color: var(--rim-light);
}

.choice:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-glow);
}

.choice input {
  flex: none;
  margin-top: 0.2em;
  accent-color: var(--accent);
}

.choice-body {
  display: grid;
  gap: var(--space-1);
}

.choice-label {
  color: var(--text);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
}

.choice-detail {
  color: var(--muted);
  font-size: var(--text-small);
  line-height: var(--leading-snug);
}

/* Settings a chosen world would not consult. Dimmed rather than disabled:
   the values are still the rider's, and still there if they change worlds
   back. */
.is-inert .field,
.is-inert .draw-details {
  opacity: 0.45;
}

/* The dressing a rider mostly does not need: real defaults, one fold. */
.draw-details {
  margin: var(--space-4) 0;
}

.draw-details > summary {
  cursor: pointer;
  color: var(--muted);
  font-size: var(--text-small);
}

.draw-details > summary:hover {
  color: var(--text);
}

/* The road's facts, then the form that keeps it. A rule between them,
   because a description and a set of inputs are different kinds of thing
   and reading straight from one into the other makes the first look
   editable. */
.summary-pairs {
  margin: 0 0 var(--space-5);
  padding-bottom: var(--space-5);
  border-bottom: 1px solid var(--hairline);
}

.summary-pairs dd {
  font-size: var(--text-panel);
  font-weight: var(--weight-medium);
}

/* Start and Finish, named on the map. Set here rather than in the editor,
   like every other thing the SVG draws. */
.map-endpoint-label {
  fill: var(--text-secondary);
  font-family: var(--font-mono);
  letter-spacing: var(--tracking-meta);
  paint-order: stroke;
  stroke: var(--face);
  stroke-width: 3px;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* The box takes its final size before any script runs, so the surfaces
   appearing cannot reflow the page under the reader. */
.draw-surface {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-control);
  background: var(--face);
  overflow: hidden;
}

.draw-svg,
.draw-canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}

.draw-canvas {
  cursor: grab;
}

.draw-canvas:active {
  cursor: grabbing;
}

/* The road, from the carried compiler. Two strokes: a casing that reads at
   a glance and the carriageway on top of it. */
.map-road-casing {
  fill: none;
  stroke: var(--rim-light);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.map-road {
  fill: none;
  stroke: var(--accent);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The road is the grab target now, so it says so. The casing under it is
   wider than the road and takes the press, which is what makes grabbing
   forgiving without drawing a fat road. */
.map-road,
.map-road-casing {
  cursor: grab;
}

/* There is no scaffold to style. The road is the only path on this
   surface, and the points are the only handles — pulling the road
   somewhere new is what adds one, so there is nothing else to draw. */
.map-corner {
  cursor: grab;
}

.map-corner {
  fill: var(--face-raised);
  stroke: var(--text);
  stroke-width: 1.5px;
  vector-effect: non-scaling-stroke;
}

.map-corner:hover,
.map-corner.is-selected {
  fill: var(--accent);
  stroke: var(--text-bright);
}

/* A violation is marked where it happens, not only in the list below. */
.map-violation {
  fill: none;
  stroke: var(--danger);
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
}

.profile-fill {
  fill: var(--accent-glow);
}

.profile-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2px;
}

.profile-rule {
  stroke: var(--rim);
  stroke-dasharray: 3 6;
}

.profile-axis,
.profile-grade {
  fill: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
}

.profile-flag {
  fill: var(--danger);
  opacity: 0.14;
}

.profile-handle {
  fill: var(--face-raised);
  stroke: var(--text);
  stroke-width: 1.5px;
  cursor: ns-resize;
}

.profile-handle:hover,
.profile-handle.is-selected {
  fill: var(--accent);
  stroke: var(--text-bright);
}

/* A live figure changes as the drawing does. It goes quiet-loud rather
   than red-green: the accent is the app's one saturated voice, and a
   figure past its limit is a state, which is what the accent marks. */
.tile-live .tile-value {
  font-variant-numeric: tabular-nums;
}

.figure-bad {
  color: var(--danger);
}

.stale-mark {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
  color: var(--future);
  visibility: hidden;
}

.stale-mark.is-stale {
  visibility: visible;
  color: var(--accent);
}

.panel-tool-text {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-meta);
  color: var(--muted);
}

.violations {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}

.violation {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-control);
  border: 1px solid var(--rim);
  background: var(--panel);
  color: var(--text-secondary);
  font-size: var(--text-small);
}

.violation-ok {
  color: var(--muted);
}

.fact-pairs {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-4);
  margin: var(--space-4) 0 0;
}

.fact-pairs dt {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}

.fact-pairs dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.stack {
  display: grid;
  gap: var(--space-4);
}
