/* reposta web client — phase 1 (issue 0006).
 * Direction: a fuel-pump instrument. Every number is monospace and tabular; the
 * cheapest station is a lit roadside price totem (the one bold element), the
 * rest a quiet ledger. Layout is three clear regions — a top control bar, a
 * results column, and the map — not overlays on a full-bleed map. No framework,
 * no build step. */

:root {
  color-scheme: light dark;

  --bg: #e8eaee;
  --surface: #ffffff;
  --raised: #f2f4f7;
  --line: #d7dce3;
  --ink: #191e25;
  --ink-dim: #5a6672;

  --road: #2f6fe0;            /* the one interactive accent */
  --road-ink: #ffffff;


  --price-low: #1c8f4f;       /* 3-step price scale, ledger only */
  --price-mid: #b9781c;
  --price-high: #d5453f;
  --save: #147a51;

  --sign-bg: #fbf1dc;         /* the totem — a lit sign, parchment by day (0041) */
  --sign-ink: #241c0e;
  --sign-dim: #6b5c3c;        /* ~5.8:1 on --sign-bg */
  --sign-glow: #a15c00;       /* ~4.6:1 on --sign-bg */
  --sign-save: #146b3f;       /* ~5.8:1 on --sign-bg */
  --sign-line: #e3d3ab;

  --radius: 12px;            /* the totem */
  --radius-sm: 9px;          /* inputs, buttons, the dropdown, the mode switch */
  --thumb: 18px;              /* dual-range thumb; the JS fill math depends on it */
  --mono: ui-monospace, "SF Mono", "SFMono-Regular", "Cascadia Code", "Roboto Mono", "Menlo", monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0e1116;
    --surface: #161b21;
    --raised: #1d232b;
    --line: #2a313b;
    --ink: #e6eaee;
    --ink-dim: #96a1af;
    --road: #4c8bff;
    --road-ink: #0b1220;
    --price-low: #34b06d;
    --price-mid: #d59433;
    --price-high: #ec5f5b;
    --save: #35b47e;

    --sign-bg: #14171c;         /* the totem — a lit sign at night */
    --sign-ink: #f6f1e5;
    --sign-dim: #b0aca0;        /* was #9a978c — ~4:1 on --sign-bg, under AA */
    --sign-glow: #ffb02e;
    --sign-save: #63d69c;
    --sign-line: #2b313a;
  }
}

* { box-sizing: border-box; }

/* `[hidden]` beats any `display` we set. The browser's own `[hidden]` rule
   lives in the UA stylesheet, so *any* author rule with a `display` outranks it
   and the element stays on screen with `el.hidden = true` set — the JS looks
   right and the element is still there. It bit `.levels__cta` (0031) and
   `#savingsShare` (0035), and 0027 patched the same thing for `.row`. One rule
   here instead of remembering it at every call site. */
[hidden] { display: none !important; }

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  /* 392px was too narrow to hold a station name, an address and a stats line
   * without truncating one of them — measured at 1916px, every row clipped
   * (issue 0060). 560 is the widest the column can be before the map stops
   * being the bigger half on a 1280px laptop. */
  grid-template-columns: minmax(400px, 560px) 1fr;
  /* The last row takes the slack, the other two are content-sized. They were
   * all `auto`, and `min-height: 100vh` then shares any leftover height out
   * between them equally — invisible while the list is long, but in the profile
   * phase (short content, everything else hidden) it put 171px of empty grid
   * above the header and another 171 below (issue 0060). */
  grid-template-rows: auto auto 1fr;
  grid-template-areas:
    "confirm confirm"
    "top top"
    "panel map";
  overscroll-behavior: contain;
  font: 15px/1.45 var(--sans);
  color: var(--ink);
  background: var(--bg);
}

button, input, select { font: inherit; color: inherit; }
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------- top bar --- */
/* Search-first hero (issue 0011): the address box is the primary input and the
 * widest cell; "Buscar" is the primary action; distance / tiempo / depósito sit
 * in a quiet secondary strip. One CSS grid — no flex-wrap staircase. */

.topbar {
  grid-area: top;
  position: relative;
  z-index: 1000;            /* the address dropdown overhangs the map below */
  padding: 12px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

/* One column of stacked rows, the same at every width (issue 0060). The two
 * hand-tuned grids this replaced (0025, 0039, 0044) existed to pack six filters
 * and three inputs onto the first screen; the filters now live inside a folded
 * <details>, so there is nothing left to pack and both grids collapse into
 * this. Only fuel and mode share a row. */
.controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas:
    "search search"
    "fuel   modes"
    "more   more"
    "go     go";
  align-items: end;
  gap: 10px 14px;
  max-width: 1160px;   /* on a wide screen the hero stays a cluster, not edge-to-edge */
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
}
.brand__mark { width: 40px; height: 40px; flex-shrink: 0; display: block; }
.brand__name { font-size: 24px; font-weight: 660; letter-spacing: -0.02em; }

.field { display: flex; flex-direction: column; gap: 5px; }
.field--search { grid-area: search; position: relative; }
.field--fuel { grid-area: fuel; }
.field--modes { grid-area: modes; }
/* The switch fills its half of the row, so the two options read as one control
 * the same width as the fuel select beside it. */
.field--modes .modes { display: flex; }
.field--modes .mode { flex: 1; }
/* The primary action, on its own full-width row (issue 0039). `#search`'s ID
 * specificity beats `.btn`'s `height: 36px` / `align-self: end` without a
 * fight. */
#search {
  grid-area: go;
  width: 100%;
  height: 52px;
  font-size: 17px;
}
/* The 84px was always the *input's* width, but it sat on the field, so the
   label ("Depósito · L" plus its `?`) needed 103px and overflowed by 19 — which
   cascaded into 3px of horizontal scroll on the whole page. `overflow: hidden`
   on the body hid that until 0042 removed it. Size the field to its widest
   child and keep the 84px where it was meant to be. */
.field--num { width: max-content; }
.field--num .field__input { width: 84px; }

/* "Más opciones" (issue 0060) — a native <details>, so open/closed, the
 * disclosure triangle and keyboard handling are the browser's job. It replaces
 * the always-open six-control strip that put the reach, the detour, the brand,
 * the tank and the nav app in front of a first-time visitor before the Buscar
 * button. */
.more {
  grid-area: more;
  border-top: 1px dashed var(--line);
  padding-top: 8px;
}
.more__sum {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  min-height: 32px;
  padding: 5px 0;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
}
.more__sum:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; border-radius: 4px; }
/* A flex <summary> loses its ::marker in Chrome, so the disclosure state gets
   said in text instead of being invisible. */
.more__label::after { content: " ▾"; color: var(--ink-dim); }
.more[open] .more__label::after { content: " ▴"; }
/* The digest of what is folded away: syncOutputs() writes it, so it can never
 * quote a number the fields disagree with. */
.more__vals {
  min-width: 0;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--ink-dim);
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Lleno / Importe (issue 0070) sits right above the tank/nav summary it
 * shares a settings-not-filters home with, so the dashed rule that used to
 * open `.more__prefs` opens this instead. */
.more__spend {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px dashed var(--line);
}
#spendField { width: 92px; }
.more__prefs {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--ink-dim);
}
.more__prefs b { font-weight: 600; color: var(--ink); }
.more__link {
  margin-left: 8px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--road);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.more__link:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }

/* Four controls now: autonomy, reach, the mode-specific limit, and brand.
 * #corridorField / #minutesField still share the `slot` cell, which is what
 * keeps a mode switch from reflowing the strip (issue 0025). The autonomy
 * column is a fixed 100px, not `max-content` (issue 0071) — 0044 already
 * found that content-based column sizing on this grid reflows with the
 * label text; a fixed width lets the label wrap instead, same fallback 0044
 * settled on for the reach column's own worst case. */
.filters {
  display: grid;
  grid-template-columns: 100px minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas:
    "auton reach  slot"
    ".     brandf brandf";
  align-items: end;
  gap: 12px 16px;
  margin-top: 4px;
}
/* `.field--num` is `width: max-content` (see the deposit-field comment above)
   so it fits its widest child instead of stretching — right for `#minutesField`
   in the flexible `slot` column, but here it would let the field escape its
   fixed 100px track. Fill the track instead and let the label wrap into it. */
#autonomyField { grid-area: auton; width: 100%; }
.filters .field--range:not(#corridorField) { grid-area: reach; }
.filters .field--brand { grid-area: brandf; position: relative; max-width: 300px; }
#corridorField, #minutesField { grid-area: slot; }
.field--range { max-width: 300px; }

/* address search (issue 0006 phase 2) — a text box over /v1/geocode with its
 * own results dropdown; picking one sets the origin like a map tap. */
.field--search .field__input { width: 100%; }

/* brand filter (issue 0038) — same dropdown shell as the address boxes
 * (`.places`), a local list instead of a remote lookup. */
.field--brand .field__input { width: 100%; }
.places__count { margin-left: 5px; color: var(--ink-dim); font-size: 11.5px; }

/* Route mode (issue 0014): the hero cell holds two of those boxes stacked,
 * Desde over Hasta, joined by a rule so they read as one trip rather than two
 * unrelated fields. The hero grid itself is unchanged — this all lives inside
 * the single `search` cell issue 0011 laid out. */
.trip { gap: 0; }
.trip__leg { position: relative; display: flex; flex-direction: column; gap: 4px; }
.trip__leg + .trip__leg { margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--line); }
.controls[data-mode="near"] #destLeg { display: none; }
.controls[data-mode="near"] #corridorField { display: none; }
.controls[data-mode="route"] #minutesField { display: none; }
[data-mode-label] { display: none; }
.controls[data-mode="route"] [data-mode-label="route"],
.controls[data-mode="near"] [data-mode-label="near"] { display: inline; }

/* The combo shell is shared with the numeric fields, whose input is monospace
 * and right-aligned inside the box. An address is neither. */
.trip .field__combo .field__input {
  font-family: var(--sans);
  text-align: left;
}

/* the locate crosshair sits inside the "Desde" box: an origin action, not a
 * filter. Inline SVG, not the old 📍 emoji — the only glyph on the page. */
.btn--locate {
  height: 26px;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-dim);
  background: none;
  border-color: transparent;
  border-radius: 7px;
}
.btn--locate:hover { background: var(--bg); color: var(--road); }

/* ------------------------------------------------- search / results phase ---
 * The form is only the main event until you have searched. After that the
 * attention belongs on the map and the results, so the whole hero collapses to
 * one summary line you tap to get it back. On a phone this is the difference
 * between a 23px results sliver and a usable list (issue 0015). */
.tripbar {
  display: none;
  width: 100%;
  align-items: baseline;
  gap: 10px;
  padding: 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: inherit;
}
body[data-phase="results"] .tripbar { display: flex; }
body[data-phase="results"] .controls { display: none; }

/* min-width:0 on both, or the nowrap text sets a min-content width that the
 * grid column has to honour — which silently widens the whole page and pushes
 * the results' price column off-screen. */
.tripbar__trip {
  min-width: 0;
  font-size: 15px;
  font-weight: 650;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tripbar__meta {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  color: var(--ink-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Looks like a button so the bar reads as tappable at a glance (issue 0043) —
 * the tap target stays the whole bar, this is only the visual cue. */
.tripbar__edit {
  flex: none;
  display: flex;
  align-items: center;
  gap: 5px;
  height: 30px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-weight: 620;
  color: var(--road);
}
.tripbar__edit-icon { width: 13px; height: 13px; flex: none; }
.tripbar:hover .tripbar__edit { border-color: var(--road); background: var(--bg); }
.tripbar:focus-visible { outline: 2px solid var(--road); outline-offset: 3px; border-radius: 6px; }

/* Opening hours (issue 0017). Green = cheapest is already the ledger's language,
 * so "open" borrows the neutral ink and only "closed" gets colour — a shut
 * station is the exception worth spotting, not a third price tier. */
.badge {
  display: inline-block;
  vertical-align: 1px;
  margin-left: 5px;
  padding: 1px 5px;
  border-radius: 5px;
  font: 600 9.5px/1.5 var(--sans);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}
.badge--open { background: color-mix(in srgb, var(--price-low) 16%, transparent); color: var(--price-low); }
.badge--shut { background: color-mix(in srgb, var(--price-high) 16%, transparent); color: var(--price-high); }
.row.is-shut .row__name,
.row.is-shut .row__price { opacity: 0.55; }
.row__hours { display: block; color: var(--ink-dim); opacity: 0.8; }
.row__hours:empty { display: none; }   /* no separator for a station with no hours */
.totem__hours { display: block; margin-top: 2px; }
.totem__sched { color: var(--sign-dim); }
.totem .badge--open { background: color-mix(in srgb, var(--sign-save) 18%, transparent); color: var(--sign-save); }
.totem .badge--shut { background: color-mix(in srgb, var(--price-high) 22%, transparent); color: color-mix(in srgb, var(--price-high) 78%, white); }

/* mode switch — two buttons reading as one control */
.modes { display: inline-flex; border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.mode {
  height: 30px;
  padding: 0 8px;
  /* "Cerca de mí" wrapped to two lines once the switch shared a row with the
     fuel select (0060), which made the row 20px taller than its neighbour. */
  white-space: nowrap;
  font: 600 11.5px/1 inherit;
  letter-spacing: 0.01em;
  background: none;
  border: 0;
  color: var(--ink-dim);
  cursor: pointer;
}
.mode + .mode { border-left: 1px solid var(--line); }
.mode[aria-pressed="true"] { background: var(--road); color: var(--road-ink); }
.mode:focus-visible { outline: 2px solid var(--road); outline-offset: -2px; }

.places {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 1;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.22);
  max-height: 264px;
  overflow-y: auto;
}
.places__item {
  padding: 8px 9px;
  border-radius: 6px;
  font-size: 13px;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.places__item:hover,
.places__item.is-active { background: var(--raised); }
.places__empty { padding: 8px 9px; font-size: 12.5px; color: var(--ink-dim); }

/* hero-field labels: sentence case, legible, full-ink (issue 0011). */
/* Wraps rather than overflows. `nowrap` here is what turned a column too narrow
   into a label sitting on top of its neighbour, with nothing to clip it once
   0042 removed the body's `overflow: hidden`. Between 861px and the 860px
   breakpoint the strip is genuinely short of room, and a second line is the
   honest way to say so. The value keeps its own `nowrap` below so a range never
   breaks across lines — "30 – 460" reads as one number or not at all. */
.field__label {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink);
}
.field__val {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
}
/* the secondary strip stays quiet — smaller, dim, uppercase. */
.filters .field__label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.filters .field__val { font-size: 13px; }
/* The readout and its unit sit inside the uppercased label strip but are data,
 * not a heading — keep "km"/"min" lowercase like everywhere else on the page. */
.filters .field__val,
.filters .field__unit { text-transform: none; letter-spacing: normal; }

/* "?" beside a filter label opens a one-line popover explaining it (issue 0020).
 * Native popover API — light-dismiss and Esc come free, no JS. */
.help {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 15px;
  height: 15px;
  padding: 0;
  margin-left: 2px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-dim);
  font: 700 10px/1 var(--sans);
  vertical-align: middle;
  cursor: help;
}
.help::after { content: ""; position: absolute; inset: -8px; }  /* bigger tap target */
.help:hover { color: var(--ink); border-color: var(--ink-dim); }
.help:focus-visible { outline: 2px solid var(--road); outline-offset: 1px; }

.help__pop {
  max-width: 240px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  font: 400 12px/1.45 var(--sans);
  letter-spacing: normal;       /* the label strip is uppercase, tracked and nowrap; */
  text-transform: none;         /* the popover text inherits all three — reset them */
  white-space: normal;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.22);
}

.field__input {
  height: 36px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0 8px;
}
.field--num .field__input { font-family: var(--mono); text-align: center; padding: 0 4px; }
.field__input:focus-visible { outline: 2px solid var(--road); outline-offset: 1px; }

/* A numeric input that carries its unit inside the box: "  5 │ min". */
.field__combo {
  display: flex;
  align-items: center;
  gap: 3px;
  height: 36px;
  padding: 0 7px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.field__combo:focus-within { outline: 2px solid var(--road); outline-offset: 1px; }
.field__combo .field__input {
  height: auto;
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font-family: var(--mono);
  text-align: right;
}
.field__combo .field__input:focus-visible { outline: none; }
.field__unit { font: 11px var(--mono); color: var(--ink-dim); }

.field__range {
  border: 0;
  padding: 0;
  height: 36px;
  background: transparent;
  accent-color: var(--road);
}

/* The reach window (issue 0017): two ranges stacked on one track, so "desde"
 * and "hasta" read as one span rather than two unrelated numbers.
 *
 * The native track has to go — `accent-color` would paint two of them, one per
 * input — so the container draws the rail and the selected span itself, from
 * two custom properties the JS keeps in sync. Only the thumbs take pointer
 * events; everything else falls through to the map-free space below. */
.range--dual {
  position: relative;
  display: block;
  height: 36px;
}
.range--dual::before,
.range--dual::after {
  content: "";
  position: absolute;
  top: 50%;
  height: 4px;
  margin-top: -2px;
  border-radius: 2px;
  pointer-events: none;
}
.range--dual::before { left: 0; right: 0; background: var(--line); }
.range--dual::after {
  left: var(--from, 0%);
  right: calc(100% - var(--to, 100%));
  background: var(--road);
}
.range--dual .field__range {
  position: absolute;
  inset: 0;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  pointer-events: none;
}
.range--dual .field__range::-webkit-slider-runnable-track { background: none; }
.range--dual .field__range::-moz-range-track { background: none; }
/* Drawn by hand for the same reason the track is: `accent-color` is off here.
 * `box-sizing: border-box` so the 2px border sits inside `--thumb`, not outside
 * it — otherwise the real thumb is 22px, its centre travels 11px..(100%-11px),
 * and the JS fill (which assumes half of `--thumb`) ends up ~2px off the thumb. */
.range--dual .field__range::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  box-sizing: border-box;
  width: var(--thumb);
  height: var(--thumb);
  border-radius: 50%;
  border: 2px solid var(--surface);
  background: var(--road);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.35);
  pointer-events: auto;
  cursor: pointer;
}
.range--dual .field__range::-moz-range-thumb {
  box-sizing: border-box;
  width: var(--thumb);
  height: var(--thumb);
  border-radius: 50%;
  border: 2px solid var(--surface);
  background: var(--road);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.35);
  pointer-events: auto;
  cursor: pointer;
}
/* Separate rules on purpose: a browser drops the whole rule if any selector in
 * a comma list is one it does not recognise. */
.range--dual .field__range:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--road);
  outline-offset: 2px;
}
.range--dual .field__range:focus-visible::-moz-range-thumb {
  outline: 2px solid var(--road);
  outline-offset: 2px;
}
/* Near-me asks one question, so it gets one thumb and a fill from the left. */
.controls[data-mode="near"] #radiusFrom { display: none; }

.btn {
  height: 36px;
  align-self: end;
  padding: 0 18px;
  font-weight: 620;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.btn--go { background: var(--road); color: var(--road-ink); }
.btn--ghost {
  background: none;
  border-color: var(--line);
  color: var(--ink-dim);
  font-weight: 600;
}
.btn--ghost:hover { border-color: var(--road); color: var(--road); }
.btn:disabled { opacity: 0.55; cursor: progress; }
.btn:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }

/* -------------------------------------------------------- results column --- */

.panel {
  grid-area: panel;
  background: var(--surface);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
}

.panel__status, .panel__stale { margin: 0; padding: 10px 16px 0; font-size: 12.5px; }
.panel__status { color: var(--ink-dim); }
.panel__status:empty { display: none; }
.panel__stale { color: var(--price-mid); font-weight: 550; }
.panel__stale::before { content: "⚠ "; }

/* "¿Repostaste aquí?" / level-up — full-width strip above the header,
 * issue 0018 / 0028, moved up here and restyled by issue 0040. Same money
 * palette as `.levels` and `.headline__save` (0046 follow-up) rather than a
 * one-off blue: it's the app's own accent for savings, already checked for
 * contrast on `--surface` in both themes, so it doesn't need its own pinned
 * colors to look consistent or to hold up in dark mode. */
.confirmbar {
  grid-area: confirm;
  margin: 0;
  padding: 10px 16px;
  font-size: 15px;
  font-weight: 550;
  border-bottom: 1px solid color-mix(in srgb, var(--save) 35%, var(--line));
  background: color-mix(in srgb, var(--save) 14%, var(--surface));
  color: var(--ink);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}
/* Always its own line: the question can be as long as a real station name,
 * and giving the buttons the full row below (instead of whatever's left on
 * the message's last line) is what stops "Sí · +X,XX €" from wrapping its
 * own text on a phone (0046). */
.confirmbar span { flex-basis: 100%; }
/* The amount, wherever it appears in the banner text — same green as the
 * running total (`.headline__save b`), so "how much" reads the same
 * everywhere it's shown. */
.confirmbar .amount { font-family: var(--mono); font-weight: 700; color: var(--save); }
.confirmbar button {
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink-dim);
  font: inherit;
  font-weight: 620;
  cursor: pointer;
}
.confirmbar button.confirmbar-yes { border-color: var(--save); color: var(--ink); }

/* content visibility driven by data-state on .panel */
.empty, .headline, .totem, .ledger { display: none; }
.panel[data-state="empty"] .empty { display: block; }
.panel[data-state="results"] .headline,
.panel[data-state="results"] .totem,
.panel[data-state="results"] .ledger { display: block; }
.panel[data-state="none"] .headline { display: block; }
.panel[data-state="none"] .headline .headline__save { display: none; }

.empty {
  margin: 0;
  padding: 22px 18px;
  color: var(--ink-dim);
  font-size: 14px;
  line-height: 1.6;
}
.empty b { color: var(--ink); }

/* --- the saving, stated plainly, above everything else --- */
.headline {
  padding: 16px 16px 14px;
  border-bottom: 1px solid var(--line);
}
.headline__save { margin: 0; font-size: 16px; font-weight: 600; }
.headline__save b {
  font-family: var(--mono);
  font-size: 20px;
  color: var(--save);
}
.headline__sub { margin: 4px 0 0; font-size: 12px; color: var(--ink-dim); }
.headline__sub .num { color: var(--ink); }

/* Reposta levels — issue 0018, restyled by 0026 into a strip, now a full
   gamified card by 0028: named levels + a progress bar, right under the logo
   (grid-area: levels — see .controls above) instead of after the whole form.
   One accent throughout (--save) rather than a per-tier color ramp — the
   level *name* carries the progression, not the color. renderLevels()
   toggles [hidden] at 0 fills. */
/* In the panel, not the form (issue 0060). `.panel` is a flex column, so one
 * `order` is the whole rule: at the top while you are searching, after the
 * list once there are results, so it never sits between a new visitor and the
 * Buscar button (where 0028 had put it, ~600px down a phone screen). `.src`
 * keeps its `margin-top: auto` and stays last either way. */
.levels {
  order: 0;
  margin: 14px;
  padding: 12px 14px 10px;
  border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--save) 30%, var(--line));
  background: color-mix(in srgb, var(--save) 9%, var(--surface));
}
body[data-phase="search"] .levels { order: -1; }
.levels__badge {
  margin: 0 0 3px;
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--save);
}
.levels__icon { width: 13px; height: 13px; fill: currentColor; flex: none; }
/* The level-up moment (0028): a couple of quick pulses, not a persistent
   animation — killed outright under prefers-reduced-motion below. */
.levels__badge--up { animation: levels-pulse 0.55s ease 2; }
@keyframes levels-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.08); }
}
/* Compact card (0060): the total and Compartir share one line, and the next
 * level and the month share the next. Reiniciar and the café button moved to
 * the profile — actions, not a glance. */
.levels__total {
  margin: 0;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.levels__total b {
  font-family: var(--mono);
  font-size: 34px;
  font-weight: 700;
  color: var(--save);
  font-variant-numeric: tabular-nums;
}
.levels__bar {
  margin: 8px 0 5px;
  height: 7px;
  border-radius: 999px;
  background: var(--raised);
  overflow: hidden;
}
.levels__fill {
  height: 100%;
  background: var(--save);
  border-radius: inherit;
  transition: width 0.4s ease;
}
.levels__next { margin: 0; font-size: 11.5px; color: var(--ink-dim); }
.levels__next b { font-family: var(--mono); color: var(--ink); }
.levels__next button {
  margin-left: 6px;
  padding: 1px 4px;
  border: 0;
  background: none;
  color: var(--road);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
/* Compartir asciende de enlace subrayado a botón de verdad (0035) — es la
   acción que produce la imagen que de verdad se comparte. */
#savingsShare {
  display: inline-flex;
  align-items: center;
  flex: none;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--save);
  color: var(--surface);
  text-decoration: none;
  font-weight: 650;
}
#savingsShare:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }
/* A real button (0036), not the underlined line 0030 left it as — Ko-fi's
   blue, dark text/icon for contrast, tall enough to tap. */
.levels__coffee-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: var(--radius-sm);
  background: #13c3ff;
  color: #14171c;
  font-size: 13px;
  font-weight: 650;
  text-decoration: none;
}
.levels__coffee-btn:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }
.levels__cta {
  display: block;
  margin: 8px 0 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 650;
  color: var(--road);
  text-decoration: underline;
  cursor: pointer;
}
.levels__cta:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }

/* Push to install (issue 0056) — its own card right under .levels, same shape
 * language (--surface, --radius, --radius-sm) but a road-tinted border so it
 * doesn't read as a second savings card. Mirrors .levels' order flip (0060)
 * so it stays directly underneath in both the search and results phase. */
.install {
  order: 0;
  margin: 0 14px 14px;
  padding: 14px;
  border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--road) 25%, var(--line));
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
body[data-phase="search"] .install { order: -1; }
.install__row { display: flex; align-items: center; gap: 12px; }
.install__icon { flex: none; border-radius: var(--radius-sm); }
.install__copy { flex-grow: 1; }
.install__title { margin: 0; font-size: 15px; font-weight: 650; }
.install__sub { margin: 2px 0 0; font-size: 13px; color: var(--ink-dim); }
/* 44px both ways (WCAG target size), same recipe as .confirmbar's buttons. */
.install__close {
  flex: none;
  width: 44px;
  height: 44px;
  border: 0;
  background: none;
  color: var(--ink-dim);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
.install__close:hover { color: var(--ink); }
.install__close:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }
/* Android/Chrome/Edge/Samsung: calls the saved beforeinstallprompt's prompt(). */
.install__btn {
  min-height: 46px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--road);
  color: var(--road-ink);
  font: inherit;
  font-size: 15px;
  font-weight: 650;
  cursor: pointer;
}
.install__btn:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }
/* iPhone/Safari: beforeinstallprompt does not exist there, so this is the
 * whole mechanism — two numbered steps instead of a button that would do
 * nothing. */
.install__steps {
  margin: 0;
  padding: 8px 12px 8px 28px;
  border-radius: var(--radius-sm);
  background: var(--raised);
  color: var(--ink);
  font-size: 13.5px;
  line-height: 1.8;
}
.install__stepicon { width: 16px; height: 16px; vertical-align: -3px; color: var(--road); }

/* --- the price totem: one bold element --- */
.totem {
  position: relative;
  margin: 14px;
  padding: 15px 16px 13px;
  border-radius: var(--radius);
  color: var(--sign-ink);
  background: var(--sign-bg);
  border: 1px solid var(--sign-line);
  overflow: hidden;
  cursor: pointer;
}
.totem::before {
  content: "";
  position: absolute;
  inset: -40% 40% auto -10%;
  height: 120px;
  pointer-events: none;
}
.totem__eyebrow {
  margin: 0 0 2px;
  padding-right: 36px; /* clears the .totem__fav button in the corner */
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sign-dim);
}
.totem__brand { margin: 0; padding-right: 36px; font-size: 15px; font-weight: 640; }
/* The cheapest station of all, in the colour the ledger uses for "cheapest".
 * It read amber purely because the totem is styled as a lit roadside sign, which
 * quietly contradicted the green/orange/red scale taught two inches below. */
.totem__price { margin: 2px 0 0; display: flex; align-items: baseline; gap: 8px; }
.totem__price > span:first-child {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(2.4rem, 9vw, 3rem);
  font-weight: 600;
  line-height: 1;
  color: var(--sign-save);
}
.totem__unit { font-size: 11px; font-weight: 600; color: var(--sign-dim); letter-spacing: 0.08em; }
.totem__foot {
  margin-top: 11px;
  padding-top: 10px;
  border-top: 1px solid var(--sign-line);
  display: flex;
  align-items: center;
  gap: 8px 12px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--sign-dim);
}
.totem__stat b, .totem__stat .num { font-family: var(--mono); color: var(--sign-ink); }
/* Blue like "Buscar": one colour for primary actions. Amber stays for the
 * cheapest-marker and the price scale, not for a button (issue 0023). */
.totem__go {
  margin-left: auto;
  background: var(--road);
  color: var(--road-ink);
  font-weight: 650;
  text-decoration: none;
  padding: 7px 13px;
  border-radius: 8px;
}
/* --road, like every other focus ring — on the dark totem it's swapped below
 * for --sign-ink, since --road doesn't show against a dark sign. */
.totem__go:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }

/* Favourite star (issue 0037), a corner toggle over the sign like a car sticker. */
.totem__fav {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  background: none;
  border: none;
  padding: 0;
  font-size: 17px;
  color: var(--sign-line);
  cursor: pointer;
  z-index: 1; /* above the ::before glow */
}
.totem__fav:hover { color: var(--sign-dim); }
.totem__fav.is-fav { color: var(--sign-glow); }
.totem__fav:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }

/* Everything above the totem that only differs at night: the dark totem
 * needs its own layered background and glow, the price its shadow, and both
 * focus rings swap to --sign-ink since --road doesn't show on a dark sign. */
@media (prefers-color-scheme: dark) {
  .totem {
    background:
      radial-gradient(130% 90% at 12% -10%, #1d232c 0%, #12151a 60%),
      var(--sign-bg);
  }
  .totem::before {
    background: radial-gradient(closest-side, color-mix(in srgb, var(--sign-save) 20%, transparent), transparent);
  }
  .totem__price > span:first-child {
    text-shadow: 0 0 22px color-mix(in srgb, var(--sign-save) 42%, transparent);
  }
  .totem__go:focus-visible,
  .totem__fav:focus-visible { outline-color: var(--sign-ink); }
}

/* --- the ledger: the rest, quiet --- */
.ledger { padding-bottom: 16px; }        /* the last row must clear the footer */
.ledger__more {
  list-style: none;
  padding: 10px 16px;
  font-size: 12px;
  color: var(--ink-dim);
}
.ledger__cap {
  margin: 0;
  padding: 6px 16px 8px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.ledger__cap span { font-weight: 500; letter-spacing: 0.02em; text-transform: none; }
.ledger__list { list-style: none; margin: 0; padding: 0; }

.row {
  display: grid;
  /* meta (issue 0047) was `minmax(0, max-content)` — unbounded, so a long
   * opening-hours string (a station open different hours per day) grew this
   * column past the row's own width, and `name`/`where` (min 0) were
   * strangled to nothing. `fit-content(240px)` is the native "grow to content,
   * but no further" primitive: 240px clears the longest real meta line seen in
   * route mode (distance + desvío + price delta, ~220px) so no row with today's
   * `L-D: 24H` shifts at all, while still capping the pathological hours case
   * so name/where get their share back. min()/max() can't mix with the
   * max-content keyword — fit-content() is the valid way to say this. */
  grid-template-columns: 18px minmax(0, 1fr) fit-content(240px) 28px;
  grid-template-areas:
    "rank name  price fav"
    "rank where meta fav";
  column-gap: 10px;
  row-gap: 3px;
  align-items: baseline;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  cursor: pointer;
}
.row:hover { background: var(--raised); }
/* Whichever row is in the totem hides its own row instead of merely being
 * marked active (issue 0027). The global `[hidden]` rule at the top is what
 * makes that stick. */
.row__rank {
  grid-area: rank;
  align-self: center;
  font: 600 12px/1 var(--mono);
  color: var(--ink-dim);
  text-align: right;
}
/* Wrap, never ellipsis (issue 0060). Every row was truncating the brand and the
 * address — "BALL…", "Calle R…" — at 390px *and* in the 392px desktop panel,
 * because both lines were nowrap in a column that had to give way to the mono
 * stats. A brand name the user cannot read costs more than a taller row. */
.row__name {
  grid-area: name;
  min-width: 0;
  font-weight: 600;
}
.row__where {
  grid-area: where;
  min-width: 0;
  font-size: 12px;
  color: var(--ink-dim);
}
.row__price {
  grid-area: price;
  justify-self: end;
  font: 600 16px/1.1 var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.row__price .u { font-size: 9px; font-weight: 600; color: var(--ink-dim); margin-left: 3px; }
/* The stats wrap now instead of ellipsising (issue 0060, closing 0051's route
 * 2): the `fit-content(240px)` cap from 0047 still stops this column eating the
 * row, but what does not fit inside it falls to a second line instead of being
 * cut, so a station with per-day opening hours still says who it is. */
.row__meta {
  grid-area: meta;
  justify-self: end;
  min-width: 0;
  max-width: 100%;
  font: 11px/1.35 var(--mono);
  color: var(--ink-dim);
  text-align: right;
}
/* Favourite star (issue 0037). A 32px tap target via negative margin, without
 * pushing the 28px grid column that keeps the row's own layout unchanged. */
.row__fav {
  grid-area: fav;
  align-self: center;
  justify-self: end;
  width: 32px;
  height: 32px;
  margin: -6px -8px -6px 0;
  display: grid;
  place-items: center;
  background: none;
  border: none;
  padding: 0;
  font-size: 15px;
  line-height: 1;
  color: var(--line);
  cursor: pointer;
}
.row__fav:hover { color: var(--ink-dim); }
.row__fav.is-fav { color: var(--sign-glow); }

.row.tier-low  .row__price { color: var(--price-low); }
.row.tier-mid  .row__price { color: var(--price-mid); }
.row.tier-high .row__price { color: var(--price-high); }

.src {
  margin-top: auto;
  padding: 12px 16px calc(14px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink-dim);
}
.src a { color: inherit; }

/* ------------------------------------------------------------------- map --- */

#map {
  grid-area: map;
  /* Desktop only (overridden below <=860px): the page scrolls the panel column,
   * and without this the map would scroll away with it, leaving the map column
   * empty. sticky + its own viewport-height keep it in view the whole time. */
  position: sticky;
  top: 0;
  height: 100vh;
  z-index: 0;               /* keep Leaflet's panes under the topbar dropdown */
  min-width: 0;
}

@media (prefers-color-scheme: dark) {
  .leaflet-tile-pane { filter: brightness(0.82) contrast(1.06); }
}

.pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  font: 700 12px/1 var(--mono);
  color: #fff;
  border: 2px solid var(--surface);
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.45);
  background: #57616e;
}
.pin--best {
  width: 30px; height: 30px;
  font-size: 14px;
  color: #1c1305;
  background: var(--sign-glow);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--sign-glow) 34%, transparent), 0 1px 6px rgba(0, 0, 0, 0.5);
}
.pin--origin {
  width: 16px; height: 16px;
  background: var(--road);
  border-color: var(--surface);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--road) 28%, transparent);
}
/* Destination: same size as the origin so the two read as a pair, hollow so
 * "where you are" and "where you're going" stay distinguishable at a glance. */
.pin--dest {
  width: 16px; height: 16px;
  background: var(--surface);
  border: 4px solid var(--road);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--road) 22%, transparent);
}

.leaflet-popup-content { font: inherit; margin: 10px 12px; }
.leaflet-popup-content a { color: var(--road); }
.leaflet-popup-content .num { font-family: var(--mono); }
.popup-where { color: var(--ink-dim); }

/* what the circles on the map mean */
.map-legend {
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 6px 9px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-dim);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
}
.map-legend div { display: flex; align-items: center; gap: 7px; white-space: nowrap; }
.map-legend .sw {
  flex: none;
  width: 18px;
  border-top: 2px solid var(--road);
}
.map-legend .sw--dash { border-top-style: dashed; }
.map-legend .sw--road { border-top-width: 4px; opacity: 0.6; }

/* --------------------------------------------------- mobile: stack them --- */

@media (max-width: 860px) {
  body {
    /* minmax(0, 1fr), not 1fr: a bare 1fr floors at the content's min-content
     * width, so one nowrap string anywhere can widen the page. */
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto 1fr;   /* the panel takes the slack — see above */
    grid-template-areas:
      "confirm"
      "top"
      "map"
      "panel";
  }
  .panel { border-right: 0; border-top: 1px solid var(--line); }
  #map {
    /* One column, no scrolling column to stick above — own height instead,
     * smaller than the phase-1 40vh so the totem sits near the fold. */
    position: static;
    height: 45vh;
    border-bottom: 1px solid var(--line);
  }

  /* The form is one column of rows at every width now (issue 0060), so the
   * second `.controls` grid this file used to carry is gone; what is left is
   * the touch sizing — 44px targets and >=16px input text so focusing one
   * doesn't zoom iOS. */
  .controls { gap: 9px 10px; }
  .brand__mark { width: 32px; height: 32px; }
  .brand__name { font-size: 21px; }
  .filters { gap: 10px 12px; }
  .filters .field--range, .filters .field--num { max-width: none; min-width: 0; width: auto; }
  .filters .field--brand { max-width: none; }
  .mode { height: 40px; font-size: 13px; }

  /* Three lines per result (issue 0060): name and price, then the address, then
   * the stats on a full-width line of their own. The two-line desktop shape
   * gives the mono stats column up to 240px (0047) of the ~300px a 390px screen
   * has, which truncated the brand and the address on *every* row — not just
   * the long-hours ones 0051 described. A third line costs ~14px of row height
   * and buys back the station's name. */
  .row {
    grid-template-columns: 18px minmax(0, 1fr) max-content 28px;
    grid-template-areas:
      "rank name  price fav"
      "rank where where fav"
      "rank meta  meta  fav";
  }
  .row__meta { justify-self: start; text-align: left; }
  /* One flowing stats line, hours included, rather than a fourth row. */
  .row__hours { display: inline; white-space: nowrap; }  /* the token wraps whole, never "L-D:" / "24H" */
  .row__hours::before { content: " · "; }

  /* Searching is a whole screen on a phone; the map is one tap away once you
   * have searched. `display: none` on an `auto` row collapses it to zero —
   * same trick as the global `[hidden]` rule above. */
  body[data-phase="search"] #map { display: none; }

  /* The trip and "Editar búsqueda" share the first line; the meta wraps under
   * them rather than being squeezed to an ellipsis on a 390px screen. */
  /* The map is not on screen while you are searching on a phone, so the copy
   * must not tell you to tap it. */
  body[data-phase="search"] .only-wide { display: none; }

  .tripbar { flex-wrap: wrap; row-gap: 1px; }
  .tripbar__edit { order: 1; }
  .tripbar__meta { order: 2; flex: 1 1 100%; }

  .field__input, .field__combo, .field__range, .btn { height: 44px; }
  /* .range--dual is a container; its absolutely-placed inputs ignore the 44px
   * above, so the height lives on the wrapper. */
  .range--dual { height: 44px; }
  .field__input, .field__combo .field__input, #fuel { font-size: 16px; }
  /* The locate button keeps a 44x44 touch target. An earlier pass shrank it to
   * 32px "so it stays a glyph, not a slab" — which is how it stopped working on
   * a phone: taps near the edge landed on the input behind it and opened the
   * keyboard instead of locating you. Looks are not worth a control you cannot
   * hit. */
  .btn--locate { min-height: 44px; min-width: 44px; }
  .btn--locate svg { width: 20px; height: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Profile (issue 0054) — a view over what localStorage already held. Native
 * popover, same reason as the filter help of 0020: light-dismiss, Esc and the
 * top layer come free. Sized as a sheet, and it scrolls inside itself rather
 * than growing the page, because the fills log can be long. */
.profile {
  /* A phase, not an overlay (0058). It lives inside `.topbar`, which already
   * spans both columns, so it needs no placement of its own — it had a
   * `grid-area: panel` that never applied because `.topbar` is not a grid
   * (issue 0060). Scrolls with the page like every other phase since 0042. */
  display: none;
  padding: 0 16px 24px;
  background: var(--surface);
  color: var(--ink);
  font: 400 13px/1.45 var(--sans);
}
body[data-phase="profile"] .profile { display: block; }
/* The profile owns the screen: no form behind it, no map beside it, and the
 * tripbar belongs to the results phase. */
body[data-phase="profile"] .controls,
body[data-phase="profile"] .panel,
body[data-phase="profile"] #map { display: none; }

.profile__head {
  position: sticky;             /* the close button stays reachable while scrolling */
  top: 0;
  max-width: 640px;
  margin: 0 auto;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 0 10px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.profile__title { margin: 0; font: 700 15px/1 var(--sans); }
.profile__close {
  min-width: 32px;
  min-height: 32px;
  border: 0;
  background: none;
  color: var(--ink-dim);
  font-size: 15px;
  cursor: pointer;
}
.profile__close:hover { color: var(--ink); }

.profile__sec { max-width: 640px; margin: 18px auto 0; }
.profile__h {
  margin: 0 0 8px;
  font: 700 11px/1 var(--sans);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.profile__count { font-weight: 400; }

.profile__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.profile__row--check { justify-content: flex-start; cursor: pointer; }
.profile__tank { width: 84px; text-align: right; }
.profile__nav { width: 150px; }
/* The two actions that left the levels card in 0060 — a tip and a reset, both
   things you do rarely and deliberately, next to the log they act on. */
.profile__acts { display: flex; align-items: center; gap: 14px; margin: 14px 0 0; flex-wrap: wrap; }
.profile__reset {
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-dim);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.profile__reset:hover { color: var(--price-high); }
.profile__reset:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }

.profile__list { list-style: none; margin: 8px 0 0; padding: 0; }
.profile__item {
  display: grid;
  /* The amount column is max-content and the name column is the one that gives:
   * a long station name must not push the delete button off the row (0047). */
  grid-template-columns: minmax(0, 1fr) max-content 32px;
  grid-template-areas:
    "main amt del"
    "sub  amt del";
  align-items: center;
  gap: 0 8px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.profile__item-main {
  grid-area: main;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
.profile__item-sub {
  grid-area: sub;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
  color: var(--ink-dim);
}
.profile__item-amt { grid-area: amt; font: 700 13px/1 var(--mono); color: var(--save); }
.profile__del {
  grid-area: del;
  min-width: 32px;
  min-height: 32px;
  border: 0;
  background: none;
  color: var(--ink-dim);
  cursor: pointer;
}
.profile__del:hover { color: var(--price-high); }

.profile__empty { margin: 6px 0 0; color: var(--ink-dim); }
.profile__sum { margin: 10px 0 0; }
.profile__sum b { font-family: var(--mono); color: var(--save); }


/* The one row that is on screen in every phase (issue 0058). */
.appbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* Same ceiling as .controls below, or on a wide screen the logo and the
   * profile button sit at the far edges of the viewport while everything else
   * lives in a 1160px column. */
  max-width: 1160px;
  margin-bottom: 10px;
}
.appbar__profile {
  display: grid;
  place-items: center;
  width: 44px;                  /* the tap-target floor this app holds to */
  height: 44px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-dim);
  cursor: pointer;
}
.appbar__profile:hover { color: var(--ink); border-color: var(--ink-dim); }
.appbar__profile:focus-visible { outline: 2px solid var(--road); outline-offset: 2px; }
body[data-phase="profile"] .appbar__profile { color: var(--road); border-color: var(--road); }
