/* 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;
}

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: flex;
  width: 100%;
  max-width: calc(var(--content-max) + 2 * var(--page-padding));
  height: var(--header-height);
  margin-inline: auto;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--page-padding);
}

.wordmark {
  display: inline-flex;
  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;
  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);
}

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

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

.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);
}

.action-row-start {
  justify-content: flex-start;
  margin-top: 0;
  padding-top: var(--space-4);
}

/* 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;
}

/* A page-level action belongs to the title above it, not to whatever
   section happens to follow — so it owns the air underneath. */
.page-actions {
  padding-top: 0;
  margin-bottom: var(--space-6);
}

/* --- 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;
}

/* --- 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,
#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 {
  border-color: var(--text-secondary);
  box-shadow: 0 0 0 3px var(--accent-glow);
}

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

.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);
}

/* --- route profile -------------------------------------------------------- */

.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;
}

/* --- 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 {
  padding-top: var(--space-4);
}

/* 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 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;
}

/* --- power chart (history detail) ----------------------------------------- */

.power-chart {
  display: block;
  width: 100%;
  height: 6rem;
  border-radius: var(--radius-control);
  margin-top: var(--space-5);
  background: var(--panel-raised);
}

/* --- 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-difficulty {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.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;
  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 {
  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;
}

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);
}

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);
}

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