/* The Lines Project — Phase 2 app chrome. Same palette as the map viewer so moving
   between the two does not feel like two products.

   RESPONSIVE (2026-09-03). Three widths matter and they are named once here:
     ≤ 640px  a phone — one column, the nav behind a menu button, tables
              stacked into rows (see `table.rsp`), maps sized to the viewport
              rather than to a fixed pixel height;
     ≤ 900px  a tablet or a narrow window — the editor's two columns become
              one (`.split`), the nav still collapses;
     ≤ 840px  the point at which ten tabs + brand + sign-out no longer fit
              on one line, which is why the nav collapses a little above the
              tablet width rather than at it.
   Everything else is fluid. The rules live at the END of this file under
   "responsive" so the desktop rules above stay readable on their own. */
:root {
  --bg:#0d0d0f; --panel:#141418; --fg:#f2f2f2; --dim:#8a8a92;
  --red:#ff2d2d; --line:#26262c; --ok:#3ddc84; --warn:#ffb020;
  /* The sticky topbar's height, which everything that parks under it
     (the connect editor's verdict panel) has to know. */
  --topbar-h:3.2rem;
}
* { box-sizing:border-box; }
html,body { margin:0; background:var(--bg); color:var(--fg);
  font:14px/1.55 ui-sans-serif,-apple-system,"Segoe UI",Roboto,sans-serif;
  /* Native form controls and scrollbars take the dark theme too. */
  color-scheme:dark;
  /* iOS Safari inflates text on landscape rotation unless told not to. */
  -webkit-text-size-adjust:100%; }
a { color:var(--fg); text-decoration:none; }
a:hover { color:var(--red); }
/* The title of a row — a ride on the activities page, a web on the stats
   page, a journey — reads as something you can click (Greg, 2026-09-03: the
   white titles "did not look like they can be clicked"). A tinted colour
   and a faint underline that fills in on hover; everything else in a row
   stays plain, so the link is the one thing that looks like one. */
td.t a, .actrow > span > a, .dayrow > span > a {
  color:#ff7a7a; text-decoration:underline; text-decoration-thickness:1px;
  text-decoration-color:rgba(255,45,45,.35); text-underline-offset:3px; }
td.t a:hover, .actrow > span > a:hover, .dayrow > span > a:hover {
  color:var(--red); text-decoration-color:var(--red); }
h1 { font-size:1.35rem; margin:0 0 .35rem; letter-spacing:-.01em; }
h2 { font-size:1rem; margin:1.4rem 0 .5rem; color:var(--dim);
  text-transform:uppercase; letter-spacing:.07em; font-weight:600; }
.dim { color:var(--dim); }
.error { color:var(--red); }
.ok { color:var(--ok); }
.warn { color:var(--warn); }
code, .mono { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:.85em; }

/* ---- chrome ---- */
.topbar { display:flex; align-items:center; gap:1.5rem; padding:.65rem 1.1rem;
  min-height:var(--topbar-h);
  border-bottom:1px solid var(--line); background:var(--panel); position:sticky; top:0; z-index:5; }
.brand .mark { color:var(--red); font-weight:700; letter-spacing:.12em; font-size:.9rem; }
.topbar nav { display:flex; gap:1.1rem; }
.topbar nav a { color:var(--dim); font-size:.9rem; }
.topbar nav a.on, .topbar nav a:hover { color:var(--fg); }
.topbar .right { margin-left:auto; display:flex; align-items:center; gap:.8rem; }
.who { color:var(--dim); font-size:.85rem; }
/* The menu button. Three bars drawn in CSS so the nav test's `>Map<` ordering
   check sees no extra label text; shown only once the nav collapses (see the
   responsive section). `aria-expanded` is the state, `.topbar.open` the paint. */
.navtoggle { display:none; background:transparent; border:1px solid var(--line);
  border-radius:6px; width:2.6rem; height:2.4rem; padding:0; margin-left:auto;
  position:relative; cursor:pointer; flex:0 0 auto; }
.navtoggle span, .navtoggle span::before, .navtoggle span::after {
  content:""; display:block; position:absolute; left:.7rem; right:.7rem;
  height:2px; background:var(--fg); border-radius:1px; transition:transform .15s, opacity .15s; }
.navtoggle span { top:calc(50% - 1px); }
.navtoggle span::before { left:0; right:0; top:-7px; }
.navtoggle span::after { left:0; right:0; top:7px; }
.topbar.open .navtoggle span { background:transparent; }
.topbar.open .navtoggle span::before { transform:translateY(7px) rotate(45deg); }
.topbar.open .navtoggle span::after { transform:translateY(-7px) rotate(-45deg); }
button, .btn { background:var(--red); color:#fff; border:0; border-radius:6px;
  padding:.45rem .85rem; font:inherit; cursor:pointer; }
button:hover, .btn:hover { filter:brightness(1.12); color:#fff; }
button.ghost, .btn.ghost { background:transparent; border:1px solid var(--line); color:var(--fg); }
button.link { background:none; border:0; color:var(--dim); padding:0; }
button.link:hover { color:var(--red); }
button.danger { background:#3a1416; border:1px solid #6b2226; color:#ff9a9a; }

main { padding:1.2rem 1.1rem 4rem; max-width:1180px; margin:0 auto; }
.card { background:var(--panel); border:1px solid var(--line); border-radius:10px;
  padding:1rem 1.1rem; margin-bottom:1rem; }
.narrow { max-width:560px; }
.centered { display:grid; place-items:center; min-height:100vh; }
.login { width:min(360px,92vw); display:flex; flex-direction:column; gap:.35rem; }
.login h1 { color:var(--red); letter-spacing:.14em; }
.login label { color:var(--dim); font-size:.75rem; text-transform:uppercase;
  letter-spacing:.06em; margin-top:.6rem; }
.login button { margin-top:1rem; }

input[type=text], input[type=password], input[type=number], input[type=search],
select, textarea {
  background:var(--bg); color:var(--fg); border:1px solid var(--line);
  border-radius:6px; padding:.42rem .55rem; font:inherit; width:100%; }
input[type=file] { color:var(--dim); }
/* One wrapper for anything wider than a phone that must not widen the page:
   a table with too many columns to stack, a <pre>, a chart. The PAGE never
   scrolls sideways; the wrapper does. */
.tablewrap { overflow-x:auto; -webkit-overflow-scrolling:touch; max-width:100%; }
/* A panel that appears mid-gesture (the trim and connect verdicts). On a
   desktop it sits where the template puts it; on anything narrower than the
   two-column editor it becomes a bottom sheet, because the sidebar it lived in
   has moved below the map and a verdict that renders below the fold is a
   question with no visible answer (Greg, 2026-08-31, on a laptop — a phone is
   that problem at every width). */
.gesturepanel { margin:0 0 .8rem; border-color:var(--ok); background:var(--panel); }
/* `.parked`: on a desktop the connect verdict stays beside the map as the
   sidebar scrolls, under the sticky topbar. In CSS rather than an inline
   style so the bottom-sheet rule below can beat it — an inline `position`
   beats any stylesheet, which is how the sheet never appeared on a phone. */
.gesturepanel.parked { position:sticky; top:calc(var(--topbar-h) + .4rem); z-index:4; }

.messages { list-style:none; margin:0; padding:.6rem 1.1rem; max-width:1180px;
  margin-inline:auto; display:flex; flex-direction:column; gap:.4rem; }
.messages li { border-left:3px solid var(--red); background:var(--panel);
  padding:.5rem .8rem; border-radius:0 6px 6px 0; }
.messages li.success { border-color:var(--ok); }
.messages li.warning { border-color:var(--warn); }

/* ---- stats + filters ---- */
.stats { display:flex; flex-wrap:wrap; gap:1.6rem; margin:.4rem 0 1rem; }
.stat b { display:block; font-size:1.5rem; line-height:1.1; }
.stat span { color:var(--dim); font-size:.75rem; text-transform:uppercase;
  letter-spacing:.06em; }
.filters { display:flex; flex-wrap:wrap; gap:.6rem; align-items:end; }
.filters .f { display:flex; flex-direction:column; gap:.2rem; }
.filters label { color:var(--dim); font-size:.7rem; text-transform:uppercase;
  letter-spacing:.06em; }
.filters select, .filters input { width:auto; min-width:9rem; }
.chips { display:flex; gap:.5rem; flex-wrap:wrap; margin:.7rem 0 0; }
.chip { border:1px solid var(--line); border-radius:999px; padding:.2rem .7rem;
  font-size:.8rem; color:var(--dim); }
.chip.on { border-color:var(--red); color:var(--fg); }

/* ---- tables ---- */
table { width:100%; border-collapse:collapse; font-size:.9rem; }
th { text-align:left; color:var(--dim); font-weight:600; font-size:.72rem;
  text-transform:uppercase; letter-spacing:.06em; padding:.5rem .6rem;
  border-bottom:1px solid var(--line); white-space:nowrap; }
td { padding:.5rem .6rem; border-bottom:1px solid var(--line); }
tr:hover td { background:#17171c; }
td.num, th.num { text-align:right; font-variant-numeric:tabular-nums; }
.badge { font-size:.68rem; text-transform:uppercase; letter-spacing:.06em;
  border-radius:4px; padding:.1rem .4rem; border:1px solid var(--line); color:var(--dim); }
.badge.suspect { border-color:var(--warn); color:var(--warn); }
.badge.edited { border-color:var(--ok); color:var(--ok); }
.badge.hidden { border-color:var(--red); color:var(--red); }
.badge.drift { border-color:var(--red); color:var(--red); }
/* A finding that has already been answered but whose answer needs a rebuild
   to land. Filled rather than outlined: it has to be visible at a glance down
   a 139-row queue, which is the whole point of it. */
.badge.queued { border-color:var(--warn); background:var(--warn); color:#141418;
  font-weight:600; }
.badge.connected { border-color:var(--ok); color:var(--ok); }
.pager { display:flex; gap:.8rem; align-items:center; margin-top:.9rem; color:var(--dim); }

/* ---- activity editor ---- */
.split { display:grid; grid-template-columns:minmax(0,1fr) 22rem; gap:1rem; }
/* A grid item's min-width is `auto` — its content's width — so a sideways-
   scrolling strip inside the column (the photo carousel) widened the whole
   page on a phone instead of scrolling. Seen 2026-09-03. */
.split > * { min-width:0; }
@media (max-width:900px) { .split { grid-template-columns:1fr; } }
#editmap { height:min(60vh,520px); border-radius:10px; border:1px solid var(--line); }
/* Every embedded map. `dvh` where the browser has it, so the iOS address bar
   collapsing does not leave a strip of page under the map; the `vh` line is
   the fallback for older engines. */
#editmap, #jmap, #gapmap, #filtermap { height:min(60vh, 520px); }
#editmap, #jmap, #gapmap, #filtermap { height:min(60dvh, 520px); }
.kv { display:grid; grid-template-columns:auto 1fr; gap:.25rem .8rem; font-size:.9rem; }
.kv dt { color:var(--dim); }
.kv dd { margin:0; }
.editrow { display:flex; gap:.4rem; align-items:center; margin:.35rem 0; }
.editrow input { width:6rem; }
.editlist li { margin:.3rem 0; display:flex; gap:.6rem; align-items:center; }
fieldset { border:1px solid var(--line); border-radius:8px; margin:0 0 .8rem;
  padding:.7rem .8rem; }
legend { color:var(--dim); font-size:.72rem; text-transform:uppercase;
  letter-spacing:.06em; padding:0 .3rem; }

/* ---- drop zone ---- */
.drop { border:2px dashed var(--line); border-radius:12px; padding:2.4rem 1rem;
  text-align:center; color:var(--dim); transition:border-color .15s,background .15s; }
.drop.over { border-color:var(--red); background:#1a1013; color:var(--fg); }
.filelist { list-style:none; padding:0; margin:.8rem 0 0; font-size:.9rem; }
.filelist li { display:flex; justify-content:space-between; gap:1rem;
  border-bottom:1px solid var(--line); padding:.3rem 0; }

/* ---- job log ---- */
pre.log { background:#08080a; border:1px solid var(--line); border-radius:8px;
  padding:.8rem; max-height:60vh; overflow:auto; font-size:.8rem; line-height:1.45;
  white-space:pre-wrap; word-break:break-word; }
.status-dot { display:inline-block; width:.55rem; height:.55rem; border-radius:50%;
  background:var(--dim); margin-right:.4rem; }
.status-dot.running { background:var(--warn); animation:pulse 1.1s infinite; }
.status-dot.done { background:var(--ok); }
.status-dot.failed { background:var(--red); }
@keyframes pulse { 50% { opacity:.35; } }

/* ---- Phase 3: stats, network pages, review queue ---- */
.netmap { height:420px; border-radius:8px; overflow:hidden;
  border:1px solid var(--line); }
.netmap { height:min(420px, 55dvh); }
.honesty { margin:.6rem 0 0; font-size:.85rem; border-top:1px solid var(--line);
  padding-top:.6rem; }
.decide { display:flex; gap:.4rem; flex-wrap:wrap; align-items:center; }
.decide button, .decide .btn { font-size:.78rem; padding:.3rem .55rem;
  white-space:nowrap; }
/* "Connect the lines" is an anchor, not a submit — it opens the editor rather
   than recording a judgment. Sized to match the button beside it so the pair
   still reads as one choice. */
.decide .btn { display:inline-block; text-decoration:none; line-height:1.35; }
#growthchart svg, #yearchart svg { display:block; }
/* The review queue's gap thumbnails (views._gap_thumbnail_svg). An SVG of the
   two recorded line-ends and the gap between them — sized here rather than in
   the file so one drawing serves the table, the phone and a 3x screen. */
.gapthumb { display:block; width:240px; max-width:100%; height:auto;
  border:1px solid var(--line); border-radius:6px; background:var(--bg); }
a:hover > .gapthumb { border-color:var(--red); }
details summary { cursor:pointer; margin:.4rem 0; }

/* ---- the basemap switcher on embedded maps (core/basemaps.js) ---- */
/* MapLibre's own .maplibregl-ctrl-group is a white pill; the app is dark, and
   a white pill floating on a dark map reads as a rendering fault rather than a
   control. This restyles the wrapper we add rather than MapLibre's own, so the
   zoom buttons keep the look everyone already recognises.

   EVERY selector below is doubled up as `.maplibregl-ctrl-group.rl-basemap-ctrl`
   on purpose, and removing that second class breaks the control. Both files
   style the same element with one class each, so the winner is decided by load
   order — and every template loads app.css from base.html's <head> and then
   maplibre-gl.css from {% block head %} AFTER it. MapLibre's `background:#fff`
   therefore won, the wrapper painted white, and the select kept its own
   near-white `color`: white text on a white pill. Greg reported it as simply
   unreadable on 2026-08-28, which is exactly what it was. Two classes beat
   one whatever the order, so this no longer depends on which file loads last.

   The select is also painted explicitly rather than left transparent: a
   transparent select is only ever as readable as whatever is behind it, which
   is the bug we just came from. `color-scheme:dark` is what makes the native
   dropdown that opens on click dark too — `option` colours are honoured on
   some platforms and quietly ignored on others, and the scheme hint is not. */
.maplibregl-ctrl-group.rl-basemap-ctrl { background:rgba(13,13,15,.94);
  border:1px solid var(--line); border-radius:6px;
  box-shadow:0 1px 4px rgba(0,0,0,.5); overflow:hidden; }
.maplibregl-ctrl-group.rl-basemap-ctrl select {
  width:auto; min-width:9rem; background:transparent; color:var(--fg);
  border:0; border-radius:6px; font:inherit; font-size:.8rem;
  padding:.35rem 1.6rem .35rem .55rem; cursor:pointer; color-scheme:dark;
  /* The native chevron is drawn in the platform's own colour, which on a dark
     pill is a dark chevron on a dark ground. Ours is drawn in --dim instead. */
  -webkit-appearance:none; appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238a8a92' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right .5rem center;
  background-size:.6rem .38rem; }
.maplibregl-ctrl-group.rl-basemap-ctrl select:hover { color:#fff; }
.maplibregl-ctrl-group.rl-basemap-ctrl select:focus-visible {
  outline:2px solid var(--red); outline-offset:-2px; }
/* The open dropdown is a native menu, not part of the pill — it needs its own
   colours or it inherits the page's and goes white-on-white in its turn. */
.maplibregl-ctrl-group.rl-basemap-ctrl select option {
  background:var(--panel); color:var(--fg); }

/* ---- upload page ---- */
/* The default .card padding is tuned for tables; the uploader is a short
   vertical stack of unlike things (prose, a drop target, a submit row) and at
   table density they ran together into one grey block. Greg flagged exactly
   that. Everything below is breathing room, nothing else. */
.uploadcard { padding:1.4rem 1.4rem 1.5rem; }
.uploadcard h1 { margin-bottom:.6rem; }
.uploadcard .lede { margin:0 0 1.3rem; max-width:62ch; line-height:1.7; }
.uploadcard .drop { padding:2.8rem 1rem; }
.uploadform { margin:0; }
.uploadrow { display:flex; gap:1.4rem; align-items:center; flex-wrap:wrap;
  margin-top:1.4rem; }
.uploadrow .rebuildtoggle { display:flex; gap:.5rem; align-items:center; }
.uploadrow .rebuildtoggle input { width:auto; }


/* =========================================================================
   Responsive. Read the note at the top of this file for the three widths.
   The rule for everything below: NOTHING here changes the desktop layout.
   ========================================================================= */

/* ---- touch targets and native control sizing, on any coarse pointer ------
   Keyed on the pointer, not the width: a tablet in landscape is wider than a
   laptop window and still driven by a thumb. 44px is the Apple HIG minimum;
   these land at ~42px, with the text buttons ("undo", "not a journey") given
   height by padding rather than a box, so they stay inline. */
@media (pointer:coarse) {
  button, .btn { min-height:2.6rem; padding-top:.55rem; padding-bottom:.55rem; }
  button.link { min-height:0; padding:.45rem .25rem; }
  .decide button, .decide .btn { padding:.5rem .7rem; }
  input[type=checkbox], input[type=radio] { width:1.25rem; height:1.25rem; }
  .chip { padding:.4rem .85rem; }
  .topbar nav a { padding:.3rem 0; }
  /* MapLibre's zoom buttons are 29px squares — fine for a mouse, a miss for a
     thumb. Only the buttons; the basemap <select> beside them sizes itself. */
  .maplibregl-ctrl-group button { width:42px; height:42px; }
  .maplibregl-ctrl-group.rl-basemap-ctrl select { padding-top:.6rem; padding-bottom:.6rem; }
  .maplibregl-popup-close-button { width:2rem; height:2rem; font-size:1.3rem; }
  /* A hover highlight on a touch screen sticks to the last tapped row. */
  tr:hover td { background:transparent; }
}

/* A popup wider than the screen cannot be dismissed — its close button is off
   the edge. Cap it below the viewport on every map in the app. */
.maplibregl-popup { max-width:min(300px, calc(100vw - 2rem)) !important; }
.maplibregl-popup-content { max-width:100%; word-break:break-word; }

/* ---- the nav collapses -------------------------------------------------- */
@media (max-width:840px) {
  .topbar { gap:.8rem; padding:.5rem .8rem; flex-wrap:wrap; }
  /* Sign-out hugs the menu button on the right; only one of the two carries
     the auto margin, or the free space is split between them and "Sign out"
     floats in the middle of the bar. */
  .navtoggle { display:block; order:3; margin-left:0; }
  .topbar .right { order:2; margin-left:auto; }
  /* The menu: a full-width list under the bar, one page per row, each row a
     whole 44px thumb target. `position:absolute` under a sticky bar means it
     scrolls with the bar, not the page. */
  .topbar nav { display:none; position:absolute; top:100%; left:0; right:0;
    flex-direction:column; gap:0; background:var(--panel);
    border-bottom:1px solid var(--line); box-shadow:0 12px 24px rgba(0,0,0,.45);
    padding:.25rem 0 .4rem;
    max-height:calc(100dvh - var(--topbar-h)); overflow-y:auto; }
  .topbar.open nav { display:flex; }
  .topbar nav a { font-size:1rem; padding:.7rem 1.1rem; min-height:2.75rem;
    display:flex; align-items:center; border-left:3px solid transparent; }
  .topbar nav a.on { border-left-color:var(--red); }
  .topbar nav a:active { background:#1c1c22; }
}

/* ---- tablet and below: the editor is one column (kept from before) -------
   `.split` already collapses at 900px above. What changes here is the panel
   that appears mid-gesture. */
@media (max-width:900px) {
  .gesturepanel, .gesturepanel.parked { position:fixed; left:.6rem; right:.6rem;
    bottom:calc(.6rem + env(safe-area-inset-bottom, 0px)); top:auto;
    z-index:30; margin:0; box-shadow:0 8px 28px rgba(0,0,0,.6); }
  .gesturepanel[hidden] { display:none; }
}

/* ---- phones ------------------------------------------------------------- */
@media (max-width:640px) {
  html,body { font-size:14px; }
  /* The wordmark went from "RED LINES" (9 characters) to "THE LINES PROJECT"
     (17) when the project was renamed on 2026-09-04 — nearly twice the width,
     in a bar that already folds its nav at 840px because it could not fit ten
     tabs. Tightened rather than abbreviated: "TLP" is not a name anyone would
     recognise, and shortening it to "LINES" drops the half being renamed to.
     Measured at 375px, this keeps the mark, Sign out and the menu button on
     one row. */
  .brand .mark { font-size:.78rem; letter-spacing:.05em; }
  main { padding:.85rem .7rem 3.5rem; }
  .card { padding:.8rem .8rem; border-radius:8px; }
  h1 { font-size:1.2rem; }
  h2 { margin-top:1.1rem; }
  .messages { padding:.5rem .7rem; }
  .who { display:none; }

  /* iOS Safari zooms the whole page into any control whose text is under
     16px, and then leaves it zoomed. 16px, everywhere a phone can focus. */
  input[type=text], input[type=password], input[type=number], input[type=search],
  input[type=email], select, textarea { font-size:16px; padding:.5rem .6rem; }
  .filters select, .filters input { font-size:16px; }

  /* Headline numbers: a grid so a narrow screen gets two per row instead of
     a ragged wrap, and smaller so 18,434 does not break mid-number. */
  .stats { display:grid; grid-template-columns:repeat(auto-fill, minmax(7.6rem, 1fr));
    gap:.8rem 1rem; }
  .stat b { font-size:1.25rem; overflow-wrap:anywhere; }

  /* Filters: two per row, full-width controls, the buttons on their own row. */
  .filters { gap:.5rem; }
  .filters .f { flex:1 1 calc(50% - .3rem); min-width:0; }
  .filters select, .filters input { width:100%; min-width:0; }
  .filters > button, .filters > .btn { flex:1 1 auto; text-align:center; }

  /* Maps: sized to the phone, never to a laptop's 520px. min-height keeps a
     landscape phone from squashing the map to a ribbon. */
  #editmap, #jmap, #gapmap, #filtermap { height:52dvh; min-height:300px; max-height:none; }
  .netmap { height:46dvh; min-height:260px; }

  .kv { grid-template-columns:auto 1fr; font-size:.88rem; }
  .editrow { flex-wrap:wrap; }
  .editrow input { width:7rem; }
  pre.log { font-size:.74rem; padding:.6rem; max-height:50dvh; }
  .uploadcard { padding:1rem .9rem 1.1rem; }
  .uploadcard .drop { padding:1.8rem .8rem; }
  .uploadrow { gap:.8rem; }
  .pager { flex-wrap:wrap; }
  .login { width:min(360px, 100%); }
  .centered { min-height:100dvh; padding:1rem; }
}

/* ---- tables ------------------------------------------------------------ */
/* Two treatments, and every table in the app carries one or the other —
   `MobileTableTests` reads the templates and fails on a table with neither.

   `table.rsp` STACKS: the header row disappears and each row becomes a block
   whose cells flow inline with their heading in front of them, taken from the
   cell's `data-l`. A cell marked `.t` is the title (the ride, the journey)
   and takes the whole first line; `.act` holds the buttons and takes the
   last. Cells with no `data-l` (a title, a badge) print bare. This is for
   the lists — activities, journeys, the review queues — where a row is one
   thing and its columns are its facts.

   Anything else sits inside `.tablewrap` and scrolls sideways within its
   card: the network-build ledger, whose thirteen columns are a spreadsheet
   and read as one. */
@media (max-width:700px) {
  table.rsp { display:block; }
  table.rsp thead { display:none; }
  table.rsp tbody { display:block; }
  table.rsp tr { display:flex; flex-wrap:wrap; align-items:baseline;
    gap:.15rem .9rem; padding:.6rem 0; border-bottom:1px solid var(--line); }
  table.rsp tr:last-child { border-bottom:0; }
  table.rsp td { display:block; padding:0; border:0; text-align:left;
    min-width:0; overflow-wrap:anywhere; }
  table.rsp td.num { text-align:left; }
  table.rsp td.t { flex-basis:100%; font-size:1rem; }
  /* A merge checkbox shares the title's line rather than sitting alone above
     it — the journeys page is the one place a row starts with a control. */
  table.rsp td.pick { flex:0 0 auto; align-self:center; }
  table.rsp td.pick + td.t { flex:1 1 0; min-width:0; }
  table.rsp td.act { flex-basis:100%; margin-top:.35rem; }
  /* The gap thumbnail gets its own line rather than flowing in beside a
     number — it is 240px wide and would push every other cell off the row. */
  table.rsp td.thumb { flex-basis:100%; margin:.35rem 0 .1rem; }
  table.rsp td.act .decide { gap:.5rem; }
  table.rsp td:empty { display:none; }
  table.rsp td[data-l]::before { content:attr(data-l); color:var(--dim);
    font-size:.66rem; text-transform:uppercase; letter-spacing:.06em;
    margin-right:.3rem; white-space:nowrap; }
  /* A row that is only hover-highlighted on a desktop has nothing to say
     about a finger. */
  table.rsp tr:hover td { background:transparent; }
  /* The sort <select> the gaps page's script adds beside a sortable table
     whose headers are now hidden. */
  .rsp-sort { display:flex; gap:.5rem; align-items:center; margin:.2rem 0 .6rem;
    font-size:.8rem; color:var(--dim); }
  .rsp-sort select { width:auto; flex:1; }
  /* The safety net for a table that carries neither class: it scrolls inside
     its own box rather than pushing the page wider than the phone. */
  table:not(.rsp) { display:block; overflow-x:auto; -webkit-overflow-scrolling:touch; }
  table:not(.rsp) th, table:not(.rsp) td { white-space:nowrap; }
  .tablewrap table:not(.rsp) { display:table; overflow:visible; }
}
.rsp-sort { display:none; }
@media (max-width:700px) { .rsp-sort { display:flex; } }
